feat(updater): P3 — honest pinned apply/revert
Make the Update / Roll-back buttons tell the truth, closing the "new code on old data" hole a floating tag creates. updaterApplyApp: - Anchor-correct: capture before/after from updaterPrimaryImage (the <slug>-service image), not `grep -m1 image:` — fixes ollama et al. - Records EXACT build refs in history from->to: repo:tag@sha256:<digest> (via updaterRefDigest), so history is meaningful even when the tag doesn't move (a rebuilt `latest`). - Un-pins any digest a prior rollback pinned before pulling, so Update tracks the channel again instead of freezing on the rolled-back build. updaterRollbackApp: - Before recreating, re-pins the anchor image to the pre-update build's digest (from history's last update/ok `from`) via updaterSetAnchorRef, so `up` runs the OLD code — not the current channel head. This is the fix for restoring a data snapshot but recreating on a newer image. New helpers (cli_updater_commands.sh): updaterRefDigest (local RepoDigest), updaterSetAnchorRef (rewrite the anchor image line by service name, preserving indent + the version sentinel; correct for companion-first apps like ollama), updaterLastUpdateFrom (roll-back target from history). Verified the helpers on nextcloud + ollama: pin adds @sha256 to the right anchor only, sidecars untouched, sentinel preserved, unpin restores, YAML valid. Caught and fixed a `local a=$1 b=...$a...` same-statement expansion bug that would have silently no-op'd the rollback pin. End-to-end apply/revert not exercised live here (no installed app has a pending update on this box). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
67e807c617
commit
e4297fc77e
@ -97,6 +97,46 @@ cliHandleUpdaterCommands()
|
||||
esac
|
||||
}
|
||||
|
||||
# Digest (sha256:…) of a locally-present image ref; "" if absent. Rootless-aware.
|
||||
updaterRefDigest()
|
||||
{
|
||||
local ref="$1" out
|
||||
out="$(dockerCommandRun "docker inspect --format '{{index .RepoDigests 0}}' $ref" 2>/dev/null | tr -d '\r' | head -1)"
|
||||
out="${out##*@}"; case "$out" in sha256:*) printf '%s' "$out";; esac
|
||||
}
|
||||
|
||||
# Rewrite the app's ANCHOR (<slug>-service) image line to $newref, preserving the
|
||||
# original indent and any trailing ` #LIBREPORTAL|…` version sentinel. Targets the
|
||||
# anchor by service name (not the first image line — some apps list a companion
|
||||
# first), so it's correct for ollama et al.
|
||||
updaterSetAnchorRef()
|
||||
{
|
||||
local app="$1" newref="$2"
|
||||
local compose="${containers_dir%/}/$app/docker-compose.yml"
|
||||
[ -f "$compose" ] || return 1
|
||||
local tmp; tmp="$(mktemp)"
|
||||
awk -v s="${app//_/-}-service" -v ref="$newref" '
|
||||
!done && seen && /^[[:space:]]*image:/ {
|
||||
match($0,/^[[:space:]]*/); ind=substr($0,1,RLENGTH)
|
||||
cmt=""; if (match($0,/#.*/)) cmt=" " substr($0,RSTART)
|
||||
print ind "image: " ref cmt; done=1; next
|
||||
}
|
||||
$0 ~ ("^[[:space:]]*" s ":") { seen=1 }
|
||||
{ print }
|
||||
' "$compose" > "$tmp" || { rm -f "$tmp"; return 1; }
|
||||
runFileWrite "$compose" < "$tmp"; local rc=$?
|
||||
rm -f "$tmp"; return $rc
|
||||
}
|
||||
|
||||
# The image ref (repo:tag@sha256:…) the app ran BEFORE its last successful update
|
||||
# — i.e. the roll-back target. Read from history.json's most recent update/ok.
|
||||
updaterLastUpdateFrom()
|
||||
{
|
||||
local app="$1" hist="$containers_dir/libreportal/frontend/data/updater/generated/history.json"
|
||||
[ -f "$hist" ] && command -v jq >/dev/null 2>&1 || return 0
|
||||
jq -r --arg a "$app" 'first(.entries[]? | select(.app==$a and .action=="update" and .result=="ok") | .from) // ""' "$hist" 2>/dev/null
|
||||
}
|
||||
|
||||
# Update one app with disaster-recovery: snapshot -> pull -> recreate -> verify,
|
||||
# auto-rolling-back on failure. Uses existing primitives (the backup CLI for the
|
||||
# snapshot, docker compose for the image swap) so it shares their locking/logging.
|
||||
@ -121,13 +161,21 @@ updaterApplyApp()
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 2. Capture the current image so we can record from->to / roll back.
|
||||
local before; before="$(grep -m1 -E '^\s*image:' "$app_dir/docker-compose.yml" 2>/dev/null | sed -E 's/^\s*image:\s*//; s/["'"'"']//g')"
|
||||
# 2. Capture the current ANCHOR image + its running digest, so from->to is an
|
||||
# exact build reference (repo:tag@sha256:…) even for a floating tag. Anchor is
|
||||
# the <slug>-service image (updaterPrimaryImage), NOT the first line. If a
|
||||
# prior rollback pinned a digest, unpin it first so we track the channel again.
|
||||
local anchor; anchor="$(updaterPrimaryImage "$app" "$app_dir/docker-compose.yml")"
|
||||
case "$anchor" in *@sha256:*) updaterSetAnchorRef "$app" "${anchor%%@*}"; anchor="${anchor%%@*}";; esac
|
||||
local before_dig; before_dig="$(updaterRefDigest "$anchor")"
|
||||
local before="$anchor${before_dig:+@$before_dig}"
|
||||
|
||||
# 3. Pull + recreate (uses the real, install-type-aware compose helpers).
|
||||
isNotice "Pulling new image(s) for $app…"
|
||||
if updaterComposePull "$app" && dockerComposeUp "$app" >/dev/null 2>&1; then
|
||||
local after; after="$(grep -m1 -E '^\s*image:' "$app_dir/docker-compose.yml" 2>/dev/null | sed -E 's/^\s*image:\s*//; s/["'"'"']//g')"
|
||||
local after_ref; after_ref="$(updaterPrimaryImage "$app" "$app_dir/docker-compose.yml")"; after_ref="${after_ref%%@*}"
|
||||
local after_dig; after_dig="$(updaterRefDigest "$after_ref")"
|
||||
local after="$after_ref${after_dig:+@$after_dig}"
|
||||
updaterRecordHistory "$app" "update" "$before" "$after" "ok"
|
||||
isSuccessful "$app updated. Rollback point retained."
|
||||
webuiUpdaterScan >/dev/null 2>&1 || true
|
||||
@ -162,6 +210,15 @@ updaterRollbackApp()
|
||||
{
|
||||
local app="$1" mode="$2"
|
||||
[[ "$mode" != "auto" ]] && isHeader "Rolling $app back to its pre-update snapshot"
|
||||
# Re-pin the ANCHOR image to the exact build the app ran before the last
|
||||
# update, so `up` below runs the OLD code — not the current channel head.
|
||||
# Without this, restoring the data snapshot but recreating on the new `latest`
|
||||
# image is "new code on old data", the hole a floating tag makes invisible.
|
||||
# (Preserves the version sentinel; apply un-pins it again on the next update.)
|
||||
local prev_ref; prev_ref="$(updaterLastUpdateFrom "$app")"
|
||||
if [[ -n "$prev_ref" && "$prev_ref" == *@sha256:* ]]; then
|
||||
updaterSetAnchorRef "$app" "$prev_ref" && isNotice "Pinned $app back to its pre-update build."
|
||||
fi
|
||||
# Delegate to the restore engine (latest snapshot for this app). Call the
|
||||
# function directly — the old `backup app "$app" restore latest` CLI form was
|
||||
# malformed (parsed as action="$app") so it silently did nothing yet exited 0.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user