7.0 KiB
dotctl — Fresh Machine Bootstrap Roadmap
Goal
A single command on a fresh machine should clone a dotfiles repo and fully apply it:
dotctl apply https://github.com/user/dotfiles.git
No manual git clone. No rebuilding from source. No multi-step dance.
Current State Summary
| Capability | Status |
|---|---|
init / add / link core loop |
✅ Working |
| Symlink creation | ✅ Working |
Config stored at ~/dotfiles/dotctl/config.yaml |
✅ Working |
sync command (pull + push) |
⚠️ Unstable — README warns against it |
| Bootstrap from existing repo | ❌ Missing |
Conflict detection on link |
❌ Missing |
Idempotent link (safe to re-run) |
❌ Missing |
| One-liner install script | ❌ Missing |
| Templating / per-machine config | ❌ Not in scope (yet) |
| Secret management | ❌ Not in scope (yet) |
Phase 1 — Fix the Foundation (prerequisite for everything)
These are bugs/gaps that block safe automation.
1.1 Make link idempotent
Problem: Running link a second time likely fails or clobbers without warning if a target path already exists.
Fix: Before creating each symlink, check the target:
- If missing → create symlink (happy path)
- If already a symlink pointing to the correct source → skip, log "already linked"
- If already a symlink pointing somewhere else → warn, skip unless
--overwriteflag passed - If a real file/directory exists → back it up to
<path>.dotctl.bak, then link (or skip with--no-backup)
Files to touch: cmd/link.go
Acceptance criteria: Running dotctl link twice on a clean setup produces no errors and no duplicate symlinks.
1.2 Fix or remove sync
Problem: The README actively warns against sync. It should either be fixed or removed so it doesn't cause data loss for new users.
Decision to make (pick one):
- Option A — Fix it: Proper git pull → detect conflicts → commit changed files → push. Use
go-gitfor in-process git operations instead of shelling out. - Option B — Remove it: Drop
sync, document that users should manage the~/dotfilesdirectory as a normal git repo. Add a note in README pointing to how to dogit pulland thendotctl link.
Recommendation: Option B now, Option A later in Phase 3. It's safer to remove a broken command than ship a half-working one.
1.3 Validate that config round-trips cleanly
Problem: The tracked file list lives in ~/dotfiles/dotctl/config.yaml. On a fresh machine, this file needs to already exist in the cloned repo for bootstrap to work. Need to confirm this file is actually committed and not gitignored.
Fix: Add a note in .gitignore explicitly not ignoring dotctl/config.yaml. Add a check in init that warns if the config file would be gitignored.
Phase 2 — The Bootstrap Command (core deliverable)
2.1 dotctl apply <repo-url>
This is the main feature. It should:
- Check if
~/dotfilesalready exists- If yes and it's a git repo →
git pull(or prompt user) - If yes but not a git repo → error with clear message
- If no →
git clone <repo-url> ~/dotfiles
- If yes and it's a git repo →
- Read
~/dotfiles/dotctl/config.yaml(fail clearly if not found — tells user their repo isn't set up for dotctl) - Run the equivalent of
dotctl linkwith idempotent behavior from Phase 1.1 - Print a summary: N linked, M skipped, K backed up
Flags:
--overwrite— overwrite existing files instead of backing up--dry-run— print what would happen without touching the filesystem--no-backup— skip backups, just skip conflicts
Files to touch: New cmd/apply.go
Acceptance criteria: On a machine with nothing but Go installed and git in PATH, running dotctl apply https://github.com/user/dotfiles.git produces a fully linked dotfiles setup.
2.2 One-liner install + apply script
The bootstrap UX needs to work before dotctl itself is installed. Options:
Option A — go install (simplest):
go install github.com/Marcusk19/dotctl@latest && dotctl apply https://github.com/user/dotfiles.git
Requires Go on the machine. Works great for developer setups.
Option B — Shell script + prebuilt binary:
curl -fsSL https://raw.githubusercontent.com/Marcusk19/dotctl/main/install.sh | bash -s -- https://github.com/user/dotfiles.git
The script: detects OS/arch → downloads the correct binary from GitHub releases → runs dotctl apply <repo>. No Go required.
Recommendation: ship both. go install for devs, the curl script for fresh machines where Go may not be present. goreleaser is already set up, so binary releases exist — just need the install script.
Files to add: install.sh in repo root
Phase 3 — Quality of Life (after core bootstrap works)
3.1 Fix sync properly
With go-git as a dependency (or shelling to git with proper error handling):
dotctl sync→ pull remote changes, re-run link, push any local changes- Detect and surface merge conflicts clearly instead of silently failing
3.2 dotctl status
Shows current state of all tracked files:
nvim → ~/.config/nvim [linked ✓]
zshrc → ~/.zshrc [linked ✓]
kitty → ~/.config/kitty [CONFLICT — real file exists]
tmux.conf → ~/.tmux.conf [broken symlink]
3.3 dotctl remove <path>
Removes a tracked file from the manifest and optionally replaces the symlink with the real file.
3.4 dotctl list
Prints all tracked entries from config — useful for auditing before running apply on a new machine.
Phase 4 — Advanced (optional, post-stabilization)
| Feature | Notes |
|---|---|
| Per-machine profiles | Tag entries in config with profiles: [work, home]; pass --profile to apply |
| Templating | Go text/template over files with a .tmpl extension — render before linking |
| Secret redaction | Warn when a tracked file contains patterns that look like secrets (API keys, tokens) |
| XDG-aware paths | Auto-resolve $XDG_CONFIG_HOME instead of hardcoding ~/.config |
Implementation Order
Phase 1.1 → idempotent link (1–2 days)
Phase 1.2 → remove/fix sync (0.5 days)
Phase 1.3 → config gitignore audit (0.5 days)
Phase 2.1 → dotctl apply command (2–3 days)
Phase 2.2 → install.sh script (1 day)
Phase 3.x → status, list, remove (2–3 days)
Phase 4.x → profiles, templating (future)
Definition of Done (for "point at a repo and go")
install.shdownloads the correct binary for current OS/archdotctl apply <url>clones repo if not present, reads config, links all tracked files- Running
applytwice produces no errors - Existing files are backed up, not silently overwritten
--dry-runworks and shows exactly what would happen- README updated with the new one-liner bootstrap flow
- At least one end-to-end test that: clones a test dotfiles repo → runs apply → asserts symlinks exist