@sebastienrousseau/dotfiles 0.2.507 → 0.2.510
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 +93 -0
- package/README.md +5 -5
- package/docs/COPYRIGHT +1 -1
- package/docs/GOVERNANCE.md +2 -2
- package/docs/adr/ADR-011-nushell-tier3-keep.md +2 -2
- package/docs/guides/INSTALL.md +3 -3
- package/docs/index.md +116 -27
- package/docs/interop/A2A.md +2 -2
- package/docs/interop/POWERSHELL.md +4 -4
- package/docs/manual/00-introduction.md +3 -3
- package/docs/manual/01-concepts/02-trust-model.md +3 -3
- package/docs/manual/01-concepts/04-fleet.md +1 -1
- package/docs/manual/02-tutorials/01-first-install.md +1 -1
- package/docs/manual/02-tutorials/05-deploy-fleet.md +2 -2
- package/docs/manual/03-reference/02-config-files.md +1 -1
- package/docs/manual/03-reference/04-templates.md +1 -1
- package/docs/manual/04-cookbook/01-recipes.md +1 -1
- package/docs/manual/04-cookbook/02-troubleshooting.md +1 -1
- package/docs/manual/05-appendices/B-security-checklist.md +1 -1
- package/docs/manual/command-index.md +76 -1
- package/docs/manual/index.md +1 -1
- package/docs/operations/ARCHITECTURE_ROADMAP.md +145 -0
- package/docs/operations/CI_CADENCE.md +2 -2
- package/docs/operations/COMPLETIONS.md +2 -2
- package/docs/operations/COVERAGE.md +4 -4
- package/docs/operations/DRIFT.md +1 -1
- package/docs/operations/MAINTENANCE.md +6 -6
- package/docs/operations/PERFORMANCE.md +6 -6
- package/docs/operations/RELIABILITY.md +1 -1
- package/docs/operations/ROADMAP_V0_2_503.md +1 -1
- package/docs/operations/TRACEABILITY.md +2 -0
- package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +6 -6
- package/docs/operations/VERSION_SYNC.md +3 -3
- package/docs/reference/UTILS.md +82 -0
- package/docs/security/AUDIT_BYPASS.md +3 -3
- package/docs/security/AUTOMATION_SECRETS.md +1 -1
- package/docs/security/CI_PINNING.md +10 -10
- package/docs/security/COMMIT_SIGNING.md +10 -10
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +4 -4
- package/docs/security/DISCLOSURE.md +3 -3
- package/docs/security/INCIDENT_RESPONSE.md +1 -1
- package/docs/security/INSTALL_VERIFICATION.md +2 -2
- package/docs/security/MCP_POLICY.md +4 -4
- package/docs/security/SCORECARD.md +7 -7
- package/docs/security/SECURITY_CHECKLIST.md +1 -1
- package/docs/security/SHELL_EXEMPTIONS.md +2 -2
- package/docs/security/SOUP_REGISTER.md +4 -4
- package/docs/stylesheets/extra.css +444 -0
- package/docs/themes/hero-shot.svg +1 -1
- package/install.sh +17 -6
- package/package.json +1 -1
- package/scripts/ci/check-copyright-headers.sh +1 -1
- package/scripts/ci/check-shell-preamble.sh +1 -1
- package/scripts/ci/guard-gitleaks-checkout.sh +1 -1
- package/scripts/dot/commands/agent.sh +7 -6
- package/scripts/dot/commands/ai.sh +8 -5
- package/scripts/dot/commands/appearance.sh +14 -1
- package/scripts/dot/commands/completion.sh +134 -0
- package/scripts/dot/commands/core.sh +8 -0
- package/scripts/dot/commands/diagnostics.sh +16 -0
- package/scripts/dot/commands/fleet.sh +7 -3
- package/scripts/dot/commands/lint.sh +55 -30
- package/scripts/dot/commands/secrets.sh +61 -7
- package/scripts/dot/commands/security.sh +8 -0
- package/scripts/dot/powershell/Dot.psm1 +1 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/ops/chezmoi-apply.sh +42 -8
- package/scripts/ops/chezmoi-update.sh +14 -0
- package/scripts/ops/heal-tools.sh +28 -0
- package/scripts/ops/teleport.sh +2 -2
- package/scripts/qa/examples-coverage.sh +94 -0
- package/scripts/version-sync.sh +29 -2
- package/docs/_config.yml +0 -59
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
|
+
## `dot completion <bash|zsh|fish|nu>` — emit shell completion for `dot`,
|
|
5
|
+
## generated from the canonical _dot_help_specs registry in bin/dot. Using one
|
|
6
|
+
## source of truth keeps all four shells in sync (the previously hand-maintained
|
|
7
|
+
## fish/nushell lists drifted and referenced non-existent commands).
|
|
8
|
+
##
|
|
9
|
+
## Usage:
|
|
10
|
+
## dot completion bash >> ~/.bashrc.d/dot.bash
|
|
11
|
+
## dot completion zsh > "${fpath[1]}/_dot"
|
|
12
|
+
## dot completion fish > ~/.config/fish/completions/dot.fish
|
|
13
|
+
## dot completion nu >> ~/.config/nushell/completions.nu
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
# shellcheck source=../../../lib/dot/utils.sh
|
|
19
|
+
source "$SCRIPT_DIR/../../../lib/dot/utils.sh"
|
|
20
|
+
|
|
21
|
+
_completion_dot_cli() {
|
|
22
|
+
local src
|
|
23
|
+
src="$(resolve_source_dir)"
|
|
24
|
+
printf '%s/bin/dot\n' "$src"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Emit "name<TAB>description" for every top-level command (a _dot_help_specs
|
|
28
|
+
# field-2 entry with no space; entries containing a space are subcommands).
|
|
29
|
+
# Strip ": ' from descriptions so they can't break any shell's quoting.
|
|
30
|
+
_completion_commands() {
|
|
31
|
+
local cli
|
|
32
|
+
cli="$(_completion_dot_cli)"
|
|
33
|
+
awk -F'|' '
|
|
34
|
+
/_dot_help_specs\(\)/ { in_func = 1; next }
|
|
35
|
+
in_func && /cat <<'\''EOF'\''/ { in_block = 1; next }
|
|
36
|
+
in_block && /^EOF$/ { exit }
|
|
37
|
+
in_block && NF >= 3 && $2 !~ / / {
|
|
38
|
+
name = $2; desc = $3
|
|
39
|
+
gsub(/^[ \t]+|[ \t]+$/, "", name)
|
|
40
|
+
gsub(/^[ \t]+|[ \t]+$/, "", desc)
|
|
41
|
+
gsub(/[":\047]/, "", desc)
|
|
42
|
+
if (name != "") print name "\t" desc
|
|
43
|
+
}
|
|
44
|
+
' "$cli"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Emit "parent<TAB>child<TAB>description" for every subcommand (a field-2
|
|
48
|
+
# entry that contains a space, e.g. "theme list" -> parent=theme child=list).
|
|
49
|
+
_completion_subcommands() {
|
|
50
|
+
local cli
|
|
51
|
+
cli="$(_completion_dot_cli)"
|
|
52
|
+
awk -F'|' '
|
|
53
|
+
/_dot_help_specs\(\)/ { in_func = 1; next }
|
|
54
|
+
in_func && /cat <<'\''EOF'\''/ { in_block = 1; next }
|
|
55
|
+
in_block && /^EOF$/ { exit }
|
|
56
|
+
in_block && NF >= 3 && $2 ~ / / {
|
|
57
|
+
full = $2; desc = $3
|
|
58
|
+
gsub(/^[ \t]+|[ \t]+$/, "", full)
|
|
59
|
+
gsub(/^[ \t]+|[ \t]+$/, "", desc)
|
|
60
|
+
gsub(/[":\047]/, "", desc)
|
|
61
|
+
parent = full; sub(/ .*/, "", parent)
|
|
62
|
+
child = full; sub(/^[^ ]+ /, "", child)
|
|
63
|
+
if (parent != "" && child != "") print parent "\t" child "\t" desc
|
|
64
|
+
}
|
|
65
|
+
' "$cli"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
gen_bash() {
|
|
69
|
+
local names
|
|
70
|
+
names="$(_completion_commands | cut -f1 | tr '\n' ' ')"
|
|
71
|
+
printf '# dot bash completion — generated by: dot completion bash\n'
|
|
72
|
+
printf "complete -W '%s' dot\n" "${names% }"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
gen_zsh() {
|
|
76
|
+
local name desc
|
|
77
|
+
printf '#compdef dot\n# generated by: dot completion zsh\n'
|
|
78
|
+
printf '_dot() {\n local -a commands\n commands=(\n'
|
|
79
|
+
while IFS=$'\t' read -r name desc; do
|
|
80
|
+
printf " '%s:%s'\n" "$name" "$desc"
|
|
81
|
+
done < <(_completion_commands)
|
|
82
|
+
printf ' )\n _describe '\''command'\'' commands\n}\n_dot "$@"\n'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
gen_fish() {
|
|
86
|
+
local name desc
|
|
87
|
+
printf '# dot fish completion — generated by: dot completion fish\n'
|
|
88
|
+
printf 'complete -c dot -f\n'
|
|
89
|
+
while IFS=$'\t' read -r name desc; do
|
|
90
|
+
printf 'complete -c dot -n "__fish_use_subcommand" -a %s -d "%s"\n' "$name" "$desc"
|
|
91
|
+
done < <(_completion_commands)
|
|
92
|
+
local parent child
|
|
93
|
+
while IFS=$'\t' read -r parent child desc; do
|
|
94
|
+
printf 'complete -c dot -n "__fish_seen_subcommand_from %s" -a %s -d "%s"\n' \
|
|
95
|
+
"$parent" "$child" "$desc"
|
|
96
|
+
done < <(_completion_subcommands)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
gen_nu() {
|
|
100
|
+
local name desc
|
|
101
|
+
printf '# dot nushell completion — generated by: dot completion nu\n'
|
|
102
|
+
printf 'export extern dot [\n command?: string@dot_commands\n ...args: string\n]\n'
|
|
103
|
+
printf 'def dot_commands [] {\n [\n'
|
|
104
|
+
while IFS=$'\t' read -r name desc; do
|
|
105
|
+
printf ' { value: "%s", description: "%s" }\n' "$name" "$desc"
|
|
106
|
+
done < <(_completion_commands)
|
|
107
|
+
printf ' ]\n}\n'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
usage() {
|
|
111
|
+
cat <<'USAGE'
|
|
112
|
+
Usage: dot completion <bash|zsh|fish|nu>
|
|
113
|
+
|
|
114
|
+
Generate shell completion for `dot` from the canonical command registry.
|
|
115
|
+
|
|
116
|
+
dot completion bash >> ~/.bashrc.d/dot.bash
|
|
117
|
+
dot completion zsh > "${fpath[1]}/_dot"
|
|
118
|
+
dot completion fish > ~/.config/fish/completions/dot.fish
|
|
119
|
+
dot completion nu >> ~/.config/nushell/completions.nu
|
|
120
|
+
USAGE
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Dispatch: $1 is the command name ("completion"); $2 is the target shell.
|
|
124
|
+
case "${2:-}" in
|
|
125
|
+
bash) gen_bash ;;
|
|
126
|
+
zsh) gen_zsh ;;
|
|
127
|
+
fish) gen_fish ;;
|
|
128
|
+
nu | nushell) gen_nu ;;
|
|
129
|
+
"" | -h | --help | help) usage ;;
|
|
130
|
+
*)
|
|
131
|
+
echo "dot completion: unknown shell '${2}' (want: bash|zsh|fish|nu)" >&2
|
|
132
|
+
exit 1
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
@@ -129,6 +129,10 @@ cmd_commit() {
|
|
|
129
129
|
run_script "dot_local/bin/executable_git-ai-commit" "Git AI commit script" "$@"
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
cmd_uninstall() {
|
|
133
|
+
run_script "scripts/uninstall.sh" "Uninstall script" "$@"
|
|
134
|
+
}
|
|
135
|
+
|
|
132
136
|
cmd_clean_cache() {
|
|
133
137
|
ui_info "Cache" "Clearing shell initialization caches"
|
|
134
138
|
local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
@@ -185,6 +189,10 @@ case "${1:-}" in
|
|
|
185
189
|
shift
|
|
186
190
|
cmd_clean_cache "$@"
|
|
187
191
|
;;
|
|
192
|
+
uninstall)
|
|
193
|
+
shift
|
|
194
|
+
cmd_uninstall "$@"
|
|
195
|
+
;;
|
|
188
196
|
*)
|
|
189
197
|
echo "Unknown core command: ${1:-}" >&2
|
|
190
198
|
exit 1
|
|
@@ -110,6 +110,14 @@ cmd_chaos() {
|
|
|
110
110
|
run_script "scripts/ops/chaos.sh" "Chaos engineering script" "$@"
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
cmd_teleport() {
|
|
114
|
+
run_script "scripts/ops/teleport.sh" "Teleport script" "$@"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
cmd_secret_audit() {
|
|
118
|
+
run_script "scripts/diagnostics/secret-governance.sh" "Secret governance script" "$@"
|
|
119
|
+
}
|
|
120
|
+
|
|
113
121
|
cmd_bundle() {
|
|
114
122
|
run_script "scripts/ops/bundle.sh" "Offline bundle script" "$@"
|
|
115
123
|
}
|
|
@@ -207,6 +215,14 @@ case "${1:-}" in
|
|
|
207
215
|
shift
|
|
208
216
|
cmd_chaos "$@"
|
|
209
217
|
;;
|
|
218
|
+
teleport)
|
|
219
|
+
shift
|
|
220
|
+
cmd_teleport "$@"
|
|
221
|
+
;;
|
|
222
|
+
secret-audit)
|
|
223
|
+
shift
|
|
224
|
+
cmd_secret_audit "$@"
|
|
225
|
+
;;
|
|
210
226
|
bundle)
|
|
211
227
|
shift
|
|
212
228
|
cmd_bundle "$@"
|
|
@@ -202,9 +202,13 @@ cmd_fleet_drift() {
|
|
|
202
202
|
local count="${1:-20}"
|
|
203
203
|
tail -n "$count" "$_DRIFT_HISTORY_FILE" | while IFS= read -r line; do
|
|
204
204
|
local time status file_count
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
205
|
+
# One jq per line (was three: .time, .status, .files|length).
|
|
206
|
+
IFS=$'\t' read -r time status file_count < <(
|
|
207
|
+
printf '%s' "$line" | jq -r '[.time, .status, (.files | length)] | @tsv' 2>/dev/null
|
|
208
|
+
)
|
|
209
|
+
[[ -n "$time" ]] || time="?"
|
|
210
|
+
[[ -n "$status" ]] || status="?"
|
|
211
|
+
[[ -n "$file_count" ]] || file_count=0
|
|
208
212
|
if [[ "$status" == "clean" ]]; then
|
|
209
213
|
ui_ok "$time" "clean"
|
|
210
214
|
else
|
|
@@ -19,6 +19,33 @@ dot_ui_command_banner "Lint" "${1:-}"
|
|
|
19
19
|
SHELLCHECK_ARGS=(--severity=error -e SC1091 -e SC2030 -e SC2031)
|
|
20
20
|
SHFMT_ARGS=(-i 2 -ci)
|
|
21
21
|
|
|
22
|
+
# True only if the file's shebang names a shell that both shellcheck and shfmt
|
|
23
|
+
# can process. The old `grep -qiE 'shell|bash|sh'` matched loosely — "sh" hit
|
|
24
|
+
# "fish", and `file` output pulled in python3 scripts — so non-shell files
|
|
25
|
+
# landed in the lint set and produced spurious SC1071 / shfmt parse "errors".
|
|
26
|
+
_lint_is_shell() {
|
|
27
|
+
local first interp
|
|
28
|
+
first="$(head -1 "$1" 2>/dev/null)"
|
|
29
|
+
case "$first" in '#!'*) ;; *) return 1 ;; esac
|
|
30
|
+
interp="${first#\#!}"
|
|
31
|
+
interp="${interp#"${interp%%[![:space:]]*}"}" # ltrim
|
|
32
|
+
case "$interp" in
|
|
33
|
+
*/env[[:space:]]*)
|
|
34
|
+
interp="${interp#*/env}"
|
|
35
|
+
interp="${interp#"${interp%%[![:space:]]*}"}"
|
|
36
|
+
interp="${interp%%[[:space:]]*}"
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
interp="${interp%%[[:space:]]*}"
|
|
40
|
+
interp="${interp##*/}"
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
case "$interp" in
|
|
44
|
+
sh | bash | dash | ksh | mksh | ash) return 0 ;;
|
|
45
|
+
*) return 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
22
49
|
cmd_lint() {
|
|
23
50
|
local mode="${1:-all}"
|
|
24
51
|
local src_dir
|
|
@@ -40,11 +67,10 @@ cmd_lint() {
|
|
|
40
67
|
chezmoi_src="$(resolve_chezmoi_source_dir)"
|
|
41
68
|
[[ -z "$chezmoi_src" ]] && chezmoi_src="$src_dir"
|
|
42
69
|
|
|
43
|
-
# dot_local/bin/executable_* scripts
|
|
70
|
+
# dot_local/bin/executable_* scripts — shell scripts only (skip python/zsh/etc
|
|
71
|
+
# by inspecting the shebang, so shellcheck/shfmt never see files they can't parse).
|
|
44
72
|
while IFS= read -r -d '' f; do
|
|
45
|
-
if
|
|
46
|
-
files+=("$f")
|
|
47
|
-
elif head -1 "$f" 2>/dev/null | grep -qE '^#!.*(bash|sh)'; then
|
|
73
|
+
if _lint_is_shell "$f"; then
|
|
48
74
|
files+=("$f")
|
|
49
75
|
fi
|
|
50
76
|
done < <(find "$chezmoi_src/dot_local/bin" -name 'executable_*' -type f -print0 2>/dev/null)
|
|
@@ -69,17 +95,16 @@ cmd_lint() {
|
|
|
69
95
|
if has_command shellcheck; then
|
|
70
96
|
ui_section "ShellCheck"
|
|
71
97
|
echo ""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
done
|
|
78
|
-
if [[ "$sc_fail" -eq 0 ]]; then
|
|
98
|
+
# One shellcheck invocation over all files (was one fork per file).
|
|
99
|
+
# gcc format is one line per issue, so failing files = unique paths.
|
|
100
|
+
local sc_out
|
|
101
|
+
sc_out="$(shellcheck -f gcc "${SHELLCHECK_ARGS[@]}" "${files[@]}" 2>/dev/null || true)"
|
|
102
|
+
if [[ -z "$sc_out" ]]; then
|
|
79
103
|
ui_ok "shellcheck" "$total files clean"
|
|
80
104
|
else
|
|
81
|
-
|
|
82
|
-
|
|
105
|
+
printf '%s\n' "$sc_out"
|
|
106
|
+
sc_errors=$(printf '%s\n' "$sc_out" | cut -d: -f1 | sort -u | grep -c .)
|
|
107
|
+
ui_err "shellcheck" "$sc_errors file(s) with errors"
|
|
83
108
|
fi
|
|
84
109
|
echo ""
|
|
85
110
|
else
|
|
@@ -91,17 +116,15 @@ cmd_lint() {
|
|
|
91
116
|
if has_command shfmt; then
|
|
92
117
|
ui_section "shfmt"
|
|
93
118
|
echo ""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
done
|
|
100
|
-
if [[ "$fmt_fail" -eq 0 ]]; then
|
|
119
|
+
# `shfmt -l` lists files needing formatting in one invocation
|
|
120
|
+
# (was `shfmt -d` per file).
|
|
121
|
+
local fmt_list
|
|
122
|
+
fmt_list="$(shfmt "${SHFMT_ARGS[@]}" -l "${files[@]}" 2>/dev/null || true)"
|
|
123
|
+
if [[ -z "$fmt_list" ]]; then
|
|
101
124
|
ui_ok "shfmt" "$total files formatted correctly"
|
|
102
125
|
else
|
|
103
|
-
fmt_errors=$
|
|
104
|
-
ui_err "shfmt" "$
|
|
126
|
+
fmt_errors=$(printf '%s\n' "$fmt_list" | grep -c .)
|
|
127
|
+
ui_err "shfmt" "$fmt_errors file(s) need formatting"
|
|
105
128
|
fi
|
|
106
129
|
echo ""
|
|
107
130
|
else
|
|
@@ -118,17 +141,19 @@ cmd_lint() {
|
|
|
118
141
|
fi
|
|
119
142
|
ui_section "Auto-fixing with shfmt"
|
|
120
143
|
echo ""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
# Find files needing formatting in one `shfmt -l` pass, then only
|
|
145
|
+
# rewrite that (usually small) set — was `shfmt -d` per file.
|
|
146
|
+
local fmt_list fixed=0
|
|
147
|
+
fmt_list="$(shfmt "${SHFMT_ARGS[@]}" -l "${files[@]}" 2>/dev/null || true)"
|
|
148
|
+
if [[ -z "$fmt_list" ]]; then
|
|
149
|
+
ui_ok "shfmt" "All files already formatted"
|
|
150
|
+
else
|
|
151
|
+
while IFS= read -r f; do
|
|
152
|
+
[[ -n "$f" ]] || continue
|
|
124
153
|
shfmt "${SHFMT_ARGS[@]}" -w "$f"
|
|
125
154
|
ui_ok "fixed" "${f#"$src_dir/"}"
|
|
126
155
|
fixed=$((fixed + 1))
|
|
127
|
-
|
|
128
|
-
done
|
|
129
|
-
if [[ "$fixed" -eq 0 ]]; then
|
|
130
|
-
ui_ok "shfmt" "All files already formatted"
|
|
131
|
-
else
|
|
156
|
+
done <<<"$fmt_list"
|
|
132
157
|
ui_ok "shfmt" "$fixed file(s) reformatted"
|
|
133
158
|
fi
|
|
134
159
|
echo ""
|
|
@@ -152,26 +152,80 @@ cmd_secrets_list() {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
cmd_env_load() {
|
|
155
|
-
|
|
155
|
+
# Usage: dot secrets load [<bucket>] [--shell posix|fish|nu]
|
|
156
|
+
# Emits shell code to set the bucket's secrets as env vars, in the target
|
|
157
|
+
# shell's dialect so it works beyond POSIX:
|
|
158
|
+
# posix (default): eval "$(dot secrets load ai)"
|
|
159
|
+
# fish: dot secrets load ai --shell fish | source
|
|
160
|
+
# nu: load-env (dot secrets load ai --shell nu | from nuon)
|
|
161
|
+
local bucket="ai" dialect="posix" data_file key value count=0
|
|
162
|
+
local -a keys=() values=()
|
|
163
|
+
while [[ $# -gt 0 ]]; do
|
|
164
|
+
case "$1" in
|
|
165
|
+
--shell)
|
|
166
|
+
dialect="${2:-posix}"
|
|
167
|
+
shift 2
|
|
168
|
+
;;
|
|
169
|
+
--shell=*)
|
|
170
|
+
dialect="${1#*=}"
|
|
171
|
+
shift
|
|
172
|
+
;;
|
|
173
|
+
-*) die "Unknown flag: $1 (usage: dot secrets load [<bucket>] [--shell posix|fish|nu])" ;;
|
|
174
|
+
*)
|
|
175
|
+
bucket="$1"
|
|
176
|
+
shift
|
|
177
|
+
;;
|
|
178
|
+
esac
|
|
179
|
+
done
|
|
180
|
+
|
|
156
181
|
data_file="$(dot_data_file)"
|
|
157
182
|
[[ -f "$data_file" ]] || die "Missing data file: $data_file"
|
|
158
183
|
|
|
159
184
|
while IFS= read -r key; do
|
|
160
185
|
value="$(dot_secrets_get "$key" || true)"
|
|
161
186
|
if [[ -n "$value" ]]; then
|
|
162
|
-
|
|
163
|
-
|
|
187
|
+
keys+=("$key")
|
|
188
|
+
values+=("$value")
|
|
164
189
|
count=$((count + 1))
|
|
165
190
|
fi
|
|
166
191
|
done < <(dot_secrets_bucket_keys "$data_file" "$bucket")
|
|
167
192
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
193
|
+
[[ "$count" -gt 0 ]] || die "No secrets loaded for bucket: $bucket"
|
|
194
|
+
|
|
195
|
+
local i k v esc
|
|
196
|
+
case "$dialect" in
|
|
197
|
+
posix | sh | bash | zsh)
|
|
198
|
+
for i in "${!keys[@]}"; do
|
|
199
|
+
printf 'export %s=%q\n' "${keys[$i]}" "${values[$i]}"
|
|
200
|
+
done
|
|
201
|
+
;;
|
|
202
|
+
fish)
|
|
203
|
+
for i in "${!keys[@]}"; do
|
|
204
|
+
v="${values[$i]}"
|
|
205
|
+
esc="${v//\\/\\\\}"
|
|
206
|
+
esc="${esc//\'/\\\'}" # fish single-quote escaping
|
|
207
|
+
printf "set -gx %s '%s'\n" "${keys[$i]}" "$esc"
|
|
208
|
+
done
|
|
209
|
+
;;
|
|
210
|
+
nu | nushell)
|
|
211
|
+
# Emit a NUON record for: load-env (dot secrets load <bucket> --shell nu | from nuon)
|
|
212
|
+
printf '{\n'
|
|
213
|
+
for i in "${!keys[@]}"; do
|
|
214
|
+
v="${values[$i]}"
|
|
215
|
+
esc="${v//\\/\\\\}"
|
|
216
|
+
esc="${esc//\"/\\\"}" # double-quote escaping
|
|
217
|
+
printf ' "%s": "%s"\n' "${keys[$i]}" "$esc"
|
|
218
|
+
done
|
|
219
|
+
printf '}\n'
|
|
220
|
+
;;
|
|
221
|
+
*)
|
|
222
|
+
die "Unknown shell dialect: $dialect (want: posix|fish|nu)"
|
|
223
|
+
;;
|
|
224
|
+
esac
|
|
171
225
|
}
|
|
172
226
|
|
|
173
227
|
cmd_secrets_load() {
|
|
174
|
-
cmd_env_load "
|
|
228
|
+
cmd_env_load "$@"
|
|
175
229
|
}
|
|
176
230
|
|
|
177
231
|
# Dispatch
|
|
@@ -40,6 +40,10 @@ cmd_usb_safety() {
|
|
|
40
40
|
run_script "scripts/security/usb-safety.sh" "USB safety script" "$@"
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
cmd_policy() {
|
|
44
|
+
run_script "scripts/security/enforce-policies.sh" "Policy enforcement script" "$@"
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
# Dispatch
|
|
44
48
|
case "${1:-}" in
|
|
45
49
|
backup)
|
|
@@ -70,6 +74,10 @@ case "${1:-}" in
|
|
|
70
74
|
shift
|
|
71
75
|
cmd_usb_safety "$@"
|
|
72
76
|
;;
|
|
77
|
+
policy)
|
|
78
|
+
shift
|
|
79
|
+
cmd_policy "$@"
|
|
80
|
+
;;
|
|
73
81
|
*)
|
|
74
82
|
echo "Unknown security command: ${1:-}" >&2
|
|
75
83
|
exit 1
|
|
@@ -126,7 +126,7 @@ function Invoke-DotHelp {
|
|
|
126
126
|
Write-Host ''
|
|
127
127
|
Write-Host " Version v$version"
|
|
128
128
|
Write-Host " Repo https://github.com/sebastienrousseau/dotfiles"
|
|
129
|
-
Write-Host " Docs https://github.com/sebastienrousseau/dotfiles/blob/
|
|
129
|
+
Write-Host " Docs https://github.com/sebastienrousseau/dotfiles/blob/main/docs/manual/"
|
|
130
130
|
Write-Host ''
|
|
131
131
|
Write-Host 'Native PowerShell commands (no bash required):'
|
|
132
132
|
Write-Host ''
|
|
@@ -141,6 +141,6 @@ if [[ $FAILED -eq 1 ]]; then
|
|
|
141
141
|
printf '%b\\n' " (Use --no-verify to bypass if absolutely necessary)"
|
|
142
142
|
exit 1
|
|
143
143
|
else
|
|
144
|
-
printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.
|
|
144
|
+
printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.510 standards maintained."
|
|
145
145
|
exit 0
|
|
146
146
|
fi
|
|
@@ -19,9 +19,11 @@ export DOT_COMMAND="apply"
|
|
|
19
19
|
# (macOS) expanding an empty array under `set -u` is an "unbound
|
|
20
20
|
# variable" error, which would fire on every clean `dot sync`.
|
|
21
21
|
_TMPFILES=()
|
|
22
|
+
_LOCK_DIR=""
|
|
22
23
|
cleanup() {
|
|
23
24
|
set +u
|
|
24
25
|
rm -f "${_TMPFILES[@]}" 2>/dev/null
|
|
26
|
+
[[ -n "$_LOCK_DIR" ]] && rmdir "$_LOCK_DIR" 2>/dev/null
|
|
25
27
|
set -u
|
|
26
28
|
}
|
|
27
29
|
trap cleanup EXIT
|
|
@@ -39,7 +41,9 @@ Environment Variables:
|
|
|
39
41
|
DOTFILES_CHEZMOI_APPLY_FLAGS Extra flags for chezmoi apply
|
|
40
42
|
DOTFILES_CHEZMOI_VERBOSE=1 Enable verbose output
|
|
41
43
|
DOTFILES_CHEZMOI_KEEP_GOING=1 Continue on errors
|
|
42
|
-
DOTFILES_NONINTERACTIVE=1
|
|
44
|
+
DOTFILES_NONINTERACTIVE=1 Skip interactive menus (AI provider installer)
|
|
45
|
+
DOTFILES_INTERACTIVE_APPLY=1 Re-enable chezmoi overwrite prompts
|
|
46
|
+
(apply is unattended/--force by default)
|
|
43
47
|
DOTFILES_ALIAS_STRICT_MODE=1 Run alias governance checks
|
|
44
48
|
DOTFILES_SNAPSHOT_ON_APPLY=1 Create baseline snapshot (default)
|
|
45
49
|
DOTFILES_POST_APPLY_REPAIR=1 Run post-apply repairs (default)
|
|
@@ -67,25 +71,55 @@ fi
|
|
|
67
71
|
has_flag() {
|
|
68
72
|
local needle="$1"
|
|
69
73
|
local arg
|
|
74
|
+
# Guard the expansion: on bash 3.2 (macOS) iterating an empty array
|
|
75
|
+
# under `set -u` is an unbound-variable error.
|
|
76
|
+
[[ ${#args[@]} -eq 0 ]] && return 1
|
|
70
77
|
for arg in "${args[@]}"; do
|
|
71
78
|
[[ "$arg" == "$needle" ]] && return 0
|
|
72
79
|
done
|
|
73
80
|
return 1
|
|
74
81
|
}
|
|
75
82
|
|
|
76
|
-
#
|
|
77
|
-
|
|
83
|
+
# Apply unattended by default, like an OS package manager. chezmoi is
|
|
84
|
+
# always run below under `gum spin` or with its output captured, so it
|
|
85
|
+
# never has a controlling TTY; its "<file> has changed since chezmoi last
|
|
86
|
+
# wrote it" confirmation prompt therefore cannot be answered and aborts
|
|
87
|
+
# the run with "could not open a new TTY". Passing --force applies the
|
|
88
|
+
# canonical source without prompting, so local drift to *managed* files
|
|
89
|
+
# yields to the source — exactly how `apt`/system updates behave. Keep
|
|
90
|
+
# machine-specific tweaks in the unmanaged ~/.zshrc.local or
|
|
91
|
+
# ~/.config/zsh/rc.d.local/*.zsh, which chezmoi never overwrites.
|
|
92
|
+
# Opt back into chezmoi's prompts with DOTFILES_INTERACTIVE_APPLY=1.
|
|
93
|
+
if [[ "${DOTFILES_INTERACTIVE_APPLY:-0}" != "1" ]] && ! has_flag "--force"; then
|
|
78
94
|
args+=("--force")
|
|
79
95
|
fi
|
|
80
96
|
|
|
97
|
+
# Whether interactive menus (the optional AI-provider installer below) may
|
|
98
|
+
# prompt. Suppressed without a TTY, under CI, or when non-interactive is
|
|
99
|
+
# requested — so unattended runs never hang waiting on input.
|
|
100
|
+
INTERACTIVE=1
|
|
101
|
+
if [[ "${DOTFILES_NONINTERACTIVE:-0}" == "1" ]] || [[ -n "${CI:-}" ]] || [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
|
|
102
|
+
INTERACTIVE=0
|
|
103
|
+
fi
|
|
104
|
+
|
|
81
105
|
ui_init
|
|
82
106
|
|
|
83
|
-
# Prevent concurrent execution
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
107
|
+
# Prevent concurrent execution. flock(1) is Linux-only — it does not exist
|
|
108
|
+
# on macOS, where `! flock` previously took the "already running" branch and
|
|
109
|
+
# made `dot apply` a silent no-op. Use flock where present, otherwise fall
|
|
110
|
+
# back to an atomic mkdir lock (portable to macOS/BSD).
|
|
111
|
+
_lock_base="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/dotfiles-chezmoi-apply"
|
|
112
|
+
if command -v flock >/dev/null 2>&1; then
|
|
113
|
+
exec 9>"${_lock_base}.lock"
|
|
114
|
+
if ! flock -n 9; then
|
|
115
|
+
ui_warn "Already running" "Another instance is active"
|
|
116
|
+
exit 0
|
|
117
|
+
fi
|
|
118
|
+
elif ! mkdir "${_lock_base}.lock.d" 2>/dev/null; then
|
|
87
119
|
ui_warn "Already running" "Another instance is active"
|
|
88
120
|
exit 0
|
|
121
|
+
else
|
|
122
|
+
_LOCK_DIR="${_lock_base}.lock.d" # removed by cleanup() on exit
|
|
89
123
|
fi
|
|
90
124
|
|
|
91
125
|
run_step() {
|
|
@@ -187,7 +221,7 @@ for _entry in "${_AI_PROVIDERS[@]}"; do
|
|
|
187
221
|
fi
|
|
188
222
|
done
|
|
189
223
|
|
|
190
|
-
if [[ ${#_ai_missing[@]} -gt 0 ]] && [[ "$
|
|
224
|
+
if [[ ${#_ai_missing[@]} -gt 0 ]] && [[ "$INTERACTIVE" == "1" ]]; then
|
|
191
225
|
if command -v mise &>/dev/null; then
|
|
192
226
|
echo ""
|
|
193
227
|
_ai_install_action=""
|
|
@@ -14,6 +14,20 @@ if [[ "${DOTFILES_CHEZMOI_VERBOSE:-0}" = "1" ]]; then
|
|
|
14
14
|
args+=("--verbose")
|
|
15
15
|
fi
|
|
16
16
|
|
|
17
|
+
# Apply unattended by default (see chezmoi-apply.sh). `chezmoi update` runs
|
|
18
|
+
# `apply` internally, so without --force it blocks on the "<file> has
|
|
19
|
+
# changed since chezmoi last wrote it" prompt — and `dot update` is often
|
|
20
|
+
# run from cron/CI/--async with no TTY, where that prompt aborts the run.
|
|
21
|
+
# --force makes updates land like an OS package-manager update; opt back
|
|
22
|
+
# into prompting with DOTFILES_INTERACTIVE_APPLY=1.
|
|
23
|
+
if [[ "${DOTFILES_INTERACTIVE_APPLY:-0}" != "1" ]]; then
|
|
24
|
+
_has_force=0
|
|
25
|
+
if [[ ${#args[@]} -gt 0 ]]; then
|
|
26
|
+
for _a in "${args[@]}"; do [[ "$_a" == "--force" ]] && _has_force=1; done
|
|
27
|
+
fi
|
|
28
|
+
[[ "$_has_force" == "0" ]] && args+=("--force")
|
|
29
|
+
fi
|
|
30
|
+
|
|
17
31
|
ASYNC=false
|
|
18
32
|
if [[ "${DOTFILES_ASYNC_UPDATE:-0}" = "1" ]]; then
|
|
19
33
|
ASYNC=true
|
|
@@ -31,6 +31,12 @@ detect_pkg_manager() {
|
|
|
31
31
|
echo "dnf"
|
|
32
32
|
elif command -v pacman >/dev/null 2>&1; then
|
|
33
33
|
echo "pacman"
|
|
34
|
+
elif command -v zypper >/dev/null 2>&1; then
|
|
35
|
+
echo "zypper"
|
|
36
|
+
elif command -v apk >/dev/null 2>&1; then
|
|
37
|
+
echo "apk"
|
|
38
|
+
elif command -v pkg >/dev/null 2>&1; then
|
|
39
|
+
echo "pkg"
|
|
34
40
|
elif command -v nix-env >/dev/null 2>&1; then
|
|
35
41
|
echo "nix"
|
|
36
42
|
else
|
|
@@ -66,6 +72,28 @@ install_package() {
|
|
|
66
72
|
fi
|
|
67
73
|
sudo pacman -S --noconfirm --quiet "$pkg" >/dev/null 2>&1
|
|
68
74
|
;;
|
|
75
|
+
zypper)
|
|
76
|
+
if ! command -v sudo >/dev/null 2>&1; then
|
|
77
|
+
log_error "sudo not found — cannot install '$pkg' via zypper"
|
|
78
|
+
return 1
|
|
79
|
+
fi
|
|
80
|
+
sudo zypper --quiet install -y "$pkg" >/dev/null 2>&1
|
|
81
|
+
;;
|
|
82
|
+
apk)
|
|
83
|
+
if command -v sudo >/dev/null 2>&1; then
|
|
84
|
+
sudo apk add --quiet "$pkg" >/dev/null 2>&1
|
|
85
|
+
else
|
|
86
|
+
apk add --quiet "$pkg" >/dev/null 2>&1
|
|
87
|
+
fi
|
|
88
|
+
;;
|
|
89
|
+
pkg)
|
|
90
|
+
# FreeBSD/OpenBSD/NetBSD — install as root when available.
|
|
91
|
+
if command -v sudo >/dev/null 2>&1; then
|
|
92
|
+
sudo pkg install -y "$pkg" >/dev/null 2>&1
|
|
93
|
+
else
|
|
94
|
+
pkg install -y "$pkg" >/dev/null 2>&1
|
|
95
|
+
fi
|
|
96
|
+
;;
|
|
69
97
|
nix) nix-env -iA "nixpkgs.$pkg" >/dev/null 2>&1 ;;
|
|
70
98
|
*)
|
|
71
99
|
log_error "No supported package manager found. Install '$pkg' manually."
|