From a8a0a66853dc3cfc6657bf05ba795282c6fc82f5 Mon Sep 17 00:00:00 2001 From: "Andrey A." <56412611+aantti@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:56:44 +0200 Subject: [PATCH] fix(self-hosted): avoid overwriting update.sh (#48690) --- docker/tests/test-update.sh | 34 ++++++++++++++++++++++++++++++ docker/update.sh | 42 ++++++++++++++++++++++++++++++------- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/docker/tests/test-update.sh b/docker/tests/test-update.sh index 1165390ee74..c1bc0e770c1 100755 --- a/docker/tests/test-update.sh +++ b/docker/tests/test-update.sh @@ -339,6 +339,40 @@ assert_file_missing_pattern ".env" "NEW_KEY" "malformed manife assert_path_absent "backups" "malformed manifest: no backup taken" assert_file_contains ".supabase-version" "ref=self-hosted/v0.9.0" "malformed manifest: stamp not advanced" +echo "" +echo "=== update.sh: never overwrites the running script; stages the target's copy as .dist ===" + +# A dedicated upstream whose docker/ ships an update.sh that DIFFERS from the one +# we run, so the merge must divert it to update.sh.dist rather than rewrite the +# live script mid-run (which would corrupt this process). +SELFSRC="$WORK/selfsrc" +mkdir -p "$SELFSRC/docker" +cd "$SELFSRC" +git init -q +git config user.email t@t.t +git config user.name t +printf 'services:\n db:\n image: x\n' > docker/docker-compose.yml +printf 'KEEP=1\n' > docker/.env.example +cp "$DOCKER_DIR/.gitignore" docker/.gitignore +printf '#!/bin/sh\necho THIS-IS-THE-NEW-UPDATE-SH\n' > docker/update.sh +git add -A && git commit -qm self && git tag self-hosted/v2.0.0 + +SELFDEP="$WORK/selfdep" +mkdir -p "$SELFDEP" +printf 'services:\n db:\n image: x\n' > "$SELFDEP/docker-compose.yml" +printf 'KEEP=1\n' > "$SELFDEP/.env" +cp "$UPDATE_SH" "$SELFDEP/update.sh" # the running script = the real update.sh +printf 'ref=self-hosted/v2.0.0\n' > "$SELFDEP/.supabase-version" +cd "$SELFDEP" +rc=0 +SUPABASE_REPO_URL="$SELFSRC" sh ./update.sh --to self-hosted/v2.0.0 --yes > "$WORK/self.log" 2>&1 || rc=$? +if [ "$rc" = "0" ]; then ok "self-update run exits 0"; else bad "expected exit 0, got $rc"; fi +if cmp -s ./update.sh "$UPDATE_SH"; then ok "running update.sh left byte-identical"; else bad "running update.sh was modified in place"; fi +assert_no_line "./update.sh" '^(<<<<<<<|=======|>>>>>>>)' "no conflict markers written into the running update.sh" +assert_path_exists "$SELFDEP/update.sh.dist" "target's update.sh staged as update.sh.dist" +assert_file_contains "$SELFDEP/update.sh.dist" "THIS-IS-THE-NEW-UPDATE-SH" "update.sh.dist holds the target's version" +assert_file_contains "$WORK/self.log" "update.sh.dist" "summary points the user at update.sh.dist" + # --- summary ----------------------------------------------------------------- echo "" diff --git a/docker/update.sh b/docker/update.sh index b23151d34c2..cf3313b639f 100755 --- a/docker/update.sh +++ b/docker/update.sh @@ -70,6 +70,7 @@ cd "$(dirname "$0")" REPO_URL="${SUPABASE_REPO_URL:-https://github.com/supabase/supabase}" STAMP_FILE=".supabase-version" +SELF_NAME=$(basename "$0") DRY_RUN=0 ASSUME_YES=0 TO_REF="" @@ -177,7 +178,7 @@ fetch_snapshot() { _dest="$2" _work=$(mktemp -d "$TMP_ROOT/fetch.XXXXXX") if _sparse_init "$_work" \ - && git -C "$_work" fetch --depth=1 -q origin "$_ref" 2>/dev/null \ + && git -C "$_work" fetch --depth=1 --filter=blob:none -q origin "$_ref" 2>/dev/null \ && git -C "$_work" checkout -q FETCH_HEAD 2>/dev/null \ && [ -d "$_work/docker" ]; then mkdir -p "$_dest" @@ -311,9 +312,8 @@ $(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$TARGET_DIR/.env.example" | cut -d= -f1) EOF fi echo "" - warn "This is NOT the full update. To see what would actually change (updated files," - warn "conflicts, breaking-change gate), record a base version - see the guidance above -" - warn "then re-run. Nothing was written." + warn "This is NOT the full update. To see what would actually change, record a base version." + warn "See the guidance above and re-run the script. Nothing was written." } # --- breaking-change gate (manifest-driven, before any writes) --------------- @@ -333,9 +333,9 @@ build_gate_report() { fi if [ -z "$BASE_VER" ] || [ -z "$TARGET_VER" ]; then - warn "Base or target is not a self-hosted/vX.Y.Z tag; cannot compute an exact" - warn "update window. Showing all applicable manual-action releases - review" - warn "which ones apply to your deployment." + warn "Base or target is not a self-hosted/vX.Y.Z tag; cannot compute an exact update window." + warn "Showing all applicable manual-action releases." + warn "Review which ones apply to your deployment." fi for k in $(jq -r 'keys[]' "$_manifest" 2>/dev/null); do @@ -469,6 +469,19 @@ merge_one_file() { fi } +# The running script is a vendor file too, but overwriting it in place would +# corrupt this process (the shell reads $0 as it runs). Never write it directly: +# if the target ships a different version, stage it as .dist to review. +stage_self_update() { + _t="$TARGET_DIR/$SELF_NAME" + if [ ! -f "$_t" ] || cmp -s "$SELF_NAME" "$_t"; then + record "unchanged" "$SELF_NAME" + return 0 + fi + [ "$DRY_RUN" = "1" ] || cp -f "$_t" "$SELF_NAME.dist" + record "self-staged" "$SELF_NAME" +} + merge_vendor_files() { _empty="$TMP_ROOT/empty" : > "$_empty" @@ -477,6 +490,10 @@ merge_vendor_files() { while IFS= read -r f; do [ -n "$f" ] || continue is_excluded "$f" && continue + if [ "$f" = "$SELF_NAME" ]; then + stage_self_update + continue + fi merge_one_file "$f" "$_empty" done <