@sebastienrousseau/dotfiles 0.2.520 → 0.2.522

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +43 -37
  5. package/install.sh +76 -10
  6. package/package.json +7 -7
  7. package/tools/README.md +49 -0
  8. package/tools/ci/install-chezmoi-verified.sh +68 -0
  9. package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
  10. package/docs/AI.md +0 -179
  11. package/docs/ARCHITECTURE.md +0 -117
  12. package/docs/CNAME +0 -1
  13. package/docs/CONFIG_STRATEGY.md +0 -124
  14. package/docs/COPYRIGHT +0 -7
  15. package/docs/ECOSYSTEM.md +0 -220
  16. package/docs/GOLD-STANDARD-AUDIT.md +0 -352
  17. package/docs/GOVERNANCE.md +0 -98
  18. package/docs/MAINTAINERS.md +0 -41
  19. package/docs/MINIMUM-TOOLCHAIN.md +0 -100
  20. package/docs/NAMING_CONVENTIONS.md +0 -102
  21. package/docs/OPENCODE.md +0 -127
  22. package/docs/README.md +0 -84
  23. package/docs/STRUCTURE.md +0 -102
  24. package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
  25. package/docs/adr/ADR-002-shell-performance.md +0 -130
  26. package/docs/adr/ADR-003-security-first.md +0 -158
  27. package/docs/adr/ADR-004-cli-architecture.md +0 -171
  28. package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
  29. package/docs/adr/ADR-006-shell-selection.md +0 -124
  30. package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
  31. package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
  32. package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
  33. package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
  34. package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
  35. package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
  36. package/docs/adr/README.md +0 -40
  37. package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
  38. package/docs/architecture/ARCHITECTURE.md +0 -20
  39. package/docs/architecture/INTEROP.md +0 -44
  40. package/docs/architecture/REPO_LAYOUT.md +0 -241
  41. package/docs/architecture/WALKTHROUGH.md +0 -86
  42. package/docs/architecture/fleet-deployment.md +0 -77
  43. package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
  44. package/docs/archive/LEGACY_ROADMAP.md +0 -6
  45. package/docs/archive/MILESTONE_v0.2.493.md +0 -47
  46. package/docs/archive/PLAN.md +0 -199
  47. package/docs/archive/REPO_AUDIT.md +0 -31
  48. package/docs/articles/.pages +0 -6
  49. package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
  50. package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
  51. package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
  52. package/docs/articles/index.md +0 -36
  53. package/docs/guides/INSTALL.md +0 -144
  54. package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +0 -121
  55. package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
  56. package/docs/guides/THEMING.md +0 -230
  57. package/docs/guides/TROUBLESHOOTING.md +0 -176
  58. package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
  59. package/docs/index.md +0 -132
  60. package/docs/interop/A2A.md +0 -39
  61. package/docs/interop/POWERSHELL.md +0 -102
  62. package/docs/manual/00-introduction.md +0 -89
  63. package/docs/manual/01-concepts/01-architecture.md +0 -138
  64. package/docs/manual/01-concepts/02-trust-model.md +0 -183
  65. package/docs/manual/01-concepts/03-theme-engine.md +0 -186
  66. package/docs/manual/01-concepts/04-fleet.md +0 -148
  67. package/docs/manual/01-concepts/05-self-healing.md +0 -204
  68. package/docs/manual/02-tutorials/01-first-install.md +0 -197
  69. package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
  70. package/docs/manual/02-tutorials/03-create-profile.md +0 -244
  71. package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
  72. package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
  73. package/docs/manual/03-reference/01-dot-cli.md +0 -475
  74. package/docs/manual/03-reference/02-config-files.md +0 -265
  75. package/docs/manual/03-reference/03-environment.md +0 -124
  76. package/docs/manual/03-reference/04-templates.md +0 -190
  77. package/docs/manual/03-reference/05-feature-flags.md +0 -187
  78. package/docs/manual/04-cookbook/01-recipes.md +0 -285
  79. package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
  80. package/docs/manual/04-cookbook/03-faq.md +0 -175
  81. package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
  82. package/docs/manual/05-appendices/B-security-checklist.md +0 -85
  83. package/docs/manual/05-appendices/C-glossary.md +0 -40
  84. package/docs/manual/05-appendices/D-bibliography.md +0 -58
  85. package/docs/manual/05-appendices/E-license.md +0 -38
  86. package/docs/manual/_toc.yml +0 -58
  87. package/docs/manual/command-index.md +0 -175
  88. package/docs/manual/concept-index.md +0 -170
  89. package/docs/manual/index.md +0 -66
  90. package/docs/migration/README.md +0 -81
  91. package/docs/migration/from-bare-git-repo.md +0 -156
  92. package/docs/migration/from-gnu-stow.md +0 -165
  93. package/docs/migration/from-plain-chezmoi.md +0 -148
  94. package/docs/migration/from-yadm.md +0 -187
  95. package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
  96. package/docs/operations/ATTESTATION.md +0 -44
  97. package/docs/operations/CI_CADENCE.md +0 -107
  98. package/docs/operations/CI_COMPOSITES.md +0 -156
  99. package/docs/operations/COMPLETIONS.md +0 -123
  100. package/docs/operations/COVERAGE.md +0 -204
  101. package/docs/operations/DRIFT.md +0 -107
  102. package/docs/operations/HARD_AUDIT_2026.md +0 -631
  103. package/docs/operations/MAINTENANCE.md +0 -63
  104. package/docs/operations/MANIFEST.md +0 -127
  105. package/docs/operations/MIGRATION.md +0 -109
  106. package/docs/operations/OPERATIONS.md +0 -188
  107. package/docs/operations/PERFORMANCE.md +0 -133
  108. package/docs/operations/PERFORMANCE_BUDGETS.md +0 -196
  109. package/docs/operations/REGISTRY.md +0 -90
  110. package/docs/operations/RELEASE_PIPELINE.md +0 -128
  111. package/docs/operations/RELIABILITY.md +0 -122
  112. package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
  113. package/docs/operations/ROADMAP.md +0 -10
  114. package/docs/operations/ROADMAP_2026.md +0 -7
  115. package/docs/operations/ROADMAP_V0_2_503.md +0 -10
  116. package/docs/operations/TESTING.md +0 -216
  117. package/docs/operations/TRACEABILITY.md +0 -44
  118. package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
  119. package/docs/operations/VERSION_SYNC.md +0 -393
  120. package/docs/packaging.md +0 -222
  121. package/docs/reference/ALIASES.md +0 -131
  122. package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
  123. package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
  124. package/docs/reference/FEATURE-MATRIX.md +0 -646
  125. package/docs/reference/FEATURES.md +0 -66
  126. package/docs/reference/FONTS.md +0 -112
  127. package/docs/reference/POWERSHELL_PARITY.md +0 -82
  128. package/docs/reference/PROFILES.md +0 -69
  129. package/docs/reference/SCREENSHOTS.md +0 -121
  130. package/docs/reference/SCRIPTS.md +0 -71
  131. package/docs/reference/SUPPORT_MATRIX.md +0 -80
  132. package/docs/reference/THEMES.md +0 -117
  133. package/docs/reference/TOOLS.md +0 -110
  134. package/docs/reference/UTILS.md +0 -243
  135. package/docs/registry.json +0 -6
  136. package/docs/schema/dot-env-v1.json +0 -110
  137. package/docs/schema/dot-registry-v1.json +0 -33
  138. package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
  139. package/docs/security/AUDIT_BYPASS.md +0 -103
  140. package/docs/security/AUTOMATION_SECRETS.md +0 -26
  141. package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
  142. package/docs/security/CI_PINNING.md +0 -129
  143. package/docs/security/COMMIT_SIGNING.md +0 -138
  144. package/docs/security/COMPLIANCE.md +0 -458
  145. package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
  146. package/docs/security/DISCLOSURE.md +0 -130
  147. package/docs/security/ENCRYPTION.md +0 -57
  148. package/docs/security/FMEA.md +0 -159
  149. package/docs/security/FUZZING.md +0 -209
  150. package/docs/security/HISTORY_FILTERING.md +0 -132
  151. package/docs/security/INCIDENT_RESPONSE.md +0 -579
  152. package/docs/security/INSTALL_VERIFICATION.md +0 -122
  153. package/docs/security/KEYS.md +0 -49
  154. package/docs/security/KEY_ROTATION.md +0 -303
  155. package/docs/security/MCP_POLICY.md +0 -78
  156. package/docs/security/POLICY_RELEASES.md +0 -37
  157. package/docs/security/README.md +0 -28
  158. package/docs/security/SCORECARD.md +0 -195
  159. package/docs/security/SECRETS.md +0 -158
  160. package/docs/security/SECURITY.md +0 -45
  161. package/docs/security/SECURITY_CHECKLIST.md +0 -55
  162. package/docs/security/SHELL_EXEMPTIONS.md +0 -145
  163. package/docs/security/SOUP_REGISTER.md +0 -36
  164. package/docs/security/THREAT_MODEL.md +0 -130
  165. package/docs/security/VERIFICATION_VALIDATION.md +0 -228
  166. package/docs/security/VERIFY_RELEASE.md +0 -201
  167. package/docs/security/security-pubkey.asc +0 -15
  168. package/docs/stylesheets/extra.css +0 -444
  169. package/docs/themes/README.md +0 -10
  170. package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
  171. package/docs/themes/hero-shot.svg +0 -78
  172. package/scripts/README.md +0 -123
  173. package/scripts/ci/check-copyright-headers.sh +0 -8
  174. package/scripts/ci/check-shell-preamble.sh +0 -8
  175. package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
  176. package/scripts/demo/record.sh +0 -43
  177. package/scripts/diagnostics/a2a-conformance.sh +0 -163
  178. package/scripts/diagnostics/alias-governance.sh +0 -165
  179. package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
  180. package/scripts/diagnostics/aliases-manifest.sh +0 -77
  181. package/scripts/diagnostics/attest-verify.sh +0 -147
  182. package/scripts/diagnostics/benchmark.sh +0 -408
  183. package/scripts/diagnostics/conflicts.sh +0 -73
  184. package/scripts/diagnostics/doctor-unified.sh +0 -43
  185. package/scripts/diagnostics/doctor.sh +0 -797
  186. package/scripts/diagnostics/drift-dashboard.sh +0 -203
  187. package/scripts/diagnostics/health.sh +0 -656
  188. package/scripts/diagnostics/history-analysis.sh +0 -86
  189. package/scripts/diagnostics/mcp-doctor.sh +0 -582
  190. package/scripts/diagnostics/perf.sh +0 -453
  191. package/scripts/diagnostics/scorecard.sh +0 -120
  192. package/scripts/diagnostics/secret-governance.sh +0 -65
  193. package/scripts/diagnostics/security-score.sh +0 -467
  194. package/scripts/diagnostics/smoke-test.sh +0 -88
  195. package/scripts/diagnostics/snapshot.sh +0 -90
  196. package/scripts/diagnostics/verify.sh +0 -108
  197. package/scripts/diagnostics/verify_state.sh +0 -73
  198. package/scripts/diagnostics/version-locks.sh +0 -94
  199. package/scripts/diagnostics/workstation-attestation.sh +0 -212
  200. package/scripts/dot/commands/agent.sh +0 -535
  201. package/scripts/dot/commands/agents.sh +0 -352
  202. package/scripts/dot/commands/ai.sh +0 -600
  203. package/scripts/dot/commands/aliases.sh +0 -277
  204. package/scripts/dot/commands/appearance.sh +0 -110
  205. package/scripts/dot/commands/completion.sh +0 -171
  206. package/scripts/dot/commands/core.sh +0 -217
  207. package/scripts/dot/commands/diagnostics.sh +0 -265
  208. package/scripts/dot/commands/env-emit.sh +0 -203
  209. package/scripts/dot/commands/fleet.sh +0 -711
  210. package/scripts/dot/commands/init.sh +0 -185
  211. package/scripts/dot/commands/lint.sh +0 -208
  212. package/scripts/dot/commands/manual.sh +0 -169
  213. package/scripts/dot/commands/meta.sh +0 -438
  214. package/scripts/dot/commands/patterns.sh +0 -55
  215. package/scripts/dot/commands/registry.sh +0 -455
  216. package/scripts/dot/commands/restore.sh +0 -232
  217. package/scripts/dot/commands/secrets.sh +0 -296
  218. package/scripts/dot/commands/security.sh +0 -102
  219. package/scripts/dot/commands/tools.sh +0 -570
  220. package/scripts/dot/data/alias-deprecations.tsv +0 -2
  221. package/scripts/dot/powershell/Dot.psm1 +0 -319
  222. package/scripts/fonts/install-nerd-fonts.sh +0 -75
  223. package/scripts/fonts/patch-fonts.sh +0 -36
  224. package/scripts/git-hooks/install.sh +0 -12
  225. package/scripts/git-hooks/pre-commit +0 -12
  226. package/scripts/git-hooks/pre-commit-audit.sh +0 -146
  227. package/scripts/git-hooks/pre-push +0 -105
  228. package/scripts/git-hooks/prepare-commit-msg +0 -29
  229. package/scripts/lib/secrets_provider.sh +0 -200
  230. package/scripts/nvim/headless-upgrade.lua +0 -81
  231. package/scripts/ops/ai-setup.sh +0 -71
  232. package/scripts/ops/bundle.sh +0 -104
  233. package/scripts/ops/chaos.sh +0 -50
  234. package/scripts/ops/chezmoi-apply.sh +0 -333
  235. package/scripts/ops/chezmoi-diff.sh +0 -16
  236. package/scripts/ops/chezmoi-remove.sh +0 -46
  237. package/scripts/ops/chezmoi-update.sh +0 -67
  238. package/scripts/ops/heal-chezmoi.sh +0 -87
  239. package/scripts/ops/heal-system.sh +0 -129
  240. package/scripts/ops/heal-tools.sh +0 -297
  241. package/scripts/ops/heal.sh +0 -223
  242. package/scripts/ops/post-apply-repair.sh +0 -107
  243. package/scripts/ops/prewarm.sh +0 -128
  244. package/scripts/ops/release.sh +0 -262
  245. package/scripts/ops/rollback.sh +0 -613
  246. package/scripts/ops/setup.sh +0 -138
  247. package/scripts/ops/teleport.sh +0 -34
  248. package/scripts/qa/check-feature-matrix.sh +0 -296
  249. package/scripts/qa/check-version-consistency.sh +0 -12
  250. package/scripts/qa/coverage-baseline.sh +0 -61
  251. package/scripts/qa/docs-coverage.sh +0 -118
  252. package/scripts/qa/examples-coverage.sh +0 -94
  253. package/scripts/qa/powershell-contract.ps1 +0 -95
  254. package/scripts/qa/reliability-audit.sh +0 -139
  255. package/scripts/qa/scorecard-snapshot.sh +0 -128
  256. package/scripts/qa/traceability-coverage.sh +0 -124
  257. package/scripts/qa/validate-examples.sh +0 -90
  258. package/scripts/qa/wsl-contract.sh +0 -12
  259. package/scripts/secrets/age-init.sh +0 -82
  260. package/scripts/secrets/create-secrets-file.sh +0 -46
  261. package/scripts/secrets/encrypt-ssh-key.sh +0 -44
  262. package/scripts/security/backup.sh +0 -58
  263. package/scripts/security/check-disclosure-key-expiry.sh +0 -111
  264. package/scripts/security/dns-doh.sh +0 -52
  265. package/scripts/security/encryption-check.sh +0 -55
  266. package/scripts/security/enforce-policies.sh +0 -552
  267. package/scripts/security/firewall.sh +0 -91
  268. package/scripts/security/lock-configs.sh +0 -67
  269. package/scripts/security/lock-screen.sh +0 -56
  270. package/scripts/security/manage-secrets.sh +0 -429
  271. package/scripts/security/ssh-cert.sh +0 -204
  272. package/scripts/security/telemetry-kill.sh +0 -51
  273. package/scripts/security/usb-safety.sh +0 -52
  274. package/scripts/theme/apply-gnome-theme.sh +0 -333
  275. package/scripts/theme/extract-heic-frames.sh +0 -115
  276. package/scripts/theme/extract-theme.py +0 -1020
  277. package/scripts/theme/install-boot-logo.sh +0 -63
  278. package/scripts/theme/install-catppuccin-themes.sh +0 -371
  279. package/scripts/theme/install-cursors.sh +0 -26
  280. package/scripts/theme/install-file-icons.sh +0 -27
  281. package/scripts/theme/install-grub-theme.sh +0 -62
  282. package/scripts/theme/install-lock-icon.sh +0 -31
  283. package/scripts/theme/merge-wallpaper.sh +0 -146
  284. package/scripts/theme/rebuild-themes.sh +0 -603
  285. package/scripts/theme/switch.sh +0 -476
  286. package/scripts/theme/wallpaper-rotate.sh +0 -137
  287. package/scripts/theme/wallpaper-sync.sh +0 -690
  288. package/scripts/tools/cmatrix.sh +0 -22
  289. package/scripts/tools/detect-collisions.py +0 -103
  290. package/scripts/tools/emoji-picker.sh +0 -49
  291. package/scripts/tools/figlet-banner.sh +0 -19
  292. package/scripts/tools/log-rotate.sh +0 -31
  293. package/scripts/tools/lolcat-wrap.sh +0 -20
  294. package/scripts/tools/pipes.sh +0 -49
  295. package/scripts/tuning/linux.sh +0 -186
  296. package/scripts/tuning/macos.sh +0 -56
  297. package/scripts/uninstall.sh +0 -86
  298. package/scripts/verify-release-versions +0 -156
  299. package/scripts/version-sync.sh +0 -714
  300. package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
  301. package/templates/chezmoi-data/mac-m1.toml.example +0 -16
  302. package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
  303. package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
  304. package/templates/projects/go/.github/workflows/ci.yml +0 -31
  305. package/templates/projects/go/README.md +0 -7
  306. package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
  307. package/templates/projects/go/go.mod +0 -3
  308. package/templates/projects/go/go.sum +0 -0
  309. package/templates/projects/molecule/README.md +0 -7
  310. package/templates/projects/molecule/converge.yml +0 -7
  311. package/templates/projects/molecule/molecule.yml +0 -16
  312. package/templates/projects/node/.github/workflows/ci.yml +0 -30
  313. package/templates/projects/node/README.md +0 -7
  314. package/templates/projects/node/package-lock.json +0 -12
  315. package/templates/projects/node/package.json +0 -10
  316. package/templates/projects/node/src/index.js +0 -3
  317. package/templates/projects/packer/README.md +0 -15
  318. package/templates/projects/packer/main.pkr.hcl +0 -15
  319. package/templates/projects/python/.github/workflows/ci.yml +0 -34
  320. package/templates/projects/python/README.md +0 -7
  321. package/templates/projects/python/pyproject.toml +0 -25
  322. package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
  323. package/templates/projects/python/tests/test_basic.py +0 -3
@@ -1,613 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: Apache-2.0 OR MIT
3
- # Copyright (c) 2015-2026 Sebastien Rousseau
4
- # shellcheck disable=SC2034
5
- # =============================================================================
6
- # Dotfiles Rollback Script - Self-Healing Configuration Recovery
7
- # Safely reverts failed dotfile applications with automatic backup
8
- # Usage: ./scripts/ops/rollback.sh [OPTIONS]
9
- # =============================================================================
10
-
11
- set -euo pipefail
12
-
13
- _cleanup_files=()
14
- trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
15
-
16
- # Configuration
17
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
- # shellcheck source=../../lib/dot/ui.sh
19
- # shellcheck disable=SC1091
20
- source "$SCRIPT_DIR/../../lib/dot/ui.sh"
21
- # shellcheck source=../../lib/dot/log.sh
22
- # shellcheck disable=SC1091
23
- source "$SCRIPT_DIR/../../lib/dot/log.sh"
24
- export DOT_COMMAND="rollback"
25
- REPO_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
26
- # Resolve symlinks for consistent path handling
27
- if command -v realpath >/dev/null 2>&1; then
28
- DOTFILES_SOURCE="$(realpath "${HOME}/.dotfiles" 2>/dev/null || echo "${HOME}/.dotfiles")"
29
- elif command -v readlink >/dev/null 2>&1; then
30
- DOTFILES_SOURCE="$(readlink -f "${HOME}/.dotfiles" 2>/dev/null || echo "${HOME}/.dotfiles")"
31
- else
32
- DOTFILES_SOURCE="${HOME}/.dotfiles"
33
- fi
34
- BACKUP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/dotfiles/backups"
35
- STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles"
36
- ROLLBACK_LOG="$STATE_DIR/rollback.log"
37
- MAX_BACKUPS=10
38
- # sysexits.h EX_TEMPFAIL: the command did nothing because another instance
39
- # holds the lock. Distinct from 1 (the rollback ran and failed) and from 0
40
- # (the rollback ran and succeeded) — a caller has to be able to tell those
41
- # three apart.
42
- EXIT_BUSY=75
43
-
44
- # Logging — delegates to shared ui.sh primitives
45
- ui_init
46
- log() { printf '%b\n' "$*"; }
47
- log_info() { ui_info "$@"; }
48
- log_success() { ui_ok "$@"; }
49
- log_warn() { ui_warn "$@"; }
50
- log_error() { ui_err "$@"; }
51
- log_step() {
52
- echo ""
53
- ui_section "$*"
54
- }
55
-
56
- # Persistent logging
57
- persist_log() {
58
- mkdir -p "$STATE_DIR"
59
- echo "[$(date -Iseconds)] $*" >>"$ROLLBACK_LOG"
60
- }
61
-
62
- usage() {
63
- cat <<EOF
64
- Dotfiles Rollback & Recovery Tool
65
-
66
- Usage: $(basename "$0") [COMMAND] [OPTIONS]
67
-
68
- Commands:
69
- status Show current state and available backups
70
- backup Create a manual backup of current dotfiles
71
- rollback Rollback to the previous backup
72
- rollback-to N Rollback to specific backup number (from status list)
73
- git-reset Reset to last known good git commit
74
- restore FILE Restore a specific file from backup
75
- clean Remove old backups (keeps last $MAX_BACKUPS)
76
-
77
- Options:
78
- -f, --force Skip confirmation prompts
79
- -n, --dry-run Show what would be done without making changes
80
- -v, --verbose Show detailed output
81
- -h, --help Show this help message
82
-
83
- Examples:
84
- $(basename "$0") status # List available backups
85
- $(basename "$0") backup # Create manual backup
86
- $(basename "$0") rollback # Rollback to previous state
87
- $(basename "$0") rollback-to 3 # Rollback to backup #3
88
- $(basename "$0") git-reset # Reset to last good commit
89
- $(basename "$0") restore .bashrc # Restore specific file
90
-
91
- EOF
92
- }
93
-
94
- # Ensure directories exist
95
- ensure_dirs() {
96
- mkdir -p "$BACKUP_DIR" "$STATE_DIR"
97
- }
98
-
99
- # List available backups
100
- list_backups() {
101
- local backups=()
102
- local i=1
103
-
104
- if [[ ! -d "$BACKUP_DIR" ]]; then
105
- log_warn "No backup directory found"
106
- return 1
107
- fi
108
-
109
- log_step "Available Backups"
110
- echo ""
111
-
112
- while IFS= read -r backup; do
113
- if [[ -d "$backup" ]]; then
114
- local name timestamp size
115
- name=$(basename "$backup")
116
- timestamp=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$backup" 2>/dev/null || stat -c "%y" "$backup" 2>/dev/null | cut -d'.' -f1)
117
- size=$(du -sh "$backup" 2>/dev/null | cut -f1)
118
- printf " %2d. %-30s %s %s\n" "$i" "$name" "$timestamp" "$size"
119
- ((i++)) || true
120
- fi
121
- done < <(find "$BACKUP_DIR" -maxdepth 1 -type d -name "backup_*" | sort -r)
122
-
123
- if [[ $i -eq 1 ]]; then
124
- log_warn "No backups found"
125
- return 1
126
- fi
127
- echo ""
128
- }
129
-
130
- # Create a backup
131
- # LCOV_EXCL_START — body mutates real $HOME (cp/mkdir of dotfiles);
132
- # can't be exercised under the coverage sandbox without destroying
133
- # fixture state. The dispatcher reaches this function but the body
134
- # is genuinely off-limits.
135
- create_backup() {
136
- local reason="${1:-manual}"
137
- local timestamp
138
- timestamp=$(date +"%Y%m%d_%H%M%S")
139
- local backup_name="backup_${timestamp}_${reason}"
140
- local backup_path="$BACKUP_DIR/$backup_name"
141
-
142
- log_step "Creating Backup: $backup_name"
143
- ensure_dirs
144
-
145
- mkdir -p "$backup_path"
146
-
147
- # List of files/directories to backup
148
- local targets=(
149
- "$HOME/.bashrc"
150
- "$HOME/.zshrc"
151
- "$HOME/.zshenv"
152
- "$HOME/.profile"
153
- "$HOME/.inputrc"
154
- "$HOME/.vimrc"
155
- "$HOME/.gitconfig"
156
- "$HOME/.config/shell"
157
- "$HOME/.config/nvim"
158
- "$HOME/.config/git"
159
- "$HOME/.config/tmux"
160
- "$HOME/.config/chezmoi"
161
- )
162
-
163
- local backed_up=0
164
- for target in "${targets[@]}"; do
165
- if [[ -e "$target" ]]; then
166
- local rel_path="${target#"$HOME"/}"
167
- local dest_dir
168
- dest_dir=$(dirname "$backup_path/$rel_path")
169
- mkdir -p "$dest_dir"
170
- cp -a "$target" "$backup_path/$rel_path" 2>/dev/null || true
171
- ((backed_up++)) || true
172
- [[ "$VERBOSE" == "1" ]] && log_info "Backed up: $rel_path"
173
- fi
174
- done
175
-
176
- local git_commit="unknown"
177
- if cd "$DOTFILES_SOURCE" 2>/dev/null; then
178
- git_commit=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
179
- fi
180
-
181
- # Record metadata
182
- cat >"$backup_path/.backup_meta" <<EOF
183
- timestamp=$timestamp
184
- reason=$reason
185
- chezmoi_version=$(chezmoi --version 2>/dev/null || echo "unknown")
186
- git_commit=$git_commit
187
- files_backed_up=$backed_up
188
- EOF
189
-
190
- log_success "Backup created: $backup_path ($backed_up items)"
191
- persist_log "BACKUP_CREATED: $backup_name ($backed_up items)"
192
-
193
- # Cleanup old backups
194
- cleanup_old_backups
195
- }
196
- # LCOV_EXCL_STOP
197
-
198
- # LCOV_EXCL_START — rm -rf of real backup dirs.
199
- # Cleanup old backups, keeping only MAX_BACKUPS
200
- cleanup_old_backups() {
201
- local count
202
- count=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "backup_*" 2>/dev/null | wc -l | tr -d ' ')
203
-
204
- if [[ $count -gt $MAX_BACKUPS ]]; then
205
- local to_delete=$((count - MAX_BACKUPS))
206
- log_info "Cleaning up $to_delete old backup(s)..."
207
-
208
- find "$BACKUP_DIR" -maxdepth 1 -type d -name "backup_*" | sort | head -n "$to_delete" | while read -r old; do
209
- rm -rf "$old"
210
- [[ "$VERBOSE" == "1" ]] && log_info "Removed: $(basename "$old")"
211
- done
212
- fi
213
- }
214
- # LCOV_EXCL_STOP
215
-
216
- # Get the most recent backup
217
- get_latest_backup() {
218
- find "$BACKUP_DIR" -maxdepth 1 -type d -name "backup_*" 2>/dev/null | sort -r | head -1
219
- }
220
-
221
- # Get backup by index (1-based)
222
- get_backup_by_index() {
223
- local index="$1"
224
- find "$BACKUP_DIR" -maxdepth 1 -type d -name "backup_*" | sort -r | sed -n "${index}p"
225
- }
226
-
227
- # LCOV_EXCL_START — body restores files into real $HOME; dispatcher
228
- # reaches it but the actual mutation is off-limits under coverage.
229
- # Rollback to a specific backup
230
- perform_rollback() {
231
- local backup_path="$1"
232
- local dry_run="${2:-0}"
233
-
234
- if [[ ! -d "$backup_path" ]]; then
235
- log_error "Backup not found: $backup_path"
236
- return 1
237
- fi
238
-
239
- log_step "Rollback from: $(basename "$backup_path")"
240
-
241
- # First, create a safety backup of current state
242
- if [[ "$dry_run" != "1" ]]; then
243
- create_backup "pre_rollback"
244
- fi
245
-
246
- # Restore files
247
- local restored=0
248
- while IFS= read -r -d '' file; do
249
- local rel_path="${file#"$backup_path"/}"
250
- local dest="$HOME/$rel_path"
251
-
252
- # Skip metadata file
253
- [[ "$rel_path" == ".backup_meta" ]] && continue
254
-
255
- if [[ "$dry_run" == "1" ]]; then
256
- log_info "[DRY-RUN] Would restore: $rel_path"
257
- else
258
- local dest_dir
259
- dest_dir=$(dirname "$dest")
260
- mkdir -p "$dest_dir"
261
- cp -a "$file" "$dest"
262
- ((restored++)) || true
263
- [[ "$VERBOSE" == "1" ]] && log_info "Restored: $rel_path"
264
- fi
265
- done < <(find "$backup_path" -type f -print0)
266
-
267
- if [[ "$dry_run" != "1" ]]; then
268
- log_success "Rollback complete: $restored file(s) restored"
269
- persist_log "ROLLBACK: from $(basename "$backup_path"), $restored files restored"
270
- dot_log info "rollback_end" "files=$restored" "backup=$(basename "$backup_path")"
271
-
272
- # AI-Driven Analysis of the failure
273
- if command -v dot >/dev/null 2>&1 && [[ "${DOTFILES_AI:-0}" == "1" ]]; then
274
- log_step "AI Root-Cause Analysis"
275
- local drift
276
- drift=$(chezmoi status 2>/dev/null || true)
277
- local last_log=""
278
- if cd "$DOTFILES_SOURCE" 2>/dev/null; then
279
- last_log=$(git log -n 5 --oneline 2>/dev/null || true)
280
- fi
281
-
282
- local ai_prompt
283
- ai_prompt="System rollback was triggered from $(basename "$backup_path").
284
- Recent Git History:
285
- $last_log
286
-
287
- Current Drift after rollback:
288
- $drift
289
-
290
- Analyze why the environment may have reached a state requiring rollback and suggest architectural hardening steps."
291
-
292
- dot ai claude --style hardener "$ai_prompt" || true
293
- fi
294
- fi
295
- }
296
- # LCOV_EXCL_STOP
297
-
298
- # LCOV_EXCL_START — runs git reset --hard, mutates working tree.
299
- # Git-based rollback
300
- git_reset() {
301
- local dry_run="${1:-0}"
302
-
303
- if [[ ! -d "$DOTFILES_SOURCE/.git" ]]; then
304
- log_error "Not a git repository: $DOTFILES_SOURCE"
305
- return 1
306
- fi
307
-
308
- log_step "Git Reset Recovery"
309
-
310
- cd "$DOTFILES_SOURCE"
311
-
312
- # Show recent commits
313
- log_info "Recent commits:"
314
- git log --oneline -5
315
- echo ""
316
-
317
- # Check for uncommitted changes
318
- if [[ -n "$(git status --porcelain)" ]]; then
319
- log_warn "Uncommitted changes detected"
320
- if [[ "$FORCE" == "1" ]]; then
321
- git stash push -m "Auto-stash before rollback $(date +%Y%m%d_%H%M%S)"
322
- log_success "Changes auto-stashed (use 'git stash pop' to recover)"
323
- else
324
- read -t 30 -rp "Stash changes? [y/N] " response || response="N"
325
- if [[ "$response" =~ ^[Yy]$ ]]; then
326
- git stash push -m "Auto-stash before rollback $(date +%Y%m%d_%H%M%S)"
327
- log_success "Changes stashed"
328
- fi
329
- fi
330
- fi
331
-
332
- # Find last good commit (last tag or HEAD~1)
333
- local target_commit
334
- target_commit=$(git describe --tags --abbrev=0 2>/dev/null || echo "HEAD~1")
335
-
336
- log_info "Reset target: $target_commit"
337
-
338
- if [[ "$dry_run" == "1" ]]; then
339
- log_info "[DRY-RUN] Would reset to: $target_commit"
340
- git diff --stat HEAD "$target_commit"
341
- else
342
- # Create backup first
343
- create_backup "pre_git_reset"
344
-
345
- git reset --hard "$target_commit"
346
- log_success "Git reset to: $target_commit"
347
-
348
- # Re-apply chezmoi
349
- if command -v chezmoi >/dev/null 2>&1; then
350
- log_info "Re-applying chezmoi..."
351
- chezmoi apply --force --exclude=scripts
352
- log_success "Chezmoi re-applied"
353
- fi
354
-
355
- persist_log "GIT_RESET: to $target_commit"
356
- fi
357
- }
358
- # LCOV_EXCL_STOP
359
-
360
- # LCOV_EXCL_START — body copies files into real $HOME.
361
- # Restore a specific file from the latest backup
362
- restore_file() {
363
- local file_path="$1"
364
- local dry_run="${2:-0}"
365
-
366
- # Normalize path
367
- file_path="${file_path#"$HOME"/}"
368
- file_path="${file_path#./}"
369
- file_path="${file_path#~/}"
370
-
371
- # Validate against path traversal (e.g., "../../etc/passwd")
372
- # Reject paths containing ".." components
373
- if [[ "$file_path" == *".."* ]]; then
374
- log_error "Path traversal detected in file path: $file_path"
375
- return 1
376
- fi
377
-
378
- local backup
379
- backup=$(get_latest_backup)
380
-
381
- if [[ -z "$backup" ]]; then
382
- log_error "No backups available"
383
- return 1
384
- fi
385
-
386
- local source_file="$backup/$file_path"
387
-
388
- # shellcheck disable=SC1091
389
- # Verify resolved source path stays within the backup directory
390
- local resolved_source
391
- resolved_source="$(cd "$(dirname "$source_file")" 2>/dev/null && pwd)/$(basename "$source_file")" || {
392
- # shellcheck disable=SC1091
393
- log_error "Cannot resolve source path: $source_file"
394
- return 1
395
- }
396
- if [[ "$resolved_source" != "$backup/"* ]]; then
397
- log_error "Path traversal detected: resolved path escapes backup directory"
398
- return 1
399
- fi
400
-
401
- # Verify resolved destination stays within HOME
402
- local resolved_dest_dir
403
- resolved_dest_dir="$(cd "$(dirname "$HOME/$file_path")" 2>/dev/null && pwd)" || {
404
- log_error "Cannot resolve destination directory for: $file_path"
405
- return 1
406
- }
407
- if [[ "$resolved_dest_dir" != "$HOME"* ]]; then
408
- log_error "Path traversal detected: destination escapes HOME directory"
409
- return 1
410
- fi
411
-
412
- if [[ ! -f "$source_file" ]]; then
413
- log_error "File not found in backup: $file_path"
414
- log_info "Available files in backup:"
415
- find "$backup" -type f -name "*.backup_meta" -prune -o -type f -print | sed "s|$backup/||" | head -20
416
- return 1
417
- fi
418
-
419
- local dest="$HOME/$file_path"
420
-
421
- if [[ "$dry_run" == "1" ]]; then
422
- log_info "[DRY-RUN] Would restore: $file_path"
423
- log_info "From: $source_file"
424
- log_info "To: $dest"
425
- else
426
- # Backup current version
427
- if [[ -f "$dest" ]]; then
428
- cp "$dest" "${dest}.rollback.$(date +%Y%m%d_%H%M%S)"
429
- fi
430
-
431
- local dest_dir
432
- dest_dir=$(dirname "$dest")
433
- mkdir -p "$dest_dir"
434
- cp -a "$source_file" "$dest"
435
- log_success "Restored: $file_path"
436
- persist_log "RESTORE_FILE: $file_path"
437
- fi
438
- }
439
- # LCOV_EXCL_STOP
440
-
441
- # Show current status
442
- show_status() {
443
- log_step "Dotfiles Rollback Status"
444
- echo ""
445
-
446
- # Chezmoi status
447
- if command -v chezmoi >/dev/null 2>&1; then
448
- log_info "Chezmoi version: $(chezmoi --version | head -1)"
449
- local status
450
- status=$(chezmoi status 2>/dev/null || echo "")
451
- if [[ -z "$status" ]]; then
452
- log_success "Chezmoi: All files in sync"
453
- else
454
- log_warn "Chezmoi: $(echo "$status" | wc -l | tr -d ' ') file(s) out of sync"
455
- fi
456
- fi
457
-
458
- # Git status
459
- if [[ -d "$DOTFILES_SOURCE/.git" ]]; then
460
- cd "$DOTFILES_SOURCE"
461
- log_info "Git commit: $(git rev-parse --short HEAD)"
462
- log_info "Git branch: $(git branch --show-current)"
463
- local changes
464
- changes=$(git status --porcelain | wc -l | tr -d ' ')
465
- if [[ "$changes" -eq 0 ]]; then
466
- log_success "Git: Working tree clean"
467
- else
468
- log_warn "Git: $changes uncommitted change(s)"
469
- fi
470
- fi
471
-
472
- # List backups
473
- echo ""
474
- list_backups || true
475
-
476
- # Recent rollback log
477
- if [[ -f "$ROLLBACK_LOG" ]]; then
478
- echo ""
479
- log_info "Recent rollback activity:"
480
- tail -5 "$ROLLBACK_LOG" | while read -r line; do
481
- echo " $line"
482
- done
483
- fi
484
- }
485
-
486
- # =============================================================================
487
- # Main
488
- # =============================================================================
489
-
490
- FORCE=0
491
- DRY_RUN=0
492
- VERBOSE=0
493
-
494
- main() {
495
- local command="${1:-status}"
496
- shift || true
497
-
498
- if [[ "$command" == "help" ]]; then
499
- usage
500
- exit 0
501
- fi
502
-
503
- # Parse global options
504
- while [[ $# -gt 0 ]]; do
505
- case "$1" in
506
- -f | --force)
507
- FORCE=1
508
- shift
509
- ;;
510
- -n | --dry-run)
511
- DRY_RUN=1
512
- shift
513
- ;;
514
- -v | --verbose)
515
- VERBOSE=1
516
- shift
517
- ;;
518
- -h | --help)
519
- usage
520
- exit 0
521
- ;;
522
- *) break ;;
523
- esac
524
- done
525
-
526
- ensure_dirs
527
-
528
- # Concurrency guard
529
- LOCK_FILE="${XDG_RUNTIME_DIR:-/tmp}/dotfiles-rollback.lock"
530
- if command -v flock >/dev/null 2>&1; then
531
- exec 9>"$LOCK_FILE"
532
- if ! flock -n 9; then
533
- # Losing the lock means this invocation did nothing. Exiting 0 made
534
- # "did nothing" indistinguishable from "rolled back" to any caller or
535
- # CI step, which is the one thing a recovery tool must never be
536
- # ambiguous about. 75 is EX_TEMPFAIL: nothing is wrong, try again.
537
- ui_warn "Already running" "Another rollback instance is active"
538
- exit "$EXIT_BUSY"
539
- fi
540
- fi
541
-
542
- dot_log info "rollback_start" "command=$command"
543
-
544
- case "$command" in
545
- status)
546
- show_status
547
- ;;
548
- backup)
549
- create_backup "manual"
550
- ;;
551
- rollback)
552
- local latest
553
- latest=$(get_latest_backup)
554
- if [[ -z "$latest" ]]; then
555
- log_error "No backups available for rollback"
556
- exit 1
557
- fi
558
- if [[ "$FORCE" != "1" ]] && [[ "$DRY_RUN" != "1" ]]; then
559
- read -t 30 -rp "Rollback to $(basename "$latest")? [y/N] " response || response="N"
560
- [[ ! "$response" =~ ^[Yy]$ ]] && exit 0
561
- fi
562
- perform_rollback "$latest" "$DRY_RUN"
563
- ;;
564
- rollback-to)
565
- local index="${1:-}"
566
- if [[ -z "$index" ]] || ! [[ "$index" =~ ^[0-9]+$ ]]; then
567
- log_error "Please specify a backup number (see 'status' command)"
568
- exit 1
569
- fi
570
- local backup
571
- backup=$(get_backup_by_index "$index")
572
- if [[ -z "$backup" ]]; then
573
- log_error "Backup #$index not found"
574
- exit 1
575
- fi
576
- if [[ "$FORCE" != "1" ]] && [[ "$DRY_RUN" != "1" ]]; then
577
- read -t 30 -rp "Rollback to $(basename "$backup")? [y/N] " response || response="N"
578
- [[ ! "$response" =~ ^[Yy]$ ]] && exit 0
579
- fi
580
- perform_rollback "$backup" "$DRY_RUN"
581
- ;;
582
- git-reset)
583
- if [[ "$FORCE" != "1" ]] && [[ "$DRY_RUN" != "1" ]]; then
584
- read -t 30 -rp "Reset to last known good commit? [y/N] " response || response="N"
585
- [[ ! "$response" =~ ^[Yy]$ ]] && exit 0
586
- fi
587
- git_reset "$DRY_RUN"
588
- ;;
589
- restore)
590
- local file="${1:-}"
591
- if [[ -z "$file" ]]; then
592
- log_error "Please specify a file to restore"
593
- exit 1
594
- fi
595
- restore_file "$file" "$DRY_RUN"
596
- ;;
597
- clean)
598
- log_info "Cleaning old backups (keeping last $MAX_BACKUPS)..."
599
- cleanup_old_backups
600
- log_success "Cleanup complete"
601
- ;;
602
- -h | --help | help)
603
- usage
604
- ;;
605
- *)
606
- log_error "Unknown command: $command"
607
- usage
608
- exit 1
609
- ;;
610
- esac
611
- }
612
-
613
- main "$@"