feat(admin): back the data up, and put it back, from the admin tool

A new BACKUP tab downloads the deployment's whole state — the SQLite file
and both media folders, photos included — as one .tar.gz, and takes the same
file back. That one artefact therefore does both jobs: the operator's backup
and the data package that moves an install onto another box.

The database is snapshotted through SQLite's own backup rather than copied,
because the file is written to while the archive streams; the media folders
are tarred straight off the volume, so no second copy of them is made.

A restore replaces the data on disk and then exits — the container's restart
policy brings the API back on the restored files, which is the only moment the
open handle can be dropped. The state being replaced is tarred aside first,
and the archive is checked for `..` entries before anything is unpacked. The
API authenticates that route before it reads a byte, and nginx lets that one
path past the body cap which holds everywhere else.
This commit is contained in:
2026-09-25 08:46:29 +07:00
parent 9016ef038d
commit d2115941c7
7 changed files with 252 additions and 4 deletions
+3 -1
View File
@@ -15,7 +15,9 @@ export const MAX_RECIPE_BYTES = 256 * 1024;
export const MAX_PHOTO_BYTES = 12 * 1024 * 1024;
export const MAX_PHOTOS_PER_USER = 12;
const DATA_DIR = process.env.DATA_DIR || './data';
// Everything the deployment owns lives here: the SQLite file and the two media
// folders. Exported because the backup/restore routes walk the same root.
export const DATA_DIR = process.env.DATA_DIR || './data';
mkdirSync(DATA_DIR, { recursive: true });
// Uploaded originals. Filenames are server-generated hex — a user filename