Built with Chinaski

Disaster Recovery

If a host dies, a restore goes wrong, or you need to move Chinaski to a new machine, this is the runbook. Three backup layers exist (system-level timer backups, in-app archive backups with a one-click restore, and an upload-your-own-archive restore) and knowing which to reach for is most of the job.

What you should have before disaster strikes

  • The daily system backup (backup.sh under systemd): a gzip’d WAL-safe snapshot of the database in /var/backups/chinaski<N>/ (integrity-checked before it’s kept, mode 0600, kept 14 days by default) plus an rsync mirror of the media directory.
  • In-app backups (Admin → Backups): full chinaski_backup_<timestamp>.tar.gz archives containing the database, media, themes, and site templates, created manually, automatically on a daily or weekly schedule you set in the Backups tab, and optionally pushed to a DigitalOcean Spaces bucket.
  • The env file from the old host (/etc/chinaski<N>/env with CHINASKI_SECRET, LISTEN_URL), see the “same secret” warning below. Back it up now; it isn’t in the database and isn’t in the tar.gz.

Restoring in place (the common case)

From Admin → Backups, upload a chinaski_backup_*.tar.gz:

  • Restore uploads are capped at 50 MiB.
  • Use dry run first: it stages the archive and previews what will change, then a confirmation click performs the real restore.
  • The restore validates the archive (a backup manifest must match), then applies it in a safe order: the database is restored atomically first, then the media directory is swapped (rolled back if the swap fails), then themes and site templates are restored; failures there are reported as warnings, not total aborts.
  • If the backup was made by a different schema version than the running app, the restore refuses rather than risk corruption; upgrade the app (or the version that made the backup) instead.
  • Options for restoring: upload from your browser, or restore directly from a Spaces-stored backup if Spaces is configured. Automatic API restores are also available (same rules) for scripting.
  • What the restore touches is visible in the audit log; failed restores leave the site unchanged.

Rebuilding a lost host

  1. Install a fresh Chinaski on the new host with the standard installer (same instance name pattern). This creates the service user, an empty database, and a new env file.
  2. Restore the env file, don’t mix secrets. Copy the old /etc/chinaski<N>/env over the generated one before starting, or expect the encrypted settings (SMTP password, API-encrypted secrets) stored in a restored database to be undecryptable. The encryption key for stored secrets is derived from CHINASKI_SECRET; restoring a database under a different secret silently yields empty values rather than an error, so fix the env secret first and re-save any settings afterwards if you can’t.
  3. Restore data from the backups you kept, easiest via the in-app restore above (upload the tar.gz).
  4. Rebuild the static site (builder page via Settings dashboard “Rebuild”) and wire the site live with the reverse proxy/TLS config from the deploy examples, then verify /health responds and content renders.

Recovering with only the system backups

If the tar.gz route isn’t available, the daily /var/backups DB snapshot plus the media mirror are enough:

  • Un-gzip chinaski-<timestamp>.db.gz back to the database path.
  • Sync the media mirror back into the instance’s media directory.
  • Note the narrow consistency window documented in the backup script itself: the DB snapshot is taken before the media mirror, so a media file deleted seconds before the backup may exist in the DB but not in the media tree. The documented reconciliation step is to delete media rows whose file is missing from the restored media directory (a per-filename pass over the media table), which the backup script comments spell out.
  • After restore: trigger a rebuild, verify /health, and take a fresh in-app backup to re-establish your normal safety net.

Guardrails worth knowing

  • Restores validate the backup’s manifest and refuse schema-version mismatches outright (“Restore aborted to prevent data corruption”), so a backup from a much older version needs to be restored into the matching app version.
  • Backup verification runs on every created archive by default (there’s a backup-verify setting; it’s on unless explicitly turned off).
  • Because backups contain password hashes and session secrets, treat the backup directory and any downloaded tar.gz as sensitive: the system backup directory is forced to mode 0700 on every run, and snapshots are stored mode 0600.
  • A scheduled Spaces backup runs only when Spaces is fully configured (endpoint, bucket, access key, secret key) and only when air-gap mode is off.
  • The last automatic backup time is visible on the Backups tab, along with the local backup list; spot-check after any server change.
Read this site in