Driftsättningspaketen: agentanvisningar och paketering i sex zip-steg

Skriptet deploy/paketera.sh skär plattformen i numrerade paket i
driftsättningsordning: plan med SHA256-summor, AWS-basen, backend-
bilderna, webbilden, arbetslasten och slutkontrollen. Varje paket bär
sin egen ANVISNING-AGENT.md med steg, verifiering och stoppvillkor,
plus fillista och versionsstämpel under paketunik väg så att alla
paket kan packas upp i samma arbetskatalog.

Ordningen är motiverad i planen: basen före bilderna (registret måste
finnas), bilderna före arbetslasten (apply drar dem), verifieringen
sist. 02 och 03 är utpekat parallelliserbara med samma bildtagg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-07 11:47:26 +00:00
parent e502de8276
commit 7e1793b8ac
8 changed files with 479 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
# Driftsättningspaketen
Paketeringen skär plattformen i numrerade zip-paket, ett per
driftsättningssteg, avsedda att utföras av agenter i ordning. Varje
paket bär sin egen `ANVISNING-AGENT.md` — den som packar upp ett paket
ska inte behöva något annat än paketet och det föregående stegets
utdata.
```sh
bash felsokning/deploy/paketera.sh <målkatalog>
```
| Paket | Innehåll | Steg |
| --- | --- | --- |
| `00-plan.zip` | Driftsättningsplanen + SHA256-summor för övriga paket | Läses först, verifierar leveransen |
| `01-aws-bas.zip` | `infra/aws` + `infra/postgres-init.sql` | Terraform: VPC, EKS, Aurora, S3, ECR, KMS, hemligheter, domän, larm |
| `02-tjanster.zip` | `services/` — plattform, ai-orkester, gemensam | Bygg och publicera backendbilderna till ECR, initiera schemat |
| `03-webb.zip` | `app/` (utan `node_modules`/`dist`) + `supabase/` | Bygg och publicera webbilden |
| `04-arbetslast.zip` | `infra/terraform` | Terraform: arbetslasten i klustret, ingress, DNS |
| `05-verifiering.zip` | `docs/` | Slutkontroll av den driftsatta miljön, driftdokumentation |
Ordningen är driftsättningsordningen och den är inte förhandlingsbar:
basen före bilderna (registret måste finnas), bilderna före arbetslasten
(poddarna drar dem vid apply), verifieringen sist. Skälen står i
respektive anvisning.
Vad som medvetet INTE packas: `node_modules` (återskapas ur
`package-lock.json`), `app/dist` (byggs i webbilden med miljöns egna
byggargument), `.git`, och värdapplikationens rotkataloger — ALVA:s
plattform är `felsokning/`.
Anvisningstexterna ligger i `anvisningar/` och versionshanteras här;
skriptet lägger in dem i paketen under `paket/<paketnamn>/` tillsammans
med `INNEHALL.txt` (fillista) och `PAKET.txt` (version, git-SHA,
byggtid) — vägen är paketunik så att alla paket kan packas upp i samma
arbetskatalog. Summorna i `00-plan.zip` beräknas över de färdiga
paketen — verifiera dem innan något packas upp.
+54
View File
@@ -0,0 +1,54 @@
# Driftsättningsplan · ALVA på AWS
Detta paket läses först. Det innehåller planen och SHA256-summorna för
de fem arbetspaketen. Allt är egenhostat i ett eget AWS-konto — ingen
extern byggtjänst, inget externt register.
## Leveransen
| Paket | Steg | Utförs av |
| --- | --- | --- |
| `01-aws-bas.zip` | AWS-basen: VPC, EKS, Aurora, S3, ECR, KMS, Secrets Manager, Route 53, larm | Agent med AWS-behörighet |
| `02-tjanster.zip` | Backendbilderna (plattform, ai-orkester) byggs, testas och publiceras; databasschemat initieras | Agent med Docker + ECR-push |
| `03-webb.zip` | Webbilden byggs med miljöns byggargument och publiceras | Agent med Docker + ECR-push |
| `04-arbetslast.zip` | Arbetslasten i klustret: tjänster, ingress, DNS, nätverkspolicyer | Agent med kluster-behörighet |
| `05-verifiering.zip` | Slutkontroll av den driftsatta miljön | Valfri agent, läsbehörighet räcker |
## Ordningen och varför
`01` före `02`/`03`: bilderna kan inte publiceras förrän registret
finns. `02`/`03` före `04`: arbetslastens poddar drar bilderna vid
apply — en apply mot tomma repon ger poddar i `ImagePullBackOff`.
`05` sist, alltid: ett steg utan verifiering är inte utfört.
`02` och `03` är oberoende av varandra och får köras parallellt av två
agenter; alla andra steg är sekventiella.
## Innan något packas upp
```sh
sha256sum -c SHA256SUMS.txt
```
Stämmer inte en summa: stanna och rapportera. Packa upp paketen i EN
gemensam arbetskatalog — vägarna är relativa `felsokning/` och lägger
sig i ett träd (`infra/`, `services/`, `app/`, `docs/`). Varje pakets
anvisning, fillista och versionsstämpel ligger under
`paket/<paketnamn>/` och krockar därför aldrig med de andras.
## Verktyg som förutsätts
Terraform (version enligt `versions.tf`), AWS CLI v2 med behörighet i
målkontot, Docker, Node 22, `kubectl`, `jq`, `psql`. Region enligt
`variables.tf` (standard `eu-north-1`).
## Regler som gäller varje steg
1. **Fortsätt aldrig förbi en misslyckad verifiering.** Rapportera vad
som fallerade och stanna — nästa paket förutsätter det förra.
2. **Hemligheter går aldrig in i Terraform, git eller ett paket.**
De sätts direkt i Secrets Manager; tillståndsfilen får aldrig se dem.
3. **En bildtagg pekar på ett bygge, för alltid.** Registret har
oföränderliga taggar; välj en ny tagg per bygge (git-SHA:t i
`PAKET.txt` är rätt val) och använd SAMMA tagg i steg 02, 03 och 04.
4. **Rapportera utfall ordagrant** — vad som kördes, vad som verifierades,
vad som återstår. `terraform output karta` är kvittot i steg 01 och 04.
@@ -0,0 +1,71 @@
# Paket 01 · AWS-basen
Detta paket lägger grunden: nätverk i tre lager, EKS-klustret, Aurora
PostgreSQL, S3 för bilagor, ECR, KMS-nycklar, Secrets Manager, Route 53
och larm. Allt efterföljande läser den här basens utdata.
## Förutsättningar
AWS-behörighet i målkontot, Terraform enligt `infra/aws/versions.tf`.
Läs `infra/aws/README.md` — den förklarar varje val; denna anvisning
är utförandeordningen.
## Steg
```sh
cd infra/aws
terraform init
terraform validate
terraform plan -out plan.ut # granska: inga destruktiva ändringar väntas i en ny miljö
terraform apply plan.ut
terraform output karta # hela basen i klartext — bifoga i rapporten
```
Variabler som ska sättas medvetet (via `-var` eller en tfvars-fil som
INTE innehåller hemligheter): `doman`, `zon_id`, `larm_epost`, och
`tillatna_api_cidr` snävat till de adresser som ska nå API-servern.
Standardvärdet är öppet enbart för att en ny miljö ska gå att nå alls.
## Efter apply — fyra saker Terraform inte gör
1. **Claude-nyckeln** in i Secrets Manager (Terraform ska inte känna
den):
```sh
aws secretsmanager put-secret-value \
--secret-id felsokning-produktion/app \
--secret-string "$(aws secretsmanager get-secret-value --secret-id felsokning-produktion/app \
--query SecretString --output text | jq '.anthropic_api_key = "sk-ant-…"')"
```
2. **Databasschemat.** Aurora ligger i ett datalager utan routing ut;
kör `infra/postgres-init.sql` inifrån klustret:
```sh
aws eks update-kubeconfig --name <klusternamn ur karta>
kubectl run psql --rm -i --image=postgres:16 --restart=Never -- \
psql "<databas_url ur terraform output databas_url>" < ../postgres-init.sql
```
Det är samma fil som integrationstestet kör — de kan inte glida isär.
3. **Verifiera att `tillatna_api_cidr` är snävat** om det inte gjordes
före apply.
4. **Bekräfta larmprenumerationen** — SNS skickar ett
bekräftelsemejl till `larm_epost`; utan klick går larmen ingenstans.
## Verifiering — allt ska vara sant innan steget rapporteras klart
```sh
terraform output karta # kvar_att_gora ska vara tomt utom det som uttryckligen skjutits upp
kubectl get nodes # samtliga Ready
aws ecr describe-repositories \
--query 'repositories[].repositoryName' # tre repon: web, plattform, ai-orkester
kubectl run schema --rm -i --image=postgres:16 --restart=Never -- \
psql "<databas_url>" -c "\dt" # tabellerna ur postgres-init.sql finns
```
Rapportera `terraform output karta` ordagrant, registeradressen och
klusternamnet — steg 0204 behöver dem.
## Stoppvillkor
Faller `terraform apply`, kör inte om i blindo: läs felet, åtgärda
orsaken, kör `plan` igen och granska. Ett kluster utan Ready-noder
eller ett `kvar_att_gora` med oförklarade punkter betyder stopp, inte
vidare till paket 02.
@@ -0,0 +1,67 @@
# Paket 02 · Backendtjänsterna
Detta paket innehåller `services/`: plattformstjänsten (auth,
händelse-API, delning, fakturering), AI-orkestern och den delade koden
i `gemensam/`. Här byggs och publiceras de två backendbilderna.
Kräver att paket 01 är klart och verifierat: registret och databasen
finns, och du har registeradressen ur dess rapport. Paket 03 är
oberoende av detta paket och får köras parallellt av en annan agent —
men med SAMMA bildtagg.
## Steg 1 — testa före bygge
Grönt lokalt är villkoret för att bygga över huvud taget:
```sh
cd services/plattform
npm ci
bash integrationstest.sh # hela ärendeflödet mot en lokal server
bash aterstallningstest.sh # säkerhetskopian går att läsa tillbaka
```
## Steg 2 — bygg bilderna
Byggkontexten är `services/` — Dockerfilerna hämtar den delade
observationsmodulen därifrån. Taggen är git-SHA:t ur `PAKET.txt`;
registret har oföränderliga taggar, så en tagg som redan finns kan
inte återanvändas.
```sh
cd services
REG=<registeradress ur paket 01> # t.ex. …dkr.ecr.eu-north-1.amazonaws.com
TAGG=<git-SHA ur PAKET.txt>
docker build -f plattform/Dockerfile -t "$REG/felsokning-plattform:$TAGG" .
docker build -f ai-orkester/Dockerfile -t "$REG/felsokning-ai-orkester:$TAGG" .
```
Exakta reponamn: `aws ecr describe-repositories` — avviker de från
raderna ovan gäller registrets namn, inte anvisningens.
## Steg 3 — publicera
```sh
aws ecr get-login-password | docker login --username AWS --password-stdin "$REG"
docker push "$REG/felsokning-plattform:$TAGG"
docker push "$REG/felsokning-ai-orkester:$TAGG"
```
## Verifiering
```sh
aws ecr describe-images --repository-name felsokning-plattform \
--query 'imageDetails[].imageTags' | grep "$TAGG"
aws ecr describe-images --repository-name felsokning-ai-orkester \
--query 'imageDetails[].imageTags' | grep "$TAGG"
docker run --rm "$REG/felsokning-plattform:$TAGG" node --version # bilden startar
```
Rapportera taggen och båda bildernas digest — paket 04 sätter taggen i
`bildtagg` och digesten är svaret på "vilken kod kör i produktion".
## Stoppvillkor
Rött integrationstest eller återställningstest: bygg ingenting.
Misslyckad push mot befintlig tagg betyder att taggen redan är använd —
välj en ny tagg (nytt SHA), skriv aldrig över.
+71
View File
@@ -0,0 +1,71 @@
# Paket 03 · Webbklienten
Detta paket innehåller `app/` (källkod, utan `node_modules` och `dist`)
och `supabase/` (värdapplikationens migrationsunderlag). Här byggs och
publiceras webbilden — en statisk SPA bakom oprivilegierad nginx.
Kräver att paket 01 är klart. Oberoende av paket 02 — får köras
parallellt, men med SAMMA bildtagg.
## Steg 1 — testa före bygge
```sh
cd app
npm ci
npx vitest run # hela sviten ska vara grön
npm run typkontroll
npm run lint # jämför mot baslinjen i rapporten från leverantören
```
## Steg 2 — bygg bilden
Vite-variablerna bakas in vid byggtillfället och skickas som
byggargument — en färdig bild är alltså knuten till sin miljö.
Plattforms- och orkesteradresserna är miljöns publika adresser (ur
paket 01:s `karta`); Supabase-värdena är värdapplikationens och hämtas
ur organisationens Supabase-projekt.
```sh
cd app
REG=<registeradress ur paket 01>
TAGG=<git-SHA ur PAKET.txt> # samma tagg som paket 02
docker build \
--build-arg VITE_PLATTFORM_URL="https://<plattformens adress>" \
--build-arg VITE_AI_ORKESTER_URL="https://<orkesterns adress>" \
--build-arg VITE_SUPABASE_URL="<supabase-url>" \
--build-arg VITE_SUPABASE_PUBLISHABLE_KEY="<publicerbar nyckel>" \
--build-arg VITE_SUPABASE_PROJECT_ID="<projekt-id>" \
-t "$REG/felsokning-web:$TAGG" .
```
Den publicerbara Supabase-nyckeln är publik per definition — men den
är det ENDA nyckelvärde som får förekomma i ett byggargument. Allt
hemligt bor i Secrets Manager och når bara backendtjänsterna.
## Steg 3 — publicera
```sh
aws ecr get-login-password | docker login --username AWS --password-stdin "$REG"
docker push "$REG/felsokning-web:$TAGG"
```
## Verifiering
```sh
aws ecr describe-images --repository-name felsokning-web \
--query 'imageDetails[].imageTags' | grep "$TAGG"
# Bilden serverar och svarar:
docker run --rm -d -p 8080:8080 --name webbprov "$REG/felsokning-web:$TAGG"
curl -fsS http://127.0.0.1:8080/ | grep -q "<div id=\"root\"" && echo "serverar"
docker rm -f webbprov
```
Rapportera taggen och bildens digest.
## Stoppvillkor
Röda tester, typfel eller lintfel utöver den kända baslinjen: bygg
ingenting. Saknade byggargument ger en bild som kraschar vid start
("supabaseUrl is required") — en sådan bild publiceras inte.
@@ -0,0 +1,57 @@
# Paket 04 · Arbetslasten
Detta paket innehåller `infra/terraform`: tjänsterna i klustret,
ingress med ALB och ACM-certifikat, DNS, nätverkspolicyer, External
Secrets och den självhostade git-tjänsten. Läs `karta.tf` först — den
beskriver hela systemet som data, och resten av filerna läser därifrån.
Kräver att paket 01 är klart (basen ger utdata som `05-aws.tf` läser)
och att paket 02 och 03 är klara (bilderna finns i registret — en
apply mot tomma repon ger `ImagePullBackOff`).
## Steg
```sh
cd infra/terraform
cp terraform.tfvars.exempel terraform.tfvars
# Fyll i: domän, register, bildtagg = taggen ur paket 02/03, repliker.
# INGA hemligheter i filen — de bor i Secrets Manager och speglas av
# External Secrets; Terraform ser aldrig ett värde.
terraform init
terraform validate
terraform plan -out plan.ut # granska innan apply
terraform apply plan.ut
terraform output karta # vad kör var, vem ser vilken hemlighet — bifoga i rapporten
```
## Verifiering
```sh
kubectl get pods -A # alla Running/Completed, inga omstartsloopar
kubectl get pods -n <namnrymd> -o wide # web, plattform, ai-orkester uppe i angivet antal repliker
kubectl get ingress -n <namnrymd> # ALB-adress tilldelad
aws acm list-certificates \
--query 'CertificateSummaryList[].{d:DomainName,s:Status}' # ISSUED för domänen
# Hälsokontrollerna genom hela kedjan (DNS → ALB → tjänst):
curl -fsS https://<plattformens adress>/halsa
curl -fsS https://<orkesterns adress>/halsa
curl -fsS https://<webbadressen>/ | grep -q "<div id=\"root\""
```
DNS-propagering kan ta några minuter efter apply — skilj på "inte än"
(avvakta, försök igen) och "fel" (ändra ingenting, rapportera).
## Rollback
Kör om med föregående `bildtagg` i `terraform.tfvars` och `apply`
igen. Taggarna är oföränderliga, så en tidigare tagg är exakt det
bygge den alltid varit.
## Stoppvillkor
Poddar i `ImagePullBackOff`: fel tagg eller paket 02/03 inte klara —
tillbaka dit, inte vidare. `CrashLoopBackOff` i plattformen: läs
`kubectl logs`; saknade hemligheter betyder att paket 01 punkt 1 inte
utfördes. Verifieringen ska vara grön i sin helhet innan paket 05.
@@ -0,0 +1,59 @@
# Paket 05 · Slutkontroll
Detta paket innehåller dokumentationen (`docs/`) och den samlade
slutkontrollen. Grundhållningen är plattformens egen: ett steg utan
verifiering är inte utfört, och en driftsättning som inte kan granskas
i efterhand är inte klar.
Kräver att paket 0104 rapporterats klara. Läsbehörighet räcker —
detta steg ändrar ingenting.
## Kontrollerna
**1. Kartan stämmer med verkligheten.**
```sh
cd infra/terraform && terraform output karta
kubectl get pods -n <namnrymd> -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
```
Varje pod kör exakt den bild och tagg som paket 02/03 rapporterade —
jämför digest, inte bara tagg.
**2. Kedjan utifrån.**
```sh
curl -fsS https://<plattformens adress>/halsa
curl -fsS https://<orkesterns adress>/halsa
curl -fsS https://<webbadressen>/ | grep -q "<div id=\"root\""
curl -fsSI https://<webbadressen>/ | grep -i strict-transport-security
```
**3. Grinden spärrar på servern.** Skapa ett provärende via API:t och
begär avslut utan uppfyllda krav — svaret ska vara hindren, inte ett
förseglat ärende. (Flödet i detalj: `docs/OPERATIONS.md`.)
**4. Säkerhetskopiering och återställning.**
```sh
aws rds describe-db-clusters --query 'DBClusters[].{id:DBClusterIdentifier,pitr:EarliestRestorableTime}'
```
PITR-fönstret finns. Larmet för utebliven backupmätpunkt är aktivt i
CloudWatch — det larmar på SAKNAD data, kontrollera att det inte redan
larmar.
**5. Larmen är beväpnade.** Fyra larm i CloudWatch, ingen i `ALARM`,
och SNS-prenumerationen på `larm_epost` är bekräftad, inte
`PendingConfirmation`.
**6. Hemligheterna ligger rätt.** `terraform.tfvars` i paket 04
innehåller inga nyckelvärden; `kubectl get secret -n <namnrymd>` visar
speglade hemligheter från External Secrets, inte handlagda.
## Rapporten
Slutrapporten listar varje kontroll med utfall och belägg (kommandot
och dess svar), bildtagg + digest per tjänst, och det som medvetet
lämnats: obekräftade prenumerationer, öppna CIDR, uppskjutna punkter
ur paket 01. En avvikelse gör inte rapporten misslyckad — en
orapporterad avvikelse gör det.
Driftdokumentationen som gäller därefter: `docs/OPERATIONS.md`
(drift), `docs/ALVA-SYSTEMBESKRIVNING.md` (systemet),
`docs/GARANTISTANDARD-FGS.md` (garantiflödet).
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Paketerar plattformen i numrerade zip-paket för agentdriven
# driftsättning på AWS. Se README.md i samma katalog.
#
# Paketen byggs i en arbetskatalog och flyttas färdiga till målet —
# ett avbrutet skript lämnar aldrig ett halvt paket där någon kan
# hitta det. Summorna i 00-plan beräknas över de färdiga paketen,
# därför byggs 00 sist trots att det läses först.
set -euo pipefail
ROT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # felsokning/
UT="${1:?ange målkatalog för paketen}"
mkdir -p "$UT"
VERSION="$(node -e 'import(process.argv[1]).then(m => console.log(m.PLATTFORMSVERSION))' \
"$ROT/services/gemensam/version.mjs" 2>/dev/null || echo "okänd")"
SHA="$(git -C "$ROT" rev-parse --short HEAD 2>/dev/null || echo "utan-git")"
STAMPEL="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# paketera <namn> <anvisningsfil> <väg>... — vägarna är relativa ROT.
paketera() {
local namn="$1" anvisning="$2"; shift 2
local arbets; arbets="$(mktemp -d)"
# node_modules återskapas ur package-lock; dist byggs i webbilden.
tar -C "$ROT" -cf - --exclude=node_modules --exclude=app/dist "$@" |
tar -C "$arbets" -xf -
# Metafilerna ligger under paket/<namn>/ så att alla paket kan packas
# upp i SAMMA arbetskatalog utan att skriva över varandras.
mkdir -p "$arbets/paket/$namn"
cp "$ROT/deploy/anvisningar/$anvisning" "$arbets/paket/$namn/ANVISNING-AGENT.md"
printf '%s · %s · paketerad %s · paket %s\n' \
"$VERSION" "$SHA" "$STAMPEL" "$namn" > "$arbets/paket/$namn/PAKET.txt"
(cd "$arbets" && find . -type f | sort | sed 's|^\./||') > "$arbets/paket/$namn/INNEHALL.txt"
(cd "$arbets" && zip -qrX "$namn.zip" .)
mv "$arbets/$namn.zip" "$UT/"
rm -rf "$arbets"
du -h "$UT/$namn.zip" | awk '{print " " $2 " " $1}'
}
echo "paketerar $VERSION ($SHA) → $UT"
rm -f "$UT"/0[0-5]-*.zip
paketera 01-aws-bas 01-aws-bas.md infra/aws infra/postgres-init.sql
paketera 02-tjanster 02-tjanster.md services
paketera 03-webb 03-webb.md app supabase
paketera 04-arbetslast 04-arbetslast.md infra/terraform
paketera 05-verifiering 05-verifiering.md docs
# 00-plan: planen + summorna över de fem färdiga paketen.
arbets="$(mktemp -d)"
cp "$ROT/deploy/anvisningar/00-plan.md" "$arbets/DRIFTSATTNING.md"
printf '%s · %s · paketerad %s · paket 00-plan\n' \
"$VERSION" "$SHA" "$STAMPEL" > "$arbets/PAKET.txt"
(cd "$UT" && sha256sum 0[1-5]-*.zip) > "$arbets/SHA256SUMS.txt"
(cd "$arbets" && zip -qrX 00-plan.zip .)
mv "$arbets/00-plan.zip" "$UT/"
rm -rf "$arbets"
du -h "$UT/00-plan.zip" | awk '{print " " $2 " " $1}'
echo "klart — läs 00-plan.zip först"