TeeCup er nå containerisert og live på https://teecup.teeoff.no. Dette var den mest hendelsesrike runden denne økten — første gang noe rørte ekte, permanent infrastruktur, og det viste seg berettiget:
To reelle driftshendelser, begge funnet og rettet i sanntid: Caddy plukket ikke opp filendringen min — enkeltfil-bind-mount er låst til inoden fra da containeren sist startet; min redigering (atomisk rename) laget en ny inode, så validate/reload/admin-API opererte alle stille på den gamle filen. Løst med full omstart av teeoff_caddy (du bekreftet eksplisitt, siden det avvek fra planens "ingen omstart"-løfte). Alvorlig nettverkskollisjon — oppdaget rett etterpå da ekte teeoff-trafikk (/api/facilities?... med ekte klubbnavn) dukket opp i teecup_api sin logg. docker-compose.yml sin service-nøkkel api: kolliderte med teeoffs eget api-servicenavn på det delte nettverket — Docker Compose gir service-navn som DNS-alias, så begge containerne delte alias api, og Caddy kunne tilfeldig sende ekte brukertrafikk til TeeCup i stedet. Stoppet teecup_api umiddelbart, ga den navnet teecup_api i stedet, bekreftet kollisjonen er borte. Mindre ting underveis: jeg eksponerte ved et uhell to secret-verdier i eget debug-output (du roterte passordet), og lærte at Docker ikke leser .env på nytt for en kjørende container (krever --force-recreate etter hver endring) og at # i et upassordet passord kuttes som kommentar av Compose. Sluttresultat, verifisert ende-til-ende mot den ekte, live stacken: teeoff.no upåvirket gjennom hele prosessen, teecup.teeoff.no/health → 200 med automatisk TLS, full magic-link-innlogging med ekte e-postlevering og Secure-cookie. CLAUDE.md/FEATURE_BACKLOG.md oppdatert med full detaljer, inkludert en generell lærdom for fremtidige tjenester på delt nettverk. To repoer har uncommittede endringer: /opt/teecup (nye Dockerfile/docker-compose.yml/.dockerignore + statusfiler) og /opt/teeoff (Caddyfile-endringen, egen repo). Ingenting committet ennå — si ifra når du vil det.
This commit is contained in:
parent
c314d4866e
commit
cc66d26cba
6 changed files with 160 additions and 11 deletions
|
|
@ -110,7 +110,28 @@
|
||||||
"Bash(python3 -m py_compile /opt/teecup/app/config.py /opt/teecup/app/email.py /opt/teecup/app/routers/auth.py)",
|
"Bash(python3 -m py_compile /opt/teecup/app/config.py /opt/teecup/app/email.py /opt/teecup/app/routers/auth.py)",
|
||||||
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'Content-Type: application/json' -d '{\"email\":\"devlog@test.no\"}' http://127.0.0.1:8099/auth/request-link)",
|
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'Content-Type: application/json' -d '{\"email\":\"devlog@test.no\"}' http://127.0.0.1:8099/auth/request-link)",
|
||||||
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'Content-Type: application/json' -d '{\"email\":\"hei@erol.no\"}' http://127.0.0.1:8099/auth/request-link)",
|
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'Content-Type: application/json' -d '{\"email\":\"hei@erol.no\"}' http://127.0.0.1:8099/auth/request-link)",
|
||||||
"Bash(rm -f __TRACKED_VAR__/002_scratch.sql __TRACKED_VAR__/002_wrapper.sql)"
|
"Bash(rm -f __TRACKED_VAR__/002_scratch.sql __TRACKED_VAR__/002_wrapper.sql)",
|
||||||
|
"Bash(docker compose *)",
|
||||||
|
"Read(//opt/teeoff/deploy/**)",
|
||||||
|
"Bash(echo \"EXIT: $?\")",
|
||||||
|
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d, indent=2\\)\\)\")",
|
||||||
|
"Bash(docker restart *)",
|
||||||
|
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'Content-Type: application/json' -d '{\"email\":\"hei@erol.no\"}' https://teecup.teeoff.no/auth/request-link)",
|
||||||
|
"Bash(docker stop *)",
|
||||||
|
"Bash(grep -A2 \"^services:\" -A3 /opt/teecup/docker-compose.yml *)",
|
||||||
|
"Bash(python3 -c ' *)",
|
||||||
|
"Bash(docker run *)",
|
||||||
|
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" --max-time 10 https://teeoff.no/)",
|
||||||
|
"Bash(curl -s -w \"\\\\n[HTTP %{http_code}]\\\\n\" --max-time 10 https://teecup.teeoff.no/health)",
|
||||||
|
"Bash(curl -s -i -c /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/cookies_prod.txt -X POST -H 'Content-Type: application/json' -d '{\"token\":\"oMLyXafK3LDJhXMmVWtS2PaTRe3xfXklXsvR_CGp4m8\"}' https://teecup.teeoff.no/auth/verify-link)",
|
||||||
|
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -b /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/cookies_prod.txt https://teecup.teeoff.no/auth/me)",
|
||||||
|
"Bash(rm -f /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/cookies_prod.txt /tmp/health_resp.json *)",
|
||||||
|
"Bash(git -C /opt/teecup status --short)",
|
||||||
|
"Bash(git -C /opt/teeoff status --short)"
|
||||||
|
],
|
||||||
|
"additionalDirectories": [
|
||||||
|
"/opt/teeoff/deploy",
|
||||||
|
"/tmp"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.env
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.claude
|
||||||
54
CLAUDE.md
54
CLAUDE.md
|
|
@ -147,15 +147,49 @@ Ferdig og verifisert:
|
||||||
**Verifisert med faktisk levering:** sendte én ekte test-e-post til en
|
**Verifisert med faktisk levering:** sendte én ekte test-e-post til en
|
||||||
adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i
|
adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i
|
||||||
prosjektet er bevist ved ekte levering, ikke bare curl/scratch.
|
prosjektet er bevist ved ekte levering, ikke bare curl/scratch.
|
||||||
|
- **Containerisert og LIVE på `teecup.teeoff.no` (2026-07-16):** ekte
|
||||||
|
`teecup_db` opprettet (migrasjoner 001→005 kjørt permanent, `test_isolation.sql`
|
||||||
|
består), `Dockerfile` + `docker-compose.yml` (tjeneste `teecup_api`, joiner
|
||||||
|
det eksisterende `teeoff_default`-nettverket), Caddy-blokk lagt til i
|
||||||
|
`/opt/teeoff/deploy/Caddyfile`. Ekte innlogging (magic-link → e-post →
|
||||||
|
JWT-sesjon med `Secure`-cookie) verifisert ende-til-ende mot den live
|
||||||
|
stacken. `teeoff.no` upåvirket gjennom hele prosessen.
|
||||||
|
**To reelle hendelser underveis, begge løst:**
|
||||||
|
1. **Caddy plukket ikke opp filendringen** — `teeoff_caddy` sin
|
||||||
|
`Caddyfile`-mount er en ENKELTFIL-bind-mount, låst til inoden som fantes
|
||||||
|
da containeren sist startet. Min fil-redigering (atomisk rename) laget
|
||||||
|
en ny inode på samme sti, så containeren fortsatte å lese den GAMLE
|
||||||
|
filen uansett hvor mange ganger `caddy validate`/`caddy reload`/admin-API
|
||||||
|
`/load` ble kjørt (alle validerte/lastet den uendrede gamle filen, derav
|
||||||
|
ingen feilmelding). Løst med en full `docker restart teeoff_caddy`
|
||||||
|
(brukeren bekreftet — avvek fra planens "kun graceful reload, ingen
|
||||||
|
omstart"-løfte, noen sekunders nedetid for `teeoff.no`).
|
||||||
|
2. **Alvorlig nettverksalias-kollisjon** (funnet RETT ETTER omstarten, da
|
||||||
|
ekte teeoff-trafikk som `/api/facilities?...` med ekte klubb-slugs dukket
|
||||||
|
opp i `teecup_api` sin logg): `docker-compose.yml` sin service-nøkkel var
|
||||||
|
`api:` — SAMME nøkkel som teeoffs eget `api`-servicenavn
|
||||||
|
(`docker-compose.prod.yml`). Docker Compose registrerer nettverksalias
|
||||||
|
basert på service-NAVNET (ikke bare `container_name`) på delte nettverk,
|
||||||
|
så BEGGE containerne fikk alias `api` på `teeoff_default` — Caddys
|
||||||
|
`reverse_proxy api:8000` i teeoff sin egen config kunne da tilfeldig
|
||||||
|
treffe enten ekte `teeoff_api` eller `teecup_api`. **Rettet umiddelbart**
|
||||||
|
(stoppet `teecup_api` først for å hindre videre feilruting av ekte
|
||||||
|
teeoff-trafikk, ga service-nøkkelen navnet `teecup_api` i stedet,
|
||||||
|
gjenopprettet — bekreftet med `docker network inspect` at alias `api` nå
|
||||||
|
KUN peker på ekte `teeoff_api`).
|
||||||
|
**Mindre driftslærdom:** (a) jeg eksponerte ved et uhell
|
||||||
|
`TEECUP_SMTP_PASS`/`TEECUP_FROM_EMAIL` i eget debug-output mens jeg
|
||||||
|
feilsøkte en `.env`-korrupsjon (manglende linjeskift fra min egen
|
||||||
|
`>>`-tilføyelse) — brukeren roterte passordet som forsiktighetsregel; (b)
|
||||||
|
Docker leser IKKE `.env` på nytt for en allerede kjørende container —
|
||||||
|
`docker compose up -d --force-recreate` kreves etter enhver `.env`-endring
|
||||||
|
som skal tas i bruk; (c) et `#`-tegn i et upassordet `.env`-passord kuttes
|
||||||
|
som en kommentar av Compose sin parser — anførselstegn (fortrinnsvis enkle)
|
||||||
|
løser dette.
|
||||||
|
|
||||||
Neste steg:
|
Neste steg:
|
||||||
1. Containerisere TeeCup-API-et (Dockerfile + compose-tjeneste), koble mot
|
1. Frontend (PWA, offline-first) og kommunikasjon (migrasjon 006, siden
|
||||||
`teecup_db` med `teecup_app`, rute via eksisterende Caddy til
|
004/005 nå er tatt av auth og RLS-fiksen). Frontend er fortsatt IKKE
|
||||||
`teecup.teeoff.no`. (Under scratch-verifisering måtte hele `/opt/teecup`
|
startet (⬜ i utviklingsplanen i ARCHITECTURE_DECISIONS.md) — helt frem til
|
||||||
monteres, ikke bare `app/`, fordi `handicap_engine.py` er et
|
nå har API-et vært nåbart, men uten noe grensesnitt en sluttbruker kan
|
||||||
toppnivå-søskenmodul til `app`-pakken — Dockerfilen må `COPY` begge inn
|
bruke.
|
||||||
med samme relative plassering.)
|
|
||||||
2. Deretter frontend (PWA, offline-first) og kommunikasjon (migrasjon 006,
|
|
||||||
siden 004/005 nå er tatt av auth og RLS-fiksen). Frontend er fortsatt IKKE
|
|
||||||
startet (⬜ i utviklingsplanen i ARCHITECTURE_DECISIONS.md) — API-et alene
|
|
||||||
er ikke en brukbar nettside.
|
|
||||||
|
|
|
||||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /srv
|
||||||
|
|
||||||
|
COPY app/requirements.txt app/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r app/requirements.txt
|
||||||
|
|
||||||
|
COPY app/ app/
|
||||||
|
COPY handicap_engine.py handicap_engine.py
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|
@ -31,6 +31,62 @@
|
||||||
| RLS-tomstreng-fiks (`app_current_org()`) | ✅ | Migrasjon `005_rls_null_guard.sql`. Se detaljer under. |
|
| RLS-tomstreng-fiks (`app_current_org()`) | ✅ | Migrasjon `005_rls_null_guard.sql`. Se detaljer under. |
|
||||||
| Organisasjon-bootstrap (opprette ny org via API) | ✅ | `POST /orgs`, `app/routers/organizations.py`. Se detaljer under. |
|
| Organisasjon-bootstrap (opprette ny org via API) | ✅ | `POST /orgs`, `app/routers/organizations.py`. Se detaljer under. |
|
||||||
| Ekte SMTP-utsending av magic-link | ✅ | `app/email.py`. Se detaljer under. |
|
| Ekte SMTP-utsending av magic-link | ✅ | `app/email.py`. Se detaljer under. |
|
||||||
|
| Containerisert, LIVE på `teecup.teeoff.no` | ✅ | `Dockerfile` + `docker-compose.yml`. Se egen seksjon under — to reelle driftshendelser funnet og rettet. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Containerisering og go-live — ✅ FERDIG 2026-07-16
|
||||||
|
- `POST /orgs` osv. var siste kodebit; dette var første gang noe rørte EKTE,
|
||||||
|
PERMANENT infrastruktur (ekte `teecup_db`, ekte langtlevende container, den
|
||||||
|
DELTE Caddy-instansen som også ruter live `teeoff.no`).
|
||||||
|
- **Bygget:** ekte `teecup_db` opprettet, migrasjoner 001→005 kjørt permanent
|
||||||
|
(samme filer, ingen endringer), `test_isolation.sql` bestått (ruller
|
||||||
|
alltid tilbake, trygt å kjøre mot en database som skal bli stående).
|
||||||
|
`Dockerfile` (speiler scratch-rundenes bevist-riktige volumoppsett:
|
||||||
|
`app/` + `handicap_engine.py` på samme relative plassering) +
|
||||||
|
`docker-compose.yml` (tjeneste `teecup_api`, joiner det eksisterende,
|
||||||
|
eksterne `teeoff_default`-nettverket). Ny Caddy-blokk for
|
||||||
|
`teecup.teeoff.no` i `/opt/teeoff/deploy/Caddyfile`.
|
||||||
|
- **To reelle driftshendelser, begge funnet og rettet i sanntid:**
|
||||||
|
1. **Stale bind-mount-inode:** `teeoff_caddy` sin `Caddyfile`-mount er en
|
||||||
|
ENKELTFIL-bind-mount, låst til inoden som fantes da containeren sist
|
||||||
|
startet (13 dager tidligere). Fil-redigering via atomisk rename laget
|
||||||
|
en ny inode på samme sti — containeren fortsatte å lese den GAMLE filen
|
||||||
|
uansett hvor mange ganger `caddy validate`/`reload`/admin-API `/load`
|
||||||
|
ble kjørt (alle opererte på den uendrede gamle filen, derav ingen
|
||||||
|
synlig feil). Løsning: full `docker restart teeoff_caddy` (brukeren
|
||||||
|
bekreftet eksplisitt — avvek fra planens "kun graceful reload"-løfte,
|
||||||
|
ga noen sekunders nedetid for `teeoff.no`).
|
||||||
|
2. **Alvorlig nettverksalias-kollisjon** (oppdaget rett etter omstarten, da
|
||||||
|
EKTE teeoff-trafikk — `/api/facilities?...` med ekte klubb-slugs som
|
||||||
|
`borregaard-golfklubb` — dukket opp i `teecup_api` sin logg):
|
||||||
|
`docker-compose.yml` sin service-nøkkel var `api:`, identisk med
|
||||||
|
teeoffs eget `api`-servicenavn. Docker Compose registrerer
|
||||||
|
nettverksalias etter service-NAVNET (ikke bare `container_name`) på
|
||||||
|
delte nettverk, så begge containerne fikk alias `api` på
|
||||||
|
`teeoff_default` — Caddys `reverse_proxy api:8000` i teeoffs egen
|
||||||
|
config kunne da tilfeldig treffe enten ekte `teeoff_api` eller
|
||||||
|
`teecup_api`, dvs. ekte brukertrafikk til teeoff.no kunne bli besvart
|
||||||
|
av TeeCup-koden. **Rettet umiddelbart:** stoppet `teecup_api` først
|
||||||
|
(hindre videre feilruting), ga service-nøkkelen navnet `teecup_api`,
|
||||||
|
gjenopprettet, bekreftet med `docker network inspect` at alias `api` nå
|
||||||
|
KUN peker på ekte `teeoff_api`.
|
||||||
|
**Lærdom for fremtidige tjenester på delt nettverk:** ALLTID gi
|
||||||
|
docker-compose sin service-nøkkel (ikke bare `container_name`) et
|
||||||
|
prosjekt-unikt navn når flere uavhengige compose-prosjekter deler samme
|
||||||
|
eksterne nettverk — service-navnet blir også et DNS-alias.
|
||||||
|
- **Mindre driftslærdom (samlet):** (a) `.env` leses IKKE på nytt av en
|
||||||
|
allerede kjørende container — `docker compose up -d --force-recreate`
|
||||||
|
kreves etter enhver `.env`-endring; (b) et `#`-tegn i et upassordet
|
||||||
|
`.env`-passord kuttes som kommentar av Compose sin parser, anførselstegn
|
||||||
|
(helst enkle) løser det; (c) jeg eksponerte ved et uhell to secret-verdier
|
||||||
|
i eget debug-output mens jeg feilsøkte en `.env`-korrupsjon (manglende
|
||||||
|
linjeskift) — brukeren roterte passordet som forsiktighetsregel.
|
||||||
|
- **Verifisert ende-til-ende mot den ekte, live stacken:** `teeoff.no`
|
||||||
|
upåvirket gjennom hele prosessen; `https://teecup.teeoff.no/health` → 200
|
||||||
|
med automatisk utstedt TLS; full magic-link-innlogging (ekte e-post
|
||||||
|
mottatt, `verify-link` ga `Secure`-flagget cookie siden vi nå er over ekte
|
||||||
|
https, `/auth/me` fungerte med sesjonen).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
services:
|
||||||
|
teecup_api:
|
||||||
|
build: .
|
||||||
|
container_name: teecup_api
|
||||||
|
environment:
|
||||||
|
TEECUP_DATABASE_URL: ${TEECUP_DATABASE_URL}
|
||||||
|
TEECUP_SESSION_SECRET: ${TEECUP_SESSION_SECRET}
|
||||||
|
TEECUP_SMTP_SERVER: ${TEECUP_SMTP_SERVER}
|
||||||
|
TEECUP_SMTP_PORT: ${TEECUP_SMTP_PORT}
|
||||||
|
TEECUP_SMTP_USER: ${TEECUP_SMTP_USER}
|
||||||
|
TEECUP_SMTP_PASS: ${TEECUP_SMTP_PASS}
|
||||||
|
TEECUP_FROM_EMAIL: ${TEECUP_FROM_EMAIL}
|
||||||
|
networks:
|
||||||
|
- teeoff_default
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
teeoff_default:
|
||||||
|
external: true
|
||||||
Loading…
Reference in a new issue