ci: build and publish the apk from this repo
Some checks failed
package / package (push) Has been cancelled
Some checks failed
package / package (push) Has been cancelled
A package workflow on the pmos runner: pmbootstrap cross-builds the aarch64 apk(s) from packaging/aport/ at the pushed commit and uploads them to the Forgejo Alpine registry, so a daemon release reaches installed phones via plain 'apk upgrade' without an fp6-img image run. Releases are gated by pkgver: the registry 409s an already-published version and the workflow moves on, so ordinary pushes are no-ops until the aport bumps pkgver/pkgrel. fp6-img is unchanged: images still build imsd from a pinned checkout of this repo, from the same aport. Needs the PACKAGE_TOKEN secret on this repo (or org-wide) to publish; until then the workflow builds and skips the upload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
17e0f6b53a
commit
e85136982c
2 changed files with 175 additions and 0 deletions
60
.forgejo/workflows/package.yml
Normal file
60
.forgejo/workflows/package.yml
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
name: package
|
||||||
|
|
||||||
|
# Builds the imsd apk(s) for aarch64 from packaging/aport/ at the pushed
|
||||||
|
# commit and publishes them to the Forgejo Alpine registry — the repo
|
||||||
|
# installed phones already point at (via catcrafts-fp6-repo), so a release
|
||||||
|
# reaches users through plain 'apk upgrade' without an fp6-img image run.
|
||||||
|
#
|
||||||
|
# Release gating is the pkgver: the registry answers 409 for an
|
||||||
|
# already-published version and the publish step treats that as "nothing to
|
||||||
|
# do" — so pushes only release when packaging/aport/APKBUILD bumps
|
||||||
|
# pkgver/pkgrel. fp6-img images keep building imsd from their own pinned
|
||||||
|
# checkout of this repo; same aport, so the two pipelines cannot skew.
|
||||||
|
#
|
||||||
|
# Runs on the privileged "pmos" runner (qemu-user binfmt on the host for
|
||||||
|
# pmbootstrap's aarch64 chroots). Requires the PACKAGE_TOKEN secret
|
||||||
|
# (catbot account, package:write scope) to publish; without it the build
|
||||||
|
# still runs and the publish step skips quietly.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
runs-on: pmos
|
||||||
|
timeout-minutes: 180
|
||||||
|
steps:
|
||||||
|
# actions/checkout is a Node action; bare alpine has no node
|
||||||
|
- name: Provision job container
|
||||||
|
run: apk add -q nodejs git curl
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: ./packaging/build-package.sh
|
||||||
|
|
||||||
|
- name: Publish to the apk registry
|
||||||
|
env:
|
||||||
|
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$PACKAGE_TOKEN" ]; then
|
||||||
|
echo "no PACKAGE_TOKEN secret configured; skipping package publish"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
found=0
|
||||||
|
for f in /home/build/.local/var/pmbootstrap/packages/*/aarch64/imsd*.apk; do
|
||||||
|
[ -e "$f" ] || continue
|
||||||
|
found=1
|
||||||
|
code=$(curl -s -o /dev/null -w '%{http_code}' \
|
||||||
|
--user "catbot:$PACKAGE_TOKEN" --upload-file "$f" \
|
||||||
|
"https://forgejo.catcrafts.net/api/packages/Catcrafts/alpine/edge/fp6")
|
||||||
|
case "$code" in
|
||||||
|
201) echo "published: $(basename "$f")" ;;
|
||||||
|
409) echo "already published: $(basename "$f")" ;;
|
||||||
|
*) echo "FAILED ($code): $(basename "$f")"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ "$found" = 1 ] || { echo "no packages found to publish"; exit 1; }
|
||||||
115
packaging/build-package.sh
Executable file
115
packaging/build-package.sh
Executable file
|
|
@ -0,0 +1,115 @@
|
||||||
|
#!/bin/sh -eu
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||||
|
# CI package build: produce the imsd apk(s) for aarch64 from THIS checkout,
|
||||||
|
# using packaging/aport/ (the canonical aport) and pmbootstrap's cross
|
||||||
|
# chroots. Runs in CI inside an Alpine container on the privileged "pmos"
|
||||||
|
# runner (qemu-user binfmt on the host for the aarch64 chroots); also
|
||||||
|
# runnable in any Alpine environment with the same privileges
|
||||||
|
# (IMSD_NO_CROSSDIRECT=1 for hosts where crossdirect's /native bridge
|
||||||
|
# breaks — the build then runs qemu-only, slower, identical output).
|
||||||
|
#
|
||||||
|
# Built packages land in ~build/.local/var/pmbootstrap/packages/*/aarch64/;
|
||||||
|
# the workflow's publish step uploads the imsd*.apk ones to the Forgejo
|
||||||
|
# Alpine registry.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
PMAPORTS_REPO=https://gitlab.postmarketos.org/postmarketOS/pmaports.git
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
||||||
|
|
||||||
|
# pmbootstrap refuses to run as root: install deps, then re-exec as a build
|
||||||
|
# user with passwordless sudo (pmbootstrap escalates itself where needed).
|
||||||
|
if [ "$(id -u)" = 0 ]; then
|
||||||
|
apk add -q git sudo python3 py3-pip multipath-tools util-linux tar
|
||||||
|
# pmbootstrap pinned from git: Alpine's package is older. The pmOS
|
||||||
|
# gitlab hiccups under crawler load; a failure costs a retry, not the run
|
||||||
|
for _i in 1 2 3; do
|
||||||
|
pip install -q --break-system-packages \
|
||||||
|
git+https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git@3.11.1 \
|
||||||
|
&& break
|
||||||
|
if [ "$_i" = 3 ]; then
|
||||||
|
echo "pmbootstrap pip install failed after 3 attempts" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "pmbootstrap pip install failed (attempt $_i/3), retrying in 15s..." >&2
|
||||||
|
sleep 15
|
||||||
|
done
|
||||||
|
# containers cannot modprobe; make pmbootstrap's 'sudo modprobe' a no-op
|
||||||
|
# (/usr/local/sbin precedes /sbin in sudo's secure_path)
|
||||||
|
mkdir -p /usr/local/sbin
|
||||||
|
printf '#!/bin/sh\nexit 0\n' > /usr/local/sbin/modprobe
|
||||||
|
chmod +x /usr/local/sbin/modprobe
|
||||||
|
id build >/dev/null 2>&1 || adduser -D build
|
||||||
|
echo 'build ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/build
|
||||||
|
# su scrubs the environment — carry the knobs that matter across it
|
||||||
|
exec su build -c "IMSD_NO_CROSSDIRECT='${IMSD_NO_CROSSDIRECT:-}' sh -eu '$SRC/packaging/build-package.sh'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# the CI checkout is root-owned; let git read it as the build user
|
||||||
|
git config --global --add safe.directory "$SRC"
|
||||||
|
|
||||||
|
# git hosts occasionally hiccup; a clone failure costs a retry, not the run
|
||||||
|
clone_retry() { # clone_retry <dest> <git clone args...>
|
||||||
|
_dest=$1; shift
|
||||||
|
for _i in 1 2 3; do
|
||||||
|
rm -rf "$_dest"
|
||||||
|
git clone "$@" "$_dest" && return 0
|
||||||
|
echo "git clone $_dest failed (attempt $_i/3), retrying in 10s..." >&2
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
echo "git clone $_dest failed after 3 attempts" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
retry() { # retry <description> <cmd...>
|
||||||
|
_desc=$1; shift
|
||||||
|
for _i in 1 2 3; do
|
||||||
|
"$@" && return 0
|
||||||
|
echo "$_desc failed (attempt $_i/3), retrying in 30s..." >&2
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
|
echo "$_desc failed after 3 attempts" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# pmbootstrap swallows its subcommands' stderr into its own log; surface it
|
||||||
|
# whenever this script dies so failures are diagnosable from CI output alone
|
||||||
|
trap 'rc=$?; if [ $rc -ne 0 ]; then
|
||||||
|
echo "=== build-package.sh failed (exit $rc); pmbootstrap log tail ==="
|
||||||
|
tail -60 "$HOME/.local/var/pmbootstrap/log.txt" 2>/dev/null || true
|
||||||
|
fi' EXIT
|
||||||
|
|
||||||
|
# --- pmaports with our aport dropped in; source tarball from this checkout
|
||||||
|
# (the Forgejo instance serves no source archives, so git-archive it)
|
||||||
|
WORK=${IMSD_PKG_WORK:-$HOME/imsd-pkg-work}
|
||||||
|
rm -rf "$WORK"
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
clone_retry "$WORK/pmaports" -q --depth=1 "$PMAPORTS_REPO"
|
||||||
|
mkdir -p "$WORK/pmaports/modem"
|
||||||
|
cp -r "$SRC/packaging/aport" "$WORK/pmaports/modem/imsd"
|
||||||
|
COMMIT=$(git -C "$SRC" rev-parse --short HEAD)
|
||||||
|
git -C "$SRC" archive --prefix=imsd/ \
|
||||||
|
-o "$WORK/pmaports/modem/imsd/imsd-$COMMIT.tar.gz" HEAD
|
||||||
|
sed -i "s/^_commit=.*/_commit=\"$COMMIT\"/" "$WORK/pmaports/modem/imsd/APKBUILD"
|
||||||
|
|
||||||
|
# --- configure pmbootstrap (config written directly; 'init' is interactive)
|
||||||
|
WORKDIR="$HOME/.local/var/pmbootstrap"
|
||||||
|
mkdir -p "$WORKDIR/cache_git"
|
||||||
|
python3 -c "import pmb.config; print(pmb.config.work_version)" > "$WORKDIR/version"
|
||||||
|
mkdir -p "$HOME/.config"
|
||||||
|
cat > "$HOME/.config/pmbootstrap_v3.cfg" <<EOF
|
||||||
|
[pmbootstrap]
|
||||||
|
aports = $WORK/pmaports
|
||||||
|
device = fairphone-fp6
|
||||||
|
ui = console
|
||||||
|
systemd = always
|
||||||
|
EOF
|
||||||
|
|
||||||
|
NOCROSS=${IMSD_NO_CROSSDIRECT:+--no-cross}
|
||||||
|
|
||||||
|
pmbootstrap checksum imsd
|
||||||
|
retry "build imsd" pmbootstrap $NOCROSS build --arch aarch64 imsd
|
||||||
|
|
||||||
|
echo "=== built packages ==="
|
||||||
|
ls -la "$WORKDIR"/packages/*/aarch64/imsd*.apk
|
||||||
Loading…
Reference in a new issue