diff --git a/docs/superpowers/plans/2026-07-21-database-and-file-backups.md b/docs/superpowers/plans/2026-07-21-database-and-file-backups.md index 655f3be..b15262a 100644 --- a/docs/superpowers/plans/2026-07-21-database-and-file-backups.md +++ b/docs/superpowers/plans/2026-07-21-database-and-file-backups.md @@ -2,22 +2,25 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Add a nightly, encrypted, deduplicated disaster-recovery backup for PostgreSQL, uploaded STL files, and Telegram session volumes, stored on a Synology NAS. +**Goal:** Add a nightly, encrypted disaster-recovery backup for PostgreSQL and Telegram session volumes, stored on a Synology NAS. -**Architecture:** A Linux-host systemd timer invokes a host orchestration script. The script verifies the mounted Synology NFS share, stops the app/worker/bot services for consistency, and runs a one-shot Docker Compose backup service. The backup service creates a PostgreSQL custom-format dump and stores it together with the three persistent file volumes in a Restic repository on the NAS. A guarded restore command reconstructs the database and volumes, and documentation describes setup and testing. +**Architecture:** A Linux-host systemd timer invokes a host orchestration script. The script verifies the mounted Synology NFS share, stops the app/worker/bot services for consistency, and runs a one-shot Docker Compose backup service. The backup service creates a PostgreSQL custom-format dump and stores it with the two TDLib session volumes in a Restic repository on the NAS. A guarded restore command reconstructs the database and sessions, and documentation describes setup and testing. STL binaries remain in Telegram; restored database metadata and mappings continue to identify the Telegram content used for lookup and delivery. **Tech Stack:** Docker Compose, PostgreSQL 16 `pg_dump`/`pg_restore`, Restic repository encryption and retention, Synology NFS, Linux systemd service/timer, Bash. ## Global Constraints - PostgreSQL data must be backed up as a logical custom-format dump; the raw `postgres_data` volume is not the primary backup. -- The `manual_uploads`, `tdlib_state`, and `tdlib_bot_state` volumes are included in every successful snapshot. -- The `tmp_zips` scratch volume is excluded. -- The Docker host must stop `app`, `worker`, and `bot` while volumes are captured; PostgreSQL remains running for `pg_dump`. +- The `tdlib_state` and `tdlib_bot_state` volumes are included in every successful snapshot. +- The `manual_uploads` and `tmp_zips` volumes are excluded. +- Completed STL binaries are not retained locally for backup; existing worker cleanup behavior remains unchanged. Telegram remains the binary store, while PostgreSQL retains the metadata and mappings needed to locate and send the files after restore. +- The Docker host must stop `app`, `worker`, and `bot` while session volumes are captured; PostgreSQL remains running for `pg_dump`. - The backup repository is encrypted and stored on a Synology NFS share restricted to the Docker host. - Retention is 30 daily snapshots; pruning is allowed only after a verified successful backup. - A failed run must restart services and preserve the last known-good snapshot. +- A guarded restore must require explicit confirmation before replacing live database or session-volume data. - No in-app backup UI is part of this implementation. +- Future Telegram channel-forwarding behavior and archive/STL-content integrity validation are explicitly out of scope. - The repository has no automated test framework; verification uses Bash syntax checks, Docker Compose validation, logs, Restic checks, and a disposable restore rehearsal. --- @@ -33,7 +36,7 @@ Create or modify only these focused units: - Create `deploy/systemd/dragons-stash-backup.service`: systemd unit invoking the host backup script. - Create `deploy/systemd/dragons-stash-backup.timer`: nightly schedule. - Create `scripts/backup/README.md`: Synology setup, host mount, secrets, first backup, restore, and operational troubleshooting. -- Modify `docker-compose.yml`: add the profile-gated one-shot `backup` service and its read-only volume mounts. +- Modify `docker-compose.yml`: add the profile-gated one-shot `backup` service and its read-only session-volume mounts. - Modify `.env.example`: document backup mount, staging, repository, and secret-file configuration without committing secrets. - Modify `README.md`: add the production backup setup and restore entry points, linking to the detailed backup guide. @@ -47,8 +50,8 @@ Create or modify only these focused units: - Create: `backup/Dockerfile` **Interfaces:** -- Consumes: existing `db`, `manual_uploads`, `tdlib_state`, and `tdlib_bot_state` Compose resources. -- Produces: a profile-gated Compose service named `backup` that mounts the three file volumes read-only, connects to the `backend` network, and exposes `/backup` and `/staging` to the container entrypoint. +- Consumes: existing `db`, `tdlib_state`, and `tdlib_bot_state` Compose resources. +- Produces: a profile-gated Compose service named `backup` that mounts the two session volumes read-only, connects to the `backend` network, and exposes `/backup` and `/staging` to the container entrypoint. - [ ] **Step 1: Add explicit backup environment variables to `.env.example`** @@ -82,7 +85,6 @@ Add a service with these properties: BACKUP_APP_VERSION: ${BACKUP_APP_VERSION:-unknown} user: "0:0" volumes: - - manual_uploads:/data/uploads:ro - tdlib_state:/data/tdlib-worker:ro - tdlib_bot_state:/data/tdlib-bot:ro - ${BACKUP_MOUNT_PATH:?Set BACKUP_MOUNT_PATH to the mounted Synology share}:/backup:rw @@ -95,7 +97,7 @@ Add a service with these properties: - backend ``` -Ensure the new service does not have `restart: always` and is not started by the normal production `docker compose up -d` command unless the `backup` profile is explicitly requested. +Do not mount `manual_uploads` or `tmp_zips`. Ensure the new service does not have `restart: always` and is not started by the normal production `docker compose up -d` command unless the `backup` profile is explicitly requested. - [ ] **Step 3: Create the backup image definition** @@ -135,7 +137,7 @@ git commit -m "feat: add backup compose service" - Create: `scripts/backup/container-entrypoint.sh` **Interfaces:** -- Consumes: `DATABASE_URL`, `RESTIC_REPOSITORY`, `RESTIC_PASSWORD_FILE`, `BACKUP_RETENTION_DAYS`, `/data/uploads`, `/data/tdlib-worker`, `/data/tdlib-bot`, `/backup`, and `/staging`. +- Consumes: `DATABASE_URL`, `RESTIC_REPOSITORY`, `RESTIC_PASSWORD_FILE`, `BACKUP_RETENTION_DAYS`, `/data/tdlib-worker`, `/data/tdlib-bot`, `/backup`, and `/staging`. - Produces: exit `0` only after a verified Restic snapshot and successful retention pruning; non-zero on any failed dump, snapshot, verification, or prune step. - [ ] **Step 1: Define strict shell behavior and required inputs** @@ -165,11 +167,11 @@ After the command succeeds, require the dump to be a non-empty regular file. Gen - [ ] **Step 4: Create the manifest** -Write a JSON manifest containing the UTC backup timestamp, repository path, retention value, dump filename, dump checksum, and the three volume paths captured. Obtain the application image/version from an explicit `BACKUP_APP_VERSION` environment value when supplied; otherwise record `unknown` rather than guessing from mutable container state. +Write a JSON manifest containing the UTC backup timestamp, repository path, retention value, dump filename, dump checksum, and the two TDLib volume paths captured. Obtain the application image/version from an explicit `BACKUP_APP_VERSION` environment value when supplied; otherwise record `unknown` rather than guessing from mutable container state. - [ ] **Step 5: Create one Restic snapshot** -Run one `restic backup` command against the staged database dump, manifest, and the three mounted persistent volumes. Use stable source labels so the snapshot can be recognized during restore. Do not include `/data/uploads` more than once and do not pass `/data/tdlib` or `/tmp/zips` paths that are not mounted. +Run one `restic backup` command against the staged database dump, manifest, and the two mounted persistent session volumes. Use stable source labels so the snapshot can be recognized during restore. Do not mount or include `manual_uploads` or `tmp_zips`. - [ ] **Step 6: Verify and apply retention** @@ -200,13 +202,13 @@ docker build -f backup/Dockerfile -t dragons-stash-backup:smoke . bash -n scripts/backup/container-entrypoint.sh ``` -Expected: image build succeeds and Bash reports no syntax errors. The full snapshot test waits until the host wrapper and a real PostgreSQL/volume environment exist. +Expected: image build succeeds and Bash reports no syntax errors. The full snapshot test waits until the host wrapper and a real PostgreSQL/session-volume environment exist. - [ ] **Step 8: Commit the backup container** ```bash git add backup/Dockerfile scripts/backup/container-entrypoint.sh -git commit -m "feat: implement encrypted database and volume snapshots" +git commit -m "feat: implement encrypted database and session snapshots" ``` ### Task 3: Add the host orchestration script and nightly systemd timer @@ -264,26 +266,16 @@ git add scripts/backup/run-backup.sh deploy/systemd/dragons-stash-backup.service git commit -m "feat: schedule nightly off-host backups" ``` -### Task 4: Make completed STL uploads durable and implement guarded restore tooling +### Task 4: Implement guarded restore tooling **Files:** - Create: `scripts/backup/restore.sh` -- Create: `prisma/migrations/20260722000000_mark_retained_manual_files/migration.sql` -- Modify: `prisma/schema.prisma` -- Modify: `src/app/api/uploads/route.ts` -- Modify: `worker/src/manual-upload.ts` **Interfaces:** - Consumes: a Restic snapshot ID, the same repository/password configuration, the backup Compose service, and the live Compose project. -- Produces: durable completed manual-upload files in the existing `manual_uploads` volume; restored PostgreSQL data and persistent volumes only after explicit confirmation for live replacement; a non-destructive staging restore by default. +- Produces: restored PostgreSQL data and Telegram session volumes only after explicit confirmation for live replacement; a non-destructive staging restore by default. -- [ ] **Step 1: Make new manual-upload files durable** - -Add nullable `retainedAt DateTime?` to `ManualUploadFile`. Create an additive migration that leaves existing rows `NULL`. Set `retainedAt: new Date()` when `src/app/api/uploads/route.ts` creates each new file record. Remove the worker's final recursive deletion of `/data/uploads/` while retaining split-file cleanup. This makes the existing `manual_uploads` volume the durable STL archive for new uploads. - -Document and report that files already deleted by older worker runs cannot be reconstructed by this feature. The restore verifier must treat `retainedAt IS NULL` rows as legacy warnings and must require every `retainedAt IS NOT NULL` path to exist. - -- [ ] **Step 2: Define restore modes and destructive guard** +- [ ] **Step 1: Define restore modes and destructive guard** Support these commands: @@ -296,11 +288,11 @@ Support these commands: Reject `restore-live` unless the exact confirmation flag is present. `list`, `verify`, and `restore-to-staging` must not stop services or modify live volumes. -- [ ] **Step 3: Implement snapshot verification and staging restore** +- [ ] **Step 2: Implement snapshot verification and staging restore** -Use `restic snapshots`, `restic check`, and `restic restore SNAPSHOT_ID --target STAGING_DIR`. Verify that the restored staging tree contains a non-empty custom-format dump, a manifest, `manual_uploads`, `tdlib-worker`, and `tdlib-bot` before reporting success. +Use `restic snapshots`, `restic check`, and `restic restore SNAPSHOT_ID --target STAGING_DIR`. Verify that the restored staging tree contains a non-empty custom-format dump, a manifest, `tdlib-worker`, and `tdlib-bot` before reporting success. Do not add file-path checks, binary checksums, archive/STL-content validation, or channel-forwarding behavior. -- [ ] **Step 4: Implement live restore sequencing** +- [ ] **Step 3: Implement live restore sequencing** For `restore-live`: @@ -308,17 +300,13 @@ For `restore-live`: 2. Stop `app`, `worker`, and `bot`. 3. Create a safety PostgreSQL dump of the current database into local staging. 4. Restore the selected snapshot to a separate staging directory. -5. Replace the three Docker volumes only after the restored tree passes validation, including retained STL files in `manual_uploads`. +5. Replace the two Docker session volumes only after the restored tree passes validation. 6. Recreate the configured database from the restored custom-format dump using `pg_restore --no-owner`. -7. Start services and run the health endpoint plus file-reference verification. +7. Start services and run the health endpoint plus worker/bot startup and authentication checks. If any step fails, leave the services stopped, print the exact staging path and failure, and do not delete the safety dump. -- [ ] **Step 5: Add file-reference verification** - -Run a small SQL query against `manual_upload_files` to enumerate retained `filePath` values and check each path inside the restored `/data/uploads` tree. Report legacy rows with `retainedAt IS NULL` as warnings. Return non-zero if any retained database reference is broken. - -- [ ] **Step 6: Validate the restore command without touching live data** +- [ ] **Step 4: Validate the restore command without touching live data** Run: @@ -329,11 +317,11 @@ bash -n scripts/backup/restore.sh Expected: syntax passes and `list` prints available snapshot IDs without stopping any service or modifying a volume. -- [ ] **Step 7: Commit durable STL retention and restore tooling** +- [ ] **Step 5: Commit guarded restore tooling** ```bash -git add scripts/backup/restore.sh prisma/schema.prisma prisma/migrations/20260722000000_mark_retained_manual_files/migration.sql src/app/api/uploads/route.ts worker/src/manual-upload.ts -git commit -m "feat: retain uploaded STL files for recovery" +git add scripts/backup/restore.sh +git commit -m "feat: add guarded database and session restore" ``` ### Task 5: Document Synology setup, operations, and recovery @@ -343,7 +331,7 @@ git commit -m "feat: retain uploaded STL files for recovery" - Modify: `README.md` **Interfaces:** -- Consumes: the exact environment variables, systemd units, and restore commands from Tasks 1–4. +- Consumes: the exact environment variables, systemd units, and restore commands from Tasks 1-4. - Produces: operator-facing instructions that do not require reading implementation files. - [ ] **Step 1: Document Synology configuration** @@ -375,15 +363,15 @@ sudo systemctl start dragons-stash-backup.service sudo journalctl -u dragons-stash-backup.service -n 100 --no-pager ``` -Explain that the first run may be long because it uploads all existing STL and session data; later Restic snapshots deduplicate unchanged data. +Explain that the first run captures PostgreSQL and TDLib session state. State clearly that STL binaries stay in Telegram, and that restored PostgreSQL metadata and mappings are what allow normal lookup and delivery after restore. - [ ] **Step 4: Document monitoring, retention, and restore** -Document how to inspect timer status, service failures, Restic snapshots, repository checks, and the four restore modes. Explicitly state that `restore-live` is destructive and requires the confirmation flag. +Document how to inspect timer status, service failures, Restic snapshots, repository checks, and the four restore modes. Explicitly state that `restore-live` is destructive and requires the confirmation flag. Explain that channel-forwarding behavior and archive/STL-content integrity validation are future work, not restore checks. - [ ] **Step 5: Add a concise production-backup section to the root README** -Add a link from the deployment/operations section to `scripts/backup/README.md`, state that Docker volumes are not backups, and identify PostgreSQL, STL uploads, and Telegram session volumes as the protected data set. +Add a link from the deployment/operations section to `scripts/backup/README.md`, state that Docker volumes are not backups, and identify the PostgreSQL logical dump and Telegram session volumes as the protected data set. State that manual uploads and temporary ZIPs are excluded and STL binaries remain in Telegram. - [ ] **Step 6: Commit documentation** @@ -398,7 +386,7 @@ git commit -m "docs: document Synology backup and recovery" - Modify: `scripts/backup/README.md` only if verification commands need correction. **Interfaces:** -- Consumes: the complete backup stack from Tasks 1–5. +- Consumes: the complete backup stack from Tasks 1-5. - Produces: evidence that the acceptance criteria are met, including a disposable restore rehearsal and a failure-path result. - [ ] **Step 1: Validate configuration and scripts** @@ -413,9 +401,9 @@ systemd-analyze verify deploy/systemd/dragons-stash-backup.service deploy/system Expected: all commands exit `0`. -- [ ] **Step 2: Seed a recognizable test record and STL file** +- [ ] **Step 2: Seed recognizable database metadata** -Using the existing app/database workflow, create one test upload whose database metadata and file can be identified after restore. Record the expected filename, upload ID, and file checksum before backup. +Using the existing app/database workflow, identify a record whose Telegram archive, message, package, and file metadata can be recognized after restore. Record the expected database identifiers before backup. Do not create or retain a local STL binary for this verification. - [ ] **Step 3: Run a real backup and inspect the snapshot** @@ -428,7 +416,7 @@ docker compose --profile backup run --rm backup snapshots docker compose --profile backup run --rm backup check ``` -Expected: the service succeeds, the snapshot exists, the repository check succeeds, all services are running again, and the test file checksum is represented in the backed-up volume. +Expected: the service succeeds, the snapshot exists, the repository check succeeds, and all services are running again. - [ ] **Step 4: Test the failure path with the NAS unavailable** @@ -436,7 +424,7 @@ Temporarily unmount the Synology share in a controlled maintenance session, run - [ ] **Step 5: Rehearse a disposable restore** -Restore the selected snapshot to a disposable Compose project or isolated Docker volumes. Import the database dump, restore the three file trees, start the disposable app/worker/bot services, call `/api/health`, and run the file-reference verification. Confirm the test upload metadata and STL checksum match the pre-backup record. +Restore the selected snapshot to a disposable Compose project or isolated Docker volumes. Import the database dump, restore the two TDLib session trees, start the disposable app/worker/bot services, and call `/api/health`. Confirm the recognizable database metadata and Telegram mappings match the pre-backup record. Do not assert the presence, checksum, content, or forwarding behavior of STL binaries. - [ ] **Step 6: Verify retention behavior** @@ -455,7 +443,8 @@ git commit -m "test: document verified backup and restore procedure" ## Plan Self-Review -- **Spec coverage:** PostgreSQL dump, STL volume, both Telegram volumes, Synology NFS, Restic encryption/deduplication, 30-day retention, maintenance window, service restart on failure, guarded restore, file-reference validation, monthly repository/restore checks, and NAS-loss caveat are covered by Tasks 1–6. +- **Spec coverage:** PostgreSQL logical dump, both Telegram session volumes, Synology NFS, Restic encryption, 30-day retention, maintenance window, service restart on failure, guarded restore, monthly repository/restore checks, database metadata recovery, and NAS-loss caveat are covered by Tasks 1-6. +- **Exclusions:** `manual_uploads` and `tmp_zips` are excluded; local STL retention, restored STL binaries, file-path/checksum validation, channel forwarding, and archive/STL-content integrity checks are not implementation requirements. - **Placeholder scan:** No `TBD`, `TODO`, or unspecified implementation task remains. Environment-dependent values are explicit configuration variables or operator-supplied paths. - **Type/interface consistency:** The Compose service name is consistently `backup`; the container command modes are `backup` and `restore`; the host wrapper owns service lifecycle; the restore script owns destructive confirmation; Restic owns snapshots and pruning. - **Scope check:** The plan contains one operational subsystem with separate backup, restore, scheduling, and documentation units that can each be reviewed and tested independently. diff --git a/docs/superpowers/specs/2026-07-21-database-and-file-backups-design.md b/docs/superpowers/specs/2026-07-21-database-and-file-backups-design.md index 92483af..55028d2 100644 --- a/docs/superpowers/specs/2026-07-21-database-and-file-backups-design.md +++ b/docs/superpowers/specs/2026-07-21-database-and-file-backups-design.md @@ -6,36 +6,35 @@ ## Problem -Dragon's Stash currently persists PostgreSQL in a Docker named volume and stores uploaded STL files in the `manual_uploads` volume. A Docker volume protects data from container recreation, but it is not an off-host backup. A host disk failure, accidental deletion, corruption, or ransomware event could destroy both the database and the files it references. +Dragon's Stash currently persists PostgreSQL in a Docker named volume. The Telegram worker and bot also persist authentication and session state in the `tdlib_state` and `tdlib_bot_state` volumes. Docker volumes protect against container recreation, but they are not off-host backups. A host disk failure, accidental deletion, corruption, or ransomware event could destroy the database and require both Telegram clients to authenticate again. -The worker currently deletes manual-upload directories after processing. The backup feature will change that behavior for new uploads so the existing `manual_uploads` volume becomes a durable STL archive. Files already deleted before this change cannot be reconstructed by the backup feature. - -The Telegram worker and bot also persist authentication/session state in `tdlib_state` and `tdlib_bot_state`. These files are sensitive and losing them requires Telegram re-authentication. +STL binaries are intentionally not retained as a local recovery set. They remain in Telegram. The PostgreSQL database preserves the archive, message, package, and file metadata needed to locate and send those Telegram-hosted binaries after the application database is restored. Existing worker cleanup behavior is unchanged. ## Goals -- Protect PostgreSQL data and uploaded STL files against loss of the application host. -- Preserve newly uploaded STL files after worker processing so the backup contains completed uploads, not only in-flight uploads. +- Protect PostgreSQL data against loss of the application host with a logical backup. +- Preserve Telegram worker and bot session state so a host restore does not normally require re-authentication. - Store backups on a Synology NAS over an authenticated, host-restricted NFS share. -- Create one recoverable snapshot containing related database and file state. +- Create one recoverable snapshot containing related database and session state. - Retain 30 daily recovery points. -- Include Telegram worker and bot session state so a host restore does not require re-authentication. -- Provide a documented, repeatable restore process. +- Provide a documented, repeatable, guarded restore process. - Detect failed or corrupt backups instead of silently pruning the last good copy. ## Non-goals - Building an in-app backup-management UI. +- Backing up `manual_uploads`, retaining completed STL binaries locally, or changing worker cleanup behavior. - Backing up temporary ZIP processing data in `tmp_zips`. - Copying the raw `postgres_data` volume as the primary database backup. -- Recovering STL binaries that were deleted by older worker runs before durable retention was enabled. +- Implementing future Telegram channel-forwarding behavior. +- Validating the content or binary integrity of Telegram archives or STL files. This is future work and is outside the backup/restore feature. - Providing protection against loss of the NAS itself. A later Synology Hyper Backup task can replicate this repository to another device or cloud destination. ## Selected approach Use a Linux-host backup script, scheduled by a systemd timer, with Restic writing to an encrypted repository on a Synology NFS share. -This approach keeps the backup and restore workflow explicit, deduplicates large STL files across daily snapshots, and avoids tying recovery to PostgreSQL's internal data-directory layout. Restic's repository encryption also protects database contents, uploaded files, and Telegram session state if the NAS share is accessed directly. +This approach keeps backup and restore explicit, avoids tying recovery to PostgreSQL's internal data-directory layout, and captures the sensitive TDLib state alongside the database. Restic repository encryption protects database contents and Telegram session state if the NAS share is accessed directly. ## Storage layout @@ -57,12 +56,11 @@ The repository lives below the mounted share. The Restic password is stored sepa Each successful Restic snapshot contains: - A PostgreSQL custom-format dump generated for that run. -- The contents of the `manual_uploads` Docker volume. - The contents of the `tdlib_state` Docker volume. - The contents of the `tdlib_bot_state` Docker volume. -- A small manifest with the backup timestamp, application image/version, and database migration state. +- A small manifest with the backup timestamp, application image/version, database migration state, and captured volume paths. -The `tmp_zips` volume is scratch space and is excluded. +The `manual_uploads` and `tmp_zips` volumes are excluded. STL binaries continue to live in Telegram; the restored database supplies the metadata and mappings required for the worker and bot to locate and send them. ## Backup flow @@ -73,28 +71,28 @@ The systemd timer invokes one backup command at the chosen nightly time. The com 3. Stops the `app`, `worker`, and `bot` services while leaving PostgreSQL running. 4. Creates a PostgreSQL custom-format dump from the running database. 5. Creates a manifest for the backup. -6. Runs one Restic backup over the dump and the read-only mounted Docker volumes. +6. Runs one Restic backup over the dump and the read-only mounted TDLib session volumes. 7. Verifies that Restic created the snapshot successfully. 8. Applies the retention policy: keep the latest 30 daily snapshots, then prune unreferenced data. 9. Restarts all stopped services, whether the backup succeeded or failed. -The service-stop window ensures that application writes and TDLib session updates do not occur while the corresponding file volumes are being captured. A failed run must never trigger retention pruning. - -New `ManualUploadFile` rows carry a retention timestamp. Restore verification requires retained file paths to exist and reports older rows without a retention timestamp as legacy warnings, because those binaries may already have been deleted before this feature was enabled. +The service-stop window ensures that application writes and TDLib session updates do not occur while the corresponding data is captured. A failed run must never trigger retention pruning. ## Restore flow The restore tooling and documentation will support this sequence: -1. Stop `app`, `worker`, and `bot`. +1. Stop `app`, `worker`, and `bot` for a live restore. 2. Select and inspect a Restic snapshot. -3. Restore the PostgreSQL dump and volume contents to a staging location. +3. Restore the PostgreSQL dump and TDLib session contents to a staging location. 4. Preserve the current database and volumes or confirm that the operator intends to replace them. -5. Restore `manual_uploads`, `tdlib_state`, and `tdlib_bot_state` into their Docker volumes with the expected ownership and paths. +5. Restore `tdlib_state` and `tdlib_bot_state` into their Docker volumes with the expected ownership and paths. 6. Restore the database from the custom-format dump into the configured PostgreSQL database. -7. Verify that `ManualUploadFile.filePath` values resolve to restored files and that the application health endpoint can connect to PostgreSQL. +7. Verify the dump, session-volume layout, database connectivity, and worker/bot authentication startup state. 8. Start the services and inspect logs for startup, migration, and worker/bot authentication errors. +After restore, existing database metadata and mappings allow normal Telegram-based STL lookup and delivery. Restoring STL binaries, forwarding Telegram content, and checking archive/STL binary integrity are outside this restore flow. + The restore process must be safe to rehearse against a disposable Compose project without modifying the live deployment. ## Failure handling and verification @@ -119,9 +117,9 @@ The restore process must be safe to rehearse against a disposable Compose projec ## Acceptance criteria - A nightly systemd timer creates a Restic snapshot on the Synology share. -- A snapshot includes PostgreSQL, all uploaded STL files, and both Telegram session volumes. -- At least 30 daily recovery points are retained without duplicating unchanged STL content unnecessarily. -- Newly uploaded STL files remain in `manual_uploads` after worker processing and are included in subsequent backup snapshots. -- A simulated host-loss restore reconstructs the database and uploaded files in a disposable Compose environment. +- A snapshot includes a PostgreSQL logical dump and both Telegram session volumes, while excluding `manual_uploads` and `tmp_zips`. +- At least 30 daily recovery points are retained. +- A simulated host-loss restore reconstructs the database and Telegram session state in a disposable Compose environment. +- The restored database retains the Telegram metadata and mappings the worker and bot use to locate and send STL binaries that remain in Telegram. - A failed backup leaves services running and preserves the last known-good snapshot. - The restore procedure is documented well enough for an operator to execute without reading the implementation.