fix(infra): wire GlitchTip DSNs (build-arg + env_file) + reject sentry.io promote
Problem - GlitchTip Issues = 0 за всё время, хотя backend + frontend SDK интегрированы (PR #207, #208). - Старый Sentry.io продолжает получать события — user видит уведомления оттуда. Root cause - frontend/Dockerfile не имеет ARG NEXT_PUBLIC_GLITCHTIP_DSN → `npm run build` бьёт с пустым env var → Next.js инлайнит undefined → SDK init guard `if (dsn)` skips. Chrome-devtools check на prod bundle подтвердил: ни в одном из 9 chunks DSN-строка не запечена; `i.env.NEXT_PUBLIC_GLITCHTIP_DSN` evaluates to undefined. - .forgejo/workflows/deploy.yml build-frontend не передавал build-args. - На VPS backend/.env.runtime содержит legacy SENTRY_DSN=...@sentry.io/... config.py:_promote_legacy_sentry_dsn слепо промоутит его в glitchtip_dsn → SDK шлёт в чужой Sentry. GLITCHTIP_DSN там не задан. - deploy.yml SSH-скрипт никогда не редактировал SENTRY_DSN/GLITCHTIP_DSN в .env.runtime. Solution 1. frontend/Dockerfile: ARG NEXT_PUBLIC_GLITCHTIP_DSN + NEXT_PUBLIC_ENVIRONMENT с пустыми defaults, ENV-mirror перед `npm run build`. Локальный build без build-args работает по-прежнему (no-op DSN). 2. .forgejo/workflows/deploy.yml: - build-frontend: `build-args` передаёт `secrets.GLITCHTIP_FRONTEND_DSN` + NEXT_PUBLIC_ENVIRONMENT=production. Инвалидирует cache → frontend image пересоберётся (expected). - deploy step: GLITCHTIP_BACKEND_DSN через secret, в SSH-скрипте: a) `sed -i '/^SENTRY_DSN=/d' backend/.env.runtime` — снести legacy b) upsert GLITCHTIP_DSN (sed/printf) тем же паттерном что SENTRY_RELEASE c) `compose up -d --force-recreate --no-deps backend worker beat` — обычный `up -d` не перечитывает env_file без image change. 3. backend/app/core/config.py: _promote_legacy_sentry_dsn ужесточён — принимает SENTRY_DSN только если host == errors.gendsgn.ru. Для других URLs (sentry.io) выдаёт UserWarning и НЕ промоутит. Anti-regression на случай если SENTRY_DSN снова окажется в .env.runtime после ручного вмешательства. Required Forgejo secrets (Settings → Actions → Secrets) - GLITCHTIP_BACKEND_DSN = https://3d6e291003e142458957490c83559867@errors.gendsgn.ru/1 - GLITCHTIP_FRONTEND_DSN = https://5d7bc85e300c4e80a8554ccc818ff56d@errors.gendsgn.ru/2 DSNs публичны (видны в browser bundle) — secrets ради build-time injection, не для конфиденциальности. Если secrets не заданы → deploy succeeds, SDK no-op, без регрессии. Test plan - Verify Forgejo deploy.yml зелёный после merge - chrome-devtools: открыть gendsgn.ru → search bundle на DSN string → должна быть запечена строка errors.gendsgn.ru/2 - Trigger frontend error → POST к errors.gendsgn.ru/api/2/envelope/ - Backend: curl на endpoint вызывающий 500 → событие в GlitchTip backend project - GlitchTip dashboard https://errors.gendsgn.ru/gendesign/issues — Issues > 0 References - vault: meta/00_credentials.md (DSNs + incident notes 2026-05-16) - vault: decisions/Dec_GlitchTip_Frontend_Sentry_SDK.md (env contract) - vault: fixes/fixes-MOC.md (#204 backend SDK init)
This commit is contained in:
parent
ea7a8f4aeb
commit
e64fbd670b
3 changed files with 49 additions and 12 deletions
|
|
@ -134,6 +134,9 @@ jobs:
|
||||||
with:
|
with:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
push: true
|
push: true
|
||||||
|
build-args: |
|
||||||
|
NEXT_PUBLIC_GLITCHTIP_DSN=${{ secrets.GLITCHTIP_FRONTEND_DSN }}
|
||||||
|
NEXT_PUBLIC_ENVIRONMENT=production
|
||||||
cache-from: type=registry,ref=${{ env.IMAGE_FRONTEND }}:buildcache
|
cache-from: type=registry,ref=${{ env.IMAGE_FRONTEND }}:buildcache
|
||||||
cache-to: type=registry,ref=${{ env.IMAGE_FRONTEND }}:buildcache,mode=max
|
cache-to: type=registry,ref=${{ env.IMAGE_FRONTEND }}:buildcache,mode=max
|
||||||
tags: |
|
tags: |
|
||||||
|
|
@ -156,12 +159,13 @@ jobs:
|
||||||
IMAGE_TAG: latest
|
IMAGE_TAG: latest
|
||||||
SENTRY_RELEASE_VAL: ${{ github.sha }}
|
SENTRY_RELEASE_VAL: ${{ github.sha }}
|
||||||
GHCR_PAT: ${{ secrets.GHCR_PAT }}
|
GHCR_PAT: ${{ secrets.GHCR_PAT }}
|
||||||
|
GLITCHTIP_BACKEND_DSN: ${{ secrets.GLITCHTIP_BACKEND_DSN }}
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.DEPLOY_HOST }}
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
username: ${{ secrets.DEPLOY_USER }}
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
port: ${{ secrets.DEPLOY_PORT }}
|
port: ${{ secrets.DEPLOY_PORT }}
|
||||||
envs: IMAGE_TAG,SENTRY_RELEASE_VAL,GHCR_PAT
|
envs: IMAGE_TAG,SENTRY_RELEASE_VAL,GHCR_PAT,GLITCHTIP_BACKEND_DSN
|
||||||
script: |
|
script: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd /opt/gendesign
|
cd /opt/gendesign
|
||||||
|
|
@ -182,6 +186,17 @@ jobs:
|
||||||
else
|
else
|
||||||
printf 'SENTRY_RELEASE=%s\n' "$SENTRY_RELEASE_VAL" >> backend/.env.runtime
|
printf 'SENTRY_RELEASE=%s\n' "$SENTRY_RELEASE_VAL" >> backend/.env.runtime
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# GlitchTip wiring: убираем legacy SENTRY_DSN (auto-promote-логика в
|
||||||
|
# backend/app/core/config.py:_promote_legacy_sentry_dsn раньше брала
|
||||||
|
# его и слала события в чужой sentry.io). Устанавливаем GLITCHTIP_DSN
|
||||||
|
# из Forgejo secret. Пустой secret = no-op (SDK не инициализируется).
|
||||||
|
sed -i '/^SENTRY_DSN=/d' backend/.env.runtime
|
||||||
|
if grep -q '^GLITCHTIP_DSN=' backend/.env.runtime; then
|
||||||
|
sed -i "s|^GLITCHTIP_DSN=.*|GLITCHTIP_DSN=$GLITCHTIP_BACKEND_DSN|" backend/.env.runtime
|
||||||
|
else
|
||||||
|
printf 'GLITCHTIP_DSN=%s\n' "$GLITCHTIP_BACKEND_DSN" >> backend/.env.runtime
|
||||||
|
fi
|
||||||
chmod 600 backend/.env.runtime
|
chmod 600 backend/.env.runtime
|
||||||
|
|
||||||
# External network для Caddy + obsidian-stack share
|
# External network для Caddy + obsidian-stack share
|
||||||
|
|
@ -227,6 +242,13 @@ jobs:
|
||||||
|
|
||||||
docker compose -p gendesign -f docker-compose.prod.yml up -d
|
docker compose -p gendesign -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# backend/.env.runtime изменения (SENTRY_RELEASE, GLITCHTIP_DSN)
|
||||||
|
# требуют --force-recreate — обычный `up -d` не перечитывает env_file
|
||||||
|
# если только image не сменился. На deploy где меняется только runtime
|
||||||
|
# без backend image change — без этого backend остаётся со старым DSN.
|
||||||
|
docker compose -p gendesign -f docker-compose.prod.yml up -d \
|
||||||
|
--force-recreate --no-deps backend worker beat
|
||||||
|
|
||||||
docker compose -p gendesign -f docker-compose.prod.yml exec -T caddy \
|
docker compose -p gendesign -f docker-compose.prod.yml exec -T caddy \
|
||||||
caddy reload --config /etc/caddy/Caddyfile || true
|
caddy reload --config /etc/caddy/Caddyfile || true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,31 @@ class Settings(BaseSettings):
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def _promote_legacy_sentry_dsn(self) -> "Settings":
|
def _promote_legacy_sentry_dsn(self) -> "Settings":
|
||||||
"""Backward-compat: если SENTRY_DSN задан, а GLITCHTIP_DSN пуст — автопродвигаем.
|
"""Backward-compat: legacy SENTRY_DSN → glitchtip_dsn ТОЛЬКО для self-hosted.
|
||||||
|
|
||||||
На существующем VPS .env.runtime содержит SENTRY_DSN=https://...
|
Старый VPS .env.runtime мог содержать SENTRY_DSN=https://...@sentry.io/...
|
||||||
(старое имя переменной). Pydantic extra="ignore" молча его отбрасывает,
|
(legacy SaaS Sentry). Промоутить его НЕЛЬЗЯ — события пойдут в чужой проект.
|
||||||
SDK остаётся no-op. Читаем напрямую из os.environ и выдаём DeprecationWarning.
|
Принимаем только URLs указывающие на наш errors.gendsgn.ru host.
|
||||||
"""
|
"""
|
||||||
if not self.glitchtip_dsn:
|
if not self.glitchtip_dsn:
|
||||||
legacy = os.getenv("SENTRY_DSN")
|
legacy = os.getenv("SENTRY_DSN")
|
||||||
if legacy:
|
if legacy:
|
||||||
warnings.warn(
|
if "errors.gendsgn.ru" in legacy:
|
||||||
"SENTRY_DSN is set but ignored by pydantic; rename to GLITCHTIP_DSN. "
|
warnings.warn(
|
||||||
"Auto-promoting for backward compat.",
|
"SENTRY_DSN is set but ignored by pydantic; rename to GLITCHTIP_DSN. "
|
||||||
DeprecationWarning,
|
"Auto-promoting for backward compat.",
|
||||||
stacklevel=2,
|
DeprecationWarning,
|
||||||
)
|
stacklevel=2,
|
||||||
self.glitchtip_dsn = legacy
|
)
|
||||||
|
self.glitchtip_dsn = legacy
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
f"SENTRY_DSN points to non-GlitchTip host "
|
||||||
|
f"({legacy.split('@', 1)[-1][:40]}...) — ignoring. "
|
||||||
|
"Set GLITCHTIP_DSN explicitly.",
|
||||||
|
UserWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# External APIs (Stage 2)
|
# External APIs (Stage 2)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ RUN --mount=type=cache,target=/root/.npm \
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
# NEXT_PUBLIC_* must be present at `npm run build` — Next.js inlines them into
|
||||||
|
# the client bundle. Defaults to empty so local `docker build` без build-args не ломается.
|
||||||
|
ARG NEXT_PUBLIC_GLITCHTIP_DSN=
|
||||||
|
ARG NEXT_PUBLIC_ENVIRONMENT=production
|
||||||
|
ENV NEXT_PUBLIC_GLITCHTIP_DSN=$NEXT_PUBLIC_GLITCHTIP_DSN
|
||||||
|
ENV NEXT_PUBLIC_ENVIRONMENT=$NEXT_PUBLIC_ENVIRONMENT
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue