diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 6e3fb08..1c0a439 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -211,6 +211,7 @@ jobs: --image "$IMAGE" \ --region us-central1 \ --project neuron-785695 \ + --execution-environment gen2 \ --service-account neuron-marketing-sa@neuron-785695.iam.gserviceaccount.com \ --update-env-vars "NODE_ENV=production,STRIPE_PUBLISHABLE_KEY=pk_test_51TPoHnJg9Fv1D3AUp1FEMcy4MGlKRZqs4scW66kjQFQjWofmNc2rottzXzDaXekHvuw1OQpyp2WCIsc7O5fXIG0G00HQQrkdGX,GCS_SHARE_BUCKET=neuron-shares-prod,SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9jb2pzZ2hhb25sdHVuaWRrenB3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc2NDIxNjgsImV4cCI6MjA5MzIxODE2OH0.e0FVFw1aahnrBVvnkR5R8a-RxCx095U8o_gsk7Quq3E,NEURON_LLM_0_FORMAT=anthropic,NEURON_LLM_0_MODEL=claude-sonnet-4-5,NEURON_LLM_0_URL=https://api.anthropic.com/v1/messages" \ --update-secrets "SUPABASE_SERVICE_KEY=supabase-service-key:latest,NEURON_LLM_0_KEY=anthropic-api-key:latest,ANTHROPIC_API_KEY=anthropic-api-key:latest,STRIPE_SECRET_KEY=stripe-secret-key-stage:latest,STRIPE_WEBHOOK_SECRET=stripe-webhook-secret-stage:latest,STRIPE_PRICE_PROFESSIONAL=stripe-price-professional-stage:latest,STRIPE_PRICE_FOUNDING=stripe-price-founding-stage:latest,STRIPE_PRICE_FAMILY_CHILD=stripe-price-family-child:latest,RESEND_API_KEY=resend-api-key:latest,DOCUSEAL_WEBHOOK_TOKEN=docuseal-webhook-token:latest" \ @@ -228,6 +229,7 @@ jobs: gcloud run services update marketing-stage \ --region us-central1 --project neuron-785695 \ + --execution-environment gen2 \ --update-env-vars "NEURON_ORIGIN=${STAGE_URL}" \ --quiet @@ -265,6 +267,7 @@ jobs: --image "$IMAGE" \ --region "$region" \ --project neuron-785695 \ + --execution-environment gen2 \ --quiet } deploy us-central1 marketing-prod-us & diff --git a/.gitignore b/.gitignore index 4b6c875..003757c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,10 @@ src/assets/js/ !dist/elhtml_impl.c !dist/entrypoint.sh !dist/engram-snapshot.json +!dist/Dockerfile.soul-demo +!dist/k3s-soul-demo.yaml + +# Build artifacts produced by the soul-demo packaging step in build-stage.sh +dist/soul-demo +dist/soul-demo-snapshot.json +dist/soul-demo-image.tar diff --git a/Dockerfile.stage b/Dockerfile.stage index d99a1ca..d06d34c 100644 --- a/Dockerfile.stage +++ b/Dockerfile.stage @@ -17,6 +17,7 @@ FROM debian:bookworm-slim AS builder RUN apt-get update \ && apt-get install -y --no-install-recommends \ build-essential \ + curl \ libcurl4-openssl-dev \ libssl-dev \ ca-certificates \ @@ -41,6 +42,10 @@ RUN cc -O2 -rdynamic \ soul-demo.c vessel_stubs.c el_runtime.o \ -lcurl -lpthread -ldl -lm -lssl -lcrypto +# ── Download k3s binary ─────────────────────────────────────────────────────── +RUN curl -fL https://github.com/k3s-io/k3s/releases/download/v1.32.4%2Bk3s1/k3s -o /usr/local/bin/k3s \ + && chmod +x /usr/local/bin/k3s + # ── Stage 2: runtime image ──────────────────────────────────────────────────── FROM debian:bookworm-slim @@ -53,7 +58,9 @@ RUN apt-get update \ && groupadd -r landing && useradd -r -g landing landing \ && mkdir -p /srv/landing/assets /srv/landing/js /srv/landing/shares \ && mkdir -p /srv/soul/engram-demo \ - && chown -R landing:landing /srv/landing /srv/soul + && chown -R landing:landing /srv/landing /srv/soul \ + && mkdir -p /var/lib/rancher/k3s /tmp/k3s \ + && chown -R landing:landing /var/lib/rancher /tmp/k3s # neuron-web binary — produced by `elb build` in CI (linux/amd64) COPY dist/neuron-landing /usr/local/bin/neuron-web @@ -61,6 +68,17 @@ RUN chmod +x /usr/local/bin/neuron-web COPY --from=builder /build/soul-demo /usr/local/bin/soul-demo +# k3s binary +COPY --from=builder /usr/local/bin/k3s /usr/local/bin/k3s + +# soul-demo OCI image tar — k3s imports this at startup (no registry needed) +RUN mkdir -p /var/lib/rancher/k3s/agent/images +COPY dist/soul-demo-image.tar /var/lib/rancher/k3s/agent/images/soul-demo.tar + +# k3s manifests — auto-applied when k3s starts +RUN mkdir -p /var/lib/rancher/k3s/server/manifests +COPY dist/k3s-soul-demo.yaml /var/lib/rancher/k3s/server/manifests/soul-demo.yaml + # Engram snapshot — baked in so soul has memory from cold start COPY dist/engram-snapshot.json /srv/soul/engram-demo/snapshot.json @@ -83,8 +101,11 @@ ENV LANDING_ROOT=/srv/landing ENV PORT=8080 ENV NEURON_HOME=/srv/soul/engram-demo ENV NEURON_PORT=7772 +ENV K3S_DATA_DIR=/var/lib/rancher/k3s +ENV KUBECONFIG=/var/lib/rancher/k3s/server/cred/admin.kubeconfig -USER landing +# k3s requires root to create network namespaces and mount cgroups. +# Cloud Run gen2 sandbox is the security boundary here. EXPOSE 8080 CMD ["/usr/local/bin/entrypoint.sh"] diff --git a/build-stage.sh b/build-stage.sh index af55074..a48ec8d 100755 --- a/build-stage.sh +++ b/build-stage.sh @@ -43,4 +43,22 @@ docker build \ -t "marketing:${TAG}" \ . +# Extract soul-demo binary and engram snapshot from built image +echo "==> Packaging soul-demo:local image for k3s..." +CONTAINER_ID=$(docker create "marketing:${TAG}") +docker cp "${CONTAINER_ID}:/usr/local/bin/soul-demo" dist/soul-demo +docker cp "${CONTAINER_ID}:/srv/soul/engram-demo/snapshot.json" dist/soul-demo-snapshot.json 2>/dev/null || true +docker rm "${CONTAINER_ID}" + +# Build minimal soul-demo container image +cp dist/soul-demo-snapshot.json dist/engram-snapshot.json 2>/dev/null || true +docker build \ + -f dist/Dockerfile.soul-demo \ + -t soul-demo:local \ + dist/ + +# Save as OCI tar for k3s to import at startup +docker save soul-demo:local -o dist/soul-demo-image.tar +echo "==> soul-demo:local image saved ($(du -sh dist/soul-demo-image.tar | cut -f1))" + echo "==> Done. marketing:${TAG} built." diff --git a/dist/Dockerfile.soul-demo b/dist/Dockerfile.soul-demo new file mode 100644 index 0000000..ca9ff6b --- /dev/null +++ b/dist/Dockerfile.soul-demo @@ -0,0 +1,13 @@ +FROM debian:bookworm-slim +RUN apt-get update \ + && apt-get install -y --no-install-recommends libcurl4 libssl3 ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && groupadd -r landing && useradd -r -g landing landing \ + && mkdir -p /srv/soul/engram-demo \ + && chown -R landing:landing /srv/soul +COPY soul-demo /usr/local/bin/soul-demo +COPY engram-snapshot.json /srv/soul/engram-demo/snapshot.json +ENV NEURON_HOME=/srv/soul/engram-demo +ENV NEURON_PORT=7772 +USER landing +CMD ["/usr/local/bin/soul-demo"] diff --git a/dist/entrypoint.sh b/dist/entrypoint.sh index 88f01dc..65a7f34 100644 --- a/dist/entrypoint.sh +++ b/dist/entrypoint.sh @@ -1,21 +1,35 @@ -#!/usr/bin/env bash -# entrypoint.sh — Start soul-demo then neuron-web in the same container. -# -# soul-demo runs in the background on :7772 (localhost only, not exposed). -# neuron-web runs in the foreground on :8080 (Cloud Run health checks this). -# If neuron-web exits, the container exits. Soul crashing is non-fatal — -# chat will return "demo soul not responding" but the page stays up. +#!/bin/sh +set -e -set -euo pipefail +echo "[entrypoint] Starting k3s server (embedded soul-demo orchestrator)..." -echo "[entrypoint] starting soul-demo on :7772" -/usr/local/bin/soul-demo & -SOUL_PID=$! +# k3s server — single-node mode, disable unused components +# --disable traefik,servicelb: we don't need an ingress or LB +# --disable metrics-server: saves ~50MB RAM +# --write-kubeconfig-mode=644: allow non-root reads +# --data-dir: use the pre-chowned dir +k3s server \ + --disable traefik \ + --disable servicelb \ + --disable metrics-server \ + --write-kubeconfig-mode=644 \ + --data-dir /var/lib/rancher/k3s \ + --node-name soul-node & -# Give the soul a few seconds to load its engram and seed safety nodes -sleep 4 +K3S_PID=$! -echo "[entrypoint] soul-demo started (pid=$SOUL_PID)" -echo "[entrypoint] starting neuron-web on :${PORT:-8080}" +echo "[entrypoint] Waiting for k3s to become ready..." +until k3s kubectl get nodes --no-headers 2>/dev/null | grep -q "Ready"; do + sleep 2 +done +echo "[entrypoint] k3s ready. soul-demo Deployment will be applied automatically from manifests." +# Wait for soul-demo pod to be Running before starting neuron-web +echo "[entrypoint] Waiting for soul-demo pod..." +until k3s kubectl get pods -l app=soul-demo --no-headers 2>/dev/null | grep -q "Running"; do + sleep 3 +done +echo "[entrypoint] soul-demo is running." + +echo "[entrypoint] Starting neuron-web on port ${PORT:-8080}..." exec /usr/local/bin/neuron-web diff --git a/dist/k3s-soul-demo.yaml b/dist/k3s-soul-demo.yaml new file mode 100644 index 0000000..e76eff1 --- /dev/null +++ b/dist/k3s-soul-demo.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: soul-demo + namespace: default + labels: + app: soul-demo +spec: + replicas: 1 + selector: + matchLabels: + app: soul-demo + template: + metadata: + labels: + app: soul-demo + spec: + containers: + - name: soul-demo + image: soul-demo:local + imagePullPolicy: Never + ports: + - containerPort: 7772 + env: + - name: NEURON_HOME + value: /srv/soul/engram-demo + - name: NEURON_PORT + value: "7772" + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 1000m + memory: 512Mi + livenessProbe: + httpGet: + path: /healthz + port: 7772 + initialDelaySeconds: 10 + periodSeconds: 15 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /healthz + port: 7772 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: engram-data + mountPath: /srv/soul/engram-demo + volumes: + - name: engram-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: soul-demo + namespace: default +spec: + type: NodePort + selector: + app: soul-demo + ports: + - port: 7772 + targetPort: 7772 + nodePort: 7772 + protocol: TCP +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: soul-demo + namespace: default +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: soul-demo + minReplicas: 1 + maxReplicas: 8 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80