@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.
- package/CHANGELOG.md +52 -0
- package/LICENSE-APACHE +190 -0
- package/LICENSE-MIT +21 -0
- package/README.md +43 -37
- package/install.sh +76 -10
- package/package.json +7 -7
- package/tools/README.md +49 -0
- package/tools/ci/install-chezmoi-verified.sh +68 -0
- package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
- package/docs/AI.md +0 -179
- package/docs/ARCHITECTURE.md +0 -117
- package/docs/CNAME +0 -1
- package/docs/CONFIG_STRATEGY.md +0 -124
- package/docs/COPYRIGHT +0 -7
- package/docs/ECOSYSTEM.md +0 -220
- package/docs/GOLD-STANDARD-AUDIT.md +0 -352
- package/docs/GOVERNANCE.md +0 -98
- package/docs/MAINTAINERS.md +0 -41
- package/docs/MINIMUM-TOOLCHAIN.md +0 -100
- package/docs/NAMING_CONVENTIONS.md +0 -102
- package/docs/OPENCODE.md +0 -127
- package/docs/README.md +0 -84
- package/docs/STRUCTURE.md +0 -102
- package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
- package/docs/adr/ADR-002-shell-performance.md +0 -130
- package/docs/adr/ADR-003-security-first.md +0 -158
- package/docs/adr/ADR-004-cli-architecture.md +0 -171
- package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
- package/docs/adr/ADR-006-shell-selection.md +0 -124
- package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
- package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
- package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
- package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
- package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
- package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
- package/docs/adr/README.md +0 -40
- package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
- package/docs/architecture/ARCHITECTURE.md +0 -20
- package/docs/architecture/INTEROP.md +0 -44
- package/docs/architecture/REPO_LAYOUT.md +0 -241
- package/docs/architecture/WALKTHROUGH.md +0 -86
- package/docs/architecture/fleet-deployment.md +0 -77
- package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
- package/docs/archive/LEGACY_ROADMAP.md +0 -6
- package/docs/archive/MILESTONE_v0.2.493.md +0 -47
- package/docs/archive/PLAN.md +0 -199
- package/docs/archive/REPO_AUDIT.md +0 -31
- package/docs/articles/.pages +0 -6
- package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
- package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
- package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
- package/docs/articles/index.md +0 -36
- package/docs/guides/INSTALL.md +0 -144
- package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +0 -121
- package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
- package/docs/guides/THEMING.md +0 -230
- package/docs/guides/TROUBLESHOOTING.md +0 -176
- package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
- package/docs/index.md +0 -132
- package/docs/interop/A2A.md +0 -39
- package/docs/interop/POWERSHELL.md +0 -102
- package/docs/manual/00-introduction.md +0 -89
- package/docs/manual/01-concepts/01-architecture.md +0 -138
- package/docs/manual/01-concepts/02-trust-model.md +0 -183
- package/docs/manual/01-concepts/03-theme-engine.md +0 -186
- package/docs/manual/01-concepts/04-fleet.md +0 -148
- package/docs/manual/01-concepts/05-self-healing.md +0 -204
- package/docs/manual/02-tutorials/01-first-install.md +0 -197
- package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
- package/docs/manual/02-tutorials/03-create-profile.md +0 -244
- package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
- package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
- package/docs/manual/03-reference/01-dot-cli.md +0 -475
- package/docs/manual/03-reference/02-config-files.md +0 -265
- package/docs/manual/03-reference/03-environment.md +0 -124
- package/docs/manual/03-reference/04-templates.md +0 -190
- package/docs/manual/03-reference/05-feature-flags.md +0 -187
- package/docs/manual/04-cookbook/01-recipes.md +0 -285
- package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
- package/docs/manual/04-cookbook/03-faq.md +0 -175
- package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
- package/docs/manual/05-appendices/B-security-checklist.md +0 -85
- package/docs/manual/05-appendices/C-glossary.md +0 -40
- package/docs/manual/05-appendices/D-bibliography.md +0 -58
- package/docs/manual/05-appendices/E-license.md +0 -38
- package/docs/manual/_toc.yml +0 -58
- package/docs/manual/command-index.md +0 -175
- package/docs/manual/concept-index.md +0 -170
- package/docs/manual/index.md +0 -66
- package/docs/migration/README.md +0 -81
- package/docs/migration/from-bare-git-repo.md +0 -156
- package/docs/migration/from-gnu-stow.md +0 -165
- package/docs/migration/from-plain-chezmoi.md +0 -148
- package/docs/migration/from-yadm.md +0 -187
- package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
- package/docs/operations/ATTESTATION.md +0 -44
- package/docs/operations/CI_CADENCE.md +0 -107
- package/docs/operations/CI_COMPOSITES.md +0 -156
- package/docs/operations/COMPLETIONS.md +0 -123
- package/docs/operations/COVERAGE.md +0 -204
- package/docs/operations/DRIFT.md +0 -107
- package/docs/operations/HARD_AUDIT_2026.md +0 -631
- package/docs/operations/MAINTENANCE.md +0 -63
- package/docs/operations/MANIFEST.md +0 -127
- package/docs/operations/MIGRATION.md +0 -109
- package/docs/operations/OPERATIONS.md +0 -188
- package/docs/operations/PERFORMANCE.md +0 -133
- package/docs/operations/PERFORMANCE_BUDGETS.md +0 -196
- package/docs/operations/REGISTRY.md +0 -90
- package/docs/operations/RELEASE_PIPELINE.md +0 -128
- package/docs/operations/RELIABILITY.md +0 -122
- package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
- package/docs/operations/ROADMAP.md +0 -10
- package/docs/operations/ROADMAP_2026.md +0 -7
- package/docs/operations/ROADMAP_V0_2_503.md +0 -10
- package/docs/operations/TESTING.md +0 -216
- package/docs/operations/TRACEABILITY.md +0 -44
- package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
- package/docs/operations/VERSION_SYNC.md +0 -393
- package/docs/packaging.md +0 -222
- package/docs/reference/ALIASES.md +0 -131
- package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
- package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
- package/docs/reference/FEATURE-MATRIX.md +0 -646
- package/docs/reference/FEATURES.md +0 -66
- package/docs/reference/FONTS.md +0 -112
- package/docs/reference/POWERSHELL_PARITY.md +0 -82
- package/docs/reference/PROFILES.md +0 -69
- package/docs/reference/SCREENSHOTS.md +0 -121
- package/docs/reference/SCRIPTS.md +0 -71
- package/docs/reference/SUPPORT_MATRIX.md +0 -80
- package/docs/reference/THEMES.md +0 -117
- package/docs/reference/TOOLS.md +0 -110
- package/docs/reference/UTILS.md +0 -243
- package/docs/registry.json +0 -6
- package/docs/schema/dot-env-v1.json +0 -110
- package/docs/schema/dot-registry-v1.json +0 -33
- package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
- package/docs/security/AUDIT_BYPASS.md +0 -103
- package/docs/security/AUTOMATION_SECRETS.md +0 -26
- package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
- package/docs/security/CI_PINNING.md +0 -129
- package/docs/security/COMMIT_SIGNING.md +0 -138
- package/docs/security/COMPLIANCE.md +0 -458
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
- package/docs/security/DISCLOSURE.md +0 -130
- package/docs/security/ENCRYPTION.md +0 -57
- package/docs/security/FMEA.md +0 -159
- package/docs/security/FUZZING.md +0 -209
- package/docs/security/HISTORY_FILTERING.md +0 -132
- package/docs/security/INCIDENT_RESPONSE.md +0 -579
- package/docs/security/INSTALL_VERIFICATION.md +0 -122
- package/docs/security/KEYS.md +0 -49
- package/docs/security/KEY_ROTATION.md +0 -303
- package/docs/security/MCP_POLICY.md +0 -78
- package/docs/security/POLICY_RELEASES.md +0 -37
- package/docs/security/README.md +0 -28
- package/docs/security/SCORECARD.md +0 -195
- package/docs/security/SECRETS.md +0 -158
- package/docs/security/SECURITY.md +0 -45
- package/docs/security/SECURITY_CHECKLIST.md +0 -55
- package/docs/security/SHELL_EXEMPTIONS.md +0 -145
- package/docs/security/SOUP_REGISTER.md +0 -36
- package/docs/security/THREAT_MODEL.md +0 -130
- package/docs/security/VERIFICATION_VALIDATION.md +0 -228
- package/docs/security/VERIFY_RELEASE.md +0 -201
- package/docs/security/security-pubkey.asc +0 -15
- package/docs/stylesheets/extra.css +0 -444
- package/docs/themes/README.md +0 -10
- package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
- package/docs/themes/hero-shot.svg +0 -78
- package/scripts/README.md +0 -123
- package/scripts/ci/check-copyright-headers.sh +0 -8
- package/scripts/ci/check-shell-preamble.sh +0 -8
- package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
- package/scripts/demo/record.sh +0 -43
- package/scripts/diagnostics/a2a-conformance.sh +0 -163
- package/scripts/diagnostics/alias-governance.sh +0 -165
- package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
- package/scripts/diagnostics/aliases-manifest.sh +0 -77
- package/scripts/diagnostics/attest-verify.sh +0 -147
- package/scripts/diagnostics/benchmark.sh +0 -408
- package/scripts/diagnostics/conflicts.sh +0 -73
- package/scripts/diagnostics/doctor-unified.sh +0 -43
- package/scripts/diagnostics/doctor.sh +0 -797
- package/scripts/diagnostics/drift-dashboard.sh +0 -203
- package/scripts/diagnostics/health.sh +0 -656
- package/scripts/diagnostics/history-analysis.sh +0 -86
- package/scripts/diagnostics/mcp-doctor.sh +0 -582
- package/scripts/diagnostics/perf.sh +0 -453
- package/scripts/diagnostics/scorecard.sh +0 -120
- package/scripts/diagnostics/secret-governance.sh +0 -65
- package/scripts/diagnostics/security-score.sh +0 -467
- package/scripts/diagnostics/smoke-test.sh +0 -88
- package/scripts/diagnostics/snapshot.sh +0 -90
- package/scripts/diagnostics/verify.sh +0 -108
- package/scripts/diagnostics/verify_state.sh +0 -73
- package/scripts/diagnostics/version-locks.sh +0 -94
- package/scripts/diagnostics/workstation-attestation.sh +0 -212
- package/scripts/dot/commands/agent.sh +0 -535
- package/scripts/dot/commands/agents.sh +0 -352
- package/scripts/dot/commands/ai.sh +0 -600
- package/scripts/dot/commands/aliases.sh +0 -277
- package/scripts/dot/commands/appearance.sh +0 -110
- package/scripts/dot/commands/completion.sh +0 -171
- package/scripts/dot/commands/core.sh +0 -217
- package/scripts/dot/commands/diagnostics.sh +0 -265
- package/scripts/dot/commands/env-emit.sh +0 -203
- package/scripts/dot/commands/fleet.sh +0 -711
- package/scripts/dot/commands/init.sh +0 -185
- package/scripts/dot/commands/lint.sh +0 -208
- package/scripts/dot/commands/manual.sh +0 -169
- package/scripts/dot/commands/meta.sh +0 -438
- package/scripts/dot/commands/patterns.sh +0 -55
- package/scripts/dot/commands/registry.sh +0 -455
- package/scripts/dot/commands/restore.sh +0 -232
- package/scripts/dot/commands/secrets.sh +0 -296
- package/scripts/dot/commands/security.sh +0 -102
- package/scripts/dot/commands/tools.sh +0 -570
- package/scripts/dot/data/alias-deprecations.tsv +0 -2
- package/scripts/dot/powershell/Dot.psm1 +0 -319
- package/scripts/fonts/install-nerd-fonts.sh +0 -75
- package/scripts/fonts/patch-fonts.sh +0 -36
- package/scripts/git-hooks/install.sh +0 -12
- package/scripts/git-hooks/pre-commit +0 -12
- package/scripts/git-hooks/pre-commit-audit.sh +0 -146
- package/scripts/git-hooks/pre-push +0 -105
- package/scripts/git-hooks/prepare-commit-msg +0 -29
- package/scripts/lib/secrets_provider.sh +0 -200
- package/scripts/nvim/headless-upgrade.lua +0 -81
- package/scripts/ops/ai-setup.sh +0 -71
- package/scripts/ops/bundle.sh +0 -104
- package/scripts/ops/chaos.sh +0 -50
- package/scripts/ops/chezmoi-apply.sh +0 -333
- package/scripts/ops/chezmoi-diff.sh +0 -16
- package/scripts/ops/chezmoi-remove.sh +0 -46
- package/scripts/ops/chezmoi-update.sh +0 -67
- package/scripts/ops/heal-chezmoi.sh +0 -87
- package/scripts/ops/heal-system.sh +0 -129
- package/scripts/ops/heal-tools.sh +0 -297
- package/scripts/ops/heal.sh +0 -223
- package/scripts/ops/post-apply-repair.sh +0 -107
- package/scripts/ops/prewarm.sh +0 -128
- package/scripts/ops/release.sh +0 -262
- package/scripts/ops/rollback.sh +0 -613
- package/scripts/ops/setup.sh +0 -138
- package/scripts/ops/teleport.sh +0 -34
- package/scripts/qa/check-feature-matrix.sh +0 -296
- package/scripts/qa/check-version-consistency.sh +0 -12
- package/scripts/qa/coverage-baseline.sh +0 -61
- package/scripts/qa/docs-coverage.sh +0 -118
- package/scripts/qa/examples-coverage.sh +0 -94
- package/scripts/qa/powershell-contract.ps1 +0 -95
- package/scripts/qa/reliability-audit.sh +0 -139
- package/scripts/qa/scorecard-snapshot.sh +0 -128
- package/scripts/qa/traceability-coverage.sh +0 -124
- package/scripts/qa/validate-examples.sh +0 -90
- package/scripts/qa/wsl-contract.sh +0 -12
- package/scripts/secrets/age-init.sh +0 -82
- package/scripts/secrets/create-secrets-file.sh +0 -46
- package/scripts/secrets/encrypt-ssh-key.sh +0 -44
- package/scripts/security/backup.sh +0 -58
- package/scripts/security/check-disclosure-key-expiry.sh +0 -111
- package/scripts/security/dns-doh.sh +0 -52
- package/scripts/security/encryption-check.sh +0 -55
- package/scripts/security/enforce-policies.sh +0 -552
- package/scripts/security/firewall.sh +0 -91
- package/scripts/security/lock-configs.sh +0 -67
- package/scripts/security/lock-screen.sh +0 -56
- package/scripts/security/manage-secrets.sh +0 -429
- package/scripts/security/ssh-cert.sh +0 -204
- package/scripts/security/telemetry-kill.sh +0 -51
- package/scripts/security/usb-safety.sh +0 -52
- package/scripts/theme/apply-gnome-theme.sh +0 -333
- package/scripts/theme/extract-heic-frames.sh +0 -115
- package/scripts/theme/extract-theme.py +0 -1020
- package/scripts/theme/install-boot-logo.sh +0 -63
- package/scripts/theme/install-catppuccin-themes.sh +0 -371
- package/scripts/theme/install-cursors.sh +0 -26
- package/scripts/theme/install-file-icons.sh +0 -27
- package/scripts/theme/install-grub-theme.sh +0 -62
- package/scripts/theme/install-lock-icon.sh +0 -31
- package/scripts/theme/merge-wallpaper.sh +0 -146
- package/scripts/theme/rebuild-themes.sh +0 -603
- package/scripts/theme/switch.sh +0 -476
- package/scripts/theme/wallpaper-rotate.sh +0 -137
- package/scripts/theme/wallpaper-sync.sh +0 -690
- package/scripts/tools/cmatrix.sh +0 -22
- package/scripts/tools/detect-collisions.py +0 -103
- package/scripts/tools/emoji-picker.sh +0 -49
- package/scripts/tools/figlet-banner.sh +0 -19
- package/scripts/tools/log-rotate.sh +0 -31
- package/scripts/tools/lolcat-wrap.sh +0 -20
- package/scripts/tools/pipes.sh +0 -49
- package/scripts/tuning/linux.sh +0 -186
- package/scripts/tuning/macos.sh +0 -56
- package/scripts/uninstall.sh +0 -86
- package/scripts/verify-release-versions +0 -156
- package/scripts/version-sync.sh +0 -714
- package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
- package/templates/chezmoi-data/mac-m1.toml.example +0 -16
- package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
- package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
- package/templates/projects/go/.github/workflows/ci.yml +0 -31
- package/templates/projects/go/README.md +0 -7
- package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
- package/templates/projects/go/go.mod +0 -3
- package/templates/projects/go/go.sum +0 -0
- package/templates/projects/molecule/README.md +0 -7
- package/templates/projects/molecule/converge.yml +0 -7
- package/templates/projects/molecule/molecule.yml +0 -16
- package/templates/projects/node/.github/workflows/ci.yml +0 -30
- package/templates/projects/node/README.md +0 -7
- package/templates/projects/node/package-lock.json +0 -12
- package/templates/projects/node/package.json +0 -10
- package/templates/projects/node/src/index.js +0 -3
- package/templates/projects/packer/README.md +0 -15
- package/templates/projects/packer/main.pkr.hcl +0 -15
- package/templates/projects/python/.github/workflows/ci.yml +0 -34
- package/templates/projects/python/README.md +0 -7
- package/templates/projects/python/pyproject.toml +0 -25
- package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
- package/templates/projects/python/tests/test_basic.py +0 -3
package/scripts/ops/rollback.sh
DELETED
|
@@ -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 "$@"
|