Skip to content

fix(scripts): record deployed version at provisioning and pre-update snapshot (NEH-216) - #289

Merged
juancobo merged 3 commits into
devfrom
scripts/neh-216-pre-deploy-record
Aug 4, 2026
Merged

fix(scripts): record deployed version at provisioning and pre-update snapshot (NEH-216)#289
juancobo merged 3 commits into
devfrom
scripts/neh-216-pre-deploy-record

Conversation

@juancobo

@juancobo juancobo commented Aug 3, 2026

Copy link
Copy Markdown
Member

Closes the first-update rollback gap from NEH-216.

The gap

update.sh derives its rollback target from last-deployed-SHA, written only on a previous successful update. A freshly provisioned unit has no such record, so its first update — Rionegro's 2026-08-03 two-month jump was exactly this — runs with no code-rollback target, and rollback-update.sh can restore only the database. The SHAs that made Rionegro recoverable were captured by hand before starting; nothing on the appliance recorded them.

Changes

  • setup.sh writes last-deployed-SHA (repo HEAD) at the end of provisioning, creating /var/lib/dtk/backups first. git runs as the repo owner via run_as_user — root-run git hits dubious-ownership on the user-cloned repo and would silently poison the record. A read failure warns instead of writing garbage.
  • update.sh writes an informational pre-update-state-<timestamp> file as part of step 1, before any build or dependency work: superproject SHA, previously-deployed SHA, git submodule status, and pixi --version. Retention matches the dump policy (newest 5).

What deliberately did NOT change

pre-update-SHA stays where it is, written next to the database dump in step 5. Moving it earlier was considered and rejected: an abort during the build would leave a fresh SHA beside a stale dump, and a later rollback-update.sh would drop the database into a dump up to five updates old while reporting success. The dump and the rollback SHA must always be written as a pair; the new state file is manual-recovery information only and nothing automated reads it.

Validation

bash -n clean on both scripts. The full first-update path (provision → record → update → rollback with a real pre-update-SHA) goes on the bench checklist for Friday's bench test — the 2026-07-23 bench only ever exercised the no-record guard, never the actual code-rollback path.

Linear: NEH-216 (GitHub mirror #285).

…pdate snapshot (NEH-216)

setup.sh now writes last-deployed-SHA when provisioning finishes, so a
unit's first update — the largest jump it will ever make — has a real
code-rollback target and a precise offline-safety answer instead of the
conservative warning. git runs as the repo owner (root-run git hits
dubious-ownership on a user-cloned repo and would poison the record).

update.sh records an informational pre-update-state snapshot (superproject
SHA, submodule SHAs, pixi version) before any build or dependency work,
for manual recovery when an update aborts early. rollback-update.sh never
reads it: the dump and pre-update-SHA are still written together in step 5
so they always pair — writing the SHA early instead could pair a fresh SHA
with a stale dump and restore data-destroying state.
Copilot AI review requested due to automatic review settings August 3, 2026 20:15
@linear-code

linear-code Bot commented Aug 3, 2026

Copy link
Copy Markdown

NEH-216

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds deployment-state recording to improve first-update recovery.

Changes:

  • Records the provisioned repository SHA during setup.
  • Captures pre-update repository, submodule, and Pixi state.
  • Retains the five newest state snapshots.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
scripts/setup.sh Records the initially provisioned SHA.
scripts/update.sh Creates and prunes pre-update state snapshots.

Comment thread scripts/update.sh
if [ -f "$LAST_DEPLOYED_SHA_FILE" ]; then
echo "previously-deployed: $(cat "$LAST_DEPLOYED_SHA_FILE")"
else
echo "previously-deployed: (no record)"
Comment thread scripts/update.sh Outdated
Comment on lines +245 to +247
# Informational only: rollback-update.sh never reads it. Its inputs — the
# dump and pre-update-SHA — are still written together in step 5 so they
# always pair; recording pre-update-SHA this early instead would let an
Comment thread scripts/update.sh Outdated
Comment on lines +250 to +262
{
echo "recorded-at: $(date +%Y-%m-%dT%H:%M:%S%z)"
echo "installing-superproject: $CURRENT_SHA"
if [ -f "$LAST_DEPLOYED_SHA_FILE" ]; then
echo "previously-deployed: $(cat "$LAST_DEPLOYED_SHA_FILE")"
else
echo "previously-deployed: (no record)"
fi
echo "submodules:"
run_as_user git -C "$PROJECT_ROOT" submodule status 2>/dev/null || echo " (unavailable)"
echo "pixi: $(run_as_user "$PIXI_BIN" --version 2>/dev/null || echo '(unavailable)')"
} > "$STATE_FILE" || true
echo " Pre-update state recorded: $STATE_FILE"

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

scripts/update.sh:262

  • A failed redirect or write is discarded by || true, after which the script unconditionally reports that the snapshot was recorded. On a full or read-only backup filesystem, operators would be told manual recovery data exists when the file is absent or partial. Preserve the informational/non-fatal behavior, but report the failure and remove any partial file.
} > "$STATE_FILE" || true
echo "  Pre-update state recorded: $STATE_FILE"

Comment thread scripts/setup.sh
# user-cloned repo and would silently poison the record.
PROVISIONED_SHA="$(run_as_user git -C "$PROJECT_ROOT" rev-parse HEAD 2>/dev/null || echo "")"
if [ -n "$PROVISIONED_SHA" ]; then
echo "$PROVISIONED_SHA" > /var/lib/dtk/backups/last-deployed-SHA
Comment thread scripts/update.sh Outdated
@@ -239,6 +239,32 @@ trap on_exit EXIT
mkdir -p "$BACKUP_DIR"
CURRENT_SHA="$(git -C "$PROJECT_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown")"
…rrespondence

The pre-update snapshot now reports a write failure instead of always
claiming success (informational file, so the update continues either way).

The "always pair" comment overstated the dump↔SHA invariant: pre-update-SHA
is a single file while five dumps are retained. The comment now states the
correspondence holds only for the newest dump, and rollback-update.sh warns
before the confirmation prompt when an operator selects an older dump.
@juancobo

juancobo commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

On the legacy-appliance point: the only deployed unit (Rionegro) gained last-deployed-SHA during its 2026-08-03 update, and the bench unit gets re-provisioned with the new setup.sh — there is no fleet of unrecorded appliances to migrate. If a legacy unit does surface, seeding the record is one line: echo <deployed-sha> > /var/lib/dtk/backups/last-deployed-SHA.

Copilot AI review requested due to automatic review settings August 3, 2026 20:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/update.sh:254

  • The snapshot can still record unknown on the supported sudo ./scripts/update.sh path: CURRENT_SHA is resolved by root at line 240, while this PR itself notes that root-run Git rejects the user-owned clone as dubious ownership. Resolve and verify CURRENT_SHA through run_as_user before it is used here; this also prevents the later deployment record from being poisoned with unknown.
    echo "installing-superproject: $CURRENT_SHA"

scripts/setup.sh:272

  • Checking only for non-empty output does not reliably detect a Git failure. Without --verify, git rev-parse HEAD can echo the unresolved token HEAD and still exit nonzero (for example with an unborn or invalid HEAD); the || echo "" preserves that output, so this branch writes garbage despite the intended warning. Branch on the command status and require a verified commit object.
PROVISIONED_SHA="$(run_as_user git -C "$PROJECT_ROOT" rev-parse HEAD 2>/dev/null || echo "")"
if [ -n "$PROVISIONED_SHA" ]; then

…rd "unknown"

CURRENT_SHA now resolves via run_as_user — root-run git can fail
dubious-ownership on the user-cloned repo, and "unknown" would poison the
snapshot and, on success, last-deployed-SHA. A successful update also
refuses to overwrite a valid record with "unknown".

rollback-update.sh validates the rollback target as the repo owner too,
matching the checkout below it: a root-side dubious-ownership failure
would otherwise falsely report the commit unavailable and skip the code
rollback entirely.
Copilot AI review requested due to automatic review settings August 4, 2026 00:59
@juancobo
juancobo merged commit d946d6a into dev Aug 4, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/update.sh:243

  • The ownership fix is incomplete: the offline-safety check later runs git cat-file, diff, and rev-parse directly as root (lines 297–317). On the user-owned checkout described here, Git rejects those probes as dubious ownership, so the newly provisioned SHA is treated as unreachable and every first update takes the “cannot confirm offline-safe” warning path. Route all of those Git probes through run_as_user as well.
# git runs as the repo owner: root-run git can fail dubious-ownership on the
# user-cloned repo, and "unknown" here would poison both the snapshot and
# (on success) last-deployed-SHA.
CURRENT_SHA="$(run_as_user git -C "$PROJECT_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown")"

scripts/rollback-update.sh:129

  • This string comparison can label the newest dump as “OLDER” when the operator names the same file through an equivalent path (for example, a symlink or a path containing ..). Compare file identity rather than pathname text; also describe a non-matching external dump as not matching the newest managed dump, since its age is unknown.
NEWEST_DUMP="$(ls -1t "$BACKUP_DIR"/pre-update-*.sql.gz 2>/dev/null | head -n 1 || true)"
if [ -n "$TARGET_SHA" ] && [ -n "$DUMP_ARG" ] && [ "$DUMP_FILE" != "$NEWEST_DUMP" ]; then
    echo "⚠  The recorded code-rollback commit ($TARGET_SHA) was written by the"
    echo "   most recent update, but you selected an OLDER dump. The restored"
    echo "   database may not correspond to that code version."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants