this derivation will be built: /nix/store/2rikia1irlv2xrx9i8fhf7c0w6afasmy-treefmt-check.drv building '/nix/store/2rikia1irlv2xrx9i8fhf7c0w6afasmy-treefmt-check.drv' treefmt-check> treefmt v2.5.0traversed 580 files treefmt-check> emitted 384 files for processing treefmt-check> formatted 384 files (7 changed) in 859ms treefmt-check> M modules/nixos/hermes/agent-config.nix treefmt-check> M modules/nixos/hermes/cli.nix treefmt-check> M modules/nixos/hermes/guest.nix treefmt-check> M modules/nixos/hermes/host.nix treefmt-check> M modules/nixos/hermes/options.nix treefmt-check> M modules/nixos/hermes/simplex.nix treefmt-check> M tests/hermes.nix treefmt-check> diff --git a/modules/nixos/hermes/agent-config.nix b/modules/nixos/hermes/agent-config.nix treefmt-check> index 417f808..1b03671 100644 treefmt-check> --- a/modules/nixos/hermes/agent-config.nix treefmt-check> +++ b/modules/nixos/hermes/agent-config.nix treefmt-check> @@ -148,16 +148,15 @@ rec { treefmt-check> # is the venv ROOT, not its bin dir. treefmt-check> # Callers append config.nix.package when the user has config repos; treefmt-check> # this file has no `config`. treefmt-check> - path = treefmt-check> - [ treefmt-check> - "${stateDir}/.venv" treefmt-check> - hermesPackage treefmt-check> - pkgs.bash treefmt-check> - pkgs.coreutils treefmt-check> - pkgs.git treefmt-check> - ] treefmt-check> - ++ basePackages treefmt-check> - ++ cfg.extraPackages; treefmt-check> + path = [ treefmt-check> + "${stateDir}/.venv" treefmt-check> + hermesPackage treefmt-check> + pkgs.bash treefmt-check> + pkgs.coreutils treefmt-check> + pkgs.git treefmt-check> + ] treefmt-check> + ++ basePackages treefmt-check> + ++ cfg.extraPackages; treefmt-check> hasConfigRepos = repos.dirs != [ ]; treefmt-check> }; treefmt-check> # The config repos this user's agent edits. ./options.nix, treefmt-check> diff --git a/modules/nixos/hermes/cli.nix b/modules/nixos/hermes/cli.nix treefmt-check> index b2b5f04..5577de6 100644 treefmt-check> --- a/modules/nixos/hermes/cli.nix treefmt-check> +++ b/modules/nixos/hermes/cli.nix treefmt-check> @@ -47,7 +47,9 @@ let treefmt-check> lib.mapAttrsToList (user: ucfg: '' treefmt-check> ${user}) treefmt-check> ADDRESS_FILE=${hlib.simplexAddressFile user} treefmt-check> - AGENT_UNIT=${if ucfg.native then "hermes-simplex-address-${user}" else "microvm@${hlib.vmName user}"} treefmt-check> + AGENT_UNIT=${ treefmt-check> + if ucfg.native then "hermes-simplex-address-${user}" else "microvm@${hlib.vmName user}" treefmt-check> + } treefmt-check> ;; treefmt-check> '') cfg.enabledUsers treefmt-check> ); treefmt-check> @@ -78,7 +80,9 @@ let treefmt-check> in treefmt-check> '' treefmt-check> ${user}) treefmt-check> - ${lib.concatStringsSep "\n " (lib.mapAttrsToList (n: v: "export ${n}=${lib.escapeShellArg v}") env)} treefmt-check> + ${lib.concatStringsSep "\n " ( treefmt-check> + lib.mapAttrsToList (n: v: "export ${n}=${lib.escapeShellArg v}") env treefmt-check> + )} treefmt-check> export PATH=${lib.escapeShellArg (lib.makeBinPath path)}:"$PATH" treefmt-check> export LD_LIBRARY_PATH=${lib.escapeShellArg rt.env.LD_LIBRARY_PATH}"''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" treefmt-check> ;; treefmt-check> diff --git a/modules/nixos/hermes/guest.nix b/modules/nixos/hermes/guest.nix treefmt-check> index 5378741..202a969 100644 treefmt-check> --- a/modules/nixos/hermes/guest.nix treefmt-check> +++ b/modules/nixos/hermes/guest.nix treefmt-check> @@ -347,7 +347,7 @@ in treefmt-check> stateDir = guestStateDir; treefmt-check> workingDirectory = guestWorkspace user; treefmt-check> addToSystemPackages = true; treefmt-check> - settings = ac.settings; treefmt-check> + inherit (ac) settings; treefmt-check> environment = treefmt-check> nixAgentEnv treefmt-check> // ac.simplexEnv { treefmt-check> @@ -361,7 +361,8 @@ in treefmt-check> HERMES_SANDBOX = "microvm"; treefmt-check> }; treefmt-check> inherit (cfg) extraPlugins; treefmt-check> - extraPackages = basePackages ++ cfg.extraPackages ++ lib.optional nixosConfigEnabled config.nix.package; treefmt-check> + extraPackages = treefmt-check> + basePackages ++ cfg.extraPackages ++ lib.optional nixosConfigEnabled config.nix.package; treefmt-check> mcpServers = lib.optionalAttrs ucfg.spacesGateway.enable { treefmt-check> spaces = { treefmt-check> command = "${pkgs.socat}/bin/socat"; treefmt-check> diff --git a/modules/nixos/hermes/host.nix b/modules/nixos/hermes/host.nix treefmt-check> index 04b66e7..65731fd 100644 treefmt-check> --- a/modules/nixos/hermes/host.nix treefmt-check> +++ b/modules/nixos/hermes/host.nix treefmt-check> @@ -310,20 +310,20 @@ in treefmt-check> )) treefmt-check> (forEachVmUser ( treefmt-check> user: ucfg: { treefmt-check> - "hermes-spaces-bridge-${user}" = lib.mkIf ucfg.spacesGateway.enable { treefmt-check> - description = "spaces bridge socket for ${vmName user}"; treefmt-check> - wantedBy = [ "sockets.target" ]; treefmt-check> - # "vsock::" binds VMADDR_CID_ANY on the host. treefmt-check> - listenStreams = [ "vsock::${toString (spacesVsockPort user)}" ]; treefmt-check> - socketConfig.Accept = true; treefmt-check> - # Any guest can complete a vsock connect. The helper rejects it treefmt-check> - # post-accept. So a hostile sibling microVM could trip the treefmt-check> - # Accept=yes trigger limit. That limit is 200 per 2s. Then it treefmt-check> - # could fail the socket. This is a cross-VM DoS. Rejected treefmt-check> - # instances exit within milliseconds. MaxConnections bounds the treefmt-check> - # buildup. treefmt-check> - socketConfig.TriggerLimitIntervalSec = 0; treefmt-check> - }; treefmt-check> + "hermes-spaces-bridge-${user}" = lib.mkIf ucfg.spacesGateway.enable { treefmt-check> + description = "spaces bridge socket for ${vmName user}"; treefmt-check> + wantedBy = [ "sockets.target" ]; treefmt-check> + # "vsock::" binds VMADDR_CID_ANY on the host. treefmt-check> + listenStreams = [ "vsock::${toString (spacesVsockPort user)}" ]; treefmt-check> + socketConfig.Accept = true; treefmt-check> + # Any guest can complete a vsock connect. The helper rejects it treefmt-check> + # post-accept. So a hostile sibling microVM could trip the treefmt-check> + # Accept=yes trigger limit. That limit is 200 per 2s. Then it treefmt-check> + # could fail the socket. This is a cross-VM DoS. Rejected treefmt-check> + # instances exit within milliseconds. MaxConnections bounds the treefmt-check> + # buildup. treefmt-check> + socketConfig.TriggerLimitIntervalSec = 0; treefmt-check> + }; treefmt-check> } treefmt-check> )) treefmt-check> ]; treefmt-check> diff --git a/modules/nixos/hermes/options.nix b/modules/nixos/hermes/options.nix treefmt-check> index 58a1db3..e5d55e1 100644 treefmt-check> --- a/modules/nixos/hermes/options.nix treefmt-check> +++ b/modules/nixos/hermes/options.nix treefmt-check> @@ -592,9 +592,7 @@ in treefmt-check> # this covers the MAC too. treefmt-check> map (fn: { treefmt-check> assertion = treefmt-check> - !ucfg.enable treefmt-check> - || ucfg.native treefmt-check> - || lib.count (u: hlib.${fn} u == hlib.${fn} user) vsockUsers == 1; treefmt-check> + !ucfg.enable || ucfg.native || lib.count (u: hlib.${fn} u == hlib.${fn} user) vsockUsers == 1; treefmt-check> message = "services.hermes-microvm: ${fn} collision on ${user} — rename one of the colliding users or disable one VM (services.hermes-microvm.users..enable = false)."; treefmt-check> }) [ "cidFor" ] treefmt-check> ) cfg.users treefmt-check> @@ -633,7 +631,9 @@ in treefmt-check> ++ lib.mapAttrsToList (user: ucfg: { treefmt-check> assertion = treefmt-check> !(ucfg.enable && ucfg.native) treefmt-check> - || lib.count (u: u.enable && u.native && u.simplexPort == ucfg.simplexPort) (lib.attrValues cfg.users) == 1; treefmt-check> + || treefmt-check> + lib.count (u: u.enable && u.native && u.simplexPort == ucfg.simplexPort) (lib.attrValues cfg.users) treefmt-check> + == 1; treefmt-check> message = "services.hermes-microvm: duplicate simplexPort ${toString ucfg.simplexPort} (${user}) — the hash-derived default collided in its 1000-port window; set services.hermes-microvm.users..simplexPort explicitly on one of them."; treefmt-check> }) cfg.users; treefmt-check> }; treefmt-check> diff --git a/modules/nixos/hermes/simplex.nix b/modules/nixos/hermes/simplex.nix treefmt-check> index fa8139c..7aac29d 100644 treefmt-check> --- a/modules/nixos/hermes/simplex.nix treefmt-check> +++ b/modules/nixos/hermes/simplex.nix treefmt-check> @@ -78,7 +78,10 @@ in treefmt-check> RestartSec = 5; treefmt-check> # No prompts: a display-name or migration prompt leaves the unit dead. treefmt-check> ExecStart = treefmt-check> - if bindPort == null then "${startScript}" else "${daemonArgs} --chat-server-port ${toString bindPort}"; treefmt-check> + if bindPort == null then treefmt-check> + "${startScript}" treefmt-check> + else treefmt-check> + "${daemonArgs} --chat-server-port ${toString bindPort}"; treefmt-check> }; treefmt-check> }; treefmt-check> treefmt-check> diff --git a/tests/hermes.nix b/tests/hermes.nix treefmt-check> index ed7ca7e..77daf78 100644 treefmt-check> --- a/tests/hermes.nix treefmt-check> +++ b/tests/hermes.nix treefmt-check> @@ -1532,7 +1532,8 @@ in treefmt-check> # LoadCredential resolves before the unit's own ExecStartPre, so the treefmt-check> # dashboard unit must not mint its own token; the agent does. treefmt-check> agentMintsToken = lib.any (lib.hasInfix "hermes-desktop-token-bob") nativeAgent.serviceConfig.ExecStartPre; treefmt-check> - dashboardMintsNone = !lib.any (lib.hasInfix "hermes-desktop-token") nativeDashboard.serviceConfig.ExecStartPre; treefmt-check> + dashboardMintsNone = treefmt-check> + !lib.any (lib.hasInfix "hermes-desktop-token") nativeDashboard.serviceConfig.ExecStartPre; treefmt-check> afterAgent = lib.elem "hermes-agent-bob.service" nativeDashboard.after; treefmt-check> wantsAgent = lib.elem "hermes-agent-bob.service" nativeDashboard.wants; treefmt-check> }; treefmt-check> @@ -1552,7 +1553,12 @@ in treefmt-check> testAgentEnv = { treefmt-check> expr = { treefmt-check> inherit (nativeAgent.environment) HOME HERMES_HOME HERMES_SANDBOX; treefmt-check> - inherit (nativeAgent.serviceConfig) User Group WorkingDirectory LoadCredential; treefmt-check> + inherit (nativeAgent.serviceConfig) treefmt-check> + User treefmt-check> + Group treefmt-check> + WorkingDirectory treefmt-check> + LoadCredential treefmt-check> + ; treefmt-check> gateway = lib.hasSuffix "/bin/hermes gateway" nativeAgent.serviceConfig.ExecStart; treefmt-check> seedsModel = lib.hasInfix ".model-seeded" nativeSeedAgent.preStart; treefmt-check> noSeedWithoutModel = lib.hasInfix ".model-seeded" nativeAgent.preStart; treefmt-check> @@ -1590,7 +1596,8 @@ in treefmt-check> # directly; the uid resolves at run time. treefmt-check> testSpacesMcp = { treefmt-check> expr = { treefmt-check> - bob = lib.hasInfix "\"mcp_servers\":{\"spaces\":{\"args\":[],\"command\":\"/nix/store/" nativeSys.services.hermes-microvm.nativeSettingsFiles.bob.text treefmt-check> + bob = treefmt-check> + lib.hasInfix "\"mcp_servers\":{\"spaces\":{\"args\":[],\"command\":\"/nix/store/" nativeSys.services.hermes-microvm.nativeSettingsFiles.bob.text treefmt-check> && lib.hasInfix "-hermes-spaces-mcp\"" nativeSys.services.hermes-microvm.nativeSettingsFiles.bob.text; treefmt-check> noVsock = lib.hasInfix "VSOCK" nativeSys.services.hermes-microvm.nativeSettingsFiles.bob.text; treefmt-check> aliceHasNone = nativeSys.services.hermes-microvm.nativeSettingsFiles ? alice; treefmt-check> @@ -1643,7 +1650,7 @@ in treefmt-check> testShim = { treefmt-check> expr = treefmt-check> let treefmt-check> - text = (hermesShimOf nativeSys).text; treefmt-check> + inherit ((hermesShimOf nativeSys)) text; treefmt-check> in treefmt-check> { treefmt-check> bobNative = lib.hasInfix "bob)\n mode=native" text; treefmt-check> @@ -1693,13 +1700,12 @@ in treefmt-check> { treefmt-check> daemonBindsUid = lib.hasInfix "--chat-server-port $((10000 + $(" nativeSimplex.startScript.text; treefmt-check> bobUsesIt = lib.hasInfix "hermes-simplex-start" (svc "hermes-simplex-bob").serviceConfig.ExecStart; treefmt-check> - aliceUsesIt = lib.hasInfix "hermes-simplex-start" (svc "hermes-simplex-alice").serviceConfig.ExecStart; treefmt-check> - bobAgentDialsPublic = treefmt-check> - lib.hasInfix "SIMPLEX_WS_URL=ws://127.0.0.1:${toString (hlib.simplexPortFor "bob")}" treefmt-check> - (svc "hermes-agent-bob").preStart; treefmt-check> - aliceAgentDialsPublic = treefmt-check> - lib.hasInfix "SIMPLEX_WS_URL=ws://127.0.0.1:${toString (hlib.simplexPortFor "alice")}" treefmt-check> - (svc "hermes-agent-alice").preStart; treefmt-check> + aliceUsesIt = lib.hasInfix "hermes-simplex-start" (svc "hermes-simplex-alice") treefmt-check> + .serviceConfig.ExecStart; treefmt-check> + bobAgentDialsPublic = lib.hasInfix "SIMPLEX_WS_URL=ws://127.0.0.1:${toString (hlib.simplexPortFor "bob")}" (svc "hermes-agent-bob") treefmt-check> + .preStart; treefmt-check> + aliceAgentDialsPublic = lib.hasInfix "SIMPLEX_WS_URL=ws://127.0.0.1:${toString (hlib.simplexPortFor "alice")}" (svc "hermes-agent-alice") treefmt-check> + .preStart; treefmt-check> addressDialsPublic = (svc "hermes-simplex-address-bob").environment.SIMPLEX_WS_URL; treefmt-check> distinct = hlib.simplexPortFor "alice" != hlib.simplexPortFor "bob"; treefmt-check> quiet = failedAssertions twoNative; treefmt-check> @@ -1756,8 +1762,11 @@ in treefmt-check> expr = { treefmt-check> nativeRefusesVm = lib.any (lib.hasInfix "hermes-native-guard-bob") nativeAgent.serviceConfig.ExecStartPre; treefmt-check> dashboardToo = lib.any (lib.hasInfix "hermes-native-guard-bob") nativeDashboard.serviceConfig.ExecStartPre; treefmt-check> - vmRefusesNative = lib.any (lib.hasInfix "hermes-vm-guard-alice") nativeSys.systemd.services."microvm-virtiofsd@hermes-alice".serviceConfig.ExecStartPre; treefmt-check> - afterFirewall = lib.elem "hermes-firewall.service" nativeDashboard.after treefmt-check> + vmRefusesNative = treefmt-check> + lib.any (lib.hasInfix "hermes-vm-guard-alice") treefmt-check> + nativeSys.systemd.services."microvm-virtiofsd@hermes-alice".serviceConfig.ExecStartPre; treefmt-check> + afterFirewall = treefmt-check> + lib.elem "hermes-firewall.service" nativeDashboard.after treefmt-check> && lib.elem "hermes-firewall.service" nativeSys.systemd.services."hermes-simplex-bob".after; treefmt-check> }; treefmt-check> expected = { error: Cannot build '/nix/store/2rikia1irlv2xrx9i8fhf7c0w6afasmy-treefmt-check.drv'. Reason: builder failed with exit code 1. Output paths: /nix/store/4wp88a99kapl19xkacwy0p2q5fbyxpr0-treefmt-check Last 25 log lines: > - lib.hasInfix "SIMPLEX_WS_URL=ws://127.0.0.1:${toString (hlib.simplexPortFor "alice")}" > - (svc "hermes-agent-alice").preStart; > + aliceUsesIt = lib.hasInfix "hermes-simplex-start" (svc "hermes-simplex-alice") > + .serviceConfig.ExecStart; > + bobAgentDialsPublic = lib.hasInfix "SIMPLEX_WS_URL=ws://127.0.0.1:${toString (hlib.simplexPortFor "bob")}" (svc "hermes-agent-bob") > + .preStart; > + aliceAgentDialsPublic = lib.hasInfix "SIMPLEX_WS_URL=ws://127.0.0.1:${toString (hlib.simplexPortFor "alice")}" (svc "hermes-agent-alice") > + .preStart; > addressDialsPublic = (svc "hermes-simplex-address-bob").environment.SIMPLEX_WS_URL; > distinct = hlib.simplexPortFor "alice" != hlib.simplexPortFor "bob"; > quiet = failedAssertions twoNative; > @@ -1756,8 +1762,11 @@ in > expr = { > nativeRefusesVm = lib.any (lib.hasInfix "hermes-native-guard-bob") nativeAgent.serviceConfig.ExecStartPre; > dashboardToo = lib.any (lib.hasInfix "hermes-native-guard-bob") nativeDashboard.serviceConfig.ExecStartPre; > - vmRefusesNative = lib.any (lib.hasInfix "hermes-vm-guard-alice") nativeSys.systemd.services."microvm-virtiofsd@hermes-alice".serviceConfig.ExecStartPre; > - afterFirewall = lib.elem "hermes-firewall.service" nativeDashboard.after > + vmRefusesNative = > + lib.any (lib.hasInfix "hermes-vm-guard-alice") > + nativeSys.systemd.services."microvm-virtiofsd@hermes-alice".serviceConfig.ExecStartPre; > + afterFirewall = > + lib.elem "hermes-firewall.service" nativeDashboard.after > && lib.elem "hermes-firewall.service" nativeSys.systemd.services."hermes-simplex-bob".after; > }; > expected = { For full logs, run: nix log /nix/store/2rikia1irlv2xrx9i8fhf7c0w6afasmy-treefmt-check.drv