Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
#!/bin/sh -eu
|
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
|
# CI package build: cross-compile fingerprintd for aarch64 with crafter-build
|
|
|
|
|
# (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
|
packaging, ci: build the apk with the Crafter.Build Flatpak
Crafter.Build's releases carry a Flatpak and nothing else since
2026-10-02 (imsd a625248): the rolling `latest` release's
crafter-build-linux-x86_64-musl-v2.tar.gz that build-package.sh fetched
is gone, so the push of 0.2.6 failed at `fetch crafter-build` (run 10,
2026-10-14, three 404s) and published nothing; the registry still
serves 0.2.5.
The move imsd made on 2026-10-04: install the Flatpak
(net.catcrafts.CrafterBuild//stable from flatpak.catcrafts.net) in the
job container and run it as crafter-build. bubblewrap inside the
container needs a privileged runner, so the job runs on the `flatpak`
label. The host keeps clang and llvm-ar for libqcomtee, which
make-libqcomtee.sh compiles against the sysroot; the daemon's compiler,
lld and libc++ are the Flatpak's, so the host packages that served the
native test-suite run go. The sysroot gains llvm-runtimes, the package
with the std module source of its own libc++ (libc++-dev ships none),
added with --upgrade so a sysroot from before this change comes out
with a matching libc++ and source. The README's Build section carries
the install lines.
Tested: build-package.sh run end to end on this commit in a privileged
alpine:edge container (podman) on the workstation: crafter-build 2.1.0
(clang 23.1.2) from `stable`, the sysroot at libc++ and llvm-runtimes
23.1.3, daemon and agent cross-built, 8 suites passed natively, the
three apks built, the daemon an aarch64 PIE against the sysroot's musl
and libc++ (65 s). The rehearsed copy differs from this commit by the
wrapping of one comment line.
2026-10-14 02:32:30 +02:00
|
|
|
# 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.
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
#
|
|
|
|
|
# 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
|
|
|
|
|
|
packaging, ci: build the apk with the Crafter.Build Flatpak
Crafter.Build's releases carry a Flatpak and nothing else since
2026-10-02 (imsd a625248): the rolling `latest` release's
crafter-build-linux-x86_64-musl-v2.tar.gz that build-package.sh fetched
is gone, so the push of 0.2.6 failed at `fetch crafter-build` (run 10,
2026-10-14, three 404s) and published nothing; the registry still
serves 0.2.5.
The move imsd made on 2026-10-04: install the Flatpak
(net.catcrafts.CrafterBuild//stable from flatpak.catcrafts.net) in the
job container and run it as crafter-build. bubblewrap inside the
container needs a privileged runner, so the job runs on the `flatpak`
label. The host keeps clang and llvm-ar for libqcomtee, which
make-libqcomtee.sh compiles against the sysroot; the daemon's compiler,
lld and libc++ are the Flatpak's, so the host packages that served the
native test-suite run go. The sysroot gains llvm-runtimes, the package
with the std module source of its own libc++ (libc++-dev ships none),
added with --upgrade so a sysroot from before this change comes out
with a matching libc++ and source. The README's Build section carries
the install lines.
Tested: build-package.sh run end to end on this commit in a privileged
alpine:edge container (podman) on the workstation: crafter-build 2.1.0
(clang 23.1.2) from `stable`, the sysroot at libc++ and llvm-runtimes
23.1.3, daemon and agent cross-built, 8 suites passed natively, the
three apks built, the daemon an aarch64 PIE against the sysroot's musl
and libc++ (65 s). The rehearsed copy differs from this commit by the
wrapping of one comment line.
2026-10-14 02:32:30 +02:00
|
|
|
# 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}
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
|
|
|
|
|
SRC=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
|
|
|
|
|
packaging, ci: build the apk with the Crafter.Build Flatpak
Crafter.Build's releases carry a Flatpak and nothing else since
2026-10-02 (imsd a625248): the rolling `latest` release's
crafter-build-linux-x86_64-musl-v2.tar.gz that build-package.sh fetched
is gone, so the push of 0.2.6 failed at `fetch crafter-build` (run 10,
2026-10-14, three 404s) and published nothing; the registry still
serves 0.2.5.
The move imsd made on 2026-10-04: install the Flatpak
(net.catcrafts.CrafterBuild//stable from flatpak.catcrafts.net) in the
job container and run it as crafter-build. bubblewrap inside the
container needs a privileged runner, so the job runs on the `flatpak`
label. The host keeps clang and llvm-ar for libqcomtee, which
make-libqcomtee.sh compiles against the sysroot; the daemon's compiler,
lld and libc++ are the Flatpak's, so the host packages that served the
native test-suite run go. The sysroot gains llvm-runtimes, the package
with the std module source of its own libc++ (libc++-dev ships none),
added with --upgrade so a sysroot from before this change comes out
with a matching libc++ and source. The README's Build section carries
the install lines.
Tested: build-package.sh run end to end on this commit in a privileged
alpine:edge container (podman) on the workstation: crafter-build 2.1.0
(clang 23.1.2) from `stable`, the sysroot at libc++ and llvm-runtimes
23.1.3, daemon and agent cross-built, 8 suites passed natively, the
three apks built, the daemon an aarch64 PIE against the sysroot's musl
and libc++ (65 s). The rehearsed copy differs from this commit by the
wrapping of one comment line.
2026-10-14 02:32:30 +02:00
|
|
|
retry() { # retry <description> <cmd...>
|
|
|
|
|
_desc=$1; shift
|
|
|
|
|
for _i in 1 2 3; do
|
|
|
|
|
"$@" && return 0
|
|
|
|
|
echo "$_desc failed (attempt $_i/3), retrying in 15s..." >&2
|
|
|
|
|
sleep 15
|
|
|
|
|
done
|
|
|
|
|
echo "$_desc failed after 3 attempts" >&2
|
|
|
|
|
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.
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
if [ "$(id -u)" = 0 ]; then
|
packaging, ci: build the apk with the Crafter.Build Flatpak
Crafter.Build's releases carry a Flatpak and nothing else since
2026-10-02 (imsd a625248): the rolling `latest` release's
crafter-build-linux-x86_64-musl-v2.tar.gz that build-package.sh fetched
is gone, so the push of 0.2.6 failed at `fetch crafter-build` (run 10,
2026-10-14, three 404s) and published nothing; the registry still
serves 0.2.5.
The move imsd made on 2026-10-04: install the Flatpak
(net.catcrafts.CrafterBuild//stable from flatpak.catcrafts.net) in the
job container and run it as crafter-build. bubblewrap inside the
container needs a privileged runner, so the job runs on the `flatpak`
label. The host keeps clang and llvm-ar for libqcomtee, which
make-libqcomtee.sh compiles against the sysroot; the daemon's compiler,
lld and libc++ are the Flatpak's, so the host packages that served the
native test-suite run go. The sysroot gains llvm-runtimes, the package
with the std module source of its own libc++ (libc++-dev ships none),
added with --upgrade so a sysroot from before this change comes out
with a matching libc++ and source. The README's Build section carries
the install lines.
Tested: build-package.sh run end to end on this commit in a privileged
alpine:edge container (podman) on the workstation: crafter-build 2.1.0
(clang 23.1.2) from `stable`, the sysroot at libc++ and llvm-runtimes
23.1.3, daemon and agent cross-built, 8 suites passed natively, the
three apks built, the daemon an aarch64 PIE against the sysroot's musl
and libc++ (65 s). The rehearsed copy differs from this commit by the
wrapping of one comment line.
2026-10-14 02:32:30 +02:00
|
|
|
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"
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
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
|
packaging, ci: build the apk with the Crafter.Build Flatpak
Crafter.Build's releases carry a Flatpak and nothing else since
2026-10-02 (imsd a625248): the rolling `latest` release's
crafter-build-linux-x86_64-musl-v2.tar.gz that build-package.sh fetched
is gone, so the push of 0.2.6 failed at `fetch crafter-build` (run 10,
2026-10-14, three 404s) and published nothing; the registry still
serves 0.2.5.
The move imsd made on 2026-10-04: install the Flatpak
(net.catcrafts.CrafterBuild//stable from flatpak.catcrafts.net) in the
job container and run it as crafter-build. bubblewrap inside the
container needs a privileged runner, so the job runs on the `flatpak`
label. The host keeps clang and llvm-ar for libqcomtee, which
make-libqcomtee.sh compiles against the sysroot; the daemon's compiler,
lld and libc++ are the Flatpak's, so the host packages that served the
native test-suite run go. The sysroot gains llvm-runtimes, the package
with the std module source of its own libc++ (libc++-dev ships none),
added with --upgrade so a sysroot from before this change comes out
with a matching libc++ and source. The README's Build section carries
the install lines.
Tested: build-package.sh run end to end on this commit in a privileged
alpine:edge container (podman) on the workstation: crafter-build 2.1.0
(clang 23.1.2) from `stable`, the sysroot at libc++ and llvm-runtimes
23.1.3, daemon and agent cross-built, 8 suites passed natively, the
three apks built, the daemon an aarch64 PIE against the sysroot's musl
and libc++ (65 s). The rehearsed copy differs from this commit by the
wrapping of one comment line.
2026-10-14 02:32:30 +02:00
|
|
|
exec su -l build -c "CRAFTER_REF='$CRAFTER_REF' sh -eu '$SRC/packaging/build-package.sh'"
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
fi
|
|
|
|
|
|
2026-10-13 21:55:27 +02:00
|
|
|
# interfaces/Fingerprintd.cppm is the version's single source of truth (same
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
# derivation as make-bin-tarball.sh)
|
2026-10-13 21:55:27 +02:00
|
|
|
VER=$(sed -n 's/.*Version = "\(.*\)".*/\1/p' "$SRC/interfaces/Fingerprintd.cppm")
|
|
|
|
|
[ -n "$VER" ] || { echo "cannot read Version from interfaces/Fingerprintd.cppm" >&2; exit 1; }
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
echo ">> packaging fingerprintd $VER"
|
|
|
|
|
|
packaging, ci: build the apk with the Crafter.Build Flatpak
Crafter.Build's releases carry a Flatpak and nothing else since
2026-10-02 (imsd a625248): the rolling `latest` release's
crafter-build-linux-x86_64-musl-v2.tar.gz that build-package.sh fetched
is gone, so the push of 0.2.6 failed at `fetch crafter-build` (run 10,
2026-10-14, three 404s) and published nothing; the registry still
serves 0.2.5.
The move imsd made on 2026-10-04: install the Flatpak
(net.catcrafts.CrafterBuild//stable from flatpak.catcrafts.net) in the
job container and run it as crafter-build. bubblewrap inside the
container needs a privileged runner, so the job runs on the `flatpak`
label. The host keeps clang and llvm-ar for libqcomtee, which
make-libqcomtee.sh compiles against the sysroot; the daemon's compiler,
lld and libc++ are the Flatpak's, so the host packages that served the
native test-suite run go. The sysroot gains llvm-runtimes, the package
with the std module source of its own libc++ (libc++-dev ships none),
added with --upgrade so a sysroot from before this change comes out
with a matching libc++ and source. The README's Build section carries
the install lines.
Tested: build-package.sh run end to end on this commit in a privileged
alpine:edge container (podman) on the workstation: crafter-build 2.1.0
(clang 23.1.2) from `stable`, the sysroot at libc++ and llvm-runtimes
23.1.3, daemon and agent cross-built, 8 suites passed natively, the
three apks built, the daemon an aarch64 PIE against the sysroot's musl
and libc++ (65 s). The rehearsed copy differs from this commit by the
wrapping of one comment line.
2026-10-14 02:32:30 +02:00
|
|
|
# --- 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
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
|
|
|
|
|
# --- aarch64 Alpine sysroot (unprivileged: apk.static --usermode)
|
|
|
|
|
SYSROOT="$HOME/.cache/fingerprintd/sysroot-aarch64-alpine"
|
|
|
|
|
retry "make sysroot" "$SRC/packaging/make-sysroot.sh" "$SYSROOT"
|
|
|
|
|
|
|
|
|
|
# --- libqcomtee: Qualcomm's BSD-3 QTEE client (quic-teec, pinned commit),
|
|
|
|
|
# which is not in Alpine and is not vendored here. crafter-build looks for it
|
|
|
|
|
# under ~/.cache/fingerprintd/libqcomtee-<target>, which is this script's
|
|
|
|
|
# default output dir. Needs git, installed above.
|
|
|
|
|
retry "make libqcomtee" "$SRC/packaging/make-libqcomtee.sh" \
|
2026-09-05 23:06:57 +02:00
|
|
|
--target=aarch64-alpine-linux-musl --sysroot="$SYSROOT" --march=armv8.6-a+fp16fml+aes+sha3+sm4
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
|
|
|
|
|
# --- cross-compile the daemon; run the suites natively. The core is portable
|
|
|
|
|
# by construction (no GLib, no libqcomtee, no system headers), which is what
|
|
|
|
|
# lets the wire formats and state machines be tested on the build host at all.
|
|
|
|
|
cd "$SRC"
|
2026-09-05 23:06:57 +02:00
|
|
|
# The phone is one SoC, 4x Cortex-A520 + 4x Cortex-A720, so build for it (the
|
|
|
|
|
# same -march goes to libqcomtee above). armv8.6-a is the highest level whose
|
|
|
|
|
# mandatory set it exposes (8.7 would assume WFxT, 9.x SVE2 - neither is in
|
|
|
|
|
# its hwcaps); +fp16fml+aes+sha3+sm4 are the optional extensions it has. Same
|
|
|
|
|
# string as the fp6-img aports.
|
|
|
|
|
XTARGET="--target=aarch64-alpine-linux-musl --sysroot=$SYSROOT --march=armv8.6-a+fp16fml+aes+sha3+sm4 --mtune=cortex-a720"
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
crafter-build -- $XTARGET
|
2026-09-05 06:21:53 +02:00
|
|
|
crafter-build -- --product=agent $XTARGET
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
crafter-build test
|
|
|
|
|
|
|
|
|
|
# --- bundle + package
|
|
|
|
|
./packaging/make-bin-tarball.sh "$VER"
|
|
|
|
|
|
|
|
|
|
PKG="$HOME/pkg"
|
|
|
|
|
rm -rf "$PKG"
|
|
|
|
|
mkdir -p "$PKG"
|
|
|
|
|
cp "$SRC/packaging/APKBUILD" "$PKG/APKBUILD"
|
2026-09-05 20:22:17 +02:00
|
|
|
# install= scripts are read from the aport dir, not from source=
|
0.2.4: the trustlet comes from the active slot, unpinned, and a refusal says why
0.2.3's manifest pinned the sha256 of one Android build's focal64
(16.82.0, the build the dev phone runs). Fairphone re-signs the trustlet
every release, so that pin matched exactly one of the six builds seen,
and two of two field reports had no sensor: one user on 16.100.0 edited
the manifest by hand, another ended up with a file QTEE refuses.
The manifest now carries '-' instead of a hash and depends on
fp6-vendor-blobs 1-r3, which tries the active slot first and verifies the
image's structure; QTEE's signature check is the gate it always was (one
flipped byte -> ERROR_ELF_SIGNATURE_ERROR, measured 2026-09-03 and again
today).
post-install reassembles the trustlet right away, so 'apk add' no longer
needs a boot for it. post-upgrade re-derives it from the active slot,
which replaces a hand-placed or wrongly pinned file, and then restarts
the daemon -- a plain restart, so a daemon that exited on a refused
trustlet comes back up on the re-derived one.
loadFromBuffer failures name the loader's verdict. The field's first
report was a bare result=12; it now reads ERROR_ELF_SIGNATURE_ERROR with
what to do about it. Probed on the phone with this build: a one-byte
tampered image and 100000 random bytes both print it, the pristine image
loads, and the suites pass 8/8.
2026-09-11 13:04:35 +02:00
|
|
|
cp "$SRC/packaging/fingerprintd.post-install" "$SRC/packaging/fingerprintd.post-upgrade" "$PKG/"
|
Package the daemon, so a fingerprint survives a reflash
An aport, the units, and everything a phone needs to come up with a working
sensor without a single command being run by hand. Verified on the dev phone
across two reboots: modules-load.d loads qcomtee, tmpfiles builds the SFS
root, the mount unit brings up persist, and the daemon is ready 51 seconds
into the boot, owning net.reactivated.Fprint with the enrolled finger
visible.
The packaging shape is the one imsd uses for 81voltd. A versioned
provides="fprintd=..." satisfies plasma-workspace -- its Users KCM is the
enrolment UI and speaks exactly this bus name -- and excludes the real
fprintd, which is not tidiness: fprintd is D-Bus-activatable, so a client
call would otherwise start it and fight us for the name. The cost is the
fprintd-* CLIs, which go with the package.
fprintd-pam is an install_if subpackage pinned to the exact fprintd version,
so the provides breaks its condition and apk purges it -- taking pam_fprintd,
which is the entire point of the daemon, with it. Depending on it explicitly
is what keeps it, and it has no dependency on fprintd itself.
Two things the packaging exposed in the daemon:
The transcript is for experiments, not for a shipped daemon. A file per start
in an unrotated directory, recording the time of every unlock, to say what
the journal already has. It is now opt-in behind --log-dir, which is what
deploy-dev.sh passes since fplearn.sh reads it.
Taking it off the daemon path also took away the setvbuf it was doing as a
side effect of dup2'ing fd 1, and under systemd stdout is a pipe, which means
full buffering: the daemon started, worked, answered D-Bus calls, and printed
nothing. A working daemon that looks hung. stdout is now line-buffered from
the first line of main.
The config ships as generated by fp6fpcfg.py --daemon --verbose, sha256
b205c756914a66f1, because that is the file every accuracy number was measured
on. The quieter variant is untested and switching is a measurement.
The trustlet is not here and never will be: focal64.mbn is a proprietary
OEM-signed blob, and the unit's ConditionPathExists is what keeps the package
inert without it -- as it does on a kernel with no CONFIG_QCOMTEE.
2026-09-05 02:52:01 +02:00
|
|
|
mv "fingerprintd-$VER.tar.gz" "$PKG/"
|
|
|
|
|
sed -i "s/^pkgver=.*/pkgver=$VER/" "$PKG/APKBUILD"
|
|
|
|
|
# a throwaway signing key: phones trust the registry-signed APKINDEX, not
|
|
|
|
|
# per-package keys (same situation as fp6-img's pmbootstrap-built packages).
|
|
|
|
|
# abuild >= 3.18 keeps keys under ~/.config/abuild and output under
|
|
|
|
|
# ~/.local/share/abuild (REPODEST default).
|
|
|
|
|
abuild-keygen -a -n >/dev/null 2>&1
|
|
|
|
|
sudo cp "$HOME"/.config/abuild/*.rsa.pub /etc/apk/keys/
|
|
|
|
|
# CHOST puts abuild in cross mode so arch="aarch64" packages on this x86_64
|
|
|
|
|
# host. -d skips dependency handling entirely: nothing compiles under abuild
|
|
|
|
|
# (with -r, cross mode would try to install a nonexistent build-base-aarch64
|
|
|
|
|
# plus the runtime depends); !tracedeps in the APKBUILD keeps abuild from
|
|
|
|
|
# resolving the aarch64 ELF NEEDED entries against this x86_64 host.
|
|
|
|
|
cd "$PKG"
|
|
|
|
|
abuild checksum
|
|
|
|
|
CHOST=aarch64 abuild -d
|
|
|
|
|
|
|
|
|
|
echo "=== built packages ==="
|
|
|
|
|
ls -la "$HOME"/.local/share/abuild/*/aarch64/fingerprintd*.apk
|