tools.hermes-voice: native Android app answers the headset button
The Android app holds the call itself instead of opening the page in a
Custom Tab: hold the headset's voice button (HFP AT+BVRA=1 starts
VOICE_COMMAND), talk, short press to hang up, all with the phone locked
and without looking at it.
- VoiceCall: the page's Call in Java over WebRTC (webrtc-sdk/android):
delegation to Hermes, voice approvals, end_call, idle hang-up.
- CallService: a self-managed Telecom call in the foreground, so the
audio goes to the headset and its hang-up (AT+CHUP) ends the call; a
tone says it listens.
- Screens: the current conversation with Call / Hang up, the list of
conversations, and the settings (app token, permissions).
- hermes-voice accepts the app's own token (--app-token-file, clan var
hermes-voice-app on coaxmetal); assist.lassul.us/app/ passes it
through without the web login.
- The page keeps Talk / Hang up in view: only the transcript scrolls.
assetlinks.json, the TWA and the page's ?listen go away with the
Custom Tab.
configs.hermes-proxy: log in to assist.lassul.us via id.lassul.us
assist.lassul.us now sits behind oauth2-proxy on neoprism (provider
oidc, issuer https://id.lassul.us), wired in with nginx auth_request on
that vhost only. The nixpkgs nginx helper (oauth2-proxy.nginx) is not
used because it turns on services.nginx.recommendedProxySettings for
every vhost on the host; the same few locations are written out here.
Unauthenticated requests are redirected (307) to /oauth2/start with a
relative rd, which oauth2-proxy accepts without a whitelist-domain;
the provider button is skipped so it goes straight to Zitadel.
Access: the Zitadel project "assist" is declarative with a grant for
lass@lassul.us only, and its projectRoleCheck blocks everyone else at
the IdP. oauth2-proxy additionally allows only that address
(authenticated-emails-file, no email domains); its OIDC provider rejects
emails whose email_verified is false. The session user is the `sub`
claim. The client id/secret come from zitadel-reconcile's env file
(/var/lib/zitadel-apps/assist-web.env, OAUTH2_PROXY_CLIENT_ID/SECRET),
used as the unit's EnvironmentFile; the cookie secret from a new
neoprism vars generator (oauth2-proxy/cookie-secret, via LoadCredential).
Public without login, as Chrome fetches them without cookies to install
the page and verify the TWA: /.well-known/assetlinks.json,
/manifest.webmanifest, /icon-192.png, /icon-512.png.
After the login, nginx sets Authorization: Bearer <access token> towards
hermes-voice, from an include in /run written at boot by a root oneshot
(hermes-voice-auth-header, owned by nginx, mode 0600), so the token
never enters the store. For that the hermes-voice generator is now a
shared var (configs/hermes-voice-vars.nix, imported by coaxmetal and
neoprism); a shared generator gets a new value, so the token is rotated.
hermes-voice keeps checking the token, which still guards its retiolum
port; the gateway plugin keeps calling /approve on localhost with
API_SERVER_KEY.
The page no longer asks for or stores the token. Its fetches use
redirect: "manual", so an expired login (redirect to id.lassul.us,
which fetch cannot follow cross-origin) shows up as an opaqueredirect
and the page reloads into the login.
tools.hermes-voice: native Android app answers the headset button
The Android app holds the call itself instead of opening the page in a
Custom Tab: hold the headset's voice button (HFP AT+BVRA=1 starts
VOICE_COMMAND), talk, short press to hang up, all with the phone locked
and without looking at it.
- VoiceCall: the page's Call in Java over WebRTC (webrtc-sdk/android):
delegation to Hermes, voice approvals, end_call, idle hang-up.
- CallService: a self-managed Telecom call in the foreground, so the
audio goes to the headset and its hang-up (AT+CHUP) ends the call; a
tone says it listens.
- Screens: the current conversation with Call / Hang up, the list of
conversations, and the settings (app token, permissions).
- hermes-voice accepts the app's own token (--app-token-file, clan var
hermes-voice-app on coaxmetal); assist.lassul.us/app/ passes it
through without the web login.
- The page keeps Talk / Hang up in view: only the transcript scrolls.
assetlinks.json, the TWA and the page's ?listen go away with the
Custom Tab.