feat: version-skew nudge — stamp store writer-version, warn when behind (EGB-713)
This commit is contained in:
parent
367b70cba1
commit
5461418c5d
2 changed files with 187 additions and 0 deletions
81
secrets
81
secrets
|
|
@ -53,6 +53,7 @@ check_cmd() {
|
|||
|
||||
check_initialized() {
|
||||
if [ -d "$SECRETS_DIR/.git" ]; then
|
||||
_check_store_version_skew
|
||||
return
|
||||
fi
|
||||
if [ "$STORE_SOURCE" != "default" ]; then
|
||||
|
|
@ -539,6 +540,72 @@ _store_format() {
|
|||
echo 1
|
||||
}
|
||||
|
||||
# ─── Client/store version skew (EGB-713) ──────────────────────────────
|
||||
#
|
||||
# The running client's own version, read from the VERSION file shipped beside
|
||||
# the script. Empty/"0.0.0.0" if absent — treated as "unknown/oldest" so a
|
||||
# missing VERSION never triggers a spurious nudge.
|
||||
_client_version() {
|
||||
local v=""
|
||||
[ -f "$SCRIPT_DIR/VERSION" ] && v=$(head -1 "$SCRIPT_DIR/VERSION" 2>/dev/null | tr -d '\r\n[:space:]')
|
||||
printf '%s' "${v:-0.0.0.0}"
|
||||
}
|
||||
|
||||
# Numeric four-field (MAJOR.MINOR.PATCH.MICRO) compare. Returns 0 iff $1 > $2.
|
||||
# Per-field numeric (so 0.10.0.0 > 0.7.0.0); missing/garbage fields → 0.
|
||||
_version_gt() {
|
||||
local a="$1" b="$2" i ai bi; local -a af bf
|
||||
IFS=. read -r -a af <<< "$a"; IFS=. read -r -a bf <<< "$b"
|
||||
for i in 0 1 2 3; do
|
||||
ai=${af[$i]:-0}; ai=${ai//[!0-9]/}; [ -n "$ai" ] || ai=0
|
||||
bi=${bf[$i]:-0}; bi=${bi//[!0-9]/}; [ -n "$bi" ] || bi=0
|
||||
if [ "$((10#$ai))" -gt "$((10#$bi))" ]; then return 0; fi
|
||||
if [ "$((10#$ai))" -lt "$((10#$bi))" ]; then return 1; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
WRITER_VERSION_FILE_NAME=".secrets-writer-version"
|
||||
# Highest client version recorded as having written to the store (empty if the
|
||||
# store predates this feature — "minus the initial builds", silent by design).
|
||||
_store_writer_version() {
|
||||
local f="$SECRETS_DIR/$WRITER_VERSION_FILE_NAME"
|
||||
if [ -f "$f" ]; then
|
||||
head -1 "$f" 2>/dev/null | tr -d '\r\n[:space:]'
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Raise the store's recorded writer-version to the client's version (monotonic;
|
||||
# never lowers it). Called right before each store-committing `git add -A` so
|
||||
# the stamp rides the same commit. Read paths (pull) never call this.
|
||||
_stamp_writer_version() {
|
||||
local cur cli
|
||||
cur=$(_store_writer_version)
|
||||
cli=$(_client_version)
|
||||
if [ -z "$cur" ] || _version_gt "$cli" "$cur"; then
|
||||
printf '%s\n' "$cli" > "$SECRETS_DIR/$WRITER_VERSION_FILE_NAME"
|
||||
fi
|
||||
}
|
||||
|
||||
# Warn ONCE per invocation if the store was last written by a newer client than
|
||||
# us. Non-fatal (read/write paths keep their exit codes). Silent when the store
|
||||
# carries no writer-version (legacy) or is same/older than us.
|
||||
_VERSION_SKEW_WARNED=0
|
||||
_check_store_version_skew() {
|
||||
[ "$_VERSION_SKEW_WARNED" = 1 ] && return 0
|
||||
local sv cv
|
||||
sv=$(_store_writer_version)
|
||||
[ -n "$sv" ] || return 0
|
||||
cv=$(_client_version)
|
||||
if _version_gt "$sv" "$cv"; then
|
||||
_VERSION_SKEW_WARNED=1
|
||||
echo "NOTE: this store was last written by secrets v$sv; you're on v$cv." >&2
|
||||
echo " Update your secrets tool: git -C \"$SCRIPT_DIR\" pull" >&2
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Resolve the on-disk path of an external blob for READING. Tries the v2 suffix
|
||||
# (.properties.age) first, then falls back to the v1 (.gradle-properties.age) for
|
||||
# `properties` externals, so an upgraded client finds the blob whichever format
|
||||
|
|
@ -1262,6 +1329,7 @@ commit_and_push_secrets() {
|
|||
# store missing the key.txt line would stage and push the private key.
|
||||
ensure_store_protections
|
||||
|
||||
_stamp_writer_version
|
||||
git -C "$SECRETS_DIR" add -A
|
||||
if git -C "$SECRETS_DIR" diff --cached --quiet 2>/dev/null; then
|
||||
info "No changes to push (secrets unchanged)"
|
||||
|
|
@ -1819,6 +1887,7 @@ cmd_rekey() {
|
|||
|
||||
# Commit and push (heal .gitignore first so add -A can't stage key.txt)
|
||||
ensure_store_protections
|
||||
_stamp_writer_version
|
||||
git -C "$SECRETS_DIR" add -A
|
||||
git -C "$SECRETS_DIR" commit -m "rekey all secrets" >/dev/null
|
||||
if git -C "$SECRETS_DIR" remote get-url origin >/dev/null 2>&1; then
|
||||
|
|
@ -1949,6 +2018,15 @@ cmd_which() {
|
|||
# EGB-700 (folded into EGB-703): surface the store format so users can tell
|
||||
# v1 from v2 during the migration window. v1 = legacy store, no format marker.
|
||||
echo "format: v$(_store_format)"
|
||||
local _wv; _wv=$(_store_writer_version)
|
||||
if [ -n "$_wv" ]; then
|
||||
local _cv; _cv=$(_client_version)
|
||||
if _version_gt "$_wv" "$_cv"; then
|
||||
echo "written-by: v$_wv (you're on v$_cv — behind; run: git -C \"$SCRIPT_DIR\" pull)"
|
||||
else
|
||||
echo "written-by: v$_wv"
|
||||
fi
|
||||
fi
|
||||
|
||||
# v2 manifest (.secrets.json): validate and summarize. Validation here
|
||||
# is deliberately fatal (symlink / malformed / unsupported version) so
|
||||
|
|
@ -2212,6 +2290,7 @@ _migrate_project() {
|
|||
return 0
|
||||
fi
|
||||
ensure_store_protections
|
||||
_stamp_writer_version
|
||||
git -C "$SECRETS_DIR" add -A
|
||||
git -C "$SECRETS_DIR" commit -m "migrate: copy-forward v2 twins for $project" >/dev/null 2>&1 || true
|
||||
# Push the twins so a --finalize on another machine sees them (finalize
|
||||
|
|
@ -2302,6 +2381,7 @@ $untwinned cd into each project and run 'secrets migrate', then re-run 'secrets
|
|||
# No v1 blobs at all — just stamp the marker (dotenv/file-only store).
|
||||
printf '2\n' > "$SECRETS_DIR/$STORE_FORMAT_FILE_NAME"
|
||||
ensure_store_protections
|
||||
_stamp_writer_version
|
||||
git -C "$SECRETS_DIR" add -A
|
||||
git -C "$SECRETS_DIR" commit -m "migrate: finalize store format v2" >/dev/null 2>&1 || true
|
||||
git -C "$SECRETS_DIR" remote get-url origin >/dev/null 2>&1 && git -C "$SECRETS_DIR" push >/dev/null 2>&1 || true
|
||||
|
|
@ -2337,6 +2417,7 @@ $untwinned cd into each project and run 'secrets migrate', then re-run 'secrets
|
|||
done < <(find "$SECRETS_DIR" -type f -name '*.gradle-properties.age')
|
||||
|
||||
ensure_store_protections
|
||||
_stamp_writer_version
|
||||
git -C "$SECRETS_DIR" add -A
|
||||
git -C "$SECRETS_DIR" commit -m "migrate: finalize store format v2 (drop $v1count v1 blob(s))" >/dev/null 2>&1 || true
|
||||
git -C "$SECRETS_DIR" remote get-url origin >/dev/null 2>&1 && git -C "$SECRETS_DIR" push >/dev/null 2>&1 || true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue