@sebastienrousseau/dotfiles 0.2.519 → 0.2.521

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