feat: secrets migrate --status surveys per-project v2 readiness (EGB-710)

This commit is contained in:
Brian Majewski 2026-06-08 07:34:55 -07:00
parent e75627deff
commit 469b08203b
2 changed files with 81 additions and 4 deletions

51
secrets
View file

@ -2186,6 +2186,46 @@ _migrate_project() {
# Store-wide finalize: the only destructive step. Refuses unless verify --all
# is green and every v1 properties blob has a v2 twin. Cuts a recovery tag,
# stamps the marker, then drops v1 blobs.
# Read-only survey: walk every project dir in the store and report each one's
# v2-readiness from the blobs on disk (no manifest, no decryption). Exits
# non-zero when any v1 properties blob lacks a v2 twin (i.e. the store is not
# yet finalize-ready) so it can gate scripting, mirroring `verify`'s posture.
_migrate_status() {
if [ "$(_store_format)" = "2" ]; then
info "Store format: v2 (finalized) — nothing to migrate."
return 0
fi
echo "Store format: v1 (not finalized). Per-project migration status:"
local any_untwinned=0 dir project v1 untwinned f new
for dir in "$SECRETS_DIR"/*/; do
[ -d "$dir" ] || continue
project=$(basename "$dir")
case "$project" in .*) continue ;; esac
v1=0; untwinned=0
while IFS= read -r f; do
[ -f "$f" ] || continue
v1=$((v1 + 1))
new="${f%.gradle-properties.age}.properties.age"
[ -f "$new" ] || untwinned=$((untwinned + 1))
done < <(find "$dir" -type f -name '*.gradle-properties.age' 2>/dev/null)
if [ "$v1" -eq 0 ]; then
echo " $project: v2-ready (no v1 properties blobs)"
elif [ "$untwinned" -eq 0 ]; then
echo " $project: migrated ($v1 v1 blob(s), all twinned)"
else
echo " $project: NEEDS MIGRATE ($untwinned of $v1 v1 blob(s) un-twinned) — cd into the project and run 'secrets migrate'"
any_untwinned=1
fi
done
echo
if [ "$any_untwinned" -eq 1 ]; then
echo "Not finalize-ready: migrate the projects marked NEEDS MIGRATE, then run 'secrets migrate --finalize'."
return 1
fi
echo "Finalize-ready: every v1 properties blob has a v2 twin. Run 'secrets migrate --finalize' once every machine is upgraded."
return 0
}
_migrate_finalize() {
local force="$1"
check_key
@ -2260,17 +2300,20 @@ $untwinned cd into each project and run 'secrets migrate', then re-run 'secrets
cmd_migrate() {
resolve_store
check_initialized
local dry_run=false finalize=false force=false
local dry_run=false finalize=false force=false status=false
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) dry_run=true; shift ;;
--finalize) finalize=true; shift ;;
--status) status=true; shift ;;
--yes) force=true; shift ;;
-*) die "Unknown migrate flag: $1. Usage: secrets migrate [--dry-run | --finalize] [--yes]" ;;
*) die "migrate takes no project argument. Run it from inside a project (copy-forward) or use --finalize (store-wide)." ;;
-*) die "Unknown migrate flag: $1. Usage: secrets migrate [--dry-run | --status | --finalize] [--yes]" ;;
*) die "migrate takes no project argument. Run it from inside a project (copy-forward), or use --status / --finalize (store-wide)." ;;
esac
done
if [ "$finalize" = true ]; then
if [ "$status" = true ]; then
_migrate_status
elif [ "$finalize" = true ]; then
_migrate_finalize "$force"
else
_migrate_project "$dry_run"