@sebastienrousseau/dotfiles 0.2.508 → 0.2.511
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 +42 -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/articles/.pages +6 -0
- package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +216 -0
- package/docs/articles/2026-07-05-fish-startup-abbr.md +153 -0
- package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +128 -0
- package/docs/articles/index.md +36 -0
- 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/diagnostics/aliases-manifest.sh +19 -1
- package/scripts/diagnostics/verify_state.sh +6 -2
- package/scripts/diagnostics/version-locks.sh +10 -6
- package/scripts/dot/commands/agent.sh +12 -7
- 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 +11 -4
- package/scripts/dot/commands/lint.sh +55 -30
- package/scripts/dot/commands/registry.sh +7 -1
- package/scripts/dot/commands/secrets.sh +61 -7
- package/scripts/dot/commands/security.sh +8 -0
- package/scripts/dot/commands/tools.sh +11 -2
- package/scripts/dot/powershell/Dot.psm1 +1 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- 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/tools/detect-collisions.py +19 -1
- package/scripts/version-sync.sh +36 -2
- package/docs/_config.yml +0 -59
|
@@ -147,9 +147,7 @@ _ai_get_cached_status() {
|
|
|
147
147
|
|
|
148
148
|
# Look up field $2 (2=present 0/1, 3=version) for binary $1 from the
|
|
149
149
|
# cached TSV. Replaces a bash-4 associative array for macOS bash 3.2.
|
|
150
|
-
_ai_status_field
|
|
151
|
-
awk -F'\t' -v b="$1" -v f="$2" '$1==b{print $f;exit}' "$AI_STATUS_CACHE_FILE" 2>/dev/null
|
|
152
|
-
}
|
|
150
|
+
# (Removed _ai_status_field — cmd_ai_status now reads both fields in one awk.)
|
|
153
151
|
|
|
154
152
|
# _ai_mise_pkg (binary -> mise package map) is defined in lib/dot/ai-install.sh.
|
|
155
153
|
|
|
@@ -193,8 +191,13 @@ cmd_ai_status() {
|
|
|
193
191
|
ui_section "$category"
|
|
194
192
|
current_category="$category"
|
|
195
193
|
fi
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
# One awk over the cache line for this tool (was two: field 2 and field 3).
|
|
195
|
+
local _st_installed _st_ver
|
|
196
|
+
IFS=$'\t' read -r _st_installed _st_ver < <(
|
|
197
|
+
awk -F'\t' -v b="$bin" '$1==b{print $2"\t"$3;exit}' "$AI_STATUS_CACHE_FILE" 2>/dev/null
|
|
198
|
+
)
|
|
199
|
+
if [[ "$_st_installed" == "1" ]]; then
|
|
200
|
+
ver="$_st_ver"
|
|
198
201
|
[[ -z "$ver" ]] && ver="installed"
|
|
199
202
|
[[ "$bin" == "claude" ]] && ver="${ver%% *}"
|
|
200
203
|
ui_ok "$name" "$ver — $desc"
|
|
@@ -33,7 +33,20 @@ cmd_wallpaper() {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
cmd_fonts() {
|
|
36
|
-
|
|
36
|
+
local sub="${1:-install}"
|
|
37
|
+
case "$sub" in
|
|
38
|
+
patch)
|
|
39
|
+
shift
|
|
40
|
+
run_script "scripts/fonts/patch-fonts.sh" "Font patch helper" "$@"
|
|
41
|
+
;;
|
|
42
|
+
install | "")
|
|
43
|
+
[[ "$sub" == install ]] && shift || true
|
|
44
|
+
run_script "scripts/fonts/install-nerd-fonts.sh" "Font install script" "$@"
|
|
45
|
+
;;
|
|
46
|
+
*)
|
|
47
|
+
run_script "scripts/fonts/install-nerd-fonts.sh" "Font install script" "$@"
|
|
48
|
+
;;
|
|
49
|
+
esac
|
|
37
50
|
}
|
|
38
51
|
|
|
39
52
|
cmd_tune() {
|
|
@@ -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 "$@"
|
|
@@ -102,7 +102,10 @@ cmd_fleet_status() {
|
|
|
102
102
|
local last_apply=""
|
|
103
103
|
local state_log="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/dot.log"
|
|
104
104
|
if [[ -f "$state_log" ]]; then
|
|
105
|
-
|
|
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)"
|
|
106
109
|
fi
|
|
107
110
|
|
|
108
111
|
if [[ "$json_mode" -eq 1 ]]; then
|
|
@@ -202,9 +205,13 @@ cmd_fleet_drift() {
|
|
|
202
205
|
local count="${1:-20}"
|
|
203
206
|
tail -n "$count" "$_DRIFT_HISTORY_FILE" | while IFS= read -r line; do
|
|
204
207
|
local time status file_count
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
208
215
|
if [[ "$status" == "clean" ]]; then
|
|
209
216
|
ui_ok "$time" "clean"
|
|
210
217
|
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 ""
|
|
@@ -102,7 +102,13 @@ _registry_fetch() {
|
|
|
102
102
|
fi
|
|
103
103
|
local tmp
|
|
104
104
|
tmp="$(mktemp "${cache_file}.XXXXXX")"
|
|
105
|
-
|
|
105
|
+
# `-fsSL -o file` should be silent, but with some upstreams (e.g.
|
|
106
|
+
# GitHub Pages) curl still writes a stray newline to stdout. That
|
|
107
|
+
# newline leaks into the caller's `$(_registry_fetch)` and later
|
|
108
|
+
# into `jq FILE` as a two-argument invocation
|
|
109
|
+
# (`jq \n /path/to/file`), producing a confusing
|
|
110
|
+
# "Could not open file" error. Silence stdout explicitly.
|
|
111
|
+
if ! curl -fsSL --max-time 15 -o "$tmp" "$url" >/dev/null; then
|
|
106
112
|
rm -f "$tmp"
|
|
107
113
|
if [[ -s "$cache_file" ]]; then
|
|
108
114
|
ui_warn "registry" "fetch failed; using stale cache at $cache_file"
|
|
@@ -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
|
|
@@ -189,8 +189,17 @@ show_language_package_managers() {
|
|
|
189
189
|
echo " pip: $(pip3 --version | cut -d' ' -f2)"
|
|
190
190
|
fi
|
|
191
191
|
if has_command pipx; then
|
|
192
|
-
local pipx_installed
|
|
193
|
-
|
|
192
|
+
local pipx_installed pipx_list_out
|
|
193
|
+
# `pipx list --short` returns non-zero when any installed package
|
|
194
|
+
# has a broken interpreter (common). Under `set -euo pipefail` a
|
|
195
|
+
# naive `local x=$(pipx …)` cascades and kills `dot packages`
|
|
196
|
+
# mid-output. Capture the pipeline separately so we can fall back
|
|
197
|
+
# cleanly without stray "N/A" lines from pipe-with-|| tricks.
|
|
198
|
+
if pipx_list_out="$(pipx list --short 2>/dev/null)"; then
|
|
199
|
+
pipx_installed="$(printf '%s' "$pipx_list_out" | wc -l | tr -d ' ')"
|
|
200
|
+
else
|
|
201
|
+
pipx_installed="N/A"
|
|
202
|
+
fi
|
|
194
203
|
echo " pipx: $(pipx --version)"
|
|
195
204
|
echo " Installed: $pipx_installed"
|
|
196
205
|
fi
|
|
@@ -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.511 standards maintained."
|
|
145
145
|
exit 0
|
|
146
146
|
fi
|
|
@@ -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."
|