Clone this repository
For self-hosted knots, clone URLs may differ based on your setup.
packages.hermes-voice-android builds an unsigned APK (us.lassul.assist,
"Assist") that opens https://assist.lassul.us/ as a Trusted Web
Activity, so Chrome handles the mic permission and the stored access
token exactly as in the browser. Its single activity is the launcher
entry and handles android.intent.action.ASSIST. That is enough to be
offered under Default apps > Digital assistant app: the ASSISTANT
role's AssistantRoleBehavior (packages/modules/Permission) qualifies a
package either by an assist-capable VoiceInteractionService or by an
exported activity resolving ACTION_ASSIST, with no system-app check.
The activity binds the first browser offering the TWA category, opens
a session and launches the TWA intent; without such a browser it opens
a Custom Tab, which other browsers treat as a plain VIEW intent. It
stays below the page in the task and finishes when returned to.
The build is plain aapt2 + javac + d8 from androidenv (build-tools and
platform 36; the SDK is unfree, so androidenv comes from a nixpkgs
instance with allowUnfree and the license accepted). The only library
is androidx.browser 1.10.0's classes.jar (AAR from Google Maven, 289
KB): the client, session and TWA intent builder used here need none of
its dependencies (androidx.core is only referenced by launchUrl-style
helpers, which are not called). The APK is 72 KB and byte-identical
across rebuilds (fixed zip timestamps).
packages.hermes-voice-android-sign zipaligns and signs it with
apksigner (v1-v3, no .idsig) from a keystore and password file, by
default the packaged unsigned APK, and prints the certificate so it
can be compared with assetlinks.json.
The voice page gets a web app manifest (standalone, colors of
style.css) and 192/512 px icons, drawn with imagemagick at build time
(date chunks excluded so the PNGs are reproducible). Both are needed
for the page to run as a Trusted Web Activity in the Android wrapper.
/.well-known/assetlinks.json delegates handle_all_urls to the wrapper
(us.lassul.assist) with the SHA-256 fingerprint of its signing
certificate, so Chrome verifies the app and drops the URL bar. Like
the static files it is served without the access token, and neoprism
already proxies every path of assist.lassul.us.
The signing key comes from the new hermes-voice-android-signing
generator on coaxmetal: a PKCS12 keystore and its password (secret,
deploy = false since only signing reads them) and the fingerprint as a
public file. The package reads the fingerprint from vars/ with
pathExists like the nix-cache key, so evaluation works before the
generator ran; assetlinks.json is then simply missing (404), which is
why the static routes now answer 404 for files not in the static dir.
Paperless-ngx at https://paperless.lassul.us, behind nginx with ACME
(client_max_body_size 100M for phone photo uploads). OCR in German and
English; the nixpkgs module builds tesseract with the languages from
PAPERLESS_OCR_LANGUAGE. Data stays at the module's default
/var/lib/paperless (static paperless user, no DynamicUser) with SQLite.
Login via Zitadel (id.lassul.us): lass.zitadel project "paperless" with
access = "declarative", so only users granted on the project (by
verified email) can log in. allauth's openid_connect provider keys
social accounts by uid_field, set explicitly to the token's sub.
paperless' environmentFile is a single env file and the client secret
has to sit inside the PAPERLESS_SOCIALACCOUNT_PROVIDERS JSON, so the
paperless-oidc-env oneshot builds it from zitadel-reconcile's env file
into /run/paperless-oidc, keeping the secret out of the store.
The regular login form is disabled; DRF token and basic auth do not go
through allauth's pre_authenticate, so API tokens keep working. The
admin password comes from the paperless-admin vars generator via the
module's passwordFile (LoadCredential).
Backups: /var/lib/paperless plus a consistent .backup of its WAL-mode
SQLite database (the live db files are excluded).
Mailbox scan@lassul.us with a vars-generated password
(mailserver-scan) for paperless' IMAP mail fetching, configured later
in paperless' database.
DNS: paperless CNAME neoprism like the other neoprism services; Gatus
checks the login page.
Keep a confirmed UDP path when a rekey relayed through another node
carries a different address hint: the hint flipped the path between
IPv6 and IPv4 on every rekey and stuck on a degraded one until the
peer's own rekey. Also stop starting tunnels to ourselves.
nixos-unstable has omp 18.11.0, whose fetchFromGitHub hash no longer
matches the tarball GitHub serves (Hydra rejects the output as well), so
coaxmetal, neoprism and barnacle fail to build. Pin the version from
nixpkgs master (18.11.2: new src and cargoDeps hashes, node_modules hash
unchanged) as an override of nixpkgs' omp. The assert breaks evaluation
as soon as nixpkgs moves away from 18.11.0, so the pin gets dropped with
the next flake update that brings a fixed omp.