CI: build and publish every package from Woodpecker

Pushes to main run the tests. The Deploy button publishes the Windows, RPM,
deb and F-Droid packages to the same NAS directories a dev machine uses, with
the same keys, then runs update-repo.sh, so public URLs don't change.

Builds run on Debian 12, in an image defined in ci/builder, so the Linux
packages now need glibc 2.36 instead of Fedora 44's 2.43.
This commit is contained in:
2026-11-01 11:54:49 -06:00
parent c7572ae7b0
commit a9ed6456da
10 changed files with 440 additions and 13 deletions

95
scripts/ci-publish.sh Executable file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/env bash
#
# ci-publish.sh — publish one target from a Woodpecker step in .woodpecker/release.yaml.
#
# Usage:
# ./scripts/ci-publish.sh windows|rpm|deb|fdroid
#
# Does what a fresh container lacks and a dev machine already has, then runs that
# target's publish script. The repos and keys are the same directories a dev
# machine publishes to, mounted at the same path, so either can publish next.
#
# Environment:
# MTGC_RELEASE_DIR the mounted release directory (default /nfs/hal9000/mtgc)
# MTGC_CI_CACHE the build cache volume (default /cache)
#
set -euo pipefail
usage() {
sed -n '3,14p' "$0" | sed 's/^# \{0,1\}//'
exit "${1:-0}"
}
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[err]\033[0m %s\n' "$*" >&2; }
RELEASE_DIR="${MTGC_RELEASE_DIR:-/nfs/hal9000/mtgc}"
CACHE="${MTGC_CI_CACHE:-/cache}"
[ $# -eq 1 ] || usage 1
case "$1" in
windows) subdir=win ;;
rpm) subdir=rpm ;;
deb) subdir=deb ;;
fdroid) subdir=repo ;;
-h | --help) usage ;;
*) usage 1 ;;
esac
target="$1"
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_root"
# --- 1. Checks --------------------------------------------------------------
#
# The version is the commit count, so a shallow clone would call this release
# 0.1.1. The publish script would refuse that, but not with a helpful message.
if [ "$(git rev-parse --is-shallow-repository)" != false ]; then
err "this is a shallow clone; the commit count needs the full history"
exit 1
fi
# --- 2. Build directories ---------------------------------------------------
#
# The publish scripts name target/ directly, so link it rather than setting
# CARGO_TARGET_DIR. It lives on the cache volume, which outlasts the pipeline
# and has the room: the workspace volume is 10G.
mkdir -p "$CACHE/target" "$HOME"
ln -sfn "$CACHE/target" target
cargo fetch --locked
if [ ! -d node_modules ]; then
npm ci
fi
# --- 3. Keys ----------------------------------------------------------------
#
# gpg wants its home to be private, and the agent wants a socket there; neither
# works on the NFS copy. /tmp goes away with the step's pod.
case "$target" in
rpm | deb)
gnupg="$(mktemp -d)"
cp -r "$RELEASE_DIR/keys/gnupg/." "$gnupg/"
chmod -R go-rwx "$gnupg"
export MTGC_RPM_GNUPGHOME="$gnupg" MTGC_DEB_GNUPGHOME="$gnupg"
;;
fdroid)
source scripts/android-env.sh >/dev/null
android="src-tauri/gen/android"
# RustPlugin reads the SDK and NDK from here; a clone doesn't have it.
printf 'sdk.dir=%s\nndk.dir=%s\n' "$ANDROID_HOME" "$NDK_HOME" >"$android/local.properties"
(
umask 077
sed "s|^storeFile=.*|storeFile=$RELEASE_DIR/keys/android-release.jks|" \
"$RELEASE_DIR/keys/keystore.properties" >"$android/keystore.properties"
)
;;
esac
# --- 4. Publish -------------------------------------------------------------
info "publishing $target to $RELEASE_DIR/$subdir"
exec "scripts/$target-publish.sh" --skip-published "$RELEASE_DIR/$subdir"

View File

@@ -13,6 +13,7 @@
# --init-key generate the package signing key (once, ever; skip it if
# rpm-publish.sh already made one, the two share it)
# --allow-dirty publish uncommitted changes; the version gets a ~dirty suffix
# --skip-published finish without error if the repo already has this version
#
# Environment:
# MTGC_DEB_DIR repo directory, if not given as an argument
@@ -27,7 +28,7 @@
set -euo pipefail
usage() {
sed -n '3,26p' "$0" | sed 's/^# \{0,1\}//'
sed -n '3,27p' "$0" | sed 's/^# \{0,1\}//'
exit "${1:-0}"
}
@@ -42,11 +43,13 @@ KEEP=3 # versions kept in the repo, so a bad release can be downgraded from
init_key=false
allow_dirty=false
skip_published=false
dir="${MTGC_DEB_DIR:-}"
while [ $# -gt 0 ]; do
case "$1" in
--init-key) init_key=true ;;
--allow-dirty) allow_dirty=true ;;
--skip-published) skip_published=true ;;
-h | --help) usage ;;
-*) usage 1 ;;
*) dir="$1" ;;
@@ -141,7 +144,9 @@ EOF
sed 's/^$/./; s/^/ /' "$dir/$PACKAGE.asc"
} >"$dir/$PACKAGE.sources"
# A landing page for whoever opens the repo's address in a browser.
# A landing page for whoever opens the repo's address in a browser. The package
# needs at least the glibc it's built against, so that depends on the build host.
glibc="$(ldd --version | awk 'NR == 1 { print $NF }')"
cat >"$dir/README.html" <<EOF
<!doctype html>
<html lang="en">
@@ -173,8 +178,8 @@ sudo apt update &amp;&amp; sudo apt install $PACKAGE</code></pre>
<p>From then on, <code>sudo apt upgrade</code> picks up new releases.</p>
<h2>Requirements</h2>
<p>A 64-bit x86 system with glibc 2.43 or newer (check with <code>ldd --version</code>).
Older releases, such as Ubuntu 24.04 and Debian 13, can't install it.</p>
<p>A 64-bit x86 system with glibc $glibc or newer (check with <code>ldd --version</code>).
Older releases can't install it.</p>
<h2>Uninstall</h2>
<pre><code>sudo apt remove $PACKAGE
@@ -208,6 +213,10 @@ while read -r version; do
[ "$patch" -gt "$newest" ] && newest="$patch"
done < <(repo_ list "$SUITE" "$PACKAGE" 2>/dev/null | awk '{ print $NF }')
if [ "$release_count" -le "$newest" ]; then
if $skip_published; then
info "version $release_version is already published; nothing to do"
exit 0
fi
err "version $release_version is not newer than the repo's x.y.$newest; commit something first"
exit 1
fi

View File

@@ -12,6 +12,7 @@
# Options:
# --init-keystore generate the app signing key (once, ever) and keystore.properties
# --allow-dirty publish uncommitted changes; the version gets a -dirty suffix
# --skip-published finish without error if the repo already has this version
#
# Environment:
# MTGC_FDROID_DIR repo directory, if not given as an argument
@@ -26,7 +27,7 @@
set -euo pipefail
usage() {
sed -n '3,24p' "$0" | sed 's/^# \{0,1\}//'
sed -n '3,25p' "$0" | sed 's/^# \{0,1\}//'
exit "${1:-0}"
}
@@ -40,11 +41,13 @@ APP_KEY_ALIAS="mtgc"
init_keystore=false
allow_dirty=false
skip_published=false
dir="${MTGC_FDROID_DIR:-}"
while [ $# -gt 0 ]; do
case "$1" in
--init-keystore) init_keystore=true ;;
--allow-dirty) allow_dirty=true ;;
--skip-published) skip_published=true ;;
-h | --help) usage ;;
-*) usage 1 ;;
*) dir="$1" ;;
@@ -187,6 +190,10 @@ for apk in "$dir"/repo/"${APP_ID}"_*.apk "$dir"/archive/"${APP_ID}"_*.apk; do
[ "$code" -gt "$newest" ] && newest="$code"
done
if [ "$version_code" -le "$newest" ]; then
if $skip_published; then
info "versionCode $version_code is already published; nothing to do"
exit 0
fi
err "versionCode $version_code is not newer than $newest, already in the repo; commit something first"
exit 1
fi

View File

@@ -12,6 +12,7 @@
# Options:
# --init-key generate the package signing key (once, ever)
# --allow-dirty publish uncommitted changes; the RPM release becomes 0.dirty
# --skip-published finish without error if the repo already has this version
#
# Environment:
# MTGC_RPM_DIR repo directory, if not given as an argument
@@ -26,7 +27,7 @@
set -euo pipefail
usage() {
sed -n '3,25p' "$0" | sed 's/^# \{0,1\}//'
sed -n '3,26p' "$0" | sed 's/^# \{0,1\}//'
exit "${1:-0}"
}
@@ -40,11 +41,13 @@ KEEP=3 # versions kept in the repo, so a bad release can be downgraded from
init_key=false
allow_dirty=false
skip_published=false
dir="${MTGC_RPM_DIR:-}"
while [ $# -gt 0 ]; do
case "$1" in
--init-key) init_key=true ;;
--allow-dirty) allow_dirty=true ;;
--skip-published) skip_published=true ;;
-h | --help) usage ;;
-*) usage 1 ;;
*) dir="$1" ;;
@@ -142,6 +145,10 @@ for rpm_file in "$dir/$PACKAGE"-*.rpm; do
[ "$patch" -gt "$newest" ] && newest="$patch"
done
if [ "$release_count" -le "$newest" ]; then
if $skip_published; then
info "version $release_version is already published; nothing to do"
exit 0
fi
err "version $release_version is not newer than the repo's x.y.$newest; commit something first"
exit 1
fi
@@ -159,7 +166,9 @@ if [ -z "$built" ]; then
err "the build produced no RPM under $outputs"
exit 1
fi
rpmsign --addsign --define "_gpg_path $GNUPGHOME" --define "_gpg_name $fpr" "$built" >/dev/null
# Debian's rpm looks for gpg2, which only Fedora has, so name the gpg in use.
rpmsign --addsign --define "__gpg $(command -v gpg)" --define "_gpg_path $GNUPGHOME" \
--define "_gpg_name $fpr" "$built" >/dev/null
# --- 7. Publish -------------------------------------------------------------

View File

@@ -9,6 +9,7 @@
#
# Options:
# --allow-dirty publish uncommitted changes; the file name gets a -dirty suffix
# --skip-published finish without error if the repo already has this version
#
# Environment:
# MTGC_WINDOWS_DIR downloads directory, if not given as an argument
@@ -23,7 +24,7 @@
set -euo pipefail
usage() {
sed -n '3,22p' "$0" | sed 's/^# \{0,1\}//'
sed -n '3,23p' "$0" | sed 's/^# \{0,1\}//'
exit "${1:-0}"
}
@@ -37,10 +38,12 @@ LATEST="$PACKAGE-latest-x64-setup.exe"
KEEP=3 # versions kept in the folder, so a bad release can be rolled back
allow_dirty=false
skip_published=false
dir="${MTGC_WINDOWS_DIR:-}"
while [ $# -gt 0 ]; do
case "$1" in
--allow-dirty) allow_dirty=true ;;
--skip-published) skip_published=true ;;
-h | --help) usage ;;
-*) usage 1 ;;
*) dir="$1" ;;
@@ -98,6 +101,10 @@ for exe in "$dir/${PACKAGE}"_*_x64-setup.exe; do
[ "$patch" -gt "$newest" ] && newest="$patch"
done
if [ "$release_count" -le "$newest" ]; then
if $skip_published; then
info "version $release_version is already published; nothing to do"
exit 0
fi
err "version $release_version is not newer than the folder's x.y.$newest; commit something first"
exit 1
fi