mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
ci: run cicd on the forge, leave sync.yml on the mirror
git.hanzo.ai is canonical and reads .hanzo/workflows, so cicd.yml moves there by changing directory and nothing else — same GitHub Actions syntax, same runs-on, byte-identical file. .github/workflows keeps exactly one workflow, sync.yml, which carries pushed refs to the forge. hanzo.yml still holds all the config, so the build and test lanes are unchanged; it just points at the new path. LLM.md documents the chain and drops a stale reference to build-and-publish.yml, a workflow this repo no longer has. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
name: sync
|
||||
|
||||
# Carries pushed refs to git.hanzo.ai, the canonical forge, and does nothing
|
||||
# else: build, test and release run natively there.
|
||||
#
|
||||
# Repo-agnostic. owner/name come from ${{ github.repository }}, so this file is
|
||||
# dropped into every repo unchanged as .github/workflows/sync.yml and is the
|
||||
# only workflow GitHub is permitted to run.
|
||||
|
||||
on:
|
||||
push:
|
||||
# '**' matches slashes; '*' does not, and would skip feature/foo.
|
||||
branches: ['**']
|
||||
tags: ['**']
|
||||
workflow_dispatch:
|
||||
|
||||
# Serialize per ref. Never cancel: a cancelled run drops that ref and nothing
|
||||
# reconciles it afterwards.
|
||||
concurrency:
|
||||
group: sync-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
# .github/workflows is also on the forge's own workflow search path, so this
|
||||
# file executes there too in any repo without .hanzo/workflows, where it
|
||||
# would push the forge to itself. server_url is the forge's AppURL, which
|
||||
# makes this the test for "running on GitHub".
|
||||
if: github.server_url == 'https://github.com'
|
||||
# GitHub-hosted on purpose. This is the one workflow that must run ON GitHub,
|
||||
# and the self-hosted pool that used to serve it is retired — a job asking for
|
||||
# it now queues forever against a label nothing answers. Carrying refs is a
|
||||
# push, not a build, so it does not belong on the build fabric anyway: builds
|
||||
# run natively on git.hanzo.ai, which is the whole point of this file.
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Push refs to git.hanzo.ai
|
||||
env:
|
||||
FORGE: https://git.hanzo.ai
|
||||
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${GIT_TOKEN}" ]; then
|
||||
echo "::error::GIT_TOKEN is unset, so this push never reached the canonical forge. Add the org secret."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REMOTE="${FORGE}/${GITHUB_REPOSITORY}.git"
|
||||
|
||||
# The forge reads the token from the basic-auth password and ignores
|
||||
# the username. base64 of a secret is not masked automatically.
|
||||
AUTH="$(printf 'x-access-token:%s' "${GIT_TOKEN}" | base64 -w0)"
|
||||
echo "::add-mask::${AUTH}"
|
||||
# Host-scoped, so the forge token is never sent to github.com.
|
||||
git config --local "http.${FORGE}/.extraheader" "Authorization: Basic ${AUTH}"
|
||||
|
||||
# Reachability is the only transient failure here, so it is the only
|
||||
# thing retried. ls-remote exits 0 on an existing empty repo and
|
||||
# non-zero when the forge is down or the repo is absent.
|
||||
ERR="$(mktemp)"
|
||||
REACHED=""
|
||||
for attempt in 1 2 3; do
|
||||
if git ls-remote "${REMOTE}" >/dev/null 2>"${ERR}"; then
|
||||
REACHED=1
|
||||
break
|
||||
fi
|
||||
echo "forge probe failed (attempt ${attempt}/3): $(cat "${ERR}")"
|
||||
if [ "${attempt}" -lt 3 ]; then
|
||||
sleep $((attempt * 5))
|
||||
fi
|
||||
done
|
||||
if [ -z "${REACHED}" ]; then
|
||||
echo "::error::git.hanzo.ai is unreachable or ${GITHUB_REPOSITORY} does not exist there. $(cat "${ERR}")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Additive and fast-forward-only: no leading '+', no --force, no
|
||||
# --prune, no --mirror. Tags go every run so a tag that missed its own
|
||||
# event still lands; git refuses to move one that already differs.
|
||||
REFS=('refs/tags/*:refs/tags/*')
|
||||
case "${GITHUB_REF}" in
|
||||
refs/heads/*) REFS+=("${GITHUB_REF}:${GITHUB_REF}") ;;
|
||||
esac
|
||||
|
||||
# Not --atomic: a diverged ref must not hold back the refs that are
|
||||
# clean. Rejections still fail the job.
|
||||
if ! git push "${REMOTE}" "${REFS[@]}"; then
|
||||
echo "::error::git.hanzo.ai rejected a ref for ${GITHUB_REPOSITORY}. The forge is canonical and is never force-updated from here; reconcile the diverged branch or tag by hand."
|
||||
exit 1
|
||||
fi
|
||||
@@ -108,9 +108,33 @@ permission model.
|
||||
- DB init (one-time): `kubectl apply -f k8s/init-db.yaml`
|
||||
|
||||
### CI/CD
|
||||
- GitHub Actions: `.github/workflows/build-and-publish.yml`
|
||||
- Pushes to `ghcr.io/hanzoai/tasks:{sha,branch,latest}` on main/release branches
|
||||
- Uses docker-bake.hcl for multi-arch builds (linux/amd64, linux/arm64)
|
||||
|
||||
One way, and it runs on our own stack:
|
||||
|
||||
push -> github.com/hanzoai/tasks (a mirror)
|
||||
.github/workflows/sync.yml carries refs onward
|
||||
-> git.hanzo.ai/hanzoai/tasks CANONICAL
|
||||
.hanzo/workflows/cicd.yml tests + builds ghcr.io/hanzoai/tasks
|
||||
-> hanzoai/cloud go.mod pins the tag this repo cuts
|
||||
|
||||
**git.hanzo.ai is canonical; GitHub is a mirror.** `.github/workflows/` holds
|
||||
exactly one file, `sync.yml`, and its only job is getting refs to the forge. Every
|
||||
build, check and deploy is a workflow under `.hanzo/workflows/`, which the forge
|
||||
reads. `.hanzo/workflows` uses GitHub Actions syntax, so a workflow moves between
|
||||
the two by changing directory and nothing else.
|
||||
|
||||
`.hanzo/workflows/cicd.yml` is a thin trigger; all config lives in the repo-root
|
||||
`hanzo.yml` — `go test -race ./pkg/... ./cmd/...` plus `go vet`, and the image
|
||||
`ghcr.io/hanzoai/tasks` from the root `Dockerfile`. `hanzo.yml` declares no
|
||||
`deploy:` key, so **cicd.yml never deploys**: it publishes an image and stops.
|
||||
|
||||
What this repo is actually load-bearing for is the **Go module tag**, which
|
||||
`hanzoai/cloud` `go.mod` pins. Tags are cut by hand; CI only stamps image tags off
|
||||
a tag that already exists.
|
||||
|
||||
The `tasks` image has no live consumer today. `tasks.hanzo.ai` is served by
|
||||
cloud's embedded `clients/tasks/ui`, not by this image, and there is no App CR for
|
||||
it in `hanzoai/universe`.
|
||||
|
||||
### Observability
|
||||
- OTEL traces: `otel-collector.hanzo.svc:4318`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Canonical CI/CD config for hanzoai/tasks — read by both the hanzoai/ci
|
||||
# reusable workflow (.github/workflows/cicd.yml) and platform.hanzo.ai
|
||||
# reusable workflow (.hanzo/workflows/cicd.yml) and platform.hanzo.ai
|
||||
# (webhook → BuildScheduler → arcd → operator rollout). One file, one way.
|
||||
#
|
||||
# The image is the root Dockerfile: native ZAP-only tasksd (no CGO, no gRPC,
|
||||
|
||||
Reference in New Issue
Block a user