Idempotence System
Idempotence System
Section titled “Idempotence System”The dotfiles system is fully idempotent - you can safely run dotfiles install multiple times without unnecessary work or data loss.
Quick Reference
Section titled “Quick Reference”Common Commands
Section titled “Common Commands”# Normal install (skips unchanged)dotfiles install
# After git pull (only changed modules run)git pull && dotfiles install
# Force reinstall everythingdotfiles install --force
# Skip failed modulesdotfiles install --skip-failed
# Only update existing (no new installs)dotfiles install --update-only
# Check what needs updatingdotfiles statusStatus Symbols
Section titled “Status Symbols”| Symbol | Meaning |
|---|---|
✓ | Up-to-date, nothing to do |
• | Needs update (version/changed/config) |
⚠ | User modified files |
! | Failed previously |
Why Things Run or Skip
Section titled “Why Things Run or Skip”Module runs when:
- First time installing
- Module version changed
- Module scripts changed (
install.sh,verify.sh,os/*.sh) - User config changed (
config.ymlvalues for this module) - Previously failed (and not using
--skip-failed) --forceflag used
Module skips when:
- Already installed and no changes detected
- Failed previously +
--skip-failedflag - New module +
--update-onlyflag
File deploys when:
- Source file changed
- Destination missing
- Symlink points to wrong location
File skips when:
- Already correct (hash matches)
- User modified (source unchanged)
Overview
Section titled “Overview”Idempotent means running the same command multiple times produces the same result as running it once. The dotfiles system now:
- ✅ Skips unchanged modules - Only runs what actually changed
- ✅ Skips unchanged files - Only deploys what’s different
- ✅ Protects user modifications - Backs up before overwriting
- ✅ Detects all changes - Version, config, scripts, and files
- ✅ Clear feedback - Always shows why something ran or was skipped
How It Works
Section titled “How It Works”Module-Level Idempotence
Section titled “Module-Level Idempotence”Before running a module, the system checks:
- Module checksum - SHA256 of module.yml + all scripts
- Config hash - SHA256 of user config affecting this module
- Version - Module version field
- Status - Previous installation status
Execution decisions:
- ✓ Skip - Already up-to-date (nothing changed)
- → Install fresh - No previous installation
- → Install retry - Failed previously (unless —skip-failed)
- → Update module - Module definition/scripts/version changed
- → Update config - User config values changed
- → Force - —force flag overrides all checks
File-Level Idempotence
Section titled “File-Level Idempotence”Before deploying each file, the system checks:
- Source hash - Did the source file change?
- Destination exists - Is the file already deployed?
- Symlink target - Does symlink point to correct location?
- Content hash - Does deployed content match source?
- User modifications - Did user change the deployed file?
Deployment decisions:
- ✓ Skip - File already correct
- → Deploy - Source changed or destination missing
- ⚠ Skip with warning - User modified (source unchanged)
- → Backup & deploy - User modified + source changed
Basic Idempotent Install
Section titled “Basic Idempotent Install”# First run - installs everythingdotfiles install
# Second run - skips everything (nothing changed)dotfiles install# Output: ✓ git (skipped: already up-to-date)# ✓ zsh (skipped: already up-to-date)After Updating Repository
Section titled “After Updating Repository”# Pull latest changesgit pull
# Install updates - only changed modules rundotfiles install# Output: ✓ git (skipped: already up-to-date)# → zsh (updating: source file changed)# ✓ zsh (updated in 2.3s)Force Reinstall
Section titled “Force Reinstall”# Force reinstall even if up-to-datedotfiles install --force
# Force specific moduledotfiles install git --forceSkip Failed Modules
Section titled “Skip Failed Modules”# Skip modules that failed previouslydotfiles install --skip-failed# Output: ✓ git (skipped: already up-to-date)# ✓ broken-module (skipped: failed previously, --skip-failed set)# → zsh (installing...)Update Only (No New Installs)
Section titled “Update Only (No New Installs)”# Only update already-installed modulesdotfiles install --update-only# Output: Skipping new modules (--update-only): neovim, tmux# → git (updating: config changed)# ✓ zsh (skipped: already up-to-date)Status Command
Section titled “Status Command”The enhanced status command shows what needs updating:
dotfiles statusOutput:
Name Version Status Update Installed OS ---- ------- ------ ------ --------- -- git 1.0.0 installed ✓ 2 days ago ubuntu zsh 1.2.0 installed • changed 1 week ago ubuntu neovim 2.1.0 failed ! failed 3 days ago ubuntu tmux 1.0.0 installed ⚠ modified 1 week ago ubuntu
Update status: ✓ up-to-date • needs update ⚠ user modified ! failed
Total: 4 modules (3 installed, 1 failed, 1 need update, 1 user modified)
Run 'dotfiles install' to update out-of-date modulesRun 'dotfiles install --force neovim' to retry failed modulesUpdate status meanings:
✓up-to-date - Module and files match, nothing to do• version- Module version changed• changed- Module scripts/definition changed• config- User config affecting this module changed⚠ modified- User modified deployed files! failed- Installation failed previously
Backup System
Section titled “Backup System”When a module update would overwrite user-modified files, the system automatically creates backups.
Backup Location
Section titled “Backup Location”~/.dotfiles/.backups/├── 20260211-143022/ # Timestamp directory│ ├── .zshrc # Backed up file│ └── .zshrc.meta.json # Metadata└── 20260211-150133/ ├── .gitconfig └── .gitconfig.meta.jsonBackup Metadata
Section titled “Backup Metadata”Each backup includes a JSON metadata file:
{ "original_path": "/home/user/.zshrc", "backup_time": "2026-02-11T14:30:22Z", "content_hash": "abc123...", "reason": "user-modified file overwritten by module update", "module": "zsh"}Restoring Backups
Section titled “Restoring Backups”Backups are manual - the system won’t automatically restore them. To restore:
# Find your backupls -lt ~/.dotfiles/.backups/
# Copy back to original locationcp ~/.dotfiles/.backups/20260211-143022/.zshrc ~/.zshrc
# Or diff to see what changeddiff ~/.zshrc ~/.dotfiles/.backups/20260211-143022/.zshrcChange Detection Details
Section titled “Change Detection Details”Module Checksum
Section titled “Module Checksum”Computed from:
module.ymlcontentinstall.shcontent (if exists)verify.shcontent (if exists)- All
os/*.shscripts (if exist)
Any change to these files triggers a re-run.
Config Hash
Section titled “Config Hash”Computed from:
user.name,user.email,user.github_user(affects templates)- Module-specific config from
config.modules.<module-name>
Changes to these values trigger a re-run. This is computed from the effective, merged
config — when a content overlay is active, the overlay’s deep-merged
values are what the hash sees, so editing your overlay’s config.yml correctly re-runs the
affected modules just as editing the base config.yml would.
File Hash
Section titled “File Hash”SHA256 of file content. Used to:
- Detect source file changes
- Detect user modifications
- Skip unchanged files
Performance
Section titled “Performance”The system is designed to be fast:
- Skipped modules: <100ms (state check only)
- Skipped files: <10ms per file (hash comparison)
- Hash computation: Cached within single run
Edge Cases
Section titled “Edge Cases”User Deleted Deployed File
Section titled “User Deleted Deployed File”Behavior: File will be redeployed on next run
rm ~/.gitconfigdotfiles install git# Output: → git (updating: destination file missing)User Modified Symlink
Section titled “User Modified Symlink”Behavior: Symlink recreated with warning
rm ~/.zshrc && echo "custom" > ~/.zshrcdotfiles install zsh# Output: ⚠ Backed up user-modified file: ~/.zshrc → ~/.dotfiles/.backups/...# → zsh (updating: symlink points to wrong location)Module Version Downgrade
Section titled “Module Version Downgrade”Behavior: Treated as update (re-runs installation)
Template Variables Changed
Section titled “Template Variables Changed”Behavior: Template re-rendered with new values
Failed Module with Partial State
Section titled “Failed Module with Partial State”Behavior: Can retry (default) or skip (—skip-failed)
Limitations
Section titled “Limitations”Scripts Always Run on Update
Section titled “Scripts Always Run on Update”Module scripts (install.sh, verify.sh, os/*.sh) always run when a module needs updating. The scripts themselves should be idempotent (using checks like command -v, pkg_installed, etc.).
Package Installs
Section titled “Package Installs”Package manager operations (apt, brew, etc.) are naturally idempotent, but rollback only provides informational messages - you must manually remove packages if needed.
Concurrent Installs
Section titled “Concurrent Installs”Running multiple dotfiles install commands simultaneously is not supported and may cause state corruption.
Best Practices
Section titled “Best Practices”- Run regularly - Safe to run after every
git pull - Check status - Use
dotfiles statusto see what needs updating - Review backups - Periodically check
~/.dotfiles/.backups/for important changes - Write idempotent scripts - Module scripts should handle being run multiple times
- Test updates - Use
--dry-runto preview changes
Troubleshooting
Section titled “Troubleshooting”Module Always Re-runs
Section titled “Module Always Re-runs”Cause: Module state missing checksums (old installation)
Fix: Run once - checksums will be recorded
File Always Redeploys
Section titled “File Always Redeploys”Cause: File state missing hashes (old installation)
Fix: Run once - hashes will be recorded
”User modified” but I didn’t change it
Section titled “”User modified” but I didn’t change it”Cause: File changed by another tool/process
Fix: This is expected - the system detects any changes, not just manual edits
Want to force reinstall
Section titled “Want to force reinstall”Solution: Use --force flag
dotfiles install git --forceMigration from Old Installations
Section titled “Migration from Old Installations”The system is backward compatible. Old installations without checksums will:
- First run: Module runs normally, checksums recorded
- Second run: Module skips (now has checksums)
No manual migration needed!
Technical Details
Section titled “Technical Details”For developers wanting to understand the implementation:
- State schema:
internal/state/state.go- ModuleState + FileState - Hash functions:
internal/module/hash.go- SHA256 computation - Decision logic:
internal/module/runner.go- shouldRunModule, shouldDeployFile - Backup system:
internal/module/backup.go- Timestamped backups - Tests:
internal/module/*_test.go- Comprehensive coverage
Examples
Section titled “Examples”Daily Workflow
Section titled “Daily Workflow”# Morning: update dotfilescd ~/dotfilesgit pulldotfiles install# Only changed modules run
# Check statusdotfiles status# See what's up-to-date, what needs attentionTesting Module Changes
Section titled “Testing Module Changes”# Edit a modulevim modules/zsh/install.sh
# See what changeddotfiles status# Output shows "• changed"
# Install with changesdotfiles install# Only zsh runsRecovering from Mistakes
Section titled “Recovering from Mistakes”# Accidentally installed broken configdotfiles install
# Check backupsls -lt ~/.dotfiles/.backups/
# Restore old versioncp ~/.dotfiles/.backups/20260211-143022/.zshrc ~/.zshrc
# Or reverse the module entirely (uninstall is the reversal path — there is# no separate `rollback` command)dotfiles uninstall zsh