@sebastienrousseau/dotfiles 0.2.500 → 0.2.502
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 +154 -0
- package/README.md +113 -45
- package/docs/.vitepress/reports/localization-readability-audit.md +4 -0
- package/docs/AI.md +8 -2
- package/docs/CNAME +1 -0
- package/docs/COPYRIGHT +1 -1
- package/docs/NAMING_CONVENTIONS.md +7 -0
- package/docs/README.md +4 -0
- package/docs/_config.yml +59 -0
- package/docs/adr/ADR-001-ci-cd-pipeline.md +17 -0
- package/docs/adr/ADR-002-shell-performance.md +9 -0
- package/docs/adr/ADR-003-security-first.md +18 -0
- package/docs/adr/ADR-004-cli-architecture.md +14 -0
- package/docs/adr/ADR-005-chezmoi-choice.md +10 -0
- package/docs/adr/ADR-006-shell-selection.md +9 -0
- package/docs/adr/ADR-007-multi-shell-parity.md +10 -1
- package/docs/adr/ADR-008-alias-system-architecture.md +10 -0
- package/docs/adr/ADR-009-wallpaper-driven-theming.md +131 -0
- package/docs/adr/ADR-010-starship-transient-prompt.md +144 -0
- package/docs/adr/ADR-011-nushell-tier3-keep.md +144 -0
- package/docs/adr/README.md +7 -0
- package/docs/architecture/ARCHITECTURE.md +4 -0
- package/docs/architecture/INTEROP.md +8 -0
- package/docs/architecture/REPO_LAYOUT.md +5 -1
- package/docs/architecture/WALKTHROUGH.md +4 -0
- package/docs/architecture/fleet-deployment.md +4 -0
- package/docs/archive/EUXIS_2026_REVIEW.md +11 -3
- package/docs/archive/LEGACY_ROADMAP.md +45 -27
- package/docs/archive/MILESTONE_v0.2.493.md +4 -0
- package/docs/archive/PLAN.md +46 -8
- package/docs/archive/REPO_AUDIT.md +8 -0
- package/docs/guides/INSTALL.md +4 -0
- package/docs/guides/NEOVIM_IDE_GUIDE.md +9 -0
- package/docs/guides/THEMING.md +8 -0
- package/docs/guides/TROUBLESHOOTING.md +28 -0
- package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +70 -1
- package/docs/index.md +6 -2
- package/docs/interop/A2A.md +7 -0
- package/docs/interop/POWERSHELL.md +102 -0
- package/docs/manual/00-introduction.md +5 -1
- package/docs/manual/01-concepts/01-architecture.md +4 -0
- package/docs/manual/01-concepts/02-trust-model.md +6 -2
- package/docs/manual/01-concepts/03-theme-engine.md +4 -0
- package/docs/manual/01-concepts/04-fleet.md +7 -1
- package/docs/manual/01-concepts/05-self-healing.md +8 -1
- package/docs/manual/02-tutorials/01-first-install.md +5 -0
- package/docs/manual/02-tutorials/02-add-wallpaper.md +5 -0
- package/docs/manual/02-tutorials/03-create-profile.md +6 -0
- package/docs/manual/02-tutorials/04-encrypt-secret.md +6 -0
- package/docs/manual/02-tutorials/05-deploy-fleet.md +5 -1
- package/docs/manual/03-reference/01-dot-cli.md +67 -45
- package/docs/manual/03-reference/02-config-files.md +8 -2
- package/docs/manual/03-reference/03-environment.md +4 -0
- package/docs/manual/03-reference/04-templates.md +7 -1
- package/docs/manual/03-reference/05-feature-flags.md +72 -3
- package/docs/manual/04-cookbook/01-recipes.md +4 -0
- package/docs/manual/04-cookbook/02-troubleshooting.md +32 -0
- package/docs/manual/04-cookbook/03-faq.md +8 -0
- package/docs/manual/05-appendices/A-platform-matrix.md +4 -0
- package/docs/manual/05-appendices/B-security-checklist.md +6 -0
- package/docs/manual/05-appendices/C-glossary.md +4 -0
- package/docs/manual/05-appendices/D-bibliography.md +5 -1
- package/docs/manual/05-appendices/E-license.md +4 -0
- package/docs/manual/_toc.yml +1 -1
- package/docs/manual/command-index.md +16 -8
- package/docs/manual/concept-index.md +4 -0
- package/docs/manual/index.md +66 -0
- package/docs/operations/ATTESTATION.md +5 -0
- package/docs/operations/CI_CADENCE.md +107 -0
- package/docs/operations/CI_COMPOSITES.md +156 -0
- package/docs/operations/COMPLETIONS.md +123 -0
- package/docs/operations/COVERAGE.md +182 -0
- package/docs/operations/DRIFT.md +107 -0
- package/docs/operations/HARD_AUDIT_2026.md +631 -0
- package/docs/operations/MAINTENANCE.md +5 -1
- package/docs/operations/MIGRATION.md +14 -6
- package/docs/operations/OPERATIONS.md +24 -0
- package/docs/operations/PERFORMANCE.md +133 -0
- package/docs/operations/REGISTRY.md +89 -0
- package/docs/operations/RELIABILITY.md +6 -0
- package/docs/operations/ROADMAP.md +36 -18
- package/docs/operations/ROADMAP_2026.md +665 -0
- package/docs/operations/TESTING.md +4 -0
- package/docs/operations/TRACEABILITY.md +7 -0
- package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +4 -0
- package/docs/operations/VERSION_SYNC.md +54 -5
- package/docs/reference/ALIASES.md +7 -0
- package/docs/reference/ALIASES_CHEATSHEET.md +5 -1
- package/docs/reference/ALIASES_DEPRECATIONS.md +5 -1
- package/docs/reference/FEATURES.md +4 -0
- package/docs/reference/FONTS.md +4 -0
- package/docs/reference/POWERSHELL_PARITY.md +80 -0
- package/docs/reference/PROFILES.md +4 -0
- package/docs/reference/SCREENSHOTS.md +4 -0
- package/docs/reference/SCRIPTS.md +4 -0
- package/docs/reference/SUPPORT_MATRIX.md +10 -4
- package/docs/reference/THEMES.md +4 -0
- package/docs/reference/TOOLS.md +4 -0
- package/docs/reference/UTILS.md +16 -12
- package/docs/registry.json +6 -0
- package/docs/security/AI_ACT_COMPLIANCE.md +4 -0
- package/docs/security/AUDIT_BYPASS.md +103 -0
- package/docs/security/AUTOMATION_SECRETS.md +4 -0
- package/docs/security/CI_EGRESS_ALLOWLIST.md +127 -0
- package/docs/security/CI_PINNING.md +113 -0
- package/docs/security/COMMIT_SIGNING.md +138 -0
- package/docs/security/COMPLIANCE.md +5 -0
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +86 -0
- package/docs/security/DISCLOSURE.md +130 -0
- package/docs/security/ENCRYPTION.md +5 -0
- package/docs/security/FMEA.md +4 -0
- package/docs/security/HISTORY_FILTERING.md +132 -0
- package/docs/security/INCIDENT_RESPONSE.md +4 -0
- package/docs/security/INSTALL_VERIFICATION.md +122 -0
- package/docs/security/KEYS.md +4 -0
- package/docs/security/KEY_ROTATION.md +85 -1
- package/docs/security/MCP_POLICY.md +9 -0
- package/docs/security/POLICY_RELEASES.md +4 -0
- package/docs/security/README.md +4 -0
- package/docs/security/SCORECARD.md +140 -0
- package/docs/security/SECRETS.md +12 -0
- package/docs/security/SECURITY.md +4 -0
- package/docs/security/SECURITY_CHECKLIST.md +11 -0
- package/docs/security/SHELL_EXEMPTIONS.md +145 -0
- package/docs/security/SOUP_REGISTER.md +4 -0
- package/docs/security/THREAT_MODEL.md +10 -0
- package/docs/security/VERIFICATION_VALIDATION.md +5 -1
- package/docs/security/security-pubkey.asc +15 -0
- package/docs/themes/README.md +4 -0
- package/docs/themes/VISUAL_INTEGRITY_REPORT.md +4 -0
- package/dot_config/ai/identity.md +3 -0
- package/dot_config/ai/patterns/architect.md +2 -0
- package/dot_config/ai/patterns/hardener.md +2 -0
- package/dot_config/ai/patterns/refactor.md +2 -0
- package/dot_config/alacritty/alacritty.toml.tmpl +3 -3
- package/dot_config/atuin/config.toml.tmpl +47 -0
- package/dot_config/dotfiles/agent-card.json +1 -1
- package/dot_config/dotfiles/boot/README.md +2 -0
- package/dot_config/dotfiles/grub/README.md +2 -0
- package/dot_config/dotfiles/lock/README.md +2 -0
- package/dot_config/fish/conf.d/direnv.fish +4 -0
- package/dot_config/fish/conf.d/init.fish.tmpl +21 -0
- package/dot_config/fish/conf.d/mise-activate.fish +5 -0
- package/dot_config/fish/functions/_cached_eval.fish +84 -11
- package/dot_config/fish/functions/_cached_eval_clear.fish +17 -0
- package/dot_config/foot/foot.ini.tmpl +3 -3
- package/dot_config/fuzzel/fuzzel.ini.tmpl +2 -2
- package/dot_config/ghostty/config.tmpl +3 -3
- package/dot_config/git/hooks/executable_commit-msg +146 -0
- package/dot_config/goose/config.yaml +2 -2
- package/dot_config/gtk-3.0/gtk.css.tmpl +2 -2
- package/dot_config/gtk-3.0/settings.ini.tmpl +2 -2
- package/dot_config/gtk-4.0/gtk.css.tmpl +2 -2
- package/dot_config/gtk-4.0/settings.ini.tmpl +2 -2
- package/dot_config/kitty/kitty.conf.tmpl +3 -3
- package/dot_config/mise/config.toml +1 -1
- package/dot_config/niri/config.kdl.tmpl +2 -2
- package/dot_config/nushell/cached_eval.nu +80 -0
- package/dot_config/nushell/env.nu.tmpl +21 -13
- package/dot_config/shell/00-core-paths.sh.tmpl +9 -1
- package/dot_config/shell/05-core-safety.sh +1 -0
- package/dot_config/shell/10-secrets.sh +1 -0
- package/dot_config/shell/40-fzf-defaults.sh.tmpl +1 -0
- package/dot_config/shell/40-ls-colors.sh +1 -0
- package/dot_config/shell/50-logic-functions-core.sh.tmpl +1 -0
- package/dot_config/shell/50-logic-functions.sh.tmpl +1 -0
- package/dot_config/shell/51-logic-functions-extra.sh.tmpl +1 -0
- package/dot_config/shell/90-ux-aliases.sh.tmpl +1 -0
- package/dot_config/shell/91-ux-aliases-lazy.sh.tmpl +1 -0
- package/dot_config/shell/README.md +25 -8
- package/dot_config/starship.toml.tmpl +2 -2
- package/dot_config/tmux/tmux.conf.tmpl +3 -3
- package/dot_config/user-dirs.dirs +1 -0
- package/dot_config/vscode/settings.json.tmpl +2 -2
- package/dot_config/waybar/config.jsonc.tmpl +2 -2
- package/dot_config/waybar/style.css.tmpl +2 -2
- package/dot_config/wezterm/wezterm.lua.tmpl +3 -3
- package/dot_config/zsh/dot_zshrc.tmpl +207 -17
- package/dot_config/zsh/rc.d/00-alias-shims.zsh +28 -6
- package/dot_config/zsh/rc.d/30-options.zsh.tmpl +27 -7
- package/dot_local/bin/executable_bm +2 -0
- package/dot_local/bin/executable_dot +56 -9
- package/dot_local/bin/executable_dot-bootstrap +0 -1
- package/dot_local/bin/executable_dot-load-benchmark +1 -1
- package/dot_local/bin/executable_dot-theme-sync +8 -8
- package/dot_local/bin/executable_notify +2 -0
- package/dot_local/bin/executable_open +2 -0
- package/dot_local/bin/executable_tour +4 -2
- package/dot_local/bin/executable_up +3 -1
- package/dot_local/share/man/man1/dot.1 +1 -1
- package/dot_local/share/zsh/completions/_dot +4 -0
- package/install.sh +53 -38
- package/package.json +1 -1
- package/scripts/ci/check-dangerous-chmod.sh +19 -0
- package/scripts/ci/check-deps-dev.sh +236 -0
- package/scripts/ci/check-insecure-tls.sh +61 -0
- package/scripts/ci/check-regression-traceability.sh +67 -0
- package/scripts/ci/check-shell-preamble.sh +106 -0
- package/scripts/ci/dot-cli-startup-bench.sh +126 -0
- package/scripts/ci/install-chezmoi-verified.sh +4 -1
- package/scripts/ci/lint-reusable-pins.sh +78 -0
- package/scripts/ci/run-coverage.sh +451 -0
- package/scripts/ci/validate-chezmoidata.sh +25 -0
- package/scripts/ci/windows-smoke-test.ps1 +136 -0
- package/scripts/diagnostics/doctor.sh +203 -5
- package/scripts/diagnostics/drift-dashboard.sh +177 -13
- package/scripts/diagnostics/health.sh +21 -4
- package/scripts/diagnostics/perf.sh +304 -77
- package/scripts/diagnostics/workstation-attestation.sh +6 -1
- package/scripts/dot/commands/agent.sh +33 -27
- package/scripts/dot/commands/agents.sh +325 -0
- package/scripts/dot/commands/ai.sh +75 -10
- package/scripts/dot/commands/aliases.sh +10 -8
- package/scripts/dot/commands/core.sh +10 -4
- package/scripts/dot/commands/fleet.sh +278 -3
- package/scripts/dot/commands/init.sh +184 -0
- package/scripts/dot/commands/meta.sh +7 -4
- package/scripts/dot/commands/registry.sh +263 -0
- package/scripts/dot/commands/tools.sh +49 -1
- package/scripts/dot/lib/bento.sh +2 -1
- package/scripts/dot/lib/log.sh +6 -0
- package/scripts/dot/lib/platform.sh +22 -8
- package/scripts/dot/lib/ui.sh +145 -2
- package/scripts/dot/lib/utils.sh +6 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/git-hooks/pre-push +66 -3
- package/scripts/lib/secrets_provider.sh +32 -6
- package/scripts/ops/heal-chezmoi.sh +41 -6
- package/scripts/ops/rollback.sh +14 -0
- package/scripts/qa/powershell-contract.ps1 +95 -0
- package/scripts/security/check-disclosure-key-expiry.sh +110 -0
- package/scripts/security/lock-configs.sh +11 -2
- package/scripts/theme/merge-wallpaper.sh +4 -0
- package/scripts/theme/switch.sh +20 -10
- package/scripts/version-sync.sh +3 -0
- package/dot_config/atuin/config.toml +0 -40
- package/dot_local/bin/__pycache__/executable_dot-load-benchmark-ptycpython-312.pyc +0 -0
|
@@ -291,10 +291,21 @@ cmd_fleet_namespace() {
|
|
|
291
291
|
local data_file
|
|
292
292
|
data_file="$(resolve_source_dir)/.chezmoidata.toml"
|
|
293
293
|
if grep -q "^namespace = " "$data_file" 2>/dev/null; then
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
# Atomic write: render into a tempfile + mv so concurrent
|
|
295
|
+
# `dot fleet namespace set` callers can't corrupt the TOML.
|
|
296
|
+
# Avoids `sed -i` portability dance (GNU `-i` vs BSD `-i ''`).
|
|
297
|
+
local _tmp
|
|
298
|
+
_tmp="$(mktemp "${data_file}.XXXXXX")" || die "Cannot create tempfile"
|
|
299
|
+
# Explicit if/else instead of A && B || C — the latter (SC2015)
|
|
300
|
+
# silently runs C when B itself fails, masking real mv errors.
|
|
301
|
+
if sed "s/^namespace = \".*\"/namespace = \"$new_ns\"/" "$data_file" >"$_tmp"; then
|
|
302
|
+
if ! mv "$_tmp" "$data_file"; then
|
|
303
|
+
rm -f "$_tmp"
|
|
304
|
+
die "Failed to commit namespace update"
|
|
305
|
+
fi
|
|
296
306
|
else
|
|
297
|
-
|
|
307
|
+
rm -f "$_tmp"
|
|
308
|
+
die "Failed to render namespace update"
|
|
298
309
|
fi
|
|
299
310
|
fi
|
|
300
311
|
ui_ok "Namespace" "Set to '$new_ns'. Run 'dot sync' to apply."
|
|
@@ -349,6 +360,266 @@ cmd_fleet_enforce() {
|
|
|
349
360
|
esac
|
|
350
361
|
}
|
|
351
362
|
|
|
363
|
+
_fleet_hosts_file() {
|
|
364
|
+
printf '%s\n' "${DOTFILES_FLEET_HOSTS:-$HOME/.config/dotfiles/fleet.toml}"
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# Parse the hosts file. Format:
|
|
368
|
+
# [hosts.laptop]
|
|
369
|
+
# ssh = "user@laptop.local"
|
|
370
|
+
# profile = "workstation"
|
|
371
|
+
#
|
|
372
|
+
# Echoes one record per line: "<name>\t<ssh-target>\t<profile>".
|
|
373
|
+
_fleet_hosts_iter() {
|
|
374
|
+
local f
|
|
375
|
+
f="$(_fleet_hosts_file)"
|
|
376
|
+
[[ -f "$f" ]] || return 0
|
|
377
|
+
awk '
|
|
378
|
+
BEGIN { name = ""; ssh = ""; profile = "" }
|
|
379
|
+
/^\[hosts\./ {
|
|
380
|
+
if (name != "") { printf "%s\t%s\t%s\n", name, ssh, profile }
|
|
381
|
+
gsub(/[\[\]]/, "", $0); sub(/^hosts\./, "", $0); name = $0
|
|
382
|
+
ssh = ""; profile = ""
|
|
383
|
+
next
|
|
384
|
+
}
|
|
385
|
+
/^ssh[[:space:]]*=/ { sub(/^ssh[[:space:]]*=[[:space:]]*/, ""); gsub(/"/, ""); ssh = $0; next }
|
|
386
|
+
/^profile[[:space:]]*=/ { sub(/^profile[[:space:]]*=[[:space:]]*/, ""); gsub(/"/, ""); profile = $0; next }
|
|
387
|
+
END {
|
|
388
|
+
if (name != "") { printf "%s\t%s\t%s\n", name, ssh, profile }
|
|
389
|
+
}
|
|
390
|
+
' "$f"
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# SSH-based "dot fleet apply" — push the local dotfiles state out to
|
|
394
|
+
# each registered host. The §3 hero-feature: nobody else owns the
|
|
395
|
+
# "Ansible for personal devices" niche.
|
|
396
|
+
cmd_fleet_apply() {
|
|
397
|
+
local dry_run=0 only_host="" cmd="" jobs=4 verify_hosts=0
|
|
398
|
+
while [[ $# -gt 0 ]]; do
|
|
399
|
+
case "$1" in
|
|
400
|
+
--dry-run | -n)
|
|
401
|
+
dry_run=1
|
|
402
|
+
shift
|
|
403
|
+
;;
|
|
404
|
+
--verify-hosts)
|
|
405
|
+
# Pre-flight check that every host already has a known_hosts
|
|
406
|
+
# entry, closing the TOFU window before any SSH connection.
|
|
407
|
+
# R3 audit N4. Without this, accept-new is the default and a
|
|
408
|
+
# first-connection MITM can seed an attacker key.
|
|
409
|
+
verify_hosts=1
|
|
410
|
+
shift
|
|
411
|
+
;;
|
|
412
|
+
--host)
|
|
413
|
+
only_host="$2"
|
|
414
|
+
shift 2
|
|
415
|
+
;;
|
|
416
|
+
--cmd)
|
|
417
|
+
cmd="$2"
|
|
418
|
+
shift 2
|
|
419
|
+
;;
|
|
420
|
+
--jobs | -j)
|
|
421
|
+
jobs="$2"
|
|
422
|
+
shift 2
|
|
423
|
+
;;
|
|
424
|
+
--help | -h)
|
|
425
|
+
cat <<EOF
|
|
426
|
+
Usage: dot fleet apply [--host <name>] [--cmd <shell>] [--dry-run] [--jobs <n>]
|
|
427
|
+
|
|
428
|
+
Push dotfiles state to every host registered in:
|
|
429
|
+
${DOTFILES_FLEET_HOSTS:-\$HOME/.config/dotfiles/fleet.toml}
|
|
430
|
+
|
|
431
|
+
Format of fleet.toml:
|
|
432
|
+
[hosts.laptop]
|
|
433
|
+
ssh = "user@laptop.local"
|
|
434
|
+
profile = "workstation"
|
|
435
|
+
|
|
436
|
+
Hostnames are validated against [A-Za-z0-9._@:+/-]+ before any SSH
|
|
437
|
+
fan-out; invalid entries abort the apply.
|
|
438
|
+
|
|
439
|
+
First-time SSH connections use StrictHostKeyChecking=accept-new (TOFU).
|
|
440
|
+
If your threat model requires no TOFU window, pre-populate
|
|
441
|
+
~/.ssh/known_hosts before running this command.
|
|
442
|
+
|
|
443
|
+
Behavior:
|
|
444
|
+
By default each host runs: dot sync && dot doctor --quiet
|
|
445
|
+
Override with --cmd "<shell>" to run an arbitrary command on every
|
|
446
|
+
host (e.g. --cmd "uptime").
|
|
447
|
+
|
|
448
|
+
WARNING: --cmd is the trust boundary. Whatever string you pass
|
|
449
|
+
executes on every remote host with the credentials your SSH key
|
|
450
|
+
carries. Verify the command before running.
|
|
451
|
+
|
|
452
|
+
Flags:
|
|
453
|
+
--host <name> Apply to a single host only.
|
|
454
|
+
--cmd <shell> Run a custom command instead of 'dot sync'.
|
|
455
|
+
--dry-run, -n Print resolved hosts + planned command; don't SSH.
|
|
456
|
+
--jobs <n> Parallelism (default 4).
|
|
457
|
+
--verify-hosts Refuse to open any SSH connection unless every
|
|
458
|
+
target host already has a key in ~/.ssh/known_hosts.
|
|
459
|
+
Use when your threat model excludes the TOFU window.
|
|
460
|
+
EOF
|
|
461
|
+
return 0
|
|
462
|
+
;;
|
|
463
|
+
*)
|
|
464
|
+
ui_err "Unknown arg" "$1"
|
|
465
|
+
return 1
|
|
466
|
+
;;
|
|
467
|
+
esac
|
|
468
|
+
done
|
|
469
|
+
|
|
470
|
+
local hosts_file
|
|
471
|
+
hosts_file="$(_fleet_hosts_file)"
|
|
472
|
+
if [[ ! -f "$hosts_file" ]]; then
|
|
473
|
+
ui_err "Fleet" "no hosts file at $hosts_file"
|
|
474
|
+
ui_info "Hint" "create it with stanzas like '[hosts.laptop]\\nssh = \"user@laptop.local\"'"
|
|
475
|
+
return 1
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
local entries
|
|
479
|
+
entries="$(_fleet_hosts_iter)"
|
|
480
|
+
if [[ -z "$entries" ]]; then
|
|
481
|
+
ui_err "Fleet" "hosts file is empty: $hosts_file"
|
|
482
|
+
return 1
|
|
483
|
+
fi
|
|
484
|
+
if [[ -n "$only_host" ]]; then
|
|
485
|
+
entries="$(printf '%s\n' "$entries" | awk -F'\t' -v h="$only_host" '$1 == h')"
|
|
486
|
+
[[ -n "$entries" ]] || {
|
|
487
|
+
ui_err "Fleet" "host not found: $only_host"
|
|
488
|
+
return 1
|
|
489
|
+
}
|
|
490
|
+
fi
|
|
491
|
+
|
|
492
|
+
local default_cmd='dot sync && dot doctor --quiet'
|
|
493
|
+
local effective_cmd="${cmd:-$default_cmd}"
|
|
494
|
+
|
|
495
|
+
ui_header "Fleet apply"
|
|
496
|
+
ui_info "Hosts file" "$hosts_file"
|
|
497
|
+
ui_info "Command" "$effective_cmd"
|
|
498
|
+
ui_info "Parallel" "$jobs"
|
|
499
|
+
|
|
500
|
+
if [[ "$dry_run" -eq 1 ]]; then
|
|
501
|
+
printf '%s\n' "$entries" | while IFS=$'\t' read -r name ssh profile; do
|
|
502
|
+
ui_info "$name" "$ssh profile=$profile cmd=$effective_cmd"
|
|
503
|
+
done
|
|
504
|
+
ui_ok "Dry-run" "no SSH connections opened"
|
|
505
|
+
return 0
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
if ! command -v ssh >/dev/null 2>&1; then
|
|
509
|
+
ui_err "ssh" "not installed"
|
|
510
|
+
return 127
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
local total=0 ok=0 fail=0
|
|
514
|
+
local tmpdir
|
|
515
|
+
# `-t` template includes PID + random, so two concurrent `dot fleet
|
|
516
|
+
# apply` invocations from the same user can't collide on $tmpdir.
|
|
517
|
+
tmpdir="$(mktemp -d -t dotfiles-fleet.XXXXXX)"
|
|
518
|
+
# Capture tmpdir's value at trap-definition time (via the eval-on-
|
|
519
|
+
# define `printf -v`), NOT at trap-fire time. A naive
|
|
520
|
+
# `trap 'rm -rf "$tmpdir"' RETURN` is unsafe under set -u because
|
|
521
|
+
# `local tmpdir` is destroyed before the RETURN trap evaluates.
|
|
522
|
+
# The SC2064 warning ("Use single quotes, otherwise this expands now
|
|
523
|
+
# rather than when signalled") is exactly the behaviour we want here —
|
|
524
|
+
# we explicitly want eager expansion. Suppress per-line.
|
|
525
|
+
local _cleanup
|
|
526
|
+
printf -v _cleanup 'rm -rf %q' "$tmpdir"
|
|
527
|
+
# shellcheck disable=SC2064
|
|
528
|
+
trap "$_cleanup" RETURN
|
|
529
|
+
|
|
530
|
+
# Validate every hostname against a conservative regex BEFORE fan-out.
|
|
531
|
+
# `user@host:port` characters only — refuses single quotes, backticks,
|
|
532
|
+
# `$()`, semicolons, spaces, any shell metacharacter. Closes the
|
|
533
|
+
# round-2 audit's hostname-injection finding.
|
|
534
|
+
while IFS=$'\t' read -r name ssh profile; do
|
|
535
|
+
[[ -n "$name" ]] || continue
|
|
536
|
+
if [[ ! "$ssh" =~ ^[a-zA-Z0-9._@:+/-]+$ ]]; then
|
|
537
|
+
ui_err "$name" "invalid ssh target ($ssh) — only [a-zA-Z0-9._@:+/-] allowed"
|
|
538
|
+
return 1
|
|
539
|
+
fi
|
|
540
|
+
done <<<"$entries"
|
|
541
|
+
|
|
542
|
+
# --verify-hosts: refuse the apply when any target host is missing
|
|
543
|
+
# from ~/.ssh/known_hosts. Closes the R3 audit N4 TOFU-window gap.
|
|
544
|
+
if ((verify_hosts == 1)); then
|
|
545
|
+
local known_hosts="${HOME}/.ssh/known_hosts"
|
|
546
|
+
if [[ ! -f "$known_hosts" ]]; then
|
|
547
|
+
ui_err "verify-hosts" "no $known_hosts — populate before --verify-hosts"
|
|
548
|
+
return 1
|
|
549
|
+
fi
|
|
550
|
+
local unknown_count=0
|
|
551
|
+
while IFS=$'\t' read -r name ssh profile; do
|
|
552
|
+
[[ -n "$name" && -n "$ssh" ]] || continue
|
|
553
|
+
# Strip `user@` prefix and `:port` suffix for the lookup.
|
|
554
|
+
local hostpart="${ssh#*@}"
|
|
555
|
+
hostpart="${hostpart%%:*}"
|
|
556
|
+
if ! ssh-keygen -F "$hostpart" -f "$known_hosts" >/dev/null 2>&1; then
|
|
557
|
+
ui_err "$name" "no known_hosts entry for $hostpart — would TOFU on first connect"
|
|
558
|
+
unknown_count=$((unknown_count + 1))
|
|
559
|
+
fi
|
|
560
|
+
done <<<"$entries"
|
|
561
|
+
if ((unknown_count > 0)); then
|
|
562
|
+
ui_err "verify-hosts" "$unknown_count host(s) missing from known_hosts — aborting"
|
|
563
|
+
return 1
|
|
564
|
+
fi
|
|
565
|
+
ui_ok "verify-hosts" "all hosts found in known_hosts"
|
|
566
|
+
fi
|
|
567
|
+
|
|
568
|
+
# Run one SSH per host, parallelised via background jobs with a
|
|
569
|
+
# semaphore. We DO NOT use `xargs -d` because that flag is GNU-only
|
|
570
|
+
# and the §3 hero feature must work on macOS BSD xargs too. Also
|
|
571
|
+
# avoids embedding `{}` substitution into a `bash -c` (the previous
|
|
572
|
+
# implementation had a quoting hazard around TOML hostnames).
|
|
573
|
+
_fleet_apply_one() {
|
|
574
|
+
local _name="$1" _ssh="$2" _cmd="$3" _tmp="$4"
|
|
575
|
+
if ssh -o BatchMode=yes -o ConnectTimeout=10 \
|
|
576
|
+
-o StrictHostKeyChecking=accept-new \
|
|
577
|
+
"$_ssh" "$_cmd" </dev/null \
|
|
578
|
+
>"$_tmp/$_name.out" 2>"$_tmp/$_name.err"; then
|
|
579
|
+
printf 'ok\n' >"$_tmp/$_name.status"
|
|
580
|
+
else
|
|
581
|
+
printf 'fail %d\n' "$?" >"$_tmp/$_name.status"
|
|
582
|
+
fi
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
local running=0
|
|
586
|
+
while IFS=$'\t' read -r name ssh profile; do
|
|
587
|
+
[[ -n "$name" && -n "$ssh" ]] || continue
|
|
588
|
+
total=$((total + 1))
|
|
589
|
+
while ((running >= jobs)); do
|
|
590
|
+
wait -n 2>/dev/null || break
|
|
591
|
+
running=$((running - 1))
|
|
592
|
+
done
|
|
593
|
+
_fleet_apply_one "$name" "$ssh" "$effective_cmd" "$tmpdir" &
|
|
594
|
+
running=$((running + 1))
|
|
595
|
+
done <<<"$entries"
|
|
596
|
+
wait
|
|
597
|
+
|
|
598
|
+
# `while < <(printf ...)` instead of `printf ... | while` — the
|
|
599
|
+
# pipe form runs the loop body in a subshell, so the ok/fail
|
|
600
|
+
# counters never propagate back to the parent. Caught by
|
|
601
|
+
# tests/unit/fleet/test_fleet_apply_mocked_ssh.sh which exercised
|
|
602
|
+
# the full apply path (the dry-run test missed this).
|
|
603
|
+
while IFS=$'\t' read -r name ssh profile; do
|
|
604
|
+
[[ -n "$name" ]] || continue
|
|
605
|
+
if [[ -s "$tmpdir/$name.status" ]] && head -1 "$tmpdir/$name.status" | grep -q '^ok'; then
|
|
606
|
+
ui_ok "$name" "$ssh"
|
|
607
|
+
ok=$((ok + 1))
|
|
608
|
+
else
|
|
609
|
+
local err_summary=""
|
|
610
|
+
[[ -s "$tmpdir/$name.err" ]] && err_summary=" — $(head -1 "$tmpdir/$name.err")"
|
|
611
|
+
ui_err "$name" "$ssh${err_summary}"
|
|
612
|
+
fail=$((fail + 1))
|
|
613
|
+
fi
|
|
614
|
+
local _evt_status="unknown"
|
|
615
|
+
[[ -s "$tmpdir/$name.status" ]] && _evt_status="$(head -1 "$tmpdir/$name.status")"
|
|
616
|
+
_fleet_emit_event "apply" "$_evt_status" "host=$name" "cmd=$effective_cmd"
|
|
617
|
+
done < <(printf '%s\n' "$entries")
|
|
618
|
+
|
|
619
|
+
ui_info "Summary" "$ok ok / $fail failed / $total total"
|
|
620
|
+
[[ "$fail" -eq 0 ]]
|
|
621
|
+
}
|
|
622
|
+
|
|
352
623
|
cmd_fleet() {
|
|
353
624
|
local subcommand="${1:-status}"
|
|
354
625
|
if [[ "${1:-}" == --* ]] || [[ -z "${1:-}" ]]; then
|
|
@@ -373,6 +644,9 @@ cmd_fleet() {
|
|
|
373
644
|
enforce)
|
|
374
645
|
cmd_fleet_enforce "$@"
|
|
375
646
|
;;
|
|
647
|
+
apply | push)
|
|
648
|
+
cmd_fleet_apply "$@"
|
|
649
|
+
;;
|
|
376
650
|
*)
|
|
377
651
|
ui_header "Fleet Commands"
|
|
378
652
|
echo ""
|
|
@@ -383,6 +657,7 @@ cmd_fleet() {
|
|
|
383
657
|
ui_ok "events" "Show recent fleet events"
|
|
384
658
|
ui_ok "namespace" "Show or set the active namespace"
|
|
385
659
|
ui_ok "enforce" "Show or set RBAC enforcement mode (advisory|strict)"
|
|
660
|
+
ui_ok "apply" "SSH out to every host in fleet.toml and run 'dot sync'"
|
|
386
661
|
;;
|
|
387
662
|
esac
|
|
388
663
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
|
|
3
|
+
# shellcheck shell=bash
|
|
4
|
+
#
|
|
5
|
+
# scripts/dot/commands/init.sh
|
|
6
|
+
#
|
|
7
|
+
# `dot init <github-user>` — bootstrap a foreign dotfiles repo through
|
|
8
|
+
# this framework's harness. The §3 audit roadmap calls for an analogue
|
|
9
|
+
# to `chezmoi init <user>` that turns this tool from "one person's
|
|
10
|
+
# config" into a runtime for any dotfiles user.
|
|
11
|
+
#
|
|
12
|
+
# Behavior:
|
|
13
|
+
# dot init alice # clones github.com/alice/dotfiles via chezmoi,
|
|
14
|
+
# # applies through the dot harness
|
|
15
|
+
# dot init https://... # explicit URL
|
|
16
|
+
# dot init alice --dry-run # preview without writing
|
|
17
|
+
# dot init alice --no-apply # clone only; don't run chezmoi apply
|
|
18
|
+
#
|
|
19
|
+
# Safety:
|
|
20
|
+
# - Refuses to clobber an existing chezmoi source dir without --force.
|
|
21
|
+
# - Disclaims that the target's scripts will run with the user's
|
|
22
|
+
# privileges and prints the source URL for inspection.
|
|
23
|
+
# - Verifies the source URL is HTTPS (no plain git://) so MITM is harder.
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
# shellcheck source=../lib/ui.sh disable=SC1091
|
|
29
|
+
source "$SCRIPT_DIR/../lib/ui.sh"
|
|
30
|
+
# shellcheck source=../lib/utils.sh disable=SC1091
|
|
31
|
+
source "$SCRIPT_DIR/../lib/utils.sh"
|
|
32
|
+
|
|
33
|
+
_init_resolve_url() {
|
|
34
|
+
local arg="$1"
|
|
35
|
+
case "$arg" in
|
|
36
|
+
http://*)
|
|
37
|
+
printf "dot init: refusing plain HTTP (use HTTPS or git+ssh)\n" >&2
|
|
38
|
+
return 2
|
|
39
|
+
;;
|
|
40
|
+
https://* | git@*:*)
|
|
41
|
+
# Trust full URLs as-is — these are the user's explicit intent.
|
|
42
|
+
printf "%s\n" "$arg"
|
|
43
|
+
;;
|
|
44
|
+
*/*)
|
|
45
|
+
# owner/repo shorthand: validate before constructing the URL so
|
|
46
|
+
# a crafted argument can't smuggle shell metacharacters into
|
|
47
|
+
# downstream tooling (chezmoi init, git clone).
|
|
48
|
+
if [[ ! "$arg" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]]; then
|
|
49
|
+
printf "dot init: invalid owner/repo (only [A-Za-z0-9._-]+/[A-Za-z0-9._-]+ allowed): %s\n" "$arg" >&2
|
|
50
|
+
return 2
|
|
51
|
+
fi
|
|
52
|
+
printf "https://github.com/%s.git\n" "$arg"
|
|
53
|
+
;;
|
|
54
|
+
*)
|
|
55
|
+
# Bare user shorthand: same character whitelist as the path
|
|
56
|
+
# segment of a GitHub URL.
|
|
57
|
+
if [[ ! "$arg" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
|
58
|
+
printf "dot init: invalid user (only [A-Za-z0-9._-] allowed): %s\n" "$arg" >&2
|
|
59
|
+
return 2
|
|
60
|
+
fi
|
|
61
|
+
printf "https://github.com/%s/dotfiles.git\n" "$arg"
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
cmd_init() {
|
|
67
|
+
local user_arg="" dry_run=0 apply=1 force=0
|
|
68
|
+
while [[ $# -gt 0 ]]; do
|
|
69
|
+
case "$1" in
|
|
70
|
+
--dry-run | -n)
|
|
71
|
+
dry_run=1
|
|
72
|
+
shift
|
|
73
|
+
;;
|
|
74
|
+
--no-apply)
|
|
75
|
+
apply=0
|
|
76
|
+
shift
|
|
77
|
+
;;
|
|
78
|
+
--force | -f)
|
|
79
|
+
force=1
|
|
80
|
+
shift
|
|
81
|
+
;;
|
|
82
|
+
--help | -h)
|
|
83
|
+
cat <<EOF
|
|
84
|
+
Usage: dot init <github-user|owner/repo|url> [--dry-run] [--no-apply] [--force]
|
|
85
|
+
|
|
86
|
+
Bootstrap a foreign dotfiles repo through chezmoi + the dot harness.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
dot init alice # github.com/alice/dotfiles
|
|
90
|
+
dot init alice/configs # github.com/alice/configs
|
|
91
|
+
dot init https://example.com/repo.git
|
|
92
|
+
dot init alice --dry-run # preview only
|
|
93
|
+
dot init alice --no-apply # clone but don't apply
|
|
94
|
+
|
|
95
|
+
Flags:
|
|
96
|
+
--dry-run, -n Show resolved URL + target dir without cloning.
|
|
97
|
+
--no-apply Clone the source but skip 'chezmoi apply'.
|
|
98
|
+
--force, -f Overwrite an existing chezmoi source dir.
|
|
99
|
+
EOF
|
|
100
|
+
return 0
|
|
101
|
+
;;
|
|
102
|
+
--)
|
|
103
|
+
shift
|
|
104
|
+
break
|
|
105
|
+
;;
|
|
106
|
+
-*)
|
|
107
|
+
ui_err "Unknown flag" "$1"
|
|
108
|
+
return 1
|
|
109
|
+
;;
|
|
110
|
+
*)
|
|
111
|
+
if [[ -n "$user_arg" ]]; then
|
|
112
|
+
ui_err "Too many arguments" "$1"
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
user_arg="$1"
|
|
116
|
+
shift
|
|
117
|
+
;;
|
|
118
|
+
esac
|
|
119
|
+
done
|
|
120
|
+
|
|
121
|
+
[[ -n "$user_arg" ]] || {
|
|
122
|
+
ui_err "init" "missing <user|repo|url>"
|
|
123
|
+
return 1
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
local url
|
|
127
|
+
url="$(_init_resolve_url "$user_arg")" || return $?
|
|
128
|
+
|
|
129
|
+
if ! command -v chezmoi >/dev/null 2>&1; then
|
|
130
|
+
ui_err "chezmoi" "not installed — run install.sh first"
|
|
131
|
+
return 127
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
local source_dir
|
|
135
|
+
source_dir="${CHEZMOI_SOURCE_DIR:-$(chezmoi source-path 2>/dev/null || echo "$HOME/.local/share/chezmoi")}"
|
|
136
|
+
|
|
137
|
+
ui_header "Bootstrap dotfiles from $user_arg"
|
|
138
|
+
ui_info "Source URL" "$url"
|
|
139
|
+
ui_info "Target dir" "$source_dir"
|
|
140
|
+
local _apply_label="no"
|
|
141
|
+
[[ "$apply" -eq 1 ]] && _apply_label="yes"
|
|
142
|
+
ui_info "Apply after?" "$_apply_label"
|
|
143
|
+
|
|
144
|
+
if [[ "$dry_run" -eq 1 ]]; then
|
|
145
|
+
ui_ok "Dry-run" "no changes made"
|
|
146
|
+
return 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [[ -e "$source_dir" ]] && [[ "$force" -ne 1 ]]; then
|
|
150
|
+
ui_err "Refusing" "$source_dir already exists; pass --force to overwrite"
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
ui_warn "Trust" "The target repo's scripts will run with your user privileges."
|
|
155
|
+
ui_warn "Trust" "Inspect $url before proceeding if you don't know the author."
|
|
156
|
+
if [[ -t 0 ]] && [[ "${DOTFILES_NONINTERACTIVE:-0}" != "1" ]]; then
|
|
157
|
+
read -r -p " Continue? [y/N] " resp
|
|
158
|
+
case "$resp" in
|
|
159
|
+
[yY] | [yY][eE][sS]) ;;
|
|
160
|
+
*)
|
|
161
|
+
ui_info "Init" "aborted by user"
|
|
162
|
+
return 1
|
|
163
|
+
;;
|
|
164
|
+
esac
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
local chezmoi_args=(init --source "$source_dir")
|
|
168
|
+
[[ "$apply" -eq 1 ]] && chezmoi_args+=(--apply)
|
|
169
|
+
chezmoi_args+=("$url")
|
|
170
|
+
|
|
171
|
+
ui_info "chezmoi" "${chezmoi_args[*]}"
|
|
172
|
+
if chezmoi "${chezmoi_args[@]}"; then
|
|
173
|
+
ui_ok "Init" "complete"
|
|
174
|
+
if [[ "$apply" -eq 1 ]]; then
|
|
175
|
+
ui_info "Hint" "run 'dot doctor' to validate the new environment"
|
|
176
|
+
else
|
|
177
|
+
ui_info "Hint" "run 'chezmoi diff' then 'chezmoi apply' when ready"
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
local rc=$?
|
|
181
|
+
ui_err "Init" "chezmoi exited $rc"
|
|
182
|
+
return "$rc"
|
|
183
|
+
fi
|
|
184
|
+
}
|
|
@@ -61,10 +61,13 @@ cmd_prewarm() {
|
|
|
61
61
|
# Clear caches first
|
|
62
62
|
ui_info "Cache" "Clearing shell initialization caches"
|
|
63
63
|
local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
64
|
-
rm -rf
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
# `find ... -delete` instead of `rm -rf $glob` so an unmatched literal
|
|
65
|
+
# pattern can't accidentally remove a real same-named directory, and
|
|
66
|
+
# so we don't pass `-r` on what should always be files.
|
|
67
|
+
[[ -d "$cache_dir/zsh" ]] && find "$cache_dir/zsh" -maxdepth 1 -type f \( -name '*-init.zsh' -o -name '*.zwc' \) -delete 2>/dev/null
|
|
68
|
+
[[ -d "$cache_dir/bash" ]] && find "$cache_dir/bash" -maxdepth 1 -type f -name '*-init.bash' -delete 2>/dev/null
|
|
69
|
+
[[ -d "$cache_dir/fish" ]] && find "$cache_dir/fish" -maxdepth 1 -type f -name '*-init.fish' -delete 2>/dev/null
|
|
70
|
+
[[ -d "$cache_dir/nushell" ]] && find "$cache_dir/nushell" -maxdepth 1 -type f -name '*.nu' -delete 2>/dev/null
|
|
68
71
|
ui_info "Cache" "Cleared. Regenerating..."
|
|
69
72
|
src_dir="$(resolve_source_dir)"
|
|
70
73
|
if [ -n "$src_dir" ] && [ -f "$src_dir/scripts/ops/prewarm.sh" ]; then
|