diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml index 6c18221..cd569f7 100644 --- a/.forgejo/workflows/package.yml +++ b/.forgejo/workflows/package.yml @@ -7,9 +7,10 @@ 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. This needs no -# privileged runner: it runs in an alpine:edge container on the ordinary -# arch-latest runner. +# 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. # # Release gating is the version: pkgver comes from interfaces/Fingerprintd.cppm, # the registry answers 409 for an already-published version, and the publish @@ -27,7 +28,7 @@ on: jobs: package: - runs-on: arch-latest + runs-on: flatpak container: image: alpine:edge timeout-minutes: 90 diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..7a62ac3 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1,2 @@ +# Read by Forgejo for the repository's Donate button. +custom: 'https://catcrafts.net/shop/donation' diff --git a/README.md b/README.md index d15276d..b64ff30 100644 --- a/README.md +++ b/README.md @@ -8,24 +8,6 @@ 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 @@ -56,55 +38,62 @@ 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. +GPL-3.0-only - see [LICENSE](LICENSE). + +## Funding + +https://catcrafts.net/shop/donation - see [FUNDING](FUNDING.yml) ## Copyright -Copyright (C) 2026 Catcrafts® catcrafts.net \ No newline at end of file +Copyright (C) 2026 Catcrafts® [catcrafts.net](https://catcrafts.net/) diff --git a/packaging/build-package.sh b/packaging/build-package.sh index 41e6d06..fe03746 100755 --- a/packaging/build-package.sh +++ b/packaging/build-package.sh @@ -5,44 +5,28 @@ # (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 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. +# 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. # # 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 -# 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} +# 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} 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 @@ -54,18 +38,43 @@ 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: 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" +# --- 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 # --- 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 3b52593..c08cbef 100755 --- a/packaging/make-sysroot.sh +++ b/packaging/make-sysroot.sh @@ -23,9 +23,14 @@ 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//. +# /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. # glib-dev is for the GDBus bus layer (net.reactivated.Fprint). -PKGS="musl-dev libc++ libc++-dev glib-dev compiler-rt llvm-libunwind-dev gcc" +PKGS="musl-dev libc++ libc++-dev llvm-runtimes glib-dev compiler-rt llvm-libunwind-dev gcc" work=$(mktemp -d) trap 'rm -rf "$work"' EXIT @@ -48,6 +53,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 $PKGS + add --upgrade $PKGS echo ">> done: $SYSROOT"