@sebastienrousseau/dotfiles 0.2.501 → 0.2.502
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/README.md +38 -8
- package/docs/COPYRIGHT +1 -1
- package/docs/index.md +2 -2
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/03-reference/01-dot-cli.md +63 -45
- package/docs/manual/03-reference/02-config-files.md +2 -2
- package/docs/manual/03-reference/05-feature-flags.md +62 -3
- package/docs/manual/_toc.yml +1 -1
- package/docs/manual/command-index.md +12 -8
- package/docs/manual/index.md +66 -0
- package/docs/operations/COVERAGE.md +36 -2
- package/docs/operations/HARD_AUDIT_2026.md +631 -0
- package/docs/operations/REGISTRY.md +89 -0
- package/docs/operations/ROADMAP_2026.md +665 -0
- package/docs/operations/TRACEABILITY.md +3 -0
- package/docs/operations/VERSION_SYNC.md +4 -4
- package/docs/reference/POWERSHELL_PARITY.md +80 -0
- package/docs/registry.json +6 -0
- package/docs/security/CI_PINNING.md +113 -0
- package/docs/security/COMMIT_SIGNING.md +138 -0
- package/docs/security/DISCLOSURE.md +130 -0
- package/docs/security/KEY_ROTATION.md +81 -1
- package/docs/security/SCORECARD.md +74 -14
- package/docs/security/security-pubkey.asc +15 -0
- package/dot_config/fish/conf.d/direnv.fish +4 -0
- package/dot_config/fish/conf.d/mise-activate.fish +5 -0
- package/dot_config/git/hooks/executable_commit-msg +1 -1
- package/dot_config/shell/00-core-paths.sh.tmpl +8 -1
- package/dot_config/shell/README.md +1 -1
- package/dot_config/zsh/dot_zshrc.tmpl +57 -4
- package/dot_config/zsh/rc.d/30-options.zsh.tmpl +1 -1
- package/dot_local/bin/executable_dot +49 -9
- package/dot_local/bin/executable_dot-bootstrap +0 -1
- package/dot_local/bin/executable_dot-theme-sync +8 -8
- package/dot_local/bin/executable_tour +2 -2
- package/dot_local/share/man/man1/dot.1 +1 -1
- package/dot_local/share/zsh/completions/_dot +4 -0
- package/install.sh +44 -37
- package/package.json +1 -1
- package/scripts/ci/dot-cli-startup-bench.sh +126 -0
- package/scripts/ci/install-chezmoi-verified.sh +4 -1
- package/scripts/ci/lint-reusable-pins.sh +78 -0
- package/scripts/ci/run-coverage.sh +89 -0
- package/scripts/ci/windows-smoke-test.ps1 +136 -0
- package/scripts/diagnostics/doctor.sh +39 -9
- package/scripts/dot/commands/agent.sh +19 -22
- package/scripts/dot/commands/agents.sh +325 -0
- package/scripts/dot/commands/aliases.sh +10 -8
- package/scripts/dot/commands/core.sh +10 -4
- package/scripts/dot/commands/fleet.sh +278 -3
- package/scripts/dot/commands/init.sh +184 -0
- package/scripts/dot/commands/meta.sh +7 -4
- package/scripts/dot/commands/registry.sh +263 -0
- package/scripts/dot/commands/tools.sh +49 -1
- package/scripts/dot/lib/bento.sh +1 -1
- package/scripts/dot/lib/platform.sh +21 -8
- package/scripts/dot/lib/ui.sh +134 -2
- package/scripts/dot/lib/utils.sh +1 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/lib/secrets_provider.sh +32 -6
- package/scripts/ops/rollback.sh +14 -0
- package/scripts/security/check-disclosure-key-expiry.sh +110 -0
- package/scripts/security/lock-configs.sh +11 -2
- package/scripts/version-sync.sh +3 -0
package/scripts/dot/lib/ui.sh
CHANGED
|
@@ -359,9 +359,13 @@ ui_run_cmd() {
|
|
|
359
359
|
done
|
|
360
360
|
wait "$pid" 2>/dev/null
|
|
361
361
|
|
|
362
|
-
|
|
363
|
-
rc
|
|
362
|
+
# Guard against the rc_file being empty (race between subshell exit
|
|
363
|
+
# and parent read): an unset rc would default to non-numeric "" and
|
|
364
|
+
# fail the `[[ == "0" ]]` check confusingly. Treat empty as failure.
|
|
365
|
+
local rc=""
|
|
366
|
+
[[ -s "$rc_file" ]] && rc="$(<"$rc_file")"
|
|
364
367
|
rm -f "$rc_file"
|
|
368
|
+
rc="${rc:-1}"
|
|
365
369
|
|
|
366
370
|
ui_clear_line
|
|
367
371
|
ui_show_cursor
|
|
@@ -472,6 +476,134 @@ ui_table_sep() {
|
|
|
472
476
|
printf '\n'
|
|
473
477
|
}
|
|
474
478
|
|
|
479
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
480
|
+
# Buffered table — accumulates rows then renders via `gum table -p`
|
|
481
|
+
# (rounded borders) when gum is on PATH and stdout is a TTY; falls back
|
|
482
|
+
# to the existing printf-based ui_table_header/row otherwise.
|
|
483
|
+
#
|
|
484
|
+
# ui_table_begin "Tool" "Version" "Source" "Requested"
|
|
485
|
+
# ui_table_add "node" "24.14.0" "~/.dotfiles/mise.toml" "24.14.0"
|
|
486
|
+
# ui_table_add "node" "25.9.0" "" ""
|
|
487
|
+
# ui_table_end
|
|
488
|
+
#
|
|
489
|
+
# Cells may contain anything except ASCII US (0x1f), the internal row
|
|
490
|
+
# separator. Empty cells render as blank in both modes.
|
|
491
|
+
#
|
|
492
|
+
# Environment overrides:
|
|
493
|
+
# DOTFILES_NO_TUI=1 force the printf fallback even if gum is installed
|
|
494
|
+
# NO_COLOR=… standard convention; also forces the fallback
|
|
495
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
496
|
+
_UI_TABLE_HEADERS=()
|
|
497
|
+
_UI_TABLE_ROWS=()
|
|
498
|
+
|
|
499
|
+
ui_table_begin() {
|
|
500
|
+
_UI_TABLE_HEADERS=("$@")
|
|
501
|
+
_UI_TABLE_ROWS=()
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
ui_table_add() {
|
|
505
|
+
if [[ "${#_UI_TABLE_HEADERS[@]}" -eq 0 ]]; then
|
|
506
|
+
return 1
|
|
507
|
+
fi
|
|
508
|
+
local IFS=$'\x1f'
|
|
509
|
+
_UI_TABLE_ROWS+=("$*")
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
ui_table_end() {
|
|
513
|
+
if [[ "${#_UI_TABLE_HEADERS[@]}" -eq 0 ]]; then
|
|
514
|
+
return 0
|
|
515
|
+
fi
|
|
516
|
+
|
|
517
|
+
local use_gum=0
|
|
518
|
+
if command -v gum >/dev/null 2>&1 &&
|
|
519
|
+
[[ -t 1 ]] &&
|
|
520
|
+
[[ "${DOTFILES_NO_TUI:-0}" != "1" ]] &&
|
|
521
|
+
[[ -z "${NO_COLOR:-}" ]]; then
|
|
522
|
+
use_gum=1
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
if ((use_gum)); then
|
|
526
|
+
local header_row
|
|
527
|
+
local IFS=$'\x1f'
|
|
528
|
+
header_row="${_UI_TABLE_HEADERS[*]}"
|
|
529
|
+
# Charm palette: 212 = hot pink (matches ui_header), 240 = muted grey.
|
|
530
|
+
# Cell colour intentionally left at terminal default so the table
|
|
531
|
+
# reads on both light and dark backgrounds.
|
|
532
|
+
# Override via DOTFILES_TABLE_{BORDER,HEADER}_FG env vars.
|
|
533
|
+
local border_fg="${DOTFILES_TABLE_BORDER_FG:-240}"
|
|
534
|
+
local header_fg="${DOTFILES_TABLE_HEADER_FG:-212}"
|
|
535
|
+
{
|
|
536
|
+
printf '%s\n' "$header_row"
|
|
537
|
+
printf '%s\n' "${_UI_TABLE_ROWS[@]}"
|
|
538
|
+
} | gum table --print \
|
|
539
|
+
--separator=$'\x1f' \
|
|
540
|
+
--border=rounded \
|
|
541
|
+
--border.foreground="$border_fg" \
|
|
542
|
+
--header.foreground="$header_fg" \
|
|
543
|
+
--padding="0 1" 2>/dev/null ||
|
|
544
|
+
_ui_table_printf_fallback
|
|
545
|
+
else
|
|
546
|
+
_ui_table_printf_fallback
|
|
547
|
+
fi
|
|
548
|
+
|
|
549
|
+
_UI_TABLE_HEADERS=()
|
|
550
|
+
_UI_TABLE_ROWS=()
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
_ui_table_printf_fallback() {
|
|
554
|
+
ui_init
|
|
555
|
+
# Pre-scan all buffered rows + headers to compute per-column width so
|
|
556
|
+
# values aren't truncated or collide. Min width 8, +2 padding.
|
|
557
|
+
local ncols="${#_UI_TABLE_HEADERS[@]}"
|
|
558
|
+
local -a widths=()
|
|
559
|
+
local i
|
|
560
|
+
for ((i = 0; i < ncols; i++)); do
|
|
561
|
+
widths[i]="${#_UI_TABLE_HEADERS[i]}"
|
|
562
|
+
done
|
|
563
|
+
local row
|
|
564
|
+
for row in "${_UI_TABLE_ROWS[@]}"; do
|
|
565
|
+
local -a _fields=()
|
|
566
|
+
local IFS=$'\x1f'
|
|
567
|
+
read -ra _fields <<<"$row"
|
|
568
|
+
for ((i = 0; i < ncols; i++)); do
|
|
569
|
+
local len="${#_fields[i]}"
|
|
570
|
+
((len > widths[i])) && widths[i]=$len
|
|
571
|
+
done
|
|
572
|
+
done
|
|
573
|
+
for ((i = 0; i < ncols; i++)); do
|
|
574
|
+
((widths[i] += 2))
|
|
575
|
+
((widths[i] < 8)) && widths[i]=8
|
|
576
|
+
done
|
|
577
|
+
|
|
578
|
+
# Emit header (bold when color available), separator, then rows.
|
|
579
|
+
local out=" "
|
|
580
|
+
for ((i = 0; i < ncols; i++)); do
|
|
581
|
+
if [[ "$UI_COLOR" = "1" ]]; then
|
|
582
|
+
out+="$(printf '%s%-*s%s' "$BOLD" "${widths[i]}" "${_UI_TABLE_HEADERS[i]}" "$NORMAL")"
|
|
583
|
+
else
|
|
584
|
+
out+="$(printf '%-*s' "${widths[i]}" "${_UI_TABLE_HEADERS[i]}")"
|
|
585
|
+
fi
|
|
586
|
+
done
|
|
587
|
+
printf '%s\n' "$out"
|
|
588
|
+
|
|
589
|
+
local total=2
|
|
590
|
+
for ((i = 0; i < ncols; i++)); do ((total += widths[i])); done
|
|
591
|
+
printf ' '
|
|
592
|
+
for ((i = 0; i < total - 2; i++)); do printf '─'; done
|
|
593
|
+
printf '\n'
|
|
594
|
+
|
|
595
|
+
for row in "${_UI_TABLE_ROWS[@]}"; do
|
|
596
|
+
local -a _fields=()
|
|
597
|
+
local IFS=$'\x1f'
|
|
598
|
+
read -ra _fields <<<"$row"
|
|
599
|
+
out=" "
|
|
600
|
+
for ((i = 0; i < ncols; i++)); do
|
|
601
|
+
out+="$(printf '%-*s' "${widths[i]}" "${_fields[i]:-}")"
|
|
602
|
+
done
|
|
603
|
+
printf '%s\n' "$out"
|
|
604
|
+
done
|
|
605
|
+
}
|
|
606
|
+
|
|
475
607
|
# ═══════════════════════════════════════════════════════════════════════
|
|
476
608
|
# Logo
|
|
477
609
|
# ═══════════════════════════════════════════════════════════════════════
|
package/scripts/dot/lib/utils.sh
CHANGED
|
@@ -274,7 +274,7 @@ dot_command_summary() {
|
|
|
274
274
|
echo "Manage secrets (set/get/list/load/provider)."
|
|
275
275
|
;;
|
|
276
276
|
env)
|
|
277
|
-
echo "
|
|
277
|
+
echo "List mise-managed toolchain versions and sources."
|
|
278
278
|
;;
|
|
279
279
|
secrets-create)
|
|
280
280
|
echo "Create a new encrypted secrets file."
|
|
@@ -140,6 +140,6 @@ if [[ $FAILED -eq 1 ]]; then
|
|
|
140
140
|
printf '%b\\n' " (Use --no-verify to bypass if absolutely necessary)"
|
|
141
141
|
exit 1
|
|
142
142
|
else
|
|
143
|
-
printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.
|
|
143
|
+
printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.502 standards maintained."
|
|
144
144
|
exit 0
|
|
145
145
|
fi
|
|
@@ -126,15 +126,41 @@ dot_secrets_set() {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
dot_secrets_get() {
|
|
129
|
-
local key="${1:-}" provider
|
|
130
|
-
[[ -n "$key" ]] ||
|
|
129
|
+
local key="${1:-}" provider value rc=0
|
|
130
|
+
[[ -n "$key" ]] || {
|
|
131
|
+
printf 'dot_secrets_get: empty key\n' >&2
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
131
134
|
provider="$(dot_secrets_provider)"
|
|
132
135
|
case "$provider" in
|
|
133
|
-
macos-keychain) dot_secrets_get_macos "$key" ;;
|
|
134
|
-
pass) dot_secrets_get_pass "$key" ;;
|
|
135
|
-
plain-enc) dot_secrets_get_plain_enc "$key" ;;
|
|
136
|
-
|
|
136
|
+
macos-keychain) value="$(dot_secrets_get_macos "$key")" || rc=$? ;;
|
|
137
|
+
pass) value="$(dot_secrets_get_pass "$key")" || rc=$? ;;
|
|
138
|
+
plain-enc) value="$(dot_secrets_get_plain_enc "$key")" || rc=$? ;;
|
|
139
|
+
none)
|
|
140
|
+
# No usable provider on this host — silent miss is wrong;
|
|
141
|
+
# callers need to know whether to fall back.
|
|
142
|
+
printf 'dot_secrets_get: no provider configured for key=%s\n' "$key" >&2
|
|
143
|
+
return 2
|
|
144
|
+
;;
|
|
145
|
+
*)
|
|
146
|
+
printf 'dot_secrets_get: unknown provider %q\n' "$provider" >&2
|
|
147
|
+
return 1
|
|
148
|
+
;;
|
|
137
149
|
esac
|
|
150
|
+
if [[ "$rc" -ne 0 ]]; then
|
|
151
|
+
printf 'dot_secrets_get: provider %q failed (rc=%d) for key=%s\n' \
|
|
152
|
+
"$provider" "$rc" "$key" >&2
|
|
153
|
+
return "$rc"
|
|
154
|
+
fi
|
|
155
|
+
if [[ -z "$value" ]]; then
|
|
156
|
+
# An empty return from a successful provider call usually means
|
|
157
|
+
# "key not found" — distinguish from "provider crashed" so the
|
|
158
|
+
# caller's fallback can be different.
|
|
159
|
+
printf 'dot_secrets_get: provider %q returned empty for key=%s (not found?)\n' \
|
|
160
|
+
"$provider" "$key" >&2
|
|
161
|
+
return 3
|
|
162
|
+
fi
|
|
163
|
+
printf '%s' "$value"
|
|
138
164
|
}
|
|
139
165
|
|
|
140
166
|
dot_secrets_bucket_keys() {
|
package/scripts/ops/rollback.sh
CHANGED
|
@@ -122,6 +122,10 @@ list_backups() {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
# Create a backup
|
|
125
|
+
# LCOV_EXCL_START — body mutates real $HOME (cp/mkdir of dotfiles);
|
|
126
|
+
# can't be exercised under the coverage sandbox without destroying
|
|
127
|
+
# fixture state. The dispatcher reaches this function but the body
|
|
128
|
+
# is genuinely off-limits.
|
|
125
129
|
create_backup() {
|
|
126
130
|
local reason="${1:-manual}"
|
|
127
131
|
local timestamp
|
|
@@ -183,7 +187,9 @@ EOF
|
|
|
183
187
|
# Cleanup old backups
|
|
184
188
|
cleanup_old_backups
|
|
185
189
|
}
|
|
190
|
+
# LCOV_EXCL_STOP
|
|
186
191
|
|
|
192
|
+
# LCOV_EXCL_START — rm -rf of real backup dirs.
|
|
187
193
|
# Cleanup old backups, keeping only MAX_BACKUPS
|
|
188
194
|
cleanup_old_backups() {
|
|
189
195
|
local count
|
|
@@ -199,6 +205,7 @@ cleanup_old_backups() {
|
|
|
199
205
|
done
|
|
200
206
|
fi
|
|
201
207
|
}
|
|
208
|
+
# LCOV_EXCL_STOP
|
|
202
209
|
|
|
203
210
|
# Get the most recent backup
|
|
204
211
|
get_latest_backup() {
|
|
@@ -211,6 +218,8 @@ get_backup_by_index() {
|
|
|
211
218
|
find "$BACKUP_DIR" -maxdepth 1 -type d -name "backup_*" | sort -r | sed -n "${index}p"
|
|
212
219
|
}
|
|
213
220
|
|
|
221
|
+
# LCOV_EXCL_START — body restores files into real $HOME; dispatcher
|
|
222
|
+
# reaches it but the actual mutation is off-limits under coverage.
|
|
214
223
|
# Rollback to a specific backup
|
|
215
224
|
perform_rollback() {
|
|
216
225
|
local backup_path="$1"
|
|
@@ -278,7 +287,9 @@ Analyze why the environment may have reached a state requiring rollback and sugg
|
|
|
278
287
|
fi
|
|
279
288
|
fi
|
|
280
289
|
}
|
|
290
|
+
# LCOV_EXCL_STOP
|
|
281
291
|
|
|
292
|
+
# LCOV_EXCL_START — runs git reset --hard, mutates working tree.
|
|
282
293
|
# Git-based rollback
|
|
283
294
|
git_reset() {
|
|
284
295
|
local dry_run="${1:-0}"
|
|
@@ -338,7 +349,9 @@ git_reset() {
|
|
|
338
349
|
persist_log "GIT_RESET: to $target_commit"
|
|
339
350
|
fi
|
|
340
351
|
}
|
|
352
|
+
# LCOV_EXCL_STOP
|
|
341
353
|
|
|
354
|
+
# LCOV_EXCL_START — body copies files into real $HOME.
|
|
342
355
|
# Restore a specific file from the latest backup
|
|
343
356
|
restore_file() {
|
|
344
357
|
local file_path="$1"
|
|
@@ -417,6 +430,7 @@ restore_file() {
|
|
|
417
430
|
persist_log "RESTORE_FILE: $file_path"
|
|
418
431
|
fi
|
|
419
432
|
}
|
|
433
|
+
# LCOV_EXCL_STOP
|
|
420
434
|
|
|
421
435
|
# Show current status
|
|
422
436
|
show_status() {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# scripts/security/check-disclosure-key-expiry.sh
|
|
5
|
+
#
|
|
6
|
+
# Reads the disclosure public key from `docs/security/security-pubkey.asc`,
|
|
7
|
+
# parses its expiry, and exits non-zero when the remaining lifetime is
|
|
8
|
+
# below threshold. Closes the R3 audit N2 finding (no automated alert
|
|
9
|
+
# for the 2029-05-15 expiry).
|
|
10
|
+
#
|
|
11
|
+
# Two thresholds:
|
|
12
|
+
# --warn-days N Emit a `::warning::` if expiry is < N days away.
|
|
13
|
+
# Default 90.
|
|
14
|
+
# --fail-days N Exit 1 (CI failure) if expiry is < N days away.
|
|
15
|
+
# Default 30.
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# bash scripts/security/check-disclosure-key-expiry.sh
|
|
19
|
+
# bash scripts/security/check-disclosure-key-expiry.sh --warn-days 180 --fail-days 60
|
|
20
|
+
#
|
|
21
|
+
# Designed to run from `.github/workflows/verify-gpg-wkd.yml` on the
|
|
22
|
+
# weekly schedule, and from a maintainer's local pre-release sanity
|
|
23
|
+
# check. Does NOT require network — reads the in-repo .asc only.
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
WARN_DAYS=90
|
|
28
|
+
FAIL_DAYS=30
|
|
29
|
+
|
|
30
|
+
while [[ $# -gt 0 ]]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--warn-days)
|
|
33
|
+
WARN_DAYS="$2"
|
|
34
|
+
shift 2
|
|
35
|
+
;;
|
|
36
|
+
--fail-days)
|
|
37
|
+
FAIL_DAYS="$2"
|
|
38
|
+
shift 2
|
|
39
|
+
;;
|
|
40
|
+
-h | --help)
|
|
41
|
+
sed -n '5,25p' "${BASH_SOURCE[0]}"
|
|
42
|
+
exit 0
|
|
43
|
+
;;
|
|
44
|
+
*)
|
|
45
|
+
echo "Unknown arg: $1" >&2
|
|
46
|
+
exit 2
|
|
47
|
+
;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
52
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
53
|
+
ASC="$REPO_ROOT/docs/security/security-pubkey.asc"
|
|
54
|
+
|
|
55
|
+
if [[ ! -f "$ASC" ]]; then
|
|
56
|
+
echo "::error::no public key at $ASC" >&2
|
|
57
|
+
exit 2
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if ! command -v gpg >/dev/null 2>&1; then
|
|
61
|
+
echo "::warning::gpg not installed — cannot verify key expiry" >&2
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Import into a throwaway keyring so we don't pollute the user's real one.
|
|
66
|
+
GNUPGHOME="$(mktemp -d -t dot-disclosure-key.XXXXXX)"
|
|
67
|
+
export GNUPGHOME
|
|
68
|
+
chmod 700 "$GNUPGHOME"
|
|
69
|
+
trap 'rm -rf "$GNUPGHOME"' EXIT
|
|
70
|
+
|
|
71
|
+
# gpg can emit harmless warnings ("keyboxd older than us", "gpg-agent
|
|
72
|
+
# IPC failed" in fresh GNUPGHOME, etc.) and exit non-zero under
|
|
73
|
+
# `set -e` even when the import itself succeeded. Verify success by
|
|
74
|
+
# checking the err log for the literal `imported: N` (N >= 1) line
|
|
75
|
+
# rather than trusting gpg's exit code.
|
|
76
|
+
gpg --batch --import "$ASC" >/dev/null 2>"$GNUPGHOME/import.err" || true
|
|
77
|
+
if ! grep -qE '^gpg:[[:space:]]*imported: [1-9]' "$GNUPGHOME/import.err"; then
|
|
78
|
+
echo "::error::gpg import failed for $ASC" >&2
|
|
79
|
+
cat "$GNUPGHOME/import.err" >&2
|
|
80
|
+
exit 2
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# `--with-colons` field 7 of the `pub:` line is the expiry in Unix
|
|
84
|
+
# seconds; 0 means no expiry.
|
|
85
|
+
expires=$(gpg --with-colons --fingerprint 2>/dev/null | awk -F: '/^pub:/{print $7; exit}')
|
|
86
|
+
|
|
87
|
+
if [[ -z "$expires" || "$expires" == "0" ]]; then
|
|
88
|
+
echo "::warning::disclosure key has no expiry — set one per docs/security/KEY_ROTATION.md" >&2
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
now=$(date -u +%s)
|
|
93
|
+
remaining_days=$(((expires - now) / 86400))
|
|
94
|
+
|
|
95
|
+
expires_date=$(date -u -r "$expires" '+%Y-%m-%d' 2>/dev/null || date -u -d "@$expires" '+%Y-%m-%d' 2>/dev/null || echo "(date format unknown)")
|
|
96
|
+
|
|
97
|
+
echo "Disclosure key expires: $expires_date ($remaining_days days from now)"
|
|
98
|
+
echo " warn threshold: $WARN_DAYS days"
|
|
99
|
+
echo " fail threshold: $FAIL_DAYS days"
|
|
100
|
+
|
|
101
|
+
if ((remaining_days < FAIL_DAYS)); then
|
|
102
|
+
echo "::error::Disclosure key expires in $remaining_days days. Rotate now per docs/security/KEY_ROTATION.md." >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if ((remaining_days < WARN_DAYS)); then
|
|
107
|
+
echo "::warning::Disclosure key expires in $remaining_days days. Schedule rotation per docs/security/KEY_ROTATION.md." >&2
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
exit 0
|
|
@@ -22,13 +22,22 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
|
22
22
|
UNLOCK_CMD="chflags nouchg"
|
|
23
23
|
CHECK_CMD="ls -lO"
|
|
24
24
|
else
|
|
25
|
-
# Linux (requires root
|
|
25
|
+
# Linux (chattr +i requires root; fail loudly when sudo is missing
|
|
26
|
+
# rather than printing a confusing error per file in automation)
|
|
26
27
|
if command -v chattr >/dev/null; then
|
|
28
|
+
if ! command -v sudo >/dev/null; then
|
|
29
|
+
echo " 'sudo' not found; chattr requires root. Aborting." >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
if ! sudo -n true 2>/dev/null && [[ ! -t 0 ]]; then
|
|
33
|
+
echo " sudo requires a password but no TTY is attached. Aborting." >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
27
36
|
LOCK_CMD="sudo chattr +i"
|
|
28
37
|
UNLOCK_CMD="sudo chattr -i"
|
|
29
38
|
CHECK_CMD="lsattr"
|
|
30
39
|
else
|
|
31
|
-
echo " 'chattr' not found. Cannot set immutability on Linux without it."
|
|
40
|
+
echo " 'chattr' not found. Cannot set immutability on Linux without it." >&2
|
|
32
41
|
exit 1
|
|
33
42
|
fi
|
|
34
43
|
fi
|
package/scripts/version-sync.sh
CHANGED
|
@@ -142,6 +142,8 @@ sed_in_place() {
|
|
|
142
142
|
fi
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
# LCOV_EXCL_START — rm -rf BACKUP_DIR + cp of real release files;
|
|
146
|
+
# only run during a real release flow, not safe under coverage.
|
|
145
147
|
create_backup() {
|
|
146
148
|
local files=("$@")
|
|
147
149
|
|
|
@@ -166,6 +168,7 @@ create_backup() {
|
|
|
166
168
|
|
|
167
169
|
log_success "Backed up $backup_count files"
|
|
168
170
|
}
|
|
171
|
+
# LCOV_EXCL_STOP
|
|
169
172
|
|
|
170
173
|
update_version_references() {
|
|
171
174
|
local target_version="$1"
|