This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / machines / coaxmetal / hermes-voice.nix
5.8 kB 151 lines
1{ 2 config, 3 lib, 4 pkgs, 5 self, 6 ... 7}: 8let 9 port = 8790; 10 # The gateway's webhook platform (hermes.nix), loopback only. 11 webhookPort = config.services.hermes-agent.settings.platforms.webhook.extra.port; 12in 13{ 14 imports = [ ../../configs/hermes-voice-vars.nix ]; 15 16 # hermes-voice (tools/hermes-voice): live voice conversations with Hermes 17 # through OpenAI's gpt-live-1. Runs next to Hermes so its API key never 18 # leaves this host; reachable over retiolum and publicly as 19 # https://assist.lassul.us through neoprism (configs/hermes-proxy.nix). 20 # 21 # SECURITY: the page drives Hermes, which has terminal tools and code 22 # execution as lass (machines/coaxmetal/hermes.nix). The access token 23 # (configs/hermes-voice-vars.nix) and the Android app's token 24 # (hermes-voice-app below) are the gates on every API route; treat both 25 # like the hermes-api key. The app takes calls from the headset button with 26 # the phone locked, approvals by voice included. 27 systemd.services.hermes-voice = { 28 description = "hermes-voice: live voice conversations with Hermes"; 29 wantedBy = [ "multi-user.target" ]; 30 wants = [ "network-online.target" ]; 31 after = [ 32 "network-online.target" 33 "hermes-agent.service" 34 ]; 35 serviceConfig = { 36 ExecStart = lib.escapeShellArgs [ 37 (lib.getExe self.packages.${pkgs.stdenv.hostPlatform.system}.hermes-voice) 38 "--listen" 39 "[::]:${toString port}" 40 # The API server binds the retiolum address (hermes.nix), not 41 # loopback; traffic to the host's own address goes through lo. 42 "--hermes-url" 43 "http://${config.networking.hostName}.r:8642" 44 "--hermes-key-file" 45 "%d/hermes-key" 46 "--openai-key-file" 47 "%d/openai-key" 48 "--access-token-file" 49 "%d/access-token" 50 "--app-token-file" 51 "%d/app-token" 52 "--matrix-webhook" 53 "http://127.0.0.1:${toString webhookPort}/webhooks/voice-approval" 54 ]; 55 LoadCredential = [ 56 "hermes-key:${config.clan.core.vars.generators.hermes-api.files."api_key".path}" 57 "openai-key:${config.clan.core.vars.generators.hermes-voice-openai.files."api-key".path}" 58 "access-token:${config.clan.core.vars.generators.hermes-voice.files."access-token".path}" 59 "app-token:${config.clan.core.vars.generators.hermes-voice-app.files.token.path}" 60 ]; 61 DynamicUser = true; 62 Restart = "on-failure"; 63 CapabilityBoundingSet = ""; 64 NoNewPrivileges = true; 65 PrivateDevices = true; 66 PrivateTmp = true; 67 ProtectHome = true; 68 ProtectSystem = "strict"; 69 ProtectKernelTunables = true; 70 ProtectKernelModules = true; 71 ProtectControlGroups = true; 72 RestrictAddressFamilies = [ 73 "AF_INET" 74 "AF_INET6" 75 ]; 76 RestrictNamespaces = true; 77 LockPersonality = true; 78 SystemCallArchitectures = "native"; 79 }; 80 }; 81 82 # Only retiolum: neoprism's proxy and devices on the mesh. Every other 83 # interface keeps the default DROP (configs/default.nix). 84 networking.firewall.interfaces.retiolum.allowedTCPPorts = [ port ]; 85 86 # hermes-voice's side in the gateway (tools/hermes-voice/hermes-plugin): the 87 # matrix_dm_send and end_call tools, on for every platform like any plugin 88 # toolset (end_call refuses outside voice sessions), and /assist, which 89 # answers approvals asked in the Matrix DM through hermes-voice 90 # (authenticated with API_SERVER_KEY). 91 services.hermes-agent.extraPlugins = [ 92 (pkgs.runCommand "hermes-voice" { } '' 93 cp -r ${../../tools/hermes-voice/hermes-plugin} $out 94 '') 95 ]; 96 services.hermes-agent.environment.HERMES_VOICE_URL = "http://localhost:${toString port}"; 97 98 # How hermes-voice asks for an approval when no call can hear it: a 99 # deliver_only route on the gateway's webhook platform (hermes.nix) posts 100 # the text to the Matrix home room, no agent turn. 101 services.hermes-agent.settings.platforms.webhook.extra.routes.voice-approval = { 102 deliver = "matrix"; 103 deliver_only = true; 104 prompt = "{text}"; 105 }; 106 107 # OpenAI project key with GPT-Live access; billed per second of open voice 108 # session. persist=true so the prompt value is stored once. 109 clan.core.vars.generators.hermes-voice-openai.prompts.api-key = { 110 description = "OpenAI API key for hermes-voice (gpt-live-1)"; 111 type = "hidden"; 112 persist = true; 113 }; 114 115 # Bearer token of the Android app (tools/hermes-voice/android), checked by 116 # hermes-voice next to the page's access token; the proxy passes it through 117 # on assist.lassul.us/app/ without the web login (configs/hermes-proxy.nix). 118 # Paste it into the app once: 119 # clan vars get coaxmetal hermes-voice-app/token 120 clan.core.vars.generators.hermes-voice-app = { 121 files.token = { }; 122 runtimeInputs = [ 123 pkgs.coreutils 124 pkgs.openssl 125 ]; 126 script = '' 127 openssl rand -hex 32 | tr -d '\n' > "$out/token" 128 ''; 129 }; 130 131 # Signing key of the Android app (packages.hermes-voice-android); only read 132 # when signing, so nothing is deployed. 133 clan.core.vars.generators.hermes-voice-android-signing = { 134 files.keystore.deploy = false; 135 files.password.deploy = false; 136 files.fingerprint.secret = false; 137 runtimeInputs = [ 138 pkgs.coreutils 139 pkgs.jdk17_headless 140 pkgs.openssl 141 ]; 142 script = '' 143 openssl rand -hex 32 | tr -d '\n' > "$out/password" 144 keytool -genkeypair -noprompt -storetype PKCS12 -keystore "$out/keystore" \ 145 -storepass:file "$out/password" -alias assist -dname CN=Assist \ 146 -keyalg RSA -keysize 4096 -validity 36500 147 keytool -exportcert -rfc -keystore "$out/keystore" -storepass:file "$out/password" -alias assist | 148 openssl x509 -noout -fingerprint -sha256 | cut -d= -f2 > "$out/fingerprint" 149 ''; 150 }; 151}