Public: anyone can read this; nobody can change it.
germinate: ship Grafana observability to daughters (#6) #8
Check · Review · Land · Live — It conflicts with its base.
feat/observability-grafana → main 1 commit
A guest
This pull comes from a guest: its preview runs without the app's variables or a forge token until a person who can write here approves it.
This pull waits for a person who can write here to review it; it never lands on its own.
4 files
changed +78
−6
bin/germinate +19 −3
| @@ -28,6 +28,9 @@ source "${MITOSIS_LIB:-$(dirname "$0")/lib.sh}" | ||
| 28 | 28 | : "${FORGEJO_USER:=plat}"; : "${FORGEJO_PASSWORD:=platplatplat}"; : "${FORGEJO_EMAIL:[email protected]}" |
| 29 | 29 | : "${FLUX_NS:=flux-system}"; : "${FLUX_PATH:=clusters/local}"; : "${FLUX_BRANCH:=main}" |
| 30 | 30 | : "${CIBTAG:=0.1.0}" # ci-builder image tag the workflows container: onto |
| 31 | : "${MONITORING:=}" # non-empty (e.g. MONITORING=1) ships the observability | |
| 32 | # stack (Prometheus+Grafana) in a LEAN daughter; the | |
| 33 | # full profile always includes it. | |
| 31 | 34 | # BYO claude credentials (default empty) — sealed into plat-agents-secrets so a |
| 32 | 35 | # daughter launched with one can build apps the instant it reconciles. Captured |
| 33 | 36 | # into named locals so we can report (NOT echo) whether agents are credentialed, |
| @@ -75,7 +78,7 @@ for b in "$WORK/seed/repos/"*.bundle; do | ||
| 75 | 78 | base=$(basename "$b" .bundle); git clone -q --branch "$FLUX_BRANCH" "$b" "$WORK/r-${base#*__}"; REPONAMES+=("${base%%__*}/${base#*__}") |
| 76 | 79 | done |
| 77 | 80 | GITOPS_DIR="$WORK/r-gitops" |
| 78 | [ "$PROFILE" = lean ] && trim_gitops_lean "$GITOPS_DIR" | |
| 81 | [ "$PROFILE" = lean ] && trim_gitops_lean "$GITOPS_DIR" "$MONITORING" | |
| 79 | 82 | |
| 80 | 83 | # Pin dind's bridge MTU to the cluster overlay so image builds' large downloads |
| 81 | 84 | # aren't silently dropped (k3d/flannel overlay = 1450; dind otherwise defaults |
| @@ -94,8 +97,10 @@ fork_rekey_sops "$GITOPS_DIR" "$FORK_PUB" | ||
| 94 | 97 | # admin password and thread it through helm/flux-git-auth so they agree with the |
| 95 | 98 | # sealed forgejo-admin + plat-mcp-secrets PLAT_AUTH/GITOPS_REMOTE. The BYO claude |
| 96 | 99 | # credential (BYO_ANTHROPIC/BYO_OAUTH) is sealed into plat-agents-secrets here. |
| 97 | FORK_ADMIN_PASSWORD=$(ANTHROPIC_API_KEY="$BYO_ANTHROPIC" CLAUDE_CODE_OAUTH_TOKEN="$BYO_OAUTH" \ | |
| 98 | fork_regen_secrets "$GITOPS_DIR" "$DOMAIN" | sed -n 's/^FORK_ADMIN_PASSWORD=//p') | |
| 100 | _REGEN_OUT=$(ANTHROPIC_API_KEY="$BYO_ANTHROPIC" CLAUDE_CODE_OAUTH_TOKEN="$BYO_OAUTH" \ | |
| 101 | fork_regen_secrets "$GITOPS_DIR" "$DOMAIN") | |
| 102 | FORK_ADMIN_PASSWORD=$(printf '%s\n' "$_REGEN_OUT" | sed -n 's/^FORK_ADMIN_PASSWORD=//p') | |
| 103 | FORK_GRAFANA_ADMIN_PASSWORD=$(printf '%s\n' "$_REGEN_OUT" | sed -n 's/^FORK_GRAFANA_ADMIN_PASSWORD=//p') | |
| 99 | 104 | [ -n "$FORK_ADMIN_PASSWORD" ] || { echo "FATAL: failed to generate admin password"; exit 1; } |
| 100 | 105 | FORGEJO_PASSWORD="$FORK_ADMIN_PASSWORD" |
| 101 | 106 | fork_set_identity_secrets "$GITOPS_DIR" "$DOMAIN" # encrypted identity fields |
| @@ -307,6 +312,11 @@ if fork_register_oauth "$GITOPS_DIR" "$FJ" "$FJA" "$DOMAIN"; then | ||
| 307 | 312 | else |
| 308 | 313 | echo " WARN: OAuth registration failed — mcp login will be broken until re-registered" |
| 309 | 314 | fi |
| 315 | # Grafana SSO: when the observability stack is shipped (full profile, or lean with | |
| 316 | # MONITORING set), register its own sovereign Forgejo OAuth app and seal the client | |
| 317 | # id/secret into grafana-oauth. fork_register_grafana_oauth no-ops when absent. | |
| 318 | fork_register_grafana_oauth "$GITOPS_DIR" "$FJ" "$FJA" "$DOMAIN" \ | |
| 319 | || echo " WARN: grafana OAuth registration failed — Grafana SSO broken until re-registered" | |
| 310 | 320 | MCPTAG=$(grep -oE 'tag: *"[0-9][0-9.]*"' "$GITOPS_DIR/clusters/local/platform/mcp/ocirepository.yaml" 2>/dev/null | grep -oE '[0-9][0-9.]*' | head -1) |
| 311 | 321 | AGENTSTAG=$(grep -oE 'tag: *"[0-9][0-9.]*"' "$GITOPS_DIR/clusters/local/platform/agents/ocirepository.yaml" 2>/dev/null | grep -oE '[0-9][0-9.]*' | head -1) |
| 312 | 322 | |
| @@ -513,6 +523,12 @@ echo " open https://git.$DOMAIN , sign in as '$FORGEJO_USER', open $FORGEJO | ||
| 513 | 523 | echo " → 'Use this template' (name + description = the spec) — an agent builds it," |
| 514 | 524 | echo " opens PR #1, a validator browser-tests it, and it ships to <app>-$FORGEJO_USER.$DOMAIN." |
| 515 | 525 | echo "" |
| 526 | if [ -f "$GITOPS_DIR/clusters/local/platform/monitoring/kustomization.yaml" ]; then | |
| 527 | echo " Observability:" | |
| 528 | echo " Grafana https://grafana.$DOMAIN — core dashboards preloaded; sign in" | |
| 529 | echo " with Forgejo, or break-glass admin / ${FORK_GRAFANA_ADMIN_PASSWORD:-<sealed>}" | |
| 530 | echo "" | |
| 531 | fi | |
| 516 | 532 | if [ -z "$BYO_ANTHROPIC" ] && [ -z "$BYO_OAUTH" ]; then |
| 517 | 533 | cat <<EOF |
| 518 | 534 | ┌──────────────────────────────────────────────────────────────────────────┐ |
bin/lib.sh +56 −3
| @@ -68,13 +68,16 @@ fork_rewrite_identity() { # $1 = gitops clone, $2 = parent domain (hint), $3 = | ||
| 68 | 68 | rewrite_identity_domain "" "" "" |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | trim_gitops_lean() { # $1 = path to a working gitops clone | |
| 72 | local g="$1" | |
| 71 | trim_gitops_lean() { # $1 = gitops clone, $2 = include monitoring (non-empty keeps the observability stack) | |
| 72 | local g="$1" mon="" | |
| 73 | 73 | # 1) platform kustomization → core only, and WITHOUT keda (keda becomes its own |
| 74 | 74 | # Flux Kustomization below so its CRDs install before forgejo's ScaledJob). |
| 75 | 75 | printf 'apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n - coredns\n - plat-system\n - forgejo\n - plat-storage\n - mcp\n - agents\n - registry-node-config\n - registry-pull-secret.yaml\n' \ |
| 76 | 76 | > "/clusters/local/platform/kustomization.yaml" |
| 77 | rm -rf "/clusters/local/platform/monitoring" "/clusters/local/platform/grafana" "/clusters/local/platform/pgadmin" | |
| 77 | # grafana/pgadmin are legacy dirs always dropped from lean; monitoring is kept | |
| 78 | # only when the caller opted in (MONITORING), else dropped to stay minimal. | |
| 79 | rm -rf "/clusters/local/platform/grafana" "/clusters/local/platform/pgadmin" | |
| 80 | [ -n "" ] || rm -rf "/clusters/local/platform/monitoring" | |
| 78 | 81 | # 2) flux-kustomizations → cert-manager/pki/cnpg/traefik + keda (separate, wait) + |
| 79 | 82 | # platform (dependsOn keda too) + apps. Splitting keda out fixes the fresh-cluster |
| 80 | 83 | # deadlock: the forgejo ScaledJob needs keda.sh CRDs that the keda HelmRelease |
| @@ -104,6 +107,18 @@ apiVersion: kustomize.toolkit.fluxcd.io/v1 | ||
| 104 | 107 | kind: Kustomization |
| 105 | 108 | metadata: { name: keda, namespace: flux-system } |
| 106 | 109 | spec: { interval: 5m, path: ./clusters/local/platform/keda, prune: true, wait: true, timeout: 6m, sourceRef: { kind: GitRepository, name: flux-system } } |
| 110 | ||
| 111 | # Observability rides a lean fork only when opted in (MONITORING). Its own | |
| 112 | # Kustomization — it ships CRDs + a sops-sealed Grafana secret (hence the | |
| 113 | # decryption block) — and is deliberately never a dependency of `apps`. | |
| 114 | [ -n "" ] && cat >> "/clusters/local/flux-kustomizations.yaml" << | |
| 115 | --- | |
| 116 | apiVersion: kustomize.toolkit.fluxcd.io/v1 | |
| 117 | kind: Kustomization | |
| 118 | metadata: { name: monitoring, namespace: flux-system } | |
| 119 | spec: { interval: 5m, path: ./clusters/local/platform/monitoring, prune: true, wait: true, timeout: 12m, decryption: { provider: sops, secretRef: { name: sops-age } }, dependsOn: [{ name: cert-manager }], sourceRef: { kind: GitRepository, name: flux-system } } | |
| 120 | ||
| 121 | cat >> "/clusters/local/flux-kustomizations.yaml" << | |
| 107 | 122 | --- |
| 108 | 123 | apiVersion: kustomize.toolkit.fluxcd.io/v1 |
| 109 | 124 | kind: Kustomization |
| @@ -308,9 +323,25 @@ stringData: | ||
| 308 | 323 | |
| 309 | 324 | ( cd "" && sops --encrypt --in-place "/registry-pull-secret.yaml" ) |
| 310 | 325 | |
| 326 | # Observability (full profile, or lean+MONITORING): the committed grafana-oauth | |
| 327 | # secret is sealed to the GENESIS recipient, which fork_verify_all_sealed rejects. | |
| 328 | # Reseal it to the fork key with a fresh break-glass admin password + a placeholder | |
| 329 | # OAuth client (filled post-handoff by fork_register_grafana_oauth). | |
| 330 | local grafana_pw="" | |
| 331 | if [ -f "//monitoring/grafana-oauth-secret.yaml" ]; then | |
| 332 | grafana_pw= | |
| 333 | _fork_seal "" "/monitoring/grafana-oauth-secret.yaml" grafana-oauth monitoring "" << | |
| 334 | GF_SECURITY_ADMIN_USER: admin | |
| 335 | GF_SECURITY_ADMIN_PASSWORD: | |
| 336 | GF_AUTH_GENERIC_OAUTH_CLIENT_ID: pending-registration | |
| 337 | GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: pending-registration | |
| 338 | ||
| 339 | fi | |
| 340 | ||
| 311 | 341 | git -C "" add -A |
| 312 | 342 | git -C "" -c user.email=mitosis@plat -c user.name=mitosis commit -q -m "mitosis fork: regenerate platform secrets (sealed to sovereign key)" || true |
| 313 | 343 | |
| 344 | [ -n "" ] && printf 'FORK_GRAFANA_ADMIN_PASSWORD=%s\n' "" | |
| 314 | 345 | printf 'FORK_ADMIN_PASSWORD=%s\n' "" |
| 315 | 346 | } |
| 316 | 347 | |
| @@ -413,6 +444,28 @@ fork_register_oauth() { # $1=gitops $2=forgejo_url $3=basicauth $4=DOMAIN | ||
| 413 | 444 | git -C "" -c user.email=mitosis@plat -c user.name=mitosis commit -aqm "mitosis fork: plat-mcp OAuth client ()" || true |
| 414 | 445 | } |
| 415 | 446 | |
| 447 | # fork_register_grafana_oauth — when the observability stack is shipped, register a | |
| 448 | # sovereign Forgejo OAuth app for Grafana and seal client_id/secret into the | |
| 449 | # grafana-oauth secret. Returns 0 (no-op) when monitoring is absent. | |
| 450 | # $1=gitops $2=forgejo_url $3=basicauth $4=DOMAIN | |
| 451 | fork_register_grafana_oauth() { | |
| 452 | local g="$1" fj="$2" auth="$3" dom="$4" | |
| 453 | local gs="$1/clusters/local/platform/monitoring/grafana-oauth-secret.yaml" resp cid csec | |
| 454 | [ -f "" ] || return 0 # observability not shipped — nothing to register | |
| 455 | resp= | |
| 456 | -d "{\"name\":\"grafana\",\"redirect_uris\":[\"https://grafana./login/generic_oauth\"],\"confidential_client\":true}" \ | |
| 457 | "/api/v1/user/applications/oauth2" 2>/dev/null) || true | |
| 458 | cid= | |
| 459 | csec= | |
| 460 | if [ -z "" ] || [ -z "" ]; then | |
| 461 | echo "WARN: fork: grafana OAuth registration returned no client_id/secret (resp: )"; return 1 | |
| 462 | fi | |
| 463 | sops set "" '["stringData"]["GF_AUTH_GENERIC_OAUTH_CLIENT_ID"]' "\"\"" | |
| 464 | sops set "" '["stringData"]["GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET"]' "\"\"" | |
| 465 | git -C "" -c user.email=mitosis@plat -c user.name=mitosis commit -aqm "mitosis fork: grafana OAuth client ()" || true | |
| 466 | echo " grafana OAuth app registered on the daughter" | |
| 467 | } | |
| 468 | ||
| 416 | 469 | # fork_seal_ca — after cert-manager mints plat-local-ca-tls, seal its ca.crt |
| 417 | 470 | # (base64) into plat-mcp-secrets.PLAT_CA_TLS_CRT_B64. Reads the secret from the |
| 418 | 471 | # live cluster; caller ensures it exists. |
bin/mitosis +2
| @@ -18,6 +18,7 @@ mitosis — Open Platform self-replication | ||
| 18 | 18 | CLUSTER=<name> K3S_IMAGE=<pinned k3s> |
| 19 | 19 | HTTP_PORT/HTTPS_PORT (default 80/443; PORTS=none to skip) |
| 20 | 20 | CLAUDE_CODE_OAUTH_TOKEN | ANTHROPIC_API_KEY (one, optional) |
| 21 | MONITORING=1 (ship Prometheus+Grafana observability) | |
| 21 | 22 | |
| 22 | 23 | mitosis seed Export a portable seed of the current platform. |
| 23 | 24 | env: PLAT_PAT (required, Forgejo PAT w/ repo read) |
| @@ -29,6 +30,7 @@ mitosis — Open Platform self-replication | ||
| 29 | 30 | env: SEED=<seed.tar.gz> (required) |
| 30 | 31 | DOMAIN=<your-domain> (default plat.local) |
| 31 | 32 | FORK_KEY=<path> (where to write the new age key) |
| 33 | MONITORING=1 (ship Prometheus+Grafana observability) | |
| 32 | 34 | |
| 33 | 35 | Examples: |
| 34 | 36 | nix run .#up # a sovereign platform locally |
bin/up +1
| @@ -118,6 +118,7 @@ fi | ||
| 118 | 118 | echo "==> germinating the platform onto k3d-${CLUSTER}…" |
| 119 | 119 | env SEED="$SEED" DOMAIN="$DOMAIN" \ |
| 120 | 120 | ${FORK_KEY:+FORK_KEY="$FORK_KEY"} \ |
| 121 | ${MONITORING:+MONITORING="$MONITORING"} \ | |
| 121 | 122 | "$HERE/germinate" |
| 122 | 123 | |
| 123 | 124 | # germinate prints the YOUR PLATFORM card (domain, admin creds, fork key, /etc/hosts, |