Skip to content

Data and backup

data/
├── longbox.db      SQLite: accounts, catalogue, settings, jobs, CV cache
├── covers/         two JPEGs per comic (grid + detail)
├── uploads/<id>/   comics uploaded through the browser, per account
└── backups/        automatic database snapshots

Only two of those cannot be rebuilt: the database, and anything under uploads/. Covers regenerate on re-import.

Automatic snapshots

Longbox backs its own database up, daily by default, keeping the newest seven. Admin → Server → Backups turns it off, changes the interval and retention, or takes one on demand.

Snapshots use SQLite's VACUUM INTO, which produces a consistent, compacted copy while the server keeps serving. That matters: with WAL enabled the committed data is spread across longbox.db and longbox.db-wal, so a plain cp of the database file alone can capture a torn state. Each snapshot is opened and integrity-checked before it is reported as successful, then gzipped and written owner-only — it contains password hashes, session tokens, OAuth client secrets and your SMTP password.

The schedule runs inside the application, so it behaves the same under Docker and systemd with nothing else to configure. If you would rather drive it externally, turn the schedule off and use the timer units in deploy/:

sudo cp deploy/longbox-backup.{service,timer} /etc/systemd/system/
sudo systemctl enable --now longbox-backup.timer

Or from the command line, for a one-off — worth doing before an upgrade:

python -m app.cli backup --label pre-upgrade
python -m app.cli backups

Two things the automatic backups do not do

They do not include uploaded comics. uploads/ holds the only copy of anything added through the browser, but sweeping potentially many gigabytes of media into a nightly database snapshot is the wrong default. Use Back up, including uploads, or --include-uploads, or copy that directory separately.

They do not leave the machine. A backup sitting on the same disk as the database is not a backup — it survives a mistake, not a failed drive. Copy them off with rsync or scp:

rsync -av longbox:/var/lib/longbox/backups/ ~/longbox-backups/

Backups are deliberately not downloadable through the web interface. An admin session should not be enough to walk away with every credential in the database; getting them off the box should require access to the box.

Checking the files are still intact

Backups protect the catalogue. This protects the comics themselves:

python -m app.cli verify            # every library
python -m app.cli verify alex       # just one

The first run hashes every file in full with SHA-256 and stores the result. Later runs re-read and compare, reporting anything that no longer matches, has gone missing, or cannot be read. Exit status is non-zero when it finds something, so it works from cron.

This is separate from the fingerprint taken at import, which is size plus the first megabyte — fast, and enough to spot duplicates, but blind to a page rotting halfway through an archive.

A file that changed is not necessarily corrupt: re-tagging a comic rewrites it. Longbox deliberately does not update the stored hash when it sees a change, because that would quietly bless whatever the file has become and call it clean next time. Once you know why it changed, accept it explicitly:

python -m app.cli verify --rehash

Restoring

Stop the service first — restoring underneath a running server will corrupt what you are restoring.

sudo systemctl stop longbox
gunzip -c backups/longbox-20260907-033000-v3.db.gz > /var/lib/longbox/longbox.db
rm -f /var/lib/longbox/longbox.db-wal /var/lib/longbox/longbox.db-shm
sudo chown longbox:longbox /var/lib/longbox/longbox.db
sudo systemctl start longbox

Removing the -wal and -shm files matters: they belong to the database you just replaced, and leaving them there mixes two different databases together. The v3 in the filename is the schema version — restoring a newer backup onto an older release will not work.

There is no restore command, deliberately. It has to happen with the service stopped, and a command that silently overwrote a live database would be a worse tool than the four lines above.