Skip to content

feat(cloud): Hetzner control plane IaC + data plane naming + legacy milady-core deprecation #766

feat(cloud): Hetzner control plane IaC + data plane naming + legacy milady-core deprecation

feat(cloud): Hetzner control plane IaC + data plane naming + legacy milady-core deprecation #766

Workflow file for this run

name: Cloud CF Deploy
# Cloudflare deployment for the eliza-cloud SPA + Workers API.
# - push to develop -> staging (api-staging.elizacloud.ai + Pages preview "develop")
# - push to main -> production (api.elizacloud.ai + Pages production)
# - pull_request -> preview (Pages preview branch backed by staging API)
#
# Required repo secrets:
# CLOUDFLARE_API_TOKEN - account-scoped token with Workers + Pages edit perms
# CLOUDFLARE_ACCOUNT_ID - matches account_id in packages/cloud-api/wrangler.toml
#
# Source: cloud/.github/workflows/cf-deploy.yml in the original elizaOS/cloud repo.
on:
push:
branches: [main, develop, develop-cf]
paths:
- "packages/cloud-api/**"
- "packages/cloud-frontend/**"
- "packages/cloud-shared/**"
- "packages/cloud-services/**"
- ".github/workflows/cloud-cf-deploy.yml"
pull_request:
branches: [main, develop, develop-cf]
paths:
- "packages/cloud-api/**"
- "packages/cloud-frontend/**"
- "packages/cloud-shared/**"
- "packages/cloud-services/**"
- ".github/workflows/cloud-cf-deploy.yml"
workflow_dispatch:
inputs:
environment:
description: Cloudflare environment to deploy
required: true
default: staging
type: choice
options:
- staging
- production
concurrency:
group: cloud-cf-deploy-${{ github.ref }}
cancel-in-progress: true
# Default to least privilege. Override per-job where needed.
permissions:
contents: read
jobs:
# ---------------------------------------------------------------------------
# API Worker
# ---------------------------------------------------------------------------
deploy-api:
name: Deploy API Worker
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.13"
- name: Install dependencies
run: bun install --no-save && git diff --exit-code -- bun.lock
- name: Build linked elizaOS workspaces
run: bun run build:core
- name: Verify Worker
run: bun run --cwd packages/cloud-api typecheck
- name: Run Worker e2e
run: bun run --cwd packages/cloud-api test:e2e
- name: Build
run: bun run --cwd packages/cloud-api build
- name: Resolve deploy environment
id: env
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "production" ]; then
echo "wrangler_args=--env production" >> "$GITHUB_OUTPUT"
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "wrangler_args=--env staging" >> "$GITHUB_OUTPUT"
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "wrangler_args=--env production" >> "$GITHUB_OUTPUT"
else
echo "wrangler_args=--env staging" >> "$GITHUB_OUTPUT"
fi
- name: Check Cloudflare credentials
id: cf
env:
HAS_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN != '' }}
HAS_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID != '' }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$HAS_TOKEN" = "true" ] && [ "$HAS_ACCOUNT" = "true" ]; then
echo "configured=true" >> "$GITHUB_OUTPUT"
else
echo "configured=false" >> "$GITHUB_OUTPUT"
MSG="CLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_ID not configured. Add them at https://github.com/elizaOS/eliza/settings/secrets/actions."
if [ "$EVENT_NAME" = "pull_request" ]; then
# PRs from forks legitimately lack access to repo secrets.
echo "::warning::$MSG Skipping Worker deploy (PR event)." >> "$GITHUB_STEP_SUMMARY"
else
echo "::error::$MSG Refusing to silently skip Worker deploy on $EVENT_NAME event." >> "$GITHUB_STEP_SUMMARY"
echo "::error::$MSG Refusing to silently skip Worker deploy on $EVENT_NAME event."
exit 1
fi
fi
- name: Deploy to Cloudflare Workers
if: steps.cf.outputs.configured == 'true'
working-directory: packages/cloud-api
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler deploy ${{ steps.env.outputs.wrangler_args }}
# ---------------------------------------------------------------------------
# Frontend (Cloudflare Pages)
# ---------------------------------------------------------------------------
deploy-frontend:
name: Deploy Frontend (Pages)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.13"
- name: Install dependencies
run: bun install --no-save && git diff --exit-code -- bun.lock
- name: Build linked elizaOS core workspace
run: bun run build:core
- name: Verify Frontend
run: bun run --cwd packages/cloud-frontend typecheck
- name: Build
run: bun run --cwd packages/cloud-frontend build
env:
# Pages preview/staging/prod all share the same API host today;
# override per-env if that changes.
VITE_API_URL: ${{ ((github.event_name == 'workflow_dispatch' && inputs.environment == 'production') || github.ref == 'refs/heads/main') && 'https://api.elizacloud.ai' || 'https://api-staging.elizacloud.ai' }}
NEXT_PUBLIC_API_URL: ${{ ((github.event_name == 'workflow_dispatch' && inputs.environment == 'production') || github.ref == 'refs/heads/main') && 'https://api.elizacloud.ai' || 'https://api-staging.elizacloud.ai' }}
NEXT_PUBLIC_APP_URL: ${{ ((github.event_name == 'workflow_dispatch' && inputs.environment == 'production') || github.ref == 'refs/heads/main') && 'https://elizacloud.ai' || 'https://staging.elizacloud.ai' }}
NEXT_PUBLIC_STEWARD_TENANT_ID: elizacloud
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ vars.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID }}
- name: Resolve Pages branch
id: pages
env:
PR_HEAD_REF: ${{ github.head_ref }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
echo "branch=$PR_HEAD_REF" >> "$GITHUB_OUTPUT"
elif [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "production" ]; then
echo "branch=main" >> "$GITHUB_OUTPUT"
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "branch=develop" >> "$GITHUB_OUTPUT"
else
echo "branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT"
fi
- name: Check Cloudflare credentials
id: cf
env:
HAS_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN != '' }}
HAS_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID != '' }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$HAS_TOKEN" = "true" ] && [ "$HAS_ACCOUNT" = "true" ]; then
echo "configured=true" >> "$GITHUB_OUTPUT"
else
echo "configured=false" >> "$GITHUB_OUTPUT"
MSG="CLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_ID not configured. Add them at https://github.com/elizaOS/eliza/settings/secrets/actions."
if [ "$EVENT_NAME" = "pull_request" ]; then
# PRs from forks legitimately lack access to repo secrets.
echo "::warning::$MSG Skipping Pages deploy (PR event)." >> "$GITHUB_STEP_SUMMARY"
else
echo "::error::$MSG Refusing to silently skip Pages deploy on $EVENT_NAME event." >> "$GITHUB_STEP_SUMMARY"
echo "::error::$MSG Refusing to silently skip Pages deploy on $EVENT_NAME event."
exit 1
fi
fi
- name: Deploy to Cloudflare Pages
if: steps.cf.outputs.configured == 'true'
working-directory: packages/cloud-frontend
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
# Do NOT pass a positional `dist` argument: wrangler.toml already sets
# `pages_build_output_dir = "./dist"`, and passing both makes wrangler
# ignore wrangler.toml — which silently drops the `functions/` upload
# (so `_middleware.ts` never runs and `/api/*` falls through to the
# SPA's index.html instead of being proxied to the API Worker).
run: bunx wrangler pages deploy --project-name=eliza-cloud --branch=${{ steps.pages.outputs.branch }}