feat(infra): GlitchTip self-hosted error tracking (#204 devops) #205

Merged
lekss361 merged 2 commits from feat/204-glitchtip-infra into main 2026-05-16 15:08:50 +00:00
Owner

Summary

  • docker-compose.prod.yml: add glitchtip-web + glitchtip-worker services; both share existing postgres (new DB glitchtip) and redis (DB index 2); mem_limit 256m each
  • Caddyfile: add errors.gendsgn.ru block — auto-TLS, reverse_proxy to glitchtip-web:8080, gzip/zstd, access log
  • .env.example: document GLITCHTIP_DB_PASS, GLITCHTIP_SECRET, GLITCHTIP_DSN, NEXT_PUBLIC_GLITCHTIP_DSN
  • scripts/bootstrap_glitchtip.sh: idempotent one-shot script — creates DB/role, pulls images, starts containers, waits for healthcheck, prints next steps (superuser, projects, DSNs)

No changes to deploy.ymldocker compose up -d picks up new services automatically.

Env variable contract (cross-PR)

Variable Set where Consumer
GLITCHTIP_DB_PASS VPS .env compose
GLITCHTIP_SECRET VPS .env compose
GLITCHTIP_DSN backend/.env (after UI setup) backend SDK
NEXT_PUBLIC_GLITCHTIP_DSN backend/.env or .env (after UI setup) frontend SDK

Test plan

  • docker compose -f docker-compose.prod.yml config validates without errors
  • Caddy validate passes (checked locally)
  • bash -n scripts/bootstrap_glitchtip.sh — syntax OK (checked)
  • After merge: run bash scripts/bootstrap_glitchtip.sh on VPS, verify https://errors.gendsgn.ru is reachable
  • Create superuser + org + projects, confirm DSNs readable
  • Backend and frontend workers DSNs trigger test events visible in GlitchTip

Closes #204

## Summary - `docker-compose.prod.yml`: add `glitchtip-web` + `glitchtip-worker` services; both share existing `postgres` (new DB `glitchtip`) and `redis` (DB index 2); mem_limit 256m each - `Caddyfile`: add `errors.gendsgn.ru` block — auto-TLS, reverse_proxy to `glitchtip-web:8080`, gzip/zstd, access log - `.env.example`: document `GLITCHTIP_DB_PASS`, `GLITCHTIP_SECRET`, `GLITCHTIP_DSN`, `NEXT_PUBLIC_GLITCHTIP_DSN` - `scripts/bootstrap_glitchtip.sh`: idempotent one-shot script — creates DB/role, pulls images, starts containers, waits for healthcheck, prints next steps (superuser, projects, DSNs) No changes to `deploy.yml` — `docker compose up -d` picks up new services automatically. ## Env variable contract (cross-PR) | Variable | Set where | Consumer | |---|---|---| | `GLITCHTIP_DB_PASS` | VPS `.env` | compose | | `GLITCHTIP_SECRET` | VPS `.env` | compose | | `GLITCHTIP_DSN` | `backend/.env` (after UI setup) | backend SDK | | `NEXT_PUBLIC_GLITCHTIP_DSN` | `backend/.env` or `.env` (after UI setup) | frontend SDK | ## Test plan - [ ] `docker compose -f docker-compose.prod.yml config` validates without errors - [ ] Caddy validate passes (checked locally) - [ ] `bash -n scripts/bootstrap_glitchtip.sh` — syntax OK (checked) - [ ] After merge: run `bash scripts/bootstrap_glitchtip.sh` on VPS, verify https://errors.gendsgn.ru is reachable - [ ] Create superuser + org + projects, confirm DSNs readable - [ ] Backend and frontend workers DSNs trigger test events visible in GlitchTip Closes #204
lekss361 added 1 commit 2026-05-16 10:38:55 +00:00
Adds glitchtip-web + glitchtip-worker to docker-compose.prod.yml using
the shared postgres/redis services. Caddy proxies errors.gendsgn.ru to
glitchtip-web:8080 with auto-TLS. Bootstrap script handles DB creation
and first-run setup idempotently.

Env contract: GLITCHTIP_DB_PASS, GLITCHTIP_SECRET in .env;
GLITCHTIP_DSN and NEXT_PUBLIC_GLITCHTIP_DSN populated after UI setup.
Author
Owner

Deep Code Review — verdict

Summary

  • Status: 🟠 HIGH — fix before merge (race + SQLi + mem) / 🟡 medium nits
  • Files reviewed: 4 (P2: 4 — docker-compose.prod.yml, Caddyfile, scripts/bootstrap_glitchtip.sh, .env.example)
  • Lines: +167 / -0
  • Head SHA: a79ee83 · base 256909d
  • Auto-merge: BLOCKED (scope: docker-compose*.yml + Caddyfile)

🔴 Critical

None.

🟠 High (fix before merge)

  • [scripts/bootstrap_glitchtip.sh:55] SQL injection / breakage via password interpolation
    Password substituted into heredoc:

    CREATE ROLE glitchtip LOGIN PASSWORD '$GLITCHTIP_DB_PASS';
    

    Any ' in GLITCHTIP_DB_PASS → SQL syntax error (best case) или injection (worst). Прод-пароль из random generator скорее всего безопасен, но контракт хрупкий.
    Fix: pass via psql variable +format():

    $COMPOSE exec -T -e PGPASSWORD_NEW="$GLITCHTIP_DB_PASS" postgres \
      psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v gt_pass="$GLITCHTIP_DB_PASS" \
      -v ON_ERROR_STOP=on <<'SQL'
    DO $$
    BEGIN
        IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='glitchtip') THEN
            EXECUTE format('CREATE ROLE glitchtip LOGIN PASSWORD %L', :'gt_pass');
        END IF;
    END$$;
    SQL
    
  • [docker-compose.prod.yml:124-160 + .forgejo/workflows/deploy.yml:194] Crash-loop on first deploy after merge
    Deploy workflow runs docker compose up -d без service-фильтра → новые glitchtip-web/glitchtip-worker стартуют немедленно, но:

    • GLITCHTIP_DB_PASS / GLITCHTIP_SECRET ещё не в VPS /opt/gendesign/.env
    • DB glitchtip + role не созданы
      → containers будут падать в restart-loop с restart: always пока человек не сделает: (1) добавить env в .env, (2) запустить bootstrap script.
      PR body утверждает "compose auto-picks up new services" — это правда, но в этом контексте это бага, не фича.
      Fix (выбрать один):
    • (a) Add profiles: ["glitchtip"] к новым services → не стартуют автоматом. Bootstrap script затем делает docker compose --profile glitchtip up -d.
    • (b) Document в PR body: "MANUAL STEP перед merge — добавить GLITCHTIP_* в VPS .env и запустить bootstrap до merge feature ветки в main".
    • (c) Bootstrap script сначала создаёт DB → потом up -d, но это требует уже залитого compose.yml → catch-22 без profiles.
      Рекомендую (a) — самый чистый.
  • [docker-compose.prod.yml:139-140,158] mem_limit: 256m слишком мал для GlitchTip Django+Gunicorn+Celery
    Upstream рекомендует min 512m для web и 256-512m для worker. С CELERY_WORKER_AUTOSCALE: "1,3" (3 worker процесса) под нагрузкой → OOM-kill, потеря событий именно тогда, когда tracker нужен (incident). Все остальные services в compose без limit — это новый паттерн только для glitchtip.
    Fix: mem_limit: 512m для web, mem_limit: 384m для worker, ИЛИ убрать limit вовсе (consistent с остальными).

🟡 Medium

  • [docker-compose.prod.yml:124,141] image: glitchtip/glitchtip:latest — pin к major (:v4) или конкретной версии. GlitchTip может выкатить breaking schema migration; :latest + auto-pull в deploy = surprise downtime.

  • [docker-compose.prod.yml:124-141] нет healthcheck

    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:8080/healthz/ || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 60s
    

    Без него Caddy в той же default сети начнёт проксировать сразу → 502 для ~30-60s после рестарта.

  • [docker-compose.prod.yml:138] CELERY_WORKER_AUTOSCALE: "1,3" на glitchtip-web — это var для Celery worker, на web (gunicorn) бессмыслен. Cosmetic, но указывает на copy-paste config. Удалить из glitchtip-web env, оставить только на glitchtip-worker.

  • Нет persistent volume для GlitchTip media/uploads. Если используется MEDIA_ROOT (event attachments, profile avatars), теряются при --force-recreate. Если default GlitchTip storage = postgres only — OK, но это нужно подтвердить и закомментировать в compose.

  • [scripts/bootstrap_glitchtip.sh:46] pg_isready ждёт только 30 секунд (30 × 2s). Если postgres стартует медленно при первом запуске PostGIS init scripts — таймаут. Increase to 60 итераций или 5s интервал.

🟢 Low / nits

  • [Caddyfile:47-57] Нет explicit security headers (X-Frame-Options, Strict-Transport-Security, Content-Security-Policy). GlitchTip upstream шлёт свои headers, но Caddy auto-TLS не добавляет HSTS по дефолту. Можно header Strict-Transport-Security "max-age=31536000;" в block. Не блокирующее.

  • [scripts/bootstrap_glitchtip.sh:32] source .env — fragile если .env содержит values с пробелами / # / " без quoting. На текущей .env работает, но docker-compose-style .env parser строже чем bash source. Edge case.

  • [bootstrap_glitchtip.sh:101-103] up -d --force-recreate --no-deps backend / worker / beat без pull сначала — recreate возьмёт existing image, ok, но если image обновился pre-existing — нужен явный pull. Cosmetic.


Cross-file impact analysis

  • PR #207 (backend GlitchTip SDK) уже merged — backend/app/main.py инициализирует sentry_sdk.init(dsn=GLITCHTIP_DSN) при non-empty DSN. Без infra (этот PR) backend просто no-op'нет → safe.
  • PR #204 frontend уже merged — @sentry/nextjs no-op при отсутствии NEXT_PUBLIC_GLITCHTIP_DSN. Safe.
  • Vault decisions: decisions/decision-glitchtip-frontend-sentry.md + fixes/fixes-MOC.md уже содержат запись о backend SDK migration. После merge этого PR нужна новая запись в decisions/decision-glitchtip-infra.md с финальным VPS bootstrap runbook → runbooks/glitchtip-bootstrap.md (per CLAUDE.md rule #6).
  • .forgejo/workflows/deploy.yml trigger paths включают docker-compose.prod.yml + Caddyfile → этот PR триггерит full deploy цикл (build всех images + SSH deploy + caddy reload). См. 🟠 #2 — race condition.

Vault cross-check

  • decisions/decision-glitchtip-frontend-sentry.md — frontend контракт consistent (NEXT_PUBLIC_GLITCHTIP_DSN)
  • fixes/fixes-MOC.md GlitchTip backend section (PR #207) — env var naming consistent (GLITCHTIP_DSN, GLITCHTIP_TRACES_SAMPLE_RATE)
  • ⚠️ Отсутствует decisions/decision-glitchtip-infra.md / runbooks/glitchtip-bootstrap.md — создать в той же сессии что merge PR

Conventions check

  • .env.example — пустые placeholders, no secret leakage
  • Bootstrap script: set -euo pipefail, :? validation для required vars, idempotent SQL via \gexec + DO $$ IF NOT EXISTS
  • Redis DB 2 свободен (backend redis_url default /0, нигде нет /2)
  • Caddy auto-TLS pattern consistent с obsidian.gendsgn.ru block
  • Postgres-sharing pattern OK: новая DB + dedicated role с password (не reuse POSTGRES_USER)
  • ⚠️ :latest tags inconsistent с остальным проектом (${IMAGE_TAG} для GHCR images) — но для внешнего vendor image приемлемо если pin не критичен; рекомендую :v4 минимум

Performance / blast radius

  • Risk: Medium (новый сервис, изолированная DB/role; но deploy automation может уронить main stack если glitchtip crash-loop удерживает compose ресурсы / postgres connections)
  • Reversibility: Easy — revert PR + manual docker compose -p gendesign rm -f glitchtip-web glitchtip-worker + DROP DATABASE glitchtip; DROP ROLE glitchtip;
  • Recommended merge window: business hours, готовность сделать bootstrap immediately после merge (т.к. без profiles services начнут crash-loop пока не пройдёт setup)

Positive observations

  • Bootstrap script отлично написан: idempotent (IF NOT EXISTS, \gexec), validation (:?), set -euo pipefail, чёткие next-steps
  • ENABLE_USER_REGISTRATION: "false" + ENABLE_ORGANIZATION_CREATION: "false" — закрыто от random signup, правильный production default
  • EMAIL_URL: dummy:// — explicit, не падёт при first start без SMTP
  • Redis DB index 2 — clean separation от Celery broker
  • Postgres role с собственным password, не shared с POSTGRES_USER
  • GLITCHTIP_DOMAIN: https://errors.gendsgn.ru корректно (нужен GlitchTip для генерации absolute URLs в emails / DSNs)
  1. MUST: Fix 🟠 #1 (SQL injection vector в bootstrap), 🟠 #2 (добавить profiles: ["glitchtip"] чтобы не стартовали авто в deploy), 🟠 #3 (mem_limit 256m → 512m web / убрать)
  2. SHOULD: 🟡 — pin :latest:v4; add healthcheck; remove CELERY_WORKER_AUTOSCALE с web container
  3. Post-merge: создать decisions/decision-glitchtip-infra.md + runbooks/glitchtip-bootstrap.md в vault
  4. Pre-merge runbook:
    • (a) Если выбран profiles approach: merge → deploy безопасен → потом VPS bootstrap
    • (b) Если оставить как есть: SSH на VPS pre-merge → добавить GLITCHTIP_DB_PASS + GLITCHTIP_SECRET в .env → run bootstrap → потом merge

Final action: НЕ мержу (scope blocked: docker-compose.prod.yml + Caddyfile). Требуется human merge после fix-ов выше + manual bootstrap план.

## Deep Code Review — verdict ### Summary - **Status**: 🟠 HIGH — fix before merge (race + SQLi + mem) / 🟡 medium nits - **Files reviewed**: 4 (P2: 4 — `docker-compose.prod.yml`, `Caddyfile`, `scripts/bootstrap_glitchtip.sh`, `.env.example`) - **Lines**: +167 / -0 - **Head SHA**: `a79ee83` · base `256909d` - **Auto-merge**: ❌ BLOCKED (scope: `docker-compose*.yml` + `Caddyfile`) --- ### 🔴 Critical _None._ ### 🟠 High (fix before merge) - **[`scripts/bootstrap_glitchtip.sh:55`] SQL injection / breakage via password interpolation** Password substituted into heredoc: ```bash CREATE ROLE glitchtip LOGIN PASSWORD '$GLITCHTIP_DB_PASS'; ``` Any `'` in `GLITCHTIP_DB_PASS` → SQL syntax error (best case) или injection (worst). Прод-пароль из random generator скорее всего безопасен, но контракт хрупкий. **Fix**: pass via psql variable +`format()`: ```bash $COMPOSE exec -T -e PGPASSWORD_NEW="$GLITCHTIP_DB_PASS" postgres \ psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v gt_pass="$GLITCHTIP_DB_PASS" \ -v ON_ERROR_STOP=on <<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='glitchtip') THEN EXECUTE format('CREATE ROLE glitchtip LOGIN PASSWORD %L', :'gt_pass'); END IF; END$$; SQL ``` - **[`docker-compose.prod.yml:124-160` + `.forgejo/workflows/deploy.yml:194`] Crash-loop on first deploy after merge** Deploy workflow runs `docker compose up -d` без service-фильтра → новые `glitchtip-web`/`glitchtip-worker` стартуют **немедленно**, но: - `GLITCHTIP_DB_PASS` / `GLITCHTIP_SECRET` ещё не в VPS `/opt/gendesign/.env` - DB `glitchtip` + role не созданы → containers будут падать в restart-loop с `restart: always` пока человек не сделает: (1) добавить env в `.env`, (2) запустить bootstrap script. PR body утверждает "compose auto-picks up new services" — это правда, но **в этом контексте это бага, не фича**. **Fix** (выбрать один): - (a) Add `profiles: ["glitchtip"]` к новым services → не стартуют автоматом. Bootstrap script затем делает `docker compose --profile glitchtip up -d`. - (b) Document в PR body: "MANUAL STEP перед merge — добавить GLITCHTIP_* в VPS `.env` и запустить bootstrap до merge feature ветки в main". - (c) Bootstrap script сначала создаёт DB → потом `up -d`, но это требует уже залитого compose.yml → catch-22 без profiles. Рекомендую (a) — самый чистый. - **[`docker-compose.prod.yml:139-140,158`] `mem_limit: 256m` слишком мал для GlitchTip Django+Gunicorn+Celery** Upstream рекомендует min 512m для web и 256-512m для worker. С `CELERY_WORKER_AUTOSCALE: "1,3"` (3 worker процесса) под нагрузкой → OOM-kill, потеря событий именно тогда, когда tracker нужен (incident). Все остальные services в compose без limit — это новый паттерн только для glitchtip. **Fix**: `mem_limit: 512m` для web, `mem_limit: 384m` для worker, ИЛИ убрать limit вовсе (consistent с остальными). ### 🟡 Medium - **[`docker-compose.prod.yml:124,141`] `image: glitchtip/glitchtip:latest`** — pin к major (`:v4`) или конкретной версии. GlitchTip может выкатить breaking schema migration; `:latest` + auto-pull в deploy = surprise downtime. - **[`docker-compose.prod.yml:124-141`] нет healthcheck** ```yaml healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8080/healthz/ || exit 1"] interval: 30s timeout: 5s retries: 5 start_period: 60s ``` Без него Caddy в той же `default` сети начнёт проксировать сразу → 502 для ~30-60s после рестарта. - **[`docker-compose.prod.yml:138`] `CELERY_WORKER_AUTOSCALE: "1,3"` на `glitchtip-web`** — это var для Celery worker, на web (gunicorn) бессмыслен. Cosmetic, но указывает на copy-paste config. Удалить из `glitchtip-web` env, оставить только на `glitchtip-worker`. - **Нет persistent volume для GlitchTip media/uploads.** Если используется `MEDIA_ROOT` (event attachments, profile avatars), теряются при `--force-recreate`. Если default GlitchTip storage = postgres only — OK, но это нужно подтвердить и закомментировать в compose. - **[`scripts/bootstrap_glitchtip.sh:46`] `pg_isready` ждёт **только 30 секунд** (30 × 2s)**. Если postgres стартует медленно при первом запуске PostGIS init scripts — таймаут. Increase to 60 итераций или 5s интервал. ### 🟢 Low / nits - **[`Caddyfile:47-57`]** Нет explicit security headers (`X-Frame-Options`, `Strict-Transport-Security`, `Content-Security-Policy`). GlitchTip upstream шлёт свои headers, но Caddy auto-TLS не добавляет HSTS по дефолту. Можно `header Strict-Transport-Security "max-age=31536000;"` в block. Не блокирующее. - **[`scripts/bootstrap_glitchtip.sh:32`] `source .env`** — fragile если `.env` содержит values с пробелами / `#` / `"` без quoting. На текущей `.env` работает, но docker-compose-style `.env` parser строже чем bash `source`. Edge case. - **[`bootstrap_glitchtip.sh:101-103`]** `up -d --force-recreate --no-deps backend / worker / beat` без `pull` сначала — recreate возьмёт existing image, ok, но если image обновился pre-existing — нужен явный `pull`. Cosmetic. --- ### Cross-file impact analysis - **PR #207 (backend GlitchTip SDK)** уже merged — `backend/app/main.py` инициализирует `sentry_sdk.init(dsn=GLITCHTIP_DSN)` при non-empty DSN. Без infra (этот PR) backend просто no-op'нет → safe. - **PR #204 frontend** уже merged — `@sentry/nextjs` no-op при отсутствии `NEXT_PUBLIC_GLITCHTIP_DSN`. Safe. - **Vault decisions**: `decisions/decision-glitchtip-frontend-sentry.md` + `fixes/fixes-MOC.md` уже содержат запись о backend SDK migration. После merge этого PR нужна **новая** запись в `decisions/decision-glitchtip-infra.md` с финальным VPS bootstrap runbook → `runbooks/glitchtip-bootstrap.md` (per CLAUDE.md rule #6). - **`.forgejo/workflows/deploy.yml`** trigger paths включают `docker-compose.prod.yml` + `Caddyfile` → этот PR **триггерит** full deploy цикл (build всех images + SSH deploy + caddy reload). См. 🟠 #2 — race condition. ### Vault cross-check - ✅ `decisions/decision-glitchtip-frontend-sentry.md` — frontend контракт consistent (`NEXT_PUBLIC_GLITCHTIP_DSN`) - ✅ `fixes/fixes-MOC.md` GlitchTip backend section (PR #207) — env var naming consistent (`GLITCHTIP_DSN`, `GLITCHTIP_TRACES_SAMPLE_RATE`) - ⚠️ Отсутствует `decisions/decision-glitchtip-infra.md` / `runbooks/glitchtip-bootstrap.md` — создать в той же сессии что merge PR ### Conventions check - ✅ `.env.example` — пустые placeholders, no secret leakage - ✅ Bootstrap script: `set -euo pipefail`, `:?` validation для required vars, idempotent SQL via `\gexec` + `DO $$ IF NOT EXISTS` - ✅ Redis DB 2 свободен (backend `redis_url` default `/0`, нигде нет `/2`) - ✅ Caddy auto-TLS pattern consistent с `obsidian.gendsgn.ru` block - ✅ Postgres-sharing pattern OK: новая DB + dedicated role с password (не reuse `POSTGRES_USER`) - ⚠️ `:latest` tags inconsistent с остальным проектом (`${IMAGE_TAG}` для GHCR images) — но для внешнего vendor image приемлемо если pin не критичен; рекомендую `:v4` минимум ### Performance / blast radius - **Risk**: Medium (новый сервис, изолированная DB/role; но deploy automation может уронить main stack если glitchtip crash-loop удерживает compose ресурсы / postgres connections) - **Reversibility**: Easy — revert PR + manual `docker compose -p gendesign rm -f glitchtip-web glitchtip-worker` + `DROP DATABASE glitchtip; DROP ROLE glitchtip;` - **Recommended merge window**: business hours, готовность сделать bootstrap immediately после merge (т.к. без profiles services начнут crash-loop пока не пройдёт setup) ### Positive observations - ✅ Bootstrap script отлично написан: idempotent (`IF NOT EXISTS`, `\gexec`), validation (`:?`), `set -euo pipefail`, чёткие next-steps - ✅ `ENABLE_USER_REGISTRATION: "false"` + `ENABLE_ORGANIZATION_CREATION: "false"` — закрыто от random signup, правильный production default - ✅ `EMAIL_URL: dummy://` — explicit, не падёт при first start без SMTP - ✅ Redis DB index 2 — clean separation от Celery broker - ✅ Postgres role с собственным password, не shared с `POSTGRES_USER` - ✅ `GLITCHTIP_DOMAIN: https://errors.gendsgn.ru` корректно (нужен GlitchTip для генерации absolute URLs в emails / DSNs) ### Recommended next steps 1. **MUST**: Fix 🟠 #1 (SQL injection vector в bootstrap), 🟠 #2 (добавить `profiles: ["glitchtip"]` чтобы не стартовали авто в deploy), 🟠 #3 (mem_limit 256m → 512m web / убрать) 2. **SHOULD**: 🟡 — pin `:latest` → `:v4`; add healthcheck; remove `CELERY_WORKER_AUTOSCALE` с web container 3. **Post-merge**: создать `decisions/decision-glitchtip-infra.md` + `runbooks/glitchtip-bootstrap.md` в vault 4. **Pre-merge runbook**: - (a) Если выбран `profiles` approach: merge → deploy безопасен → потом VPS bootstrap - (b) Если оставить как есть: SSH на VPS pre-merge → добавить `GLITCHTIP_DB_PASS` + `GLITCHTIP_SECRET` в `.env` → run bootstrap → потом merge --- **Final action**: НЕ мержу (scope blocked: `docker-compose.prod.yml` + `Caddyfile`). Требуется human merge после fix-ов выше + manual bootstrap план.
lekss361 added 1 commit 2026-05-16 15:01:59 +00:00
- Add profiles: ["glitchtip"] to glitchtip-web and glitchtip-worker so
  plain `compose up -d` does not start them before secrets/DB are ready
- Fix SQL injection vector: pass password via psql -v variable + format(%L)
  instead of bare shell interpolation in heredoc
- glitchtip-web mem_limit: 256m -> 512m (upstream minimum for Django+gunicorn)
- glitchtip-worker mem_limit: 256m -> 384m (handles CELERY_WORKER_AUTOSCALE)
- Move CELERY_WORKER_AUTOSCALE to worker only (no-op on gunicorn web service)
- Pin image glitchtip/glitchtip:latest -> 6.1.6
- Add healthcheck to glitchtip-web (wget /api/0/)
- bootstrap: use --profile glitchtip for pull/up; add COMPOSE_PROFILES note
- bootstrap: use wget (consistent with compose healthcheck) in wait loop
Author
Owner

Review fixes in ffaa363 — all HIGH and medium items addressed:

Closed:

  • [HIGH #1] SQL injection: password now passed via psql -v gt_pass=... + format(%L, :'gt_pass') — shell never interpolates into SQL string
  • [HIGH #2] Race condition: added profiles: ["glitchtip"] to both services; bootstrap uses --profile glitchtip for pull/up; added COMPOSE_PROFILES=glitchtip note for persistent activation in .env
  • [HIGH #3] mem_limit: glitchtip-web 256m → 512m, glitchtip-worker 256m → 384m
  • [medium #4] Image pinned: glitchtip/glitchtip:latestglitchtip/glitchtip:6.1.6 (current latest minor on Docker Hub)
  • [medium #5] healthcheck added to glitchtip-web: wget -q --spider http://localhost:8080/api/0/, 30s/5s/5r/60s start_period
  • [medium #6] CELERY_WORKER_AUTOSCALE moved from glitchtip-web to glitchtip-worker only

Deferred (low priority):

  • HSTS header in Caddyfile — separate PR
  • source .env fragility — out of scope for this infra PR
  • pull before --force-recreate — deploy.yml concern, separate improvement

Note on healthcheck tool: glitchtip image (Django/Alpine-based) includes wget, so using that consistently in both compose healthcheck and bootstrap wait loop.

Review fixes in ffaa363 — all HIGH and medium items addressed: **Closed:** - [HIGH #1] SQL injection: password now passed via psql `-v gt_pass=...` + `format(%L, :'gt_pass')` — shell never interpolates into SQL string - [HIGH #2] Race condition: added `profiles: ["glitchtip"]` to both services; bootstrap uses `--profile glitchtip` for pull/up; added `COMPOSE_PROFILES=glitchtip` note for persistent activation in `.env` - [HIGH #3] mem_limit: glitchtip-web 256m → 512m, glitchtip-worker 256m → 384m - [medium #4] Image pinned: `glitchtip/glitchtip:latest` → `glitchtip/glitchtip:6.1.6` (current latest minor on Docker Hub) - [medium #5] healthcheck added to glitchtip-web: `wget -q --spider http://localhost:8080/api/0/`, 30s/5s/5r/60s start_period - [medium #6] `CELERY_WORKER_AUTOSCALE` moved from glitchtip-web to glitchtip-worker only **Deferred (low priority):** - HSTS header in Caddyfile — separate PR - `source .env` fragility — out of scope for this infra PR - `pull` before `--force-recreate` — deploy.yml concern, separate improvement **Note on healthcheck tool:** glitchtip image (Django/Alpine-based) includes `wget`, so using that consistently in both compose healthcheck and bootstrap wait loop.
Author
Owner

Deep Code Review — verdict (re-review after fixup)

Summary

  • Status: APPROVE
  • Head SHA: ffaa363 (base e0055d2)
  • Files: 4 (docker-compose.prod.yml, Caddyfile, scripts/bootstrap_glitchtip.sh, .env.example) · +193 / -0
  • Previous verdict: HIGH (a79ee83) — all blocking items fixed

Verification of previous HIGH issues

# Item Status
HIGH #1 SQL injection via password heredoc FIXED — psql -v gt_pass=... + server-side format('CREATE ROLE glitchtip LOGIN PASSWORD %L', :'gt_pass') (scripts/bootstrap_glitchtip.sh:53-59). Shell no longer interpolates password into SQL string.
HIGH #2 Crash-loop on first deploy (services start before env+DB ready) FIXED — profiles: ["glitchtip"] on both glitchtip-web (line 130) and glitchtip-worker (line 154). Plain compose up -d skips them. Bootstrap activates explicitly via --profile glitchtip up -d and instructs adding COMPOSE_PROFILES=glitchtip to /opt/gendesign/.env for persistent activation across future deploys.
HIGH #3 mem_limit: 256m too small FIXED — web: 512m, worker: 384m. Worker autoscale 1,3 reasonable within 384m.
medium #4 :latest tag FIXED — pinned to glitchtip/glitchtip:6.1.6.
medium #5 No healthcheck FIXED — wget -q --spider http://localhost:8080/api/0/, 30s/5s/5r/60s start_period (matches bootstrap wait loop probe).
medium #6 CELERY_WORKER_AUTOSCALE on web container FIXED — moved to worker only.

Conventions check

  • .env.example: pure placeholders, no leak
  • Bootstrap: set -euo pipefail, :? validation, idempotent SQL (IF NOT EXISTS + \gexec), wait loops bounded
  • Profile activation pattern correctly explained in inline comment in compose (# Bootstrap script activates the profile after DB + secrets are ready.)
  • Healthcheck and bootstrap probe use the same wget --spider so behavior is consistent

Deferred (acceptable)

  • HSTS header in Caddyfile — separate PR
  • source .env strictness vs docker-compose parser — edge case
  • pull before --force-recreate for backend/worker/beat — deploy.yml concern

Risk

  • Risk: Low (services profile-gated; manual bootstrap is the activation step; no automatic deploy crash-loop)
  • Reversibility: Easy (revert + docker compose rm -f glitchtip-* + drop DB/role)

Post-merge follow-up

  • Create decisions/decision-glitchtip-infra.md + runbooks/glitchtip-bootstrap.md in vault (per CLAUDE.md rule #6)
  • Run bash scripts/bootstrap_glitchtip.sh on VPS after merge with GLITCHTIP_DB_PASS / GLITCHTIP_SECRET set in /opt/gendesign/.env

Merging via deep-code-reviewer — verdict APPROVE.

<!-- gendesign-review-bot: sha=ffaa363 verdict=approve --> ## Deep Code Review — verdict (re-review after fixup) ### Summary - **Status**: APPROVE - **Head SHA**: `ffaa363` (base `e0055d2`) - **Files**: 4 (`docker-compose.prod.yml`, `Caddyfile`, `scripts/bootstrap_glitchtip.sh`, `.env.example`) · +193 / -0 - **Previous verdict**: HIGH (`a79ee83`) — all blocking items fixed ### Verification of previous HIGH issues | # | Item | Status | |---|------|--------| | HIGH #1 | SQL injection via password heredoc | FIXED — `psql -v gt_pass=...` + server-side `format('CREATE ROLE glitchtip LOGIN PASSWORD %L', :'gt_pass')` (`scripts/bootstrap_glitchtip.sh:53-59`). Shell no longer interpolates password into SQL string. | | HIGH #2 | Crash-loop on first deploy (services start before env+DB ready) | FIXED — `profiles: ["glitchtip"]` on both `glitchtip-web` (line 130) and `glitchtip-worker` (line 154). Plain `compose up -d` skips them. Bootstrap activates explicitly via `--profile glitchtip up -d` and instructs adding `COMPOSE_PROFILES=glitchtip` to `/opt/gendesign/.env` for persistent activation across future deploys. | | HIGH #3 | `mem_limit: 256m` too small | FIXED — web: 512m, worker: 384m. Worker autoscale `1,3` reasonable within 384m. | | medium #4 | `:latest` tag | FIXED — pinned to `glitchtip/glitchtip:6.1.6`. | | medium #5 | No healthcheck | FIXED — `wget -q --spider http://localhost:8080/api/0/`, 30s/5s/5r/60s start_period (matches bootstrap wait loop probe). | | medium #6 | `CELERY_WORKER_AUTOSCALE` on web container | FIXED — moved to worker only. | ### Conventions check - `.env.example`: pure placeholders, no leak - Bootstrap: `set -euo pipefail`, `:?` validation, idempotent SQL (`IF NOT EXISTS` + `\gexec`), wait loops bounded - Profile activation pattern correctly explained in inline comment in compose (`# Bootstrap script activates the profile after DB + secrets are ready.`) - Healthcheck and bootstrap probe use the same `wget --spider` so behavior is consistent ### Deferred (acceptable) - HSTS header in Caddyfile — separate PR - `source .env` strictness vs docker-compose parser — edge case - `pull` before `--force-recreate` for backend/worker/beat — deploy.yml concern ### Risk - Risk: Low (services profile-gated; manual bootstrap is the activation step; no automatic deploy crash-loop) - Reversibility: Easy (revert + `docker compose rm -f glitchtip-*` + drop DB/role) ### Post-merge follow-up - Create `decisions/decision-glitchtip-infra.md` + `runbooks/glitchtip-bootstrap.md` in vault (per CLAUDE.md rule #6) - Run `bash scripts/bootstrap_glitchtip.sh` on VPS after merge with `GLITCHTIP_DB_PASS` / `GLITCHTIP_SECRET` set in `/opt/gendesign/.env` Merging via deep-code-reviewer — verdict APPROVE.
lekss361 merged commit d3aa96336b into main 2026-05-16 15:08:50 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#205
No description provided.