feat: bring up local dev stack — all services running
- Ory Hydra + Kratos: fixed secret management, DSN config, DB migrations,
OAuth2Client CRD (helm template skips crds/ dir), login-ui env vars
- SeaweedFS: added s3.json credentials file via -s3.config CLI flag
- OpenBao: standalone mode with auto-unseal sidecar, keys in K8s secret
- OpenSearch: increased memory to 1.5Gi / JVM 1g heap
- Gitea: SSL_MODE disable, S3 bucket creation fixed
- Hive: automountServiceAccountToken: false (Lima virtiofs read-only rootfs quirk)
- LiveKit: API keys in values, hostPort conflict resolved
- Linkerd: native sidecar (proxy.nativeSidecar=true) to avoid blocking Jobs
- All placeholder images replaced: pingora→nginx:alpine, login-ui→oryd/kratos-selfservice-ui-node
Full stack running: postgres, valkey, openbao, opensearch, seaweedfs,
kratos, hydra, gitea, livekit, hive (placeholder), login-ui
2026-02-28 22:08:38 +00:00
#!/usr/bin/env bash
# Seed all secrets for the local dev stack.
# - Initializes OpenBao (if needed) and stores root token + unseal key
# - Sets postgres user passwords
# - Creates K8s secrets consumed by each service
# - Stores all secrets in OpenBao as source of truth
#
# Idempotent: safe to run multiple times.
set -euo pipefail
CTX = "--context=sunbeam"
SCRIPT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd ) "
# Deterministic local-dev passwords (simple, memorable, not for production)
DB_PASSWORD = "localdev"
S3_ACCESS_KEY = "minioadmin"
S3_SECRET_KEY = "minioadmin"
HYDRA_SYSTEM_SECRET = "local-hydra-system-secret-at-least-16"
HYDRA_COOKIE_SECRET = "local-hydra-cookie-secret-at-least-16"
HYDRA_PAIRWISE_SALT = "local-hydra-pairwise-salt-value-1"
LIVEKIT_API_KEY = "devkey"
LIVEKIT_API_SECRET = "secret-placeholder"
# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------
ensure_ns( ) {
kubectl $CTX create namespace " $1 " --dry-run= client -o yaml | kubectl $CTX apply -f - 2>/dev/null
}
create_secret( ) {
local ns = " $1 " ; shift
local name = " $1 " ; shift
# remaining args are --from-literal=key=value
kubectl $CTX create secret generic " $name " -n " $ns " " $@ " \
--dry-run= client -o yaml | kubectl $CTX apply -f -
}
# ---------------------------------------------------------------------------
# 1. Wait for postgres to be ready
# ---------------------------------------------------------------------------
echo "==> Waiting for postgres cluster..."
for i in $( seq 1 60) ; do
PHASE = $( kubectl $CTX -n data get cluster postgres -o jsonpath = '{.status.phase}' 2>/dev/null || echo "" )
if [ [ " $PHASE " = = "Cluster in healthy state" ] ] ; then
echo " Postgres is ready."
break
fi
if [ [ $i -eq 60 ] ] ; then
echo "WARN: Postgres not ready after 5 min, continuing anyway..."
fi
sleep 5
done
# ---------------------------------------------------------------------------
# 2. Set postgres user passwords
# ---------------------------------------------------------------------------
echo "==> Setting postgres user passwords..."
PG_POD = $( kubectl $CTX -n data get pods -l cnpg.io/cluster= postgres,role= primary -o jsonpath = '{.items[0].metadata.name}' 2>/dev/null || echo "" )
if [ [ -n " $PG_POD " ] ] ; then
scripts: replace local-up.sh with idempotent Python lifecycle script
local-up.py is a stdlib-only Python rewrite of local-up.sh +
local-seed-secrets.sh. Key improvements:
- Correctly parses limactl list --json NDJSON output (json.load()
choked on NDJSON, causing spurious VM creation attempts)
- Handles all Lima VM states: none, Running, Stopped, Broken, etc.
- Inlines seed secrets (no separate local-seed-secrets.sh subprocess)
- Partial runs: --seed, --apply, --restart flags
- Consistent idempotency: every step checks state before acting
- Adds people-backend/celery to restart list; find to PG users list
local-up.sh patched: yq in prereqs, NDJSON-safe VM detection,
--server-side for Linkerd apply, people in restart list, Mail URL.
2026-03-01 18:22:54 +00:00
for user in kratos hydra gitea hive docs meet drive messages conversations people find; do
feat: bring up local dev stack — all services running
- Ory Hydra + Kratos: fixed secret management, DSN config, DB migrations,
OAuth2Client CRD (helm template skips crds/ dir), login-ui env vars
- SeaweedFS: added s3.json credentials file via -s3.config CLI flag
- OpenBao: standalone mode with auto-unseal sidecar, keys in K8s secret
- OpenSearch: increased memory to 1.5Gi / JVM 1g heap
- Gitea: SSL_MODE disable, S3 bucket creation fixed
- Hive: automountServiceAccountToken: false (Lima virtiofs read-only rootfs quirk)
- LiveKit: API keys in values, hostPort conflict resolved
- Linkerd: native sidecar (proxy.nativeSidecar=true) to avoid blocking Jobs
- All placeholder images replaced: pingora→nginx:alpine, login-ui→oryd/kratos-selfservice-ui-node
Full stack running: postgres, valkey, openbao, opensearch, seaweedfs,
kratos, hydra, gitea, livekit, hive (placeholder), login-ui
2026-02-28 22:08:38 +00:00
kubectl $CTX -n data exec " $PG_POD " -c postgres -- \
psql -U postgres -c " ALTER USER $user WITH PASSWORD ' $DB_PASSWORD '; " 2>/dev/null || true
done
echo " Done."
else
echo "WARN: No postgres primary pod found, skipping password setup."
fi
# ---------------------------------------------------------------------------
# 3. Create K8s secrets for each service
# ---------------------------------------------------------------------------
echo "==> Creating K8s secrets..."
# Ory namespace
ensure_ns ory
# Secret name must match chart release name (secret.enabled: false means chart uses release name)
create_secret ory hydra \
--from-literal= dsn = " postgresql://hydra: ${ DB_PASSWORD } @postgres-rw.data.svc.cluster.local:5432/hydra_db?sslmode=disable " \
--from-literal= secretsSystem = " $HYDRA_SYSTEM_SECRET " \
--from-literal= secretsCookie = " $HYDRA_COOKIE_SECRET " \
--from-literal= pairwise-salt= " $HYDRA_PAIRWISE_SALT "
# Kratos chart (secret.enabled: true, nameOverride: kratos-secrets) creates kratos-secrets
# from Helm values — DSN is in kratos-values.yaml, random secrets generated by chart.
# This create is a no-op placeholder; chart apply overwrites with Helm-generated values.
# Devtools namespace
ensure_ns devtools
create_secret devtools gitea-db-credentials \
--from-literal= password = " $DB_PASSWORD "
create_secret devtools gitea-s3-credentials \
--from-literal= access-key= " $S3_ACCESS_KEY " \
--from-literal= secret-key= " $S3_SECRET_KEY "
# Storage namespace
ensure_ns storage
create_secret storage seaweedfs-s3-credentials \
--from-literal= S3_ACCESS_KEY = " $S3_ACCESS_KEY " \
--from-literal= S3_SECRET_KEY = " $S3_SECRET_KEY "
# La Suite namespace
ensure_ns lasuite
create_secret lasuite seaweedfs-s3-credentials \
--from-literal= S3_ACCESS_KEY = " $S3_ACCESS_KEY " \
--from-literal= S3_SECRET_KEY = " $S3_SECRET_KEY "
create_secret lasuite hive-db-url \
--from-literal= url = " postgresql://hive: ${ DB_PASSWORD } @postgres-rw.data.svc.cluster.local:5432/hive_db "
create_secret lasuite hive-oidc \
--from-literal= client-id= "hive-local" \
--from-literal= client-secret= "hive-local-secret"
scripts: replace local-up.sh with idempotent Python lifecycle script
local-up.py is a stdlib-only Python rewrite of local-up.sh +
local-seed-secrets.sh. Key improvements:
- Correctly parses limactl list --json NDJSON output (json.load()
choked on NDJSON, causing spurious VM creation attempts)
- Handles all Lima VM states: none, Running, Stopped, Broken, etc.
- Inlines seed secrets (no separate local-seed-secrets.sh subprocess)
- Partial runs: --seed, --apply, --restart flags
- Consistent idempotency: every step checks state before acting
- Adds people-backend/celery to restart list; find to PG users list
local-up.sh patched: yq in prereqs, NDJSON-safe VM detection,
--server-side for Linkerd apply, people in restart list, Mail URL.
2026-03-01 18:22:54 +00:00
# People (desk)
create_secret lasuite people-db-credentials \
--from-literal= password = " $DB_PASSWORD "
create_secret lasuite people-django-secret \
--from-literal= DJANGO_SECRET_KEY = "local-dev-people-django-secret-key-not-for-production"
feat: bring up local dev stack — all services running
- Ory Hydra + Kratos: fixed secret management, DSN config, DB migrations,
OAuth2Client CRD (helm template skips crds/ dir), login-ui env vars
- SeaweedFS: added s3.json credentials file via -s3.config CLI flag
- OpenBao: standalone mode with auto-unseal sidecar, keys in K8s secret
- OpenSearch: increased memory to 1.5Gi / JVM 1g heap
- Gitea: SSL_MODE disable, S3 bucket creation fixed
- Hive: automountServiceAccountToken: false (Lima virtiofs read-only rootfs quirk)
- LiveKit: API keys in values, hostPort conflict resolved
- Linkerd: native sidecar (proxy.nativeSidecar=true) to avoid blocking Jobs
- All placeholder images replaced: pingora→nginx:alpine, login-ui→oryd/kratos-selfservice-ui-node
Full stack running: postgres, valkey, openbao, opensearch, seaweedfs,
kratos, hydra, gitea, livekit, hive (placeholder), login-ui
2026-02-28 22:08:38 +00:00
# Media namespace
ensure_ns media
echo " Done."
# ---------------------------------------------------------------------------
# 4. Initialize and unseal OpenBao (if deployed)
# ---------------------------------------------------------------------------
echo "==> Checking OpenBao..."
OB_POD = $( kubectl $CTX -n data get pods -l app.kubernetes.io/name= openbao,component= server -o jsonpath = '{.items[0].metadata.name}' 2>/dev/null || echo "" )
if [ [ -z " $OB_POD " ] ] ; then
echo " OpenBao pod not found, skipping."
else
# Wait for pod to be running (not necessarily ready — it won't be ready until unsealed)
kubectl $CTX -n data wait pod " $OB_POD " --for= jsonpath = '{.status.phase}' = Running --timeout= 120s 2>/dev/null || true
# Check if initialized
INIT_STATUS = $( kubectl $CTX -n data exec " $OB_POD " -c openbao -- bao status -format= json 2>/dev/null | grep '"initialized"' | grep -c 'true' || echo "0" )
if [ [ " $INIT_STATUS " != "1" ] ] ; then
echo "==> Initializing OpenBao..."
INIT_OUTPUT = $( kubectl $CTX -n data exec " $OB_POD " -c openbao -- bao operator init -key-shares= 1 -key-threshold= 1 -format= json 2>/dev/null)
UNSEAL_KEY = $( echo " $INIT_OUTPUT " | jq -r '.unseal_keys_b64[0]' )
ROOT_TOKEN = $( echo " $INIT_OUTPUT " | jq -r '.root_token' )
# Store keys in K8s secret
create_secret data openbao-keys \
--from-literal= key = " $UNSEAL_KEY " \
--from-literal= root-token= " $ROOT_TOKEN "
echo " Initialized. Keys stored in secret/openbao-keys."
else
echo " Already initialized."
# Read unseal key from existing secret
UNSEAL_KEY = $( kubectl $CTX -n data get secret openbao-keys -o jsonpath = '{.data.key}' 2>/dev/null | base64 -d || echo "" )
ROOT_TOKEN = $( kubectl $CTX -n data get secret openbao-keys -o jsonpath = '{.data.root-token}' 2>/dev/null | base64 -d || echo "" )
fi
# Unseal if sealed
SEALED = $( kubectl $CTX -n data exec " $OB_POD " -c openbao -- bao status -format= json 2>/dev/null | grep '"sealed"' | grep -c 'true' || echo "0" )
if [ [ " $SEALED " = = "1" && -n " $UNSEAL_KEY " ] ] ; then
echo "==> Unsealing OpenBao..."
kubectl $CTX -n data exec " $OB_POD " -c openbao -- bao operator unseal " $UNSEAL_KEY "
echo " Unsealed."
fi
# Seed secrets into OpenBao
if [ [ -n " $ROOT_TOKEN " ] ] ; then
echo "==> Seeding secrets into OpenBao..."
kubectl $CTX -n data exec " $OB_POD " -c openbao -- sh -c "
export BAO_ADDR = http://127.0.0.1:8200
export BAO_TOKEN = '$ROOT_TOKEN'
bao secrets enable -path= secret -version= 2 kv 2>/dev/null || true
bao kv put secret/postgres password = '$DB_PASSWORD'
bao kv put secret/hydra db-password= '$DB_PASSWORD' system-secret= '$HYDRA_SYSTEM_SECRET' cookie-secret= '$HYDRA_COOKIE_SECRET' pairwise-salt= '$HYDRA_PAIRWISE_SALT'
bao kv put secret/kratos db-password= '$DB_PASSWORD'
bao kv put secret/gitea db-password= '$DB_PASSWORD' s3-access-key= '$S3_ACCESS_KEY' s3-secret-key= '$S3_SECRET_KEY'
bao kv put secret/seaweedfs access-key= '$S3_ACCESS_KEY' secret-key= '$S3_SECRET_KEY'
bao kv put secret/hive db-url= 'postgresql://hive:${DB_PASSWORD}@postgres-rw.data.svc.cluster.local:5432/hive_db' oidc-client-id= 'hive-local' oidc-client-secret= 'hive-local-secret'
scripts: replace local-up.sh with idempotent Python lifecycle script
local-up.py is a stdlib-only Python rewrite of local-up.sh +
local-seed-secrets.sh. Key improvements:
- Correctly parses limactl list --json NDJSON output (json.load()
choked on NDJSON, causing spurious VM creation attempts)
- Handles all Lima VM states: none, Running, Stopped, Broken, etc.
- Inlines seed secrets (no separate local-seed-secrets.sh subprocess)
- Partial runs: --seed, --apply, --restart flags
- Consistent idempotency: every step checks state before acting
- Adds people-backend/celery to restart list; find to PG users list
local-up.sh patched: yq in prereqs, NDJSON-safe VM detection,
--server-side for Linkerd apply, people in restart list, Mail URL.
2026-03-01 18:22:54 +00:00
bao kv put secret/people db-password= '$DB_PASSWORD' django-secret-key= 'local-dev-people-django-secret-key-not-for-production'
feat: bring up local dev stack — all services running
- Ory Hydra + Kratos: fixed secret management, DSN config, DB migrations,
OAuth2Client CRD (helm template skips crds/ dir), login-ui env vars
- SeaweedFS: added s3.json credentials file via -s3.config CLI flag
- OpenBao: standalone mode with auto-unseal sidecar, keys in K8s secret
- OpenSearch: increased memory to 1.5Gi / JVM 1g heap
- Gitea: SSL_MODE disable, S3 bucket creation fixed
- Hive: automountServiceAccountToken: false (Lima virtiofs read-only rootfs quirk)
- LiveKit: API keys in values, hostPort conflict resolved
- Linkerd: native sidecar (proxy.nativeSidecar=true) to avoid blocking Jobs
- All placeholder images replaced: pingora→nginx:alpine, login-ui→oryd/kratos-selfservice-ui-node
Full stack running: postgres, valkey, openbao, opensearch, seaweedfs,
kratos, hydra, gitea, livekit, hive (placeholder), login-ui
2026-02-28 22:08:38 +00:00
bao kv put secret/livekit api-key= '$LIVEKIT_API_KEY' api-secret= '$LIVEKIT_API_SECRET'
" 2>/dev/null
echo " Done."
fi
fi
echo ""
echo "==> All secrets seeded."