Projekt-Dashboard — Runbook

Setup-, Deploy- und Betriebs-Anleitung. Geschwister: _index.md (Projekt-Stub), plan.md (Implementations-Plan).

Pre-Deploy-Steps (einmalig, vor cdk deploy DashboardStack)

Step 1 — Google OAuth Client anlegen

Cognito braucht einen Google-OAuth-Client für „Sign in with Google”.

  1. Browser: Google Cloud Console — OAuth Client erstellen
  2. Projekt anlegen (oder existierendes nutzen — Vorschlag „Agentic Ventures Dashboard”)
  3. OAuth 2.0 Client IDsCreate Credentials
  4. Application type: Web application
  5. Name: av-dashboard
  6. Authorized JavaScript origins:
    • https://dXXXXXX.cloudfront.net (kennst du erst nach erstem Deploy — fürs erste leer lassen oder Platzhalter, später updaten)
    • https://dashboard.agenticventures.de
    • http://localhost:4321
  7. Authorized redirect URIs:
    • https://agentic-ventures-dashboard.auth.eu-central-1.amazoncognito.com/oauth2/idpresponse
    • Wichtig: das ist die Cognito-Hosted-UI-Callback, NICHT die Frontend-Callback
  8. Speichern → Client-ID + Client-Secret notieren

Step 2 — Secret in AWS Secrets Manager (av-production)

# 1Password Memo "Google OAuth dashboard" → Client-ID + Secret bereithalten
aws secretsmanager create-secret \
  --name dashboard/google-oauth \
  --description "Google OAuth Client fuer Cognito Sign-in" \
  --secret-string '{"client_id":"123-abc.apps.googleusercontent.com","client_secret":"GOCSPX-..."}' \
  --profile av-prod --region eu-central-1

Verifikation:

aws secretsmanager get-secret-value \
  --secret-id dashboard/google-oauth \
  --query SecretString --output text \
  --profile av-prod --region eu-central-1 | jq .

Step 3 — Cost-Reader-Role im mgmt-Account

Lambda im av-production assumed Role im mgmt-Account um org-weite Cost-Explorer-Daten zu lesen.

Erst Stack einmal deployen (Step 4), dann den Output RefreshLambdaRoleArn notieren, dann diese Role im mgmt-Account anlegen:

# In mgmt-Account (Profile mgmt)
aws iam create-role \
  --role-name dashboard-cost-reader \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"AWS": "<RefreshLambdaRoleArn aus DashboardStack-Output>"},
      "Action": "sts:AssumeRole"
    }]
  }' \
  --profile mgmt
 
aws iam attach-role-policy \
  --role-name dashboard-cost-reader \
  --policy-arn arn:aws:iam::aws:policy/AWSBillingReadOnlyAccess \
  --profile mgmt
 
# Plus inline policy für Cost Explorer
aws iam put-role-policy \
  --role-name dashboard-cost-reader \
  --policy-name CostExplorerRead \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["ce:GetCostAndUsage","ce:GetCostForecast","ce:GetDimensionValues"],
      "Resource": "*"
    }]
  }' \
  --profile mgmt

Anmerkung Cost Explorer: Cost Explorer muss org-weit aktiviert sein, sonst sieht der mgmt-Account nur eigene Kosten. Prüfen:

aws ce get-cost-and-usage \
  --time-period Start=2026-05-01,End=2026-05-14 \
  --granularity DAILY --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=LINKED_ACCOUNT \
  --profile mgmt --region us-east-1

Wenn die Groups alle Sub-Account-IDs zeigen → org-weit aktiv. Sonst: Console → AWS Cost Management → Settings → enable „Include linked accounts”.

Step 4 — Deploy

cd ~/source/agents-platform
AWS_PROFILE=av-prod npm run synth -- DashboardStack          # synth zur Verifikation
AWS_PROFILE=av-prod npx --prefix infra cdk diff DashboardStack
AWS_PROFILE=av-prod npx --prefix infra cdk deploy DashboardStack

Erster Deploy bricht ggf bei der Google-IdP-Resource ab wenn das Secret noch nicht da ist → Step 2 wiederholen, dann nochmal.

Outputs notieren (kommen am Ende des Deploys auf stdout):

  • CloudFrontDomain → fuer Cloudflare-CNAME (Sprint 1.5)
  • CloudFrontDistributionId → fuer Frontend-Deploy-Invalidation
  • FrontendBucketName, DataBucketName
  • UserPoolId, UserPoolClientId, CognitoFullDomain → fuer Frontend .env
  • RefreshLambdaRoleArn → in Step 3 fuer mgmt-Trust-Policy verwenden

Step 5 — Lambda manuell testen (Smoke)

AWS_PROFILE=av-prod aws lambda invoke \
  --function-name agent-dashboard-refresh \
  --region eu-central-1 \
  /tmp/dashboard-out.json
cat /tmp/dashboard-out.json | jq .
 
AWS_PROFILE=av-prod aws s3 ls s3://av-dashboard-data-425924867359/api/data/ --region eu-central-1
AWS_PROFILE=av-prod aws s3 cp s3://av-dashboard-data-425924867359/api/data/aws-costs.json - --region eu-central-1 | jq .

Wenn aws-costs.json echte Zahlen zeigt → Backend live.

Step 6 — Frontend bauen + deployen

av-cockpit hat ein eigenes Deploy-Script das Build + S3-Sync + CloudFront-Invalidation macht:

cd ~/source/av-cockpit
npm install
npm run deploy    # = npm run build && aws s3 sync out/ s3://... + cloudfront invalidation

Alternativ manuell:

cd ~/source/av-cockpit
NEXT_STATIC=1 npm run build
AWS_PROFILE=av-production aws s3 sync out/ s3://av-dashboard-frontend-425924867359/ --delete --region eu-central-1
AWS_PROFILE=av-production aws cloudfront create-invalidation \
  --distribution-id <CloudFrontDistributionId> --paths "/*"

Step 7 — DNS-Cutover (Sprint 1.5)

Cloudflare-DNS:

  • CNAME dashboard<CloudFrontDomain>.cloudfront.net
  • Proxied: OFF (DNS only — CloudFront macht eigenes TLS, Cloudflare-Proxy würde es brechen)

ACM-Cert in us-east-1 fuer dashboard.agenticventures.de:

  • Wird via Stack-Update angefuegt (TODO Sprint 1.5)
  • Validation via Cloudflare-CNAME (manueller Schritt)

Debug

Lambda-Logs

AWS_PROFILE=av-prod aws logs tail /aws/lambda/agent-dashboard-refresh --follow --region eu-central-1

Cognito-Sign-Up fehlgeschlagen

Pre-Sign-Up-Lambda blockiert? Log lesen:

AWS_PROFILE=av-prod aws logs tail /aws/lambda/av-dashboard-presignup --follow --region eu-central-1

Häufige Ursachen:

  • ALLOWED_EMAIL passt nicht (in Lambda env ALLOWED_EMAIL checken)
  • Google-Account vs hello@-Mail-Mismatch (case-sensitivity? Lambda lowert beides)

Frontend zeigt White-Screen

  1. Browser-Devtools öffnen → Network-Tab → 404? 403?
  2. CloudFront-Invalidation laufen lassen
  3. localStorage purgen → erneut einloggen

Rollback

Daten-Bucket wiederherstellen

Data-Bucket ist versioned, 30 Tage Lifecycle:

AWS_PROFILE=av-prod aws s3api list-object-versions \
  --bucket av-dashboard-data-425924867359 \
  --prefix api/data/aws-costs.json --region eu-central-1
# vorherige Version restoren mit aws s3api copy-object --copy-source <bucket>/<key>?versionId=<id>

Stack komplett zurueckrollen

AWS_PROFILE=av-prod npx --prefix ~/source/agents-platform/infra cdk destroy DashboardStack

Hinweis: Buckets haben RemovalPolicy.RETAIN — bleiben übrig und müssen manuell gelöscht werden falls gewollt.

Cost-Watching

Erwartet Sprint 1 Kosten/Monat:

  • CloudFront PriceClass 100: ~$0 fuer Marvin-only-Traffic
  • S3 Storage (Frontend + Data): <100 MB → ~$0.003
  • Lambda 3x/Tag × 30 Tage × <30s × 1024 MB: ~$0.05
  • Cognito User Pool (1 MAU): kostenlos im Free-Tier
  • Total: <$2/Monat

Bei > $10/Monat: prüfen ob Cron öfter läuft als erwartet oder Bucket-Sync ungewollt scant.

Agent-Cockpit Live-State (seit 2026-05-21)

Live-Sync der state.md-Files pro Projekt nach S3 + CloudFront.

Pfade

KomponentePfad
Vault SoTintern/projekte/<slug>/state.md
Push-Script~/source/av-cockpit/scripts/push-state.mjs
Local Mirror~/source/av-cockpit/public/api/states/<slug>.json
S3s3://av-dashboard-data-425924867359/api/states/<slug>.json
Live URLhttps://dashboard.agenticventures.de/api/states/<slug>.json

CloudFront-Config

  • Distribution: E3KFMPWSKO68UU
  • Cache-Policy: av-states-livesync (ID fd4aa170-ccda-4eb3-bf54-5ed2676762d8) — MinTTL=0, DefaultTTL=10, MaxTTL=30
  • Behavior: /api/states/*DashboardStackCDNOrigin2E8B97E77 (data-Bucket)
  • Auth: Cloudflare Access (gleiche Schutz-Schicht wie Rest der Site)

Push-Commands

cd ~/source/av-cockpit
npm run push-state -- <slug>             # ein Projekt → S3 + local
npm run push-all-states                  # alle Projekte mit state.md
npm run push-state -- <slug> --local     # nur local (kein S3, kein AWS-Login nötig)
npm run push-state -- <slug> --force     # ignoriert Diff-Check

Diff-Check: Push skipped wenn S3-Object-Hash mit lokalem Body übereinstimmt (Metadata.content-sha256).

Troubleshooting

Push fehlt SSO-Token:

✗  <slug>  ExpiredToken: The provided token has expired

aws sso login --profile av-production

Frontend zeigt „verbinde…” statt „live”:

  • Browser DevTools → Network: GET /api/states/<slug>.json Status?
  • 200: SWR-Bug oder JSON-Parse-Fehler — Console checken
  • 404: state.md nicht gepusht → npm run push-state -- <slug> laufen lassen
  • 302 zu cloudflareaccess.com: Marvin nicht eingeloggt → CF Access Login

CloudFront cached zu lang (>30s):

aws cloudfront create-invalidation --distribution-id E3KFMPWSKO68UU \
  --paths "/api/states/<slug>.json" --profile av-production

Kostet $0.005 pro Invalidation, in Praxis nie nötig.

state.md gepflegt, Dashboard zeigt veralteten Stand:

  1. Push gelaufen? → S3 prüfen: aws s3 ls s3://av-dashboard-data-425924867359/api/states/
  2. CloudFront-TTL? → max 10s warten oder F5 mit Cache-Bust
  3. Browser-Tab nicht sichtbar? → SyncIndicator zeigt „paused”, Page-Visibility-API pausiert SWR

Kosten-Monitoring

Bestehende AWS Cost Explorer Konfiguration deckt api/states/* automatisch ab (gleicher Bucket wie api/data/*).

Ziel: <0.10. Worst-Case (Polling 24/7 ohne Visibility-Pause): ~$4/Monat. Siehe plan-agent-cockpit.md §Kosten.