#!/usr/bin/env bash # Deploy DCOS to Kubernetes (standalone pipeline, mirrors stonks-oracle pattern) # Usage: runmefirst.sh # Detects existing deploy and adds/updates; idempotent. set -euo pipefail NAMESPACE="dcos-service" REPO_DIR="$HOME/sources/sama/sama" KUBE_DIR="$(cd "$(dirname "$0")" && pwd)" GHCR="ghcr.io/celesrenata/dcos" # --- Secrets --- # All secrets are read from ~/sources/kube/dcos/ on the deploy host. # This directory is NOT a git repo — secrets stay local to the deploy host. # # Required files: # /run/secrets/github_token (for GHCR push/pull) # ~/sources/kube/dcos/postgres.password (PG password for dcos user) # ~/sources/kube/dcos/redis.password (Redis password) _read_secret() { local file="$1" local default="${2:-}" if [ -f "$file" ]; then cat "$file" | tr -d '[:space:]' elif [ -n "$default" ]; then echo "$default" else echo "ERROR: Secret file not found: $file" >&2 exit 1 fi } GHCR_TOKEN=$(_read_secret /run/secrets/github_token) PG_PASSWORD=$(_read_secret "$KUBE_DIR/postgres.password") REDIS_PASSWORD=$(_read_secret "$KUBE_DIR/redis.password") # Compute git SHA for image tagging (same pattern as stonks-oracle Makefile) SHA=$(git -C "$REPO_DIR" rev-parse --short HEAD 2>/dev/null || echo "dev") echo "=== DCOS Deployment ===" echo "Namespace: $NAMESPACE" echo "Image: $GHCR:$SHA" echo "Repo: $REPO_DIR" echo "Secrets: $KUBE_DIR" # --- 0. Pull latest code --- echo "[0/6] Pulling latest code..." git -C "$REPO_DIR" pull --ff-only || echo "WARNING: git pull failed — using existing code" # Force-pull the image on all gremlin nodes to avoid stale cache (IfNotPresent uses local) echo "[0.5/6] Forcing fresh image pull on cluster nodes..." for node in $(kubectl get nodes -o name 2>/dev/null | sed 's/node\.//'); do echo " Pulling on $node..." kubectl debug node/$node --image=busybox:1.36 -- chroot /host sh -c "docker rmi ghcr.io/celesrenata/dcos:$SHA 2>/dev/null; docker pull ghcr.io/celesrenata/dcos:$SHA" || true done # --- 1. Ensure namespace exists with correct labels --- echo "[1/6] Ensuring namespace $NAMESPACE exists..." if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then kubectl create namespace "$NAMESPACE" fi kubectl label namespace "$NAMESPACE" app.kubernetes.io/managed-by=Helm --overwrite kubectl annotate namespace "$NAMESPACE" meta.helm.sh/release-name=dcos meta.helm.sh/release-namespace=$NAMESPACE --overwrite # --- 2. Build and push image --- echo "[2/6] Building DCOS image..." docker build \ --build-arg "SERVICE_CMD=python -m dcos.core --init" \ -t $GHCR:$SHA \ -t $GHCR:latest \ -f "$REPO_DIR/docker/Dockerfile.dcos" "$REPO_DIR" || { echo "ERROR: Docker build failed." >&2 exit 1 } echo "Pushing image..." if ! echo "$GHCR_TOKEN" | docker login ghcr.io -u celesrenata --password-stdin >/dev/null 2>&1; then echo "WARNING: GHCR login failed — token may be expired or missing scopes." >&2 echo " Ensure /run/secrets/github_token has 'read:packages' scope." >&2 echo " Generate a new token at: https://github.com/settings/tokens" >&2 exit 1 fi docker push $GHCR:$SHA docker push $GHCR:latest # --- 3. Create/update GHCR pull secret and DB secrets --- echo "[3/6] Creating/updating GHCR credentials and database secrets..." # Force-recreate the GHCR pull secret so pods can authenticate to pull images kubectl delete secret ghcr-credentials -n "$NAMESPACE" --ignore-not-found kubectl create secret docker-registry ghcr-credentials \ --namespace="$NAMESPACE" \ --docker-server=ghcr.io \ --docker-username=celesrenata \ --docker-password="$GHCR_TOKEN" \ --dry-run=client -o yaml | kubectl apply -f - # Read MinIO credentials (same as stonks-oracle) MINIO_ACCESS_KEY=$(_read_secret "$KUBE_DIR/minio.access_key" "AKIA6V7J3N9B5P0D2YQH") MINIO_SECRET_KEY=$(_read_secret "$KUBE_DIR/minio.secret_key" "8fG3!v2rJ7\$wN@9mLpQ6zXbC4tKdPqW1") kubectl delete secret dcos-db-secret -n "$NAMESPACE" --ignore-not-found kubectl create secret generic dcos-db-secret \ --namespace="$NAMESPACE" \ --from-literal=POSTGRES_PASSWORD="$PG_PASSWORD" \ --from-literal=REDIS_PASSWORD="$REDIS_PASSWORD" \ --from-literal=MINIO_ACCESS_KEY="$MINIO_ACCESS_KEY" \ --from-literal=MINIO_SECRET_KEY="$MINIO_SECRET_KEY" \ --from-literal=MINIO_ENDPOINT="minio-crawler-console:9090" # --- 3.5. Apply middleware (Traefik dependency, no version label to update) --- echo "[3.5/6] Applying Traefik middleware..." kubectl apply -f "$KUBE_DIR/middleware.yaml" -n "$NAMESPACE" # --- 4. Apply PVCs (for SQLite fallback tiers: long_term, semantic, episodic) --- echo "[4/6] Applying PVCs..." kubectl apply -f "$KUBE_DIR/pvc.yaml" -n "$NAMESPACE" # --- 5. Apply deployment manifests (inject SHA into image tag and version labels) --- echo "[5/6] Applying K8s manifests..." # Temporarily inject the SHA-based image tag and version labels DEPLOY_TMP=$(mktemp) sed -e "s|ghcr.io/celesrenata/dcos:[0-9.]*|$GHCR:$SHA|" \ -e 's|app.kubernetes.io/version: "[0-9.]*"|app.kubernetes.io/version: "'"$SHA"'"|' \ "$KUBE_DIR/deployment.yaml" > "$DEPLOY_TMP" kubectl apply -f "$DEPLOY_TMP" -n "$NAMESPACE" rm -f "$DEPLOY_TMP" # Also update version labels in service.yaml and ingress.yaml SVC_TMP=$(mktemp) sed -e 's|app.kubernetes.io/version: "[0-9.]*"|app.kubernetes.io/version: "'"$SHA"'"|' \ "$KUBE_DIR/service.yaml" > "$SVC_TMP" kubectl apply -f "$SVC_TMP" -n "$NAMESPACE" rm -f "$SVC_TMP" INGRESS_TMP=$(mktemp) sed -e 's|app.kubernetes.io/version: "[0-9.]*"|app.kubernetes.io/version: "'"$SHA"'"|' \ "$KUBE_DIR/ingress.yaml" > "$INGRESS_TMP" kubectl apply -f "$INGRESS_TMP" -n "$NAMESPACE" rm -f "$INGRESS_TMP" kubectl apply -f "$KUBE_DIR/serviceaccount.yaml" -n "$NAMESPACE" # --- 6. Rolling restart to pick up new image --- echo "[6/6] Rolling restart..." kubectl rollout restart deployment/dcos -n "$NAMESPACE" echo "" echo "=== Deployment complete ===" echo "Waiting for pods..." sleep 10 kubectl get pods -n "$NAMESPACE" -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[0].ready,STATUS:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount' echo "" echo "Ingress endpoints:" kubectl get ingress -n "$NAMESPACE" -o custom-columns='HOST:.spec.rules[0].host,ADDRESS:.status.loadBalancer.ingress[0].ip' 2>/dev/null || echo " (no ingress found)"