hanzo.chat ships as a site, not an image

hanzo.chat is the chat2 project on the Sites plane, served by cloud by Host,
and nothing runs ghcr.io/hanzoai/chat2. So the lane that matters is the site:
build the export, publish it to s3://hanzo-sites/hanzo/chat2. The image and its
Dockerfile go.

The trigger named master and this repository has only main, so no push has run
this lane since the rename.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-19 09:58:18 -07:00
co-authored by Hanzo Dev
parent 15942dd366
commit 2f1e47a75f
3 changed files with 15 additions and 92 deletions
+3 -18
View File
@@ -3,27 +3,12 @@ name: CI/CD
# The caller, and deliberately nothing else: triggers plus the import. All real
# config is the repo-root hanzo.yml, which platform.hanzo.ai reads too.
#
# `.hanzo/workflows`, NOT `.github/workflows`, on BOTH sides of the `uses:`.
# Gitea collects workflows from the first of WORKFLOW_DIRS present in the commit
# and resolves a reusable through the same allowlist, and github.com has zero
# self-hosted runners for this org — so a job asking for linux-amd64
# there is never claimed, and an unclaimable job does not fail, it waits out the
# 24h timeout while the next push queues behind it. Naming the .github path is
# what took hanzoai/console's CI down.
#
# `master`, because that is this repository's default branch — measured, not
# assumed (`git remote show origin` → HEAD branch: master). `main` exists too
# and currently points at the same commit, which is a duplicate ref rather than
# a second lane: listing both would put two runs on one tag, and since the
# version comes from hanzoai/ci's bin/imgver deriving max(declared, published)+1
# patch, two concurrent runs race to mint the same one. One branch, one lane.
# Delete the duplicate and this list stays one entry.
# `main` is the only branch this repository has; a trigger naming another one
# never fires, which is how this lane sat idle.
on:
push:
branches: [master]
# A `v*` tag is a RELEASE and publishes an image named after it, verbatim.
tags: ['v*']
branches: [main]
pull_request:
workflow_dispatch:
-40
View File
@@ -1,40 +0,0 @@
# syntax=docker/dockerfile:1.7
# Hanzo Chat — the Vite SPA built once, served by hanzoai/spa.
FROM node:24-alpine AS build
WORKDIR /build
ENV CI=true PNPM_HOME=/pnpm PATH=$PNPM_HOME:$PATH
RUN corepack enable && corepack prepare [email protected] --activate
# Manifests first so a source edit re-runs the build and not the resolve.
#
# pnpm-workspace.yaml is REQUIRED even though this repo has no workspace
# members: every dependency is `catalog:`, and the catalog lives in that file.
# Without it the install resolves nothing and fails on the first `catalog:`.
#
# FROZEN, and the lockfile ships. A resolver left free to drift builds a tree
# nobody ran — hanzoai/id shipped exactly that, where a free resolve moved vite
# to a different version than the tested lockfile and the image died on a
# missing module while the same commit built cleanly from the lockfile. Frozen,
# the image gets the tree the gate passed against, and a stale lockfile fails
# here, loudly, instead of silently building something else.
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY tsconfig.json vite.config.ts index.html ./
COPY src src
# `pnpm build` is `tsc --noEmit && vite build`, so the typecheck is part of the
# image and dead code cannot ship (tsconfig sets noUnusedLocals).
#
# `&&`, not `;`: with `;` the RUN carries the status of the LAST command and a
# failed build would be masked. A vite build that emits nothing still exits 0,
# which is what the two file tests are for.
RUN pnpm build && [ -s dist/index.html ] && [ -d dist/assets ]
# hanzoai/spa, not hanzoai/static, and not a webserver of our own: this is a
# client-routed SPA, so it needs index.html served for every path. hanzoai/static
# defaults to `Content-Security-Policy: default-src 'none'`, which blocks the
# bundle the page loads and leaves a blank screen. Defaults: PORT=3000, ROOT=/public.
FROM ghcr.io/hanzoai/spa:1.4.13
COPY --from=build /build/dist /public
EXPOSE 3000
+12 -34
View File
@@ -1,21 +1,16 @@
# Hanzo CI/CD — chat. All real config is here; .hanzo/workflows/cicd.yml is
# Hanzo CI/CD — chat. All real config is here; .github/workflows/cicd.yml is
# triggers and an import of hanzoai/ci.
test:
# Frozen, so the gate resolves the same tree the Dockerfile does. The estate
# note that this repo needs --no-frozen-lockfile is stale: the lockfile has
# caught up with the `catalog:` conversion and a frozen install now resolves
# clean.
# Frozen, so the gate resolves the same tree the site is built from.
- name: install
run: |
set -e
corepack enable
pnpm install --frozen-lockfile
# Verbatim the build line the image runs, run before there is an image, so a
# break arrives in a minute instead of after a full layer stack. `pnpm build`
# is `tsc --noEmit && vite build`. A vite build that emits nothing still exits
# 0, hence the file test.
# `pnpm build` is `tsc --noEmit && vite build`. A vite build that emits
# nothing still exits 0, hence the file test.
- name: build
run: |
set -e
@@ -31,28 +26,11 @@ test:
set -e
pnpm test
images:
# NOT `ghcr.io/hanzoai/chat`, and the name is the whole reason.
#
# That name belongs to the LibreChat fork, which is built out of the SEPARATE
# repo hanzoai/chat-v1 by its own .hanzo/workflows/deploy.yml, and it is the
# image two live Deployments run: universe charts/app/values/hanzo/chat.yaml
# (hanzo.chat, 1.0.273) and lux-chat.yaml (lux.chat, 1.0.268). Publishing this
# application under that name would put two different programs behind one
# reference, and the pods are replicas:1 + strategy:Recreate over an RWO SQLite
# claim, so there is no half-rolled state to notice it in — the next pull is
# the whole service.
#
# This name is the package's own (@hanzo/chat2). It stops being needed when
# chat-v1 is retired and this repo inherits `chat`; until then two names is the
# honest count, because there are two applications.
- name: chat2
context: .
dockerfile: Dockerfile
repo: ghcr.io/hanzoai/chat2
platforms: [linux/amd64]
# No build_secrets, and none belong here. The publishable keys in
# src/brand.ts are pk- values, public by construction, and each is held in
# its OWN brand record — so the host that selects the brand selects the key
# with it. That is what keeps one bundle serving hanzo.chat, lux.chat and
# zoolabs.io without filing three brands' traffic under one tenant.
# hanzo.chat is this export: the `chat2` project on the Sites plane
# (s3://hanzo-sites/hanzo/chat2), served by cloud by Host and calling only
# api.hanzo.ai. No image, no pod.
site:
slug: chat2
dir: dist
build: corepack enable && pnpm install --frozen-lockfile && pnpm build
on: [main]