@sebastienrousseau/dotfiles 0.2.519 → 0.2.521

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/CHANGELOG.md +200 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/{LICENSE → LICENSE-MIT} +1 -1
  4. package/README.md +1172 -166
  5. package/install.sh +77 -11
  6. package/package.json +8 -8
  7. package/tools/README.md +49 -0
  8. package/tools/ci/install-chezmoi-verified.sh +68 -0
  9. package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
  10. package/docs/AI.md +0 -179
  11. package/docs/CNAME +0 -1
  12. package/docs/CONFIG_STRATEGY.md +0 -124
  13. package/docs/COPYRIGHT +0 -7
  14. package/docs/GOVERNANCE.md +0 -98
  15. package/docs/MAINTAINERS.md +0 -41
  16. package/docs/NAMING_CONVENTIONS.md +0 -102
  17. package/docs/OPENCODE.md +0 -127
  18. package/docs/README.md +0 -84
  19. package/docs/STRUCTURE.md +0 -102
  20. package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
  21. package/docs/adr/ADR-002-shell-performance.md +0 -130
  22. package/docs/adr/ADR-003-security-first.md +0 -158
  23. package/docs/adr/ADR-004-cli-architecture.md +0 -171
  24. package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
  25. package/docs/adr/ADR-006-shell-selection.md +0 -124
  26. package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
  27. package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
  28. package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
  29. package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
  30. package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
  31. package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
  32. package/docs/adr/README.md +0 -40
  33. package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
  34. package/docs/architecture/ARCHITECTURE.md +0 -117
  35. package/docs/architecture/INTEROP.md +0 -44
  36. package/docs/architecture/REPO_LAYOUT.md +0 -241
  37. package/docs/architecture/WALKTHROUGH.md +0 -86
  38. package/docs/architecture/fleet-deployment.md +0 -77
  39. package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
  40. package/docs/archive/LEGACY_ROADMAP.md +0 -6
  41. package/docs/archive/MILESTONE_v0.2.493.md +0 -47
  42. package/docs/archive/PLAN.md +0 -199
  43. package/docs/archive/REPO_AUDIT.md +0 -31
  44. package/docs/articles/.pages +0 -6
  45. package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
  46. package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
  47. package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
  48. package/docs/articles/index.md +0 -36
  49. package/docs/guides/INSTALL.md +0 -144
  50. package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
  51. package/docs/guides/THEMING.md +0 -230
  52. package/docs/guides/TROUBLESHOOTING.md +0 -176
  53. package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
  54. package/docs/index.md +0 -132
  55. package/docs/interop/A2A.md +0 -39
  56. package/docs/interop/POWERSHELL.md +0 -102
  57. package/docs/manual/00-introduction.md +0 -89
  58. package/docs/manual/01-concepts/01-architecture.md +0 -138
  59. package/docs/manual/01-concepts/02-trust-model.md +0 -183
  60. package/docs/manual/01-concepts/03-theme-engine.md +0 -186
  61. package/docs/manual/01-concepts/04-fleet.md +0 -148
  62. package/docs/manual/01-concepts/05-self-healing.md +0 -204
  63. package/docs/manual/02-tutorials/01-first-install.md +0 -197
  64. package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
  65. package/docs/manual/02-tutorials/03-create-profile.md +0 -244
  66. package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
  67. package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
  68. package/docs/manual/03-reference/01-dot-cli.md +0 -450
  69. package/docs/manual/03-reference/02-config-files.md +0 -265
  70. package/docs/manual/03-reference/03-environment.md +0 -124
  71. package/docs/manual/03-reference/04-templates.md +0 -190
  72. package/docs/manual/03-reference/05-feature-flags.md +0 -187
  73. package/docs/manual/04-cookbook/01-recipes.md +0 -285
  74. package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
  75. package/docs/manual/04-cookbook/03-faq.md +0 -175
  76. package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
  77. package/docs/manual/05-appendices/B-security-checklist.md +0 -85
  78. package/docs/manual/05-appendices/C-glossary.md +0 -40
  79. package/docs/manual/05-appendices/D-bibliography.md +0 -58
  80. package/docs/manual/05-appendices/E-license.md +0 -38
  81. package/docs/manual/_toc.yml +0 -58
  82. package/docs/manual/command-index.md +0 -155
  83. package/docs/manual/concept-index.md +0 -168
  84. package/docs/manual/index.md +0 -66
  85. package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
  86. package/docs/operations/ATTESTATION.md +0 -44
  87. package/docs/operations/CI_CADENCE.md +0 -107
  88. package/docs/operations/CI_COMPOSITES.md +0 -156
  89. package/docs/operations/COMPLETIONS.md +0 -123
  90. package/docs/operations/COVERAGE.md +0 -204
  91. package/docs/operations/DRIFT.md +0 -107
  92. package/docs/operations/HARD_AUDIT_2026.md +0 -631
  93. package/docs/operations/MAINTENANCE.md +0 -63
  94. package/docs/operations/MANIFEST.md +0 -127
  95. package/docs/operations/MIGRATION.md +0 -109
  96. package/docs/operations/OPERATIONS.md +0 -188
  97. package/docs/operations/PERFORMANCE.md +0 -133
  98. package/docs/operations/REGISTRY.md +0 -90
  99. package/docs/operations/RELEASE_PIPELINE.md +0 -128
  100. package/docs/operations/RELIABILITY.md +0 -122
  101. package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
  102. package/docs/operations/ROADMAP.md +0 -10
  103. package/docs/operations/ROADMAP_2026.md +0 -7
  104. package/docs/operations/ROADMAP_V0_2_503.md +0 -10
  105. package/docs/operations/TESTING.md +0 -216
  106. package/docs/operations/TRACEABILITY.md +0 -43
  107. package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
  108. package/docs/operations/VERSION_SYNC.md +0 -393
  109. package/docs/reference/ALIASES.md +0 -131
  110. package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
  111. package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
  112. package/docs/reference/FEATURES.md +0 -66
  113. package/docs/reference/FONTS.md +0 -112
  114. package/docs/reference/POWERSHELL_PARITY.md +0 -82
  115. package/docs/reference/PROFILES.md +0 -69
  116. package/docs/reference/SCREENSHOTS.md +0 -121
  117. package/docs/reference/SCRIPTS.md +0 -71
  118. package/docs/reference/SUPPORT_MATRIX.md +0 -80
  119. package/docs/reference/THEMES.md +0 -117
  120. package/docs/reference/TOOLS.md +0 -110
  121. package/docs/reference/UTILS.md +0 -242
  122. package/docs/registry.json +0 -6
  123. package/docs/schema/dot-env-v1.json +0 -110
  124. package/docs/schema/dot-registry-v1.json +0 -33
  125. package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
  126. package/docs/security/AUDIT_BYPASS.md +0 -103
  127. package/docs/security/AUTOMATION_SECRETS.md +0 -26
  128. package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
  129. package/docs/security/CI_PINNING.md +0 -129
  130. package/docs/security/COMMIT_SIGNING.md +0 -138
  131. package/docs/security/COMPLIANCE.md +0 -458
  132. package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
  133. package/docs/security/DISCLOSURE.md +0 -130
  134. package/docs/security/ENCRYPTION.md +0 -57
  135. package/docs/security/FMEA.md +0 -159
  136. package/docs/security/FUZZING.md +0 -114
  137. package/docs/security/HISTORY_FILTERING.md +0 -132
  138. package/docs/security/INCIDENT_RESPONSE.md +0 -579
  139. package/docs/security/INSTALL_VERIFICATION.md +0 -122
  140. package/docs/security/KEYS.md +0 -49
  141. package/docs/security/KEY_ROTATION.md +0 -303
  142. package/docs/security/MCP_POLICY.md +0 -78
  143. package/docs/security/POLICY_RELEASES.md +0 -37
  144. package/docs/security/README.md +0 -28
  145. package/docs/security/SCORECARD.md +0 -195
  146. package/docs/security/SECRETS.md +0 -158
  147. package/docs/security/SECURITY.md +0 -45
  148. package/docs/security/SECURITY_CHECKLIST.md +0 -55
  149. package/docs/security/SHELL_EXEMPTIONS.md +0 -145
  150. package/docs/security/SOUP_REGISTER.md +0 -36
  151. package/docs/security/THREAT_MODEL.md +0 -130
  152. package/docs/security/VERIFICATION_VALIDATION.md +0 -228
  153. package/docs/security/VERIFY_RELEASE.md +0 -201
  154. package/docs/security/security-pubkey.asc +0 -15
  155. package/docs/stylesheets/extra.css +0 -444
  156. package/docs/themes/README.md +0 -10
  157. package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
  158. package/docs/themes/hero-shot.svg +0 -78
  159. package/scripts/README.md +0 -123
  160. package/scripts/ci/check-copyright-headers.sh +0 -8
  161. package/scripts/ci/check-shell-preamble.sh +0 -8
  162. package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
  163. package/scripts/demo/record.sh +0 -43
  164. package/scripts/diagnostics/a2a-conformance.sh +0 -163
  165. package/scripts/diagnostics/alias-governance.sh +0 -138
  166. package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
  167. package/scripts/diagnostics/aliases-manifest.sh +0 -77
  168. package/scripts/diagnostics/benchmark.sh +0 -408
  169. package/scripts/diagnostics/conflicts.sh +0 -73
  170. package/scripts/diagnostics/doctor-unified.sh +0 -39
  171. package/scripts/diagnostics/doctor.sh +0 -751
  172. package/scripts/diagnostics/drift-dashboard.sh +0 -202
  173. package/scripts/diagnostics/health.sh +0 -623
  174. package/scripts/diagnostics/history-analysis.sh +0 -86
  175. package/scripts/diagnostics/mcp-doctor.sh +0 -582
  176. package/scripts/diagnostics/perf.sh +0 -453
  177. package/scripts/diagnostics/scorecard.sh +0 -119
  178. package/scripts/diagnostics/secret-governance.sh +0 -65
  179. package/scripts/diagnostics/security-score.sh +0 -467
  180. package/scripts/diagnostics/smoke-test.sh +0 -88
  181. package/scripts/diagnostics/snapshot.sh +0 -90
  182. package/scripts/diagnostics/verify.sh +0 -108
  183. package/scripts/diagnostics/verify_state.sh +0 -73
  184. package/scripts/diagnostics/version-locks.sh +0 -94
  185. package/scripts/diagnostics/workstation-attestation.sh +0 -187
  186. package/scripts/dot/commands/agent.sh +0 -485
  187. package/scripts/dot/commands/agents.sh +0 -336
  188. package/scripts/dot/commands/ai.sh +0 -587
  189. package/scripts/dot/commands/aliases.sh +0 -277
  190. package/scripts/dot/commands/appearance.sh +0 -110
  191. package/scripts/dot/commands/completion.sh +0 -134
  192. package/scripts/dot/commands/core.sh +0 -217
  193. package/scripts/dot/commands/diagnostics.sh +0 -265
  194. package/scripts/dot/commands/env-emit.sh +0 -203
  195. package/scripts/dot/commands/fleet.sh +0 -688
  196. package/scripts/dot/commands/init.sh +0 -185
  197. package/scripts/dot/commands/lint.sh +0 -208
  198. package/scripts/dot/commands/manual.sh +0 -169
  199. package/scripts/dot/commands/meta.sh +0 -333
  200. package/scripts/dot/commands/patterns.sh +0 -55
  201. package/scripts/dot/commands/registry.sh +0 -419
  202. package/scripts/dot/commands/restore.sh +0 -232
  203. package/scripts/dot/commands/secrets.sh +0 -296
  204. package/scripts/dot/commands/security.sh +0 -102
  205. package/scripts/dot/commands/tools.sh +0 -556
  206. package/scripts/dot/data/alias-deprecations.tsv +0 -2
  207. package/scripts/dot/powershell/Dot.psm1 +0 -319
  208. package/scripts/fonts/install-nerd-fonts.sh +0 -75
  209. package/scripts/fonts/patch-fonts.sh +0 -36
  210. package/scripts/git-hooks/install.sh +0 -12
  211. package/scripts/git-hooks/pre-commit +0 -12
  212. package/scripts/git-hooks/pre-commit-audit.sh +0 -146
  213. package/scripts/git-hooks/pre-push +0 -105
  214. package/scripts/git-hooks/prepare-commit-msg +0 -29
  215. package/scripts/lib/secrets_provider.sh +0 -185
  216. package/scripts/ops/ai-setup.sh +0 -71
  217. package/scripts/ops/bundle.sh +0 -104
  218. package/scripts/ops/chaos.sh +0 -50
  219. package/scripts/ops/chezmoi-apply.sh +0 -333
  220. package/scripts/ops/chezmoi-diff.sh +0 -16
  221. package/scripts/ops/chezmoi-remove.sh +0 -46
  222. package/scripts/ops/chezmoi-update.sh +0 -63
  223. package/scripts/ops/heal-chezmoi.sh +0 -87
  224. package/scripts/ops/heal-system.sh +0 -129
  225. package/scripts/ops/heal-tools.sh +0 -297
  226. package/scripts/ops/heal.sh +0 -223
  227. package/scripts/ops/post-apply-repair.sh +0 -107
  228. package/scripts/ops/prewarm.sh +0 -128
  229. package/scripts/ops/release.sh +0 -262
  230. package/scripts/ops/rollback.sh +0 -604
  231. package/scripts/ops/setup.sh +0 -138
  232. package/scripts/ops/teleport.sh +0 -34
  233. package/scripts/qa/check-version-consistency.sh +0 -124
  234. package/scripts/qa/coverage-baseline.sh +0 -61
  235. package/scripts/qa/docs-coverage.sh +0 -112
  236. package/scripts/qa/examples-coverage.sh +0 -94
  237. package/scripts/qa/powershell-contract.ps1 +0 -95
  238. package/scripts/qa/reliability-audit.sh +0 -139
  239. package/scripts/qa/scorecard-snapshot.sh +0 -128
  240. package/scripts/qa/traceability-coverage.sh +0 -117
  241. package/scripts/qa/validate-examples.sh +0 -27
  242. package/scripts/qa/wsl-contract.sh +0 -12
  243. package/scripts/secrets/age-init.sh +0 -82
  244. package/scripts/secrets/create-secrets-file.sh +0 -46
  245. package/scripts/secrets/encrypt-ssh-key.sh +0 -44
  246. package/scripts/security/backup.sh +0 -58
  247. package/scripts/security/check-disclosure-key-expiry.sh +0 -111
  248. package/scripts/security/dns-doh.sh +0 -52
  249. package/scripts/security/encryption-check.sh +0 -55
  250. package/scripts/security/enforce-policies.sh +0 -335
  251. package/scripts/security/firewall.sh +0 -91
  252. package/scripts/security/lock-configs.sh +0 -67
  253. package/scripts/security/lock-screen.sh +0 -56
  254. package/scripts/security/manage-secrets.sh +0 -429
  255. package/scripts/security/ssh-cert.sh +0 -204
  256. package/scripts/security/telemetry-kill.sh +0 -51
  257. package/scripts/security/usb-safety.sh +0 -52
  258. package/scripts/theme/apply-gnome-theme.sh +0 -333
  259. package/scripts/theme/extract-heic-frames.sh +0 -115
  260. package/scripts/theme/extract-theme.py +0 -742
  261. package/scripts/theme/install-boot-logo.sh +0 -63
  262. package/scripts/theme/install-catppuccin-themes.sh +0 -371
  263. package/scripts/theme/install-cursors.sh +0 -26
  264. package/scripts/theme/install-file-icons.sh +0 -27
  265. package/scripts/theme/install-grub-theme.sh +0 -62
  266. package/scripts/theme/install-lock-icon.sh +0 -31
  267. package/scripts/theme/merge-wallpaper.sh +0 -146
  268. package/scripts/theme/rebuild-themes.sh +0 -544
  269. package/scripts/theme/switch.sh +0 -449
  270. package/scripts/theme/wallpaper-rotate.sh +0 -137
  271. package/scripts/theme/wallpaper-sync.sh +0 -690
  272. package/scripts/tools/cmatrix.sh +0 -22
  273. package/scripts/tools/detect-collisions.py +0 -103
  274. package/scripts/tools/emoji-picker.sh +0 -49
  275. package/scripts/tools/figlet-banner.sh +0 -19
  276. package/scripts/tools/log-rotate.sh +0 -31
  277. package/scripts/tools/lolcat-wrap.sh +0 -20
  278. package/scripts/tools/pipes.sh +0 -49
  279. package/scripts/tuning/linux.sh +0 -186
  280. package/scripts/tuning/macos.sh +0 -56
  281. package/scripts/uninstall.sh +0 -86
  282. package/scripts/version-sync.sh +0 -654
  283. package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
  284. package/templates/chezmoi-data/mac-m1.toml.example +0 -16
  285. package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
  286. package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
  287. package/templates/projects/go/.github/workflows/ci.yml +0 -31
  288. package/templates/projects/go/README.md +0 -7
  289. package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
  290. package/templates/projects/go/go.mod +0 -3
  291. package/templates/projects/go/go.sum +0 -0
  292. package/templates/projects/molecule/README.md +0 -7
  293. package/templates/projects/molecule/converge.yml +0 -7
  294. package/templates/projects/molecule/molecule.yml +0 -16
  295. package/templates/projects/node/.github/workflows/ci.yml +0 -30
  296. package/templates/projects/node/README.md +0 -7
  297. package/templates/projects/node/package-lock.json +0 -12
  298. package/templates/projects/node/package.json +0 -10
  299. package/templates/projects/node/src/index.js +0 -3
  300. package/templates/projects/packer/README.md +0 -15
  301. package/templates/projects/packer/main.pkr.hcl +0 -15
  302. package/templates/projects/python/.github/workflows/ci.yml +0 -34
  303. package/templates/projects/python/README.md +0 -7
  304. package/templates/projects/python/pyproject.toml +0 -25
  305. package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
  306. package/templates/projects/python/tests/test_basic.py +0 -3
@@ -1,688 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2015-2026 Sebastien Rousseau
4
- # Dotfiles CLI - Fleet Commands
5
- # fleet status|nodes|drift|events|namespace
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
- dot_ui_command_banner "Fleet" "${1:-}"
16
-
17
- _FLEET_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/fleet"
18
- _FLEET_EVENTS_FILE="$_FLEET_STATE_DIR/events.jsonl"
19
-
20
- _fleet_enabled() {
21
- local data_file
22
- data_file="$(resolve_chezmoi_source_dir)/.chezmoidata.toml"
23
- if [[ -f "$data_file" ]] && grep -q '^enabled = true' "$data_file" 2>/dev/null; then
24
- return 0
25
- fi
26
- return 1
27
- }
28
-
29
- _fleet_node_id() {
30
- local data_file node_id=""
31
- data_file="$(resolve_chezmoi_source_dir)/.chezmoidata.toml"
32
- if [[ -f "$data_file" ]]; then
33
- node_id="$(sed -n 's/^node_id = "\(.*\)"/\1/p' "$data_file" | head -1)"
34
- fi
35
- if [[ -z "$node_id" ]]; then
36
- node_id="$(hostname -s 2>/dev/null || echo "unknown")"
37
- fi
38
- printf '%s\n' "$node_id"
39
- }
40
-
41
- _fleet_namespace() {
42
- local data_file ns=""
43
- data_file="$(resolve_chezmoi_source_dir)/.chezmoidata.toml"
44
- if [[ -f "$data_file" ]]; then
45
- ns="$(sed -n 's/^namespace = "\(.*\)"/\1/p' "$data_file" | head -1)"
46
- fi
47
- printf '%s\n' "${ns:-default}"
48
- }
49
-
50
- _fleet_emit_event() {
51
- local event="$1" status="${2:-ok}"
52
- shift 2 || true
53
- local ts node_id namespace
54
- ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
55
- node_id="$(_fleet_node_id)"
56
- namespace="$(_fleet_namespace)"
57
- mkdir -p "$_FLEET_STATE_DIR" 2>/dev/null || return 0
58
- local payload
59
- payload=$(printf '{"time":"%s","event":"%s","status":"%s","node_id":"%s","namespace":"%s","trace_id":"%s"' \
60
- "$ts" "$event" "$status" "$node_id" "$namespace" "$DOT_TRACE_ID")
61
- while [[ $# -gt 0 ]]; do
62
- payload+=$(printf ',"%s":"%s"' "${1%%=*}" "${1#*=}")
63
- shift
64
- done
65
- payload+='}'
66
- printf '%s\n' "$payload" >>"$_FLEET_EVENTS_FILE" 2>/dev/null || true
67
-
68
- # Forward to endpoint if configured
69
- local endpoint=""
70
- local data_file
71
- data_file="$(resolve_chezmoi_source_dir)/.chezmoidata.toml"
72
- if [[ -f "$data_file" ]]; then
73
- endpoint="$(sed -n 's/^endpoint = "\(.*\)"/\1/p' "$data_file" | head -1)"
74
- fi
75
- if [[ -n "$endpoint" ]] && [[ "$endpoint" == https://* ]]; then
76
- curl --proto '=https' --tlsv1.2 -fsSL -X POST -H "Content-Type: application/json" \
77
- -d "$payload" "$endpoint" >/dev/null 2>&1 || true
78
- fi
79
- }
80
-
81
- cmd_fleet_status() {
82
- local json_mode=0
83
- [[ "${1:-}" == "--json" || "${1:-}" == "-j" ]] && json_mode=1
84
-
85
- local node_id namespace version os_type kernel shell_type
86
- node_id="$(_fleet_node_id)"
87
- namespace="$(_fleet_namespace)"
88
- version="$(dotfiles_version)"
89
- os_type="$(uname -s)"
90
- kernel="$(uname -r)"
91
- shell_type="${SHELL##*/}"
92
-
93
- local drift_status="clean"
94
- if has_command chezmoi; then
95
- local drift_output
96
- drift_output="$(chezmoi status 2>/dev/null || true)"
97
- if [[ -n "$drift_output" ]]; then
98
- drift_status="drifted"
99
- fi
100
- fi
101
-
102
- local last_apply=""
103
- local state_log="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/dot.log"
104
- if [[ -f "$state_log" ]]; then
105
- # `set -euo pipefail` at the top of this script kills the whole
106
- # command when grep matches nothing (rc=1). Wrap the pipeline so
107
- # `last_apply` cleanly becomes empty and the UI still renders.
108
- last_apply="$(grep 'apply' "$state_log" 2>/dev/null | tail -1 | sed -n 's/^\[\([^]]*\)\].*/\1/p' || true)"
109
- fi
110
-
111
- if [[ "$json_mode" -eq 1 ]]; then
112
- printf '{"node_id":"%s","namespace":"%s","version":"%s","os":"%s","kernel":"%s","shell":"%s","drift":"%s","last_apply":"%s"}\n' \
113
- "$node_id" "$namespace" "$version" "$os_type" "$kernel" "$shell_type" "$drift_status" "$last_apply"
114
- return 0
115
- fi
116
-
117
- ui_header "Fleet Node Status"
118
- echo ""
119
- ui_ok "Node ID" "$node_id"
120
- ui_ok "Namespace" "$namespace"
121
- ui_ok "Version" "v$version"
122
- ui_ok "OS" "$os_type $kernel"
123
- ui_ok "Shell" "$shell_type"
124
- if [[ "$drift_status" == "clean" ]]; then
125
- ui_ok "Drift" "$drift_status"
126
- else
127
- ui_warn "Drift" "$drift_status"
128
- fi
129
- if [[ -n "$last_apply" ]]; then
130
- ui_info "Last Apply" "$last_apply"
131
- fi
132
-
133
- _fleet_emit_event "status" "ok" "version=$version" "drift=$drift_status"
134
- }
135
-
136
- _DRIFT_HISTORY_FILE="$_FLEET_STATE_DIR/drift-history.jsonl"
137
-
138
- _fleet_drift_append_history() {
139
- local drift_output="$1"
140
- local ts
141
- ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
142
- mkdir -p "$_FLEET_STATE_DIR" 2>/dev/null || return 0
143
- if [[ -z "$drift_output" ]]; then
144
- printf '{"time":"%s","status":"clean","files":[]}\n' "$ts" >>"$_DRIFT_HISTORY_FILE" 2>/dev/null || true
145
- else
146
- local files_json
147
- files_json="$(printf '%s\n' "$drift_output" | awk '{print $NF}' | jq -R . | jq -s . 2>/dev/null || echo '[]')"
148
- printf '{"time":"%s","status":"drifted","files":%s}\n' "$ts" "$files_json" >>"$_DRIFT_HISTORY_FILE" 2>/dev/null || true
149
- fi
150
- }
151
-
152
- cmd_fleet_drift() {
153
- local subcommand="${1:-check}"
154
- if [[ "${1:-}" == --* ]] || [[ -z "${1:-}" ]]; then
155
- subcommand="check"
156
- else
157
- shift || true
158
- fi
159
-
160
- case "$subcommand" in
161
- check)
162
- ui_header "Fleet Drift Report"
163
- echo ""
164
-
165
- if ! has_command chezmoi; then
166
- ui_err "chezmoi" "not installed"
167
- return 1
168
- fi
169
-
170
- local drift_output
171
- drift_output="$(chezmoi status 2>/dev/null || true)"
172
-
173
- _fleet_drift_append_history "$drift_output"
174
-
175
- if [[ -z "$drift_output" ]]; then
176
- ui_ok "Status" "No drift detected"
177
- _fleet_emit_event "drift_check" "clean"
178
- return 0
179
- fi
180
-
181
- ui_warn "Status" "Configuration drift detected"
182
- echo ""
183
- printf '%s\n' "$drift_output" | while IFS= read -r line; do
184
- local change_type="${line:0:2}"
185
- local file_path="${line:3}"
186
- case "$change_type" in
187
- "MM" | "A " | " M")
188
- ui_warn "$change_type" "$file_path"
189
- ;;
190
- *)
191
- ui_info "$change_type" "$file_path"
192
- ;;
193
- esac
194
- done
195
-
196
- _fleet_emit_event "drift_check" "drifted" "count=$(echo "$drift_output" | wc -l | tr -d ' ')"
197
- ;;
198
- history)
199
- ui_header "Drift History"
200
- echo ""
201
- if [[ ! -f "$_DRIFT_HISTORY_FILE" ]]; then
202
- ui_info "No drift history recorded yet."
203
- return 0
204
- fi
205
- local count="${1:-20}"
206
- tail -n "$count" "$_DRIFT_HISTORY_FILE" | while IFS= read -r line; do
207
- local time status file_count
208
- # One jq per line (was three: .time, .status, .files|length).
209
- IFS=$'\t' read -r time status file_count < <(
210
- printf '%s' "$line" | jq -r '[.time, .status, (.files | length)] | @tsv' 2>/dev/null
211
- )
212
- [[ -n "$time" ]] || time="?"
213
- [[ -n "$status" ]] || status="?"
214
- [[ -n "$file_count" ]] || file_count=0
215
- if [[ "$status" == "clean" ]]; then
216
- ui_ok "$time" "clean"
217
- else
218
- ui_warn "$time" "drifted ($file_count files)"
219
- fi
220
- done
221
- ;;
222
- predict)
223
- ui_header "Drift Prediction"
224
- echo ""
225
- if [[ ! -f "$_DRIFT_HISTORY_FILE" ]]; then
226
- ui_info "Not enough history for prediction."
227
- return 0
228
- fi
229
- # Simple heuristic: files that drifted in >50% of the last 10 checks
230
- local threshold=5
231
- jq -r '.files[]?' "$_DRIFT_HISTORY_FILE" | tail -n 1000 | sort | uniq -c | sort -rn | while read -r count file; do
232
- if [[ "$count" -ge "$threshold" ]]; then
233
- ui_warn "Likely to drift" "$file (drifted $count times recently)"
234
- fi
235
- done
236
- local total_checks
237
- total_checks="$(wc -l <"$_DRIFT_HISTORY_FILE" | tr -d ' ')"
238
- ui_info "History" "$total_checks checks recorded"
239
- ;;
240
- *)
241
- die "Usage: dot fleet drift [check|history|predict]"
242
- ;;
243
- esac
244
- }
245
-
246
- cmd_fleet_events() {
247
- local count="${1:-20}"
248
- if [[ ! -f "$_FLEET_EVENTS_FILE" ]]; then
249
- ui_info "No fleet events recorded yet."
250
- ui_info "Events file" "$_FLEET_EVENTS_FILE"
251
- return 0
252
- fi
253
-
254
- ui_header "Fleet Events (last $count)"
255
- echo ""
256
-
257
- if has_command jq; then
258
- tail -n "$count" "$_FLEET_EVENTS_FILE" | jq -r '"\(.time)\t\(.event)\t\(.status)\t\(.node_id)"' | while IFS=$'\t' read -r time event status node; do
259
- if [[ "$status" == "ok" || "$status" == "clean" ]]; then
260
- ui_ok "$event" "$time ($node)"
261
- else
262
- ui_warn "$event" "$time ($node)"
263
- fi
264
- done
265
- else
266
- tail -n "$count" "$_FLEET_EVENTS_FILE"
267
- fi
268
- }
269
-
270
- cmd_fleet_namespace() {
271
- local subcommand="${1:-show}"
272
- shift || true
273
-
274
- case "$subcommand" in
275
- show)
276
- local ns
277
- ns="$(_fleet_namespace)"
278
- ui_header "Fleet Namespace"
279
- ui_ok "Active" "$ns"
280
-
281
- local data_file
282
- data_file="$(resolve_chezmoi_source_dir)/.chezmoidata.toml"
283
- if [[ -f "$data_file" ]]; then
284
- echo ""
285
- ui_section "Available Namespaces"
286
- grep '^\[namespaces\.' "$data_file" | sed 's/\[namespaces\.\(.*\)\]/\1/' | while IFS= read -r name; do
287
- if [[ "$name" == "$ns" ]]; then
288
- ui_ok "$name" "[active]"
289
- else
290
- ui_info "$name" ""
291
- fi
292
- done
293
- fi
294
- ;;
295
- set)
296
- local new_ns="${1:-}"
297
- [[ -n "$new_ns" ]] || die "Usage: dot fleet namespace set <name>"
298
- validate_name "$new_ns" "namespace"
299
- local data_file
300
- data_file="$(resolve_chezmoi_source_dir)/.chezmoidata.toml"
301
- if grep -q "^namespace = " "$data_file" 2>/dev/null; then
302
- # Atomic write: render into a tempfile + mv so concurrent
303
- # `dot fleet namespace set` callers can't corrupt the TOML.
304
- # Avoids `sed -i` portability dance (GNU `-i` vs BSD `-i ''`).
305
- local _tmp
306
- _tmp="$(mktemp "${data_file}.XXXXXX")" || die "Cannot create tempfile"
307
- # Explicit if/else instead of A && B || C — the latter (SC2015)
308
- # silently runs C when B itself fails, masking real mv errors.
309
- if sed "s/^namespace = \".*\"/namespace = \"$new_ns\"/" "$data_file" >"$_tmp"; then
310
- if ! mv "$_tmp" "$data_file"; then
311
- rm -f "$_tmp"
312
- die "Failed to commit namespace update"
313
- fi
314
- else
315
- rm -f "$_tmp"
316
- die "Failed to render namespace update"
317
- fi
318
- fi
319
- ui_ok "Namespace" "Set to '$new_ns'. Run 'dot sync' to apply."
320
- _fleet_emit_event "namespace_set" "ok" "namespace=$new_ns"
321
- ;;
322
- *)
323
- die "Usage: dot fleet namespace [show|set <name>]"
324
- ;;
325
- esac
326
- }
327
-
328
- cmd_fleet_enforce() {
329
- local subcommand="${1:-status}"
330
- shift || true
331
-
332
- local repo_root
333
- repo_root="$(resolve_chezmoi_source_dir)"
334
- [[ -z "$repo_root" ]] && repo_root="$(resolve_source_dir)"
335
- local profiles_file="$repo_root/dot_config/dotfiles/agent-profiles.json"
336
-
337
- case "$subcommand" in
338
- status)
339
- if [[ ! -f "$profiles_file" ]]; then
340
- ui_err "Profiles" "agent-profiles.json not found"
341
- return 1
342
- fi
343
- local enforcement
344
- enforcement="$(jq -r '.rbac.enforcement // "advisory"' "$profiles_file")"
345
- ui_header "RBAC Enforcement"
346
- ui_ok "Mode" "$enforcement"
347
- ui_ok "Default role" "$(jq -r '.rbac.defaultRole // "developer"' "$profiles_file")"
348
- jq -r '.rbac.roles | to_entries[] | "\(.key)\t\(.value.allowedProfiles | join(", "))"' "$profiles_file" | while IFS=$'\t' read -r role profiles; do
349
- ui_info "$role" "$profiles"
350
- done
351
- ;;
352
- set)
353
- local mode="${1:-}"
354
- [[ -n "$mode" ]] || die "Usage: dot fleet enforce set <advisory|strict>"
355
- case "$mode" in
356
- advisory | strict) ;;
357
- *) die "Invalid enforcement mode: $mode (use advisory or strict)" ;;
358
- esac
359
- [[ -f "$profiles_file" ]] || die "agent-profiles.json not found"
360
- local tmp
361
- tmp="$(jq --arg mode "$mode" '.rbac.enforcement = $mode' "$profiles_file")"
362
- printf '%s\n' "$tmp" >"$profiles_file"
363
- ui_ok "Enforcement" "set to '$mode'"
364
- _fleet_emit_event "enforcement_set" "ok" "mode=$mode"
365
- ;;
366
- *)
367
- die "Usage: dot fleet enforce [status|set <advisory|strict>]"
368
- ;;
369
- esac
370
- }
371
-
372
- _fleet_hosts_file() {
373
- printf '%s\n' "${DOTFILES_FLEET_HOSTS:-$HOME/.config/dotfiles/fleet.toml}"
374
- }
375
-
376
- # Parse the hosts file. Format:
377
- # [hosts.laptop]
378
- # ssh = "user@laptop.local"
379
- # profile = "workstation"
380
- #
381
- # Echoes one record per line: "<name>\t<ssh-target>\t<profile>".
382
- _fleet_hosts_iter() {
383
- local f
384
- f="$(_fleet_hosts_file)"
385
- [[ -f "$f" ]] || return 0
386
- awk '
387
- BEGIN { name = ""; ssh = ""; profile = "" }
388
- /^\[hosts\./ {
389
- if (name != "") { printf "%s\t%s\t%s\n", name, ssh, profile }
390
- gsub(/[\[\]]/, "", $0); sub(/^hosts\./, "", $0); name = $0
391
- ssh = ""; profile = ""
392
- next
393
- }
394
- /^ssh[[:space:]]*=/ { sub(/^ssh[[:space:]]*=[[:space:]]*/, ""); gsub(/"/, ""); ssh = $0; next }
395
- /^profile[[:space:]]*=/ { sub(/^profile[[:space:]]*=[[:space:]]*/, ""); gsub(/"/, ""); profile = $0; next }
396
- END {
397
- if (name != "") { printf "%s\t%s\t%s\n", name, ssh, profile }
398
- }
399
- ' "$f"
400
- }
401
-
402
- # SSH-based "dot fleet apply" — push the local dotfiles state out to
403
- # each registered host. The §3 hero-feature: nobody else owns the
404
- # "Ansible for personal devices" niche.
405
- cmd_fleet_apply() {
406
- local dry_run=0 only_host="" cmd="" jobs=4 verify_hosts=0
407
- while [[ $# -gt 0 ]]; do
408
- case "$1" in
409
- --dry-run | -n)
410
- dry_run=1
411
- shift
412
- ;;
413
- --verify-hosts)
414
- # Pre-flight check that every host already has a known_hosts
415
- # entry, closing the TOFU window before any SSH connection.
416
- # R3 audit N4. Without this, accept-new is the default and a
417
- # first-connection MITM can seed an attacker key.
418
- verify_hosts=1
419
- shift
420
- ;;
421
- --host)
422
- only_host="$2"
423
- shift 2
424
- ;;
425
- --cmd)
426
- cmd="$2"
427
- shift 2
428
- ;;
429
- --jobs | -j)
430
- jobs="$2"
431
- shift 2
432
- ;;
433
- --help | -h)
434
- cat <<EOF
435
- Usage: dot fleet apply [--host <name>] [--cmd <shell>] [--dry-run] [--jobs <n>]
436
-
437
- Push dotfiles state to every host registered in:
438
- ${DOTFILES_FLEET_HOSTS:-\$HOME/.config/dotfiles/fleet.toml}
439
-
440
- Format of fleet.toml:
441
- [hosts.laptop]
442
- ssh = "user@laptop.local"
443
- profile = "workstation"
444
-
445
- Hostnames are validated against [A-Za-z0-9._@:+/-]+ before any SSH
446
- fan-out; invalid entries abort the apply.
447
-
448
- First-time SSH connections use StrictHostKeyChecking=accept-new (TOFU).
449
- If your threat model requires no TOFU window, pre-populate
450
- ~/.ssh/known_hosts before running this command.
451
-
452
- Behavior:
453
- By default each host runs: dot sync && dot doctor --quiet
454
- Override with --cmd "<shell>" to run an arbitrary command on every
455
- host (e.g. --cmd "uptime").
456
-
457
- WARNING: --cmd is the trust boundary. Whatever string you pass
458
- executes on every remote host with the credentials your SSH key
459
- carries. Verify the command before running.
460
-
461
- Flags:
462
- --host <name> Apply to a single host only.
463
- --cmd <shell> Run a custom command instead of 'dot sync'.
464
- --dry-run, -n Print resolved hosts + planned command; don't SSH.
465
- --jobs <n> Parallelism (default 4).
466
- --verify-hosts Refuse to open any SSH connection unless every
467
- target host already has a key in ~/.ssh/known_hosts.
468
- Use when your threat model excludes the TOFU window.
469
- EOF
470
- return 0
471
- ;;
472
- *)
473
- ui_err "Unknown arg" "$1"
474
- return 1
475
- ;;
476
- esac
477
- done
478
-
479
- local hosts_file
480
- hosts_file="$(_fleet_hosts_file)"
481
- if [[ ! -f "$hosts_file" ]]; then
482
- ui_err "Fleet" "no hosts file at $hosts_file"
483
- ui_info "Hint" "create it with stanzas like '[hosts.laptop]\\nssh = \"user@laptop.local\"'"
484
- return 1
485
- fi
486
-
487
- local entries
488
- entries="$(_fleet_hosts_iter)"
489
- if [[ -z "$entries" ]]; then
490
- ui_err "Fleet" "hosts file is empty: $hosts_file"
491
- return 1
492
- fi
493
- if [[ -n "$only_host" ]]; then
494
- entries="$(printf '%s\n' "$entries" | awk -F'\t' -v h="$only_host" '$1 == h')"
495
- [[ -n "$entries" ]] || {
496
- ui_err "Fleet" "host not found: $only_host"
497
- return 1
498
- }
499
- fi
500
-
501
- local default_cmd='dot sync && dot doctor --quiet'
502
- local effective_cmd="${cmd:-$default_cmd}"
503
-
504
- ui_header "Fleet apply"
505
- ui_info "Hosts file" "$hosts_file"
506
- ui_info "Command" "$effective_cmd"
507
- ui_info "Parallel" "$jobs"
508
-
509
- if [[ "$dry_run" -eq 1 ]]; then
510
- printf '%s\n' "$entries" | while IFS=$'\t' read -r name ssh profile; do
511
- ui_info "$name" "$ssh profile=$profile cmd=$effective_cmd"
512
- done
513
- ui_ok "Dry-run" "no SSH connections opened"
514
- return 0
515
- fi
516
-
517
- if ! command -v ssh >/dev/null 2>&1; then
518
- ui_err "ssh" "not installed"
519
- return 127
520
- fi
521
-
522
- local total=0 ok=0 fail=0
523
- local tmpdir
524
- # `-t` template includes PID + random, so two concurrent `dot fleet
525
- # apply` invocations from the same user can't collide on $tmpdir.
526
- tmpdir="$(mktemp -d -t dotfiles-fleet.XXXXXX)"
527
- # Capture tmpdir's value at trap-definition time (via the eval-on-
528
- # define `printf -v`), NOT at trap-fire time. A naive
529
- # `trap 'rm -rf "$tmpdir"' RETURN` is unsafe under set -u because
530
- # `local tmpdir` is destroyed before the RETURN trap evaluates.
531
- # The SC2064 warning ("Use single quotes, otherwise this expands now
532
- # rather than when signalled") is exactly the behaviour we want here —
533
- # we explicitly want eager expansion. Suppress per-line.
534
- local _cleanup
535
- printf -v _cleanup 'rm -rf %q' "$tmpdir"
536
- # shellcheck disable=SC2064
537
- trap "$_cleanup" RETURN
538
-
539
- # Validate every hostname against a conservative regex BEFORE fan-out.
540
- # `user@host:port` characters only — refuses single quotes, backticks,
541
- # `$()`, semicolons, spaces, any shell metacharacter. Closes the
542
- # round-2 audit's hostname-injection finding.
543
- while IFS=$'\t' read -r name ssh profile; do
544
- [[ -n "$name" ]] || continue
545
- if [[ ! "$ssh" =~ ^[a-zA-Z0-9._@:+/-]+$ ]]; then
546
- ui_err "$name" "invalid ssh target ($ssh) — only [a-zA-Z0-9._@:+/-] allowed"
547
- return 1
548
- fi
549
- done <<<"$entries"
550
-
551
- # --verify-hosts: refuse the apply when any target host is missing
552
- # from ~/.ssh/known_hosts. Closes the R3 audit N4 TOFU-window gap.
553
- if ((verify_hosts == 1)); then
554
- local known_hosts="${HOME}/.ssh/known_hosts"
555
- if [[ ! -f "$known_hosts" ]]; then
556
- ui_err "verify-hosts" "no $known_hosts — populate before --verify-hosts"
557
- return 1
558
- fi
559
- local unknown_count=0
560
- while IFS=$'\t' read -r name ssh profile; do
561
- [[ -n "$name" && -n "$ssh" ]] || continue
562
- # Strip `user@` prefix and `:port` suffix for the lookup.
563
- local hostpart="${ssh#*@}"
564
- hostpart="${hostpart%%:*}"
565
- if ! ssh-keygen -F "$hostpart" -f "$known_hosts" >/dev/null 2>&1; then
566
- ui_err "$name" "no known_hosts entry for $hostpart — would TOFU on first connect"
567
- unknown_count=$((unknown_count + 1))
568
- fi
569
- done <<<"$entries"
570
- if ((unknown_count > 0)); then
571
- ui_err "verify-hosts" "$unknown_count host(s) missing from known_hosts — aborting"
572
- return 1
573
- fi
574
- ui_ok "verify-hosts" "all hosts found in known_hosts"
575
- fi
576
-
577
- # Run one SSH per host, parallelised via background jobs with a
578
- # semaphore. We DO NOT use `xargs -d` because that flag is GNU-only
579
- # and the §3 hero feature must work on macOS BSD xargs too. Also
580
- # avoids embedding `{}` substitution into a `bash -c` (the previous
581
- # implementation had a quoting hazard around TOML hostnames).
582
- _fleet_apply_one() {
583
- local _name="$1" _ssh="$2" _cmd="$3" _tmp="$4"
584
- if ssh -o BatchMode=yes -o ConnectTimeout=10 \
585
- -o StrictHostKeyChecking=accept-new \
586
- "$_ssh" "$_cmd" </dev/null \
587
- >"$_tmp/$_name.out" 2>"$_tmp/$_name.err"; then
588
- printf 'ok\n' >"$_tmp/$_name.status"
589
- else
590
- printf 'fail %d\n' "$?" >"$_tmp/$_name.status"
591
- fi
592
- }
593
-
594
- # Throttle to `jobs` concurrent workers. `wait -n` (wait for the next
595
- # job to finish) is bash 4.3+, but macOS ships bash 3.2 — there it
596
- # silently fails and the throttle collapses to unbounded parallelism.
597
- # Track PIDs and block on the oldest when at capacity instead; works on
598
- # bash 3.2 and 4+ alike.
599
- local _pids=()
600
- while IFS=$'\t' read -r name ssh profile; do
601
- [[ -n "$name" && -n "$ssh" ]] || continue
602
- total=$((total + 1))
603
- while ((${#_pids[@]} >= jobs)); do
604
- wait "${_pids[0]}" 2>/dev/null || true
605
- _pids=("${_pids[@]:1}")
606
- done
607
- _fleet_apply_one "$name" "$ssh" "$effective_cmd" "$tmpdir" &
608
- _pids+=("$!")
609
- done <<<"$entries"
610
- wait
611
-
612
- # `while < <(printf ...)` instead of `printf ... | while` — the
613
- # pipe form runs the loop body in a subshell, so the ok/fail
614
- # counters never propagate back to the parent. Caught by
615
- # tests/unit/fleet/test_fleet_apply_mocked_ssh.sh which exercised
616
- # the full apply path (the dry-run test missed this).
617
- while IFS=$'\t' read -r name ssh profile; do
618
- [[ -n "$name" ]] || continue
619
- if [[ -s "$tmpdir/$name.status" ]] && head -1 "$tmpdir/$name.status" | grep -q '^ok'; then
620
- ui_ok "$name" "$ssh"
621
- ok=$((ok + 1))
622
- else
623
- local err_summary=""
624
- [[ -s "$tmpdir/$name.err" ]] && err_summary=" — $(head -1 "$tmpdir/$name.err")"
625
- ui_err "$name" "$ssh${err_summary}"
626
- fail=$((fail + 1))
627
- fi
628
- local _evt_status="unknown"
629
- [[ -s "$tmpdir/$name.status" ]] && _evt_status="$(head -1 "$tmpdir/$name.status")"
630
- _fleet_emit_event "apply" "$_evt_status" "host=$name" "cmd=$effective_cmd"
631
- done < <(printf '%s\n' "$entries")
632
-
633
- ui_info "Summary" "$ok ok / $fail failed / $total total"
634
- [[ "$fail" -eq 0 ]]
635
- }
636
-
637
- cmd_fleet() {
638
- local subcommand="${1:-status}"
639
- if [[ "${1:-}" == --* ]] || [[ -z "${1:-}" ]]; then
640
- subcommand="status"
641
- else
642
- shift || true
643
- fi
644
-
645
- case "$subcommand" in
646
- status)
647
- cmd_fleet_status "$@"
648
- ;;
649
- drift)
650
- cmd_fleet_drift "$@"
651
- ;;
652
- events)
653
- cmd_fleet_events "$@"
654
- ;;
655
- namespace | ns)
656
- cmd_fleet_namespace "$@"
657
- ;;
658
- enforce)
659
- cmd_fleet_enforce "$@"
660
- ;;
661
- apply | push)
662
- cmd_fleet_apply "$@"
663
- ;;
664
- *)
665
- ui_header "Fleet Commands"
666
- echo ""
667
- ui_info "Usage" "dot fleet [command]"
668
- echo ""
669
- ui_ok "status" "Show this node's fleet status (--json for machine output)"
670
- ui_ok "drift" "Check for configuration drift"
671
- ui_ok "events" "Show recent fleet events"
672
- ui_ok "namespace" "Show or set the active namespace"
673
- ui_ok "enforce" "Show or set RBAC enforcement mode (advisory|strict)"
674
- ui_ok "apply" "SSH out to every host in fleet.toml and run 'dot sync'"
675
- ;;
676
- esac
677
- }
678
-
679
- # Dispatch
680
- case "${1:-}" in
681
- fleet)
682
- shift
683
- cmd_fleet "$@"
684
- ;;
685
- *)
686
- cmd_fleet "$@"
687
- ;;
688
- esac