e922f5f3b9
CI / changes (pull_request) Successful in 4s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 24s
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m41s
Add pgBackRest-based PITR for the production database, shipped disarmed (archive_mode and the base-backup timer gated off) so it stays inert until the operator arms it before real payments — an un-armed deploy cannot pile WAL onto the disk. - Postgres now builds from a thin image carrying pgBackRest (archive_command runs in-process); the prod overlay wires an encrypted (AES-256-CBC), path-style S3 repository with 30-day retention and a daily full base-backup systemd timer. - Repository config/secrets flow through the PROD_PGBACKREST_* Gitea set and write-prod-env.sh; prod-rollback re-renders the same env so a rollback cannot disarm archiving. - Grafana alerts watch pg_stat_archiver (failing / stalled), absent-safe on the test contour, which never archives. - Fix the pre-migration pg_dump to snapshot the whole database (was backend-only, silently excluding payments). - Document the PITR runbook, the arming sequence and the restore drill in deploy/README.md; record the measured cost/perf assessment.
39 lines
2.1 KiB
YAML
39 lines
2.1 KiB
YAML
---
|
|
# Service account the CI prod-deploy workflow uses to drive docker on the hosts.
|
|
# Membership in the docker group is root-equivalent (docker socket access), which
|
|
# is all the deploy workflow needs; the account is deliberately not given sudo.
|
|
deploy_user: deploy
|
|
|
|
# Public half of the dedicated CI deploy SSH key, read from the controller at run
|
|
# time. The private half is generated on the controller during provisioning and
|
|
# stored ONLY in the Gitea PROD_SSH_KEY secret; it is never committed. Override the
|
|
# path with -e deploy_ci_pubkey_path=/path/to/key.pub if the key lives elsewhere.
|
|
deploy_ci_pubkey_path: "{{ lookup('env', 'HOME') }}/.ssh/scrabble_ci_deploy_ed25519.pub"
|
|
deploy_ci_pubkey: "{{ lookup('file', deploy_ci_pubkey_path) }}"
|
|
|
|
# Base directory the deploy workflow rsyncs compose files, config, certs and dumps
|
|
# into. Owned by deploy_user so the workflow needs no elevation.
|
|
scrabble_base_dir: /opt/scrabble
|
|
|
|
# Docker daemon json-file log rotation, mirroring the compose x-logging anchor so
|
|
# the host's own containers (and any ad-hoc runs) rotate identically.
|
|
docker_log_max_size: "10m"
|
|
docker_log_max_file: "3"
|
|
|
|
# Swap file as an OOM cushion. The per-container memory caps (docker-compose.prod.yml)
|
|
# sum to more than the tight main host's RAM (2 vCPU / 1.9 GiB), so a simultaneous
|
|
# spike could otherwise hit the kernel OOM-killer and it might pick postgres. A small
|
|
# swap absorbs the overshoot; swappiness stays low so swap is a cushion, not a hot
|
|
# path (a slow service beats a killed one). Set swap_size to "0" to skip provisioning.
|
|
swap_size: "1G"
|
|
swap_swappiness: 10
|
|
|
|
# Point-in-time recovery (pgBackRest). The base-backup systemd timer on the main host is
|
|
# gated by pitr_enabled so provisioning stays inert until archiving is armed — the timer
|
|
# would fail nightly if it ran before the S3 repository + stanza exist. Arm it as part of
|
|
# the PITR rollout (deploy/README.md): flip it on with `-e pitr_enabled=true` (or a main
|
|
# host_var) once the stanza is created. Only the `main` role reads these.
|
|
pitr_enabled: false
|
|
# systemd OnCalendar for the daily full base backup — a low-traffic hour on the main host.
|
|
pitr_backup_oncalendar: "*-*-* 04:00:00"
|