CI: release-on-tag workflow — produce binary artifacts + publish to Forgejo package registry #6

Closed
opened 2026-05-02 05:05:51 +00:00 by mik-tf · 1 comment
Owner

Context

hero_assistance is the only lhumina_code repo without a CI workflow. We have three tagged releases (v0.1.0, v0.2.0, v0.3.0) but they're bare git tags with no attached binaries and no entry in the package registry. Every other hero crate (hero_books, hero_proc, hero_embedder, hero_osis, hero_os, hero_slides, …) ships a .forgejo/workflows/build-linux.yaml that builds + publishes on tag push.

Assessed in session 32 against:

  • znzfreezone_code/znzfreezone_deploy (different model — OCI containers, not binaries; not applicable here)
  • lhumina_code/hero_books (canonical short pattern; closest analog to our setup)
  • lhumina_code/hero_proc (self-contained variant; ~110 lines)
  • lhumina_code/hero_embedder (cross-arch matrix variant)
  • lhumina_code/hero_osis / hero_os / hero_slides (host-runner variant; package-registry-only)

Recommendation — pattern (A): build_lib.sh-based workflow modelled on hero_books

We already have scripts/build_lib.sh (78 KB — the standard hero helper substrate) and buildenv.sh (declares PROJECT_NAME=hero_assistance, BINARIES="hero_assistance_server hero_assistance_ui hero_assistance", VERSION=0.3.0). The canonical pattern just adds one workflow YAML on top.

What lands

.forgejo/workflows/build-linux.yaml (~50 lines, modelled on hero_books):

on:
  push:
    tags: ["v*"]
  workflow_dispatch:
permissions:
  contents: write
  packages: write
jobs:
  build-linux:
    runs-on: docker
    container: { image: ghcr.io/despiegk/builder:latest }
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            artifact: linux-amd64
    defaults: { run: { shell: bash } }
    steps:
      - name: Checkout
        run: |
          set -e
          git config --global --add safe.directory "$PWD"
          BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-main}}"
          git clone --depth 1 --branch "$BRANCH" https://forge.ourworld.tf/${GITHUB_REPOSITORY}.git .
      - name: Toolchain
        run: source scripts/build_lib.sh && ci_setup_toolchain "${{ matrix.target }}"
      - name: Build
        run: source scripts/build_lib.sh && build_binaries "${{ matrix.target }}"
      - name: Publish
        env: { FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} }
        run: |
          source scripts/build_lib.sh
          forge_config
          export BIN_DIR="$(bin_dir "${{ matrix.target }}")"
          publish_binaries "${{ matrix.artifact }}"

What it produces on the next tag push

  1. Forgejo Release page at https://forge.ourworld.tf/lhumina_code/hero_assistance/releases/tag/<tag> with the 3 binaries packaged as downloadable assets.
  2. Generic package at https://forge.ourworld.tf/lhumina_code/-/packages/generic/hero_assistance/<version> (consumable via curl-by-version for downstream automation).

Prep / known gotchas

  1. scripts/build_lib.sh helper functions need verification — confirm ci_setup_toolchain, build_binaries, forge_config, publish_binaries, bin_dir all exist and handle our 3-binary workspace cleanly. The 78 KB size suggests standard hero helpers, but quick grep first. Add stubs if any are missing.
  2. buildenv.sh VERSION=0.3.0 is stale post-s31 git-filter-repo work — the workflow extracts the version from the tag name, not from buildenv.sh, so non-blocking. But buildenv.sh should track current tag for tooling consistency. Trivial bump in the same PR.
  3. hero_assistance_app (desktop binary) is NOT in BINARIES — intentional (GTK/webkit2gtk deps don't belong in server-deploy artifacts). Workflow won't build it. Document why somewhere visible if anyone wonders.
  4. v0.2.0 won't get a workflow run — the tag was pushed before the workflow exists. To get artifacts on v0.2.0 specifically: re-tag (risky for any downstream consumers, none currently exist) or workflow_dispatch manually after the workflow lands. Otherwise: v0.2.0 stays bare; v0.2.1 (the next L-04+L-03 patch tag) becomes the first artifact-carrying tag. This is the suggested path.

Why not the other patterns

  • Pattern (B) — self-contained docker+curl à la hero_proc (~110 lines). Would only be worth it if we wanted to drop the build_lib.sh dependency, which we don't.
  • Pattern (C) — freezone OCI-only model. Not applicable; we ship CLI/server binaries, not containers.
  • Cross-arch matrix (x86_64-musl + aarch64-gnu legs à la hero_embedder) — defer until we have an arm64 customer. Single x86_64-unknown-linux-gnu covers current deploy targets.

Acceptance criteria

  • .forgejo/workflows/build-linux.yaml lands on development.
  • scripts/build_lib.sh exposes ci_setup_toolchain, build_binaries, forge_config, publish_binaries, bin_dir (verify or add stubs).
  • buildenv.sh VERSION matches the latest tag (cosmetic but expected).
  • Tag v0.2.1 (or v0.2.0-test first to validate the workflow without burning a real version) → workflow runs green → https://forge.ourworld.tf/lhumina_code/hero_assistance/releases/tag/v0.2.1 shows binary assets → https://forge.ourworld.tf/lhumina_code/-/packages/generic/hero_assistance/0.2.1 exists.
  • Optional: add a sibling test.yaml workflow that runs cargo test --no-fail-fast on every push to development (matches hero_books/hero_proc/hero_embedder convention). Out of scope for this issue if we want to keep it tight; can be a follow-up.

Effort estimate

~30 min in a single session. No source-code changes; pure CI/scaffolding.

Notes

  • Use ${{ secrets.FORGEJO_TOKEN }} (matches all hero crates' convention; hero_proc aliases it as env TOKEN but that's the only outlier).
  • Runner label: docker with ghcr.io/despiegk/builder:latest (provides Rust toolchain + musl-tools + buildah). Bare-metal runs-on: host is the alternative used by hero_osis/hero_os/hero_slides; not preferred here since we already use containers everywhere else.
  • The forge-release-workflow Claude skill auto-detects toolchain + scaffolds this YAML in one shot — likely fastest path to the first PR.

References

  • lhumina_code/hero_books/.forgejo/workflows/build-linux.yaml (canonical short pattern)
  • lhumina_code/hero_proc/.forgejo/workflows/build-linux.yaml (self-contained variant)
  • lhumina_code/hero_embedder/.forgejo/workflows/build-linux.yaml (cross-arch matrix)
  • Session 32 assessment in runs/phase16b_findings.md (informally; full reconnaissance in the s32 chat transcript not committed to this repo)
## Context `hero_assistance` is the only `lhumina_code` repo without a CI workflow. We have three tagged releases (`v0.1.0`, `v0.2.0`, `v0.3.0`) but they're bare git tags with no attached binaries and no entry in [the package registry](https://forge.ourworld.tf/lhumina_code/-/packages). Every other hero crate (hero_books, hero_proc, hero_embedder, hero_osis, hero_os, hero_slides, …) ships a `.forgejo/workflows/build-linux.yaml` that builds + publishes on tag push. Assessed in session 32 against: - `znzfreezone_code/znzfreezone_deploy` (different model — OCI containers, not binaries; not applicable here) - `lhumina_code/hero_books` (canonical short pattern; closest analog to our setup) - `lhumina_code/hero_proc` (self-contained variant; ~110 lines) - `lhumina_code/hero_embedder` (cross-arch matrix variant) - `lhumina_code/hero_osis` / `hero_os` / `hero_slides` (host-runner variant; package-registry-only) ## Recommendation — pattern (A): `build_lib.sh`-based workflow modelled on hero_books We already have `scripts/build_lib.sh` (78 KB — the standard hero helper substrate) and `buildenv.sh` (declares `PROJECT_NAME=hero_assistance`, `BINARIES="hero_assistance_server hero_assistance_ui hero_assistance"`, `VERSION=0.3.0`). The canonical pattern just adds one workflow YAML on top. ### What lands **`.forgejo/workflows/build-linux.yaml`** (~50 lines, modelled on hero_books): ```yaml on: push: tags: ["v*"] workflow_dispatch: permissions: contents: write packages: write jobs: build-linux: runs-on: docker container: { image: ghcr.io/despiegk/builder:latest } strategy: matrix: include: - target: x86_64-unknown-linux-gnu artifact: linux-amd64 defaults: { run: { shell: bash } } steps: - name: Checkout run: | set -e git config --global --add safe.directory "$PWD" BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-main}}" git clone --depth 1 --branch "$BRANCH" https://forge.ourworld.tf/${GITHUB_REPOSITORY}.git . - name: Toolchain run: source scripts/build_lib.sh && ci_setup_toolchain "${{ matrix.target }}" - name: Build run: source scripts/build_lib.sh && build_binaries "${{ matrix.target }}" - name: Publish env: { FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} } run: | source scripts/build_lib.sh forge_config export BIN_DIR="$(bin_dir "${{ matrix.target }}")" publish_binaries "${{ matrix.artifact }}" ``` ### What it produces on the next tag push 1. **Forgejo Release page** at `https://forge.ourworld.tf/lhumina_code/hero_assistance/releases/tag/<tag>` with the 3 binaries packaged as downloadable assets. 2. **Generic package** at `https://forge.ourworld.tf/lhumina_code/-/packages/generic/hero_assistance/<version>` (consumable via `curl`-by-version for downstream automation). ### Prep / known gotchas 1. **`scripts/build_lib.sh` helper functions need verification** — confirm `ci_setup_toolchain`, `build_binaries`, `forge_config`, `publish_binaries`, `bin_dir` all exist and handle our 3-binary workspace cleanly. The 78 KB size suggests standard hero helpers, but quick `grep` first. Add stubs if any are missing. 2. **`buildenv.sh` VERSION=0.3.0 is stale post-s31 git-filter-repo work** — the workflow extracts the version from the tag name, not from buildenv.sh, so non-blocking. But buildenv.sh should track current tag for tooling consistency. Trivial bump in the same PR. 3. **`hero_assistance_app` (desktop binary) is NOT in `BINARIES`** — intentional (GTK/webkit2gtk deps don't belong in server-deploy artifacts). Workflow won't build it. Document why somewhere visible if anyone wonders. 4. **v0.2.0 won't get a workflow run** — the tag was pushed before the workflow exists. To get artifacts on v0.2.0 specifically: re-tag (risky for any downstream consumers, none currently exist) or `workflow_dispatch` manually after the workflow lands. Otherwise: **v0.2.0 stays bare; v0.2.1 (the next L-04+L-03 patch tag) becomes the first artifact-carrying tag.** This is the suggested path. ### Why not the other patterns - **Pattern (B)** — self-contained docker+curl à la `hero_proc` (~110 lines). Would only be worth it if we wanted to drop the `build_lib.sh` dependency, which we don't. - **Pattern (C)** — freezone OCI-only model. Not applicable; we ship CLI/server binaries, not containers. - **Cross-arch matrix** (`x86_64-musl` + `aarch64-gnu` legs à la `hero_embedder`) — defer until we have an arm64 customer. Single `x86_64-unknown-linux-gnu` covers current deploy targets. ## Acceptance criteria - [ ] `.forgejo/workflows/build-linux.yaml` lands on `development`. - [ ] `scripts/build_lib.sh` exposes `ci_setup_toolchain`, `build_binaries`, `forge_config`, `publish_binaries`, `bin_dir` (verify or add stubs). - [ ] `buildenv.sh` VERSION matches the latest tag (cosmetic but expected). - [ ] Tag `v0.2.1` (or `v0.2.0-test` first to validate the workflow without burning a real version) → workflow runs green → `https://forge.ourworld.tf/lhumina_code/hero_assistance/releases/tag/v0.2.1` shows binary assets → `https://forge.ourworld.tf/lhumina_code/-/packages/generic/hero_assistance/0.2.1` exists. - [ ] Optional: add a sibling `test.yaml` workflow that runs `cargo test --no-fail-fast` on every push to `development` (matches `hero_books`/`hero_proc`/`hero_embedder` convention). Out of scope for this issue if we want to keep it tight; can be a follow-up. ## Effort estimate ~30 min in a single session. No source-code changes; pure CI/scaffolding. ## Notes - Use `${{ secrets.FORGEJO_TOKEN }}` (matches all hero crates' convention; `hero_proc` aliases it as env `TOKEN` but that's the only outlier). - Runner label: `docker` with `ghcr.io/despiegk/builder:latest` (provides Rust toolchain + musl-tools + buildah). Bare-metal `runs-on: host` is the alternative used by hero_osis/hero_os/hero_slides; not preferred here since we already use containers everywhere else. - The `forge-release-workflow` Claude skill auto-detects toolchain + scaffolds this YAML in one shot — likely fastest path to the first PR. ## References - `lhumina_code/hero_books/.forgejo/workflows/build-linux.yaml` (canonical short pattern) - `lhumina_code/hero_proc/.forgejo/workflows/build-linux.yaml` (self-contained variant) - `lhumina_code/hero_embedder/.forgejo/workflows/build-linux.yaml` (cross-arch matrix) - Session 32 assessment in `runs/phase16b_findings.md` (informally; full reconnaissance in the s32 chat transcript not committed to this repo)
Author
Owner

Closed by v0.2.1 (286ae22; session 32 bonus, Phase 21 prep).

What landed

  • .forgejo/workflows/build-linux.yaml — release-on-tag CI per the canonical lhumina_code pattern (modelled on hero_books). Triggers on push.tags: ['v*'] + workflow_dispatch. Runs in ghcr.io/despiegk/builder:latest container, x86_64-unknown-linux-gnu target.
  • buildenv.sh VERSION0.2.1 (was 0.3.0, aspirational from s31's filter-repo work).
  • scripts/build_lib.sh URL fix: /api/crates/.../api/packages/... (the upstream template carried Forgejo's Cargo Registry path; Generic uploads need /api/packages/). See debugging trail below.
  • Repo-level FORGEJO_TOKEN secret added to override the org-level secret which lacks write:package scope. See "Follow-up" below.

What it produces

Verified working — package landed at lhumina_code/-/packages/generic/hero_assistance/0.2.1:

Binary Size SHA-256 (first 16)
hero_assistance_server-linux-amd64 3.3 MB b2612ca5135be34b
hero_assistance_ui-linux-amd64 3.1 MB 417326199190ba79
hero_assistance-linux-amd64 2.3 MB 926041412066b3a6

Downloadable via curl:

curl -O https://forge.ourworld.tf/api/packages/lhumina_code/generic/hero_assistance/0.2.1/hero_assistance_server-linux-amd64

hero_assistance_app (Dioxus desktop binary) is intentionally NOT in the deploy artifact set — it needs GTK/webkit2gtk system libs not present on server hosts.

Debugging trail (3 CI runs)

  1. Run #1 (task 23791, on 287a2ad): Build OK (1m27s), Publish failed at HTTP 404. Direct curl confirmed the bug:

    • PUT /api/crates/{owner}/generic/{pkg}/{version}/{file} → 404 (Cargo Registry path)
    • PUT /api/packages/{owner}/generic/{pkg}/{version}/{file} → 201 (correct Generic path)
      Patched scripts/build_lib.sh lines 1116 + 1584 (commit 286ae22).
  2. Run #2 (task 23792, on 286ae22, force-re-tagged v0.2.1): Build OK (1m30s), Publish failed at HTTP 401 with reqPackageAccess. Token lacked write:package scope. Investigation showed:

    • Org-level FORGEJO_TOKEN secret was created 2026-04-28, never tested for package writes.
    • hero_books v0.1.5 was uploaded 2026-02-10 (predates org secret); creator mik-tf — likely manual curl not CI.
    • My PAT (env.sh) DOES have write:package (verified via PUT probe to a throw-away version → 201, then deleted).
      Set repo-level FORGEJO_TOKEN secret = working PAT (overrides org-level).
  3. Run #3 (task 23795, workflow_dispatch-triggered): Full success in 2m07s. Package landed; verified via download.

Lessons recorded

The forge_ci Claude skill was invaluable for the log-fetch dance:

  • Forgejo REST API has no log endpoint; the web UI uses an undocumented internal POST at /{owner}/{repo}/actions/runs/{run_index}/jobs/{job_index}/attempt/1 with {"logCursors":[{"step":N,"cursor":0,"expanded":true}]} body. Public repos only.
  • Use index_in_repo (the human-readable run number, e.g., 1/2/3) in the log endpoint URL, NOT the internal task ID (23791/...).

The /proc/<pid>/status diagnostic from B2 (s32 main work) generalises: always check the actual API response code before assuming a workflow's intent matches its config. The 404→401 progression here was diagnostic gold — each error narrowed the search by an order of magnitude.

Follow-up (out of scope for this issue)

  • Org-level FORGEJO_TOKEN should be re-issued with write:package + write:repository scopes, then the repo-level override can be deleted. This is an ops-level concern across all lhumina_code repos that adopt the build-linux pattern. Current status: hero_assistance has its own working repo-level token; other repos may have the same issue silently (their build-linux workflows haven't been triggered recently).
  • scripts/build_lib.sh URL fix should be back-ported to the upstream template (likely lives in another hero crate's scripts/ or a shared "hero build helpers" repo). Other lhumina_code repos using this template have the same latent bug.
  • Optional sibling test.yaml workflow for branch-push CI (cargo test --no-fail-fast on development pushes + PRs) per the canonical pattern. Out of scope for this issue; can be a follow-up.

Acceptance criteria

  • .forgejo/workflows/build-linux.yaml lands on development.
  • scripts/build_lib.sh exposes the 5 helpers (verified: ci_setup_toolchain, build_binaries, forge_config, bin_dir, publish_binaries — all present).
  • buildenv.sh VERSION matches the latest tag.
  • Tag v0.2.1 → workflow runs green → Generic package exists with all 3 binaries downloadable.
  • Optional: sibling test.yaml workflow — deferred, follow-up.

Closes this issue.

**Closed by v0.2.1** ([`286ae22`](https://forge.ourworld.tf/lhumina_code/hero_assistance/commit/286ae2201927db0afcf55823fe51926951fdbffe); session 32 bonus, Phase 21 prep). ## What landed - [`.forgejo/workflows/build-linux.yaml`](https://forge.ourworld.tf/lhumina_code/hero_assistance/src/branch/development/.forgejo/workflows/build-linux.yaml) — release-on-tag CI per the canonical `lhumina_code` pattern (modelled on `hero_books`). Triggers on `push.tags: ['v*']` + `workflow_dispatch`. Runs in `ghcr.io/despiegk/builder:latest` container, `x86_64-unknown-linux-gnu` target. - `buildenv.sh` `VERSION` → `0.2.1` (was `0.3.0`, aspirational from s31's filter-repo work). - `scripts/build_lib.sh` URL fix: `/api/crates/...` → `/api/packages/...` (the upstream template carried Forgejo's Cargo Registry path; Generic uploads need `/api/packages/`). See debugging trail below. - Repo-level `FORGEJO_TOKEN` secret added to override the org-level secret which lacks `write:package` scope. **See "Follow-up" below.** ## What it produces Verified working — package landed at [`lhumina_code/-/packages/generic/hero_assistance/0.2.1`](https://forge.ourworld.tf/lhumina_code/-/packages/generic/hero_assistance/0.2.1): | Binary | Size | SHA-256 (first 16) | |---|---:|---| | `hero_assistance_server-linux-amd64` | 3.3 MB | `b2612ca5135be34b` | | `hero_assistance_ui-linux-amd64` | 3.1 MB | `417326199190ba79` | | `hero_assistance-linux-amd64` | 2.3 MB | `926041412066b3a6` | Downloadable via `curl`: ``` curl -O https://forge.ourworld.tf/api/packages/lhumina_code/generic/hero_assistance/0.2.1/hero_assistance_server-linux-amd64 ``` `hero_assistance_app` (Dioxus desktop binary) is intentionally NOT in the deploy artifact set — it needs GTK/webkit2gtk system libs not present on server hosts. ## Debugging trail (3 CI runs) 1. **Run #1** (`task 23791`, on `287a2ad`): Build OK (1m27s), Publish failed at `HTTP 404`. Direct curl confirmed the bug: - `PUT /api/crates/{owner}/generic/{pkg}/{version}/{file}` → 404 (Cargo Registry path) - `PUT /api/packages/{owner}/generic/{pkg}/{version}/{file}` → 201 (correct Generic path) Patched `scripts/build_lib.sh` lines 1116 + 1584 (commit `286ae22`). 2. **Run #2** (`task 23792`, on `286ae22`, force-re-tagged `v0.2.1`): Build OK (1m30s), Publish failed at `HTTP 401` with `reqPackageAccess`. Token lacked `write:package` scope. Investigation showed: - Org-level `FORGEJO_TOKEN` secret was created `2026-04-28`, never tested for package writes. - `hero_books` v0.1.5 was uploaded `2026-02-10` (predates org secret); creator `mik-tf` — likely manual `curl` not CI. - My PAT (env.sh) DOES have `write:package` (verified via PUT probe to a throw-away version → 201, then deleted). Set repo-level `FORGEJO_TOKEN` secret = working PAT (overrides org-level). 3. **Run #3** (`task 23795`, `workflow_dispatch`-triggered): Full success in 2m07s. Package landed; verified via download. ## Lessons recorded The `forge_ci` Claude skill was invaluable for the log-fetch dance: - Forgejo REST API has **no log endpoint**; the web UI uses an undocumented internal POST at `/{owner}/{repo}/actions/runs/{run_index}/jobs/{job_index}/attempt/1` with `{"logCursors":[{"step":N,"cursor":0,"expanded":true}]}` body. Public repos only. - Use `index_in_repo` (the human-readable run number, e.g., `1`/`2`/`3`) in the log endpoint URL, NOT the internal task ID (`23791`/...). The `/proc/<pid>/status` diagnostic from B2 (s32 main work) generalises: **always check the actual API response code** before assuming a workflow's intent matches its config. The 404→401 progression here was diagnostic gold — each error narrowed the search by an order of magnitude. ## Follow-up (out of scope for this issue) - **Org-level `FORGEJO_TOKEN` should be re-issued** with `write:package` + `write:repository` scopes, then the repo-level override can be deleted. This is an ops-level concern across all `lhumina_code` repos that adopt the build-linux pattern. Current status: hero_assistance has its own working repo-level token; other repos may have the same issue silently (their build-linux workflows haven't been triggered recently). - **`scripts/build_lib.sh` URL fix should be back-ported** to the upstream template (likely lives in another hero crate's `scripts/` or a shared "hero build helpers" repo). Other lhumina_code repos using this template have the same latent bug. - **Optional sibling `test.yaml` workflow** for branch-push CI (`cargo test --no-fail-fast` on `development` pushes + PRs) per the canonical pattern. Out of scope for this issue; can be a follow-up. ## Acceptance criteria - [x] `.forgejo/workflows/build-linux.yaml` lands on `development`. - [x] `scripts/build_lib.sh` exposes the 5 helpers (verified: `ci_setup_toolchain`, `build_binaries`, `forge_config`, `bin_dir`, `publish_binaries` — all present). - [x] `buildenv.sh` VERSION matches the latest tag. - [x] Tag `v0.2.1` → workflow runs green → Generic package exists with all 3 binaries downloadable. - [ ] ~~Optional: sibling `test.yaml` workflow~~ — deferred, follow-up. Closes this issue.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_assistance#6
No description provided.