From a25e51e7da48af657d83ad70e4d1fabd17915373 Mon Sep 17 00:00:00 2001 From: "Else, Someone" Date: Fri, 19 Sep 2025 23:15:09 +0300 Subject: [PATCH] profiles/vmapp-demo: init MVE ...moving bits and pieces from .dotfiles --- examples/dummy.nix | 13 +- pkgs/desktopAdapters.nix | 65 ++++++++++ profiles/uvms-guest.nix | 2 - profiles/vmapp-demo.nix | 261 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 336 insertions(+), 5 deletions(-) create mode 100644 pkgs/desktopAdapters.nix create mode 100644 profiles/vmapp-demo.nix diff --git a/examples/dummy.nix b/examples/dummy.nix index af142a1..24b41e1 100644 --- a/examples/dummy.nix +++ b/examples/dummy.nix @@ -13,6 +13,9 @@ system.stateVersion = "25.11"; + vmapps.enable = true; + _module.args.inputs = import ../npins; + # following microvm.nix: boot.loader.grub.enable = false; boot.initrd.systemd.enable = true; @@ -22,9 +25,8 @@ options = [ "size=20%,mode=0755" ]; neededForBoot = true; }; - boot.initrd.systemd.emergencyAccess = true; - boot.initrd.systemd.settings.Manager.DefaultTimeoutStartSec = 15; - systemd.settings.Manager.DefaultTimeoutStartSec = 15; + boot.initrd.systemd.settings.Manager.DefaultTimeoutStartSec = 30; + systemd.settings.Manager.DefaultTimeoutStopSec= 10; networking.useNetworkd = true; networking.nftables.enable = true; @@ -32,7 +34,12 @@ users.mutableUsers = false; users.users.root.password = "hacktheplanet!"; + services.getty.autologinUser = "root"; + systemd.services."suid-sgid-wrappers".serviceConfig = { + StandardOutput = "journal+console"; + StandardError = "journal+console"; + }; # TODO: cmdline, kernel, initrd, fileSystems } diff --git a/pkgs/desktopAdapters.nix b/pkgs/desktopAdapters.nix new file mode 100644 index 0000000..bb5a7fb --- /dev/null +++ b/pkgs/desktopAdapters.nix @@ -0,0 +1,65 @@ +{ + lib, + newScope, + makeDesktopItem, + writeShellScriptBin, +}: + +lib.makeScope newScope (self: { + # Scope-level configuration for adapters + useSessionUnits = true; # use --user units, instead of system units + vmUnitPrefix = "uvm@"; + vmSshPrefix = if self.useSessionUnits then "uuvm/" else "uvm/"; + vmUser = "user"; + waylandProxyUnit = "wayland-proxy.service"; # could be sommelier + oomScoreAdjust = -200; + + # Helper scripts + mkRunInVM = vmName: lib.getExe (self.mkRunInVMPkg vmName); + mkRunInVMPkg = + vmName: + writeShellScriptBin "run-in-${vmName}" '' + localArgs=( + ${lib.optionalString self.useSessionUnits "--user"} + --property=Requires=${self.vmUnitPrefix}${vmName}.service + --property=After=${self.vmUnitPrefix}${vmName}.service + --property=SyslogIdentifier="$1 (${vmName})" + ) + remoteArgs=( + systemd-run + --user + --property=StandardError="journal+console" + --property=StandardOutput="journal+console" + --property=OOMScoreAdjust=${toString self.oomScoreAdjust} + ${lib.optionalString (self.waylandProxyUnit != null) '' + --property=Requires=${self.waylandProxyUnit} + --property=After=${self.waylandProxyUnit} + ''} + ) + systemd-run \ + "''${localArgs[@]}" \ + ssh ${self.vmUser}@${self.vmSshPrefix}${vmName} \ + "''${remoteArgs[@]}" $@ + ''; + + # Actual .desktop item adapters + toVM = + vmName: attrs: + attrs + // { + exec = "${self.mkRunInVM vmName} ${attrs.exec}"; + }; + toBrowser = + attrs: + attrs + // { + categories = attrs.categories or [ ] ++ [ + "WebBrowser" + "Network" + ]; + mimeTypes = attrs.mimeTypes or [ ] ++ [ + "x-scheme-handler/http" + "x-scheme-handler/https" + ]; + }; +}) diff --git a/profiles/uvms-guest.nix b/profiles/uvms-guest.nix index 6b7bd3b..fb9bc1c 100644 --- a/profiles/uvms-guest.nix +++ b/profiles/uvms-guest.nix @@ -33,10 +33,8 @@ in ]; }) { - boot.kernelParams = [ "zswap.enabled=1" ]; zramSwap.enable = false; - } ]; } diff --git a/profiles/vmapp-demo.nix b/profiles/vmapp-demo.nix new file mode 100644 index 0000000..67078aa --- /dev/null +++ b/profiles/vmapp-demo.nix @@ -0,0 +1,261 @@ +{ + options, + config, + lib, + pkgs, + modulesPath, + ... +}: + +let + cfg = config.vmapps; + desktopItems' = pkgs.callPackage ../pkgs/desktopAdapters.nix { }; + desktopItems = desktopItems'.overrideScope ( + _: pre: { + vmUser = "root"; + waylandProxyUnit = + if config.microvm.graphics.enable or false then "wayland-proxy.service" else null; + + } + ); + nixosSystem = import (pkgs.path + "/nixos/lib/eval-config.nix"); + mkStaticVM = + name: extraModules: extraHostModules: + let + evaluated = nixosSystem { + inherit (pkgs.stdenv) system; + inherit pkgs; + modules = extraModules ++ [ + { + microvm.hypervisor = "cloud-hypervisor"; + microvm.hotpluggedMem = 128; + microvm.hotplugMem = 512; + microvm.shares = [ + { + source = "send"; + mountPoint = "/home/user/uvms/${name}/send"; + tag = "rw-send"; + proto = "virtiofs"; + } + { + source = "authorized_keys.d"; + mountPoint = "/etc/ssh/authorized_keys.d"; + tag = "ro-auth-keys"; + proto = "virtiofs"; + } + ]; + networking.hostName = name; + services.openssh.enable = true; + } + ./uvms-guest.nix + ./vsock-connect-guest.nix + (modulesPath + "/profiles/minimal.nix") + (config._module.args.inputs."microvm.nix" + "/nixos-modules/microvm") + ]; + specialArgs = { + inherit (config._module.args) inputs; + }; + }; + runner = evaluated.config.microvm.declaredRunner; + in + lib.mkMerge ( + extraHostModules + ++ [ + (lib.mkIf (options ? "debug"."closure"."layers") { + debug.closure.extraLayers = [ runner ]; + }) + { + systemd.user.targets.default.wants = [ "uvm@${name}.service" ]; + + # TODO: Ugly dirty hack, we didn't have the time to arrange a more + # sensible workaround for CH trying to create TAPs, or to figure out + # which of the services actually need the extra permissions (probably + # microvm-run and tap-up) + security.wrappers = lib.listToAttrs ( + map + ( + exe: + lib.nameValuePair "${exe}-${name}" { + owner = "root"; + group = "kvm"; + setuid = false; + capabilities = "cap_net_admin+ep"; + source = pkgs.writeShellScript "${exe}-${name}" '' + PATH=${lib.getBin pkgs.util-linux}/bin''${PATH:+:}''${PATH} + PATH=${lib.getBin runner}/bin''${PATH:+:}''${PATH} + ${exe} $@ + ''; + } + ) + [ + "microvm-run" + "microvm-shutdown" + "virtiofsd-run" + "virtiofsd-shutdown" + "tap-up" + "tap-down" + ] + ); + } + ] + ); + # TODO: clean up; came out of copy-pasting stuff backnforth + preamble = '' + set -euo pipefail + + INSTANCE="$1" + shift + + STATE_DIR="$HOME/uvms/$INSTANCE" + mkdir -p "$STATE_DIR" + + QUICKSHARE_DIR="$HOME/uvms/$INSTANCE/send" + mkdir -p "$QUICKSHARE_DIR" + cd "$STATE_DIR" + + mkdir -p "$STATE_DIR"/authorized_keys.d + ( + umask 0077 \ + && cp --no-preserve=all /etc/ssh/ssh_host_ed25519_key.pub "$STATE_DIR"/authorized_keys.d/root \ + && cp --no-preserve=all /etc/ssh/ssh_host_ed25519_key.pub "$STATE_DIR"/authorized_keys.d/user \ + ) + + PATH=/run/wrappers/bin''${PATH:+:}''${PATH} + set -x + ''; +in +{ + options = { + vmapps.enable = lib.mkEnableOption "Enable \"VM Apps\" demo"; + }; + # demo is currently based on microvm.nix + config = lib.mkMerge [ + (lib.mkIf (!(config._module.args ? "inputs"."microvm.nix")) { vmapps.enable = false; }) + (lib.mkIf cfg.enable { + networking.useNetworkd = true; + networking.nftables.enable = true; + systemd.network.enable = true; + networking.firewall.extraInputRules = '' + iifname "vt-*" udp dport { 53, 67 } accept + ''; + networking.nat.internalInterfaces = [ "vt-*" ]; + + users.motd = '' + vmapp-demo.nix + ============== + + Check `systemctl status --user uvm@browser`. + Run a command inside the VM: `run-in-browser echo foo`, + and see the outputs in syslog: `journalctl --user -u uvm@browser -e` + ''; + systemd.user.services."root-ssh-keys" = { + wantedBy = [ "default.target" ]; + before = [ "default.target" ]; + script = '' + [[ "$USER" == "root" ]] || exit 0 + [[ -f "$HOME/.ssh/config" ]] && echo ".ssh/config already exists, not writing" && exit 0 + cat > "$HOME/.ssh/config" << \EOF + Host * + IdentityFile /etc/ssh/ssh_host_ed25519_key + EOF + ''; + }; + systemd.user.services."uvm@" = rec { + requires = [ + "virtiofsd@.service" + "taps@.service" + ]; + after = requires; + serviceConfig.Type = "notify"; + serviceConfig.LimitMEMLOCK = "infinity"; + serviceConfig.LimitNOFILE = 1048576; + serviceConfig.NotifyAccess = "all"; + serviceConfig.ExecStop = "${pkgs.writeShellScript "microvm-shutdown-instance" '' + ${preamble} + microvm-shutdown-"$INSTANCE" + ''} %i"; + serviceConfig.ExecStart = "${pkgs.writeShellScript "uuvm-start-instance" '' + ${preamble} + microvm-run-"$INSTANCE" + ''} %i"; + }; + systemd.user.services."virtiofsd@" = { + requires = [ "graphical-session.target" ]; + after = [ "graphical-session.target" ]; + serviceConfig.Type = "notify"; + serviceConfig.LimitMEMLOCK = "infinity"; + serviceConfig.LimitNOFILE = 1048576; + serviceConfig.NotifyAccess = "all"; + serviceConfig.ExecStop = "${pkgs.writeShellScript "virtiofsd-shutdown-instance" '' + ${preamble} + virtiofsd-shutdown-"$INSTANCE" + ''} %i"; + serviceConfig.ExecStart = "${pkgs.writeShellScript "virtiofsd-start-instance" '' + ${preamble} + virtiofsd-run-"$INSTANCE" + ''} %i"; + }; + systemd.user.services."taps@" = { + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + serviceConfig.SyslogIdentifier = "taps@"; + serviceConfig.LimitMEMLOCK = "infinity"; + serviceConfig.NotifyAccess = "all"; + serviceConfig.ExecStop = "${pkgs.writeShellScript "taps-down-instance" '' + ${preamble} + tap-down-"$INSTANCE" + ''} %i"; + serviceConfig.ExecStart = "${pkgs.writeShellScript "taps-start-instance" '' + ${preamble} + tap-up-"$INSTANCE" + ''} %i"; + }; + + boot.initrd.systemd.settings.Manager.DefaultTimeoutStartSec = 30; + systemd.settings.Manager.DefaultTimeoutStopSec = 10; + systemd.services."user@".serviceConfig.TimeoutStopSec = 10; + + services.openssh.enable = true; + + environment.systemPackages = [ pkgs.xdg-utils ]; + users.users.microvm.isSystemUser = true; + users.users.microvm.group = "kvm"; + + }) + (lib.mkIf cfg.enable ( + mkStaticVM "browser" + [ + { + microvm.interfaces = [ + { + type = "tap"; + id = "vt-browse"; + mac = "00:00:00:00:00:02"; + } + ]; + environment.systemPackages = [ pkgs.tor-browser ]; + } + ] + [ + { + environment.systemPackages = [ + (desktopItems.mkRunInVMPkg "browser") + (pkgs.makeDesktopItem ( + desktopItems.toBrowser ( + (desktopItems.toVM "browser" { + name = "tor-browser"; + exec = "tor-browser %U"; + icon = "${pkgs.tor-browser}/share/icons/hicolor/128x128/apps/tor-browser.png"; + desktopName = "Tor (uuvm/browser)"; + startupNotify = true; + terminal = false; + type = "Application"; + }) + ) + )) + ]; + } + ] + )) + ]; +}