Files
elizaos--eliza/.github/workflows/build-example-app-images.yml
T
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

355 lines
16 KiB
YAML

name: Build Example App Images
# Builds + pushes standalone example-app images as a smoke/publish lane. The
# #9300 real-staging showcase loop deploys these apps from source through the
# normal app deploy builder (`POST /api/v1/apps/:id/deploy` with repo/ref/
# Dockerfile), not by pinning these tags operator-side.
#
# Why we bundle on the runner instead of building ./Dockerfile from scratch:
# server.ts imports `@elizaos/cloud-sdk` (workspace:*). That only resolves
# inside the monorepo, and the SDK's dist/ is gitignored. A standalone docker
# build of ./Dockerfile would crash at boot ("Cannot find package
# @elizaos/cloud-sdk"). Instead we run `bun build server.ts` on the runner —
# where the workspace IS resolvable — which inlines the SDK into a single
# self-contained server.js (verified: 0 residual @elizaos/cloud-sdk imports),
# then ship that with Dockerfile.bundle from a tiny context. Same pre-build
# sequence build-agent-image.yml uses to make the cloud-sdk dist available.
on:
push:
branches: [develop]
paths:
- "packages/examples/cloud/edad/**"
- "packages/examples/cloud/clone-ur-crush/**"
- "packages/cloud/sdk/**"
- ".github/workflows/build-example-app-images.yml"
workflow_dispatch:
# One in-flight build per ref; a newer push cancels the stale one (avoids the
# queue pile-up the agent-image build learned the hard way).
concurrency:
group: build-example-edad-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: elizaos/example-edad
# Least privilege by default; the build job opts into packages: write.
permissions:
contents: read
jobs:
build-edad:
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
submodules: false
show-progress: false
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: "canary"
- name: Cache Bun install
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-canary-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-canary-
bun-${{ runner.os }}-
- name: Install dependencies
# Creates the @elizaos/cloud-sdk workspace symlink edad resolves through.
# --no-frozen-lockfile mirrors build-agent-image.yml: a downstream
# lockfile drift on develop shouldn't short-circuit the install.
env:
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
run: bun install --no-frozen-lockfile
- name: Build @elizaos/cloud-sdk
# The SDK's `exports.import` points at ./dist/index.js, which is
# gitignored (absent on a cold checkout). Without this build, the
# `bun build` below fails to resolve "@elizaos/cloud-sdk" even with the
# workspace symlink in place. build-agent-image.yml builds this same
# package for the same reason.
env:
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
run: |
cd packages/cloud/sdk
bun install --no-frozen-lockfile --ignore-scripts
bun run build
- name: Bundle the EDAD server (inline the workspace SDK)
# Produces a single self-contained server.js with @elizaos/cloud-sdk
# inlined — no workspace dep, no bun install needed in the image. Output
# goes to ./dist (already gitignored, same dir edad's `build` script
# uses); dist/ (server.js + public/) is the tiny docker build context.
run: |
set -euo pipefail
cd packages/examples/cloud/edad
node ../../../../packages/scripts/rm-path-recursive.mjs ./dist
bun build server.ts --outdir=./dist --target=bun --format=esm
cp -r public ./dist/public
test -f ./dist/server.js
# Guard: the bundle must NOT still reference the workspace dep, else
# the image would crash at boot.
if grep -q "@elizaos/cloud-sdk" ./dist/server.js; then
echo "::error::server.js still references @elizaos/cloud-sdk — bundle did not inline the workspace dep" >&2
exit 1
fi
- name: Smoke-test the image (gate the GHCR push on a working container)
# Build + run the exact bundle image and assert it actually serves before
# we publish it — so the showcase deploy never pulls a broken :showcase
# tag. Mirrors the local verification in DEPLOY_AND_VALIDATE.md.
run: |
set -euo pipefail
assert_asset() {
local asset_path="$1"
local expected_type="$2"
local min_bytes="$3"
local headers
local body
headers="$(mktemp)"
body="$(mktemp)"
curl -fsS -D "$headers" -o "$body" "http://127.0.0.1:3000/${asset_path}"
if ! grep -qi "content-type: ${expected_type}" "$headers"; then
echo "::error::${asset_path} content-type was not ${expected_type}" >&2
cat "$headers" >&2
exit 1
fi
local bytes
bytes="$(wc -c < "$body" | tr -d ' ')"
if [ "$bytes" -lt "$min_bytes" ]; then
echo "::error::${asset_path} was too small (${bytes} bytes)" >&2
exit 1
fi
}
docker build --load -t edad-smoke:ci \
-f packages/examples/cloud/edad/Dockerfile.bundle \
packages/examples/cloud/edad/dist
docker rm -f edad-smoke >/dev/null 2>&1 || true
trap 'docker rm -f edad-smoke >/dev/null 2>&1 || true' EXIT
docker run -d --name edad-smoke -p 3000:3000 edad-smoke:ci
ok=""
for _ in $(seq 1 30); do
if curl -fsS http://127.0.0.1:3000/health >/dev/null 2>&1; then ok=1; break; fi
sleep 1
done
if [ -z "$ok" ]; then
echo "::error::EDAD image never became healthy" >&2
docker logs edad-smoke || true
exit 1
fi
test "$(curl -fsS http://127.0.0.1:3000/health)" = "ok"
curl -fsS http://127.0.0.1:3000/ | grep -q "eDad" \
|| { echo "::error::EDAD did not serve its UI" >&2; exit 1; }
curl -fsS http://127.0.0.1:3000/ | grep -q 'content="http://127.0.0.1:3000/og-image.png"' \
|| { echo "::error::EDAD did not emit an absolute social image URL" >&2; exit 1; }
curl -fsS http://127.0.0.1:3000/api/config | grep -q "cloud_url" \
|| { echo "::error::EDAD /api/config is broken" >&2; exit 1; }
assert_asset dad-portrait.svg image/svg+xml 4000
assert_asset dad-avatar.svg image/svg+xml 2000
assert_asset you-avatar.svg image/svg+xml 500
assert_asset favicon.svg image/svg+xml 500
assert_asset favicon.png image/png 1000
assert_asset apple-touch-icon.png image/png 4000
assert_asset og-image.png image/png 100000
docker rm -f edad-smoke >/dev/null 2>&1 || true
trap - EXIT
echo "EDAD image smoke test passed: healthy + serves UI + /api/config + generated assets."
- uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c
- id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# :showcase → the moving tag the showcase spec deploys
# :sha-<short> → immutable per-commit tag (mirrors build-agent-image.yml)
tags: |
type=raw,value=showcase
type=sha,prefix=sha-,format=short
- id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
with:
context: packages/examples/cloud/edad/dist
file: packages/examples/cloud/edad/Dockerfile.bundle
push: true
provenance: false
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Make image public
# The deploy puller pulls anonymously; keep the package public.
# Idempotent — patching to public when already public is a no-op.
run: |
curl --fail-with-body --show-error --silent \
-X PATCH \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/orgs/elizaOS/packages/container/example-edad/visibility \
-d '{"visibility":"public"}' || \
echo "::warning::Could not set package visibility to public — set it once manually in the GHCR package settings"
build-clone-ur-crush:
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
submodules: false
show-progress: false
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: "canary"
- name: Cache Bun install
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-canary-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-canary-
bun-${{ runner.os }}-
- name: Install dependencies
env:
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
run: bun install --no-frozen-lockfile
- name: Build clone-ur-crush (Next.js standalone bundle)
# next.config sets output:"standalone"; with outputFileTracingRoot at the
# monorepo root the server lands at the repo-relative path inside the
# bundle. Next does NOT copy static/public into standalone, so we place
# them next to server.js where the standalone runtime serves them.
#
# CRITICAL (#9300): build.mjs builds under a temp distDir
# (.next-build-<pid>) then renames it to .next, but the standalone server
# BAKES that original distDir name into required-server-files.json and
# serves /_next/static from <bakedDistDir>/static. Copying static to the
# literal .next/static (the renamed dir) puts it at a path the runtime
# never reads, so every CSS/JS chunk 404s. Copy static to the BAKED dist
# dir instead.
env:
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
run: |
set -euo pipefail
cd packages/examples/cloud/clone-ur-crush
node ../../../../packages/scripts/rm-path-recursive.mjs .next
bun run build
app=.next/standalone/packages/examples/cloud/clone-ur-crush
test -f "$app/server.js"
# The standalone's baked distDir = the dir holding required-server-files.json.
rsf="$(find "$app" -maxdepth 2 -name required-server-files.json | head -1)"
test -n "$rsf"
baked="$(basename "$(dirname "$rsf")")"
echo "standalone baked distDir: $baked"
mkdir -p "$app/$baked/static"
cp -r .next/static/. "$app/$baked/static/"
cp -r public "$app/public"
- name: Smoke-test the image (gate the GHCR push on a working container)
# Build + run the exact standalone image and assert it actually serves its
# UI before we publish it, so the showcase deploy never pulls a broken
# :showcase tag. Mirrors the EDAD job's gate above (DoD: failures surface
# loudly; the showcase deploy can never pull a broken tag).
run: |
set -euo pipefail
docker build --load -t cuc-smoke:ci \
-f packages/examples/cloud/clone-ur-crush/Dockerfile.bundle \
packages/examples/cloud/clone-ur-crush/.next/standalone
docker rm -f cuc-smoke >/dev/null 2>&1 || true
trap 'docker rm -f cuc-smoke >/dev/null 2>&1 || true' EXIT
docker run -d --name cuc-smoke -p 3000:3000 cuc-smoke:ci
ok=""
for _ in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then ok=1; break; fi
sleep 1
done
if [ -z "$ok" ]; then
echo "::error::Clone Ur Crush image never started serving" >&2
docker logs cuc-smoke || true
exit 1
fi
home="$(curl -fsS http://127.0.0.1:3000/)"
echo "$home" | grep -q "Clone Your Crush" \
|| { echo "::error::Clone Ur Crush did not serve its UI" >&2; docker logs cuc-smoke || true; exit 1; }
# Assert a real /_next/static chunk serves 200 — this is what catches the
# distDir/static path mismatch (a broken bundle serves HTML but 404s
# every CSS/JS asset). Pull a static URL out of the rendered HTML.
asset="$(printf '%s' "$home" | grep -oE '/_next/static/[^"]+\.(js|css)' | head -1)"
test -n "$asset" || { echo "::error::no /_next/static asset referenced in HTML" >&2; exit 1; }
code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:3000${asset}")"
test "$code" = "200" \
|| { echo "::error::static asset ${asset} returned ${code} (expected 200) — broken /_next/static path" >&2; docker logs cuc-smoke || true; exit 1; }
# OG card must exist (was a 404 before #9300 fix).
og="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/og-image.png)"
test "$og" = "200" || { echo "::error::og-image.png returned ${og} (expected 200)" >&2; exit 1; }
docker rm -f cuc-smoke >/dev/null 2>&1 || true
trap - EXIT
echo "Clone Ur Crush image smoke test passed: serves UI + /_next/static (${asset}) + og-image.png."
- uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c
- id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9
with:
images: ${{ env.REGISTRY }}/elizaos/example-clone-ur-crush
tags: |
type=raw,value=showcase
type=sha,prefix=sha-,format=short
- id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
with:
context: packages/examples/cloud/clone-ur-crush/.next/standalone
file: packages/examples/cloud/clone-ur-crush/Dockerfile.bundle
push: true
provenance: false
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Make image public
run: |
curl --fail-with-body --show-error --silent \
-X PATCH \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/orgs/elizaOS/packages/container/example-clone-ur-crush/visibility \
-d '{"visibility":"public"}' || \
echo "::warning::Could not set package visibility to public — set it once manually in the GHCR package settings"