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.
88 lines
4.1 KiB
Bash
Executable File
88 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Render the prod main-host runtime env.sh from the workflow job environment.
|
|
#
|
|
# Sourced identically by prod-deploy (deploy-main) and prod-rollback
|
|
# (rollback-main) so the two paths cannot drift: a rollback recreates the
|
|
# containers, so it must re-render the SAME runtime env a full deploy does —
|
|
# otherwise transactional email, VK login and Grafana alerts silently go dark
|
|
# after a rollback until the next full deploy.
|
|
#
|
|
# Usage: APP_VERSION=<tag> bash deploy/write-prod-env.sh <out-path>
|
|
#
|
|
# Every other value comes from the caller's environment (the job `env:` block,
|
|
# vars.* / secrets.*). Mirrors the compose interpolation contract in
|
|
# deploy/.env.example; keep in sync with deploy/docker-compose{,.prod}.yml.
|
|
out="${1:?usage: write-prod-env.sh <out-path>}"
|
|
|
|
# Derive the public URLs from the one canonical origin instead of storing each
|
|
# under its own variable. The path suffixes are structural (SPA routes / the
|
|
# Caddy /_gm sub-path), identical on every contour.
|
|
base="${PUBLIC_BASE_URL%/}"
|
|
TELEGRAM_MINIAPP_URL="$base/telegram/"
|
|
GRAFANA_ROOT_URL="$base/_gm/grafana/"
|
|
VITE_VK_ID_REDIRECT_URL="$base/app/"
|
|
|
|
# Grafana needs a BARE from-address (it rejects the "Name" <addr> form the backend
|
|
# go-mail accepts, and validates it even when SMTP is disabled — a bad value
|
|
# crash-loops Grafana). Split the display-format SERVICE From into address + name;
|
|
# the backend keeps the full form.
|
|
svc_from="${SMTP_RELAY_SERVICE_FROM:-}"
|
|
GRAFANA_SMTP_FROM_NAME=''
|
|
case "$svc_from" in
|
|
*"<"*">"*)
|
|
GRAFANA_SMTP_FROM_ADDRESS="$(printf '%s' "$svc_from" | sed -E 's/.*<([^>]+)>.*/\1/')"
|
|
GRAFANA_SMTP_FROM_NAME="$(printf '%s' "$svc_from" | sed -E 's/[[:space:]]*<[^>]*>.*$//; s/^"//; s/"$//')" ;;
|
|
*) GRAFANA_SMTP_FROM_ADDRESS="$svc_from" ;;
|
|
esac
|
|
|
|
cat > "$out" <<EOF
|
|
export REGISTRY='$REGISTRY'
|
|
export SCRABBLE_CONFIG_DIR='/opt/scrabble'
|
|
export POSTGRES_DB='${POSTGRES_DB:-scrabble}'
|
|
export POSTGRES_USER='${POSTGRES_USER:-scrabble}'
|
|
export POSTGRES_PASSWORD='$POSTGRES_PASSWORD'
|
|
export GM_BASICAUTH_USER='${GM_BASICAUTH_USER:-gm}'
|
|
export GM_BASICAUTH_HASH='$GM_BASICAUTH_HASH'
|
|
export GRAFANA_ADMIN_PASSWORD='$GRAFANA_ADMIN_PASSWORD'
|
|
export GRAFANA_ROOT_URL='$GRAFANA_ROOT_URL'
|
|
export CADDY_SITE_ADDRESS='$CADDY_SITE_ADDRESS'
|
|
export LOG_LEVEL='${LOG_LEVEL:-info}'
|
|
export DICT_VERSION='$DICT_VERSION'
|
|
export APP_VERSION='$APP_VERSION'
|
|
export TELEGRAM_BOT_TOKEN='$TELEGRAM_BOT_TOKEN'
|
|
export TELEGRAM_MINIAPP_URL='$TELEGRAM_MINIAPP_URL'
|
|
export GATEWAY_VK_APP_SECRET='$GATEWAY_VK_APP_SECRET'
|
|
export VITE_VK_APP_ID='$VITE_VK_APP_ID'
|
|
export VITE_VK_ID_REDIRECT_URL='$VITE_VK_ID_REDIRECT_URL'
|
|
export GATEWAY_VK_ID_CLIENT_SECRET='$GATEWAY_VK_ID_CLIENT_SECRET'
|
|
export EXPORT_SIGN_KEY='$EXPORT_SIGN_KEY'
|
|
export SMTP_RELAY_HOST='$SMTP_RELAY_HOST'
|
|
export SMTP_RELAY_PORT='$SMTP_RELAY_PORT'
|
|
export SMTP_RELAY_TLS='$SMTP_RELAY_TLS'
|
|
export SMTP_RELAY_USER='$SMTP_RELAY_USER'
|
|
export SMTP_RELAY_PASS='$SMTP_RELAY_PASS'
|
|
export SMTP_RELAY_FROM='$SMTP_RELAY_FROM'
|
|
export SMTP_RELAY_ADMIN_FROM='$SMTP_RELAY_ADMIN_FROM'
|
|
export ADMIN_EMAIL='$ADMIN_EMAIL'
|
|
export SMTP_RELAY_SERVICE_FROM='$SMTP_RELAY_SERVICE_FROM'
|
|
export GRAFANA_SMTP_FROM_ADDRESS='$GRAFANA_SMTP_FROM_ADDRESS'
|
|
export GRAFANA_SMTP_FROM_NAME='$GRAFANA_SMTP_FROM_NAME'
|
|
export SERVICE_EMAIL='$SERVICE_EMAIL'
|
|
export GRAFANA_SMTP_PORT='$GRAFANA_SMTP_PORT'
|
|
export GF_SMTP_ENABLED='$GF_SMTP_ENABLED'
|
|
export PUBLIC_BASE_URL='$PUBLIC_BASE_URL'
|
|
export GATEWAY_HONEYTOKEN='$GATEWAY_HONEYTOKEN'
|
|
export GATEWAY_ABUSE_BAN_ENABLED='true'
|
|
# Continuous WAL archiving (pgBackRest -> S3) for point-in-time recovery. The artifact
|
|
# ships disarmed: PGBACKREST_ARCHIVE_MODE defaults off, so archiving stays inert until the
|
|
# operator sets the S3 repository values + secrets, creates the stanza and flips the switch
|
|
# on (deploy/README.md). Empty repository values are harmless while the mode is off.
|
|
export PGBACKREST_ARCHIVE_MODE='${PGBACKREST_ARCHIVE_MODE:-off}'
|
|
export PGBACKREST_S3_ENDPOINT='$PGBACKREST_S3_ENDPOINT'
|
|
export PGBACKREST_S3_BUCKET='$PGBACKREST_S3_BUCKET'
|
|
export PGBACKREST_S3_REGION='$PGBACKREST_S3_REGION'
|
|
export PGBACKREST_S3_KEY='$PGBACKREST_S3_KEY'
|
|
export PGBACKREST_S3_KEY_SECRET='$PGBACKREST_S3_KEY_SECRET'
|
|
export PGBACKREST_CIPHER_PASS='$PGBACKREST_CIPHER_PASS'
|
|
EOF
|