Files
admin b8d1aca740
CI (SIAX Cloud) / security (push) Successful in 13s
CI (SIAX Cloud) / contracts (push) Successful in 16s
CI (SIAX Cloud) / quality (push) Successful in 54s
CI (SIAX Cloud) / contracts (pull_request) Successful in 41s
CI (SIAX Cloud) / security (pull_request) Successful in 42s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m13s
docs(deploy): scan worker + AUD0 wire + env contract
2026-09-16 23:07:47 +02:00

124 lines
6.4 KiB
Markdown

# C0PY — Deployment
## SIAX Cloud Deploy
- **Config**: [`siax.config.json`](siax.config.json)
- **CI/CD**: `.gitea/workflows/ci.yml` — körs vid push/PR till main (Gitea Actions, git.siax.io). Deploy är **manuell** (se nedan) — CI triggar ej Coolify (git-buildpack trasig för detta monorepo, auto-deploy togs bort 2026-09-16).
- **Runtime**: Node 22, port 3000
- **Domain**: `c0py-web.siax.io` (web) / `c0py-api.siax.io` (API)
## Produktion (live 2026-09-16)
| Komponent | Coolify app-uuid | Server | Domän | Image |
|---|---|---|---|---|
| API (Fastify) | `wvmn6eg40jofssvm3nigmav6` | server6 | `c0py-api.siax.io` | `wvmn6eg40jofssvm3nigmav6:latest` |
| Web (Next.js) | `58pa82h4qlzcwsc4shreuwk0` | server6 | `c0py-web.siax.io` + `c0py.siax.io` (apex) | `58pa82h4qlzcwsc4shreuwk0:latest` |
| Postgres | `qhucpjjpv3mx9jupqkbyf5uh` | server6 | — (internt) | — |
- **Deploy-mönster**: Coolify-appar finns som registry-poster, men bygge sker manuellt på
server6 (`/tmp/c0py-build && docker build -t <app-uuid>:latest .`) följt av
`cd /data/coolify/applications/<app-uuid> && docker compose up -d` — Coolifys
dockerfile+git-buildpack sätter build-context till helper-container utan repo-trädet
och failar för detta monorepo (`"/apps": not found`).
- **DNS**: `c0py-api` + `c0py-web` = Cloudflare A → 65.109.19.146, **DNS-only** (proxied=false).
Edge-certet täcker bara `*.siax.io` — tredjenivå-hostnames (`api.c0py.siax.io`) ger
TLS-handshake-failure via proxied; DNS-only låter Traefik LE-certifiera direkt (samma
mönster som api.act0.siax.io).
- **⚠️ c0py.siax.io (apex) var kapad av en Cloudflare Worker/Pages** ("Hello World!",
text/plain) — LÖST 2026-09-16 utan Workers-perms: apex A-recorden sattes **DNS-only**
(proxied=false), vilket omgår Worker-routes (de gäller endast proxied-trafik), och
`c0py.siax.io` lades som domän på web-appen (compose-labels + DB-fqdn). LE-cert
utfärdas av Traefik (SAN under c0py-web-certet). **VARNING:** sätts apex-recorden
till proxied=true igen kommer Worker:n tillbaka — CLOUDFLARE_API_TOKEN saknar
Workers-perms (kan ej identifiera/radera Worker:n). Server6-fällor: Coolify
regenererar compose-filen vid nästa deploy och tappar apex-Hostnamnet från
labels — efter varje deploy, sed:a `Host(`c0py-web.siax.io`) || Host(`c0py.siax.io`)`
i `/data/coolify/applications/58pa82h4qlzcwsc4shreuwk0/docker-compose.yaml` och
recreate (docker rm -f + compose up).
## Deploy via Coolify
- **Coolify dashboard**: `cloud.siax.io`
- **Resource type**: Application (dockerfile build pack, git source git.siax.io/sax3l/c0py)
- **Build pack**: Dockerfile — API: `/Dockerfile`, Web: `/Dockerfile.web`
- **ports_exposes**: `3000` (KRITISKT — default 80 ger Trasfik-labels mot fel port)
## Docker
```bash
docker build -t siax/c0py .
docker run -d -p 3000:3000 \
-e PORT=3000 \
-e NODE_ENV=production \
-e DATABASE_URL=postgresql://... \
-e CL0UD_BASE_URL=https://cl0ud.siax.io \
-e ZITADEL_ISSUER=https://id-customers.siax.io \
-e ZITADEL_AUDIENCE=c0py-api.siax.io \
-e AUD0_BASE_URL=https://aud0.siax.io \
-e AUD0_API_KEY=... \
-e ST0RE_BASE_URL=https://st0re.siax.io \
siax/c0py
```
## Miljövariabler
| Variable | Krävs | Default | Beskrivning |
|---|---|---|---|
| `PORT` | Nej | `3000` | HTTP-serverport |
| `NODE_ENV` | Nej | `development` | Läge |
| `CL0UD_BASE_URL` | Ja | — | CL0UD kontrollplan URL |
| `ZITADEL_ISSUER` | Ja | — | OIDC-utfärdare (`https://id-customers.siax.io`) |
| `ZITADEL_AUDIENCE` | Ja | — | API-audience |
| `ZITADEL_INTROSPECTION_CLIENT_ID` | Prod | — | RFC 7662-introspection-klient (Zitadel app `c0py-introspect`) — utan dessa körs endast Bearer-presence-check (warn-logg i prod) |
| `ZITADEL_INTROSPECTION_CLIENT_SECRET` | Prod | — | Introspection-klientsecret (Infisical: `C0PY_ZITADEL_INTROSPECTION_CLIENT_SECRET`) |
| `ZITADEL_EXPECTED_AUDIENCE` | Prod | — | Projektnivå-aud (Zitadel projekt-id `391064954179944451`); PAT-introspection bär aud=[clientId, projectId] |
| `AUD0_BASE_URL` | Nej | — | AUD0 evidens-URL (`https://api.aud0.siax.io`) — utan denna+key tappas events (advisory, loggad) |
| `AUD0_API_KEY` | Nej | — | AUD0 Bearer (Infisical: `AUD0_AUTH_TOKEN`) |
| `CHROMIUM_EXECUTABLE_PATH` | Nej | `/usr/bin/chromium-browser` | Browser för scan-worker (image installerar `apk chromium`) |
| `DATABASE_URL` | Ja | — | Postgres-anslutning |
| `AUD0_BASE_URL` | Nej | — | AUD0 evidens-URL |
| `AUD0_API_KEY` | Nej | — | AUD0 autentisering |
| `ST0RE_BASE_URL` | Nej | — | ST0RE lagrings-URL |
| `INF0_BASE_URL` | Nej | — | INF0 AI-URL |
| `N0D_BASE_URL` | Nej | — | N0D beräknings-URL |
## Migrationer
- SQL: [`apps/api/migrations/`](apps/api/migrations/) — idempotenta (CREATE TABLE IF NOT EXISTS)
- Tillämpa (server6): `docker cp apps/api/migrations/001_*..sql qhucpjjpv3mx9jupqkbyf5uh:/tmp/ && docker exec qhucpjjpv3mx9jupqkbyf5uh psql -U c0py -d c0py -f /tmp/001_*.sql`
- Postgres: app-uuid `qhucpjjpv3mx9jupqkbyf5uh` (postgres:16-alpine, db/user `c0py`)
## API-endpoints (alla fail-closed, owner-scoped via introspected subject)
| Metod | Path | Svar |
|---|---|---|
| GET | `/health` | 200 (exempt från auth) |
| GET | `/v1/c0py/registries` | ägarens registries |
| POST | `/v1/c0py/registries` | 201 / 400 (name+url krävs) |
| GET | `/v1/c0py/registries/:id` | 200 / 404 (främmande = 404) |
| POST | `/v1/c0py/scans` | 201 / 400 / 404 (registry-ägarskap verifieras i SQL) |
| GET | `/v1/c0py/scans?registryId=` | ägarens scans, valbar filter |
| GET | `/v1/c0py/scans/:id` | 200 / 404 |
## Scan worker + AUD0
- API-containern kör en **in-process scan-worker** (poll 2s, atomic claim via
`FOR UPDATE SKIP LOCKED`): capture med playwright-core + system-chromium
(DNS-only domän måste vara nåbar från server6) → typed `EvidenceRecord`s
(runtime-html/dom/computed-style/stylesheet/asset/network-request/screenshot,
`confidence: measured`) → `scans.result` (JSONB) → status completed/failed.
- **AUD0 (fire-and-forget, advisory)**: `c0py.registry_created` (L1),
`c0py.scan_created` (L1), `c0py.scan_completed`/`c0py.scan_failed` (L2).
tenant_id = Zitadel resourceowner-org (server-side assertat; klienten kan
aldrig sätta tenant). AUD0-outage tappar events men blockerar aldrig API:et.
- `page.evaluate` körs som **ren string** — tsx/esbuild injicerar annars
`__name`-helpers som kraschar i browser-kontextet.
## Hälsocheck
```
GET /health
```
Förväntad respons: `200 OK`