Skip to content

Backups: plain-file NAS mirror + read-only archive copy - #38

Merged
davior merged 4 commits into
mainfrom
claude/wonderful-goldberg-ifbtdt
Oct 22, 2026
Merged

davior merged 4 commits into
mainfrom
claude/wonderful-goldberg-ifbtdt

Conversation

@davior

@davior davior commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Why

The restic sidecar stores encrypted, deduplicated chunks, so nothing on the NAS can be opened or run. This adds a plain-file mirror next to it, plus a read-only copy of GAM that runs from that mirror in Synology's Container Manager, with no dependency on production or Notes.

Mirror (ops/backup)

  • Turned on by BACKUP_MIRROR_PATH, and runs in the same scheduled run after restic. If it's empty, the mirror stays off.
  • Uses rclone over the same SFTP account, key and pinned host key, so the NAS needs no changes. Hash checks are off because DSM's SFTP-only account has no shell. Size+modtime is enough since media names are write-once UUIDs.
  • Order of each run:
    1. Media, with sync, so deletions propagate. restic keeps the history.
    2. A rollback-journal copy of the DB, uploaded as a partial file and renamed into place.
    3. MIRROR_INFO, with the time and alembic revision.
  • .partial files are excluded.

Read-only mode (backend + frontend)

  • READ_ONLY=true:
    • opens SQLite mode=ro, with NullPool so each session sees the newly swapped-in file;
    • refuses every non-GET with {"detail":{"code":"read_only",…}};
    • starts no workers;
    • skips migrations in entrypoint.sh.
  • READ_ONLY_USER (an id or username) replaces sign-in. Settings refuse it unless READ_ONLY is also set, and current_user checks both again.
  • /api/config gains read_only. The shell shows an amber banner and hides sign-out.

Deployment / docs

  • ops/archive/ holds a standalone compose project for Container Manager: read-only mounts, its own network, and a distinct JWT secret.
  • ops/archive/README.md is the NAS walkthrough.
  • ops/backup/README.md is now one from-scratch procedure: NAS → restic → mirror → restore. The dead backup-strategy.md reference is gone.
  • New section, "Already backing up Gecko Notes from this server?". It reuses Notes' SFTP account, key and known_hosts, and puts GAM under /GeckoNotes/gam/{restic,mirror}, next to Notes' /GeckoNotes/restic, so the NAS needs no changes. Checked against gecko-notes' ops/backup.
  • The default BACKUP_SFTP_REMOTE_PATH is now /GAM/restic. The old /gam default was the root of DSM's chroot, where the backup account can't write.

Testing

  • pytest -q: 1272 passed, including new tests/test_read_only.py.
  • npm test: 395 passed. npm run build is clean.
  • backup.sh was run end to end against rclone's local backend. .partial was excluded, the DB came out in delete mode and opened mode=ro, MIRROR_INFO was written, and a deletion propagated on the second run.
  • uvicorn was run with READ_ONLY=true against a migrated DB on a read-only directory. /api/me returned the fixed user, a write got 403 read_only, and a DB swapped in by rename was picked up without a restart.
  • CI builds the Docker images. Not tested: real SFTP to a Synology, and Container Manager itself.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XWoa1a1upTria7ntq56x3B

The restic sidecar stores encrypted chunks, so nothing on the NAS can be
opened or run. backup.sh now optionally (BACKUP_MIRROR_PATH) keeps a
plain-file mirror beside it: rclone over the same SFTP account, media
first and then a rollback-journal copy of the database, plus a
MIRROR_INFO with the time and migration revision.

READ_ONLY=true opens the database mode=ro with a fresh connection per
session (the mirror renames a new file in under it), refuses every
non-GET with a read_only error, starts no workers and skips migrations.
READ_ONLY_USER replaces sign-in in an archive and is refused without
READ_ONLY. The frontend shows a banner and drops sign-out.

ops/archive/ holds the Container Manager project for the Synology, and
ops/backup/README.md is now one from-scratch procedure: NAS, restic,
mirror, restore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWoa1a1upTria7ntq56x3B
…ction

Confirmed against gecko-notes' ops/backup: its account already writes to
GeckoNotes, with its repository at /GeckoNotes/restic, so GAM beside it
under /GeckoNotes/gam/ needs no NAS change at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWoa1a1upTria7ntq56x3B
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWoa1a1upTria7ntq56x3B
@davior
davior marked this pull request as ready for review October 22, 2026 01:33
@davior
davior merged commit 1973148 into main Oct 22, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants