Compare commits

..
5 changed files with 94 additions and 100 deletions

View file

@ -7,10 +7,9 @@ name: package
# #
# Build: crafter-build cross-compiles against an Alpine aarch64 sysroot # Build: crafter-build cross-compiles against an Alpine aarch64 sysroot
# (packaging/build-package.sh — the README's "Cross-compiling" flow), the test # (packaging/build-package.sh — the README's "Cross-compiling" flow), the test
# suite runs natively, and packaging/APKBUILD wraps the result. crafter-build # suite runs natively, and packaging/APKBUILD wraps the result. This needs no
# is the Crafter.Build Flatpak, and bubblewrap inside the job container needs # privileged runner: it runs in an alpine:edge container on the ordinary
# a privileged runner: the job runs on the `flatpak` label (the Catcrafts # arch-latest runner.
# organisation's privileged runner), in an alpine:edge container.
# #
# Release gating is the version: pkgver comes from interfaces/Fingerprintd.cppm, # Release gating is the version: pkgver comes from interfaces/Fingerprintd.cppm,
# the registry answers 409 for an already-published version, and the publish # the registry answers 409 for an already-published version, and the publish
@ -28,7 +27,7 @@ on:
jobs: jobs:
package: package:
runs-on: flatpak runs-on: arch-latest
container: container:
image: alpine:edge image: alpine:edge
timeout-minutes: 90 timeout-minutes: 90

View file

@ -1,2 +0,0 @@
# Read by Forgejo for the repository's Donate button.
custom: 'https://catcrafts.net/shop/donation'

107
README.md
View file

@ -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 dev box with no phone, no TEE and no sensor. The daemon shell holds everything
that touches hardware. that touches hardware.
## Build
```sh
crafter-build # bin/fingerprintd-<target>-<march>/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-enroll -f right-index-finger user ten stages, enroll-completed
fprintd-list user - #0: right-index-finger 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` announced as `FingerMatched` for the session agent. `--standby-unlock=off`
disables it. 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
# <finger> [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 ## Runtime dependencies
The `focal64` trustlet is proprietary and is not in this repo. It is 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 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. `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 ## License
GPL-3.0-only - see [LICENSE](LICENSE). GPL-3.0-only, see LICENSE.
## Funding
https://catcrafts.net/shop/donation - see [FUNDING](FUNDING.yml)
## Copyright ## Copyright
Copyright (C) 2026 Catcrafts® [catcrafts.net](https://catcrafts.net/) Copyright (C) 2026 Catcrafts® catcrafts.net

View file

@ -5,28 +5,44 @@
# (the README's "Cross-compiling" flow), run the test suite natively, and # (the README's "Cross-compiling" flow), run the test suite natively, and
# package the result via packaging/APKBUILD. Expects an x86_64 Alpine # package the result via packaging/APKBUILD. Expects an x86_64 Alpine
# environment with root — the workflow runs it in an alpine:edge job # environment with root — the workflow runs it in an alpine:edge job
# container on the privileged `flatpak` runner: crafter-build is the # container on an ordinary runner. Root only installs packages and hands off
# Crafter.Build Flatpak, and bubblewrap inside the container needs the # to a scratch user: the sysroot is built with apk.static --usermode (which
# privilege. Root only installs packages and hands off to a scratch user: # refuses root) and abuild wants a user too.
# 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; # Built packages land in /home/build/.local/share/abuild/*/aarch64/fingerprintd*.apk;
# the workflow's publish step uploads them to the Forgejo Alpine registry. # the workflow's publish step uploads them to the Forgejo Alpine registry.
set -eu set -eu
# crafter-build is the Crafter.Build Flatpak: it carries its own clang, lld # The musl build of crafter-build (Crafter.Build CI's release-musl job): this
# and libc++, and its SDK has glib, so nothing the daemon's build compiles # container is Alpine, and the glibc launcher cannot run on musl. v2 = SSE4.2
# with is installed here. `stable` is the branch Crafter.Build publishes from # baseline: the CI box is an Intel N5105 (no AVX). Overridable for local
# its release tags (`beta` follows every push to its master), so the # rehearsals (file:// works).
# toolchain of a release moves only with a release of the toolchain. The CRAFTER_URL=${CRAFTER_URL:-https://forgejo.catcrafts.net/Catcrafts/Crafter.Build/releases/download/latest/crafter-build-linux-x86_64-musl-v2.tar.gz}
# 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) 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 <description> <cmd...> retry() { # retry <description> <cmd...>
_desc=$1; shift _desc=$1; shift
for _i in 1 2 3; do for _i in 1 2 3; do
@ -38,43 +54,18 @@ retry() { # retry <description> <cmd...>
return 1 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 # interfaces/Fingerprintd.cppm is the version's single source of truth (same
# derivation as make-bin-tarball.sh) # derivation as make-bin-tarball.sh)
VER=$(sed -n 's/.*Version = "\(.*\)".*/\1/p' "$SRC/interfaces/Fingerprintd.cppm") VER=$(sed -n 's/.*Version = "\(.*\)".*/\1/p' "$SRC/interfaces/Fingerprintd.cppm")
[ -n "$VER" ] || { echo "cannot read Version from interfaces/Fingerprintd.cppm" >&2; exit 1; } [ -n "$VER" ] || { echo "cannot read Version from interfaces/Fingerprintd.cppm" >&2; exit 1; }
echo ">> packaging fingerprintd $VER" echo ">> packaging fingerprintd $VER"
# --- crafter-build: the Flatpak, under the name the rest of this file uses. # --- crafter-build: static launcher from the rolling release
# The build log names the build it ran with. mkdir -p "$HOME/crafter-build"
mkdir -p "$HOME/bin" retry "fetch crafter-build" \
printf '#!/bin/sh\nexec flatpak run %s "$@"\n' "$CRAFTER_REF" > "$HOME/bin/crafter-build" sh -c "curl -fsSL '$CRAFTER_URL' | tar -xz -C '$HOME/crafter-build'"
chmod +x "$HOME/bin/crafter-build" PATH="$HOME/crafter-build/bin:$PATH"
PATH="$HOME/bin:$PATH" export CRAFTER_BUILD_HOME="$HOME/crafter-build/share/crafter-build"
flatpak info "$CRAFTER_REF" | sed -n 's/^ *\(Version\|Commit\|Date\): */crafter-build \1: /p'
crafter-build --version
# --- aarch64 Alpine sysroot (unprivileged: apk.static --usermode) # --- aarch64 Alpine sysroot (unprivileged: apk.static --usermode)
SYSROOT="$HOME/.cache/fingerprintd/sysroot-aarch64-alpine" SYSROOT="$HOME/.cache/fingerprintd/sysroot-aarch64-alpine"

View file

@ -23,14 +23,9 @@ MIRROR="${MIRROR:-https://dl-cdn.alpinelinux.org/alpine/edge}"
ARCH=aarch64 ARCH=aarch64
# gcc is in the list for its crtbeginS.o/libgcc: clang's driver for # 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 # *-alpine-linux-musl links against the GCC runtime it detects under
# <sysroot>/usr/lib/gcc/<triple>/. llvm-runtimes is in it for the std module # <sysroot>/usr/lib/gcc/<triple>/.
# 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). # 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) work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT trap 'rm -rf "$work"' EXIT
@ -53,6 +48,6 @@ mkdir -p "$SYSROOT"
--arch "$ARCH" --root "$SYSROOT" --initdb --no-scripts --no-cache \ --arch "$ARCH" --root "$SYSROOT" --initdb --no-scripts --no-cache \
--usermode --keys-dir "$work/usr/share/apk/keys/$ARCH" \ --usermode --keys-dir "$work/usr/share/apk/keys/$ARCH" \
-X "$MIRROR/main" -X "$MIRROR/community" \ -X "$MIRROR/main" -X "$MIRROR/community" \
add --upgrade $PKGS add $PKGS
echo ">> done: $SYSROOT" echo ">> done: $SYSROOT"