2026-08-08 15:42:45 +02:00
|
|
|
name: image
|
|
|
|
|
|
2026-09-20 16:58:49 +02:00
|
|
|
# Builds the flashable postmarketOS images for the Fairphone 6 (kernel from
|
|
|
|
|
# milos-linux combined-stable + imsd) — Plasma Mobile (fp6-img.tar.xz) and
|
|
|
|
|
# Phosh (fp6-img-phosh.tar.xz) — and publishes both as the rolling 'latest'
|
|
|
|
|
# release.
|
2026-08-08 15:42:45 +02:00
|
|
|
#
|
|
|
|
|
# Runs on the dedicated privileged runner label "pmos" (loop devices for
|
|
|
|
|
# pmbootstrap install, qemu-user binfmt on the host for the aarch64 chroots).
|
|
|
|
|
# Until that runner is registered, dispatched runs will sit queued.
|
|
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
workflow_dispatch:
|
2026-08-08 16:20:02 +02:00
|
|
|
# push trigger is temporary, for pipeline bring-up; narrow to
|
|
|
|
|
# workflow_dispatch + a nightly schedule once the pipeline is green:
|
|
|
|
|
push:
|
|
|
|
|
branches: [main]
|
2026-08-08 15:42:45 +02:00
|
|
|
# schedule:
|
|
|
|
|
# - cron: '30 3 * * *'
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
image:
|
|
|
|
|
runs-on: pmos
|
|
|
|
|
timeout-minutes: 420
|
|
|
|
|
steps:
|
2026-08-08 21:52:46 +02:00
|
|
|
# actions/checkout & friends are Node actions; bare alpine has no node.
|
2026-08-09 14:58:41 +02:00
|
|
|
# bash, curl, jq: required by the forgejo-release composite action —
|
|
|
|
|
# its setup_api falls back to apt-get when jq/curl are missing, which
|
|
|
|
|
# exits 127 on alpine.
|
2026-08-08 16:21:47 +02:00
|
|
|
- name: Provision job container
|
2026-08-09 14:58:41 +02:00
|
|
|
run: apk add -q nodejs git bash curl jq
|
2026-08-08 16:21:47 +02:00
|
|
|
|
2026-08-08 15:42:45 +02:00
|
|
|
- name: Checkout
|
|
|
|
|
uses: actions/checkout@v4
|
|
|
|
|
with:
|
|
|
|
|
persist-credentials: true
|
|
|
|
|
|
2026-09-20 16:58:49 +02:00
|
|
|
- name: Build image (Plasma Mobile)
|
2026-08-08 15:42:45 +02:00
|
|
|
run: ./build.sh
|
|
|
|
|
|
2026-09-20 16:58:49 +02:00
|
|
|
# Second UI from the same package builds (the kernel and the aports
|
|
|
|
|
# above are already current in pmbootstrap's work dir, so this is the
|
|
|
|
|
# install + export only): fp6-img-phosh.tar.xz next to fp6-img.tar.xz
|
|
|
|
|
# in the same release.
|
|
|
|
|
- name: Build image (Phosh)
|
|
|
|
|
run: FP6IMG_UI=phosh FP6IMG_KEEP_DIST=1 ./build.sh
|
|
|
|
|
|
2026-08-08 15:42:45 +02:00
|
|
|
- name: Upload artifacts
|
|
|
|
|
uses: actions/upload-artifact@v3
|
|
|
|
|
with:
|
|
|
|
|
name: fp6-image-${{ github.sha }}
|
|
|
|
|
path: dist/*
|
|
|
|
|
if-no-files-found: error
|
|
|
|
|
|
drop the libqmi fork: Alpine's snapshot is past the !470 merge
Alpine's community/libqmi is at upstream 1c972b8a (2026-10-05), 23 commits
past the 30f3e998 this fork pinned "just past the !470 merge", with the
qmi-glib 1.39.1 the GNSS patches in modemmanager need. No patch was left;
the fork's one remaining addition, the FP6-tuned build (-march/-mtune,
whose gain was never measured), goes with it. build.sh loses its tarball,
checksum and build steps.
The registry still serves the fork (1.39.1_git20260808-r100 and -r101),
and that version outranks Alpine's 1.38.0_git* line, so with the aport
alone installed phones keep the fork and a fresh image takes it back at
its first 'apk upgrade': the fork's packages are deleted from the
registry with this push. Fresh images then install Alpine's libqmi and
keep it. Installed phones keep the fork until 'apk upgrade -a' downgrades
them to Alpine's or Alpine's version passes 1.39.1; the new modemmanager
works on either (verified on the FP6 with both).
pmbootstrap keeps a local package repository in its work dir and consults
it before every other repository. Whether that dir survives from one CI
run to the next is not established (run 60 rebuilt libqmi, modemmanager
and libcamera at the versions run 58 had built, which says it did not),
but nothing guarantees it either way, so build.sh deletes any
libqmi/qmi-utils apks found there before building, re-indexes the
repository from the files present and fails the run if any are left; the
publish step skips libqmi-*/qmi-utils-* as the backstop. pmbootstrap's
'zap --pkgs-local-mismatch' cannot do this: with systemd selected it looks
at packages/systemd-edge, not at the packages/edge the builds land in.
2026-10-12 00:40:13 +02:00
|
|
|
# Ship every locally built apk (kernel, modemmanager, callaudioshim,
|
|
|
|
|
# audio files, ...) to the Forgejo Alpine registry, so
|
2026-08-08 22:33:45 +02:00
|
|
|
# installed systems get updates via 'apk upgrade' instead of losing
|
|
|
|
|
# the FP6 patches to the next upstream version bump. Requires the
|
|
|
|
|
# PACKAGE_TOKEN repo secret (catbot account, package:write scope);
|
|
|
|
|
# skips quietly until it exists. 409 = same version already published.
|
Install fingerprintd from the registry, so the image unlocks with a finger
The daemon's own package CI publishes it to the registry the same way imsd's
does, so the image takes it from there: the exact apk a user later gets via
apk upgrade, sha256-pinned, re-signed for the chroot. Section 3b now fetches
both sets, and every fetched file must have a pin -- the check used to be
`grep . | sha256sum -c`, which an empty pin list would have sailed through
with nothing checked.
Three apks: the daemon, its systemd units, and the session agent, which does
nothing until a user writes ~/.config/fingerprintd/fingers.conf. The daemon
needs the kernel aport's CONFIG_QCOMTEE=m (pkgrel 101) and fp6-vendor-blobs
1-r2's mbn directive to reassemble the trustlet, both built in this run;
0.2.3 says >=1-r2 so a mismatched pair is refused rather than installed.
The CI publish step skips fingerprintd-* like imsd-*: registry-sourced, not
ours to republish. README: fingerprint in the list, and the two things a user
will otherwise report as a dead sensor -- the lock screen listens for 60
seconds after it appears, and a held press is what the matcher was measured
on -- plus the untested question of stock Android's own fingerprints after
using this.
Verified on the dev phone (fp6 repo journal/fingerprint/, 2026-09-05): the
registry 0.2.2 package enrols through Plasma's Users page and unlocks the
lock screen; 0.2.3 differs by the dependency and a post-upgrade restart. The
image build itself, with the fprintd purge inside the chroot, runs first in
CI.
2026-09-05 20:31:37 +02:00
|
|
|
# imsd and fingerprintd are skipped: build.sh 3b took them FROM the
|
|
|
|
|
# registry (re-signed for the chroot), so they are not ours to publish.
|
drop the libqmi fork: Alpine's snapshot is past the !470 merge
Alpine's community/libqmi is at upstream 1c972b8a (2026-10-05), 23 commits
past the 30f3e998 this fork pinned "just past the !470 merge", with the
qmi-glib 1.39.1 the GNSS patches in modemmanager need. No patch was left;
the fork's one remaining addition, the FP6-tuned build (-march/-mtune,
whose gain was never measured), goes with it. build.sh loses its tarball,
checksum and build steps.
The registry still serves the fork (1.39.1_git20260808-r100 and -r101),
and that version outranks Alpine's 1.38.0_git* line, so with the aport
alone installed phones keep the fork and a fresh image takes it back at
its first 'apk upgrade': the fork's packages are deleted from the
registry with this push. Fresh images then install Alpine's libqmi and
keep it. Installed phones keep the fork until 'apk upgrade -a' downgrades
them to Alpine's or Alpine's version passes 1.39.1; the new modemmanager
works on either (verified on the FP6 with both).
pmbootstrap keeps a local package repository in its work dir and consults
it before every other repository. Whether that dir survives from one CI
run to the next is not established (run 60 rebuilt libqmi, modemmanager
and libcamera at the versions run 58 had built, which says it did not),
but nothing guarantees it either way, so build.sh deletes any
libqmi/qmi-utils apks found there before building, re-indexes the
repository from the files present and fails the run if any are left; the
publish step skips libqmi-*/qmi-utils-* as the backstop. pmbootstrap's
'zap --pkgs-local-mismatch' cannot do this: with systemd selected it looks
at packages/systemd-edge, not at the packages/edge the builds land in.
2026-10-12 00:40:13 +02:00
|
|
|
# libqmi is skipped too: its fork was retired and deleted from the
|
|
|
|
|
# registry (2026-10-12); should a leftover apk ever sit in the runner's
|
|
|
|
|
# package dir, it must not bring the fork back.
|
2026-08-08 22:33:45 +02:00
|
|
|
- name: Publish packages 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
|
|
|
|
|
apk add -q curl
|
|
|
|
|
found=0
|
|
|
|
|
for f in /home/build/.local/var/pmbootstrap/packages/*/aarch64/*.apk; do
|
|
|
|
|
[ -e "$f" ] || continue
|
2026-09-02 16:11:50 +02:00
|
|
|
case "$(basename "$f")" in
|
Install fingerprintd from the registry, so the image unlocks with a finger
The daemon's own package CI publishes it to the registry the same way imsd's
does, so the image takes it from there: the exact apk a user later gets via
apk upgrade, sha256-pinned, re-signed for the chroot. Section 3b now fetches
both sets, and every fetched file must have a pin -- the check used to be
`grep . | sha256sum -c`, which an empty pin list would have sailed through
with nothing checked.
Three apks: the daemon, its systemd units, and the session agent, which does
nothing until a user writes ~/.config/fingerprintd/fingers.conf. The daemon
needs the kernel aport's CONFIG_QCOMTEE=m (pkgrel 101) and fp6-vendor-blobs
1-r2's mbn directive to reassemble the trustlet, both built in this run;
0.2.3 says >=1-r2 so a mismatched pair is refused rather than installed.
The CI publish step skips fingerprintd-* like imsd-*: registry-sourced, not
ours to republish. README: fingerprint in the list, and the two things a user
will otherwise report as a dead sensor -- the lock screen listens for 60
seconds after it appears, and a held press is what the matcher was measured
on -- plus the untested question of stock Android's own fingerprints after
using this.
Verified on the dev phone (fp6 repo journal/fingerprint/, 2026-09-05): the
registry 0.2.2 package enrols through Plasma's Users page and unlocks the
lock screen; 0.2.3 differs by the dependency and a post-upgrade restart. The
image build itself, with the fprintd purge inside the chroot, runs first in
CI.
2026-09-05 20:31:37 +02:00
|
|
|
imsd-*|fingerprintd-*) echo "registry-sourced, not republished: $(basename "$f")"; continue ;;
|
drop the libqmi fork: Alpine's snapshot is past the !470 merge
Alpine's community/libqmi is at upstream 1c972b8a (2026-10-05), 23 commits
past the 30f3e998 this fork pinned "just past the !470 merge", with the
qmi-glib 1.39.1 the GNSS patches in modemmanager need. No patch was left;
the fork's one remaining addition, the FP6-tuned build (-march/-mtune,
whose gain was never measured), goes with it. build.sh loses its tarball,
checksum and build steps.
The registry still serves the fork (1.39.1_git20260808-r100 and -r101),
and that version outranks Alpine's 1.38.0_git* line, so with the aport
alone installed phones keep the fork and a fresh image takes it back at
its first 'apk upgrade': the fork's packages are deleted from the
registry with this push. Fresh images then install Alpine's libqmi and
keep it. Installed phones keep the fork until 'apk upgrade -a' downgrades
them to Alpine's or Alpine's version passes 1.39.1; the new modemmanager
works on either (verified on the FP6 with both).
pmbootstrap keeps a local package repository in its work dir and consults
it before every other repository. Whether that dir survives from one CI
run to the next is not established (run 60 rebuilt libqmi, modemmanager
and libcamera at the versions run 58 had built, which says it did not),
but nothing guarantees it either way, so build.sh deletes any
libqmi/qmi-utils apks found there before building, re-indexes the
repository from the files present and fails the run if any are left; the
publish step skips libqmi-*/qmi-utils-* as the backstop. pmbootstrap's
'zap --pkgs-local-mismatch' cannot do this: with systemd selected it looks
at packages/systemd-edge, not at the packages/edge the builds land in.
2026-10-12 00:40:13 +02:00
|
|
|
libqmi-*|qmi-utils-*) echo "retired fork, not republished: $(basename "$f")"; continue ;;
|
2026-09-02 16:11:50 +02:00
|
|
|
esac
|
2026-08-08 22:33:45 +02:00
|
|
|
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; }
|
|
|
|
|
|
2026-08-08 15:42:45 +02:00
|
|
|
- name: Update rolling 'latest' tag
|
|
|
|
|
run: |
|
2026-08-08 16:30:19 +02:00
|
|
|
git config --global --add safe.directory "$PWD"
|
2026-08-08 15:42:45 +02:00
|
|
|
git config user.email "ci@catcrafts.net"
|
|
|
|
|
git config user.name "fp6-img CI"
|
|
|
|
|
git tag -f latest
|
|
|
|
|
git push origin latest --force
|
|
|
|
|
|
|
|
|
|
- name: Publish rolling 'latest' release
|
|
|
|
|
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
|
|
|
|
with:
|
|
|
|
|
direction: upload
|
|
|
|
|
url: ${{ github.server_url }}
|
|
|
|
|
repo: ${{ github.repository }}
|
|
|
|
|
tag: latest
|
2026-08-18 15:20:55 +02:00
|
|
|
title: Latest FP6 postmarketOS image
|
2026-08-08 15:42:45 +02:00
|
|
|
prerelease: true
|
|
|
|
|
override: true
|
|
|
|
|
release-dir: dist
|
|
|
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
2026-08-10 16:22:20 +02:00
|
|
|
# Empty, NOT 'false': the action guards its release-notes-assistant
|
|
|
|
|
# cache step with `if: ${{ inputs.release-notes-assistant }}`, where
|
|
|
|
|
# the default string 'false' is truthy. That step then can't reach the
|
|
|
|
|
# runner's cache server (it advertises the host's public IP) and burns
|
|
|
|
|
# ~4m40s per run on a connect timeout before reporting a miss.
|
|
|
|
|
release-notes-assistant: ''
|