e3c2e80a0a
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Failing after 24s
CI / ui (pull_request) Successful in 1m17s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped
The direct rail runs on НПД, where the provider neither files with the tax service nor issues a receipt — so nobody was doing it. This registers each rouble purchase, annuls its receipt on a refund, and hands the buyer the receipt by email. Two properties of the (unofficial) lknpd API shape the design. Registering an income takes no idempotency key, so an error does not mean nothing happened: the service name is frozen before the call and carries a marker from the tail of the order id, and after a failure the taxpayer's income list is searched for that exact name. Found means filed; not found halts the queue for a human, because declaring an income twice is as wrong as not declaring it. And faults are classified rather than logged: a token is renewed silently, a throttle backs off, an outage retries, but three unfixable rejections take the rail out of service — a changed format must not become thousands of requests overnight. The console button and the worker share one RunBatch. Automatic mode is armed from the console, not from configuration, so the operator can watch a run go through by hand first. A daily watchdog runs whether or not it is armed, since the case it exists for is the export being off. An idle queue issues no call at all — not even an authentication. No payment path changed: the purchase letter rides the existing payment-event outbox on its own cursor, the receipt and annulment letters ride the export row. Decisions D53-D60.
105 lines
5.2 KiB
Bash
Executable File
105 lines
5.2 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 GATEWAY_MIN_CLIENT_VERSION='$GATEWAY_MIN_CLIENT_VERSION'
|
|
export GATEWAY_RECOMMENDED_CLIENT_VERSION='$GATEWAY_RECOMMENDED_CLIENT_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 YOOKASSA_WEB_SHOP_ID='$YOOKASSA_WEB_SHOP_ID'
|
|
export YOOKASSA_WEB_SECRET_KEY='$YOOKASSA_WEB_SECRET_KEY'
|
|
export YOOKASSA_WEB_TEST='$YOOKASSA_WEB_TEST'
|
|
export YOOKASSA_ANDROID_SHOP_ID='$YOOKASSA_ANDROID_SHOP_ID'
|
|
export YOOKASSA_ANDROID_SECRET_KEY='$YOOKASSA_ANDROID_SECRET_KEY'
|
|
export YOOKASSA_ANDROID_TEST='$YOOKASSA_ANDROID_TEST'
|
|
export YOOKASSA_VAT_CODE='${YOOKASSA_VAT_CODE:-1}'
|
|
export MYNALOG_KEY='$MYNALOG_KEY'
|
|
export MYNALOG_TZ='${MYNALOG_TZ:-Europe/Moscow}'
|
|
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'
|
|
# Community IP blocklist (Spamhaus DROP): opt-in — the operator enables it and sets the feed URL +
|
|
# allowlist via PROD_GATEWAY_BLOCKLIST_* vars once the feed is verified. Unset ⇒ off (safe).
|
|
export GATEWAY_BLOCKLIST_ENABLED='${GATEWAY_BLOCKLIST_ENABLED:-false}'
|
|
export GATEWAY_BLOCKLIST_URL='$GATEWAY_BLOCKLIST_URL'
|
|
export GATEWAY_BLOCKLIST_ALLOW='$GATEWAY_BLOCKLIST_ALLOW'
|
|
# 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_PORT='${PGBACKREST_S3_PORT:-443}'
|
|
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
|