From 2aec448e719cbccb637bc6c080f576baccb3b1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dav=C3=AD=C3=B0=20Steinn=20Geirsson?= Date: Wed, 18 Mar 2026 19:13:32 +0000 Subject: [PATCH 1/2] feat: add per-VM tray.enable option (default false) Tray proxy was unconditionally enabled for all crosvm VMs. Add tray.enable so it must be opted into per VM. When disabled, neither the host-side tray service nor the guest-side tray daemon are created. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 3 +- modules/options.nix | 14 +++++++ modules/scripts.nix | 2 +- modules/services.nix | 96 ++++++++++++++++++++++---------------------- 4 files changed, 65 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 4b41d6e..a9557dc 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ There are a lot of configuration options but you don't really need to touch most | `color` | string | `"darkred"` | Window decoration color (named color or hex, e.g., `"#2ecc71"`) | | `network.nameservers` | list of strings | `[]` | DNS nameservers for this VM | | `network.interfaces` | attrset of interface configs | `{}` | Network interfaces (keys are guest-visible names) | +| `tray.enable` | bool | `false` | Enable tray proxy for this VM (proxies guest SNI tray items to host system tray) | | `autoShutdown.enable` | bool | `false` | Auto-shutdown when idle (after `autoShutdown.after` seconds) | | `autoShutdown.after` | int | `60` | Seconds to wait before shutdown | | `autoStart` | bool | `false` | Start VM automatically (GPU VMs: on session start; non-GPU VMs: at boot) | @@ -575,7 +576,7 @@ The host provides: ### Tray Integration -VM system tray applets (StatusNotifierItems like nm-applet, bluetooth, etc.) automatically appear in the host KDE system tray. No configuration needed — tray proxying is always enabled. +VM system tray applets (StatusNotifierItems like nm-applet, bluetooth, etc.) can appear in the host KDE system tray. Enable per VM with `tray.enable = true`. - Tray items are prefixed with the VM name (e.g., `[banking] NetworkManager`) - User interactions (clicks, scrolls, context menus) are forwarded back to the guest app diff --git a/modules/options.nix b/modules/options.nix index 79ef1d2..1d665d3 100644 --- a/modules/options.nix +++ b/modules/options.nix @@ -251,6 +251,20 @@ let ''; }; + tray = lib.mkOption { + type = lib.types.submodule { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable host-side tray proxy for this VM. Proxies guest SNI tray items to the host system tray."; + }; + }; + }; + default = { }; + description = "Tray proxy configuration for this VM."; + }; + autoShutdown = lib.mkOption { type = lib.types.submodule { options = { diff --git a/modules/scripts.nix b/modules/scripts.nix index ce010f4..3fa8dfa 100644 --- a/modules/scripts.nix +++ b/modules/scripts.nix @@ -498,7 +498,7 @@ let pkgs.callPackage ../rootfs-nixos { inherit (cfg._internal) wayland-proxy-virtwl sommelier; inherit soundConfig; - vmsilo-tray = cfg._internal.vmsilo-tray; + vmsilo-tray = if vm.tray.enable then cfg._internal.vmsilo-tray else null; trayLogLevel = cfg.vmsilo-tray.logLevel; waylandProxy = vm.waylandProxy; guestPrograms = vm.guestPrograms; diff --git a/modules/services.nix b/modules/services.nix index d80b753..abd388b 100644 --- a/modules/services.nix +++ b/modules/services.nix @@ -275,53 +275,53 @@ in } ) (lib.attrValues cfg.nixosVms) ++ - # VM services (run crosvm as root) - map ( - vm: - lib.nameValuePair "vmsilo-${vm.name}-vm" { - description = "vmsilo VM: ${vm.name}"; - wantedBy = lib.optionals (vm.autoStart && vm.gpu == false) [ "multi-user.target" ]; - wants = - map (depName: "vmsilo-${depName}-vm.service") vm.dependsOn - ++ lib.optional ( - vm.network.netvm != null && vm.network.netvm != "host" - ) "vmsilo-${vm.network.netvm}-vm.service" - ++ map (brName: "${brName}-netdev.service") (vmBridges vm); - after = [ "network.target" ] ++ map (brName: "${brName}-netdev.service") (vmBridges vm); - serviceConfig = - let - ephemeralPath = "/var/lib/vmsilo/${vm.name}-ephemeral.raw"; - createEphemeral = pkgs.writeShellScript "create-ephemeral-${vm.name}" '' - truncate -s ${vm.rootOverlay.size} ${ephemeralPath} - ''; - deleteEphemeral = pkgs.writeShellScript "delete-ephemeral-${vm.name}" '' - rm -f ${ephemeralPath} - ''; - startPreScripts = lib.optionals (vm.rootOverlay.type == "raw") [ "${createEphemeral}" ]; - cleanupSocket = pkgs.writeShellScript "cleanup-socket-${vm.name}" '' - rm -f /run/vmsilo/${vm.name}/crosvm-control.socket - rm -f /run/vmsilo/${vm.name}/cloud-hypervisor-control.socket - ''; - usbCleanup = pkgs.writeShellScript "usb-cleanup-${vm.name}" '' - source ${cfg._internal.usbHelperLib} - usb_cleanup_vm "${vm.name}" - ''; - stopPostScripts = [ - "${cleanupSocket}" - ] - ++ lib.optional (vm.hypervisor == "crosvm") "${usbCleanup}" - ++ lib.optionals (vm.rootOverlay.type == "raw") [ "${deleteEphemeral}" ]; - in - { - Type = "simple"; - ExecStart = "${cfg._internal.vmScripts.${vm.name}}"; - ExecStopPost = stopPostScripts; - } - // lib.optionalAttrs (startPreScripts != [ ]) { - ExecStartPre = startPreScripts; - }; - } - ) (lib.attrValues cfg.nixosVms) + # VM services (run crosvm as root) + map ( + vm: + lib.nameValuePair "vmsilo-${vm.name}-vm" { + description = "vmsilo VM: ${vm.name}"; + wantedBy = lib.optionals (vm.autoStart && vm.gpu == false) [ "multi-user.target" ]; + wants = + map (depName: "vmsilo-${depName}-vm.service") vm.dependsOn + ++ lib.optional ( + vm.network.netvm != null && vm.network.netvm != "host" + ) "vmsilo-${vm.network.netvm}-vm.service" + ++ map (brName: "${brName}-netdev.service") (vmBridges vm); + after = [ "network.target" ] ++ map (brName: "${brName}-netdev.service") (vmBridges vm); + serviceConfig = + let + ephemeralPath = "/var/lib/vmsilo/${vm.name}-ephemeral.raw"; + createEphemeral = pkgs.writeShellScript "create-ephemeral-${vm.name}" '' + truncate -s ${vm.rootOverlay.size} ${ephemeralPath} + ''; + deleteEphemeral = pkgs.writeShellScript "delete-ephemeral-${vm.name}" '' + rm -f ${ephemeralPath} + ''; + startPreScripts = lib.optionals (vm.rootOverlay.type == "raw") [ "${createEphemeral}" ]; + cleanupSocket = pkgs.writeShellScript "cleanup-socket-${vm.name}" '' + rm -f /run/vmsilo/${vm.name}/crosvm-control.socket + rm -f /run/vmsilo/${vm.name}/cloud-hypervisor-control.socket + ''; + usbCleanup = pkgs.writeShellScript "usb-cleanup-${vm.name}" '' + source ${cfg._internal.usbHelperLib} + usb_cleanup_vm "${vm.name}" + ''; + stopPostScripts = [ + "${cleanupSocket}" + ] + ++ lib.optional (vm.hypervisor == "crosvm") "${usbCleanup}" + ++ lib.optionals (vm.rootOverlay.type == "raw") [ "${deleteEphemeral}" ]; + in + { + Type = "simple"; + ExecStart = "${cfg._internal.vmScripts.${vm.name}}"; + ExecStopPost = stopPostScripts; + } + // lib.optionalAttrs (startPreScripts != [ ]) { + ExecStartPre = startPreScripts; + }; + } + ) (lib.attrValues cfg.nixosVms) ++ # Proxy template services (per-connection) map ( @@ -478,7 +478,7 @@ in # Tray proxy services (crosvm VMs only — kernel vsock required) lib.concatMap ( vm: - lib.optional (vm.hypervisor == "crosvm") ( + lib.optional (vm.tray.enable && vm.hypervisor == "crosvm") ( lib.nameValuePair "vmsilo-${vm.name}-tray" { description = "Tray proxy for VM ${vm.name}"; wantedBy = [ "vmsilo-${vm.name}-vm.service" ]; From a90054eb2a8c8e3a980d4fe452c277e772cfd494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dav=C3=AD=C3=B0=20Steinn=20Geirsson?= Date: Wed, 18 Mar 2026 19:15:16 +0000 Subject: [PATCH 2/2] revert: always include vmsilo-tray in guest rootfs The guest-side tray daemon is harmless when idle and keeping it in the rootfs allows all VMs to share the same image regardless of tray.enable. Co-Authored-By: Claude Opus 4.6 (1M context) --- modules/scripts.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/scripts.nix b/modules/scripts.nix index 3fa8dfa..ce010f4 100644 --- a/modules/scripts.nix +++ b/modules/scripts.nix @@ -498,7 +498,7 @@ let pkgs.callPackage ../rootfs-nixos { inherit (cfg._internal) wayland-proxy-virtwl sommelier; inherit soundConfig; - vmsilo-tray = if vm.tray.enable then cfg._internal.vmsilo-tray else null; + vmsilo-tray = cfg._internal.vmsilo-tray; trayLogLevel = cfg.vmsilo-tray.logLevel; waylandProxy = vm.waylandProxy; guestPrograms = vm.guestPrograms;