Files

98 lines
4.5 KiB
YAML

name: deploy
# store.hanzo.ai is a SITE, not an App: a static export with no compute. It ships on the
# Sites plane. Not GitHub Pages, not Cloudflare Pages, and not an image — baking
# files that never execute into a container so a Go binary can serve them means an
# image, a push, a tag pinned in a CR and running pods, all to hand back bytes.
#
# build -> out
# -> site
#
# The publish mechanics are NOT here. They are bin/site in hanzoai/ci,
# reached through its action, and every static surface in the fleet calls that one
# script — ensure-project, enqueue, presigned per-file upload, complete. A
# contract transcribed per repo is a contract that drifts, and this file used to
# carry its own copy of every call. It also SELF-PROVISIONS: the project is
# created idempotently before the enqueue, so committing this file is enough to
# stand the site up.
#
# The only credentials are KMS_CLIENT_ID / KMS_CLIENT_SECRET, which identify us
# to kms.hanzo.ai; the action fetches the deploy grant there, so no storage key
# is held in a repo. They are set ON THE FORGE: this file lives under
# .hanzo/workflows/, which only git.hanzo.ai reads, so GitHub's secret store is
# not in this path at all.
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
site:
# NOT ubuntu-latest. This file lives in .hanzo/workflows, which GitHub never
# reads — git.hanzo.ai is the only thing that can run it, and its fleet
# advertises hanzo-build-linux-amd64. An unmatched label is not an error here,
# it is silence: the job queues until the 24h timeout and nothing says so.
runs-on: hanzo-build-linux-amd64
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: build the static export
env:
NEXT_TELEMETRY_DISABLED: '1'
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ secrets.WALLETCONNECT_PROJECT_ID }}
NEXT_PUBLIC_PUBLISHABLE_KEY: ${{ secrets.PUBLISHABLE_KEY }}
run: |
set -euo pipefail
# package-lock.json is lockfileVersion 3 and the tree needs the peer-dep
# escape; without --legacy-peer-deps the install fails outright.
npm ci --legacy-peer-deps
# The catalog pages are GENERATED from data/agents/*.json and
# data/tools/*.json before the export; skip this and next build exports
# an empty store.
npm run generate-store
npm run build
test -s out/index.html || { echo "::error::next build produced no out/index.html"; exit 1; }
# Clean URLs. next.config.js does not set trailingSlash, so the export is
# FLAT (out/agents.html, …) while the edge serves a DIRECTORY's
# index.html — write every top-level page as <name>/index.html too.
for f in out/*.html; do
case "$f" in out/index.html|out/404.html) continue ;; esac
d="${f%.html}"; mkdir -p "$d"; cp "$f" "$d/index.html"
done
echo "exported $(find out -type f | wc -l) files"
# ONE credential. The 202 carries a prefix-scoped, 30-minute presigned POST
# grant, so nothing here holds a bucket key. Do NOT add SITES_S3_* — that is
# the standing shared-bucket credential the grant replaced, and it could
# overwrite every other org's site.
#
# THE SLUG IS `hanzo-store`, NOT `store`, and it cannot be `store`. That label
# is in cloud's baked-in reserved set (apps/sites/reserved.go — brand terms an
# impersonator would want, beside `hanzo`, `docs` and `blog`), which create
# time rejects outright: POST /v1/projects answers 400 "slug is a reserved
# subdomain and cannot be used". The set is additive-only by design, so no
# operator config can free it. That 400 is why every run of this workflow
# since the Sites plane landed here has failed — four in a row, on four
# different commits, before the enqueue ever ran.
#
# The HOSTNAME is unaffected: store.hanzo.ai is a domain binding, a separate
# record from the slug, and a build never decides which site owns a host.
- name: Publish to the Sites plane
uses: https://git.hanzo.ai/hanzoai/ci/.github/actions/site@v1
with:
slug: hanzo-store
dir: out
env:
KMS_CLIENT_ID: ${{ secrets.KMS_CLIENT_ID }}
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}