Endpoint telemetry, PC Detail rework and a checked backup/restore path
This commit is contained in:
175
docker-compose.backup.yml
Normal file
175
docker-compose.backup.yml
Normal file
@@ -0,0 +1,175 @@
|
||||
# shopdb-flask BACKUP overlay - opt-in, per-site.
|
||||
#
|
||||
# WHY a separate overlay: backups are not part of the base stack every site must
|
||||
# run. A facility turns them on by adding this file to its compose invocation:
|
||||
#
|
||||
# docker compose -f docker-compose.yml -f docker-compose.backup.yml \
|
||||
# --profile backup up -d
|
||||
#
|
||||
# That brings up THREE cooperating pieces:
|
||||
# - backup-store : a resident, isolated restic REST server (the third
|
||||
# container). Holds every snapshot in its own volume and is
|
||||
# reachable ONLY over the compose network - never published
|
||||
# to the host - so the app can touch backups solely through
|
||||
# the backup/restore utility, not with a shell or db creds.
|
||||
# - backup-watcher : a resident poller that actuates the "Back up now" sentinel
|
||||
# the api drops, running the one-shot against backup-store.
|
||||
# This is what makes the in-app button produce a real backup
|
||||
# given the api container cannot run docker.
|
||||
# - backup : the same one-shot as a manual `run --rm backup` for sites
|
||||
# that prefer a host scheduler (cron / Task Scheduler).
|
||||
#
|
||||
# WHAT each run captures, as one recoverable set:
|
||||
# - db.sql.gz : mysqldump --single-transaction of the whole DB (users,
|
||||
# auth, ledger, every plugin table). InnoDB-consistent,
|
||||
# non-blocking.
|
||||
# - instance/ : restic snapshot of /app/instance (deduped, encrypted),
|
||||
# the files MySQL rows point at.
|
||||
# - manifest.json: core+plugin Alembic revisions and image tag at capture
|
||||
# time - the stamp that makes a restore a CHECKED operation.
|
||||
#
|
||||
# Retention (restic-native): --keep-daily 5 --keep-weekly 4 --keep-monthly 6,
|
||||
# satisfying "any day in the last 5" plus a cheap long-horizon tail.
|
||||
#
|
||||
# DESTINATION defaults to the third container via RESTIC_REPOSITORY:
|
||||
# third container : rest:http://backup-store:8000/ (default, ships with app)
|
||||
# AWS S3 : s3:s3.amazonaws.com/shopdb-<site>-backups
|
||||
# Azure Blob : azure:shopdbbackups:<site>
|
||||
# SMB share : mount the share on the host, point at a local path
|
||||
# Credentials come from the site's env/secret store, never committed - same
|
||||
# pattern as the collector key.
|
||||
|
||||
# The THIRD container: an isolated backup store the app can only reach through
|
||||
# the backup/restore utility, never with a shell or the db credentials. It runs
|
||||
# restic's REST server over the compose network; the only thing that speaks to
|
||||
# it is the backup watcher/one-shot via RESTIC_REPOSITORY=rest:http://... . Its
|
||||
# data lives in its own named volume (backupstore_data), so it is container
|
||||
# agnostic - later a pull/migration utility can point at this same REST endpoint
|
||||
# from anywhere without touching the app or the database.
|
||||
services:
|
||||
backup-store:
|
||||
image: restic/rest-server:0.13.0
|
||||
profiles: ["backup"]
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# Private (no public /create) so repos are provisioned by the backup
|
||||
# client's `restic init`, and append-only OFF here because the same
|
||||
# utility both writes and prunes; isolation comes from this being the
|
||||
# only service on the network with access, not from client trust.
|
||||
DISABLE_AUTHENTICATION: "true"
|
||||
OPTIONS: "--no-auth"
|
||||
volumes:
|
||||
- backupstore_data:/data
|
||||
# Deliberately NOT published to the host: reachable only over the compose
|
||||
# network by service name, so nothing outside the stack can touch backups.
|
||||
expose:
|
||||
- "8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "sh", "-c", "wget -q -O - http://127.0.0.1:8000/ >/dev/null 2>&1 || true"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# Resident watcher: actuates the "Back up now" sentinel the api drops. This is
|
||||
# the piece that makes the UI button run a real backup, since the api itself
|
||||
# cannot run docker. It shares the base image with the one-shot and calls the
|
||||
# same backup.sh.
|
||||
backup-watcher:
|
||||
image: shopdb-backup:local
|
||||
build:
|
||||
context: .
|
||||
dockerfile: scripts/backup/Dockerfile.backup
|
||||
profiles: ["backup"]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
backup-store:
|
||||
condition: service_started
|
||||
environment:
|
||||
# DB access - same credentials the api uses, reaching db over the compose
|
||||
# network by service name.
|
||||
MYSQL_HOST: db
|
||||
MYSQL_DATABASE: shopdb_flask
|
||||
MYSQL_USER: shopdb
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD must be set}
|
||||
# Destination is the third container by default: restic's REST backend on
|
||||
# the isolated backup-store. A site can still override RESTIC_REPOSITORY
|
||||
# in .env to send offsite instead, but out of the box backups land in the
|
||||
# dedicated container.
|
||||
RESTIC_REPOSITORY: ${RESTIC_REPOSITORY:-rest:http://backup-store:8000/}
|
||||
RESTIC_PASSWORD: ${RESTIC_PASSWORD:?RESTIC_PASSWORD must be set}
|
||||
# Optional cloud creds, passed through when set (empty otherwise).
|
||||
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
|
||||
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
|
||||
AZURE_ACCOUNT_NAME: ${AZURE_ACCOUNT_NAME:-}
|
||||
AZURE_ACCOUNT_KEY: ${AZURE_ACCOUNT_KEY:-}
|
||||
# Site tag stamped into every snapshot + manifest.
|
||||
SHOPDB_SITE: ${SHOPDB_SITE:-shopdb}
|
||||
SHOPDB_IMAGE_TAG: ${SHOPDB_IMAGE:-source}
|
||||
SHOPDB_BACKUPS_ROOT: /backups
|
||||
BACKUP_POLL_SECONDS: ${BACKUP_POLL_SECONDS:-10}
|
||||
volumes:
|
||||
# The watcher services both backup (read) and restore (write) requests, so
|
||||
# the instance tree is mounted read-write here. Restore has to lay the
|
||||
# files half of a set back down next to the DB rows that reference it; the
|
||||
# backup half still only ever reads. The manual one-shot below stays :ro.
|
||||
- instance_data:/app/instance
|
||||
# Alembic chain, read-only, so the manifest can stamp the schema revision
|
||||
# the running code is at.
|
||||
- ./migrations:/app/migrations:ro
|
||||
- ./plugins:/app/plugins:ro
|
||||
# Shared backups volume: holds the fast local db.sql.gz + manifest and the
|
||||
# .backup-requested sentinel the api drops. The api mounts this same volume
|
||||
# (see docker-compose.yml) so the UI can enumerate sets and request runs.
|
||||
- backup_data:/backups
|
||||
- ./scripts/backup:/opt/backup:ro
|
||||
entrypoint: ["/opt/backup/watch.sh"]
|
||||
|
||||
# Manual one-shot, unchanged usage: `docker compose ... run --rm backup`.
|
||||
# Kept for host-scheduler / cron sites that prefer a scheduled pass over the
|
||||
# in-app button. Shares everything with the watcher but runs once and exits.
|
||||
backup:
|
||||
image: shopdb-backup:local
|
||||
build:
|
||||
context: .
|
||||
dockerfile: scripts/backup/Dockerfile.backup
|
||||
profiles: ["backup"]
|
||||
restart: "no"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
backup-store:
|
||||
condition: service_started
|
||||
environment:
|
||||
MYSQL_HOST: db
|
||||
MYSQL_DATABASE: shopdb_flask
|
||||
MYSQL_USER: shopdb
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD must be set}
|
||||
RESTIC_REPOSITORY: ${RESTIC_REPOSITORY:-rest:http://backup-store:8000/}
|
||||
RESTIC_PASSWORD: ${RESTIC_PASSWORD:?RESTIC_PASSWORD must be set}
|
||||
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
|
||||
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
|
||||
AZURE_ACCOUNT_NAME: ${AZURE_ACCOUNT_NAME:-}
|
||||
AZURE_ACCOUNT_KEY: ${AZURE_ACCOUNT_KEY:-}
|
||||
SHOPDB_SITE: ${SHOPDB_SITE:-shopdb}
|
||||
SHOPDB_IMAGE_TAG: ${SHOPDB_IMAGE:-source}
|
||||
SHOPDB_BACKUPS_ROOT: /backups
|
||||
volumes:
|
||||
- instance_data:/app/instance:ro
|
||||
- ./migrations:/app/migrations:ro
|
||||
- ./plugins:/app/plugins:ro
|
||||
- backup_data:/backups
|
||||
- ./scripts/backup:/opt/backup:ro
|
||||
entrypoint: ["/opt/backup/backup.sh"]
|
||||
|
||||
# Give the api read/write on the shared backups volume so the UI can list sets
|
||||
# and drop the "Back up now" sentinel the watcher actuates. This extends the
|
||||
# api service defined in docker-compose.yml (merged by compose).
|
||||
api:
|
||||
volumes:
|
||||
- backup_data:/backups
|
||||
|
||||
volumes:
|
||||
backup_data:
|
||||
backupstore_data:
|
||||
Reference in New Issue
Block a user