Builds crates/remote/Dockerfile from our mirror at git.lair.cafe rather than from GitHub, and resolves the version from the mirror's tags rather than GitHub's releases API. BloopAI has announced a sunset; the mirror exists so this build outlives them, which is pointless if the build still asks github.com what to build. Nothing in this image's path touches GitHub. Gitea mirrors carry tags but not releases, so latest is resolved by filtering to the strict release pattern v<semver>-<14-digit datestamp> and sorting on the datestamp -- which also skips the malformed historical tags in the upstream repo (vv.20250708094151, vv0.0.40-nbump.2....). FEATURES is deliberately left unset: the Dockerfile strips the private billing crate only when it is empty, which is the documented self-host path. Setting it would send the build looking for BloopAI/vibe-kanban-private over SSH, which we cannot reach. Consumed by the vibe-kanban quadlets on bob (kanban.internal). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsmUEtbyTkgQ18tCFYXo1h
142 lines
6.2 KiB
YAML
142 lines
6.2 KiB
YAML
name: images
|
|
# Build container images required by lair infra and publish them to the Gitea
|
|
# registry at git.lair.cafe. Convention mirrors gongfoo/.gitea/workflows/images.yml.
|
|
#
|
|
# Hermes is the first image: built directly from NousResearch/hermes-agent's own
|
|
# Dockerfile at the latest upstream release tag, and published as
|
|
# git.lair.cafe/lair/hermes:{<version>,latest}. bob then pulls it via a normal
|
|
# AutoUpdate=registry quadlet.
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "images/**"
|
|
- ".gitea/workflows/images.yml"
|
|
schedule:
|
|
# Daily poll for new upstream releases (Gitea can't subscribe to GitHub
|
|
# release webhooks, so we poll). Release-triggered in effect: a build only
|
|
# runs when the resolved upstream version isn't already in our registry.
|
|
- cron: "0 7 * * *"
|
|
workflow_dispatch:
|
|
inputs:
|
|
force:
|
|
description: "Rebuild even if the version is already published"
|
|
type: boolean
|
|
default: false
|
|
|
|
jobs:
|
|
hermes:
|
|
runs-on:
|
|
- metal
|
|
- podman
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: resolve latest upstream release
|
|
id: rel
|
|
run: |
|
|
# Prefer a published release; fall back to the newest tag.
|
|
tag=$(curl -fsS 'https://api.github.com/repos/NousResearch/hermes-agent/releases/latest' | jq -r '.tag_name // empty')
|
|
if [ -z "$tag" ]; then
|
|
tag=$(curl -fsS 'https://api.github.com/repos/NousResearch/hermes-agent/tags' | jq -r '.[0].name // empty')
|
|
fi
|
|
if [ -z "$tag" ] || [ "$tag" = "null" ]; then
|
|
echo "ERROR: could not resolve an upstream hermes release/tag"; exit 1
|
|
fi
|
|
echo "upstream latest: $tag"
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: login to registry
|
|
run: podman login -u ${{ gitea.actor }} -p ${{ secrets.REGISTRY_TOKEN }} git.lair.cafe
|
|
|
|
- name: build & push (release-triggered, self-healing)
|
|
env:
|
|
TAG: ${{ steps.rel.outputs.tag }}
|
|
VERSION: ${{ steps.rel.outputs.version }}
|
|
FORCE: ${{ github.event.inputs.force }}
|
|
run: |
|
|
IMAGE=git.lair.cafe/lair/hermes
|
|
# Self-healing: the source of truth is "is this version in the registry?"
|
|
# — not a committed pin that can desync if a prior build failed.
|
|
# NB: when the *build definition* changes (e.g. the writable-tree
|
|
# layer), republish the same version with the `force` dispatch input.
|
|
if [ "$FORCE" != "true" ] && skopeo inspect "docker://${IMAGE}:${VERSION}" >/dev/null 2>&1; then
|
|
echo "${IMAGE}:${VERSION} already published — nothing to build"
|
|
exit 0
|
|
fi
|
|
# Two-stage: (1) build upstream from the git context into a local tag,
|
|
# (2) derive our published image from it via images/hermes/Containerfile
|
|
# (makes /opt/hermes writable by uid 10000 — see that file).
|
|
BASE="localhost/hermes-upstream:${VERSION}"
|
|
echo "[1/2] building upstream ${BASE} from NousResearch/hermes-agent#${TAG}"
|
|
podman build --pull=newer -t "${BASE}" \
|
|
"https://github.com/NousResearch/hermes-agent.git#${TAG}"
|
|
echo "[2/2] building derived (writable /opt/hermes) -> ${IMAGE}:${VERSION}"
|
|
podman build --build-arg BASE="${BASE}" \
|
|
-t "${IMAGE}:${VERSION}" \
|
|
-t "${IMAGE}:latest" \
|
|
images/hermes
|
|
podman push "${IMAGE}:${VERSION}"
|
|
podman push "${IMAGE}:latest"
|
|
echo "published ${IMAGE}:${VERSION} (and :latest)"
|
|
|
|
vibe-kanban-remote:
|
|
runs-on:
|
|
- metal
|
|
- podman
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# Resolved from OUR MIRROR, not GitHub: BloopAI has announced a sunset and
|
|
# the mirror exists so this build outlives them — which is pointless if the
|
|
# build asks github.com what to build. Gitea mirrors carry tags but not
|
|
# releases, so filter to the strict release pattern and sort on the trailing
|
|
# datestamp (also skips malformed historical tags like `vv.20250708094151`).
|
|
- name: resolve latest upstream release (from the mirror)
|
|
id: rel
|
|
run: |
|
|
tag=$(for p in 1 2 3 4 5; do
|
|
curl -fsS "https://git.lair.cafe/api/v1/repos/BloopAI/vibe-kanban/tags?limit=100&page=${p}" \
|
|
| jq -r '.[].name'
|
|
done \
|
|
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-[0-9]{14}$' \
|
|
| sort -t- -k2 -n \
|
|
| tail -1)
|
|
if [ -z "$tag" ]; then
|
|
echo "ERROR: could not resolve a vibe-kanban tag from the mirror"; exit 1
|
|
fi
|
|
echo "mirror latest: $tag"
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: login to registry
|
|
run: podman login -u ${{ gitea.actor }} -p ${{ secrets.REGISTRY_TOKEN }} git.lair.cafe
|
|
|
|
- name: build & push (release-triggered, self-healing)
|
|
env:
|
|
TAG: ${{ steps.rel.outputs.tag }}
|
|
VERSION: ${{ steps.rel.outputs.version }}
|
|
FORCE: ${{ github.event.inputs.force }}
|
|
run: |
|
|
IMAGE=git.lair.cafe/lair/vibe-kanban-remote
|
|
if [ "$FORCE" != "true" ] && skopeo inspect "docker://${IMAGE}:${VERSION}" >/dev/null 2>&1; then
|
|
echo "${IMAGE}:${VERSION} already published — nothing to build"
|
|
exit 0
|
|
fi
|
|
# Upstream ships the Dockerfile; context is the repo root, so -f points
|
|
# into it. FEATURES is deliberately unset — the Dockerfile strips the
|
|
# private billing crate only when it is empty (the self-host path), and
|
|
# we have no access to BloopAI/vibe-kanban-private.
|
|
# VITE_RELAY_API_BASE_URL is baked into the SPA; empty = relay disabled.
|
|
echo "building ${IMAGE}:${VERSION} from the mirror at ${TAG}"
|
|
podman build --pull=newer \
|
|
-f crates/remote/Dockerfile \
|
|
--build-arg VITE_RELAY_API_BASE_URL= \
|
|
-t "${IMAGE}:${VERSION}" \
|
|
-t "${IMAGE}:latest" \
|
|
"https://git.lair.cafe/BloopAI/vibe-kanban.git#${TAG}"
|
|
podman push "${IMAGE}:${VERSION}"
|
|
podman push "${IMAGE}:latest"
|
|
echo "published ${IMAGE}:${VERSION} (and :latest)"
|