mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-09-21 05:21:43 +00:00
docs: harden disposable restore rehearsal
This commit is contained in:
+39
-21
@@ -196,11 +196,14 @@ do not run `docker compose down -v` without the explicit rehearsal
|
|||||||
`--project-name` shown below.
|
`--project-name` shown below.
|
||||||
|
|
||||||
Run these commands from the production Compose checkout as an operator allowed
|
Run these commands from the production Compose checkout as an operator allowed
|
||||||
to read `/etc/dragons-stash/backup.env`. They use production secrets only to
|
to read `/etc/dragons-stash/backup.env`. They use the configuration required
|
||||||
start a separate application stack; do not expose its published port beyond the
|
for a separate application stack; do not expose its published port beyond the
|
||||||
host. First select a snapshot and set the expected values for a known retained
|
host. The worker and bot are deliberately replaced with inert processes, so
|
||||||
STL that was recorded when the backup was made. `EXPECTED_FILE_PATH` must be
|
this validates their restored images and volumes without executing Telegram
|
||||||
the database value under `/data/uploads`, and `EXPECTED_FILE_SIZE` is bytes.
|
clients or using production Telegram credentials. First select a snapshot and
|
||||||
|
set the expected values for a known retained STL that was recorded when the
|
||||||
|
backup was made. `EXPECTED_FILE_PATH` must be the database value under
|
||||||
|
`/data/uploads`, and `EXPECTED_FILE_SIZE` is bytes.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
@@ -214,6 +217,7 @@ REHEARSAL_ID="$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
|||||||
REHEARSAL_PROJECT="dragonsstash-rehearsal-$REHEARSAL_ID"
|
REHEARSAL_PROJECT="dragonsstash-rehearsal-$REHEARSAL_ID"
|
||||||
REHEARSAL_DIR="$BACKUP_STAGING_PATH/monthly-rehearsal-$REHEARSAL_ID"
|
REHEARSAL_DIR="$BACKUP_STAGING_PATH/monthly-rehearsal-$REHEARSAL_ID"
|
||||||
REHEARSAL_ENV="$REHEARSAL_DIR/compose.env"
|
REHEARSAL_ENV="$REHEARSAL_DIR/compose.env"
|
||||||
|
REHEARSAL_OVERRIDE="$REHEARSAL_DIR/compose.rehearsal.override.yml"
|
||||||
REHEARSAL_APP_PORT=13000 # Choose an unused host-local port.
|
REHEARSAL_APP_PORT=13000 # Choose an unused host-local port.
|
||||||
|
|
||||||
EXPECTED_UPLOAD_ID=RECORDED_UPLOAD_ID
|
EXPECTED_UPLOAD_ID=RECORDED_UPLOAD_ID
|
||||||
@@ -232,6 +236,23 @@ umask 077
|
|||||||
cp .env "$REHEARSAL_ENV"
|
cp .env "$REHEARSAL_ENV"
|
||||||
printf '\nAPP_PORT=%s\nNEXT_PUBLIC_APP_URL=http://localhost:%s\n' \
|
printf '\nAPP_PORT=%s\nNEXT_PUBLIC_APP_URL=http://localhost:%s\n' \
|
||||||
"$REHEARSAL_APP_PORT" "$REHEARSAL_APP_PORT" >> "$REHEARSAL_ENV"
|
"$REHEARSAL_APP_PORT" "$REHEARSAL_APP_PORT" >> "$REHEARSAL_ENV"
|
||||||
|
|
||||||
|
cat > "$REHEARSAL_OVERRIDE" <<'EOF'
|
||||||
|
services:
|
||||||
|
worker:
|
||||||
|
environment:
|
||||||
|
TELEGRAM_API_ID: ""
|
||||||
|
TELEGRAM_API_HASH: ""
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command: ["exec sleep infinity"]
|
||||||
|
bot:
|
||||||
|
environment:
|
||||||
|
BOT_TOKEN: ""
|
||||||
|
TELEGRAM_API_ID: ""
|
||||||
|
TELEGRAM_API_HASH: ""
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command: ["exec sleep infinity"]
|
||||||
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
Confirm that `RESTORE_ROOT` names exactly one `backup-*` directory before
|
Confirm that `RESTORE_ROOT` names exactly one `backup-*` directory before
|
||||||
@@ -239,12 +260,16 @@ continuing. The staging restore has already verified the custom PostgreSQL dump
|
|||||||
and restored `data/uploads`, `data/tdlib-worker`, and `data/tdlib-bot`.
|
and restored `data/uploads`, `data/tdlib-worker`, and `data/tdlib-bot`.
|
||||||
|
|
||||||
Create the isolated project and volumes, start only its database, then import
|
Create the isolated project and volumes, start only its database, then import
|
||||||
the dump. The `create` command makes the project-scoped application volumes
|
the dump. `compose_rehearsal()` always applies the disposable override created
|
||||||
without starting app, worker, or bot.
|
above: worker and bot retain their restored images and volumes but have their
|
||||||
|
Telegram credential variables blanked and run only `sleep infinity`, never
|
||||||
|
Telegram clients. The `create` command makes the project-scoped application
|
||||||
|
volumes without starting app, worker, or bot.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
compose_rehearsal() {
|
compose_rehearsal() {
|
||||||
docker compose --project-name "$REHEARSAL_PROJECT" --env-file "$REHEARSAL_ENV" "$@"
|
docker compose --project-name "$REHEARSAL_PROJECT" --env-file "$REHEARSAL_ENV" \
|
||||||
|
-f docker-compose.yml -f "$REHEARSAL_OVERRIDE" "$@"
|
||||||
}
|
}
|
||||||
rehearsal_volume() {
|
rehearsal_volume() {
|
||||||
docker volume ls \
|
docker volume ls \
|
||||||
@@ -287,9 +312,11 @@ restore_rehearsal_volume tdlib_state "$REHEARSAL_DIR/data/tdlib-worker"
|
|||||||
restore_rehearsal_volume tdlib_bot_state "$REHEARSAL_DIR/data/tdlib-bot"
|
restore_rehearsal_volume tdlib_bot_state "$REHEARSAL_DIR/data/tdlib-bot"
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the disposable app, worker, and bot services. Check the health endpoint,
|
Start the disposable app, worker, and bot containers. The app and database
|
||||||
then retain the `ps` and log output as rehearsal evidence. Do not use the bot
|
perform their normal health and restored-data checks; the worker and bot remain
|
||||||
to send messages during this check.
|
inert `sleep infinity` processes, so this does not execute Telegram clients or
|
||||||
|
send messages. Check the health endpoint, then retain the `ps` and log output
|
||||||
|
as rehearsal evidence.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
compose_rehearsal --profile full up -d app worker bot
|
compose_rehearsal --profile full up -d app worker bot
|
||||||
@@ -331,16 +358,7 @@ metadata_rows="$(compose_rehearsal exec -T db psql --no-psqlrc --tuples-only \
|
|||||||
--dbname "${POSTGRES_DB:-dragonsstash}" \
|
--dbname "${POSTGRES_DB:-dragonsstash}" \
|
||||||
--set="upload_id=$EXPECTED_UPLOAD_ID" --set="upload_status=$EXPECTED_UPLOAD_STATUS" \
|
--set="upload_id=$EXPECTED_UPLOAD_ID" --set="upload_status=$EXPECTED_UPLOAD_STATUS" \
|
||||||
--set="file_name=$EXPECTED_FILE_NAME" --set="file_path=$EXPECTED_FILE_PATH" \
|
--set="file_name=$EXPECTED_FILE_NAME" --set="file_path=$EXPECTED_FILE_PATH" \
|
||||||
--set="file_size=$EXPECTED_FILE_SIZE" --command '
|
--set="file_size=$EXPECTED_FILE_SIZE" --command "SELECT count(*) FROM \"manual_uploads\" u JOIN \"manual_upload_files\" f ON f.\"uploadId\" = u.id WHERE u.id = :'upload_id' AND u.status::text = :'upload_status' AND f.\"fileName\" = :'file_name' AND f.\"filePath\" = :'file_path' AND f.\"fileSize\" = :'file_size'::bigint AND f.\"retainedAt\" IS NOT NULL;")"
|
||||||
SELECT count(*) FROM "manual_uploads" u
|
|
||||||
JOIN "manual_upload_files" f ON f."uploadId" = u.id
|
|
||||||
WHERE u.id = :'upload_id'
|
|
||||||
AND u.status::text = :'upload_status'
|
|
||||||
AND f."fileName" = :'file_name'
|
|
||||||
AND f."filePath" = :'file_path'
|
|
||||||
AND f."fileSize" = :'file_size'::bigint
|
|
||||||
AND f."retainedAt" IS NOT NULL;
|
|
||||||
')"
|
|
||||||
test "$metadata_rows" = 1
|
test "$metadata_rows" = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user