Release / tsunagi-linux-aarch64 (push) Successful in 2m21s
Release / tsunagi-linux-x86_64 (push) Successful in 2m29s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
Adds a Debian package to the release for the x86_64 and aarch64 Linux builds, so `apt install ./tsunagi_*.deb` is a convenient path on the Ubuntu hosts. Because the binary is static musl, the package declares no Depends — it is built with plain dpkg-deb on the runner (no cargo-deb or nfpm), from dist/deb/build-deb.sh. The package installs: - the static binary at /usr/bin/tsunagi; - a systemd service at /lib/systemd/system/tsunagi.service that runs the agent as root (ExecStart rewritten to /usr/bin; the source unit targets /usr/local/bin for the tarball installer); - a polkit rule at /usr/share/polkit-1/rules.d granting the resolve1 actions to a "tsunagi" group, for running the agent as a non-root user instead of via the root service. postinst creates that system group and runs daemon-reload; prerm disables the service on removal. Both release workflows gain a "Build the Debian package" step on the two Linux matrix entries (deb/deb_arch), include the .deb in the uploaded run artifacts, and attach it to the release (Gitea via the API loop, GitHub via gh). macOS and Windows are unchanged. Service runs as root by default; the polkit rule/group is the documented opt-in for the non-root path — say if you'd rather the service itself run as the tsunagi user. YAML and shell syntax validated; the .deb build itself runs only on the Linux runner (no dpkg-deb locally) and has not been exercised in this session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
238 lines
8.7 KiB
YAML
238 lines
8.7 KiB
YAML
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
|
|
deb: true
|
|
deb_arch: amd64
|
|
- os: ubuntu-latest
|
|
target: aarch64-unknown-linux-musl
|
|
asset: tsunagi-linux-aarch64
|
|
archive: tar.gz
|
|
zig: true
|
|
deb: true
|
|
deb_arch: arm64
|
|
- 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
|
|
|
|
# A dependency-free .deb for the x86_64 and aarch64 Linux builds: it drops
|
|
# the static binary, the systemd service and the polkit rule. Built with
|
|
# dpkg-deb (present on the runner); see dist/deb/build-deb.sh.
|
|
- name: Build the Debian package
|
|
if: matrix.deb
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
version="${GITHUB_REF_NAME#v}"
|
|
bash dist/deb/build-deb.sh \
|
|
"target/${{ matrix.target }}/release/tsunagi" \
|
|
"${{ matrix.deb_arch }}" \
|
|
"$version" \
|
|
"tsunagi_${version}_${{ matrix.deb_arch }}.deb"
|
|
|
|
# @v3 on purpose: Gitea does not support the v4 artifact backend. This is
|
|
# what makes the archive (and the .deb, where built) show up under the run
|
|
# and hands it to `publish`.
|
|
- name: Upload the build outputs as run artifacts
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: ${{ matrix.asset }}
|
|
path: |
|
|
${{ matrix.asset }}-*.*
|
|
*.deb
|
|
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' -o -name '*.deb' \) -print0)
|
|
exit "$rc"
|