#!/usr/bin/env bash # Start the Sunbeam local dev stack. # Idempotent: safe to run multiple times. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" CTX="--context=sunbeam" # --------------------------------------------------------------------------- # 1. Check prerequisites # --------------------------------------------------------------------------- echo "==> Checking prerequisites..." for tool in limactl mkcert kubectl kustomize linkerd jq yq; do if ! command -v "$tool" &>/dev/null; then echo "ERROR: '$tool' not found. Install with: brew install $tool" >&2 exit 1 fi done echo " OK" # --------------------------------------------------------------------------- # 2. Start Lima VM (skip if already running) # --------------------------------------------------------------------------- # Separate existence check from status — avoids falling through to "create" # when VM exists but has an unexpected status (Broken, Starting, etc.) LIMA_STATUS=$(limactl list --json 2>/dev/null | \ python3 -c "import sys,json; vms=[v for v in json.load(sys.stdin) if v['name']=='sunbeam']; print(vms[0]['status'] if vms else 'none')" 2>/dev/null || echo "none") if [[ "$LIMA_STATUS" == "none" ]]; then echo "==> Creating Lima VM 'sunbeam' (k3s, 6 CPU / 12 GB / 60 GB)..." limactl start \ --name=sunbeam \ template:k3s \ --memory=12 \ --cpus=6 \ --disk=60 \ --vm-type=vz \ --mount-type=virtiofs elif [[ "$LIMA_STATUS" == "Running" ]]; then echo "==> Lima VM 'sunbeam' already running." else # Covers Stopped, Broken, Starting, or any other state echo "==> Starting Lima VM 'sunbeam' (status: $LIMA_STATUS)..." limactl start sunbeam fi # --------------------------------------------------------------------------- # 3. Merge kubeconfig into ~/.kube/config as context "sunbeam" # --------------------------------------------------------------------------- echo "==> Merging kubeconfig..." LIMA_KUBECONFIG="/Users/$USER/.lima/sunbeam/copied-from-guest/kubeconfig.yaml" if [[ ! -f "$LIMA_KUBECONFIG" ]]; then echo "ERROR: Lima kubeconfig not found at $LIMA_KUBECONFIG" >&2 exit 1 fi # Extract cert data and set context mkdir -p ~/.kube /tmp/sunbeam-kube yq '.clusters[0].cluster.certificate-authority-data' "$LIMA_KUBECONFIG" | base64 -d > /tmp/sunbeam-kube/ca.crt yq '.users[0].user.client-certificate-data' "$LIMA_KUBECONFIG" | base64 -d > /tmp/sunbeam-kube/client.crt yq '.users[0].user.client-key-data' "$LIMA_KUBECONFIG" | base64 -d > /tmp/sunbeam-kube/client.key kubectl config set-cluster sunbeam --server=https://127.0.0.1:6443 --certificate-authority=/tmp/sunbeam-kube/ca.crt --embed-certs=true kubectl config set-credentials sunbeam-admin --client-certificate=/tmp/sunbeam-kube/client.crt --client-key=/tmp/sunbeam-kube/client.key --embed-certs=true kubectl config set-context sunbeam --cluster=sunbeam --user=sunbeam-admin rm -rf /tmp/sunbeam-kube echo " Context 'sunbeam' ready." # --------------------------------------------------------------------------- # 4. Disable Traefik (k3s default) if still present # --------------------------------------------------------------------------- if kubectl $CTX get helmchart traefik -n kube-system &>/dev/null; then echo "==> Removing Traefik (replaced by Pingora)..." kubectl $CTX delete helmchart traefik traefik-crd -n kube-system 2>/dev/null || true fi # Remove startup manifest so k3s doesn't re-create it limactl shell sunbeam sudo rm -f /var/lib/rancher/k3s/server/manifests/traefik.yaml 2>/dev/null || true # --------------------------------------------------------------------------- # 5. Install cert-manager # --------------------------------------------------------------------------- if ! kubectl $CTX get ns cert-manager &>/dev/null; then echo "==> Installing cert-manager..." kubectl $CTX apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml echo " Waiting for cert-manager webhooks..." kubectl $CTX -n cert-manager rollout status deployment/cert-manager --timeout=120s kubectl $CTX -n cert-manager rollout status deployment/cert-manager-webhook --timeout=120s kubectl $CTX -n cert-manager rollout status deployment/cert-manager-cainjector --timeout=120s echo " cert-manager installed." else echo "==> cert-manager already installed." fi # --------------------------------------------------------------------------- # 6. Install Gateway API CRDs + Linkerd via CLI# --------------------------------------------------------------------------- if ! kubectl $CTX get ns linkerd &>/dev/null; then echo "==> Installing Gateway API CRDs..." kubectl $CTX apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml echo "==> Installing Linkerd CRDs..." linkerd install --crds | kubectl $CTX apply --server-side -f - echo "==> Installing Linkerd control plane..." linkerd install | kubectl $CTX apply --server-side -f - kubectl $CTX -n linkerd rollout status deployment/linkerd-identity --timeout=120s kubectl $CTX -n linkerd rollout status deployment/linkerd-destination --timeout=120s kubectl $CTX -n linkerd rollout status deployment/linkerd-proxy-injector --timeout=120s echo " Linkerd installed." else echo "==> Linkerd already installed." fi # --------------------------------------------------------------------------- # 7. Generate mkcert wildcard cert# --------------------------------------------------------------------------- # Use eth1 (socket_vmnet shared network) — the address reachable from the Mac host. LIMA_IP=$(limactl shell sunbeam ip -4 addr show eth1 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) if [[ -z "$LIMA_IP" ]]; then # Fallback: first non-loopback IP (works on first-boot before eth1 is up) LIMA_IP=$(limactl shell sunbeam hostname -I | awk '{print $1}') fi DOMAIN="${LIMA_IP}.sslip.io" SECRETS_DIR="$REPO_ROOT/secrets/local" if [[ ! -f "$SECRETS_DIR/tls.crt" ]]; then echo "==> Generating TLS cert for *.$DOMAIN..." mkdir -p "$SECRETS_DIR" cd "$SECRETS_DIR" mkcert "*.$DOMAIN" mv "_wildcard.${DOMAIN}.pem" tls.crt mv "_wildcard.${DOMAIN}-key.pem" tls.key cd "$REPO_ROOT" else echo "==> TLS cert already exists." fi # --------------------------------------------------------------------------- # 8. Create TLS Secret in ingress namespace# --------------------------------------------------------------------------- echo "==> Applying TLS Secret to ingress namespace..." kubectl $CTX create namespace ingress --dry-run=client -o yaml | kubectl $CTX apply -f - kubectl $CTX create secret tls pingora-tls \ --cert="$SECRETS_DIR/tls.crt" \ --key="$SECRETS_DIR/tls.key" \ -n ingress \ --dry-run=client -o yaml | kubectl $CTX apply -f - # --------------------------------------------------------------------------- # 9. Apply manifests (server-side apply handles large CRDs)# --------------------------------------------------------------------------- echo "==> Applying manifests (domain: $DOMAIN)..." cd "$REPO_ROOT" kustomize build overlays/local --enable-helm | \ sed "s/DOMAIN_SUFFIX/${DOMAIN}/g" | \ kubectl $CTX apply --server-side --force-conflicts -f - # --------------------------------------------------------------------------- # 10. Seed secrets (waits for postgres, creates K8s secrets, inits OpenBao)# --------------------------------------------------------------------------- echo "==> Seeding secrets..." bash "$SCRIPT_DIR/local-seed-secrets.sh" # --------------------------------------------------------------------------- # 11. Restart deployments that were waiting for secrets# --------------------------------------------------------------------------- echo "==> Restarting services that were waiting for secrets..." for ns_deploy in \ "ory/hydra" \ "ory/kratos" \ "ory/login-ui" \ "devtools/gitea" \ "storage/seaweedfs-filer" \ "lasuite/hive" \ "media/livekit-server"; do ns="${ns_deploy%%/*}" dep="${ns_deploy##*/}" kubectl $CTX -n "$ns" rollout restart deployment/"$dep" 2>/dev/null || true done # --------------------------------------------------------------------------- # 12. Wait for core components# --------------------------------------------------------------------------- echo "==> Waiting for Valkey..." kubectl $CTX rollout status deployment/valkey -n data --timeout=120s || true echo "==> Waiting for Kratos..." kubectl $CTX rollout status deployment/kratos -n ory --timeout=120s || true echo "==> Waiting for Hydra..." kubectl $CTX rollout status deployment/hydra -n ory --timeout=120s || true # --------------------------------------------------------------------------- # 13. Print URLs# --------------------------------------------------------------------------- echo "" echo "==> Stack is up. Domain: $DOMAIN" echo "" echo "Services:" echo " Auth: https://auth.${DOMAIN}/" echo " Docs: https://docs.${DOMAIN}/" echo " Meet: https://meet.${DOMAIN}/" echo " Drive: https://drive.${DOMAIN}/" echo " Messages: https://messages.${DOMAIN}/" echo " Mail: https://mail.${DOMAIN}/" echo " People: https://people.${DOMAIN}/" echo " Gitea: https://src.${DOMAIN}/" echo "" echo "OpenBao UI: kubectl $CTX -n data port-forward svc/openbao 8200:8200" echo " http://localhost:8200 (token from: kubectl $CTX -n data get secret openbao-keys -o jsonpath='{.data.root-token}' | base64 -d)"