Files
neuron-web/build-stage.sh
T
will.anderson 8dca40c288 feat: embed k3s in neuron-web image to run soul-demo as managed pods
soul-demo now runs as a k3s Deployment with HPA (1–8 replicas, 60% CPU
target) instead of a bare background process. k3s starts first in
entrypoint.sh, imports the soul-demo:local OCI tar from
/var/lib/rancher/k3s/agent/images, and auto-applies the Deployment,
NodePort Service, and HPA from the server/manifests dir. neuron-web
starts only after the soul-demo pod is Running. Cloud Run gen2 execution
environment required for k3s (provides /dev/kmsg and Linux capabilities).
2026-05-07 00:57:06 -05:00

136 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# build-stage.sh — Build the Stage marketing image (neuron-web + soul-demo).
#
# Pipeline:
# 1. Stage the foundation El runtime into ./runtime/.
# 2. Compile client-side El sources (src/js/*.el) to dist/js/*.js using
# the JS-capable elc binary at bin/elc-linux-amd64 (CI) or the local
# elc (dev). Output is gitignored and rebuilt every run.
# 3. Concatenate src/*.el into dist/main-combined.el (component-first,
# main.el last; matches the historical order from build-local.sh).
# 4. Compile dist/main-combined.el → dist/main.c using the canonical
# native elc at foundation/el/dist/platform/elc.
# 5. Inject the host-side stub forward declarations into dist/main.c
# (sed header rewrite, same set as the prior in-Dockerfile sed).
# 6. docker buildx build --platform linux/amd64 -f Dockerfile.stage.
#
# bootstrap.py is no longer in the build path. The container image now
# expects dist/main.c to be a finished C source — it just runs cc on it.
#
# Usage:
# ./build-stage.sh <tag> — build marketing:<tag>
set -euo pipefail
cd "$(dirname "$0")"
TAG="${1:-dev}"
LANDING_DIR=$(pwd)
EL_HOME="${EL_HOME:-${LANDING_DIR}/../../foundation/el}"
ELC="${EL_HOME}/dist/platform/elc"
RUNTIME_SRC="${EL_HOME}/el-compiler/runtime"
# JS-capable elc: prefer committed bin/elc-linux-amd64 on CI (linux/amd64),
# fall back to the local elc from the El checkout on macOS dev.
if [ -f "${LANDING_DIR}/bin/elc-linux-amd64" ] && uname -m | grep -q x86_64; then
ELC_JS="${LANDING_DIR}/bin/elc-linux-amd64"
elif [ -x "${ELC}" ]; then
ELC_JS="${ELC}"
else
echo "elc for JS compilation not found — expected bin/elc-linux-amd64 or ${ELC}" >&2
exit 1
fi
if [ ! -x "${ELC}" ]; then
echo "elc not found at ${ELC}" >&2
exit 1
fi
echo "==> Staging El runtime from ${RUNTIME_SRC}"
mkdir -p runtime dist
cp "${RUNTIME_SRC}/el_runtime.c" runtime/
cp "${RUNTIME_SRC}/el_runtime.h" runtime/
# The JS compiler looks for el_runtime.js in the same directory as the
# source file being compiled. Copy it there so --bundle can inline it.
cp "${RUNTIME_SRC}/el_runtime.js" "${LANDING_DIR}/src/js/"
echo "==> Compiling client-side El (src/js/*.el) → dist/js/"
mkdir -p dist/js
for f in "${LANDING_DIR}/src/js/"*.el; do
name=$(basename "$f" .el)
"${ELC_JS}" --target=js --bundle --minify --obfuscate "$f" > "${LANDING_DIR}/dist/js/${name}.js"
echo " compiled: src/js/${name}.el → dist/js/${name}.js"
done
# Clean up the staged runtime (not a source file)
rm -f "${LANDING_DIR}/src/js/el_runtime.js"
echo "==> Combining El sources → dist/main-combined.el"
COMPONENTS=(nav hero pillars how_it_works inference efficiency comparison
environmental enterprise mission local_first pricing marketplace viral
footer styles about founding_badge terms enterprise_terms checkout safety
gallery account)
{
for f in "${COMPONENTS[@]}"; do
if [ -f "src/${f}.el" ]; then
grep -hv '^[[:space:]]*from\|^[[:space:]]*import' "src/${f}.el"
echo ""
fi
done
grep -v '^from\|^import' src/main.el
} > dist/main-combined.el
echo " $(wc -l < dist/main-combined.el) lines"
echo "==> Compiling dist/main-combined.el → dist/main.c via ${ELC}"
"${ELC}" dist/main-combined.el > dist/main.c
echo " $(wc -l < dist/main.c) lines of C"
echo "==> Injecting host-side stub forward declarations"
# GNU vs BSD sed: -i with no arg works on GNU, breaks on macOS BSD sed
# (BSD requires -i ''). Detect and branch.
SED_INPLACE=(-i)
if sed --version >/dev/null 2>&1; then
SED_INPLACE=(-i)
else
SED_INPLACE=(-i '')
fi
sed "${SED_INPLACE[@]}" \
's|#include "el_runtime.h"|#include "el_runtime.h"\nel_val_t http_get_auth(el_val_t url, el_val_t tok);\nel_val_t http_post_auth(el_val_t url, el_val_t tok, el_val_t body);\nel_val_t http_post_auth_json(el_val_t url, el_val_t tok, el_val_t body);\nel_val_t http_delete_auth(el_val_t url, el_val_t bearer_tok, el_val_t apikey);\nel_val_t cwd(void);\nel_val_t color_bold(el_val_t s);\nel_val_t unix_timestamp(void);\nel_val_t gcs_write(el_val_t bucket, el_val_t object_name, el_val_t content);\nel_val_t gcs_read(el_val_t bucket, el_val_t object_name);\nel_val_t supabase_insert(el_val_t project_url, el_val_t service_key, el_val_t table, el_val_t row_json);\nel_val_t supabase_get(el_val_t project_url, el_val_t service_key, el_val_t table_and_query);\nel_val_t supabase_auth_user(el_val_t project_url, el_val_t anon_key, el_val_t user_jwt);\nel_val_t supabase_admin_invite(el_val_t project_url, el_val_t service_key, el_val_t body_json);\nel_val_t supabase_upsert_user(el_val_t project_url, el_val_t anon_key, el_val_t user_jwt, el_val_t table_and_query, el_val_t row_json);|' \
dist/main.c
echo "==> Building Docker image marketing:${TAG}"
# Plain `docker build` — the gitea runner doesn't ship buildx, so
# `docker buildx build --platform ...` exits 125 ("unknown flag:
# --platform"). The runner host is already linux/amd64 so the
# explicit --platform is redundant. BUILDKIT_INLINE_CACHE works with
# plain docker as long as DOCKER_BUILDKIT=1 is set (default on the
# runner).
docker build \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from us-central1-docker.pkg.dev/neuron-785695/neuron-marketing/marketing:latest \
-f Dockerfile.stage \
-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."