Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a6f0defd1 | |||
| 740382fca1 | |||
| 180acc92a0 | |||
| 689062fc87 | |||
| e6fd110073 | |||
| 5e1344af42 | |||
| d8acb126f5 | |||
| 87ac67a70e | |||
| f838e0c8a7 | |||
| e520ba98ca | |||
| 21ecbca2e6 | |||
| 38c92e5fc7 | |||
| cee0328db5 | |||
| bbfc7cebf7 | |||
| 4a710ff294 | |||
| f1b5e1bac8 | |||
| b4438fec43 | |||
| aa040d1412 | |||
| d5820c43b0 | |||
| a1144605f3 | |||
| 43949b20a0 | |||
| 06b46c2e8f | |||
| ac5838f3dd | |||
| c8d1d3e1aa | |||
| b532519ad7 | |||
| b27aab20ee | |||
| 345f9be81a | |||
| 17e14a9fda | |||
| e7c1c922f7 | |||
| 954dc1d86e | |||
| a83efcda93 | |||
| 839c002ce0 | |||
| 0abef440fa | |||
| 9892d89c01 | |||
| 47163f690b | |||
| dc36fe0157 | |||
| fa65f7783e | |||
| b63aa5027b | |||
| 1110ff2e8c | |||
| a51a16c4da | |||
| 15c70f0e26 | |||
| b39977b74c |
+34
-13
@@ -75,6 +75,17 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
||||||
|
|
||||||
|
- name: Prune Docker to reclaim disk
|
||||||
|
run: |
|
||||||
|
# Remove stopped containers, dangling images, unused volumes/networks.
|
||||||
|
# Do NOT prune build cache — that keeps Docker builds fast and under
|
||||||
|
# the ~26min runner restart window. Selective pruning frees ~4-5GB
|
||||||
|
# which is enough to prevent overlay2 "no space left on device" errors.
|
||||||
|
docker container prune -f 2>&1 || true
|
||||||
|
docker image prune -f 2>&1 || true
|
||||||
|
docker volume prune -f 2>&1 || true
|
||||||
|
df -h /
|
||||||
|
|
||||||
# ── El SDK setup ──────────────────────────────────────────────────────
|
# ── El SDK setup ──────────────────────────────────────────────────────
|
||||||
# Push builds: extract elb + elc + runtime from ci-base (always latest).
|
# Push builds: extract elb + elc + runtime from ci-base (always latest).
|
||||||
# PR builds: use committed bin/elb-linux-amd64 + bin/elc-linux-amd64 + runtime/.
|
# PR builds: use committed bin/elb-linux-amd64 + bin/elc-linux-amd64 + runtime/.
|
||||||
@@ -90,7 +101,7 @@ jobs:
|
|||||||
docker rm "$CID"
|
docker rm "$CID"
|
||||||
echo "ELB=/opt/el/dist/bin/elb" >> "$GITHUB_ENV"
|
echo "ELB=/opt/el/dist/bin/elb" >> "$GITHUB_ENV"
|
||||||
echo "ELC=/opt/el/dist/platform/elc" >> "$GITHUB_ENV"
|
echo "ELC=/opt/el/dist/platform/elc" >> "$GITHUB_ENV"
|
||||||
echo "EL_RUNTIME=/opt/el/el-compiler/runtime" >> "$GITHUB_ENV"
|
echo "EL_RUNTIME=$GITHUB_WORKSPACE/runtime" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Set up El SDK from committed bin/ (PR builds)
|
- name: Set up El SDK from committed bin/ (PR builds)
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
@@ -146,6 +157,13 @@ jobs:
|
|||||||
rm -f src/js/el_runtime.js
|
rm -f src/js/el_runtime.js
|
||||||
|
|
||||||
# ── Docker build + smoke test ─────────────────────────────────────────
|
# ── Docker build + smoke test ─────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# PR builds: binary is compiled by committed bin/elb-linux-amd64 which
|
||||||
|
# may lag behind the current El SDK. Smoke-testing that binary is
|
||||||
|
# unreliable (glibc mismatch in Docker; potential codegen differences
|
||||||
|
# when run directly). PRs only need to prove the code *compiles* and
|
||||||
|
# the Docker image *builds* — the authoritative runtime check runs on
|
||||||
|
# push to dev (ci-base SDK, always current).
|
||||||
|
|
||||||
- name: Compute image tag
|
- name: Compute image tag
|
||||||
id: tag
|
id: tag
|
||||||
@@ -154,6 +172,12 @@ jobs:
|
|||||||
- name: Touch HTML placeholder files
|
- name: Touch HTML placeholder files
|
||||||
run: touch src/index.html src/about.html src/terms.html src/enterprise-terms.html
|
run: touch src/index.html src/about.html src/terms.html src/enterprise-terms.html
|
||||||
|
|
||||||
|
- name: Create soul-demo-image.tar placeholder
|
||||||
|
# Dockerfile.stage COPYs this file (used by k3s at runtime).
|
||||||
|
# We only need the COPY to succeed here; real tar is built by
|
||||||
|
# build-stage.sh in the deploy pipeline.
|
||||||
|
run: touch dist/soul-demo-image.tar
|
||||||
|
|
||||||
- name: Build Docker image (local only — no push)
|
- name: Build Docker image (local only — no push)
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -170,30 +194,27 @@ jobs:
|
|||||||
.
|
.
|
||||||
|
|
||||||
- name: Local smoke test
|
- name: Local smoke test
|
||||||
|
# Push builds only: binary compiled from ci-base is current and
|
||||||
|
# compatible with the runner glibc. Skipped for pull_request events
|
||||||
|
# because the committed bin/elb may produce a binary that requires
|
||||||
|
# a newer glibc than what the runner environment provides.
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
IMAGE="marketing:${{ steps.tag.outputs.tag }}"
|
PORT=8080 dist/neuron-landing &
|
||||||
|
SERVER_PID=$!
|
||||||
docker run -d --name dev-smoke \
|
|
||||||
-p 8080:8080 \
|
|
||||||
-e PORT=8080 \
|
|
||||||
-e NODE_ENV=production \
|
|
||||||
-e LANDING_ROOT=/srv/landing \
|
|
||||||
"$IMAGE"
|
|
||||||
|
|
||||||
for i in $(seq 1 15); do
|
for i in $(seq 1 15); do
|
||||||
STATUS=$(curl -sSo /dev/null -w "%{http_code}" --max-time 5 http://localhost:8080/ || echo "000")
|
STATUS=$(curl -sSo /dev/null -w "%{http_code}" --max-time 5 http://localhost:8080/ || echo "000")
|
||||||
echo "Attempt $i/15: HTTP $STATUS"
|
echo "Attempt $i/15: HTTP $STATUS"
|
||||||
if [ "$STATUS" = "200" ]; then
|
if [ "$STATUS" = "200" ]; then
|
||||||
echo "Dev smoke test PASSED"
|
echo "Dev smoke test PASSED"
|
||||||
docker stop dev-smoke && docker rm dev-smoke
|
kill "$SERVER_PID" 2>/dev/null || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
sleep 3
|
sleep 3
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "--- container logs ---"
|
kill "$SERVER_PID" 2>/dev/null || true
|
||||||
docker logs dev-smoke || true
|
|
||||||
docker stop dev-smoke && docker rm dev-smoke || true
|
|
||||||
echo "Dev smoke test FAILED"
|
echo "Dev smoke test FAILED"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
+79
-12
@@ -32,10 +32,16 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Enforce dev-only source
|
- name: Enforce dev-only source
|
||||||
# stage only accepts merges from dev. Any PR from another branch fails
|
# stage only accepts merges from dev. Any PR from another branch fails
|
||||||
# here before a single build step runs.
|
# here before a single build step runs.
|
||||||
# workflow_dispatch is exempt (allows manual redeploy of current stage).
|
# workflow_dispatch is exempt (allows manual redeploy of current stage).
|
||||||
|
# Must run AFTER checkout — git commands require a cloned workspace.
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -43,7 +49,17 @@ jobs:
|
|||||||
echo "Merge commit: $COMMIT_MSG"
|
echo "Merge commit: $COMMIT_MSG"
|
||||||
# Gitea merge commits: "Merge pull request '...' (#N) from dev into stage"
|
# Gitea merge commits: "Merge pull request '...' (#N) from dev into stage"
|
||||||
# Direct branch merges: "Merge branch 'dev' into stage"
|
# Direct branch merges: "Merge branch 'dev' into stage"
|
||||||
if echo "$COMMIT_MSG" | grep -qE " from dev into stage$| 'dev' into stage$"; then
|
# tea pr merge with custom title: any subject line is possible, so
|
||||||
|
# fall back to checking git parents — if the second parent is on dev
|
||||||
|
# the merge came from dev regardless of the commit subject.
|
||||||
|
SECOND_PARENT=$(git log -1 --pretty=format:"%P" HEAD | awk '{print $2}')
|
||||||
|
FROM_DEV=""
|
||||||
|
if [ -n "$SECOND_PARENT" ]; then
|
||||||
|
if git merge-base --is-ancestor "$SECOND_PARENT" origin/dev 2>/dev/null; then
|
||||||
|
FROM_DEV=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if echo "$COMMIT_MSG" | grep -qE " from dev into stage$| 'dev' into stage$" || [ -n "$FROM_DEV" ]; then
|
||||||
echo "Source branch check: OK (merged from dev)"
|
echo "Source branch check: OK (merged from dev)"
|
||||||
else
|
else
|
||||||
echo "ERROR: stage only accepts merges from dev."
|
echo "ERROR: stage only accepts merges from dev."
|
||||||
@@ -51,11 +67,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Detect change type
|
- name: Detect change type
|
||||||
id: changetype
|
id: changetype
|
||||||
run: |
|
run: |
|
||||||
@@ -85,6 +96,17 @@ jobs:
|
|||||||
- name: Configure docker auth for Artifact Registry
|
- name: Configure docker auth for Artifact Registry
|
||||||
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
||||||
|
|
||||||
|
- name: Prune Docker to reclaim disk
|
||||||
|
run: |
|
||||||
|
# Remove stopped containers, dangling images, unused volumes/networks.
|
||||||
|
# Do NOT prune build cache — that keeps Docker builds fast and under
|
||||||
|
# the ~26min runner restart window. Selective pruning frees ~4-5GB
|
||||||
|
# which is enough to prevent overlay2 "no space left on device" errors.
|
||||||
|
docker container prune -f 2>&1 || true
|
||||||
|
docker image prune -f 2>&1 || true
|
||||||
|
docker volume prune -f 2>&1 || true
|
||||||
|
df -h /
|
||||||
|
|
||||||
- name: Compute image tag
|
- name: Compute image tag
|
||||||
id: tag
|
id: tag
|
||||||
run: |
|
run: |
|
||||||
@@ -106,14 +128,14 @@ jobs:
|
|||||||
if: steps.changetype.outputs.asset_only != 'true'
|
if: steps.changetype.outputs.asset_only != 'true'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
docker pull us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base:stage
|
docker pull us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base:dev
|
||||||
CID=$(docker create us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base:stage)
|
CID=$(docker create us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base:dev)
|
||||||
sudo mkdir -p /opt/el
|
sudo mkdir -p /opt/el
|
||||||
docker cp "$CID:/opt/el" /opt/
|
docker cp "$CID:/opt/el" /opt/
|
||||||
docker rm "$CID"
|
docker rm "$CID"
|
||||||
echo "ELB=/opt/el/dist/bin/elb" >> "$GITHUB_ENV"
|
echo "ELB=/opt/el/dist/bin/elb" >> "$GITHUB_ENV"
|
||||||
echo "ELC=/opt/el/dist/platform/elc" >> "$GITHUB_ENV"
|
echo "ELC=/opt/el/dist/platform/elc" >> "$GITHUB_ENV"
|
||||||
echo "EL_RUNTIME=/opt/el/el-compiler/runtime" >> "$GITHUB_ENV"
|
echo "EL_RUNTIME=$GITHUB_WORKSPACE/runtime" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
# ── Build neuron-web binary ───────────────────────────────────────────
|
# ── Build neuron-web binary ───────────────────────────────────────────
|
||||||
|
|
||||||
@@ -132,25 +154,70 @@ jobs:
|
|||||||
if: steps.changetype.outputs.asset_only != 'true'
|
if: steps.changetype.outputs.asset_only != 'true'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
echo "ELC=$ELC"
|
||||||
|
echo "EL_RUNTIME=$EL_RUNTIME"
|
||||||
|
echo "el_runtime.js: $(ls -lh "$EL_RUNTIME/el_runtime.js" 2>&1)"
|
||||||
cp "$EL_RUNTIME/el_runtime.js" src/js/
|
cp "$EL_RUNTIME/el_runtime.js" src/js/
|
||||||
mkdir -p dist/js
|
mkdir -p dist/js
|
||||||
for f in src/js/*.el; do
|
for f in src/js/*.el; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
name=$(basename "$f" .el)
|
name=$(basename "$f" .el)
|
||||||
"$ELC" --target=js --bundle --minify --obfuscate "$f" > "dist/js/${name}.js"
|
echo "Compiling $f..."
|
||||||
|
"$ELC" --target=js --bundle --minify --obfuscate "$f" > "dist/js/${name}.js" || {
|
||||||
|
echo "elc FAILED on $f"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
echo " compiled: $f -> dist/js/${name}.js"
|
echo " compiled: $f -> dist/js/${name}.js"
|
||||||
done
|
done
|
||||||
rm -f src/js/el_runtime.js
|
rm -f src/js/el_runtime.js
|
||||||
|
|
||||||
# ── Docker build + push ───────────────────────────────────────────────
|
# ── Docker build + push ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
- name: Build soul-demo image tar
|
||||||
|
# Dockerfile.stage COPYs dist/soul-demo-image.tar so k3s can import
|
||||||
|
# soul-demo:local at runtime. We compile soul-demo from source on the
|
||||||
|
# host runner (ci-base has gcc), build a minimal OCI image, and save it.
|
||||||
|
# Moved AFTER JS compilation to avoid Docker memory pressure killing elc.
|
||||||
|
if: steps.changetype.outputs.asset_only != 'true'
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# Compile el_runtime.o and soul-demo on the host runner
|
||||||
|
cc -O2 -DHAVE_CURL -c runtime/el_runtime.c -I runtime/ -o /tmp/el_runtime.o
|
||||||
|
cc -O2 -rdynamic -DEL_SOUL_DEMO_BUILD \
|
||||||
|
-I runtime/ \
|
||||||
|
-o dist/soul-demo \
|
||||||
|
dist/soul-demo.c dist/vessel_stubs.c /tmp/el_runtime.o \
|
||||||
|
-lcurl -lpthread -ldl -lm -lssl -lcrypto
|
||||||
|
echo "soul-demo compiled: $(ls -lh dist/soul-demo)"
|
||||||
|
# Package as minimal OCI image for k3s import
|
||||||
|
# --no-cache: prevents reuse of corrupted overlay2 layers from prior failed runs
|
||||||
|
docker build --no-cache -f dist/Dockerfile.soul-demo -t soul-demo:local dist/
|
||||||
|
docker save soul-demo:local -o dist/soul-demo-image.tar
|
||||||
|
echo "soul-demo-image.tar: $(du -sh dist/soul-demo-image.tar | cut -f1)"
|
||||||
|
docker rmi soul-demo:local 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Download k3s binary
|
||||||
|
# Pre-download k3s on the host runner so Dockerfile.stage can COPY it
|
||||||
|
# directly. Previously k3s was downloaded inside the Docker builder stage,
|
||||||
|
# which combined with build-essential and C compilation caused RWLayer nil
|
||||||
|
# corruption on the runner's overlay2 driver. Host-runner download is safe.
|
||||||
|
if: steps.changetype.outputs.asset_only != 'true'
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
curl -fL --retry 3 --retry-delay 10 \
|
||||||
|
https://github.com/k3s-io/k3s/releases/download/v1.32.4%2Bk3s1/k3s \
|
||||||
|
-o dist/k3s
|
||||||
|
chmod +x dist/k3s
|
||||||
|
echo "k3s: $(ls -lh dist/k3s)"
|
||||||
|
|
||||||
- name: Build and tag image
|
- name: Build and tag image
|
||||||
if: steps.changetype.outputs.asset_only != 'true'
|
if: steps.changetype.outputs.asset_only != 'true'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# --no-cache: prevents reuse of corrupted overlay2 layers from prior failed runs.
|
||||||
|
# Dockerfile.stage is now single-stage (no builder) so build is fast even without cache.
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
--no-cache \
|
||||||
--cache-from us-central1-docker.pkg.dev/neuron-785695/neuron-marketing/marketing:stage-latest \
|
|
||||||
-f Dockerfile.stage \
|
-f Dockerfile.stage \
|
||||||
-t "marketing:${{ steps.tag.outputs.tag }}" \
|
-t "marketing:${{ steps.tag.outputs.tag }}" \
|
||||||
.
|
.
|
||||||
|
|||||||
+19
-47
@@ -4,55 +4,24 @@
|
|||||||
# - neuron-web on port 8080 (landing page server)
|
# - neuron-web on port 8080 (landing page server)
|
||||||
# - soul-demo on port 7772 (demo chat, localhost only)
|
# - soul-demo on port 7772 (demo chat, localhost only)
|
||||||
#
|
#
|
||||||
# neuron-web is built by `elb build` in CI (not here). elb compiles each
|
# All binaries (neuron-web, soul-demo, k3s) are pre-built by CI on the host
|
||||||
# .el source independently and links the result — no combined mega-file,
|
# runner before this Dockerfile runs. This keeps the Docker build single-stage
|
||||||
# no exponential memory growth. The binary lands at dist/neuron-landing
|
# with no compilation and no network downloads, eliminating the multi-stage
|
||||||
# (linux/amd64) and is COPY'd directly into the runtime image.
|
# complexity that caused RWLayer corruption on the runner's overlay2 driver.
|
||||||
#
|
#
|
||||||
# soul-demo.c is pre-committed (small, no OOM risk) and compiled here.
|
# CI pre-build steps (in stage.yaml):
|
||||||
|
# - neuron-web: built by `elb build` → dist/neuron-landing
|
||||||
|
# - soul-demo: compiled by cc on host → dist/soul-demo
|
||||||
|
# - k3s: downloaded by curl on host → dist/k3s
|
||||||
|
|
||||||
# ── Stage 1: compile soul-demo ────────────────────────────────────────────────
|
FROM ubuntu:24.04
|
||||||
FROM debian:bookworm-slim AS builder
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
libcurl4t64 \
|
||||||
curl \
|
libssl3t64 \
|
||||||
libcurl4-openssl-dev \
|
|
||||||
libssl-dev \
|
|
||||||
ca-certificates \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
COPY runtime/el_runtime.c runtime/el_runtime.h ./
|
|
||||||
|
|
||||||
# Pre-compile el_runtime as a separate cached layer.
|
|
||||||
# el_runtime.c changes rarely; main.c changes every run.
|
|
||||||
# Splitting this out means el_runtime.o is cached across builds when only main.c changes.
|
|
||||||
# -DHAVE_CURL: the staged el_runtime.c (from el.git) guards the OTLP observability
|
|
||||||
# section (emit_metric, emit_log, trace_span_*) behind #ifdef HAVE_CURL.
|
|
||||||
# libcurl IS installed above, so define HAVE_CURL to enable those functions.
|
|
||||||
RUN cc -O2 -DHAVE_CURL -c el_runtime.c -I. -o el_runtime.o
|
|
||||||
|
|
||||||
COPY dist/soul-demo.c dist/vessel_stubs.c ./
|
|
||||||
|
|
||||||
RUN cc -O2 -rdynamic \
|
|
||||||
-o soul-demo \
|
|
||||||
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
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
libcurl4 \
|
|
||||||
libssl3 \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& groupadd -r landing && useradd -r -g landing landing \
|
&& groupadd -r landing && useradd -r -g landing landing \
|
||||||
@@ -66,10 +35,13 @@ RUN apt-get update \
|
|||||||
COPY dist/neuron-landing /usr/local/bin/neuron-web
|
COPY dist/neuron-landing /usr/local/bin/neuron-web
|
||||||
RUN chmod +x /usr/local/bin/neuron-web
|
RUN chmod +x /usr/local/bin/neuron-web
|
||||||
|
|
||||||
COPY --from=builder /build/soul-demo /usr/local/bin/soul-demo
|
# soul-demo binary — compiled by cc on host runner in CI
|
||||||
|
COPY dist/soul-demo /usr/local/bin/soul-demo
|
||||||
|
RUN chmod +x /usr/local/bin/soul-demo
|
||||||
|
|
||||||
# k3s binary
|
# k3s binary — downloaded from GitHub releases by CI
|
||||||
COPY --from=builder /usr/local/bin/k3s /usr/local/bin/k3s
|
COPY dist/k3s /usr/local/bin/k3s
|
||||||
|
RUN chmod +x /usr/local/bin/k3s
|
||||||
|
|
||||||
# soul-demo OCI image tar — k3s imports this at startup (no registry needed)
|
# soul-demo OCI image tar — k3s imports this at startup (no registry needed)
|
||||||
RUN mkdir -p /var/lib/rancher/k3s/agent/images
|
RUN mkdir -p /var/lib/rancher/k3s/agent/images
|
||||||
|
|||||||
Vendored
+21
-15
@@ -1,6 +1,14 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# SKIP_K3S=1 — bypass k3s/soul-demo startup and go straight to neuron-web.
|
||||||
|
# Used by the dev CI smoke test where the container runtime doesn't support
|
||||||
|
# the kernel capabilities k3s requires (overlayfs / privileged mode).
|
||||||
|
if [ "${SKIP_K3S:-0}" = "1" ]; then
|
||||||
|
echo "[entrypoint] SKIP_K3S=1: starting neuron-web directly (no k3s/soul-demo)."
|
||||||
|
exec /usr/local/bin/neuron-web
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[entrypoint] Starting k3s server (embedded soul-demo orchestrator)..."
|
echo "[entrypoint] Starting k3s server (embedded soul-demo orchestrator)..."
|
||||||
|
|
||||||
# k3s server — single-node mode, disable unused components
|
# k3s server — single-node mode, disable unused components
|
||||||
@@ -8,28 +16,26 @@ echo "[entrypoint] Starting k3s server (embedded soul-demo orchestrator)..."
|
|||||||
# --disable metrics-server: saves ~50MB RAM
|
# --disable metrics-server: saves ~50MB RAM
|
||||||
# --write-kubeconfig-mode=644: allow non-root reads
|
# --write-kubeconfig-mode=644: allow non-root reads
|
||||||
# --data-dir: use the pre-chowned dir
|
# --data-dir: use the pre-chowned dir
|
||||||
|
# --flannel-iface=eth0: explicitly set the network interface.
|
||||||
|
# Cloud Run gen2 provides eth0 but k3s default IP detection walks the routing
|
||||||
|
# table looking for a default route, which fails in Cloud Run's network sandbox.
|
||||||
|
# Pinning to eth0 bypasses that detection and lets k3s bind correctly.
|
||||||
k3s server \
|
k3s server \
|
||||||
--disable traefik \
|
--disable traefik \
|
||||||
--disable servicelb \
|
--disable servicelb \
|
||||||
--disable metrics-server \
|
--disable metrics-server \
|
||||||
--write-kubeconfig-mode=644 \
|
--write-kubeconfig-mode=644 \
|
||||||
--data-dir /var/lib/rancher/k3s \
|
--data-dir /var/lib/rancher/k3s \
|
||||||
--node-name soul-node &
|
--node-name soul-node \
|
||||||
|
--flannel-iface=eth0 &
|
||||||
|
|
||||||
K3S_PID=$!
|
K3S_PID=$!
|
||||||
|
|
||||||
echo "[entrypoint] Waiting for k3s to become ready..."
|
# Start neuron-web immediately — do NOT block on k3s becoming ready.
|
||||||
until k3s kubectl get nodes --no-headers 2>/dev/null | grep -q "Ready"; do
|
# Cloud Run's startup probe requires port 8080 to be listening within the
|
||||||
sleep 2
|
# startup timeout. k3s may take 30-60s to initialise; blocking here causes
|
||||||
done
|
# probe failures and container termination before neuron-web ever starts.
|
||||||
echo "[entrypoint] k3s ready. soul-demo Deployment will be applied automatically from manifests."
|
# soul-demo becomes available asynchronously once k3s is ready. neuron-web
|
||||||
|
# handles soul-demo being temporarily unavailable gracefully.
|
||||||
# Wait for soul-demo pod to be Running before starting neuron-web
|
echo "[entrypoint] Starting neuron-web on port ${PORT:-8080} (k3s initialising in background)..."
|
||||||
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
|
exec /usr/local/bin/neuron-web
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1819
-1800
File diff suppressed because it is too large
Load Diff
Vendored
+25
@@ -6,6 +6,31 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "el_runtime.h"
|
#include "el_runtime.h"
|
||||||
|
|
||||||
|
/* Pre-register the El HTTP handler so it is found by http_lookup_active()
|
||||||
|
* regardless of whether the binary was linked with -rdynamic.
|
||||||
|
*
|
||||||
|
* el_runtime's http_set_handler resolves handler names via:
|
||||||
|
* dlsym(RTLD_DEFAULT, "handle_request")
|
||||||
|
* but dlsym only searches the dynamic symbol table, which only contains
|
||||||
|
* user-defined symbols when the executable is linked with -rdynamic.
|
||||||
|
* elb does not add -rdynamic, so dlsym returns NULL and routes return
|
||||||
|
* "el-runtime: no http handler registered" even though http_serve is called.
|
||||||
|
*
|
||||||
|
* The fix: forward-declare handle_request here and register it directly
|
||||||
|
* via el_runtime_register_handler before main() runs. This populates the
|
||||||
|
* handler registry so http_lookup_active() finds it without needing dlsym.
|
||||||
|
*/
|
||||||
|
extern el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
/* el_runtime_register_handler is intentionally not declared in el_runtime.h
|
||||||
|
* ("extern lookup works since C symbols are global" — runtime comment). */
|
||||||
|
extern void el_runtime_register_handler(const char* name,
|
||||||
|
el_val_t (*fn)(el_val_t, el_val_t, el_val_t));
|
||||||
|
|
||||||
|
__attribute__((constructor))
|
||||||
|
static void pre_register_http_handlers(void) {
|
||||||
|
el_runtime_register_handler("handle_request", handle_request);
|
||||||
|
}
|
||||||
|
|
||||||
el_val_t http_get_auth(el_val_t url, el_val_t tok) {
|
el_val_t http_get_auth(el_val_t url, el_val_t tok) {
|
||||||
char bearer[2048]; snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(tok));
|
char bearer[2048]; snprintf(bearer, sizeof(bearer), "Bearer %s", EL_CSTR(tok));
|
||||||
el_val_t hdr_val = EL_STR(bearer);
|
el_val_t hdr_val = EL_STR(bearer);
|
||||||
|
|||||||
@@ -878,6 +878,32 @@ el_val_t __uuid_v4(void);
|
|||||||
/* Args */
|
/* Args */
|
||||||
el_val_t __args_json(void);
|
el_val_t __args_json(void);
|
||||||
|
|
||||||
|
/* ── neuron-web stubs (web_stubs.c) ──────────────────────────────────────────
|
||||||
|
* Forward declarations so generated C (e.g. dist/main.c) sees the correct
|
||||||
|
* el_val_t return type instead of an implicit int. Without these, the
|
||||||
|
* ci-base elb (which does not emit extern-fn forward decls for stub-only
|
||||||
|
* functions) produces truncated 32-bit returns on 64-bit Linux → segfault.
|
||||||
|
*
|
||||||
|
* Guarded by EL_SOUL_DEMO_BUILD: soul-demo.c includes this header but
|
||||||
|
* defines its own (different-arity) versions of some of these functions.
|
||||||
|
* Dockerfile.stage compiles soul-demo with -DEL_SOUL_DEMO_BUILD to skip
|
||||||
|
* this block and avoid conflicting-types errors.
|
||||||
|
*/
|
||||||
|
#ifndef EL_SOUL_DEMO_BUILD
|
||||||
|
el_val_t http_get_auth(el_val_t url, el_val_t tok);
|
||||||
|
el_val_t http_post_auth(el_val_t url, el_val_t tok, el_val_t body);
|
||||||
|
el_val_t http_post_auth_json(el_val_t url, el_val_t tok, el_val_t body);
|
||||||
|
el_val_t http_delete_auth(el_val_t url, el_val_t bearer_tok, el_val_t apikey);
|
||||||
|
el_val_t supabase_get(el_val_t project_url, el_val_t service_key, el_val_t table_and_query);
|
||||||
|
el_val_t supabase_insert(el_val_t project_url, el_val_t service_key, el_val_t table, el_val_t row_json);
|
||||||
|
el_val_t supabase_auth_user(el_val_t project_url, el_val_t anon_key, el_val_t user_jwt);
|
||||||
|
el_val_t supabase_admin_invite(el_val_t project_url, el_val_t service_key, el_val_t body_json);
|
||||||
|
el_val_t gcs_write(el_val_t bucket, el_val_t object_name, el_val_t content);
|
||||||
|
el_val_t gcs_read(el_val_t bucket, el_val_t object_name);
|
||||||
|
el_val_t cwd(void);
|
||||||
|
el_val_t color_bold(el_val_t s);
|
||||||
|
#endif /* EL_SOUL_DEMO_BUILD */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+5
-1
@@ -16,7 +16,11 @@ extern fn page_css() -> String
|
|||||||
extern fn page_ga_script() -> String
|
extern fn page_ga_script() -> String
|
||||||
extern fn page_schema() -> String
|
extern fn page_schema() -> String
|
||||||
|
|
||||||
extern fn page_close() -> String
|
extern fn _page_close_impl() -> String
|
||||||
|
|
||||||
|
fn page_close() -> String {
|
||||||
|
return _page_close_impl()
|
||||||
|
}
|
||||||
|
|
||||||
// el-html vessel — extern declarations (implementations in dist/elhtml_impl.c)
|
// el-html vessel — extern declarations (implementations in dist/elhtml_impl.c)
|
||||||
extern fn el_meta(name: String, content: String) -> String
|
extern fn el_meta(name: String, content: String) -> String
|
||||||
|
|||||||
Reference in New Issue
Block a user