diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml deleted file mode 100644 index 61243b1..0000000 --- a/.gitea/workflows/release.yml +++ /dev/null @@ -1,375 +0,0 @@ -name: Release - -# PRD F8, Gitea edition. On a v* tag push, build the single-file kigi binary -# for the five supported targets, package kigi--. -# {tar.gz|zip} (binary + LICENSE + NOTICE + THIRD-PARTY-NOTICES), and publish -# them as a Gitea Release alongside SHA256SUMS. install.sh, install.ps1 and -# auto_update::release_asset_name() all resolve these exact names — keep the -# naming in lockstep with them. -# -# RUNNERS. Gitea has no hosted runners; every label below must be a runner you -# registered yourself (docs/RELEASE.md has the register commands): -# -# ubuntu-latest Linux x86_64, container mode — the label the Linux server's -# runner already advertises. Builds both Linux targets; -# aarch64 is a cross-compile, so this leg installs the -# aarch64 GNU toolchain. Because the image is a bare -# runner-image, the Linux legs provision their own apt -# packages and rustup (see the provision steps below). -# Registering a dedicated `linux-x86_64:docker://...` runner -# instead is fine — swap the three labels back if you do. -# macos-arm64 Apple Silicon Mac, host mode. Builds both Darwin targets; -# x86_64 is a cross-compile against the same SDK. macOS -# CANNOT be built anywhere else — Apple's SDK licence -# restricts it to Apple hardware. -# windows-amd64 Windows x86_64, host mode, with VS 2022 Build Tools -# (C++ workload). Needed because the tree links ring, -# mimalloc, zstd-sys and libz-sys against a static MSVC CRT -# (-C target-feature=+crt-static). -# -# SHELLS. Every step names its shell explicitly and the Windows leg never uses -# bash. On a normal Windows box `bash` resolves to WSL (not Git Bash), and -# `pwsh` (PowerShell 7) is usually absent — so Windows steps use `powershell`, -# which is the built-in 5.1. Keep those steps 5.1-compatible: no `&&`, no -# ternary, no null-coalescing. -# -# Note: because this file exists, Gitea ignores .github/workflows entirely. -# -# PUBLISH ORDER. The release is created as a draft and only made public once -# SHA256SUMS is attached, so `releases/latest` never hands the installer a -# release whose checksum manifest is still missing. - -on: - push: - tags: ["v*"] - -permissions: - contents: write - -env: - CARGO_TERM_COLOR: always - # Matches the version pinned by the dotslash launcher in bin/protoc. That - # launcher is not used here: it has no Windows entry and needs a `cargo - # install dotslash` first, so every platform fetches protoc directly. - PROTOC_VERSION: "29.3" - -jobs: - prepare: - name: prepare draft release - runs-on: ubuntu-latest - outputs: - release_id: ${{ steps.draft.outputs.release_id }} - steps: - - uses: actions/checkout@v4 - - - name: Check tag matches workspace version - shell: bash - run: | - tag_version="${GITHUB_REF_NAME#v}" - cargo_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)" - if [ "$tag_version" != "$cargo_version" ]; then - echo "Tag $GITHUB_REF_NAME does not match workspace version $cargo_version" >&2 - exit 1 - fi - - - name: Create or reuse draft release - id: draft - shell: bash - env: - TOKEN: ${{ secrets.GITEA_TOKEN }} - API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} - run: | - set -eu - # The runner-image is bare; jq and curl are both used below. - if ! command -v jq >/dev/null || ! command -v curl >/dev/null; then - SUDO=""; command -v sudo >/dev/null && SUDO=sudo - export DEBIAN_FRONTEND=noninteractive - $SUDO apt-get update -qq - $SUDO apt-get install -y -qq --no-install-recommends jq curl ca-certificates - fi - - # Idempotent so a re-run of a failed tag build reuses the same draft - # instead of erroring on a duplicate tag_name. - existing="$(curl -sS -H "Authorization: token $TOKEN" \ - "$API/releases/tags/$GITHUB_REF_NAME")" - release_id="$(printf '%s' "$existing" | jq -r '.id // empty')" - - if [ -z "$release_id" ]; then - prerelease=false - case "$GITHUB_REF_NAME" in *-*) prerelease=true ;; esac - created="$(curl -fsS -X POST \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\",\"draft\":true,\"prerelease\":$prerelease}" \ - "$API/releases")" - release_id="$(printf '%s' "$created" | jq -r '.id')" - echo "Created draft release $release_id (prerelease=$prerelease)" - else - echo "Reusing release $release_id for $GITHUB_REF_NAME" - fi - - echo "release_id=$release_id" >> "$GITHUB_OUTPUT" - - build: - name: build (${{ matrix.target }}) - needs: prepare - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-gnu - runner: ubuntu-latest - protoc: linux-x86_64 - ext: tar.gz - linux: true - - target: aarch64-unknown-linux-gnu - runner: ubuntu-latest - protoc: linux-x86_64 - ext: tar.gz - linux: true - cross_gnu: true - - target: aarch64-apple-darwin - runner: macos-arm64 - protoc: osx-aarch_64 - ext: tar.gz - - target: x86_64-apple-darwin - runner: macos-arm64 - protoc: osx-aarch_64 - ext: tar.gz - - target: x86_64-pc-windows-msvc - runner: windows-amd64 - protoc: win64 - ext: zip - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v4 - - # ── linux container provisioning ────────────────────────────────────── - # The Linux legs run in a bare gitea runner-image, which ships git and - # node for the JavaScript actions but no compiler, no unzip (protoc) and - # no rustup. Host-mode macOS and Windows already have their toolchains - # installed, so this step is Linux-only. - - name: Provision Linux build deps - if: matrix.linux - shell: bash - run: | - set -eu - SUDO=""; command -v sudo >/dev/null && SUDO=sudo - export DEBIAN_FRONTEND=noninteractive - $SUDO apt-get update -qq - $SUDO apt-get install -y -qq --no-install-recommends \ - build-essential pkg-config ca-certificates curl unzip tar jq - - # ── protoc ──────────────────────────────────────────────────────────── - - name: Install protoc (unix) - if: matrix.ext == 'tar.gz' - shell: bash - run: | - set -eu - url="https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${{ matrix.protoc }}.zip" - curl -fsSL -o protoc.zip "$url" - rm -rf "$HOME/protoc" - unzip -q -o protoc.zip -d "$HOME/protoc" - chmod +x "$HOME/protoc/bin/protoc" - echo "$HOME/protoc/bin" >> "$GITHUB_PATH" - - - name: Install protoc (windows) - if: matrix.ext == 'zip' - shell: powershell - run: | - $ErrorActionPreference = "Stop" - $url = "https://github.com/protocolbuffers/protobuf/releases/download/v$env:PROTOC_VERSION/protoc-$env:PROTOC_VERSION-${{ matrix.protoc }}.zip" - Invoke-WebRequest -Uri $url -OutFile protoc.zip - $dest = Join-Path $env:USERPROFILE "protoc" - if (Test-Path $dest) { Remove-Item $dest -Recurse -Force } - Expand-Archive protoc.zip -DestinationPath $dest -Force - Add-Content -Path $env:GITHUB_PATH -Value (Join-Path $dest "bin") - - # ── toolchain ───────────────────────────────────────────────────────── - # rust-toolchain.toml pins the channel (1.97.0) and preinstalls both - # Linux targets; `rustup show` materialises exactly that. - - name: Install toolchain (unix) - if: matrix.ext == 'tar.gz' - shell: bash - run: | - set -eu - # No-op on the macOS host runner, which has rustup installed already. - # `--default-toolchain none` leaves the channel choice to - # rust-toolchain.toml, which `rustup show` then materialises. - if ! command -v rustup >/dev/null; then - curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs \ - | sh -s -- -y --no-modify-path --profile minimal --default-toolchain none - echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - export PATH="$HOME/.cargo/bin:$PATH" - fi - rustup show - rustup target add ${{ matrix.target }} - - - name: Install toolchain (windows) - if: matrix.ext == 'zip' - shell: powershell - run: | - $ErrorActionPreference = "Stop" - rustup show - rustup target add ${{ matrix.target }} - - # ring, mimalloc, zstd-sys and libz-sys are all cc-rs builds, so the - # cross target needs its own C toolchain — cc-rs picks these up from the - # target-suffixed vars. - - name: Install aarch64 cross toolchain - if: matrix.cross_gnu - shell: bash - run: | - set -eu - SUDO=""; command -v sudo >/dev/null && SUDO=sudo - $SUDO apt-get update -qq - $SUDO apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - { - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" - echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc" - echo "CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++" - echo "AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar" - } >> "$GITHUB_ENV" - - # ── build ───────────────────────────────────────────────────────────── - - name: Build kigi (unix) - if: matrix.ext == 'tar.gz' - shell: bash - run: cargo build --profile release-dist -p kigi-bin --locked --target ${{ matrix.target }} - - - name: Build kigi (windows) - if: matrix.ext == 'zip' - shell: powershell - run: | - $ErrorActionPreference = "Stop" - cargo build --profile release-dist -p kigi-bin --locked --target ${{ matrix.target }} - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - # ── package, upload, checksum ───────────────────────────────────────── - # Archives go straight to the release rather than through the artifact - # store: they are large, and the release is the only place install.sh - # can read them from anyway. Only the checksum line travels as an - # artifact, for the publish job to concatenate. - - name: Package and upload (unix) - if: matrix.ext == 'tar.gz' - shell: bash - env: - TOKEN: ${{ secrets.GITEA_TOKEN }} - API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} - RELEASE_ID: ${{ needs.prepare.outputs.release_id }} - run: | - set -eu - version="${GITHUB_REF_NAME#v}" - archive="kigi-${version}-${{ matrix.target }}.tar.gz" - staging="$(mktemp -d)" - cp "target/${{ matrix.target }}/release-dist/kigi" "$staging/kigi" - cp LICENSE "$staging/" - [ -f NOTICE ] && cp NOTICE "$staging/" - for f in THIRD-PARTY-NOTICES THIRD-PARTY-NOTICES.md; do - [ -f "$f" ] && cp "$f" "$staging/" - done - tar -C "$staging" -czf "$archive" . - - curl -fsS -X POST \ - -H "Authorization: token $TOKEN" \ - -F "attachment=@${archive}" \ - "$API/releases/$RELEASE_ID/assets?name=${archive}" > /dev/null - echo "Uploaded $archive" - - if command -v sha256sum >/dev/null; then - sha256sum "$archive" > "checksum-${{ matrix.target }}.txt" - else - shasum -a 256 "$archive" > "checksum-${{ matrix.target }}.txt" - fi - cat "checksum-${{ matrix.target }}.txt" - - - name: Package and upload (windows) - if: matrix.ext == 'zip' - shell: powershell - env: - TOKEN: ${{ secrets.GITEA_TOKEN }} - API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} - RELEASE_ID: ${{ needs.prepare.outputs.release_id }} - run: | - $ErrorActionPreference = "Stop" - $version = $env:GITHUB_REF_NAME.TrimStart("v") - $archive = "kigi-$version-${{ matrix.target }}.zip" - $staging = Join-Path $env:RUNNER_TEMP "staging" - if (Test-Path $staging) { Remove-Item $staging -Recurse -Force } - New-Item -ItemType Directory -Force -Path $staging | Out-Null - Copy-Item "target/${{ matrix.target }}/release-dist/kigi.exe" (Join-Path $staging "kigi.exe") - Copy-Item LICENSE $staging - if (Test-Path NOTICE) { Copy-Item NOTICE $staging } - foreach ($f in @("THIRD-PARTY-NOTICES", "THIRD-PARTY-NOTICES.md")) { - if (Test-Path $f) { Copy-Item $f $staging } - } - Compress-Archive -Path (Join-Path $staging "*") -DestinationPath $archive -Force - - # System32\curl.exe, not PowerShell's `curl` alias for - # Invoke-WebRequest, which cannot do multipart uploads. - $api = "$env:API/releases/$env:RELEASE_ID/assets?name=$archive" - & "$env:SystemRoot\System32\curl.exe" -fsS -X POST ` - -H "Authorization: token $env:TOKEN" ` - -F "attachment=@$archive" $api - if ($LASTEXITCODE -ne 0) { throw "asset upload failed ($LASTEXITCODE)" } - Write-Host "Uploaded $archive" - - # sha256sum's exact format, so the publish job can just concatenate. - # ASCII keeps the BOM out — a BOM would corrupt the first hash. - $hash = (Get-FileHash -Algorithm SHA256 -Path $archive).Hash.ToLowerInvariant() - "$hash $archive" | Out-File -FilePath "checksum-${{ matrix.target }}.txt" -Encoding ascii - Get-Content "checksum-${{ matrix.target }}.txt" - - - uses: actions/upload-artifact@v3 - with: - name: checksum-${{ matrix.target }} - path: checksum-${{ matrix.target }}.txt - if-no-files-found: error - - publish: - name: publish release - needs: [prepare, build] - runs-on: ubuntu-latest - steps: - - uses: actions/download-artifact@v3 - with: - path: checksums - - - name: Assemble SHA256SUMS - shell: bash - run: | - set -eu - # download-artifact v3 nests each artifact in its own directory. The - # Windows line arrives CRLF-terminated; strip CR so every line is LF - # for install.sh's `read -r hash name`. - find checksums -name 'checksum-*.txt' -exec cat {} + \ - | tr -d '\r' | grep -v '^$' | sort -k2 > SHA256SUMS - lines="$(wc -l < SHA256SUMS)" - if [ "$lines" -ne 5 ]; then - echo "expected 5 checksum lines, got $lines" >&2 - cat SHA256SUMS >&2 - exit 1 - fi - cat SHA256SUMS - - - name: Attach SHA256SUMS and publish - shell: bash - env: - TOKEN: ${{ secrets.GITEA_TOKEN }} - API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} - RELEASE_ID: ${{ needs.prepare.outputs.release_id }} - run: | - set -eu - curl -fsS -X POST \ - -H "Authorization: token $TOKEN" \ - -F "attachment=@SHA256SUMS" \ - "$API/releases/$RELEASE_ID/assets?name=SHA256SUMS" > /dev/null - - # Flip the draft public only now that every asset install.sh looks - # for is attached. - curl -fsS -X PATCH \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"draft":false}' \ - "$API/releases/$RELEASE_ID" > /dev/null - echo "Published $GITHUB_REF_NAME" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..107dd92 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,255 @@ +name: Release + +# PRD F8. On a v* tag push, build the single-file kigi binary for the five +# supported targets, package kigi--.{tar.gz|zip} +# (binary + LICENSE + NOTICE + THIRD-PARTY-NOTICES), and publish them as a +# GitHub Release alongside SHA256SUMS. install.sh, install.ps1 and +# auto_update::release_asset_name() all resolve these exact names — keep the +# naming in lockstep with them. +# +# RUNNERS. GitHub-hosted only; no job needs local state, and the repo is +# public, so nothing here may move to a self-hosted runner. +# +# ubuntu-latest x86_64-unknown-linux-gnu +# ubuntu-24.04-arm aarch64-unknown-linux-gnu, built natively (no cross C +# toolchain for ring, mimalloc, zstd-sys, libz-sys) +# macos-latest both Darwin targets; the runner is arm64, so x86_64 is +# a cross-compile against the same SDK +# windows-latest x86_64-pc-windows-msvc (VS Build Tools preinstalled, +# needed for the static MSVC CRT link) +# +# PUBLISH ORDER. The release is created as a draft and only made public once +# SHA256SUMS is attached, so `releases/latest` never hands the installer a +# release whose checksum manifest is still missing. + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +env: + CARGO_TERM_COLOR: always + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + # Matches the version pinned by the dotslash launcher in bin/protoc. That + # launcher is not used here: it has no Windows entry and needs a `cargo + # install dotslash` first, so every platform fetches protoc directly. + PROTOC_VERSION: "29.3" + +jobs: + prepare: + name: prepare draft release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check tag matches workspace version + shell: bash + run: | + tag_version="${GITHUB_REF_NAME#v}" + cargo_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)" + if [ "$tag_version" != "$cargo_version" ]; then + echo "Tag $GITHUB_REF_NAME does not match workspace version $cargo_version" >&2 + exit 1 + fi + + # Idempotent so a re-run of a failed tag build reuses the same draft + # instead of erroring on a duplicate tag_name. + - name: Create or reuse draft release + shell: bash + run: | + set -eu + if gh release view "$GITHUB_REF_NAME" > /dev/null 2>&1; then + echo "Reusing release $GITHUB_REF_NAME" + exit 0 + fi + prerelease="" + case "$GITHUB_REF_NAME" in *-*) prerelease="--prerelease" ;; esac + gh release create "$GITHUB_REF_NAME" --draft --verify-tag \ + --title "$GITHUB_REF_NAME" --generate-notes $prerelease + echo "Created draft release $GITHUB_REF_NAME ${prerelease}" + + build: + name: build (${{ matrix.target }}) + needs: prepare + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + runner: ubuntu-latest + protoc: linux-x86_64 + ext: tar.gz + - target: aarch64-unknown-linux-gnu + runner: ubuntu-24.04-arm + protoc: linux-aarch_64 + ext: tar.gz + - target: aarch64-apple-darwin + runner: macos-latest + protoc: osx-aarch_64 + ext: tar.gz + - target: x86_64-apple-darwin + runner: macos-latest + protoc: osx-aarch_64 + ext: tar.gz + - target: x86_64-pc-windows-msvc + runner: windows-latest + protoc: win64 + ext: zip + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + + # ── protoc ──────────────────────────────────────────────────────────── + - name: Install protoc (unix) + if: matrix.ext == 'tar.gz' + shell: bash + run: | + set -eu + url="https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${{ matrix.protoc }}.zip" + curl -fsSL -o protoc.zip "$url" + rm -rf "$HOME/protoc" + unzip -q -o protoc.zip -d "$HOME/protoc" + chmod +x "$HOME/protoc/bin/protoc" + echo "$HOME/protoc/bin" >> "$GITHUB_PATH" + + - name: Install protoc (windows) + if: matrix.ext == 'zip' + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $url = "https://github.com/protocolbuffers/protobuf/releases/download/v$env:PROTOC_VERSION/protoc-$env:PROTOC_VERSION-${{ matrix.protoc }}.zip" + Invoke-WebRequest -Uri $url -OutFile protoc.zip + $dest = Join-Path $env:USERPROFILE "protoc" + if (Test-Path $dest) { Remove-Item $dest -Recurse -Force } + Expand-Archive protoc.zip -DestinationPath $dest -Force + Add-Content -Path $env:GITHUB_PATH -Value (Join-Path $dest "bin") + + # ── toolchain ───────────────────────────────────────────────────────── + # rust-toolchain.toml pins the channel (1.97.0); `rustup show` + # materialises exactly that. + - name: Install toolchain + shell: bash + run: | + set -eu + # The arm64 Linux image is not guaranteed to ship rustup. + # `--default-toolchain none` leaves the channel choice to + # rust-toolchain.toml. + if ! command -v rustup >/dev/null; then + curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs \ + | sh -s -- -y --no-modify-path --profile minimal --default-toolchain none + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.cargo/bin:$PATH" + fi + rustup show + rustup target add ${{ matrix.target }} + + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + + # ── build ───────────────────────────────────────────────────────────── + - name: Build kigi + shell: bash + run: cargo build --profile release-dist -p kigi-bin --locked --target ${{ matrix.target }} + + # ── package, upload, checksum ───────────────────────────────────────── + # Archives go straight to the release rather than through the artifact + # store: they are large, and the release is the only place install.sh + # can read them from anyway. Only the checksum line travels as an + # artifact, for the publish job to concatenate. + - name: Package (unix) + if: matrix.ext == 'tar.gz' + shell: bash + run: | + set -eu + version="${GITHUB_REF_NAME#v}" + archive="kigi-${version}-${{ matrix.target }}.tar.gz" + staging="$(mktemp -d)" + cp "target/${{ matrix.target }}/release-dist/kigi" "$staging/kigi" + cp LICENSE "$staging/" + [ -f NOTICE ] && cp NOTICE "$staging/" + for f in THIRD-PARTY-NOTICES THIRD-PARTY-NOTICES.md; do + [ -f "$f" ] && cp "$f" "$staging/" + done + tar -C "$staging" -czf "$archive" . + echo "ARCHIVE=$archive" >> "$GITHUB_ENV" + + - name: Package (windows) + if: matrix.ext == 'zip' + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $version = $env:GITHUB_REF_NAME.TrimStart("v") + $archive = "kigi-$version-${{ matrix.target }}.zip" + $staging = Join-Path $env:RUNNER_TEMP "staging" + if (Test-Path $staging) { Remove-Item $staging -Recurse -Force } + New-Item -ItemType Directory -Force -Path $staging | Out-Null + Copy-Item "target/${{ matrix.target }}/release-dist/kigi.exe" (Join-Path $staging "kigi.exe") + Copy-Item LICENSE $staging + if (Test-Path NOTICE) { Copy-Item NOTICE $staging } + foreach ($f in @("THIRD-PARTY-NOTICES", "THIRD-PARTY-NOTICES.md")) { + if (Test-Path $f) { Copy-Item $f $staging } + } + Compress-Archive -Path (Join-Path $staging "*") -DestinationPath $archive -Force + Add-Content -Path $env:GITHUB_ENV -Value "ARCHIVE=$archive" + + # Git Bash on Windows; sha256sum is available on every runner except + # macOS, which has shasum. + - name: Upload and checksum + shell: bash + run: | + set -eu + gh release upload "$GITHUB_REF_NAME" "$ARCHIVE" --clobber + echo "Uploaded $ARCHIVE" + if command -v sha256sum >/dev/null; then + sha256sum "$ARCHIVE" > "checksum-${{ matrix.target }}.txt" + else + shasum -a 256 "$ARCHIVE" > "checksum-${{ matrix.target }}.txt" + fi + cat "checksum-${{ matrix.target }}.txt" + + - uses: actions/upload-artifact@v4 + with: + name: checksum-${{ matrix.target }} + path: checksum-${{ matrix.target }}.txt + if-no-files-found: error + + publish: + name: publish release + needs: [prepare, build] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + path: checksums + pattern: checksum-* + merge-multiple: true + + - name: Assemble SHA256SUMS + shell: bash + run: | + set -eu + # Strip CR (Git Bash may emit CRLF) and the binary-mode `*` marker + # so every line is ` ` for install.sh. + cat checksums/checksum-*.txt \ + | tr -d '\r' | sed 's/ \*/ /' | grep -v '^$' | sort -k2 > SHA256SUMS + lines="$(wc -l < SHA256SUMS)" + if [ "$lines" -ne 5 ]; then + echo "expected 5 checksum lines, got $lines" >&2 + cat SHA256SUMS >&2 + exit 1 + fi + cat SHA256SUMS + + # Flip the draft public only now that every asset install.sh looks for + # is attached. + - name: Attach SHA256SUMS and publish + shell: bash + run: | + set -eu + gh release upload "$GITHUB_REF_NAME" SHA256SUMS --clobber + gh release edit "$GITHUB_REF_NAME" --draft=false + echo "Published $GITHUB_REF_NAME" diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 51cea41..db3150c 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -1,13 +1,11 @@ # Release checklist (PRD F8) Kigi ships as a single-file binary for five targets, published on this -repo's Gitea Releases by `.gitea/workflows/release.yml`. Users install via -`install.sh` / `install.ps1` and stay current through the in-app -self-updater (`kigi-update`), which resolves the same Releases API. - -`.github/workflows/release.yml` is the retired GitHub pipeline. Gitea ignores -`.github/workflows` entirely whenever `.gitea/workflows` is non-empty, so it -does not run — it is kept only as a reference for the GitHub-hosted layout. +repo's GitHub Releases +() by +`.github/workflows/release.yml`. Users install via `install.sh` / +`install.ps1` and stay current through the in-app self-updater +(`kigi-update`), which resolves the same Releases API. ## Cutting a release @@ -34,15 +32,15 @@ does not run — it is kept only as a reference for the GitHub-hosted layout. `release-dist` profile, packages `kigi--.{tar.gz|zip}` archives (binary + LICENSE + NOTICE + THIRD-PARTY-NOTICES), generates `SHA256SUMS`, and - publishes the Gitea Release. Tags containing `-` (e.g. + publishes the GitHub Release. Tags containing `-` (e.g. `v0.2.0-alpha.1`) publish as pre-releases, which only the `alpha` update channel picks up. The release is created as a **draft** and only flipped public once `SHA256SUMS` is attached, so `releases/latest` never hands the installer a release whose checksum manifest is still missing. A failed run leaves - the draft in place; re-pushing the tag reuses it rather than erroring on - a duplicate `tag_name`. + the draft in place; re-running the workflow reuses it rather than + erroring on a duplicate `tag_name`. 5. Smoke-test an installed artifact: ```sh @@ -52,127 +50,39 @@ does not run — it is kept only as a reference for the GitHub-hosted layout. ## Build runners -Gitea has no hosted runners, so all three `runs-on` labels below are machines -we register ourselves with `gitea-runner` (the binary formerly called -`act_runner`). Labels are declared in the runner's `config.yaml` as -`