Three faults a first tag would have hit. The version check and the collect step looked for target/release/wallet-app, but the binary has been blackbeard-wallet since the product was renamed for packaging. The bundle step ran the Tauri CLI with --dir ui, where there is no Tauri project to find; it now runs from crates/wallet-app, checked with `tauri info`, and the CLI is pinned to 2.11.4 as doc/reproducibility.md said it would be once a release was cut. The publish job's `rpm -K` right after signing would exit non-zero on a runner whose rpm database does not hold the lair.cafe key; the check now runs on the repo host after the rsync, where gitea_ci verifies cichlid-0.1.9 as OK today. Refs #32 Refs #33 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW
289 lines
12 KiB
YAML
289 lines
12 KiB
YAML
name: release
|
|
|
|
# A release is a tag on the workspace version. Push `vX.Y.Z` where X.Y.Z is
|
|
# `version` in Cargo.toml (script/stamp-version.sh refuses anything else),
|
|
# and this builds the Linux bundles and attaches them to a Gitea release for
|
|
# that tag. The package and publish jobs then put the same binary on
|
|
# rpm.lair.cafe through the house signed-repo pipeline (#33): Fedora 44 only
|
|
# for v1, prebuilt-binary spec, signed with RPM_SIGNING_KEY, rsynced as
|
|
# gitea_ci to oolon, createrepo_c and packages.json regenerated. The Windows
|
|
# and macOS builders are their own child (#34).
|
|
#
|
|
# Secrets this needs on the repository: RPM_SIGNING_KEY, RPM_SIGNING_KEY_ID,
|
|
# RSYNC_SSH_KEY (the same values the other rpm.lair.cafe publishers hold).
|
|
|
|
on:
|
|
push:
|
|
tags: ["v*"]
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
linux:
|
|
# `rust-gtk3`: tauri-build and the bundler need GTK and WebKitGTK dev
|
|
# libraries (gitea-runners.md §3). The Tauri CLI comes from npm as a
|
|
# prebuilt binary rather than `cargo install`, which would rebuild it
|
|
# on every ephemeral runner.
|
|
runs-on: rust-gtk3
|
|
env:
|
|
# linuxdeploy ships as an AppImage; without FUSE in the container it
|
|
# must extract itself first. Stripping is left to the bundler's
|
|
# defaults per target, not linuxdeploy's.
|
|
APPIMAGE_EXTRACT_AND_RUN: "1"
|
|
NO_STRIP: "true"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: the tag is the workspace version
|
|
id: version
|
|
run: |
|
|
set -euo pipefail
|
|
script/stamp-version.sh "${GITHUB_REF_NAME}"
|
|
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
|
# The tagged commit's time, not the build's clock, for every
|
|
# timestamp the bundlers stamp (doc/reproducibility.md).
|
|
echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"
|
|
|
|
- name: webview dependencies
|
|
working-directory: ui
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: bundle
|
|
# `beforeBuildCommand` in tauri.conf.json builds the webview first.
|
|
# The signing key produces the .sig beside each updater artifact
|
|
# (createUpdaterArtifacts in tauri.conf.json); the public half is in
|
|
# the config and the plugin refuses anything else (#35).
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_KEY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Run from the Tauri crate, where the CLI finds tauri.conf.json.
|
|
# The CLI is pinned exactly (doc/reproducibility.md); bump it
|
|
# with the tauri crates.
|
|
cd crates/wallet-app
|
|
pnpm dlx @tauri-apps/cli@2.11.4 build --bundles deb,rpm,appimage
|
|
cd ../..
|
|
ls -l target/release/bundle/*/
|
|
|
|
- name: the binary says the version
|
|
run: |
|
|
set -euo pipefail
|
|
got="$(target/release/blackbeard-wallet --version)"
|
|
echo "$got"
|
|
[ "$got" = "blackbeard-wallet ${{ steps.version.outputs.version }}" ]
|
|
|
|
- name: collect
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
cp target/release/bundle/deb/*.deb dist/
|
|
cp target/release/bundle/rpm/*.rpm dist/
|
|
cp target/release/bundle/appimage/*.AppImage dist/
|
|
cp target/release/bundle/appimage/*.AppImage.sig dist/
|
|
# The bare binary too: the rpm.lair.cafe spec packages it (#33).
|
|
install -m 0755 target/release/blackbeard-wallet "dist/blackbeard-wallet-${{ steps.version.outputs.version }}-x86_64-linux-gnu"
|
|
(cd dist && sha256sum * > SHA256SUMS)
|
|
ls -l dist
|
|
|
|
- name: release
|
|
# The repository's own Actions token can create a release and
|
|
# upload assets; no personal token is involved.
|
|
env:
|
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases"
|
|
tag="${GITHUB_REF_NAME}"
|
|
body="blackbeard wallet ${tag}. Linux bundles built on Fedora 44 (deb, rpm, AppImage); SHA256SUMS lists every asset."
|
|
existing="$(curl -sf -H "Authorization: token ${TOKEN}" "${api}/tags/${tag}" | jq -r '.id' || true)"
|
|
if [ -n "${existing}" ] && [ "${existing}" != "null" ]; then
|
|
id="${existing}"
|
|
else
|
|
id="$(curl -sf -X POST "${api}" \
|
|
-H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg t "${tag}" --arg b "${body}" '{tag_name: $t, name: $t, body: $b, draft: false, prerelease: false}')" \
|
|
| jq -r '.id')"
|
|
fi
|
|
[ -n "${id}" ] && [ "${id}" != "null" ] || { echo "no release id"; exit 1; }
|
|
for f in dist/*; do
|
|
name="$(basename "$f")"
|
|
echo "uploading ${name}"
|
|
curl -sf -X POST "${api}/${id}/assets?name=${name}" \
|
|
-H "Authorization: token ${TOKEN}" -H "Content-Type: application/octet-stream" \
|
|
--data-binary "@${f}" > /dev/null
|
|
done
|
|
echo "release ${tag}: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${tag}"
|
|
|
|
- name: the updater manifest
|
|
# latest.json, as tauri-plugin-updater reads it: one entry per
|
|
# platform that opts in, pointing at the release asset with its
|
|
# signature. Windows and macOS entries arrive with #34.
|
|
run: |
|
|
set -euo pipefail
|
|
v="${{ steps.version.outputs.version }}"
|
|
appimage="$(basename dist/*.AppImage)"
|
|
sig="$(cat "dist/${appimage}.sig")"
|
|
jq -n --arg v "$v" --arg notes "blackbeard wallet ${v}" \
|
|
--arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg url "${{ github.server_url }}/${{ github.repository }}/releases/download/v${v}/${appimage}" \
|
|
--arg sig "$sig" \
|
|
'{version: $v, notes: $notes, pub_date: $date, platforms: {"linux-x86_64": {url: $url, signature: $sig}}}' \
|
|
> dist/latest.json
|
|
cat dist/latest.json
|
|
|
|
- name: keep the bundles for the packaging job
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: linux-bundles
|
|
path: dist/
|
|
retention-days: 7
|
|
|
|
package:
|
|
needs: linux
|
|
# `rpm`: rpmbuild, rpmlint and the signing tools (gitea-runners.md §3).
|
|
# One Fedora version for v1 (#33); the binary is a Fedora 44 binary
|
|
# because rust-gtk3 is Fedora 44, so a wider matrix would need a source
|
|
# build under mock or COPR, not a longer list here.
|
|
runs-on: rpm
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
fedora_version: ["44"]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: the tag is the workspace version
|
|
id: version
|
|
run: |
|
|
set -euo pipefail
|
|
script/stamp-version.sh "${GITHUB_REF_NAME}"
|
|
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: the bundles from the linux job
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
name: linux-bundles
|
|
path: artifacts/
|
|
|
|
- name: changelog entry
|
|
uses: https://git.lair.cafe/actions/rpm-changelog@v1
|
|
with:
|
|
spec: rpm/blackbeard-wallet.spec
|
|
version: ${{ steps.version.outputs.version }}
|
|
|
|
- name: build the rpm
|
|
run: |
|
|
set -euo pipefail
|
|
rm -f ~/.rpmmacros
|
|
rpmdev-setuptree
|
|
v="${{ steps.version.outputs.version }}"
|
|
install -m 0755 "artifacts/blackbeard-wallet-${v}-x86_64-linux-gnu" ~/rpmbuild/SOURCES/blackbeard-wallet
|
|
cp crates/wallet-app/blackbeard-wallet.desktop rpm/cafe.lair.blackbeard.wallet.metainfo.xml ~/rpmbuild/SOURCES/
|
|
cp crates/wallet-app/icons/32x32.png crates/wallet-app/icons/128x128.png \
|
|
crates/wallet-app/icons/128x128@2x.png crates/wallet-app/icons/icon.png ~/rpmbuild/SOURCES/
|
|
rpmbuild -bb rpm/blackbeard-wallet.spec \
|
|
--define "wallet_version ${v}" \
|
|
--undefine dist \
|
|
--define "dist .fc${{ matrix.fedora_version }}"
|
|
rpmlint ~/rpmbuild/RPMS/x86_64/*.rpm || true
|
|
ls -l ~/rpmbuild/RPMS/x86_64/
|
|
|
|
- name: keep the rpm for publishing
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: rpm-fc${{ matrix.fedora_version }}
|
|
path: ~/rpmbuild/RPMS/x86_64/*.rpm
|
|
retention-days: 7
|
|
|
|
publish:
|
|
needs: package
|
|
runs-on: rpm
|
|
# Serialise repository metadata updates (createrepo_c) across every
|
|
# publisher on rpm.lair.cafe.
|
|
concurrency:
|
|
group: rpm-publish
|
|
cancel-in-progress: false
|
|
env:
|
|
RPM_REPO_HOST: oolon.kosherinata.internal
|
|
strategy:
|
|
fail-fast: false
|
|
max-parallel: 1
|
|
matrix:
|
|
fedora_version: ["44"]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: the rpm
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
name: rpm-fc${{ matrix.fedora_version }}
|
|
path: rpms/
|
|
|
|
- name: import the signing key
|
|
run: |
|
|
set -euo pipefail
|
|
command -v sq >/dev/null || { echo "sequoia-sq is not installed"; exit 1; }
|
|
echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import
|
|
fpr=$(gpg --batch --with-colons --list-keys "${{ secrets.RPM_SIGNING_KEY_ID }}" | awk -F: '/^fpr:/ { print $10; exit }')
|
|
echo "${fpr}:6:" | gpg --batch --import-ownertrust
|
|
sed "s/@GPG_NAME@/${{ secrets.RPM_SIGNING_KEY_ID }}/" rpm/rpmmacros > ~/.rpmmacros
|
|
|
|
- name: sign
|
|
run: |
|
|
set -euo pipefail
|
|
for rpm in rpms/*.rpm; do
|
|
echo "signing ${rpm}"
|
|
rpm --addsign "${rpm}"
|
|
done
|
|
|
|
- name: ssh to the repo host
|
|
env:
|
|
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
install --directory --mode 700 ~/.ssh
|
|
echo "${RSYNC_SSH_KEY}" | install --mode 600 /dev/stdin ~/.ssh/id_ed25519
|
|
ssh -o StrictHostKeyChecking=accept-new "gitea_ci@${RPM_REPO_HOST}" exit
|
|
|
|
- name: publish to the stable channel
|
|
run: |
|
|
set -euo pipefail
|
|
dir="/var/www/rpm/fedora/${{ matrix.fedora_version }}/x86_64"
|
|
ssh "gitea_ci@${RPM_REPO_HOST}" "mkdir -p ${dir}"
|
|
rsync --archive --verbose --chmod D755,F644 rpms/*.rpm "gitea_ci@${RPM_REPO_HOST}:${dir}/"
|
|
ssh "gitea_ci@${RPM_REPO_HOST}" "cd ${dir} && createrepo_c --update ."
|
|
# Verify where the key is known: the repo host's rpm database has
|
|
# the lair.cafe public key; this runner's does not.
|
|
for rpm in rpms/*.rpm; do
|
|
ssh "gitea_ci@${RPM_REPO_HOST}" "rpm -Kv ${dir}/$(basename "${rpm}")"
|
|
done
|
|
scp script/generate-packages-json.py "gitea_ci@${RPM_REPO_HOST}:/tmp/"
|
|
ssh "gitea_ci@${RPM_REPO_HOST}" \
|
|
"python3 /tmp/generate-packages-json.py \
|
|
--repodata-dir ${dir}/repodata \
|
|
--output ${dir}/packages.json \
|
|
--base-url https://rpm.lair.cafe/fedora/${{ matrix.fedora_version }}/x86_64"
|
|
echo "published: https://rpm.lair.cafe/fedora/${{ matrix.fedora_version }}/x86_64/"
|
|
|
|
- name: the bundles again, for the manifest
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
name: linux-bundles
|
|
path: artifacts/
|
|
|
|
- name: publish the updater manifest
|
|
# Served from the same host as the dnf repo, at the endpoint in
|
|
# tauri.conf.json. Written last, so a manifest never points at an
|
|
# asset that is not yet on the release.
|
|
run: |
|
|
set -euo pipefail
|
|
ssh "gitea_ci@${RPM_REPO_HOST}" "mkdir -p /var/www/rpm/blackbeard-wallet"
|
|
rsync --archive --chmod D755,F644 artifacts/latest.json \
|
|
"gitea_ci@${RPM_REPO_HOST}:/var/www/rpm/blackbeard-wallet/latest.json"
|
|
echo "manifest: https://rpm.lair.cafe/blackbeard-wallet/latest.json"
|