Skip to content

Commit 1deba52

Browse files
committed
Initial commit
A dotfile management tool that creates intelligent symlinks between configuration files in a git repository and their expected locations. Features: - Directory-based operation (like git, npm, make) - Simple .cfgman.json configuration format - Built-in gitignore-style pattern matching - Flexible link mappings with file/directory-level control - Safety features: dry-run mode, confirmations, cross-repo protection - Zero external dependencies See CHANGELOG.md for full feature list.
0 parents  commit 1deba52

26 files changed

+5981
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Build output
2+
bin/
3+
4+
# Test artifacts
5+
*.test
6+
coverage.out
7+
coverage.html
8+
9+
# OS files
10+
.DS_Store

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Changelog
2+
3+
All notable changes to cfgman will be documented in this file.
4+
5+
## [1.0.0-beta.1] - 2025-06-24
6+
7+
Initial beta release of cfgman.
8+
9+
- **Directory-based operation** - Works from repository directory (like git, npm, make)
10+
- **Simple configuration format** - Single `.cfgman.json` file with link mappings
11+
- **Built-in ignore patterns** - Gitignore-style pattern matching without git dependency
12+
- **Flexible link mappings** - Map any source directory to any target location
13+
- **Smart linking strategies** - Choose between file-level or directory-level linking
14+
- **Safety features**:
15+
- Dry-run mode for all operations
16+
- Confirmation prompts for destructive actions
17+
- Cross-repository symlink protection
18+
- **Core commands**:
19+
- `init` - Create minimal configuration template
20+
- `status` - Show all managed symlinks with their state
21+
- `adopt` - Move existing files into repository management
22+
- `orphan` - Remove files from management (restore to original location)
23+
- `create-links` - Create symlinks based on configuration
24+
- `remove-links` - Remove all managed symlinks
25+
- `prune-links` - Remove broken symlinks
26+
- **Performance** - Concurrent operations for status checking
27+
- **Zero dependencies** - Pure Go implementation using only standard library

CONTRIBUTING.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Contributing to cfgman
2+
3+
cfgman is an opinionated personal project designed to meet specific workflow needs.
4+
5+
While contributions may be considered, please note:
6+
7+
- This project reflects personal preferences and workflows
8+
- There is no guarantee that contributions will be accepted
9+
- Features and changes are driven by the maintainer's needs
10+
- The project may not follow conventional open source practices
11+
12+
If you find cfgman useful, you're welcome to:
13+
14+
- Fork it for your own needs
15+
- Report bugs via GitHub issues
16+
- Share ideas, though implementation is at maintainer's discretion
17+
18+
Thank you for your understanding.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Christopher Plain
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.PHONY: help build clean test install test-coverage fmt lint check
2+
3+
# Default target - show help
4+
help:
5+
@echo "cfgman - Configuration Management Tool"
6+
@echo ""
7+
@echo "Usage: make [target]"
8+
@echo ""
9+
@echo "Variables:"
10+
@echo " PREFIX=$(PREFIX) Installation prefix (override with PREFIX=/path)"
11+
@echo " BINDIR=$(BINDIR) Binary directory (override with BINDIR=/path)"
12+
@echo ""
13+
@echo "Targets:"
14+
@echo " help Show this help message"
15+
@echo " build Build the cfgman binary"
16+
@echo " install Build and install cfgman to BINDIR"
17+
@echo " clean Remove build artifacts and installed binary"
18+
@echo " test Run all tests with verbose output"
19+
@echo " test-coverage Run tests with coverage report (generates HTML)"
20+
@echo " fmt Format all Go code"
21+
@echo " lint Run go vet for static analysis"
22+
@echo " check Run fmt, test, and lint in sequence"
23+
24+
# Installation prefix
25+
PREFIX ?= /usr/local
26+
BINDIR ?= $(PREFIX)/bin
27+
28+
# Build the cfgman binary
29+
build:
30+
mkdir -p bin
31+
@# Get version from git or use "dev" as fallback
32+
@VERSION=$$(git describe --tags --always 2>/dev/null || echo "dev"); \
33+
COMMIT=$$(git rev-parse --short HEAD 2>/dev/null || echo "unknown"); \
34+
DATE=$$(date -u '+%Y-%m-%d %H:%M:%S UTC' 2>/dev/null || date); \
35+
echo "Building cfgman version $$VERSION ($$COMMIT)..."; \
36+
go build -ldflags "-X 'main.version=$$VERSION' -X 'main.commit=$$COMMIT' -X 'main.date=$$DATE'" -o bin/cfgman cmd/cfgman/main.go
37+
38+
# Install cfgman to BINDIR
39+
install: build
40+
@# Check if we can write to the target directory
41+
@if [ -d "$(BINDIR)" ]; then \
42+
if [ ! -w "$(BINDIR)" ]; then \
43+
echo "Error: Cannot write to $(BINDIR)"; \
44+
echo "Try one of the following:"; \
45+
echo " sudo make install"; \
46+
echo " make install PREFIX=~/.local"; \
47+
exit 1; \
48+
fi \
49+
else \
50+
parent_dir=$$(dirname "$(BINDIR)"); \
51+
if [ ! -w "$$parent_dir" ]; then \
52+
echo "Error: Cannot write to $$parent_dir"; \
53+
echo "Try one of the following:"; \
54+
echo " sudo make install"; \
55+
echo " make install PREFIX=~/.local"; \
56+
exit 1; \
57+
fi \
58+
fi
59+
mkdir -p $(BINDIR)
60+
cp bin/cfgman $(BINDIR)/cfgman
61+
chmod +x $(BINDIR)/cfgman
62+
63+
# Clean build artifacts
64+
clean:
65+
rm -rf bin/
66+
rm -f coverage.out coverage.html
67+
rm -f $(BINDIR)/cfgman
68+
69+
# Run tests
70+
test:
71+
go test -v ./...
72+
73+
# Run tests with coverage
74+
test-coverage:
75+
go test -coverprofile=coverage.out ./...
76+
go tool cover -html=coverage.out -o coverage.html
77+
78+
# Format code
79+
fmt:
80+
@if command -v goimports >/dev/null 2>&1; then \
81+
echo "Running goimports..."; \
82+
goimports -w .; \
83+
else \
84+
echo "goimports not found, using gofmt..."; \
85+
gofmt -w .; \
86+
fi
87+
88+
# Run static analysis with go vet
89+
lint:
90+
@echo "Running go vet..."
91+
go vet ./...
92+
93+
# Run all checks
94+
check: fmt test lint

README.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# cfgman
2+
3+
A fast, reliable dotfile management tool. Manage your configuration files across machines using intelligent symlinks.
4+
5+
## Key Features
6+
7+
- **Single binary** - No dependencies required (git integration optional)
8+
- **File-level and directory-level linking** - Links files by default and directories by configuration
9+
- **Flexible configuration** - Support for public and private config repositories
10+
- **Safety first** - Dry-run mode and clear status reporting
11+
- **Bidirectional operations** - Adopt existing files or orphan managed ones
12+
13+
## Installation
14+
15+
```bash
16+
# Build from source
17+
git clone https://github.com/cpplain/cfgman.git
18+
cd cfgman
19+
make install
20+
```
21+
22+
## Quick Start
23+
24+
```bash
25+
# 1. Set up your config repository
26+
mkdir -p ~/dotfiles/{home,private/home}
27+
cd ~/dotfiles
28+
git init
29+
30+
# 2. Initialize cfgman in your repository
31+
cfgman init
32+
# Edit .cfgman.json to configure your mappings
33+
34+
# 3. Adopt existing configs
35+
cfgman adopt ~/.gitconfig home
36+
cfgman adopt ~/.ssh/config private/home
37+
38+
# 4. Create symlinks on new machines
39+
cfgman create-links
40+
```
41+
42+
## Configuration
43+
44+
cfgman uses a single configuration file `.cfgman.json` in your dotfiles repository that controls linking behavior.
45+
46+
**Important**: cfgman must be run from within the repository directory containing `.cfgman.json`.
47+
48+
### Configuration File (.cfgman.json)
49+
50+
Example configuration (after editing the template created by `cfgman init`):
51+
52+
```json
53+
{
54+
"ignore_patterns": [".DS_Store", "*.swp", "*~", "Thumbs.db"],
55+
"link_mappings": [
56+
{
57+
"source": "home",
58+
"target": "~/",
59+
"link_as_directory": [".config/nvim"]
60+
},
61+
{
62+
"source": "private/home",
63+
"target": "~/",
64+
"link_as_directory": [".ssh"]
65+
}
66+
]
67+
}
68+
```
69+
70+
- **ignore_patterns**: Gitignore-style patterns for files to never link
71+
- **source**: Directory in your repo containing configs
72+
- **target**: Where symlinks are created (usually `~/`)
73+
- **link_as_directory**: Directories to link as complete units instead of individual files
74+
75+
## Commands
76+
77+
### Configuration Commands
78+
79+
```bash
80+
cfgman init # Create a minimal .cfgman.json template
81+
```
82+
83+
### Basic Commands
84+
85+
```bash
86+
cfgman status # Show all managed symlinks
87+
cfgman create-links [--dry-run] # Create symlinks from repo to home
88+
cfgman remove-links [--dry-run] # Remove all managed symlinks
89+
cfgman prune-links # Remove broken symlinks
90+
```
91+
92+
### File Operations
93+
94+
```bash
95+
# Adopt a file/directory into your repository
96+
cfgman adopt <path> [source_dir] [--dry-run]
97+
cfgman adopt ~/.gitconfig home # Adopt to public repo
98+
cfgman adopt ~/.ssh/config private/home # Adopt to private repo
99+
100+
# Orphan a file/directory (remove from management)
101+
cfgman orphan <path> [--dry-run]
102+
cfgman orphan ~/.config/oldapp # Stop managing a config
103+
```
104+
105+
### Global Options
106+
107+
```bash
108+
cfgman --version # Show version
109+
cfgman help [command] # Get help
110+
```
111+
112+
## How It Works
113+
114+
### File-Level and Directory-Level Linking
115+
116+
By default, cfgman links individual files rather than entire directories. When you need to link entire directories (like `.config/nvim`), add them to `link_as_directory` in your `.cfgman.json`.
117+
118+
### Ignore Patterns
119+
120+
cfgman supports gitignore-style patterns in the `ignore_patterns` field to exclude files from linking. The `cfgman init` command creates an empty `ignore_patterns` array that you can populate with patterns like `.DS_Store`, `*.swp`, and other files you want to exclude.
121+
122+
## Common Workflows
123+
124+
### Setting Up a New Machine
125+
126+
```bash
127+
# 1. Clone your dotfiles
128+
git clone https://github.com/you/dotfiles.git ~/dotfiles
129+
cd ~/dotfiles && git submodule update --init # If using private submodule
130+
131+
# 2. Create links (must be run from repository directory)
132+
cd ~/dotfiles
133+
cfgman create-links
134+
```
135+
136+
### Adding New Configurations
137+
138+
```bash
139+
# Adopt a new app config (from repository directory)
140+
cd ~/dotfiles
141+
cfgman adopt ~/.config/newapp home
142+
143+
# If it's a directory with plugins/state, link as directory
144+
# You'll be prompted during adoption, or edit .cfgman.json manually
145+
```
146+
147+
### Managing Sensitive Files
148+
149+
```bash
150+
# Keep work/private configs separate
151+
cfgman adopt ~/.ssh/config private/home
152+
cfgman adopt ~/.config/work-vpn.conf private/home
153+
```
154+
155+
## Tips
156+
157+
- Always run cfgman commands from your repository directory
158+
- Use `--dry-run` to preview changes before making them
159+
- Keep sensitive configs in a separate private directory or git submodule
160+
- Run `cfgman status` regularly to check for broken links
161+
- Use `ignore_patterns` in `.cfgman.json` to exclude unwanted files
162+
- Consider separate source directories for different contexts (work, personal)

0 commit comments

Comments
 (0)