# Operations This document covers deployment, backups, and rollback for the librenot.es production environment. For developer / contributor docs see the top-level [README](../README.md). ## Deployment The repository ships a CI workflow at `.gitea/workflows/deploy.yml` that builds and pushes a Docker image on every push to `main` and on every `vX.Y.Z` tag, then SSHes to the deployment host and runs `docker compose pull && up -d`. The workflow is gated on the repository variable `DEPLOY_ENABLED=true`. Deployment is opt-in; flipping the variable disables the workflow without removing the file. ### Required secrets The workflow expects these secrets and variables on the repo: | Name | Type | Purpose | | ---- | ---- | ------- | | `REGISTRY` | secret | hostname of the OCI registry | | `REGISTRY_USER` | secret | robot account | | `REGISTRY_PASS` | secret | robot token | | `DEPLOY_HOST` | secret | `user@host` SSH target | | `DEPLOY_KEY` | secret | passphrase-less private key | | `DEPLOY_PATH` | secret | absolute path on host with `docker-compose.*.yml` | | `HEALTH_URL` | secret | e.g. `https://librenot.es/healthz` | | `DEPLOY_ENABLED` | variable | `true` to enable the workflow | ### Production compose stack On the deployment host, place `docker-compose.yml` and `docker-compose.prod.yml` from this repo at `$DEPLOY_PATH`, together with an `.env` file containing the runtime configuration (JWT secret, SMTP credentials, public base URL, image tag). Bring it up with: ```sh docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d ``` ### Rollback Image tags are immutable per commit / version. To roll back, set `LIBRENOTES_IMAGE` to the previous tag in `.env` and run the same `up -d` command. The deployment workflow does not auto-rollback on health-check failure — failed health alerts the operator via the workflow itself, who can then redeploy the prior tag manually. ```sh # Example: roll back to v0.1.2 sed -i 's/^LIBRENOTES_IMAGE=.*/LIBRENOTES_IMAGE=registry.librete.ch\/librenotes:v0.1.2/' .env docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d ``` ## Backups `scripts/backup.sh` is a self-contained backup driver suitable for running from cron, a systemd timer, or the supplied `scripts/librenotes-backup.{service,timer}` units. Required tools on the backup host: `sqlite3`, `tar`, `gzip`, and optionally `rclone` for off-site copy. On Debian/Ubuntu: `apt install sqlite3 rclone`. ### What is backed up - The SQLite database (`$LIBRENOTES_DB`) via the SQLite `.backup` command, which produces a consistent online snapshot without needing to stop the application. - The per-tenant note directory (`$LIBRENOTES_DATA_DIR`) as a gzipped tar. - An `info.txt` in each archive recording the backup timestamp, hostname, and version. ### Off-site copy Set `BACKUP_REMOTE` to an `rclone` destination (e.g. `s3:librenotes-backups`). When set, the script invokes `rclone copy` to upload the archive after creation. Without it, backups stay on the local disk only. ### Retention `scripts/backup-prune.sh` enforces the policy "keep 30 daily + 12 monthly". Run it from the same timer as the backup. Files keep the form `librenotes-YYYYMMDD-HHMMSS.tar.gz` so the prune script can sort and select by name alone. ### Restore test `scripts/backup-restore-test.sh` picks the most recent archive, extracts it into a scratch directory, runs `sqlite3 .schema` on the database to confirm it is readable, and verifies that the note tar lists at least one entry. It is wired into a separate weekly timer so a silent backup-corruption regression cannot hide indefinitely. ### Systemd Drop `scripts/librenotes-backup.{service,timer}` into `/etc/systemd/system/`, then: ```sh systemctl daemon-reload systemctl enable --now librenotes-backup.timer ``` Confirm with: ```sh systemctl list-timers librenotes-backup.timer ```