Files
inbuxa-migrate/.github/workflows/ci.yml
T
jcoffey-dev 7156c925e7
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 3m31s
ci / announce (pull_request) Skipped
ci / test (pull_request) Skipped
Prepare the 2026.9.30 release, with notes from the changelog
The changelog's first section becomes 2026.9.30 and gains the phase 1 and 2
changes: re-export updates, one copy per message, the Sieve rename,
timeouts, scoped certificate checks, resilient and bounded imports, batched
export with progress, and the predictive dry run.

The tag build now takes each release's notes from its version's section of
CHANGELOG.md, followed by the line about the binaries, so the release and
the announcement made from it say what changed. A version with no section
falls back to that line alone.
2026-09-30 14:13:17 -07:00

268 lines
12 KiB
YAML

# SPDX-FileCopyrightText: 2026 John Coffey <[email protected]>
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
# CI on GitHub Actions, for the GitHub copy of this repository.
#
# The repository lives on the self-hosted Gitea; GitHub holds a push mirror
# that Gitea updates on every commit. Nothing is merged here -- pull requests
# happen on Gitea, and their branches reach GitHub as ordinary pushes, which is
# why this workflow runs on `push` and not on `pull_request`.
#
# Which forge does the building is one switch, the variable BUILD_ON, set on
# both forges at the organization level:
#
# BUILD_ON=github every job here runs; .gitea/workflows/ci.yml skips its
# test job and waits for the status this workflow reports
# back instead.
# unset / other every job here skips; Gitea runs the tests. Releases need
# GitHub: they are built on native amd64 and arm64 runners.
#
# The result goes back to Gitea as one commit status, "github/ci (branch)" or
# "github/ci (tag)", which is what Gitea's `github` job waits on.
#
# v* tags build a release: the tag must be on main and name the version in
# Cargo.toml; each architecture is built on its own native runner by
# scripts/build-release.sh, and the archives plus SHA256SUMS are attached to
# the Gitea Release -- Gitea is where the install guide points. The Release is
# created as a draft, filled, then published, so it is never visible with
# assets missing; if an upload fails, a Release this run created is deleted.
# Gitea announces the release once this run has reported success.
#
# The binaries link glibc dynamically, so they are built on Ubuntu 22.04: the
# oldest glibc GitHub offers, and so the widest range of servers they run on.
#
# Configuration, all at the organization level on GitHub: variables BUILD_ON,
# GITEA_URL; secret GITEA_TOKEN (write:repository on Gitea).
#
# Every `uses:` is pinned to a full commit SHA, with the release it was in the
# trailing comment. Nothing schedules here: schedules belong to Gitea.
name: ci
on:
push:
branches: ['**']
tags: ['**']
# For a run GitHub queued and then orphaned. Run it from a tag to redo that
# tag's release; attaching skips any file already on the Release.
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref_type != 'tag' }}
permissions:
contents: read
env:
GITEA_URL: ${{ vars.GITEA_URL }}
CONTEXT: github/ci (${{ github.ref_type }})
CARGO_TERM_COLOR: never
jobs:
# Tells Gitea a result is on its way, so a status that is still missing
# reads as "running" rather than "never started".
pending:
if: vars.BUILD_ON == 'github'
runs-on: ubuntu-latest
steps:
- env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
curl -fsS -o /dev/null -X POST -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \
"$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA" \
-d "$(jq -n --arg c "$CONTEXT" --arg u "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
'{state:"pending", context:$c, target_url:$u, description:"GitHub Actions"}')"
# The unit and mock tests. The #[ignore]d ones need live servers or
# containers and run by hand (README, "Testing").
test:
if: vars.BUILD_ON == 'github'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-test-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}
restore-keys: cargo-test-${{ runner.os }}-${{ runner.arch }}-
# rustup is on the runner; rust-toolchain.toml picks the toolchain.
- run: rustup show active-toolchain || rustup toolchain install
- run: cargo test --locked
# Guards shared by both architectures, checked once.
version:
if: vars.BUILD_ON == 'github' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
runs-on: ubuntu-latest
env:
TAG: ${{ github.ref_name }}
steps:
# Full history: the ancestry check cannot be answered from a shallow
# clone.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# A release can never describe code that was not reviewed onto main,
# and its tag must be the version the binary reports.
- run: |
git fetch --quiet origin main
git merge-base --is-ancestor "$(git rev-parse "${TAG}^{commit}")" origin/main \
|| { echo "::error::$TAG is not on main"; exit 1; }
want="v$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
[ "$TAG" = "$want" ] || { echo "::error::$TAG does not match Cargo.toml ($want)"; exit 1; }
build:
if: vars.BUILD_ON == 'github' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
needs: [test, version]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: true
matrix:
include:
- arch: amd64
runner: ubuntu-22.04
- arch: arm64
runner: ubuntu-22.04-arm
env:
TAG: ${{ github.ref_name }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- run: rustup show active-toolchain || rustup toolchain install
- run: |
SOURCE_DATE_EPOCH="$(git log -1 --format=%ct "$TAG")" scripts/build-release.sh "$TAG" dist
sha256sum dist/*.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: archive-${{ matrix.arch }}
path: dist/*.tar.gz
retention-days: 7
overwrite: true
if-no-files-found: error
release:
if: vars.BUILD_ON == 'github' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
needs: [build]
runs-on: ubuntu-latest
env:
TAG: ${{ github.ref_name }}
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
pattern: archive-*
merge-multiple: true
- run: |
(cd dist && sha256sum ./*.tar.gz | sed 's| \./| |' > SHA256SUMS)
cat dist/SHA256SUMS
# The release notes are this version's section of CHANGELOG.md, so the
# release, and the announcement made from it, say what changed.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: CHANGELOG.md
sparse-checkout-cone-mode: false
- env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
API="$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY"
auth=(-H "Authorization: token $GITEA_TOKEN")
files="Binaries for linux/amd64 and linux/arm64. Verify with SHA256SUMS."
notes="$(awk -v v="${TAG#v}" 'index($0, "## [" v "]") == 1 {f = 1; next}
f && (/^## / || /^---$/) {exit}
f' CHANGELOG.md | sed -e '/./,$!d')"
if [ -n "$notes" ]; then notes="$notes"$'\n\n'"$files"; else notes="$files"; fi
# Reuse the Release if the tag already has one (a re-run), else make a
# draft of our own.
created=0
id="$(curl -fsS "${auth[@]}" "$API/releases/tags/$TAG" 2>/dev/null | jq -r '.id // empty' || true)"
if [ -z "$id" ]; then
id="$(curl -fsS "${auth[@]}" -H 'Content-Type: application/json' \
-d "$(jq -n --arg t "$TAG" --arg b "$notes" '{tag_name:$t, name:$t, draft:true, body:$b}')" \
"$API/releases" | jq -r '.id // empty')"
[ -n "$id" ] || { echo "::error::could not create the release"; exit 1; }
created=1
fi
have="$(curl -fsS "${auth[@]}" "$API/releases/$id/assets" | jq -r '.[].name')"
for f in dist/*; do
n="$(basename "$f")"
if grep -qxF "$n" <<<"$have"; then echo "already attached: $n"; continue; fi
echo "uploading $n"
curl -fsS -o /dev/null "${auth[@]}" --form "attachment=@$f" "$API/releases/$id/assets?name=$n" || {
[ "$created" = 1 ] && curl -sS -o /dev/null "${auth[@]}" -X DELETE "$API/releases/$id"
exit 1
}
done
if [ "$created" = 1 ]; then
curl -fsS -o /dev/null "${auth[@]}" -H 'Content-Type: application/json' -X PATCH \
-d '{"draft":false}' "$API/releases/$id"
fi
# ---------------------------------------------------- github release ------
# Copies this tag's Gitea release -- notes and files -- to a GitHub release,
# so the replica's Releases page, and anyone watching it, keeps up. Gitea's
# release is the real one; this is left out of the report to Gitea, so a
# failure here cannot fail a release. PR and issue numbers in the notes are
# rewritten to Gitea links: on GitHub a bare #16 is some other PR.
github-release:
if: ${{ vars.BUILD_ON == 'github' && github.ref_type == 'tag' }}
needs: [release]
runs-on: ubuntu-latest
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL }}
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
steps:
- run: |
set -euo pipefail
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "GitHub already has a release for $TAG"; exit 0
fi
# The Gitea release exists by now if this run made it; allow a few
# minutes either way.
code=0
for _ in $(seq 1 15); do
code="$(curl -sS -o rel.json -w '%{http_code}' "$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/tags/$TAG")"
[ "$code" = 200 ] && break
sleep 20
done
if [ "$code" != 200 ]; then echo "No Gitea release for $TAG; nothing to copy"; exit 0; fi
if [ "$(jq -r .draft rel.json)" = true ]; then echo "The Gitea release is a draft; not copying"; exit 0; fi
export BASE="$(jq -r '.html_url | sub("/releases/tag/.*$"; "")' rel.json)"
jq -r '.body // ""' rel.json | perl -pe 's{(?<![\w/&\[])#(\d+)\b}{[#$1]($ENV{BASE}/pulls/$1)}g' > notes.md
printf '\n\n_Mirrored from [the Gitea release](%s); report issues on [Gitea](%s/issues)._\n' \
"$(jq -r .html_url rel.json)" "$BASE" >> notes.md
files=()
mkdir -p files
while IFS=$'\t' read -r name url; do
curl -fsSL -o "files/$name" "$url"; files+=("files/$name")
done < <(jq -r '.assets[]? | [.name, .browser_download_url] | @tsv' rel.json)
title="$(jq -r '.name // ""' rel.json)"; [ -n "$title" ] || title="$TAG"
if [ "$(jq -r .prerelease rel.json)" = true ]; then kind=--prerelease; else kind=--latest; fi
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$title" \
--notes-file notes.md "$kind" "${files[@]}"
echo "created the GitHub release for $TAG with ${#files[@]} file(s)"
report:
if: always() && vars.BUILD_ON == 'github'
needs: [pending, test, version, build, release]
runs-on: ubuntu-latest
steps:
- env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
STATE: ${{ contains(needs.*.result, 'failure') && 'failure' || (contains(needs.*.result, 'cancelled') && 'cancelled' || 'success') }}
run: |
# A cancelled run was superseded by a newer run for the same commit (the
# mirror can push one commit twice); that run reports. Posting "failure"
# here would fail the Gitea check while the real build is still going.
if [ "$STATE" = cancelled ]; then echo "cancelled: leaving the result to the newer run"; exit 0; fi
curl -fsS -o /dev/null -X POST -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \
"$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA" \
-d "$(jq -n --arg s "$STATE" --arg c "$CONTEXT" --arg u "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
'{state:$s, context:$c, target_url:$u, description:"GitHub Actions"}')"