# Kopia backups to Cloudflare R2 This directory defines restore-focused backups for the Docker services that are actually running on the host. It uses Kopia with Cloudflare R2 as an S3-compatible repository. ## Policy - Use Kopia as the only backup engine. - Store the repository in Cloudflare R2. - Keep secrets outside this repo in `/opt/kopia/kopia.env`. - Snapshot data needed to restore services and configuration. - Generate logical database dumps before each snapshot. - Include Paperless documents and Gitea repositories. - Exclude Nextcloud user data/multimedia until storage usage and R2 cost are explicitly accepted. Current size check on 2026-04-27: | Path | Size | | --- | ---: | | `/opt/paperless` | 316M | | `/opt/gitea` | 6.0M | | `/opt/nextcloud` | 2.8G | | `/opt/onlyoffice` | 4.3M | | `/opt/authentik` | 8.3M | | `/opt/trilium` | 3.6M | | `/opt/karakeep` | 5.2M | | `/opt/spintools` | 335M | ## What is backed up The snapshot paths are listed in `backup-paths.txt`. The important choices are: - Paperless: `/opt/paperless/data`, `/opt/paperless/media`, export and consume directories. - Gitea: `/opt/gitea/data` and runner data. - Nextcloud: config, themes and a MariaDB dump only. `/opt/nextcloud/data`, `/opt/nextcloud/custom_apps` and `/opt/nextcloud/html` are excluded. - Databases: PostgreSQL and MariaDB dumps are written to `/opt/kopia/staging/dumps/latest`. - Service configuration: deployment repo, Traefik, Authentik, n8n, Memos, Vikunja, Trilium, Karakeep, AdGuard, WireGuard, mail relay, Beszel, Homepage, Dozzle, Kasm and OpenCode. - Local active deployments: Scheduler, Spintools, THSCOM, OpenClaw restore state and OpenCode runtime state. Live database directories are excluded when a logical dump exists. Kasm's PostgreSQL database is backed up with `pg_dump`; Docker image/layer storage under `/opt/kasm/opt/docker` is intentionally excluded because it is rebuildable and large. OpenProject's embedded PostgreSQL data is not dumped by this first version; treat its restore as best-effort unless a service-specific dump command is added. ## Install Kopia Install Kopia on the host using the package method appropriate for the server. Then create the runtime directory: ```bash sudo mkdir -p /opt/kopia sudo cp backups/kopia/env.example /opt/kopia/kopia.env sudo chmod 600 /opt/kopia/kopia.env ``` Edit `/opt/kopia/kopia.env` and set the R2 bucket, endpoint, access keys and `KOPIA_PASSWORD`. Cloudflare R2 endpoint format: ```text .r2.cloudflarestorage.com ``` ## Initialize ```bash backups/kopia/init-r2-repository.sh ``` ## Run manually ```bash backups/kopia/run-backup.sh ``` The script: 1. Creates database dumps. 2. Snapshots the paths in `backup-paths.txt`. 3. Applies retention: latest 7, daily 7, weekly 4, monthly 6. 4. Runs `kopia repository validate-provider`. ## Enable timer Install the scripts under `/opt/kopia/scripts`; systemd should not execute them directly from a home directory on SELinux-enforcing hosts. ```bash sudo install -d -m 0755 /opt/kopia/scripts sudo install -m 0755 backups/kopia/*.sh /opt/kopia/scripts/ sudo install -m 0644 backups/kopia/backup-paths.txt backups/kopia/excludes.txt /opt/kopia/scripts/ sudo cp backups/kopia/systemd/kopia-docker-backup.* /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now kopia-docker-backup.timer ``` ## Web UI Kopia UI is exposed at: ```text https://kopia.sherlockhomeless.net ``` The service listens only on the Docker host gateway `10.0.0.1:51515` and is published through Traefik using a local `traefik-kopia.yml` copied from `traefik-kopia.yml.example`. Credentials are stored outside the repo in `/opt/kopia/kopia.env`: ```bash sudo awk -F= '/^KOPIA_SERVER_USERNAME|^KOPIA_SERVER_PASSWORD/ {print}' /opt/kopia/kopia.env ``` Install or update the service and Traefik route: ```bash sudo cp backups/kopia/systemd/kopia-server.service /etc/systemd/system/ cp backups/kopia/traefik-kopia.yml.example backups/kopia/traefik-kopia.yml # Edit backups/kopia/traefik-kopia.yml and replace the Basic auth placeholder. sudo cp backups/kopia/traefik-kopia.yml /opt/traefik/dynamic/kopia.yml sudo systemctl daemon-reload sudo systemctl enable --now kopia-server.service ``` ## Restore smoke test Requires `jq`. ```bash backups/kopia/restore-smoke-test.sh /tmp/kopia-restore-test ``` For real restores, restore into a temporary directory first, stop the target service, move data into place with correct ownership, then start the stack and validate the application.