Round four, on round three's diff. 47 -> 34 -> 15 -> 7 raw findings. **The printed restart command could strand the restored data.** Round three replaced `start` with `up -d` because `start` ignores a newly pinned tag — but printed it bare. In the git-clone layout BOTH compose files exist, so the scripts' COMPOSE_FILE fallback never fires and a bare `docker compose up -d` reads `docker-compose.yml`: different volume names, locally built images, no IMAGE_TAG at all. `stop` and `exec` had been fine there (they resolve running containers by project and service), but `up -d` RECREATES — so the operator pastes the hint straight after a successful restore and gets `db` on a new empty volume and a first-run setup wizard, with their data in an orphaned volume. Verified: the two files resolve `siftlode_pgdata` vs `pgdata` and different images. The scripts no longer print a command that recreates from a file they did not resolve. They name the compose file when they resolved one, and otherwise say to start it the way the operator normally does — with the caveats on their own lines rather than in a parenthetical at the end of a very long one. **The reset flag is now decided where the live hash is already parsed.** Round three cleared it in the token effect, which only runs when a NEW verify token arrives — so the back button, or re-pasting an already-consumed verify link, still left the banner promising a recovery whose token was gone. Setting it from the hash on every fragment change is both smaller and closes those. Also: the doc said `start` "still carries the old tag" four lines after "pin the older IMAGE_TAG", where "old" then reads as the tag being rolled back TO — it now reuses the scripts' unambiguous wording; the Windows recipe removes a partially-copied dump before throwing, so a mid-stream `cp` failure cannot leave a truncated file under the final name; and `$WarningPreference` is pinned beside `$ErrorActionPreference` so an inherited SilentlyContinue cannot drop the half-restored warning, with the comment corrected to say what the warning stream actually is. Gate green: vitest 685, pytest 950.
56 lines
3.2 KiB
Bash
56 lines
3.2 KiB
Bash
#!/bin/sh
|
|
# Restores a Siftlode Postgres dump created by backup.sh into the running db container.
|
|
# Usage: ./scripts/restore.sh backups/siftlode-YYYYMMDD-HHMMSS.dump
|
|
#
|
|
# Restoring the database does NOT restore the secrets that decrypt it. If this dump is from an
|
|
# instance with a different TOKEN_ENCRYPTION_KEY than the current .env, the encrypted sysconfig
|
|
# values and refresh tokens will not be readable. See docs/self-hosting.md § Backups.
|
|
set -e
|
|
|
|
# See backup.sh: the documented self-host layout ships only docker-compose.selfhost.yml, so plain
|
|
# `docker compose` has no configuration file to find there.
|
|
if [ -z "${COMPOSE_FILE:-}" ] && [ ! -f docker-compose.yml ] && [ -f docker-compose.selfhost.yml ]; then
|
|
COMPOSE_FILE=docker-compose.selfhost.yml
|
|
export COMPOSE_FILE
|
|
fi
|
|
|
|
FILE="$1"
|
|
if [ -z "$FILE" ]; then echo "Usage: restore.sh <dumpfile>"; exit 1; fi
|
|
if [ ! -f "$FILE" ]; then echo "Dump file not found: $FILE"; exit 1; fi
|
|
USER_NAME="${POSTGRES_USER:-siftlode}"
|
|
DB_NAME="${POSTGRES_DB:-siftlode}"
|
|
|
|
# Stop the app before dropping its tables. `--clean` DROPs each object in its own transaction, and a
|
|
# running api (with its scheduler) holds locks that make those DROPs queue behind it — abort the wait
|
|
# and the database is left half-dropped. It also keeps the app from writing into a schema that is
|
|
# only partly back.
|
|
#
|
|
# It is NOT restarted automatically, on purpose. Every start runs `alembic upgrade head`, so on the
|
|
# documented rollback path (restore a pre-upgrade dump, THEN pin the older IMAGE_TAG) an automatic
|
|
# restart would re-apply the very migrations the restore just undid. On a FAILED restore it would
|
|
# put the app back on a half-restored schema, which is the second thing the stop exists to prevent.
|
|
# The operator starts it when they are ready — with `up -d`, not `start`: `start` relaunches the
|
|
# EXISTING container, so it ignores a newly pinned IMAGE_TAG and comes back on the image being
|
|
# rolled back FROM.
|
|
if ! docker compose stop api worker; then
|
|
echo "Could not stop api/worker — refusing to restore while the app may still be writing." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# pg_restore exits non-zero for ignorable warnings too (--clean --if-exists on a fresh database
|
|
# reports every missing object it tried to drop), so a non-zero code is reported as "check this",
|
|
# not swallowed and not dressed up as success.
|
|
if docker compose exec -T db pg_restore -U "$USER_NAME" -d "$DB_NAME" --clean --if-exists < "$FILE"; then
|
|
echo "Restored $FILE"
|
|
echo "api and worker are stopped. Start them again the way you normally start Siftlode:"
|
|
echo " docker compose ${COMPOSE_FILE:+-f $COMPOSE_FILE }up -d"
|
|
echo "Use the SAME compose file you started with — 'up -d' recreates from whatever file it reads,"
|
|
echo "and a different one points at different volumes. Rolling a release back? Pin the older"
|
|
echo "IMAGE_TAG in .env FIRST: every start re-runs the migrations, and 'start' would relaunch the"
|
|
echo "existing container on the image you are rolling back FROM."
|
|
else
|
|
echo "pg_restore reported errors while restoring $FILE — read the output above before treating this instance as restored." >&2
|
|
echo "api and worker are left STOPPED: the database may be half-restored. Fix the dump and re-run before starting them." >&2
|
|
exit 1
|
|
fi
|