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.1 kB 131 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) is the gate on every API route; treat it 24 # like the hermes-api key. 25 systemd.services.hermes-voice = { 26 description = "hermes-voice: live voice conversations with Hermes"; 27 wantedBy = [ "multi-user.target" ]; 28 wants = [ "network-online.target" ]; 29 after = [ 30 "network-online.target" 31 "hermes-agent.service" 32 ]; 33 serviceConfig = { 34 ExecStart = lib.escapeShellArgs [ 35 (lib.getExe self.packages.${pkgs.stdenv.hostPlatform.system}.hermes-voice) 36 "--listen" 37 "[::]:${toString port}" 38 # The API server binds the retiolum address (hermes.nix), not 39 # loopback; traffic to the host's own address goes through lo. 40 "--hermes-url" 41 "http://${config.networking.hostName}.r:8642" 42 "--hermes-key-file" 43 "%d/hermes-key" 44 "--openai-key-file" 45 "%d/openai-key" 46 "--access-token-file" 47 "%d/access-token" 48 "--matrix-webhook" 49 "http://127.0.0.1:${toString webhookPort}/webhooks/voice-approval" 50 ]; 51 LoadCredential = [ 52 "hermes-key:${config.clan.core.vars.generators.hermes-api.files."api_key".path}" 53 "openai-key:${config.clan.core.vars.generators.hermes-voice-openai.files."api-key".path}" 54 "access-token:${config.clan.core.vars.generators.hermes-voice.files."access-token".path}" 55 ]; 56 DynamicUser = true; 57 Restart = "on-failure"; 58 CapabilityBoundingSet = ""; 59 NoNewPrivileges = true; 60 PrivateDevices = true; 61 PrivateTmp = true; 62 ProtectHome = true; 63 ProtectSystem = "strict"; 64 ProtectKernelTunables = true; 65 ProtectKernelModules = true; 66 ProtectControlGroups = true; 67 RestrictAddressFamilies = [ 68 "AF_INET" 69 "AF_INET6" 70 ]; 71 RestrictNamespaces = true; 72 LockPersonality = true; 73 SystemCallArchitectures = "native"; 74 }; 75 }; 76 77 # Only retiolum: neoprism's proxy and devices on the mesh. Every other 78 # interface keeps the default DROP (configs/default.nix). 79 networking.firewall.interfaces.retiolum.allowedTCPPorts = [ port ]; 80 81 # hermes-voice's side in the gateway (tools/hermes-voice/hermes-plugin): the 82 # matrix_dm_send and end_call tools, on for every platform like any plugin 83 # toolset (end_call refuses outside voice sessions), and /assist, which 84 # answers approvals asked in the Matrix DM through hermes-voice 85 # (authenticated with API_SERVER_KEY). 86 services.hermes-agent.extraPlugins = [ 87 (pkgs.runCommand "hermes-voice" { } '' 88 cp -r ${../../tools/hermes-voice/hermes-plugin} $out 89 '') 90 ]; 91 services.hermes-agent.environment.HERMES_VOICE_URL = "http://localhost:${toString port}"; 92 93 # How hermes-voice asks for an approval when no call can hear it: a 94 # deliver_only route on the gateway's webhook platform (hermes.nix) posts 95 # the text to the Matrix home room, no agent turn. 96 services.hermes-agent.settings.platforms.webhook.extra.routes.voice-approval = { 97 deliver = "matrix"; 98 deliver_only = true; 99 prompt = "{text}"; 100 }; 101 102 # OpenAI project key with GPT-Live access; billed per second of open voice 103 # session. persist=true so the prompt value is stored once. 104 clan.core.vars.generators.hermes-voice-openai.prompts.api-key = { 105 description = "OpenAI API key for hermes-voice (gpt-live-1)"; 106 type = "hidden"; 107 persist = true; 108 }; 109 110 # Signing key of the Android wrapper (packages.hermes-voice-android); only 111 # read when signing, so nothing is deployed. The fingerprint goes into the 112 # page's assetlinks.json (tools/hermes-voice/flake-module.nix). 113 clan.core.vars.generators.hermes-voice-android-signing = { 114 files.keystore.deploy = false; 115 files.password.deploy = false; 116 files.fingerprint.secret = false; 117 runtimeInputs = [ 118 pkgs.coreutils 119 pkgs.jdk17_headless 120 pkgs.openssl 121 ]; 122 script = '' 123 openssl rand -hex 32 | tr -d '\n' > "$out/password" 124 keytool -genkeypair -noprompt -storetype PKCS12 -keystore "$out/keystore" \ 125 -storepass:file "$out/password" -alias assist -dname CN=Assist \ 126 -keyalg RSA -keysize 4096 -validity 36500 127 keytool -exportcert -rfc -keystore "$out/keystore" -storepass:file "$out/password" -alias assist | 128 openssl x509 -noout -fingerprint -sha256 | cut -d= -f2 > "$out/fingerprint" 129 ''; 130 }; 131}