Skip to content

Commit 8167c8a

Browse files
committed
fix(selfhost): snapshot.sh asks where DATABASE_URL points, not only whether postgres is defined
A postgres service that is still defined while env/api.env names another host (NEON.md's step 5 skipped, or any managed Postgres) holds nothing the services write; archiving it was a snapshot of the wrong database, and a rollback would have restored it and started the stack against the untouched real one. Now that case is external, with a note naming the host (never the credentials). DATABASE_URL_FILE, which the host cannot read, falls back to the service. Proven on oa-ci: bundled -> volume (and 0/50 wrong over repeats), URL elsewhere -> external + note, restore of a volume snapshot there -> refused, DATABASE_URL_FILE -> volume, neon overlay -> external.
1 parent 183b866 commit 8167c8a

1 file changed

Lines changed: 35 additions & 5 deletions

File tree

‎infra/selfhost/snapshot.sh‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,21 +124,47 @@ volume_at() {
124124
echo "$name"
125125
}
126126

127-
# True when this compose project runs its own Postgres. Asked of the resolved
128-
# configuration, so an override that moves `postgres` behind a profile — which
129-
# is what `docker-compose.neon.yml` does — counts as not running it.
127+
# True when this compose project defines a `postgres` service. Asked of the
128+
# resolved configuration, so an override that moves `postgres` behind a profile
129+
# — which is what `docker-compose.neon.yml` does — counts as not defining it.
130130
#
131131
# The list is captured before it is searched, never piped into `grep -q`: under
132132
# `pipefail`, grep exiting at its first match can SIGPIPE compose while it is
133133
# still writing, and the pipeline then reads as "no postgres" — which would
134134
# silently leave a bundled database out of the snapshot. For the same reason a
135135
# configuration compose cannot resolve is an error here, not an answer.
136-
postgres_in_stack() {
136+
postgres_service_defined() {
137137
local services
138138
services="$(compose config --services)" || die "docker compose could not resolve this stack's configuration, so it cannot tell whether Postgres is part of it"
139139
grep -qx postgres <<<"$services"
140140
}
141141

142+
# The host the api's DATABASE_URL names, or empty when env/api.env gives it
143+
# through DATABASE_URL_FILE instead — that path is inside the container, so the
144+
# host cannot read it. Host only: the credentials never leave this function.
145+
database_host() {
146+
local url
147+
url="$(grep -E '^DATABASE_URL=' env/api.env 2>/dev/null | tail -1 || true)"
148+
url="${url#DATABASE_URL=}"
149+
[ -n "$url" ] || return 0
150+
url="${url#*://}"
151+
url="${url##*@}"
152+
echo "${url%%[:/?]*}"
153+
}
154+
155+
# True when the database the services actually use is the bundled one: the
156+
# service is defined AND the api's URL points at it. A `postgres` container that
157+
# is still defined while DATABASE_URL names another host — NEON.md's step 5
158+
# skipped, or any other managed Postgres — holds nothing the services write, so
159+
# archiving it would be a snapshot of the wrong database. When the URL cannot be
160+
# read (DATABASE_URL_FILE), the service alone decides, as it did before.
161+
postgres_in_stack() {
162+
postgres_service_defined || return 1
163+
local host
164+
host="$(database_host)"
165+
[ -z "$host" ] || [ "$host" = postgres ]
166+
}
167+
142168
# --- helpers that run inside the helper image -------------------------------
143169
volume_bytes() {
144170
# `printf "%d"` rather than `print`: several awks render a large product in
@@ -219,6 +245,10 @@ do_create() {
219245
else
220246
pg_mode=external
221247
pg_volume=external
248+
if postgres_service_defined; then
249+
echo "snapshot: note — a postgres service is defined, but env/api.env points DATABASE_URL at"
250+
echo " $(database_host). That is the database in use; the local container is not archived."
251+
fi
222252
fi
223253
ch_volume="$(volume_at clickhouse /var/lib/clickhouse)"
224254

@@ -336,7 +366,7 @@ do_restore() {
336366
[ "$pg_mode" = external ] || [ -f "$from/pg-data.tar.gz" ] || die "$from/pg-data.tar.gz is missing"
337367
[ -f "$from/ch-data.tar.gz" ] || die "$from/ch-data.tar.gz is missing"
338368
if [ "$pg_mode" = volume ] && ! postgres_in_stack; then
339-
die "$from holds a Postgres volume, but this stack runs no postgres service — restore it into a stack that does, or restore the database by hand"
369+
die "$from holds a Postgres volume, but this stack does not use a bundled postgres (no such service, or DATABASE_URL names another host) — restore it into a stack that does, or restore the database by hand"
340370
fi
341371

342372
local pg_volume="" ch_volume

0 commit comments

Comments
 (0)