diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml index cd569f7..6c18221 100644 --- a/.forgejo/workflows/package.yml +++ b/.forgejo/workflows/package.yml @@ -7,10 +7,9 @@ name: package # # Build: crafter-build cross-compiles against an Alpine aarch64 sysroot # (packaging/build-package.sh — the README's "Cross-compiling" flow), the test -# suite runs natively, and packaging/APKBUILD wraps the result. crafter-build -# is the Crafter.Build Flatpak, and bubblewrap inside the job container needs -# a privileged runner: the job runs on the `flatpak` label (the Catcrafts -# organisation's privileged runner), in an alpine:edge container. +# suite runs natively, and packaging/APKBUILD wraps the result. This needs no +# privileged runner: it runs in an alpine:edge container on the ordinary +# arch-latest runner. # # Release gating is the version: pkgver comes from interfaces/Fingerprintd.cppm, # the registry answers 409 for an already-published version, and the publish @@ -28,7 +27,7 @@ on: jobs: package: - runs-on: flatpak + runs-on: arch-latest container: image: alpine:edge timeout-minutes: 90 diff --git a/FUNDING.yml b/FUNDING.yml deleted file mode 100644 index 7a62ac3..0000000 --- a/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -# Read by Forgejo for the repository's Donate button. -custom: 'https://catcrafts.net/shop/donation' diff --git a/README.md b/README.md index b64ff30..d15276d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,24 @@ recovered by reverse-engineering, so all of it is pinned by tests that run on a dev box with no phone, no TEE and no sensor. The daemon shell holds everything that touches hardware. +## Build + +```sh +crafter-build # bin/fingerprintd--/fingerprintd +crafter-build test # the unit suites +``` + +Cross-compiling for the phone: + +```sh +packaging/make-sysroot.sh +crafter-build -- --target=aarch64-alpine-linux-musl --sysroot=~/.cache/fingerprintd/sysroot-aarch64-alpine --march=armv8.6-a+fp16fml+aes+sha3+sm4 --mtune=cortex-a720 +crafter-build test --target=aarch64-alpine-linux-musl --sysroot=~/.cache/fingerprintd/sysroot-aarch64-alpine --march=armv8.6-a+fp16fml+aes+sha3+sm4 --mtune=cortex-a720 +``` + +Verified on the device: all five suites pass cross-built and run on the phone +itself, not only under emulation. + ``` fprintd-enroll -f right-index-finger user ten stages, enroll-completed fprintd-list user - #0: right-index-finger @@ -38,62 +56,55 @@ Two routes, both on by default: announced as `FingerMatched` for the session agent. `--standby-unlock=off` disables it. -## Per-finger macros - -The trustlet reports *which* finger matched, so a finger can mean more than -"it was you". Two files, in two trust domains: - -- **`~/.config/fingerprintd/fingers.conf`** runs a command as you, in your - session. This is how a finger opens an application. -- **`/etc/fingerprintd/actions.conf`** runs a command as root, and can mark a - finger as one that never unlocks. - -### In your session: `fingers.conf` - -``` -# ~/.config/fingerprintd/fingers.conf -right-ring-finger kde-open camera -left-index-finger plasma-settings -left-thumb sh -c 'notify-send "hello from a finger"' -``` - -- using these requires the fingerprintd-agent service to be running. - -### As root: `actions.conf` - -``` -# /etc/fingerprintd/actions.conf -# [no-unlock] [absolute command...] -right-ring-finger /usr/local/bin/toggle-something -left-little-finger no-unlock /etc/fingerprintd/panic.sh -left-thumb no-unlock -``` - -- `no-unlock`: the finger matches and the client is told it did not. PAM - sees `verify-no-match`, the standby unlock stays locked. The command, if - there is one, runs regardless. -- the command: an absolute path, run by root through `/bin/sh -c` with a - fixed `PATH` and `FINGERPRINTD_FINGER` set to the finger's name, detached - from the daemon with its stdio on `/dev/null`, so it may block or reboot - the phone without stalling the sensor. -- a line needs at least one of the two, and a finger gets at most one line. -- editing this file requires a restart of the fingerprintd service before changes take effect. - - ## Runtime dependencies The `focal64` trustlet is proprietary and is not in this repo. It is extracted from the device's own stock Android partition on first boot by the `fp6-vendor-blobs` mechanism, the same way other firmware in fp6-img is. +## Releases + +Every push to `main` is built and tested by CI, which then publishes the +apks to the Forgejo Alpine registry that installed phones already point at. +CI treats the registry's "already published" answer as nothing to do, so a +push publishes only a package version new to the registry. The package +version is `pkgver-rpkgrel`: `pkgver` is the `Version` constant in +`interfaces/Fingerprintd.cppm`, `pkgrel` is the APKBUILD's and stays at 0. +An installed phone picks a published version up with plain `apk upgrade`. + +From 0.2.6 on, a release is a range of commits ending in a bump commit +titled `fingerprintd X.Y.Z` that changes only the version: the constant and +the APKBUILD's `pkgver`. Each release is tagged `vX.Y.Z` on its bump commit +(signed, annotated) with the release notes as the tag message, and +published as a Forgejo release of the same name and text. No version +before 0.2.6 has a tag or release notes. The notes are written for the +people whose phones will upgrade: + +``` +fingerprintd X.Y.Z + +## New + +- **What a user can now do.** One bullet per change, the most important + first, with anything the user has to do inside the bullet. + +## Fixes + +- **What went wrong, in the user's terms.** What they saw, what changes, + what to do. +``` + +A section with nothing in it is left out, and so is behaviour the release +does not change. Versioning, from 0.2.6 on, is `MAJOR.MINOR.PATCH`: the +patch number counts releases, fixes and features alike; the minor number +moves when a release changes what a user can rely on the daemon for; the +major number moves when a release changes what an installed phone does +without being asked. + ## License -GPL-3.0-only - see [LICENSE](LICENSE). - -## Funding - -https://catcrafts.net/shop/donation - see [FUNDING](FUNDING.yml) +GPL-3.0-only, see LICENSE. ## Copyright -Copyright (C) 2026 Catcrafts® [catcrafts.net](https://catcrafts.net/) +Copyright (C) 2026 Catcrafts® catcrafts.net \ No newline at end of file diff --git a/packaging/build-package.sh b/packaging/build-package.sh index fe03746..41e6d06 100755 --- a/packaging/build-package.sh +++ b/packaging/build-package.sh @@ -5,28 +5,44 @@ # (the README's "Cross-compiling" flow), run the test suite natively, and # package the result via packaging/APKBUILD. Expects an x86_64 Alpine # environment with root — the workflow runs it in an alpine:edge job -# container on the privileged `flatpak` runner: crafter-build is the -# Crafter.Build Flatpak, and bubblewrap inside the container needs the -# privilege. Root only installs packages and hands off to a scratch user: -# the sysroot is built with apk.static --usermode (which refuses root) and -# abuild wants a user too. +# container on an ordinary runner. Root only installs packages and hands off +# to a scratch user: the sysroot is built with apk.static --usermode (which +# refuses root) and abuild wants a user too. # # Built packages land in /home/build/.local/share/abuild/*/aarch64/fingerprintd*.apk; # the workflow's publish step uploads them to the Forgejo Alpine registry. set -eu -# crafter-build is the Crafter.Build Flatpak: it carries its own clang, lld -# and libc++, and its SDK has glib, so nothing the daemon's build compiles -# with is installed here. `stable` is the branch Crafter.Build publishes from -# its release tags (`beta` follows every push to its master), so the -# toolchain of a release moves only with a release of the toolchain. The -# branch is overridable for a rehearsal against another one. -CRAFTER_REMOTE=${CRAFTER_REMOTE:-https://flatpak.catcrafts.net/catcrafts.flatpakrepo} -CRAFTER_REF=${CRAFTER_REF:-net.catcrafts.CrafterBuild//stable} +# The musl build of crafter-build (Crafter.Build CI's release-musl job): this +# container is Alpine, and the glibc launcher cannot run on musl. v2 = SSE4.2 +# baseline: the CI box is an Intel N5105 (no AVX). Overridable for local +# rehearsals (file:// works). +CRAFTER_URL=${CRAFTER_URL:-https://forgejo.catcrafts.net/Catcrafts/Crafter.Build/releases/download/latest/crafter-build-linux-x86_64-musl-v2.tar.gz} SRC=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd) +# clang cross-targets aarch64 natively and the target's libc++/glib come from +# the sysroot; llvm-runtimes/libc++-dev/glib-dev here serve the NATIVE +# test-suite run. build-base = Alpine's standard build environment (the one +# abuild implies): binutils' ld/ar for clang's default link driver, gcc's +# libgcc_s/crt objects the musl clang driver links against. +if [ "$(id -u)" = 0 ]; then + apk add -q git curl tar clang lld llvm llvm-runtimes libc++-dev llvm-libunwind-dev glib-dev \ + build-base abuild sudo + id build >/dev/null 2>&1 || adduser -D build + addgroup build abuild 2>/dev/null || true + echo 'build ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/build + # abuild in cross mode strips with $CHOST-strip; llvm-strip handles any + # ELF arch, so give it that name + ln -sf "$(command -v llvm-strip)" /usr/local/bin/aarch64-alpine-linux-musl-strip + # the CI checkout arrives root-owned; crafter-build writes bin/ into it + chown -R build "$SRC" + # -l: a login shell, so HOME really is /home/build (abuild keys + output); + # it scrubs the environment, so carry the one knob that matters across + exec su -l build -c "CRAFTER_URL='${CRAFTER_URL:-}' sh -eu '$SRC/packaging/build-package.sh'" +fi + retry() { # retry _desc=$1; shift for _i in 1 2 3; do @@ -38,43 +54,18 @@ retry() { # retry return 1 } -# clang and llvm-ar here are for libqcomtee: make-libqcomtee.sh compiles it -# with the host clang against the sysroot (C, objects and an archive, no -# link). llvm also has llvm-strip, which abuild in cross mode strips with. -if [ "$(id -u)" = 0 ]; then - apk add -q git curl tar flatpak clang llvm abuild sudo - # the Flatpak as a system installation the build user runs; its SDK - # (org.freedesktop.Sdk) comes from Flathub - flatpak remote-add --if-not-exists catcrafts "$CRAFTER_REMOTE" - flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo - retry "install crafter-build" flatpak install -y --noninteractive catcrafts "$CRAFTER_REF" - id build >/dev/null 2>&1 || adduser -D build - addgroup build abuild 2>/dev/null || true - echo 'build ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/build - # abuild in cross mode strips with $CHOST-strip; llvm-strip handles any - # ELF arch, so give it that name - ln -sf "$(command -v llvm-strip)" /usr/local/bin/aarch64-alpine-linux-musl-strip - # the CI checkout arrives root-owned; crafter-build writes bin/ into it - chown -R build "$SRC" - # -l: a login shell, so HOME really is /home/build (abuild keys + output); - # it scrubs the environment, so carry the one knob that matters across - exec su -l build -c "CRAFTER_REF='$CRAFTER_REF' sh -eu '$SRC/packaging/build-package.sh'" -fi - # interfaces/Fingerprintd.cppm is the version's single source of truth (same # derivation as make-bin-tarball.sh) VER=$(sed -n 's/.*Version = "\(.*\)".*/\1/p' "$SRC/interfaces/Fingerprintd.cppm") [ -n "$VER" ] || { echo "cannot read Version from interfaces/Fingerprintd.cppm" >&2; exit 1; } echo ">> packaging fingerprintd $VER" -# --- crafter-build: the Flatpak, under the name the rest of this file uses. -# The build log names the build it ran with. -mkdir -p "$HOME/bin" -printf '#!/bin/sh\nexec flatpak run %s "$@"\n' "$CRAFTER_REF" > "$HOME/bin/crafter-build" -chmod +x "$HOME/bin/crafter-build" -PATH="$HOME/bin:$PATH" -flatpak info "$CRAFTER_REF" | sed -n 's/^ *\(Version\|Commit\|Date\): */crafter-build \1: /p' -crafter-build --version +# --- crafter-build: static launcher from the rolling release +mkdir -p "$HOME/crafter-build" +retry "fetch crafter-build" \ + sh -c "curl -fsSL '$CRAFTER_URL' | tar -xz -C '$HOME/crafter-build'" +PATH="$HOME/crafter-build/bin:$PATH" +export CRAFTER_BUILD_HOME="$HOME/crafter-build/share/crafter-build" # --- aarch64 Alpine sysroot (unprivileged: apk.static --usermode) SYSROOT="$HOME/.cache/fingerprintd/sysroot-aarch64-alpine" diff --git a/packaging/make-sysroot.sh b/packaging/make-sysroot.sh index c08cbef..3b52593 100755 --- a/packaging/make-sysroot.sh +++ b/packaging/make-sysroot.sh @@ -23,14 +23,9 @@ MIRROR="${MIRROR:-https://dl-cdn.alpinelinux.org/alpine/edge}" ARCH=aarch64 # gcc is in the list for its crtbeginS.o/libgcc: clang's driver for # *-alpine-linux-musl links against the GCC runtime it detects under -# /usr/lib/gcc//. llvm-runtimes is in it for the std module -# source of this very libc++ (usr/share/libc++/v1/std.cppm; libc++-dev ships -# none): crafter-build compiles the sysroot's own copy first. --upgrade below -# is for a run over an existing sysroot: added alone beside an older libc++, -# a newer llvm-runtimes' source does not compile against its headers -# (imsd, 2026-10-04: 23.1.2 against 22.1.8); upgraded together, it does. +# /usr/lib/gcc//. # glib-dev is for the GDBus bus layer (net.reactivated.Fprint). -PKGS="musl-dev libc++ libc++-dev llvm-runtimes glib-dev compiler-rt llvm-libunwind-dev gcc" +PKGS="musl-dev libc++ libc++-dev glib-dev compiler-rt llvm-libunwind-dev gcc" work=$(mktemp -d) trap 'rm -rf "$work"' EXIT @@ -53,6 +48,6 @@ mkdir -p "$SYSROOT" --arch "$ARCH" --root "$SYSROOT" --initdb --no-scripts --no-cache \ --usermode --keys-dir "$work/usr/share/apk/keys/$ARCH" \ -X "$MIRROR/main" -X "$MIRROR/community" \ - add --upgrade $PKGS + add $PKGS echo ">> done: $SYSROOT"