Terraform blir enda vägen, och backup ett obligatoriskt val
Kustomize- och Argo CD-vägen är borttagen. Den beskrev samma system en
gång till och kunde inte köras samtidigt som Terraform utan att de
motarbetade varandra — selfHeal återställde det Terraform ändrade och
prune tog bort det Terraform skapade. Kvar utanför Terraform är bara
postgres-init.sql, som läses av både definitionen och integrationstestet
så att schemat inte kan glida isär från det som testas.
Säkerhetskopieringen var en förhoppning: en StatefulSet med en volym och
ingen kopia. Går volymen förlorad är det inte "data" som försvinner utan
varje ärendes bevisvärde — vad som kontrollerades, av vem, när, med
vilken evidens — och det går inte att återskapa i efterhand.
databas_lage är därför ett obligatoriskt val utan standardvärde:
extern managerad Postgres, leverantörens backup och PITR
(rekommenderat i produktion)
cnpg CloudNativePG i klustret: basbackup 02:30, kontinuerlig
WAL-arkivering till objektlagring, PITR och failover
inbyggd en volym, ingen backup — spärras av en precondition när
miljön är produktion
Preconditions fångar felkonfiguration vid plan i stället för vid drift:
extern utan anslutning, cnpg utan backupmål eller nycklar, inbyggd i
produktion.
Driftsättningen är nu två åtskilda flöden. Publicera bygger och taggar
bilderna vid varje main-push; Driftsätt startas för hand med en tagg mot
en GitHub-miljö som kan kräva godkännande, kör fmt/init/validate/plan/
apply, skriver ut kartan och rökkontrollerar hälsa och API-spec. En bild
i registret är inte samma sak som en bild som kör. Rollback är att köra
Driftsätt igen med en tidigare tagg. CI kör dessutom terraform validate
på varje PR — den kontroll jag inte kunde köra själv.
Två fel hittade vid egengranskning av definitionen: schemafilen delades
på semikolon, vilket hade klippt itu plpgsql-funktionen med
append-only-triggern (nu hela filen via postInitApplicationSQLRefs), och
null-satta fält i kubernetes_manifest utelämnas nu i stället.
Verifierat: 87 vitest-tester, typkontroll, eslint, OpenAPI-validering,
terraform fmt, statisk referenskontroll av modulen och integrationstest
mot riktig Postgres. terraform validate kunde inte köras här —
registry.terraform.io är blockerad av sessionens egress-policy, därav
CI-jobbet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
+54
-60
@@ -21,7 +21,7 @@ flowchart LR
|
||||
| `web` | SPA:n bakom oprivilegierad nginx (`Dockerfile`, `docker/nginx.conf`) | Deployment + Service + HPA + PDB |
|
||||
| `plattform` | Självhostad backend (`services/plattform`): **multi-tenant** — registrering skapar organisation + systemadministratör, admin hanterar användare (tekniker/arbetsledare/admin), all ärendedata organisationsisolerad. Inloggning (bcrypt via pgcrypto, HS256-JWT med roll + org i anspråken), append-only händelse-API, publik delningsendpoint | Deployment + Service + HPA + PDB |
|
||||
| `ai-orkester` | AI-orkestern (`services/ai-orkester`): fyra uppgifter routade till Sonnet 5 / Opus 5 / Haiku 4.5 — verifierar plattformens JWT (delad hemlighet) | Deployment + Service + HPA + PDB |
|
||||
| `postgres` | Händelselogg + användare; **append-only garanterat med databastriggers** — historik kan inte ändras eller raderas oavsett roll | StatefulSet + PVC (10 Gi). Produktion: CloudNativePG-operatorn för backup/failover/PITR |
|
||||
| `postgres` | Händelselogg + användare; **append-only garanterat med databastriggers** — historik kan inte ändras eller raderas oavsett roll | Tre lägen: extern managerad Postgres (rekommenderat), CloudNativePG i klustret, eller en enkel StatefulSet utan backup för prov |
|
||||
| Hemligheter | `anthropic-api-key`, `jwt-secret` (delas av plattform + orkester), `postgres-losenord`, `integration-nyckel` (krypterar kundernas märkesspecifika credentials) |
|
||||
| Miljöflaggor | `TILLATNA_URSPRUNG` (CORS-lista; utelämnad = `*`), `TILLAT_INTERNA_UPPSLAG` (`true` tillåter leverantörsuppslag mot privata nät), `REGISTRERING_OPPEN`, `ECM_REGLER_FIL`, `INTEGRATIONER_FIL` | Secret `felsokning-hemligheter` — aldrig i bilder eller manifest |
|
||||
|
||||
@@ -34,11 +34,11 @@ Börja i `karta.tf`: hela systemet beskrivet en gång som data (tjänster,
|
||||
portar, routing, hemligheter, dataflöden, gränser). `terraform output
|
||||
karta` skriver ut samma sak i klartext.
|
||||
|
||||
`infra/k8s` + `infra/overlays` + `infra/gitops` beskriver samma system i
|
||||
kustomize, synkat av Argo CD. **Kör inte båda mot samma kluster** — Argo
|
||||
CD:s `selfHeal` återställer det Terraform ändrar och `prune` tar bort det
|
||||
Terraform skapar. Terraform-vägen har dessutom nätverkspolicyer och
|
||||
säkerhetskontext på databasen, vilket kustomize-vägen saknar.
|
||||
Definitionen omfattar hemligheter, databasschema, nätverksgränser och
|
||||
alla tre databaslägena. Kustomize- och Argo CD-vägen är borttagen —
|
||||
`infra/postgres-init.sql` är det enda som blivit kvar utanför Terraform,
|
||||
och den läses av både Terraform och integrationstestet så att schemat
|
||||
inte kan glida isär från det som testas.
|
||||
|
||||
## Nätverksgränser
|
||||
|
||||
@@ -61,59 +61,52 @@ dokumentation, inte skydd.
|
||||
|
||||
## Driftsätta
|
||||
|
||||
Allt går genom Terraform — hemligheter, schema och nätverksgränser
|
||||
ingår. Det finns ingen `kubectl apply` att komma ihåg.
|
||||
|
||||
```sh
|
||||
# 1. Bygg och publicera bilderna (ersätt registry i infra/k8s/*.yaml)
|
||||
docker build -t ghcr.io/ORG/guidad-felsokning-web \
|
||||
--build-arg VITE_PLATTFORM_URL=https://app.exempel.se .
|
||||
docker build -t ghcr.io/ORG/guidad-felsokning-ai-orkester services/ai-orkester
|
||||
docker build -t ghcr.io/ORG/guidad-felsokning-plattform services/plattform
|
||||
docker push ghcr.io/ORG/guidad-felsokning-web
|
||||
docker push ghcr.io/ORG/guidad-felsokning-ai-orkester
|
||||
docker push ghcr.io/ORG/guidad-felsokning-plattform
|
||||
cd infra/terraform
|
||||
cp terraform.tfvars.exempel terraform.tfvars # domän, register, databasläge, nycklar
|
||||
terraform init
|
||||
terraform plan
|
||||
terraform apply -var bildtagg=<git-sha>
|
||||
|
||||
# 2. Skapa secret:en (eller använd External Secrets/Sealed Secrets)
|
||||
kubectl create namespace guidad-felsokning
|
||||
kubectl -n guidad-felsokning create secret generic felsokning-hemligheter \
|
||||
--from-literal=anthropic-api-key='sk-ant-…' \
|
||||
--from-literal=jwt-secret="$(openssl rand -base64 48)" \
|
||||
--from-literal=postgres-losenord="$(openssl rand -base64 24)" \
|
||||
--from-literal=integration-nyckel="$(openssl rand -hex 32)"
|
||||
|
||||
# 3. Applicera manifesten (Postgres initieras med schema + append-only-triggers)
|
||||
kubectl apply -k infra/k8s
|
||||
|
||||
# 4. Verifiera
|
||||
kubectl -n guidad-felsokning get pods
|
||||
curl https://app.exempel.se/halsa # → {"status":"ok"} (plattformen)
|
||||
curl https://app.exempel.se/api/openapi.yaml # API-first: hela API-specen
|
||||
terraform output karta # hela systemet i klartext
|
||||
terraform output endpoints # adresser att kontrollera
|
||||
```
|
||||
|
||||
Med Terraform i stället: `cd infra/terraform && terraform apply -var bildtagg=<git-sha>`.
|
||||
Sedan:
|
||||
|
||||
Byt domän och cert-issuer i `infra/k8s/ingress.yaml` (eller `var.doman` i Terraform). Att skapa nya organisationer är öppet i beta — stäng med `REGISTRERING_OPPEN=false` på plattformens Deployment; användare inom en organisation skapas alltid av dess systemadministratör.
|
||||
```sh
|
||||
curl https://app.exempel.se/halsa # → {"status":"ok"}
|
||||
curl https://app.exempel.se/api/openapi.yaml # hela API-specen
|
||||
```
|
||||
|
||||
## Märkesspecifika kopplingar
|
||||
Klustret behöver: en CNI som tillämpar NetworkPolicy, ingress-nginx,
|
||||
cert-manager, en metrics-server och en StorageClass med ReadWriteOnce.
|
||||
|
||||
Varje verkstad har sina egna avtal med tillverkare och dataleverantörer.
|
||||
Kopplingarna konfigureras därför av kunden själv under **Inställningar →
|
||||
Märkesspecifika kopplingar**: systemadministratören väljer leverantör och
|
||||
fyller i sina credentials.
|
||||
Att skapa nya organisationer är stängt som standard
|
||||
(`registrering_oppen = false`); användare inom en organisation skapas
|
||||
alltid av dess systemadministratör.
|
||||
|
||||
* **Uppgifterna når aldrig webbläsaren.** De krypteras med AES-256-GCM
|
||||
(`INTEGRATION_NYCKEL`, 32 byte hex eller base64) innan de skrivs till
|
||||
tabellen `integrationer`, och API:t returnerar hemliga fält maskerade
|
||||
(`••••3456`). Alla uppslag mot leverantören görs av servern.
|
||||
* **Fail closed.** Saknas `INTEGRATION_NYCKEL` sparas ingenting — API:t
|
||||
svarar 503 och inställningssidan säger varför. Inga uppgifter hamnar
|
||||
någonsin i klartext.
|
||||
* **Leverantörer är data, inte kod.** Registret ligger i
|
||||
`services/plattform/integrationer.json` och kan bytas mot en
|
||||
ConfigMap-mount via `INTEGRATIONER_FIL`. Nya märken läggs till genom
|
||||
att beskriva URL-mall, autentiseringstyp och svarsmappning — ingen
|
||||
ombyggnad av applikationen krävs.
|
||||
* **Testresultat loggas på kopplingen.** Varje uppslag skriver
|
||||
`senast_testad` och `senaste_status`, så ett trasigt abonnemang syns i
|
||||
inställningarna i stället för att tyst ge tomma svar.
|
||||
## Databasen: valet som avgör om det finns backup
|
||||
|
||||
`databas_lage` saknar standardvärde med flit.
|
||||
|
||||
| Läge | Backup | Failover | Använd när |
|
||||
| --- | --- | --- | --- |
|
||||
| `extern` | Leverantörens, med PITR | Leverantörens | **Produktion.** Cloud SQL, RDS, Neon, Azure |
|
||||
| `cnpg` | Basbackup 02:30 + WAL-arkiv → objektlagring, PITR | Ja | Produktion när databasen måste ligga i klustret |
|
||||
| `inbyggd` | **Ingen** | Nej | Prov och demo — spärras när `miljo = "produktion"` |
|
||||
|
||||
Går händelseloggen förlorad är det inte "data" som försvinner utan varje
|
||||
ärendes bevisvärde: vad som kontrollerades, av vem, när, med vilken
|
||||
evidens. Det går inte att återskapa i efterhand.
|
||||
|
||||
`cnpg` kräver CloudNativePG-operatorn installerad först — Terraform slår
|
||||
upp dess CRD redan vid plan. I `extern` läge kör ni
|
||||
`infra/postgres-init.sql` mot databasen själva; det är samma fil som
|
||||
integrationstestet kör.
|
||||
|
||||
## Multi-tenant och roller
|
||||
|
||||
@@ -137,18 +130,19 @@ Integrationstestet (`services/plattform/integrationstest.sh`, körs även i CI m
|
||||
|
||||
**CI** (`.github/workflows/ci.yml`): tester, produktionsbygge, integrationstest mot riktig Postgres och verifierande containerbyggen på varje push/PR.
|
||||
|
||||
**CD** (`.github/workflows/publicera.yml` + Argo CD): klustret följer git — ingen CI-process har kubectl-åtkomst.
|
||||
**CD** — två flöden, medvetet åtskilda: en bild i registret är inte samma sak som en bild som kör.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P[Push till main] --> B[Bygg + publicera\n3 bilder till GHCR\ntaggade med git-SHA]
|
||||
B --> O[Uppdatera\ninfra/overlays/produktion\n+ commit till git]
|
||||
O --> A[Argo CD ser ändringen] --> S[Synkar klustret\nprune + selfHeal]
|
||||
P[Push till main] --> B[Publicera:\nbygger 3 bilder\ntaggade med git-SHA] --> G[(GHCR)]
|
||||
G -.-> D[Driftsätt:\nstartas för hand\nmed en tagg]
|
||||
D --> M[miljö: produktion\ngodkännande] --> T[terraform apply] --> K[Klustret]
|
||||
T --> R[Rökkontroll\nhälsa + API-spec]
|
||||
```
|
||||
|
||||
1. Varje main-push bygger de tre bilderna, publicerar till GHCR (`GITHUB_TOKEN`, inga externa hemligheter) och uppdaterar produktions-overlayens taggar med `kustomize edit set image` — overlayen ombyggs som verifiering innan commiten.
|
||||
2. **Argo CD är enda vägen in i klustret.** Bootstrap en gång: installera Argo CD, ersätt repo-URL:en i `infra/gitops/argocd-application.yaml` och `kubectl apply -f` den. Därefter: `prune` tar bort det som försvinner ur git, `selfHeal` återställer manuella klusteravvikelser.
|
||||
3. **Rollback = `git revert`** av gitops-commiten — Argo CD synkar tillbaka föregående SHA-taggade bilder.
|
||||
4. Repo-variabeln `PLATTFORM_URL` (Settings → Variables) styr webbyggets `VITE_PLATTFORM_URL`/`VITE_AI_ORKESTER_URL`. Hemligheten `felsokning-hemligheter` ligger utanför både git och synken.
|
||||
1. **Publicera** vid varje main-push: bygger de tre bilderna och taggar med git-SHA:t (`GITHUB_TOKEN`, inga externa hemligheter).
|
||||
2. **Driftsätt** startas för hand med en tagg, mot GitHub-miljön `produktion` som kan kräva godkännande. Kör `fmt`, `init`, `validate`, `plan`, `apply`, skriver ut kartan och rökkontrollerar hälsa och API-spec. `bara_plan` visar planen utan att applicera.
|
||||
3. **Rollback** = kör Driftsätt igen med en tidigare tagg.
|
||||
|
||||
Kustomize- och Argo CD-vägen är borttagen. Den beskrev samma system en gång till och kunde inte köras samtidigt som Terraform utan att de motarbetade varandra — `selfHeal` återställde det Terraform ändrade och `prune` tog bort det Terraform skapade.
|
||||
|
||||
Manuell `kubectl apply -k infra/k8s` (avsnittet Driftsätta ovan) fungerar fortfarande för miljöer utan Argo CD.
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på st
|
||||
| Instrumentavläsning (visual-first) | ✅ Kameran som universellt gränssnitt: `📷 Instrument` i Dokumentera-panelen fotograferar multimetrar, diagnosskärmar, batteritestare m.m. — bildtolkningen identifierar instrumenttyp och extraherar värden/enheter/felkoder med konfidens per värde; teknikern bekräftar innan något loggas. Originalbilden loggas alltid tillsammans med de strukturerade mätvärdena — strukturerad data ersätter aldrig originalevidensen. Ingen integration mot diagnossystem krävs. |
|
||||
| Utskrift | ✅ Kundrapport och Live Share-vy skrivs ut svart på vitt; interaktiva element döljs automatiskt. Utskriften går genom ECM-kvalitetsgrinden. |
|
||||
| Märkesspecifika kopplingar | ✅ Verkstaden konfigurerar sina egna OEM-/fordonsdataleverantörer under Inställningar med sina egna credentials ([moduler/markesspecifika-kopplingar.md](moduler/markesspecifika-kopplingar.md)): uppgifterna krypteras med AES-256-GCM i vila, returneras alltid maskerade (`••••3456`) och **alla uppslag görs av servern** — leverantörsnycklar når aldrig webbläsaren. Endast systemadministratören hanterar dem, kopplingarna är organisationsknutna och saknas krypteringsnyckeln sparas ingenting alls (fail closed). Leverantörer är data, inte kod: URL-mall, autentiseringstyp (bearer/header/basic/query) och svarsmappning beskrivs i `integrationer.json` (ConfigMap-utbytbar via `INTEGRATIONER_FIL`) — nya märken läggs till utan ombyggnad. Varje uppslag loggar teststatus, så ett utgånget abonnemang syns i inställningarna i stället för att ge tysta tomma svar. Verifierat i integrationstestet (rollstyrning, maskering, kryptering i databasen, organisationsisolering, fail closed). |
|
||||
| Infrastruktur som kod | ✅ `infra/terraform` är systemets definition ([README](../infra/terraform/README.md)): `karta.tf` beskriver hela systemet en gång som data — tjänster, portar, routing, hemligheter per tjänst, dataflöden och gränser — och `terraform output karta` skriver ut samma sak i klartext. Namnrymden är stängd med nätverkspolicyer (bara ingress→tjänster, plattform→postgres, HTTPS ut utom privata nät), Postgres kör med säkerhetskontext, hemligheter kan genereras eller komma från en secrets-hanterare. Kustomize-/Argo CD-vägen finns kvar men de två ska inte köras mot samma kluster — dokumenterat med rekommendation och bytesväg. |
|
||||
| Infrastruktur som kod | ✅ `infra/terraform` är systemets definition ([README](../infra/terraform/README.md)): `karta.tf` beskriver hela systemet en gång som data — tjänster, portar, routing, hemligheter per tjänst, dataflöden och gränser — och `terraform output karta` skriver ut samma sak i klartext. Namnrymden är stängd med nätverkspolicyer (bara ingress→tjänster, plattform→postgres, HTTPS ut utom privata nät), Postgres kör med säkerhetskontext, hemligheter kan genereras eller komma från en secrets-hanterare. **Databasen har tre lägen** och `databas_lage` saknar standardvärde med flit — valet avgör om det finns säkerhetskopiering: `extern` (managerad Postgres, leverantörens PITR — rekommenderat i produktion), `cnpg` (CloudNativePG i klustret: basbackup 02:30 + WAL-arkivering + failover) och `inbyggd` (en volym, ingen backup, spärrad av en precondition när miljön är produktion). Driftsättning är ett eget CI-flöde som startas för hand med en bildtagg mot en miljö med godkännandekrav, kör plan/apply, skriver ut kartan och rökkontrollerar. Kustomize-/Argo CD-vägen är borttagen. |
|
||||
| Öppet API | ✅ Plattforms-API:t är dokumenterat med OpenAPI 3.0 (`services/plattform/openapi.yaml`) — auth, användare, ärenden/händelser (append-only), översikt, publik delning och AI-orkestern, med scheman för alla händelsetyper. Specen valideras maskinellt, paritetstestas mot serverns rutter och serveras live på `GET /api/openapi.yaml`. |
|
||||
|
||||
## Arkitekturprinciper i koden
|
||||
|
||||
Reference in New Issue
Block a user