name: Release # Build the agent for every supported target on a version tag and attach the # archives to a 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. # # Targeted at Gitea Actions: the release assets are uploaded straight to the # Gitea release API with curl, rather than through actions/upload-artifact and # `gh`, because the v4 artifact backend and the GitHub REST API are not # available there. The only runner tools it needs are curl and coreutils. 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. # A concrete release always has a permanent official URL, so the # download succeeds even if mirrors are down, and the version stays # compatible with cargo-zigbuild. 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 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 - name: Upload the archive to the release shell: bash env: # Gitea injects this with write access to the current repository. TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -u version="${GITHUB_REF_NAME#v}" archive="${{ matrix.asset }}-${version}.${{ matrix.archive }}" tag="${GITHUB_REF_NAME}" api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" auth="Authorization: token ${TOKEN}" prerelease=false case "$tag" in *-*) prerelease=true ;; esac # The release id for this tag, or empty if there is no release yet. # Parsed with grep so the job needs no jq on the runner image. get_id() { curl -fsSL -H "$auth" "$api/releases/tags/$tag" 2>/dev/null \ | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2 } # Create the release if it is not there. Several matrix jobs can race # here, so a create that loses the race is ignored and the id is then # read back by tag. id="$(get_id)" if [ -z "$id" ]; then curl -fsS -X POST -H "$auth" -H 'Content-Type: application/json' \ -d "{\"tag_name\":\"$tag\",\"name\":\"tsunagi $tag\",\"prerelease\":$prerelease}" \ "$api/releases" >/dev/null 2>&1 || true id="$(get_id)" fi if [ -z "$id" ]; then echo "could not create or find a release for $tag at $api" >&2 exit 1 fi curl -fsS -X POST -H "$auth" \ -F "attachment=@${archive}" \ "$api/releases/$id/assets?name=${archive}" >/dev/null echo "uploaded $archive to release $tag (id $id)"