@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
|
@@ -1,455 +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 shell=bash
|
|
5
|
-
#
|
|
6
|
-
# scripts/dot/commands/registry.sh
|
|
7
|
-
#
|
|
8
|
-
# `dot registry` — verified module registry for reusable chezmoi sources.
|
|
9
|
-
#
|
|
10
|
-
# §3 audit roadmap: ship a registry of reusable dotfile modules
|
|
11
|
-
# ("rust-dev-setup", "k8s-operator-laptop") to seed network effects.
|
|
12
|
-
# Hosted as a GitHub-Pages-indexed JSON file to keep ops cost near
|
|
13
|
-
# zero.
|
|
14
|
-
#
|
|
15
|
-
# Subcommands:
|
|
16
|
-
# list Show modules in the configured registry
|
|
17
|
-
# search <q> Filter modules by keyword (name, description, tags)
|
|
18
|
-
# info <name> Print full metadata for a module
|
|
19
|
-
# install <name> Verify and preview a module; --yes applies it.
|
|
20
|
-
# url Show the active registry URL
|
|
21
|
-
# set-url <u> Override the registry URL (writes to user config)
|
|
22
|
-
#
|
|
23
|
-
# Registry JSON shape:
|
|
24
|
-
# {
|
|
25
|
-
# "version": 1,
|
|
26
|
-
# "updated": "2026-05-15T16:00:00Z",
|
|
27
|
-
# "modules": [
|
|
28
|
-
# { "name": "rust-dev-setup",
|
|
29
|
-
# "description": "Rust toolchain + cargo plugins + IDE config",
|
|
30
|
-
# "repo": "https://github.com/example/rust-dev-setup",
|
|
31
|
-
# "tags": ["rust", "dev", "language"],
|
|
32
|
-
# "maintainer": "alice@example.com",
|
|
33
|
-
# "version": "1.2.0",
|
|
34
|
-
# "archive_url": "https://example.com/rust-dev-setup-1.2.0.tar.gz",
|
|
35
|
-
# "sha256": "<64 lowercase hexadecimal characters>" }
|
|
36
|
-
# ]
|
|
37
|
-
# }
|
|
38
|
-
|
|
39
|
-
set -euo pipefail
|
|
40
|
-
|
|
41
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
42
|
-
# shellcheck source=../../../lib/dot/ui.sh disable=SC1091
|
|
43
|
-
source "$SCRIPT_DIR/../../../lib/dot/ui.sh"
|
|
44
|
-
# shellcheck source=../../../lib/dot/utils.sh disable=SC1091
|
|
45
|
-
source "$SCRIPT_DIR/../../../lib/dot/utils.sh"
|
|
46
|
-
# shellcheck source=../../../lib/dot/verified-download.sh disable=SC1091
|
|
47
|
-
source "$SCRIPT_DIR/../../../lib/dot/verified-download.sh"
|
|
48
|
-
|
|
49
|
-
_registry_default_url() {
|
|
50
|
-
printf '%s\n' "https://sebastienrousseau.github.io/dotfiles/registry.json"
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
_registry_config_file() {
|
|
54
|
-
printf '%s/dotfiles/registry.toml\n' "${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
_registry_url() {
|
|
58
|
-
if [[ -n "${DOTFILES_REGISTRY_URL:-}" ]]; then
|
|
59
|
-
printf '%s\n' "$DOTFILES_REGISTRY_URL"
|
|
60
|
-
return
|
|
61
|
-
fi
|
|
62
|
-
local cfg
|
|
63
|
-
cfg="$(_registry_config_file)"
|
|
64
|
-
if [[ -f "$cfg" ]]; then
|
|
65
|
-
local u
|
|
66
|
-
u="$(awk -F'[ \t]*=[ \t]*' '/^url[ \t]*=/{gsub(/"/,"",$2); print $2; exit}' "$cfg")"
|
|
67
|
-
[[ -n "$u" ]] && {
|
|
68
|
-
printf '%s\n' "$u"
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
fi
|
|
72
|
-
_registry_default_url
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
_registry_cache_dir() {
|
|
76
|
-
printf '%s/dotfiles/registry\n' "${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
## The index cache is keyed by URL, not by one fixed path.
|
|
80
|
-
##
|
|
81
|
-
## It used to live at <cache>/index.json for every registry, and be treated as
|
|
82
|
-
## fresh for six hours: a freshness window says nothing about WHICH registry
|
|
83
|
-
## produced the file, so changing DOTFILES_REGISTRY_URL — or running
|
|
84
|
-
## `dot registry set-url` — kept serving the previous registry's index for up
|
|
85
|
-
## to six hours. Keying by URL also means switching back and forth does not
|
|
86
|
-
## re-download.
|
|
87
|
-
##
|
|
88
|
-
## SHA-256 where a hasher exists, POSIX `cksum` otherwise: this is a cache
|
|
89
|
-
## key, not a security boundary, and every index is schema-validated on read.
|
|
90
|
-
_registry_cache_key() {
|
|
91
|
-
local url="$1" digest=""
|
|
92
|
-
if command -v shasum >/dev/null 2>&1; then
|
|
93
|
-
digest="$(printf '%s' "$url" | shasum -a 256 2>/dev/null | awk '{print $1}')"
|
|
94
|
-
elif command -v sha256sum >/dev/null 2>&1; then
|
|
95
|
-
digest="$(printf '%s' "$url" | sha256sum 2>/dev/null | awk '{print $1}')"
|
|
96
|
-
fi
|
|
97
|
-
if [[ -z "$digest" ]]; then
|
|
98
|
-
digest="$(printf '%s' "$url" | cksum | awk '{print $1 "-" $2}')"
|
|
99
|
-
fi
|
|
100
|
-
printf '%s\n' "${digest:0:32}"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
## _registry_cache_file [url] — absolute path of the cached index for a URL
|
|
104
|
-
## (the active one by default). Tests seed the cache through this.
|
|
105
|
-
_registry_cache_file() {
|
|
106
|
-
local url="${1:-}"
|
|
107
|
-
[[ -n "$url" ]] || url="$(_registry_url)"
|
|
108
|
-
printf '%s/index-%s.json\n' "$(_registry_cache_dir)" "$(_registry_cache_key "$url")"
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
_registry_data_dir() {
|
|
112
|
-
printf '%s/dotfiles/modules\n' "${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
_registry_validate_index() {
|
|
116
|
-
local index="$1"
|
|
117
|
-
jq -e '
|
|
118
|
-
.version == 1 and
|
|
119
|
-
(.modules | type == "array") and
|
|
120
|
-
all(.modules[];
|
|
121
|
-
(.name | test("^[a-z0-9][a-z0-9-]{0,31}$")) and
|
|
122
|
-
(.version | test("^[0-9]+\\.[0-9]+\\.[0-9]+([+-][0-9A-Za-z.-]+)?$")) and
|
|
123
|
-
(.description | type == "string" and length <= 200) and
|
|
124
|
-
(.archive_url | test("^(https|file)://")) and
|
|
125
|
-
(.sha256 | test("^[0-9a-f]{64}$"))
|
|
126
|
-
)
|
|
127
|
-
' "$index" >/dev/null 2>&1
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
# Prints the path of a validated index file on stdout. Every diagnostic goes
|
|
131
|
-
# to stderr: stdout is this function's return channel, and callers read it
|
|
132
|
-
# with `index="$(_registry_fetch)"` — a warning printed here would be
|
|
133
|
-
# captured into that variable and then handed to jq as a filename.
|
|
134
|
-
_registry_fetch() {
|
|
135
|
-
local url cache_dir cache_file
|
|
136
|
-
url="$(_registry_url)"
|
|
137
|
-
[[ "$url" =~ ^(https://|file://) ]] || {
|
|
138
|
-
ui_err "registry" "registry URL must use https:// (or file:// for local testing)" >&2
|
|
139
|
-
return 1
|
|
140
|
-
}
|
|
141
|
-
cache_dir="$(_registry_cache_dir)"
|
|
142
|
-
cache_file="$(_registry_cache_file "$url")"
|
|
143
|
-
mkdir -p "$cache_dir"
|
|
144
|
-
# Refresh if older than 6h or missing.
|
|
145
|
-
local now mtime
|
|
146
|
-
now="$(date +%s)"
|
|
147
|
-
if [[ -s "$cache_file" ]]; then
|
|
148
|
-
if ! _registry_validate_index "$cache_file"; then
|
|
149
|
-
rm -f "$cache_file"
|
|
150
|
-
elif mtime="$(stat -c %Y "$cache_file" 2>/dev/null || stat -f %m "$cache_file" 2>/dev/null)"; then
|
|
151
|
-
if ((now - mtime < 21600)); then
|
|
152
|
-
printf '%s\n' "$cache_file"
|
|
153
|
-
return 0
|
|
154
|
-
fi
|
|
155
|
-
fi
|
|
156
|
-
fi
|
|
157
|
-
if ! command -v curl >/dev/null 2>&1; then
|
|
158
|
-
ui_err "registry" "curl not installed" >&2
|
|
159
|
-
return 127
|
|
160
|
-
fi
|
|
161
|
-
local tmp
|
|
162
|
-
tmp="$(mktemp "${cache_file}.XXXXXX")"
|
|
163
|
-
# `-fsSL -o file` should be silent, but with some upstreams (e.g.
|
|
164
|
-
# GitHub Pages) curl still writes a stray newline to stdout. That
|
|
165
|
-
# newline leaks into the caller's `$(_registry_fetch)` and later
|
|
166
|
-
# into `jq FILE` as a two-argument invocation
|
|
167
|
-
# (`jq \n /path/to/file`), producing a confusing
|
|
168
|
-
# "Could not open file" error. Silence stdout explicitly.
|
|
169
|
-
local curl_args=(-fsSL --max-time 15)
|
|
170
|
-
if [[ "$url" == https://* ]]; then
|
|
171
|
-
curl_args+=(--proto '=https' --tlsv1.2)
|
|
172
|
-
fi
|
|
173
|
-
if ! curl "${curl_args[@]}" -o "$tmp" "$url" >/dev/null; then
|
|
174
|
-
rm -f "$tmp"
|
|
175
|
-
if [[ -s "$cache_file" ]]; then
|
|
176
|
-
ui_warn "registry" "fetch failed; using stale cache at $cache_file" >&2
|
|
177
|
-
printf '%s\n' "$cache_file"
|
|
178
|
-
return 0
|
|
179
|
-
fi
|
|
180
|
-
ui_err "registry" "could not fetch $url" >&2
|
|
181
|
-
return 1
|
|
182
|
-
fi
|
|
183
|
-
mv "$tmp" "$cache_file"
|
|
184
|
-
if ! _registry_validate_index "$cache_file"; then
|
|
185
|
-
rm -f "$cache_file"
|
|
186
|
-
ui_err "registry" "index failed schema and integrity validation" >&2
|
|
187
|
-
return 1
|
|
188
|
-
fi
|
|
189
|
-
printf '%s\n' "$cache_file"
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
_registry_require_jq() {
|
|
193
|
-
command -v jq >/dev/null 2>&1 || {
|
|
194
|
-
ui_err "registry" "jq is required"
|
|
195
|
-
return 127
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
_registry_archive_is_safe() {
|
|
200
|
-
local archive="$1"
|
|
201
|
-
local entry
|
|
202
|
-
while IFS= read -r entry; do
|
|
203
|
-
[[ -n "$entry" ]] || continue
|
|
204
|
-
if [[ "$entry" == /* || "$entry" == ../* || "$entry" == *"/../"* || "$entry" == *"/.." ]]; then
|
|
205
|
-
ui_err "registry" "archive contains unsafe path: $entry"
|
|
206
|
-
return 1
|
|
207
|
-
fi
|
|
208
|
-
done < <(tar -tzf "$archive")
|
|
209
|
-
if tar -tvzf "$archive" | awk 'substr($1,1,1) == "l" || substr($1,1,1) == "h" { found=1 } END { exit !found }'; then
|
|
210
|
-
ui_err "registry" "archive contains links; links are forbidden in registry modules"
|
|
211
|
-
return 1
|
|
212
|
-
fi
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
_registry_install() (
|
|
216
|
-
local name="$1"
|
|
217
|
-
local apply="${2:-0}"
|
|
218
|
-
local index metadata version archive_url expected tmp archive extract module_root actual destination
|
|
219
|
-
|
|
220
|
-
[[ "$name" =~ ^[a-z0-9][a-z0-9-]{0,31}$ ]] || {
|
|
221
|
-
ui_err "install" "invalid module name: $name"
|
|
222
|
-
return 1
|
|
223
|
-
}
|
|
224
|
-
_registry_require_jq || return $?
|
|
225
|
-
index="$(_registry_fetch)" || return $?
|
|
226
|
-
_registry_validate_index "$index" || {
|
|
227
|
-
ui_err "registry" "index failed schema validation"
|
|
228
|
-
return 1
|
|
229
|
-
}
|
|
230
|
-
metadata="$(jq -c --arg name "$name" '.modules[] | select(.name == $name)' "$index")"
|
|
231
|
-
[[ -n "$metadata" ]] || {
|
|
232
|
-
ui_err "install" "module not found: $name"
|
|
233
|
-
return 1
|
|
234
|
-
}
|
|
235
|
-
version="$(jq -r '.version' <<<"$metadata")"
|
|
236
|
-
archive_url="$(jq -r '.archive_url' <<<"$metadata")"
|
|
237
|
-
expected="$(jq -r '.sha256' <<<"$metadata")"
|
|
238
|
-
|
|
239
|
-
tmp="$(mktemp -d -t dot-registry.XXXXXX)"
|
|
240
|
-
archive="$tmp/module.tar.gz"
|
|
241
|
-
extract="$tmp/source"
|
|
242
|
-
trap 'rm -rf "$tmp"' EXIT
|
|
243
|
-
mkdir -p "$extract"
|
|
244
|
-
local curl_args=(-fsSL --max-time 60)
|
|
245
|
-
if [[ "$archive_url" == https://* ]]; then
|
|
246
|
-
curl_args+=(--proto '=https' --tlsv1.2)
|
|
247
|
-
fi
|
|
248
|
-
if ! curl "${curl_args[@]}" -o "$archive" "$archive_url"; then
|
|
249
|
-
ui_err "install" "could not download $archive_url"
|
|
250
|
-
return 1
|
|
251
|
-
fi
|
|
252
|
-
local archive_size
|
|
253
|
-
archive_size="$(wc -c <"$archive" | tr -d '[:space:]')"
|
|
254
|
-
if ((archive_size > 52428800)); then
|
|
255
|
-
ui_err "install" "archive exceeds the 50 MiB safety limit"
|
|
256
|
-
return 1
|
|
257
|
-
fi
|
|
258
|
-
actual="$(_dot_sha256_file "$archive")" || return $?
|
|
259
|
-
[[ "$actual" == "$expected" ]] || {
|
|
260
|
-
ui_err "install" "SHA-256 mismatch for $name@$version"
|
|
261
|
-
return 1
|
|
262
|
-
}
|
|
263
|
-
_registry_archive_is_safe "$archive" || return $?
|
|
264
|
-
tar -xzf "$archive" -C "$extract" --no-same-owner --no-same-permissions
|
|
265
|
-
|
|
266
|
-
module_root="$extract"
|
|
267
|
-
local roots=()
|
|
268
|
-
while IFS= read -r entry; do roots+=("$entry"); done < <(find "$extract" -mindepth 1 -maxdepth 1 -print)
|
|
269
|
-
if [[ ${#roots[@]} -eq 1 && -d "${roots[0]}" ]]; then
|
|
270
|
-
module_root="${roots[0]}"
|
|
271
|
-
fi
|
|
272
|
-
[[ -n "$(find "$module_root" -mindepth 1 -print -quit)" ]] || {
|
|
273
|
-
ui_err "install" "module archive is empty"
|
|
274
|
-
return 1
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
ui_ok "Verified" "$name@$version ($actual)"
|
|
278
|
-
ui_section "Chezmoi preview"
|
|
279
|
-
chezmoi apply --source "$module_root" --destination "$HOME" --dry-run --no-tty
|
|
280
|
-
if [[ "$apply" != "1" ]]; then
|
|
281
|
-
ui_info "Preview only" "rerun with --yes to install and apply"
|
|
282
|
-
return 0
|
|
283
|
-
fi
|
|
284
|
-
|
|
285
|
-
destination="$(_registry_data_dir)/$name/$version"
|
|
286
|
-
mkdir -p "$(dirname "$destination")"
|
|
287
|
-
rm -rf "$destination"
|
|
288
|
-
mv "$module_root" "$destination"
|
|
289
|
-
chezmoi apply --source "$destination" --destination "$HOME" --no-tty
|
|
290
|
-
printf '%s\n' "$metadata" >"$(dirname "$destination")/installed.json"
|
|
291
|
-
ui_ok "Installed" "$name@$version"
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
cmd_registry() {
|
|
295
|
-
local subcommand="${1:-list}"
|
|
296
|
-
shift || true
|
|
297
|
-
|
|
298
|
-
case "$subcommand" in
|
|
299
|
-
url)
|
|
300
|
-
printf '%s\n' "$(_registry_url)"
|
|
301
|
-
;;
|
|
302
|
-
set-url)
|
|
303
|
-
local new_url="${1:-}"
|
|
304
|
-
[[ -n "$new_url" ]] || {
|
|
305
|
-
ui_err "set-url" "missing URL"
|
|
306
|
-
return 1
|
|
307
|
-
}
|
|
308
|
-
# Refuse non-HTTPS schemes. The registry index is unsigned today,
|
|
309
|
-
# so HTTPS is the only transport that gives us cert-pinned
|
|
310
|
-
# integrity. The `file://` exemption is for local testing only
|
|
311
|
-
# (the bench-script and unit tests use it).
|
|
312
|
-
if [[ ! "$new_url" =~ ^(https://|file://) ]]; then
|
|
313
|
-
ui_err "set-url" "registry URL must use https:// (or file:// for local testing) — got: $new_url"
|
|
314
|
-
return 1
|
|
315
|
-
fi
|
|
316
|
-
local cfg
|
|
317
|
-
cfg="$(_registry_config_file)"
|
|
318
|
-
mkdir -p "$(dirname "$cfg")"
|
|
319
|
-
# Atomic write so a concurrent invocation can't read a half-
|
|
320
|
-
# written file. Explicit if/else (avoid SC2015 A && B || C).
|
|
321
|
-
local _tmp
|
|
322
|
-
_tmp="$(mktemp "${cfg}.XXXXXX")"
|
|
323
|
-
if printf 'url = "%s"\n' "$new_url" >"$_tmp"; then
|
|
324
|
-
if ! mv "$_tmp" "$cfg"; then
|
|
325
|
-
rm -f "$_tmp"
|
|
326
|
-
ui_err "set-url" "failed to commit $cfg"
|
|
327
|
-
return 1
|
|
328
|
-
fi
|
|
329
|
-
else
|
|
330
|
-
rm -f "$_tmp"
|
|
331
|
-
ui_err "set-url" "failed to write $cfg"
|
|
332
|
-
return 1
|
|
333
|
-
fi
|
|
334
|
-
ui_ok "registry" "set to $new_url ($cfg)"
|
|
335
|
-
;;
|
|
336
|
-
list)
|
|
337
|
-
_registry_require_jq || return $?
|
|
338
|
-
local index
|
|
339
|
-
index="$(_registry_fetch)" || return $?
|
|
340
|
-
ui_header "Registry modules"
|
|
341
|
-
ui_info "Source" "$(_registry_url)"
|
|
342
|
-
echo ""
|
|
343
|
-
if ! jq -e '.modules | length > 0' "$index" >/dev/null 2>&1; then
|
|
344
|
-
ui_warn "registry" "no modules published yet — see docs/operations/REGISTRY.md to contribute one"
|
|
345
|
-
return 0
|
|
346
|
-
fi
|
|
347
|
-
ui_table_begin "Module" "Version" "Description"
|
|
348
|
-
while IFS=$'\t' read -r name ver desc; do
|
|
349
|
-
ui_table_add "$name" "v$ver" "$desc"
|
|
350
|
-
done < <(jq -r '.modules[] | "\(.name)\t\(.version // "-")\t\(.description // "")"' "$index")
|
|
351
|
-
ui_table_end
|
|
352
|
-
;;
|
|
353
|
-
search)
|
|
354
|
-
_registry_require_jq || return $?
|
|
355
|
-
local q="${1:-}"
|
|
356
|
-
[[ -n "$q" ]] || {
|
|
357
|
-
ui_err "search" "missing query"
|
|
358
|
-
return 1
|
|
359
|
-
}
|
|
360
|
-
local index
|
|
361
|
-
index="$(_registry_fetch)" || return $?
|
|
362
|
-
ui_header "Registry search: $q"
|
|
363
|
-
echo ""
|
|
364
|
-
ui_table_begin "Module" "Version" "Description"
|
|
365
|
-
while IFS=$'\t' read -r name ver desc; do
|
|
366
|
-
ui_table_add "$name" "v$ver" "$desc"
|
|
367
|
-
done < <(jq -r --arg q "$q" '
|
|
368
|
-
.modules[]
|
|
369
|
-
| select(
|
|
370
|
-
(.name // "" | ascii_downcase | contains($q | ascii_downcase)) or
|
|
371
|
-
(.description // "" | ascii_downcase | contains($q | ascii_downcase)) or
|
|
372
|
-
((.tags // []) | map(ascii_downcase) | index($q | ascii_downcase))
|
|
373
|
-
)
|
|
374
|
-
| "\(.name)\t\(.version // "-")\t\(.description // "")"
|
|
375
|
-
' "$index")
|
|
376
|
-
ui_table_end
|
|
377
|
-
;;
|
|
378
|
-
info)
|
|
379
|
-
_registry_require_jq || return $?
|
|
380
|
-
local name="${1:-}"
|
|
381
|
-
[[ -n "$name" ]] || {
|
|
382
|
-
ui_err "info" "missing module name"
|
|
383
|
-
return 1
|
|
384
|
-
}
|
|
385
|
-
local index
|
|
386
|
-
index="$(_registry_fetch)" || return $?
|
|
387
|
-
local found
|
|
388
|
-
found="$(jq -r --arg n "$name" '.modules[] | select(.name == $n) | "OK"' "$index" 2>/dev/null)"
|
|
389
|
-
if [[ "$found" != "OK" ]]; then
|
|
390
|
-
ui_err "info" "module not found: $name"
|
|
391
|
-
return 1
|
|
392
|
-
fi
|
|
393
|
-
jq -r --arg n "$name" '.modules[] | select(.name == $n) | to_entries[] | "\(.key)\t\(.value | if type == "array" then join(", ") else tostring end)"' "$index" |
|
|
394
|
-
while IFS=$'\t' read -r key value; do
|
|
395
|
-
ui_ok "$key" "$value"
|
|
396
|
-
done
|
|
397
|
-
;;
|
|
398
|
-
install)
|
|
399
|
-
local name="${1:-}"
|
|
400
|
-
[[ -n "$name" ]] || {
|
|
401
|
-
ui_err "install" "missing module name"
|
|
402
|
-
return 1
|
|
403
|
-
}
|
|
404
|
-
shift || true
|
|
405
|
-
local apply=0
|
|
406
|
-
case "${1:-}" in
|
|
407
|
-
"") ;;
|
|
408
|
-
--yes | -y) apply=1 ;;
|
|
409
|
-
--dry-run | -n) apply=0 ;;
|
|
410
|
-
*)
|
|
411
|
-
ui_err "install" "unknown option: $1"
|
|
412
|
-
return 2
|
|
413
|
-
;;
|
|
414
|
-
esac
|
|
415
|
-
_registry_install "$name" "$apply"
|
|
416
|
-
;;
|
|
417
|
-
installed)
|
|
418
|
-
_registry_require_jq || return $?
|
|
419
|
-
local modules_dir
|
|
420
|
-
modules_dir="$(_registry_data_dir)"
|
|
421
|
-
if [[ ! -d "$modules_dir" ]]; then
|
|
422
|
-
ui_info "registry" "no modules installed"
|
|
423
|
-
return 0
|
|
424
|
-
fi
|
|
425
|
-
find "$modules_dir" -name installed.json -type f -exec jq -r '"\(.name)\t\(.version)\t\(.description)"' {} \; |
|
|
426
|
-
while IFS=$'\t' read -r module version description; do
|
|
427
|
-
ui_ok "$module" "v$version — $description"
|
|
428
|
-
done
|
|
429
|
-
;;
|
|
430
|
-
--help | -h | help)
|
|
431
|
-
cat <<EOF
|
|
432
|
-
Usage: dot registry <subcommand>
|
|
433
|
-
|
|
434
|
-
Subcommands:
|
|
435
|
-
list List modules in the configured registry
|
|
436
|
-
search <q> Filter modules by keyword (name, description, tags)
|
|
437
|
-
info <name> Print metadata for a single module
|
|
438
|
-
install <name> Verify and preview a module; pass --yes to apply
|
|
439
|
-
installed List locally installed modules
|
|
440
|
-
url Show the active registry URL
|
|
441
|
-
set-url <url> Override the registry URL (persists to user config)
|
|
442
|
-
|
|
443
|
-
Env overrides:
|
|
444
|
-
DOTFILES_REGISTRY_URL One-shot override of the registry URL.
|
|
445
|
-
|
|
446
|
-
Default registry: $(_registry_default_url)
|
|
447
|
-
EOF
|
|
448
|
-
;;
|
|
449
|
-
*)
|
|
450
|
-
ui_err "Unknown subcommand" "$subcommand"
|
|
451
|
-
echo "Run 'dot registry --help' for usage." >&2
|
|
452
|
-
return 1
|
|
453
|
-
;;
|
|
454
|
-
esac
|
|
455
|
-
}
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
|
-
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
|
-
# dot restore - Restore dotfiles from backup or previous state
|
|
5
|
-
# Usage: dot restore [--list|-l|--latest|-L|<backup-id>]
|
|
6
|
-
|
|
7
|
-
set -euo pipefail
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
-
# shellcheck source=../../../lib/dot/utils.sh
|
|
11
|
-
source "$SCRIPT_DIR/../../../lib/dot/utils.sh"
|
|
12
|
-
# shellcheck source=../../../lib/dot/log.sh
|
|
13
|
-
source "$SCRIPT_DIR/../../../lib/dot/log.sh"
|
|
14
|
-
|
|
15
|
-
DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}"
|
|
16
|
-
BACKUP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/dotfiles/backups"
|
|
17
|
-
CHEZMOI_SOURCE="${HOME}/.local/share/chezmoi"
|
|
18
|
-
|
|
19
|
-
ui_init
|
|
20
|
-
|
|
21
|
-
usage() {
|
|
22
|
-
echo "Usage: dot restore [OPTIONS]"
|
|
23
|
-
echo ""
|
|
24
|
-
echo "Options:"
|
|
25
|
-
echo " --list, -l List available backups"
|
|
26
|
-
echo " --latest, -L Restore from latest backup"
|
|
27
|
-
echo " --git, -g <ref> Restore from git ref (commit, tag, branch)"
|
|
28
|
-
echo " --diff, -d <ref> Show diff between current and ref"
|
|
29
|
-
echo " --dry-run, -n Show what would be restored"
|
|
30
|
-
echo " -h, --help Show this help"
|
|
31
|
-
echo ""
|
|
32
|
-
echo "Examples:"
|
|
33
|
-
echo " dot restore --list"
|
|
34
|
-
echo " dot restore --latest"
|
|
35
|
-
echo " dot restore --git HEAD~1"
|
|
36
|
-
echo " dot restore --git v0.2.470"
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
list_backups() {
|
|
40
|
-
if [[ ! -d "$BACKUP_DIR" ]]; then
|
|
41
|
-
log_warn "No backups found at $BACKUP_DIR"
|
|
42
|
-
return 1
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
ui_header "Available Backups"
|
|
46
|
-
echo "─────────────────────────────────────────"
|
|
47
|
-
|
|
48
|
-
while IFS= read -r backup; do
|
|
49
|
-
echo " $backup"
|
|
50
|
-
done < <(list_backup_names)
|
|
51
|
-
|
|
52
|
-
echo ""
|
|
53
|
-
echo ""
|
|
54
|
-
ui_header "Git History (last 10)"
|
|
55
|
-
echo "─────────────────────────────────────────"
|
|
56
|
-
|
|
57
|
-
if [[ -d "$DOTFILES_DIR/.git" ]]; then
|
|
58
|
-
git -C "$DOTFILES_DIR" log --oneline -10
|
|
59
|
-
elif [[ -d "$CHEZMOI_SOURCE/.git" ]]; then
|
|
60
|
-
git -C "$CHEZMOI_SOURCE" log --oneline -10
|
|
61
|
-
fi
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
restore_from_git() {
|
|
65
|
-
local ref="$1"
|
|
66
|
-
local dry_run="${2:-false}"
|
|
67
|
-
local source_dir
|
|
68
|
-
|
|
69
|
-
if [[ -d "$DOTFILES_DIR/.git" ]]; then
|
|
70
|
-
source_dir="$DOTFILES_DIR"
|
|
71
|
-
elif [[ -d "$CHEZMOI_SOURCE/.git" ]]; then
|
|
72
|
-
source_dir="$CHEZMOI_SOURCE"
|
|
73
|
-
else
|
|
74
|
-
log_error "No git repository found"
|
|
75
|
-
return 1
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
log_info "Restoring from git ref: $ref"
|
|
79
|
-
|
|
80
|
-
if $dry_run; then
|
|
81
|
-
log_info "Dry run - showing changes:"
|
|
82
|
-
git -C "$source_dir" diff "$ref" --stat
|
|
83
|
-
return 0
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
# Create backup first
|
|
87
|
-
create_backup
|
|
88
|
-
|
|
89
|
-
# Restore
|
|
90
|
-
git -C "$source_dir" checkout "$ref" -- .
|
|
91
|
-
log_success "Restored from $ref"
|
|
92
|
-
|
|
93
|
-
# Re-apply chezmoi
|
|
94
|
-
if has_command chezmoi; then
|
|
95
|
-
log_info "Re-applying chezmoi..."
|
|
96
|
-
chezmoi apply
|
|
97
|
-
fi
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
show_diff() {
|
|
101
|
-
local ref="$1"
|
|
102
|
-
local source_dir
|
|
103
|
-
|
|
104
|
-
if [[ -d "$DOTFILES_DIR/.git" ]]; then
|
|
105
|
-
source_dir="$DOTFILES_DIR"
|
|
106
|
-
elif [[ -d "$CHEZMOI_SOURCE/.git" ]]; then
|
|
107
|
-
source_dir="$CHEZMOI_SOURCE"
|
|
108
|
-
else
|
|
109
|
-
log_error "No git repository found"
|
|
110
|
-
return 1
|
|
111
|
-
fi
|
|
112
|
-
|
|
113
|
-
git -C "$source_dir" diff "$ref"
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
create_backup() {
|
|
117
|
-
local backup_name
|
|
118
|
-
backup_name="backup-$(date +%Y%m%d_%H%M%S)"
|
|
119
|
-
local backup_path="$BACKUP_DIR/$backup_name"
|
|
120
|
-
local rel_path
|
|
121
|
-
|
|
122
|
-
mkdir -p "$backup_path"
|
|
123
|
-
|
|
124
|
-
log_info "Creating backup: $backup_name"
|
|
125
|
-
|
|
126
|
-
# Backup key config files
|
|
127
|
-
local files_to_backup=(
|
|
128
|
-
"$HOME/.zshrc"
|
|
129
|
-
"$HOME/.config/zsh"
|
|
130
|
-
"$HOME/.config/nvim"
|
|
131
|
-
"$HOME/.config/git"
|
|
132
|
-
"$HOME/.gitconfig"
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
for f in "${files_to_backup[@]}"; do
|
|
136
|
-
if [[ -e "$f" ]]; then
|
|
137
|
-
rel_path="${f#"$HOME"/}"
|
|
138
|
-
mkdir -p "$backup_path/$(dirname "$rel_path")"
|
|
139
|
-
cp -r "$f" "$backup_path/$rel_path" 2>/dev/null || true
|
|
140
|
-
fi
|
|
141
|
-
done
|
|
142
|
-
|
|
143
|
-
log_success "Backup created at $backup_path"
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
restore_latest() {
|
|
147
|
-
if [[ ! -d "$BACKUP_DIR" ]]; then
|
|
148
|
-
log_error "No backups found"
|
|
149
|
-
return 1
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
local latest
|
|
153
|
-
latest=$(list_backup_names | head -1)
|
|
154
|
-
|
|
155
|
-
if [[ -z "$latest" ]]; then
|
|
156
|
-
log_error "No backups found"
|
|
157
|
-
return 1
|
|
158
|
-
fi
|
|
159
|
-
|
|
160
|
-
log_info "Restoring from: $latest"
|
|
161
|
-
|
|
162
|
-
local backup_path="$BACKUP_DIR/$latest"
|
|
163
|
-
local item rel_path target
|
|
164
|
-
while IFS= read -r -d '' item; do
|
|
165
|
-
rel_path="${item#"$backup_path"/}"
|
|
166
|
-
target="$HOME/$rel_path"
|
|
167
|
-
mkdir -p "$(dirname "$target")"
|
|
168
|
-
cp -r "$item" "$target"
|
|
169
|
-
log_success "Restored: $rel_path"
|
|
170
|
-
done < <(find "$backup_path" -mindepth 1 -maxdepth 1 -print0)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
list_backup_names() {
|
|
174
|
-
local backup_path
|
|
175
|
-
|
|
176
|
-
shopt -s nullglob
|
|
177
|
-
for backup_path in "$BACKUP_DIR"/backup-*; do
|
|
178
|
-
[[ -d "$backup_path" ]] || continue
|
|
179
|
-
printf '%s\n' "${backup_path##*/}"
|
|
180
|
-
done | while IFS= read -r backup_name; do
|
|
181
|
-
printf '%s\t%s\n' "$(portable_mtime "$BACKUP_DIR/$backup_name")" "$backup_name"
|
|
182
|
-
done | sort -rn | cut -f2-
|
|
183
|
-
shopt -u nullglob
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
portable_mtime() {
|
|
187
|
-
if stat -c %Y "$1" >/dev/null 2>&1; then
|
|
188
|
-
stat -c %Y "$1"
|
|
189
|
-
else
|
|
190
|
-
stat -f %m "$1"
|
|
191
|
-
fi
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
# Parse arguments
|
|
195
|
-
DRY_RUN=false
|
|
196
|
-
|
|
197
|
-
while [[ $# -gt 0 ]]; do
|
|
198
|
-
case "$1" in
|
|
199
|
-
-l | --list)
|
|
200
|
-
list_backups
|
|
201
|
-
exit 0
|
|
202
|
-
;;
|
|
203
|
-
--latest | -L)
|
|
204
|
-
restore_latest
|
|
205
|
-
exit 0
|
|
206
|
-
;;
|
|
207
|
-
--git | -g)
|
|
208
|
-
restore_from_git "$2" "$DRY_RUN"
|
|
209
|
-
exit 0
|
|
210
|
-
;;
|
|
211
|
-
--diff | -d)
|
|
212
|
-
show_diff "$2"
|
|
213
|
-
exit 0
|
|
214
|
-
;;
|
|
215
|
-
--dry-run | -n)
|
|
216
|
-
DRY_RUN=true
|
|
217
|
-
shift
|
|
218
|
-
;;
|
|
219
|
-
-h | --help)
|
|
220
|
-
usage
|
|
221
|
-
exit 0
|
|
222
|
-
;;
|
|
223
|
-
*)
|
|
224
|
-
log_error "Unknown option: $1"
|
|
225
|
-
usage
|
|
226
|
-
exit 1
|
|
227
|
-
;;
|
|
228
|
-
esac
|
|
229
|
-
done
|
|
230
|
-
|
|
231
|
-
# Default: show usage
|
|
232
|
-
usage
|