No description
Find a file
2026-06-24 16:55:26 +03:00
deploy Add metabase.waldemarleo.com to Caddyfile 2026-06-24 16:55:26 +03:00
.gitignore Document deploy prerequisites and token helper 2026-06-11 16:26:39 +03:00
README.md Revert "deploy: add apps2 backup export scripts" 2026-06-12 16:18:04 +03:00

apps-infra

Shared deployment repository for backend services on one VPS.

Target layout on server:

/srv/apps/
  analytics-api/
  ruwords-api/
  apps-infra/

apps-infra/deploy/docker-compose.yml builds services from sibling repos (../analytics-api, ../ruwords-api) and runs them behind one Caddy instance.

Use deploy/Caddyfile with the shared compose stack. deploy/Caddyfile.base is a legacy reference from the pre-split deployment and does not match the current service names.

Services

  • Universal analytics API (prod + staging)
  • RuWords API (prod + staging)
  • Shared Postgres instance (separate databases)
  • Shared Caddy (separate domains)

Gateway policy (Caddy)

  • Analytics domains expose only:
    • GET /healthz
    • POST /v1/events
  • RuWords domains expose only:
    • GET /healthz
    • GET /content/v1/* (requires Authorization: Bearer <CONTENT_READ_TOKEN_...> at gateway)
    • /v1/* leaderboard API routes (auth handled by ruwords-api service token)
  • Other paths return 404 (or 401 for missing/invalid content token).

Production prerequisites

Create deploy/.env from deploy/.env.example and keep it uncommitted. It contains domains, database credentials, token peppers, content read tokens, and leaderboard bearer tokens.

DNS for all configured domains must point at the target VPS before starting Caddy, otherwise Let's Encrypt certificate issuance will fail.

RuWords content paths must use the release layout expected by ruwords-api:

/srv/content/ruwords/prod/
  current -> releases/<release_id>
  releases/<release_id>/manifest.json
  releases/<release_id>/packs/<pack_id>/<version>.json

/srv/content/ruwords/staging/
  current -> releases/<release_id>
  releases/<release_id>/manifest.json
  releases/<release_id>/packs/<pack_id>/<version>.json

Analytics migrations create the apps, ingest_tokens, and events_raw tables, but do not create app rows or ingest tokens. Seed production/staging apps and token hashes after migrations. The service checks bearer tokens by storing sha256(TOKEN_PEPPER + ':' + token) in ingest_tokens.token_hash.

Backups must cover deploy/.env, the repository revisions, RuWords content releases, Caddy data/config volumes, and Postgres logical dumps. Do not rely on copying the live Postgres volume as the only database backup.

Analytics Ingest Tokens

After analytics migrations have run, seed one token per app/environment. Store the plaintext token in the app's secret storage or release configuration; the database stores only the derived hash.

export ANALYTICS_INGEST_TOKEN='replace_with_token_from_password_manager'

deploy/scripts/analytics-token.sh seed prod ruwords-ios "RuWords iOS"
deploy/scripts/analytics-token.sh seed staging ruwords-ios-staging "RuWords iOS Staging"

Revoke a token by providing the same plaintext token:

export ANALYTICS_INGEST_TOKEN='token_to_revoke'

deploy/scripts/analytics-token.sh revoke prod

Quick start

cp deploy/.env.example deploy/.env
# edit domains, secrets, and mount paths

docker compose -f deploy/docker-compose.yml up -d --build

# run DB migrations

docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_analytics_prod
docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_analytics_staging
docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_ruwords_prod
docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_ruwords_staging