Compare commits

...
Author SHA1 Message Date
UltradesuandClaude Opus 4.8 eac54f8c79 Revert the GitHub workflow guard to server_url
Release / tsunagi-linux-aarch64 (push) Canceled after 15s
Release / tsunagi-linux-x86_64 (push) Canceled after 0s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
Misdiagnosed the earlier report: Gitea was correctly running its own
.gitea/workflows copy, not this .github one, so the server_url guard was fine.
The switch to a GITHUB_RELEASE repo variable then made the workflow skip on
GitHub itself, since that variable is not set there.

Put both job guards back to `github.server_url == 'https://github.com'`, which
runs on GitHub with no manual setup and skips on Gitea (its .gitea copy is the
one that runs). No repository variable is needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 12:53:26 +01:00
UltradesuandClaude Opus 4.8 a8dfd7017b Gate the GitHub release workflow on a repo variable, not server_url
Release / tsunagi-linux-aarch64 (push) Canceled after 35s
Release / tsunagi-linux-x86_64 (push) Canceled after 0s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
The github.server_url guard did not keep this workflow from running on Gitea:
Gitea reports github.server_url as https://github.com, so the condition was
true there too and the jobs ran (and would fail on @v4/gh).

Switch both job guards to `vars.GITHUB_RELEASE == 'true'`, a repository variable
set only in the GitHub repo's settings. It is deterministic: absent on Gitea, so
the jobs skip there and the .gitea workflow is the one that runs; present on
GitHub once configured, so it runs there. The header documents how to set it
(Settings → Secrets and variables → Actions → Variables → GITHUB_RELEASE=true).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 12:50:04 +01:00
UltradesuandClaude Opus 4.8 99f4cf0220 Add a GitHub release workflow alongside the Gitea one
Release / tsunagi-linux-aarch64 (push) Canceled after 36s
Release / tsunagi-linux-x86_64 (push) Canceled after 0s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
Gitea and GitHub need different release plumbing, so each gets its own file: the
Gitea workflow stays in .gitea/workflows, and this GitHub one lives in
.github/workflows. The build half is identical (static musl Linux via
cargo-zigbuild, native macOS aarch64 and Windows x86_64); only publishing
differs — GitHub uses the v4 artifact backend and `gh release create`/`upload`,
which the Gitea file cannot use.

Because Gitea reads both .gitea/workflows and .github/workflows, every job here
is guarded with `if: github.server_url == 'https://github.com'`, so on Gitea
this file's jobs are skipped (no v4/gh failures) and the .gitea file is the one
that runs. GitHub never reads .gitea, so there it runs only this one.

Publish is re-run safe: it creates the release on the first run and uploads with
--clobber onto an existing release afterwards; a pre-release suffix on the tag
marks it prerelease.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 12:47:19 +01:00
UltradesuandClaude Opus 4.8 69263b2dd9 Move the release workflow to .gitea/workflows
Release / tsunagi-linux-aarch64 (push) Successful in 3m39s
Release / tsunagi-linux-x86_64 (push) Successful in 3m27s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
The release pipeline is Gitea-specific (v3 artifacts, Gitea release API), so it
must not run on github.com, where it would fail. GitHub only reads
.github/workflows, while Gitea reads both, so moving the file to
.gitea/workflows keeps it running on Gitea and invisible to GitHub. A separate
GitHub-flavoured release workflow can be added under .github later.

The portable .github/workflows/ci.yml (fmt/clippy/test) is left in place; it
works on both and is not what this change is about.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 12:13:45 +01:00
UltradesuandClaude Opus 4.8 20444013c0 Split release into build artifacts + a publish job (Gitea)
The direct-upload-from-each-build approach failed ("Upload the archive to the
release" exit 1) and left no artifacts to inspect. Restructured into the
familiar shape: build jobs now leave their archive as a run artifact, and a
single publish job — gated on all targets succeeding — collects them and fills
the release.

- Build jobs use actions/upload-artifact@v3 (Gitea has no v4 backend; v3 is
  what shows under the run), so the archive is visible and handed on.
- publish (needs: build) downloads everything with download-artifact@v3 and
  uploads each archive to the Gitea release with curl, once, with no matrix
  race. It prints the API base and the HTTP status/body of every call, so a
  failure (e.g. a read-only Actions token → 403, or a bad API URL) is now
  visible in the log instead of a bare exit 1.
- Release API base prefers GITHUB_API_URL (Gitea sets it to its /api/v1 root)
  and falls back to GITHUB_SERVER_URL/api/v1. Asset uploads run in the main
  shell via process substitution so a failed upload fails the job.

Build, package, musl/zig setup and the target matrix are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 12:12:53 +01:00
UltradesuandClaude Opus 4.8 2196f6635b Quiet the cosmetic zig linker_messages warning on the musl build
The musl release build now links cleanly through cargo-zigbuild, but rustc's
new linker_messages lint echoed zig's "ignoring deprecated linker optimization
setting '1'" note — zig's linker declining the -O1 GNU-ld flag Rust passes. It
is cosmetic and the binary is correct, so allow that one lint, scoped to the
zig build step only. macOS and Windows keep full linker diagnostics, and a real
link error still fails the build.

No behaviour change to the produced binaries; takes effect on the next tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 12:06:04 +01:00
UltradesuandClaude Opus 4.8 6438982d45 Upload release assets via the Gitea API instead of artifacts + gh
Release / tsunagi-linux-aarch64 (push) Failing after 2m57s
Release / tsunagi-linux-x86_64 (push) Failing after 2m53s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
The release ran on Gitea Actions, where it failed at actions/upload-artifact@v4:
the v4 artifact backend (and the GitHub REST API that `gh release create` uses)
are not available on Gitea/GHES, so both the artifact handoff and the publish
job were dead ends there.

Reworked so each build job uploads its archive straight to the release through
Gitea's native API (`/api/v1/repos/<repo>/releases`) with curl: create the
release for the tag if absent (races between matrix jobs are resolved by reading
the id back by tag), then POST the archive as an asset. The separate publish
job, download-artifact and `gh` are gone. The id is parsed with grep, so the
runner needs only curl and coreutils — no jq, no third-party release action.
Uses the Gitea-injected GITHUB_TOKEN, GITHUB_SERVER_URL and GITHUB_REPOSITORY;
a tag with a pre-release suffix (e.g. -rc.2) is marked prerelease.

This targets Gitea by design; it no longer runs as-is on github.com (different
API), which is fine — the GitHub build is being retired. Build and package
steps, the musl/zig setup and the target matrix are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 12:04:20 +01:00
UltradesuandClaude Opus 4.8 21b619388c Pin Zig to a real release so the musl release build fetches reliably
Release / tsunagi-linux-aarch64 (push) Failing after 3m49s
Release / tsunagi-linux-x86_64 (push) Failing after 3m26s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
The release run failed at "Install zig": mlugg/setup-zig, left unpinned, resolved
to the newest version it knew of — an unreleased 0.16.0 whose tarball 404s on
ziglang.org — and the community mirrors returned 502/503 around it, so the step
errored out before cargo-zigbuild ever ran.

Pin the action to Zig 0.14.1, an archived release with a permanent official
download URL. The official source then succeeds even when the mirrors are flaky,
and the version stays compatible with cargo-zigbuild. Also moves the action to
@v2.

Only the Linux musl jobs use zig; macOS and Windows are unaffected. Not run on a
real tag yet in this session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 11:54:29 +01:00
2 changed files with 270 additions and 16 deletions

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
name: Release
# Build the agent for every supported target on a version tag, leave each build
# as a run artifact, and then — only after every target has built — attach the
# archives to the release. Linux is a fully static musl binary (built with
# cargo-zigbuild) so one artifact runs on any distro and can be repackaged
# however a distro prefers.
#
# Targeted at Gitea Actions: artifacts use @v3 (the v4 backend is not available
# on Gitea/GHES), and the release is filled through Gitea's own API with curl
# rather than `gh`, which speaks only to github.com.
on:
push:
tags: ["v*"]
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
CARGO_NET_RETRY: "10"
jobs:
build:
name: ${{ matrix.asset }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
asset: tsunagi-linux-x86_64
archive: tar.gz
zig: true
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
asset: tsunagi-linux-aarch64
archive: tar.gz
zig: true
- os: macos-latest
target: aarch64-apple-darwin
asset: tsunagi-macos-aarch64
archive: tar.gz
zig: false
- os: windows-latest
target: x86_64-pc-windows-msvc
asset: tsunagi-windows-x86_64
archive: zip
zig: false
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
# aws-lc-sys (iroh's rustls backend) and the bundled sqlite are C, and the
# musl targets have no prebuilt bindings, so bindgen needs libclang and the
# C build needs cmake. zig is the cross C compiler cargo-zigbuild drives.
- name: Install musl build tooling
if: matrix.zig
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang libclang-dev
- name: Install zig
if: matrix.zig
uses: mlugg/setup-zig@v2
with:
# Pin a real, archived Zig release. Left unpinned the action resolves
# to the newest version it knows of (a yet-unreleased 0.16.0-dev),
# whose tarball 404s on ziglang.org while the community mirrors flake.
version: 0.14.1
- name: Install cargo-zigbuild
if: matrix.zig
uses: taiki-e/install-action@v2
with:
tool: cargo-zigbuild
- name: Build
shell: bash
run: |
set -euo pipefail
if [[ "${{ matrix.zig }}" == "true" ]]; then
# zig's linker ignores the `-O1` GNU-ld optimization flag Rust passes
# and says so on stderr, which rustc now surfaces as a linker_messages
# warning. It is cosmetic — the binary is correct — so quiet just that
# one lint to keep the musl build log clean. A real link error still
# fails the build.
export RUSTFLAGS="${RUSTFLAGS:-} -A linker_messages"
cargo zigbuild --release --locked --target "${{ matrix.target }}" --bin tsunagi
else
cargo build --release --locked --target "${{ matrix.target }}" --bin tsunagi
fi
- name: Package
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
stage="dist/pkg/${{ matrix.asset }}"
mkdir -p "$stage"
bin="tsunagi"
[[ "${{ runner.os }}" == "Windows" ]] && bin="tsunagi.exe"
cp "target/${{ matrix.target }}/release/${bin}" "$stage/"
# The top-level README, plus the per-OS install notes and integration
# files, so the archive carries what the platform needs to set up.
cp README.md "$stage/"
case "${{ runner.os }}" in
Linux)
cp dist/linux/tsunagi.service dist/linux/install.sh "$stage/"
cp dist/linux/README.md "$stage/INSTALL.md"
;;
macOS)
cp dist/macos/cy.hexor.tsunagi.plist "$stage/"
cp dist/macos/README.md "$stage/INSTALL.md"
;;
Windows)
cp dist/windows/README.md "$stage/INSTALL.md"
;;
esac
archive="${{ matrix.asset }}-${version}.${{ matrix.archive }}"
if [[ "${{ matrix.archive }}" == "zip" ]]; then
(cd dist/pkg && 7z a "../../${archive}" "${{ matrix.asset }}")
else
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
fi
# @v3 on purpose: Gitea does not support the v4 artifact backend. This is
# what makes the archive show up under the run and hands it to `publish`.
- name: Upload the archive as a run artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.asset }}
path: ${{ matrix.asset }}-*.*
if-no-files-found: error
publish:
name: Publish release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download every build artifact
uses: actions/download-artifact@v3
with:
path: artifacts
- name: Show what was collected
run: find artifacts -type f
- name: Create the release and upload the archives
shell: bash
env:
# Gitea injects this with access to the current repository. If the
# release calls below return 403, the repo/runner is configured to
# hand Actions a read-only token — grant it write access to releases.
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -u
tag="${GITHUB_REF_NAME}"
# Gitea sets GITHUB_API_URL to its own API root (…/api/v1); fall back
# to building it from the server URL if it is absent.
base="${GITHUB_API_URL:-${GITHUB_SERVER_URL}/api/v1}"
api="${base}/repos/${GITHUB_REPOSITORY}"
auth="Authorization: token ${TOKEN}"
echo "release API: $api"
prerelease=false
case "$tag" in *-*) prerelease=true ;; esac
# The release id for this tag, if it already exists.
code=$(curl -sS -o resp.json -w '%{http_code}' -H "$auth" "$api/releases/tags/$tag" || true)
echo "get release by tag: HTTP $code"
id=$(grep -o '"id":[0-9]*' resp.json 2>/dev/null | head -n1 | cut -d: -f2 || true)
if [ -z "$id" ]; then
code=$(curl -sS -o resp.json -w '%{http_code}' -X POST -H "$auth" \
-H 'Content-Type: application/json' \
-d "{\"tag_name\":\"$tag\",\"name\":\"tsunagi $tag\",\"prerelease\":$prerelease}" \
"$api/releases" || true)
echo "create release: HTTP $code"
id=$(grep -o '"id":[0-9]*' resp.json 2>/dev/null | head -n1 | cut -d: -f2 || true)
if [ -z "$id" ]; then
echo "could not create the release; response was:" >&2
cat resp.json >&2 || true
exit 1
fi
fi
echo "release id: $id"
# download-artifact@v3 nests each artifact in a directory named after
# its upload, with the single archive inside. The read loop runs in
# this shell (process substitution, not a pipe) so a failed upload
# actually fails the job.
rc=0
while IFS= read -r -d '' f; do
name=$(basename "$f")
code=$(curl -sS -o up.json -w '%{http_code}' -X POST -H "$auth" \
-F "attachment=@${f}" \
"$api/releases/$id/assets?name=${name}" || true)
echo "upload $name: HTTP $code"
if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then
cat up.json >&2 || true
rc=1
fi
done < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' \) -print0)
exit "$rc"
+55 -16
View File
@@ -1,15 +1,20 @@
name: Release
# Build the agent for every supported target on a version tag and attach the
# archives to a GitHub release. Linux is a fully static musl binary (built with
# cargo-zigbuild) so one artifact runs on any distro — Arch, Alpine, NixOS, old
# glibc systems alike — and can then be repackaged however a distro prefers.
# GitHub version of the release pipeline. The build half mirrors the Gitea one
# in .gitea/workflows/release.yml (static musl Linux via cargo-zigbuild, native
# macOS and Windows); only the publish half differs: GitHub needs the v4
# artifact backend and fills the release with `gh`.
#
# Every job is gated on `github.server_url == 'https://github.com'`, so this file
# runs only on GitHub. Gitea runs its own copy in .gitea/workflows; should a
# Gitea instance also read .github/workflows, the guard skips these jobs there
# so they never fail on the @v4 artifact backend or `gh`.
on:
push:
tags: ["v*"]
permissions:
contents: read
contents: write
env:
CARGO_TERM_COLOR: always
@@ -18,6 +23,7 @@ env:
jobs:
build:
name: ${{ matrix.asset }}
if: ${{ github.server_url == 'https://github.com' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
@@ -63,7 +69,12 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang libclang-dev
- name: Install zig
if: matrix.zig
uses: mlugg/setup-zig@v1
uses: mlugg/setup-zig@v2
with:
# Pin a real, archived Zig release. Left unpinned the action resolves
# to the newest version it knows of (a yet-unreleased 0.16.0-dev),
# whose tarball 404s on ziglang.org while the community mirrors flake.
version: 0.14.1
- name: Install cargo-zigbuild
if: matrix.zig
uses: taiki-e/install-action@v2
@@ -75,6 +86,12 @@ jobs:
run: |
set -euo pipefail
if [[ "${{ matrix.zig }}" == "true" ]]; then
# zig's linker ignores the `-O1` GNU-ld optimization flag Rust passes
# and says so on stderr, which rustc now surfaces as a linker_messages
# warning. It is cosmetic — the binary is correct — so quiet just that
# one lint to keep the musl build log clean. A real link error still
# fails the build.
export RUSTFLAGS="${RUSTFLAGS:-} -A linker_messages"
cargo zigbuild --release --locked --target "${{ matrix.target }}" --bin tsunagi
else
cargo build --release --locked --target "${{ matrix.target }}" --bin tsunagi
@@ -117,7 +134,8 @@ jobs:
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
fi
- uses: actions/upload-artifact@v4
- name: Upload the archive as a run artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.asset }}
path: ${{ matrix.asset }}-*.*
@@ -126,23 +144,44 @@ jobs:
publish:
name: Publish release
needs: build
if: ${{ github.server_url == 'https://github.com' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- name: Download every build artifact
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create release and upload assets
- name: Show what was collected
run: find artifacts -type f
- name: Create the release and upload the archives
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
gh release create "$RELEASE_TAG" artifacts/*/* \
--title "tsunagi ${RELEASE_TAG}" \
--generate-notes \
--verify-tag
# download-artifact@v4 nests each artifact under its own directory, so
# the archives are artifacts/<asset>/<file>.
mapfile -t files < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' \))
printf 'asset: %s\n' "${files[@]}"
prerelease=()
case "$TAG" in *-*) prerelease=(--prerelease) ;; esac
# Create the release on the first run; on a re-run, add/replace assets
# on the release that is already there.
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release upload "$TAG" "${files[@]}" --repo "$GITHUB_REPOSITORY" --clobber
else
gh release create "$TAG" "${files[@]}" \
--repo "$GITHUB_REPOSITORY" \
--title "tsunagi $TAG" \
--generate-notes \
--verify-tag \
"${prerelease[@]}"
fi