···11-# sigexec executor: signed remote command execution as root, for
22-# administration. The signature is the authorization — commands are signed
33-# with the CLI's local ed25519 key (or a passkey) and verified against the
44-# key list below; no shell parses them, argv runs literally.
11+# sigexec: signed remote command execution as root, for administration.
22+# The signature is the authorization — commands are signed in the browser
33+# (passkey) or by the CLI's local ed25519 key, verified against the key list
44+# below; no shell parses them, argv runs literally.
55#
66-# The executor itself has no TLS and listens on localhost; nginx terminates
77-# TLS on the public vhost: sigexec run -x https://sigexec.lassul.us -- uptime
66+# nginx terminates TLS and serves the dashboard (page + reverse proxy, no key
77+# material, no job data); the executor is localhost-only behind it.
88+# browser: https://sigexec.lassul.us (register passkeys at /register)
99+# CLI: sigexec run -x https://sigexec.lassul.us/x/neoprism -- uptime
1010+# (statements sign the path relative to the base URL, so the executor
1111+# verifies /jobs after the proxy strips /x/neoprism)
812let
913 domain = "sigexec.lassul.us";
1010- port = 7601;
1414+ executorPort = 7601;
1515+ dashboardPort = 7501;
1116in
1217{ self, ... }:
1318{
1414- imports = [ self.inputs.sigexec.nixosModules.executor ];
1919+ imports = [ self.inputs.sigexec.nixosModules.default ];
15201621 services.sigexec-executor = {
1722 enable = true;
···2126 group = "root";
2227 # ssh-style: login shell env, bare command names work.
2328 login = true;
2424- listen = "127.0.0.1:${toString port}";
2929+ listen = "127.0.0.1:${toString executorPort}";
3030+ # Browser passkeys sign against the dashboard's origin.
3131+ rpId = domain;
3232+ origins = [ "https://${domain}" ];
2533 authorizedKeys = [
2634 "ed25519 RhYj8TzT8w3q50Oi3-q7U1KLRLrm66FltogC15ootJg read,write lass@ignavia"
2735 ];
3636+ };
3737+3838+ services.sigexec-dashboard = {
3939+ enable = true;
4040+ rpId = domain;
4141+ port = dashboardPort;
4242+ executors.neoprism = "http://127.0.0.1:${toString executorPort}";
2843 };
29443045 services.nginx.virtualHosts.${domain} = {
3146 enableACME = true;
3247 forceSSL = true;
3348 locations."/" = {
3434- proxyPass = "http://127.0.0.1:${toString port}";
4949+ proxyPass = "http://127.0.0.1:${toString dashboardPort}";
3550 extraConfig = ''
3651 # Job streams are long-lived chunked responses: unbuffered so output
3752 # arrives live, and a generous read timeout because `logs` on a