diff --git a/modules/common/services/default.nix b/modules/common/services/default.nix index 781f6e58b..874a2e8bb 100644 --- a/modules/common/services/default.nix +++ b/modules/common/services/default.nix @@ -6,5 +6,8 @@ ./audio.nix ./wifi.nix ./firmware.nix + ./desktop.nix + ./pdfopen.nix + ./namespaces.nix ]; } diff --git a/modules/common/services/desktop.nix b/modules/common/services/desktop.nix new file mode 100644 index 000000000..bbcb9478c --- /dev/null +++ b/modules/common/services/desktop.nix @@ -0,0 +1,128 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: let + inherit (builtins) filter map hasAttr; + inherit (lib) mkIf mkEnableOption head any optionals optionalAttrs; + cfg = config.ghaf.services.desktop; + + winConfig = + if (hasAttr "reference" config.ghaf) + then + if (hasAttr "programs" config.ghaf.reference) + then config.ghaf.reference.programs.windows-launcher + else {} + else {}; + isIdsvmEnabled = any (vm: vm == "ids-vm") config.ghaf.namespaces.vms; + # TODO: The desktop configuration needs to be re-worked. +in { + options.ghaf.services.desktop = { + enable = mkEnableOption "Enable the desktop configuration"; + }; + + config = mkIf cfg.enable { + ghaf = optionalAttrs (hasAttr "graphics" config.ghaf) { + profiles.graphics.compositor = "labwc"; + graphics = { + launchers = let + hostEntry = filter (x: x.name == "ghaf-host-debug") config.ghaf.networking.hosts.entries; + hostAddress = head (map (x: x.ip) hostEntry); + powerControl = pkgs.callPackage ../../../packages/powercontrol {}; + privateSshKeyPath = config.ghaf.security.sshKeys.sshKeyPath; + in + [ + { + # The SPKI fingerprint is calculated like this: + # $ openssl x509 -noout -in mitmproxy-ca-cert.pem -pubkey | openssl asn1parse -noout -inform pem -out public.key + # $ openssl dgst -sha256 -binary public.key | openssl enc -base64 + name = "Chromium"; + path = + if isIdsvmEnabled + then "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no chromium-vm run-waypipe chromium --enable-features=UseOzonePlatform --ozone-platform=wayland --user-data-dir=/home/${config.ghaf.users.accounts.user}/.config/chromium/Default --ignore-certificate-errors-spki-list=Bq49YmAq1CG6FuBzp8nsyRXumW7Dmkp7QQ/F82azxGU=" + else "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no chromium-vm run-waypipe chromium --enable-features=UseOzonePlatform --ozone-platform=wayland"; + icon = "${pkgs.icon-pack}/chromium.svg"; + } + + { + name = "GALA"; + path = "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no gala-vm run-waypipe gala --enable-features=UseOzonePlatform --ozone-platform=wayland"; + icon = "${pkgs.icon-pack}/distributor-logo-android.svg"; + } + + { + name = "PDF Viewer"; + path = "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no zathura-vm run-waypipe zathura"; + icon = "${pkgs.icon-pack}/document-viewer.svg"; + } + + { + name = "Element"; + path = "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no element-vm run-waypipe element-desktop --enable-features=UseOzonePlatform --ozone-platform=wayland"; + icon = "${pkgs.icon-pack}/element-desktop.svg"; + } + + { + name = "AppFlowy"; + path = "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no appflowy-vm run-waypipe appflowy"; + icon = "${pkgs.appflowy}/opt/data/flutter_assets/assets/images/flowy_logo.svg"; + } + + { + name = "Network Settings"; + path = "${pkgs.nm-launcher}/bin/nm-launcher"; + icon = "${pkgs.icon-pack}/preferences-system-network.svg"; + } + + { + name = "Shutdown"; + path = "${powerControl.makePowerOffCommand { + inherit hostAddress; + inherit privateSshKeyPath; + }}"; + icon = "${pkgs.icon-pack}/system-shutdown.svg"; + } + + { + name = "Reboot"; + path = "${powerControl.makeRebootCommand { + inherit hostAddress; + inherit privateSshKeyPath; + }}"; + icon = "${pkgs.icon-pack}/system-reboot.svg"; + } + + # Temporarly disabled as it fails to turn off display when suspended + # { + # name = "Suspend"; + # path = "${powerControl.makeSuspendCommand { + # inherit hostAddress; + # inherit privateSshKeyPath; + # }}"; + # icon = "${pkgs.icon-pack}/system-suspend.svg"; + # } + + # Temporarly disabled as it doesn't work at all + # { + # name = "Hibernate"; + # path = "${powerControl.makeHibernateCommand { + # inherit hostAddress; + # inherit privateSshKeyPath; + # }}"; + # icon = "${pkgs.icon-pack}/system-suspend-hibernate.svg"; + # } + ] + ++ optionals (hasAttr "spice-host" winConfig) [ + { + name = "Windows"; + path = "${pkgs.virt-viewer}/bin/remote-viewer -f spice://${winConfig.spice-host}:${toString winConfig.spice-port}"; + icon = "${pkgs.icon-pack}/distributor-logo-windows.svg"; + } + ]; + }; + }; + }; +} diff --git a/modules/common/services/fprint.nix b/modules/common/services/fprint.nix index debe35489..958b5aeeb 100644 --- a/modules/common/services/fprint.nix +++ b/modules/common/services/fprint.nix @@ -6,81 +6,60 @@ pkgs, ... }: let + inherit (lib) mkEnableOption mkIf; cfg = config.ghaf.services.fprint; - inherit (lib) mkEnableOption mkOption types mkIf; in { options.ghaf.services.fprint = { enable = mkEnableOption "Enable fingerprint reader support"; - qemuExtraArgs = mkOption { - type = types.listOf types.str; - default = []; - description = '' - Extra arguments to pass to qemu when enabling the fingerprint reader. - ''; - }; - extraConfigurations = mkOption { - type = types.attrsOf types.anything; - default = {}; - description = '' - Extra configurations when enabling the fingerprint reader in a guest. - ''; - }; }; config = mkIf cfg.enable { - ghaf.services.fprint = { - # Use qemu arguments generated for the device - qemuExtraArgs = config.ghaf.hardware.usb.internal.qemuExtraArgs.fprint-reader; + # Enable service and package for fingerprint reader + services.fprintd.enable = true; + environment.systemPackages = [pkgs.fprintd]; - extraConfigurations = { - # Enable service and package for fingerprint reader - services.fprintd.enable = true; - environment.systemPackages = [pkgs.fprintd]; - - # Enable polkit and add rules - ghaf.systemd.withPolkit = true; - security = { - polkit = { - enable = true; - debug = true; - # Polkit rules for fingerprint reader - extraConfig = '' - // Allow user to verify fingerprints - polkit.addRule(function(action, subject) { - if (action.id == "net.reactivated.fprint.device.verify" && - subject.user == "ghaf") { - return polkit.Result.YES; - } - }); - // Allow user to enroll fingerprints - polkit.addRule(function(action, subject) { - if (action.id == "net.reactivated.fprint.device.enroll" && - subject.user == "ghaf") { - return polkit.Result.YES; - } - }); - ''; - }; - # PAM rules for swaylock fingerprint reader - pam.services = { - swaylock.text = '' - # Account management. - account required pam_unix.so + # Enable polkit and add rules + ghaf.systemd.withPolkit = true; + security = { + polkit = { + enable = true; + debug = true; + # Polkit rules for fingerprint reader + extraConfig = '' + // Allow user to verify fingerprints + polkit.addRule(function(action, subject) { + if (action.id == "net.reactivated.fprint.device.verify" && + subject.user == "ghaf") { + return polkit.Result.YES; + } + }); + // Allow user to enroll fingerprints + polkit.addRule(function(action, subject) { + if (action.id == "net.reactivated.fprint.device.enroll" && + subject.user == "ghaf") { + return polkit.Result.YES; + } + }); + ''; + }; + # PAM rules for swaylock fingerprint reader + pam.services = { + swaylock.text = '' + # Account management. + account required pam_unix.so - # Authentication management. - auth sufficient pam_unix.so likeauth try_first_pass - auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so - auth required pam_deny.so + # Authentication management. + auth sufficient pam_unix.so likeauth try_first_pass + auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so + auth required pam_deny.so - # Password management. - password sufficient pam_unix.so nullok sha512 + # Password management. + password sufficient pam_unix.so nullok sha512 - # Session management. - session required pam_env.so conffile=/etc/pam/environment readenv=0 - session required pam_unix.so - ''; - }; - }; + # Session management. + session required pam_env.so conffile=/etc/pam/environment readenv=0 + session required pam_unix.so + ''; }; }; }; diff --git a/modules/common/services/namespaces.nix b/modules/common/services/namespaces.nix new file mode 100644 index 000000000..6597c6209 --- /dev/null +++ b/modules/common/services/namespaces.nix @@ -0,0 +1,25 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: let + inherit (builtins) attrNames hasAttr; + inherit (lib) mkOption types optionalAttrs; +in { + options.ghaf.namespaces = { + vms = mkOption { + type = types.listOf types.str; + default = []; + description = "List of VMs currently enabled."; + }; + }; + config = { + ghaf = optionalAttrs (hasAttr "microvm" config) { + namespaces = optionalAttrs (hasAttr "vms" config.microvm) { + vms = attrNames config.microvm.vms; + }; + }; + }; +} diff --git a/modules/common/services/pdfopen.nix b/modules/common/services/pdfopen.nix new file mode 100644 index 000000000..f8ecadaa1 --- /dev/null +++ b/modules/common/services/pdfopen.nix @@ -0,0 +1,58 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: let + inherit (builtins) toString; + inherit (lib) mkEnableOption mkOption mkIf types; + cfg = config.ghaf.services.pdfopener; + + # TODO: Fix the path to get the sshKeyPath so that + # openPdf can be exported as a normal package from + # packaged/flake-module.nix and hence easily imported + # into all targets + openPdf = pkgs.callPackage ../../../packages/openPdf { + inherit (config.ghaf.security.sshKeys) sshKeyPath; + }; +in { + options.ghaf.services.pdfopener = { + enable = mkEnableOption "Enable the pdf opening service"; + xdgPdfPort = mkOption { + type = types.int; + default = 1200; + description = "TCP port for the PDF XDG socket"; + }; + }; + + config = mkIf cfg.enable { + # PDF XDG handler service receives a PDF file path from the chromium-vm and executes the openpdf script + systemd.user = { + sockets."pdf" = { + unitConfig = { + Description = "PDF socket"; + }; + socketConfig = { + ListenStream = "${toString cfg.xdgPdfPort}"; + Accept = "yes"; + }; + wantedBy = ["sockets.target"]; + }; + + services."pdf@" = { + description = "PDF opener"; + serviceConfig = { + ExecStart = "${openPdf}/bin/openPdf"; + StandardInput = "socket"; + StandardOutput = "journal"; + StandardError = "journal"; + }; + }; + }; + + # Open TCP port for the PDF XDG socket. + networking.firewall.allowedTCPPorts = [cfg.xdgPdfPort]; + }; +} diff --git a/modules/hardware/common/default.nix b/modules/hardware/common/default.nix index a6ffa7b9d..12c4dfa7c 100644 --- a/modules/hardware/common/default.nix +++ b/modules/hardware/common/default.nix @@ -4,6 +4,8 @@ imports = [ ./usb/internal.nix ./usb/external.nix - ./passthru.nix + ./devices.nix + ./kernel.nix + ./qemu.nix ]; } diff --git a/modules/hardware/common/passthru.nix b/modules/hardware/common/devices.nix similarity index 65% rename from modules/hardware/common/passthru.nix rename to modules/hardware/common/devices.nix index b2a1f732e..8605abc3f 100644 --- a/modules/hardware/common/passthru.nix +++ b/modules/hardware/common/devices.nix @@ -6,17 +6,8 @@ ... }: let inherit (lib) mkOption types mkForce; - - # PCI device passthroughs for vfio - filterDevices = builtins.filter (d: d.vendorId != null && d.productId != null); - mapPciIdsToString = builtins.map (d: "${d.vendorId}:${d.productId}"); - vfioPciIds = mapPciIdsToString (filterDevices ( - config.ghaf.hardware.definition.network.pciDevices - ++ config.ghaf.hardware.definition.gpu.pciDevices - ++ config.ghaf.hardware.definition.audio.pciDevices - )); in { - options.ghaf.hardware.passthrough = { + options.ghaf.hardware.devices = { netvmPCIPassthroughModule = mkOption { type = types.attrsOf types.anything; default = {}; @@ -45,24 +36,10 @@ in { Virtio evdev paths' to passthrough to the guivm. ''; }; - guivmQemuExtraArgs = mkOption { - type = types.listOf types.str; - default = []; - description = '' - Extra arguments to pass to qemu when enabling the guivm. - ''; - }; - audiovmKernelParams = mkOption { - type = types.attrsOf types.anything; - default = {}; - description = '' - Sound hardware specific kernel parameters to configure audiovm. - ''; - }; }; config = { - ghaf.hardware.passthrough = { + ghaf.hardware.devices = { netvmPCIPassthroughModule = { microvm.devices = mkForce ( builtins.map (d: { @@ -110,31 +87,6 @@ in { # TODO: Remove this once wifi-signal-strength is changed ghaf.hardware.definition.network.pciDevices = config.ghaf.hardware.definition.network.pciDevices; }; - - guivmQemuExtraArgs = [ - # Button - "-device" - "button" - # Battery - "-device" - "battery" - # AC adapter - "-device" - "acad" - ]; - - audiovmKernelParams = { - microvm = { - inherit (config.ghaf.hardware.definition.audio) kernelParams; - }; - }; - }; - - # Enable VFIO for PCI devices - boot = { - kernelParams = [ - "vfio-pci.ids=${builtins.concatStringsSep "," vfioPciIds}" - ]; }; }; } diff --git a/modules/hardware/common/kernel.nix b/modules/hardware/common/kernel.nix new file mode 100644 index 000000000..28e1c1831 --- /dev/null +++ b/modules/hardware/common/kernel.nix @@ -0,0 +1,82 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Module for Kernel Configuration Definitions +# +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib) mkOption types optionalAttrs; + inherit (builtins) concatStringsSep filter map hasAttr; + + # Only x86 targets with hw definition supported at the moment + inherit (pkgs.stdenv.hostPlatform) isx86; + fullVirtualization = isx86 && (hasAttr "hardware" config.ghaf); +in { + options.ghaf.kernel = { + host = mkOption { + type = types.attrs; + default = {}; + description = "Host kernel configuration"; + }; + guivm = mkOption { + type = types.attrs; + default = {}; + description = "GuiVM kernel configuration"; + }; + audiovm = mkOption { + type = types.attrs; + default = {}; + description = "AudioVM kernel configuration"; + }; + }; + + config = { + # Host kernel configuration + boot = optionalAttrs fullVirtualization { + initrd = { + inherit (config.ghaf.hardware.definition.host.kernelConfig.stage1) kernelModules; + }; + inherit (config.ghaf.hardware.definition.host.kernelConfig.stage2) kernelModules; + kernelParams = let + # PCI device passthroughs for vfio + filterDevices = filter (d: d.vendorId != null && d.productId != null); + mapPciIdsToString = map (d: "${d.vendorId}:${d.productId}"); + vfioPciIds = mapPciIdsToString (filterDevices ( + config.ghaf.hardware.definition.network.pciDevices + ++ config.ghaf.hardware.definition.gpu.pciDevices + ++ config.ghaf.hardware.definition.audio.pciDevices + )); + in + config.ghaf.hardware.definition.host.kernelConfig.kernelParams + ++ [ + "vfio-pci.ids=${concatStringsSep "," vfioPciIds}" + ]; + }; + + # Guest kernel configurations + ghaf.kernel = optionalAttrs fullVirtualization { + guivm = { + boot = { + initrd = { + inherit (config.ghaf.hardware.definition.gpu.kernelConfig.stage1) kernelModules; + }; + inherit (config.ghaf.hardware.definition.gpu.kernelConfig.stage2) kernelModules; + inherit (config.ghaf.hardware.definition.gpu.kernelConfig) kernelParams; + }; + }; + audiovm = { + boot = { + initrd = { + inherit (config.ghaf.hardware.definition.audio.kernelConfig.stage1) kernelModules; + }; + inherit (config.ghaf.hardware.definition.audio.kernelConfig.stage2) kernelModules; + inherit (config.ghaf.hardware.definition.audio.kernelConfig) kernelParams; + }; + }; + }; + }; +} diff --git a/modules/hardware/common/qemu.nix b/modules/hardware/common/qemu.nix new file mode 100644 index 000000000..290390c17 --- /dev/null +++ b/modules/hardware/common/qemu.nix @@ -0,0 +1,38 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +# +{ + config, + lib, + ... +}: let + inherit (builtins) hasAttr; + inherit (lib) mkOption types optionals optionalAttrs; +in { + options.ghaf.qemu = { + guivm = mkOption { + type = types.attrs; + default = {}; + description = "Extra qemu arguments for GuiVM"; + }; + }; + + config = { + ghaf.qemu.guivm = optionalAttrs (hasAttr "hardware" config.ghaf) { + microvm.qemu.extraArgs = + [ + # Button + "-device" + "button" + # Battery + "-device" + "battery" + # AC adapter + "-device" + "acad" + ] + ++ optionals (hasAttr "fprint-reader" config.ghaf.hardware.usb.internal.qemuExtraArgs) + config.ghaf.hardware.usb.internal.qemuExtraArgs.fprint-reader; + }; + }; +} diff --git a/modules/hardware/definition.nix b/modules/hardware/definition.nix index 22b2c7883..39ebe324b 100644 --- a/modules/hardware/definition.nix +++ b/modules/hardware/definition.nix @@ -90,14 +90,58 @@ in { type = types.listOf types.str; default = []; description = '' - + List of input device names. ''; }; evdev = mkOption { type = types.listOf types.str; default = []; description = '' + List of event devices. + ''; + }; + }; + }; + # Kernel configuration submodule + kernelConfig = types.submodule { + options = { + stage1 = { + kernelModules = mkOption { + description = "Hardware specific kernel modules"; + type = types.listOf types.str; + default = []; + example = literalExpression '' + [ + "i915" + ] + ''; + }; + }; + stage2 = { + kernelModules = mkOption { + description = "Hardware specific kernel modules"; + type = types.listOf types.str; + default = []; + example = literalExpression '' + [ + "i915" + ] + ''; + }; + }; + kernelParams = mkOption { + description = "Hardware specific kernel parameters"; + type = types.listOf types.str; + default = []; + example = literalExpression '' + [ + "intel_iommu=on,sm_on" + "iommu=pt" + "module_blacklist=i915" + "acpi_backlight=vendor" + "acpi_osi=linux" + ] ''; }; }; @@ -109,20 +153,11 @@ in { default = ""; }; - generic = { - kernelParams = mkOption { - description = "Hardware specific kernel parameters for the host"; - type = types.listOf types.str; - default = []; - example = literalExpression '' - [ - "intel_iommu=on,sm_on" - "iommu=pt" - "module_blacklist=i915" - "acpi_backlight=vendor" - "acpi_osi=linux" - ] - ''; + host = { + kernelConfig = mkOption { + description = "Host kernel configuration"; + type = kernelConfig; + default = {}; }; }; @@ -204,6 +239,11 @@ in { }] ''; }; + kernelConfig = mkOption { + description = "Hardware specific kernel configuration for gpu devices"; + type = kernelConfig; + default = {}; + }; }; udevRules = mkOption { @@ -251,16 +291,10 @@ in { ] ''; }; - kernelParams = mkOption { - description = "Hardware specific kernel parameters for audio devices"; - type = types.listOf types.str; - default = []; - example = literalExpression '' - [ - "snd_intel_dspcfg.dsp_driver=3" - "snd_sof_intel_hda_common.dmic_num=4" - ] - ''; + kernelConfig = mkOption { + description = "Hardware specific kernel configuration for audio devices"; + type = kernelConfig; + default = {}; }; }; diff --git a/modules/hardware/lenovo-x1/default.nix b/modules/hardware/lenovo-x1/default.nix index 61225f80e..a0a21ffd3 100644 --- a/modules/hardware/lenovo-x1/default.nix +++ b/modules/hardware/lenovo-x1/default.nix @@ -22,7 +22,7 @@ in { config = { # Hardware definition ghaf.hardware.definition = { - inherit (hwDefinition) generic; + inherit (hwDefinition) host; inherit (hwDefinition) input; inherit (hwDefinition) disks; inherit (hwDefinition) network; @@ -34,11 +34,6 @@ in { # Disk configuration disko.devices.disk = hwDefinition.disks; - # Hardware specific kernel parameters - boot = { - inherit (hwDefinition.generic) kernelParams; - }; - # Host udev rules services.udev.extraRules = '' # Keyboard diff --git a/modules/hardware/lenovo-x1/definitions/x1-gen10.nix b/modules/hardware/lenovo-x1/definitions/x1-gen10.nix index 81c833763..87db6dd9a 100644 --- a/modules/hardware/lenovo-x1/definitions/x1-gen10.nix +++ b/modules/hardware/lenovo-x1/definitions/x1-gen10.nix @@ -4,8 +4,8 @@ { name = "Lenovo X1 Carbon Gen 10"; - generic = { - kernelParams = [ + host = { + kernelConfig.kernelParams = [ "intel_iommu=on,sm_on" "iommu=pt" "module_blacklist=i915" # Prevent i915 module from being accidentally used by host @@ -66,14 +66,20 @@ } ]; - gpu.pciDevices = [ - { - # Passthrough Intel Iris GPU - path = "0000:00:02.0"; - vendorId = "8086"; - productId = "46a6"; - } - ]; + gpu = { + pciDevices = [ + { + # Passthrough Intel Iris GPU + path = "0000:00:02.0"; + vendorId = "8086"; + productId = "46a6"; + } + ]; + kernelConfig = { + stage1.kernelModules = ["i915"]; + kernelParams = ["earlykms"]; + }; + }; # With the current implementation, the whole PCI IOMMU group 13: # 00:1f.x in the Lenovo X1 Carbon 10 gen @@ -105,7 +111,7 @@ productId = "51a4"; } ]; - kernelParams = [ + kernelConfig.kernelParams = [ "snd_intel_dspcfg.dsp_driver=3" "snd_sof_intel_hda_common.dmic_num=4" ]; diff --git a/modules/hardware/lenovo-x1/definitions/x1-gen11.nix b/modules/hardware/lenovo-x1/definitions/x1-gen11.nix index 0f174dd6b..e7491730c 100644 --- a/modules/hardware/lenovo-x1/definitions/x1-gen11.nix +++ b/modules/hardware/lenovo-x1/definitions/x1-gen11.nix @@ -4,8 +4,8 @@ { name = "Lenovo X1 Carbon Gen 11"; - generic = { - kernelParams = [ + host = { + kernelConfig.kernelParams = [ "intel_iommu=on,sm_on" "iommu=pt" "module_blacklist=i915" # Prevent i915 module from being accidentally used by host @@ -68,14 +68,20 @@ } ]; - gpu.pciDevices = [ - { - # Passthrough Intel Iris GPU - path = "0000:00:02.0"; - vendorId = "8086"; - productId = "a7a1"; - } - ]; + gpu = { + pciDevices = [ + { + # Passthrough Intel Iris GPU + path = "0000:00:02.0"; + vendorId = "8086"; + productId = "a7a1"; + } + ]; + kernelConfig = { + stage1.kernelModules = ["i915"]; + kernelParams = ["earlykms"]; + }; + }; # With the current implementation, the whole PCI IOMMU group 14: # 00:1f.x in the example from Lenovo X1 Carbon @@ -107,7 +113,7 @@ productId = "51a4"; } ]; - kernelParams = [ + kernelConfig.kernelParams = [ "snd_intel_dspcfg.dsp_driver=3" "snd_sof_intel_hda_common.dmic_num=4" ]; diff --git a/modules/microvm/virtualization/microvm/guivm.nix b/modules/microvm/virtualization/microvm/guivm.nix index 9c86177fc..af8db612e 100644 --- a/modules/microvm/virtualization/microvm/guivm.nix +++ b/modules/microvm/virtualization/microvm/guivm.nix @@ -6,7 +6,6 @@ pkgs, ... }: let - configHost = config; vmName = "gui-vm"; macAddress = "02:00:00:02:02:02"; inherit (import ../../../../lib/launcher.nix {inherit pkgs lib;}) rmDesktopEntries; @@ -22,19 +21,18 @@ ... }: { ghaf = { - users.accounts.enable = lib.mkDefault configHost.ghaf.users.accounts.enable; + users.accounts.enable = lib.mkDefault config.ghaf.users.accounts.enable; profiles = { - debug.enable = lib.mkDefault configHost.ghaf.profiles.debug.enable; + debug.enable = lib.mkDefault config.ghaf.profiles.debug.enable; applications.enable = false; graphics.enable = true; }; # To enable screen locking set to true graphics.labwc.autolock.enable = false; - windows-launcher.enable = false; development = { - ssh.daemon.enable = lib.mkDefault configHost.ghaf.development.ssh.daemon.enable; - debug.tools.enable = lib.mkDefault configHost.ghaf.development.debug.tools.enable; - nix-setup.enable = lib.mkDefault configHost.ghaf.development.nix-setup.enable; + ssh.daemon.enable = lib.mkDefault config.ghaf.development.ssh.daemon.enable; + debug.tools.enable = lib.mkDefault config.ghaf.development.debug.tools.enable; + nix-setup.enable = lib.mkDefault config.ghaf.development.nix-setup.enable; }; systemd = { enable = true; @@ -42,7 +40,7 @@ withNss = true; withResolved = true; withTimesyncd = true; - withDebug = configHost.ghaf.profiles.debug.enable; + withDebug = config.ghaf.profiles.debug.enable; withHardenedConfigs = true; }; }; @@ -79,14 +77,15 @@ pkgs.nm-launcher pkgs.pamixer ] - ++ (lib.optional (configHost.ghaf.profiles.debug.enable && configHost.ghaf.virtualization.microvm.idsvm.mitmproxy.enable) pkgs.mitmweb-ui); + ++ (lib.optional (config.ghaf.profiles.debug.enable && config.ghaf.virtualization.microvm.idsvm.mitmproxy.enable) pkgs.mitmweb-ui); }; + time.timeZone = config.time.timeZone; system.stateVersion = lib.trivial.release; nixpkgs = { - buildPlatform.system = configHost.nixpkgs.buildPlatform.system; - hostPlatform.system = configHost.nixpkgs.hostPlatform.system; + buildPlatform.system = config.nixpkgs.buildPlatform.system; + hostPlatform.system = config.nixpkgs.hostPlatform.system; }; # Suspend inside Qemu causes segfault @@ -101,8 +100,8 @@ shares = [ { tag = "rw-waypipe-ssh-public-key"; - source = configHost.ghaf.security.sshKeys.waypipeSshPublicKeyDir; - mountPoint = configHost.ghaf.security.sshKeys.waypipeSshPublicKeyDir; + source = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; + mountPoint = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; } { tag = "ro-store"; @@ -124,7 +123,7 @@ x86_64-linux = "q35"; aarch64-linux = "virt"; } - .${configHost.nixpkgs.hostPlatform.system}; + .${config.nixpkgs.hostPlatform.system}; }; }; @@ -211,8 +210,8 @@ in { # This directory needs to be created before any of the microvms start. systemd.services."create-waypipe-ssh-public-key-directory" = let script = pkgs.writeShellScriptBin "create-waypipe-ssh-public-key-directory" '' - mkdir -pv ${configHost.ghaf.security.sshKeys.waypipeSshPublicKeyDir} - chown -v microvm ${configHost.ghaf.security.sshKeys.waypipeSshPublicKeyDir} + mkdir -pv ${config.ghaf.security.sshKeys.waypipeSshPublicKeyDir} + chown -v microvm ${config.ghaf.security.sshKeys.waypipeSshPublicKeyDir} ''; in { enable = true; diff --git a/modules/microvm/virtualization/microvm/modules.nix b/modules/microvm/virtualization/microvm/modules.nix index c55560c16..1774e65cd 100644 --- a/modules/microvm/virtualization/microvm/modules.nix +++ b/modules/microvm/virtualization/microvm/modules.nix @@ -6,20 +6,35 @@ pkgs, ... }: let - inherit (lib) mkOption types optionals optionalAttrs mkEnableOption hasAttr; + inherit (builtins) hasAttr; + inherit (lib) mkOption types optionals optionalAttrs; cfg = config.ghaf.virtualization.microvm; - # Currently only x86 is supported by this module - isX86 = pkgs.stdenv.hostPlatform.isx86; + # Currently only x86 with hw definition supported + inherit (pkgs.stdenv.hostPlatform) isx86; + fullVirtualization = + isx86 + && (hasAttr "hardware" config.ghaf) + && (hasAttr "devices" config.ghaf.hardware); - # Hardware passthrough modules - hardwareModules = optionalAttrs isX86 { + # Hardware devices passthrough modules + deviceModules = optionalAttrs fullVirtualization { inherit - (config.ghaf.hardware.passthrough) + (config.ghaf.hardware.devices) netvmPCIPassthroughModule audiovmPCIPassthroughModule - audiovmKernelParams + guivmPCIPassthroughModule + guivmVirtioInputHostEvdevModule + ; + }; + + # Kernel configurations + kernelConfigs = optionalAttrs fullVirtualization { + inherit + (config.ghaf.kernel) + guivm + audiovm ; }; @@ -28,14 +43,42 @@ config.ghaf.services.firmware.enable = true; }; - # Audio module configuration - audioModule = optionalAttrs cfg.audiovm.audio { - config.ghaf.services.audio.enable = true; + # Qemu configuration modules + qemuModules = { + inherit (config.ghaf.qemu) guivm; }; - # Wifi module configuration - wifiModule = optionalAttrs cfg.netvm.wifi { - config.ghaf.services.wifi.enable = true; + # Service modules + serviceModules = { + # Audio module + audio = optionalAttrs cfg.audiovm.audio { + config.ghaf.services.audio.enable = true; + }; + + # Wifi module + wifi = optionalAttrs cfg.netvm.wifi { + config.ghaf.services.wifi.enable = true; + }; + + # Fprint module + fprint = optionalAttrs cfg.guivm.fprint { + config.ghaf.services.fprint.enable = true; + }; + + # Desktop module + desktop = { + config.ghaf.services.desktop.enable = true; + }; + + # PDF opener + pdfOpener = { + config.ghaf.services.pdfopener.enable = true; + }; + + # Common namespace to share (built-time) between host and VMs + commonNamespace = { + config.ghaf.namespaces = config.ghaf.namespaces; + }; }; # Reference services module @@ -46,12 +89,20 @@ }; }; }; + + # Reference programs module + referenceProgramsModule = { + config.ghaf = optionalAttrs (hasAttr "reference" config.ghaf) { + reference = optionalAttrs (hasAttr "programs" config.ghaf.reference) { + inherit (config.ghaf.reference) programs; + }; + }; + }; in { options.ghaf.virtualization.microvm = { - enable = mkEnableOption "Enable MicroVM module configuration. Only x86 is supported."; netvm.wifi = mkOption { type = types.bool; - default = isX86 && cfg.netvm.enable; + default = isx86 && cfg.netvm.enable; description = '' Enable Wifi module configuration. ''; @@ -63,22 +114,43 @@ in { Enable Audio module configuration. ''; }; + guivm.fprint = mkOption { + type = types.bool; + default = cfg.guivm.enable; + description = '' + Enable Fingerprint module configuration. + ''; + }; }; config = { - ghaf.virtualization.microvm = optionalAttrs isX86 { + # System VM configurations + ghaf.virtualization.microvm = optionalAttrs fullVirtualization { # Netvm modules netvm.extraModules = optionals cfg.netvm.enable [ - hardwareModules.netvmPCIPassthroughModule + deviceModules.netvmPCIPassthroughModule firmwareModule - wifiModule + serviceModules.wifi referenceServiceModule ]; # Audiovm modules audiovm.extraModules = optionals cfg.audiovm.enable [ - hardwareModules.audiovmPCIPassthroughModule - hardwareModules.audiovmKernelParams - audioModule + deviceModules.audiovmPCIPassthroughModule + kernelConfigs.audiovm + serviceModules.audio + ]; + # Guivm modules + guivm.extraModules = optionals cfg.guivm.enable [ + deviceModules.guivmPCIPassthroughModule + deviceModules.guivmVirtioInputHostEvdevModule + kernelConfigs.guivm + firmwareModule + qemuModules.guivm + serviceModules.desktop + serviceModules.fprint + serviceModules.pdfOpener + serviceModules.commonNamespace + referenceProgramsModule ]; }; }; diff --git a/modules/reference/appvms/chromium.nix b/modules/reference/appvms/chromium.nix index 738a0939e..958b9b321 100644 --- a/modules/reference/appvms/chromium.nix +++ b/modules/reference/appvms/chromium.nix @@ -60,7 +60,7 @@ in { microvm.qemu.extraArgs = lib.optionals config.ghaf.hardware.usb.internal.enable config.ghaf.hardware.usb.internal.qemuExtraArgs.webcam; microvm.devices = []; - ghaf.programs.chromium.enable = true; + ghaf.reference.programs.chromium.enable = true; # Set default PDF XDG handler xdg.mime.defaultApplications."application/pdf" = "ghaf-pdf.desktop"; diff --git a/modules/reference/appvms/zathura.nix b/modules/reference/appvms/zathura.nix index 80edc50ed..26eacb95c 100644 --- a/modules/reference/appvms/zathura.nix +++ b/modules/reference/appvms/zathura.nix @@ -15,7 +15,7 @@ { imports = [../programs/zathura.nix]; time.timeZone = config.time.timeZone; - ghaf.programs.zathura.enable = true; + ghaf.reference.programs.zathura.enable = true; } ]; borderColor = "#122263"; diff --git a/modules/reference/programs/chromium.nix b/modules/reference/programs/chromium.nix index 715df1205..2b8e33a81 100644 --- a/modules/reference/programs/chromium.nix +++ b/modules/reference/programs/chromium.nix @@ -5,9 +5,9 @@ lib, ... }: let - cfg = config.ghaf.programs.chromium; + cfg = config.ghaf.reference.programs.chromium; in { - options.ghaf.programs.chromium = { + options.ghaf.reference.programs.chromium = { enable = lib.mkEnableOption "Enable Chromium program settings"; useZathuraVM = lib.mkEnableOption "Open PDFs in Zathura VM"; }; diff --git a/modules/reference/programs/windows-launcher.nix b/modules/reference/programs/windows-launcher.nix index 4f3959d9c..b75df7eb0 100644 --- a/modules/reference/programs/windows-launcher.nix +++ b/modules/reference/programs/windows-launcher.nix @@ -6,13 +6,13 @@ config, ... }: let - cfg = config.ghaf.windows-launcher; + cfg = config.ghaf.reference.programs.windows-launcher; windows-launcher = pkgs.callPackage ../../../packages/windows-launcher {enableSpice = cfg.spice;}; in { #TODO fix all these imports to correct scoping imports = [../../desktop]; - options.ghaf.windows-launcher = { + options.ghaf.reference.programs.windows-launcher = { enable = lib.mkEnableOption "Windows launcher"; spice = lib.mkEnableOption "remote access to the virtual machine using spice"; diff --git a/modules/reference/programs/zathura.nix b/modules/reference/programs/zathura.nix index eb7360181..57ebcaa4a 100644 --- a/modules/reference/programs/zathura.nix +++ b/modules/reference/programs/zathura.nix @@ -5,9 +5,9 @@ lib, ... }: let - cfg = config.ghaf.programs.zathura; + cfg = config.ghaf.reference.programs.zathura; in { - options.ghaf.programs.zathura = { + options.ghaf.reference.programs.zathura = { enable = lib.mkEnableOption "Enable Zathura program settings"; }; config = lib.mkIf cfg.enable { diff --git a/targets/generic-x86_64/flake-module.nix b/targets/generic-x86_64/flake-module.nix index 45d9db3a8..db20a8241 100644 --- a/targets/generic-x86_64/flake-module.nix +++ b/targets/generic-x86_64/flake-module.nix @@ -74,7 +74,7 @@ # Uncomment this line to use Labwc instead of Weston: #graphics.compositor = "labwc"; }; - windows-launcher.enable = true; + reference.programs.windows-launcher.enable = true; }; #TODO: how to handle the majority of laptops that need a little diff --git a/targets/lenovo-x1/everything.nix b/targets/lenovo-x1/everything.nix index ba5bf0548..bd35afbfd 100644 --- a/targets/lenovo-x1/everything.nix +++ b/targets/lenovo-x1/everything.nix @@ -55,9 +55,6 @@ usb.external.enable = true; }; - # TODO: move this to module - services.fprint.enable = true; - reference.appvms = { enable = true; chromium-vm = true; @@ -72,6 +69,13 @@ dendrite = true; }; + reference.programs = { + windows-launcher = { + enable = true; + spice = true; + }; + }; + # Virtualization options virtualization = { microvm-host = { @@ -97,12 +101,7 @@ guivm = { enable = true; - extraModules = - # TODO convert this to an actual module - import ./guivmExtraModules.nix { - inherit lib pkgs self; - configH = config; - }; + extraModules = [self.nixosModules.reference-programs]; }; audiovm = { @@ -126,11 +125,6 @@ profiles = { applications.enable = false; }; - - windows-launcher = { - enable = true; - spice = true; - }; }; }) ] diff --git a/targets/lenovo-x1/guivmExtraModules.nix b/targets/lenovo-x1/guivmExtraModules.nix deleted file mode 100644 index 68ceaea10..000000000 --- a/targets/lenovo-x1/guivmExtraModules.nix +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors -# SPDX-License-Identifier: Apache-2.0 -# -{ - lib, - pkgs, - self, - configH, - ... -}: let - # TODO: Fix the path to get the sshKeyPath so that - # openPdf can be exported as a normal package from - # packaged/flake-module.nix and hence easily imported - # into all targets - openPdf = pkgs.callPackage ../../packages/openPdf { - inherit (configH.ghaf.security.sshKeys) sshKeyPath; - }; - # TODO generalize this TCP port used by PDF XDG handler - xdgPdfPort = 1200; - - winConfig = configH.ghaf.windows-launcher; - - guivmExtraConfigurations = { - ghaf = { - profiles.graphics.compositor = "labwc"; - graphics = { - launchers = let - hostAddress = "192.168.101.2"; - powerControl = pkgs.callPackage ../../packages/powercontrol {}; - privateSshKeyPath = configH.ghaf.security.sshKeys.sshKeyPath; - in [ - { - # The SPKI fingerprint is calculated like this: - # $ openssl x509 -noout -in mitmproxy-ca-cert.pem -pubkey | openssl asn1parse -noout -inform pem -out public.key - # $ openssl dgst -sha256 -binary public.key | openssl enc -base64 - name = "Chromium"; - path = - if configH.ghaf.virtualization.microvm.idsvm.mitmproxy.enable - then "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no chromium-vm run-waypipe chromium --enable-features=UseOzonePlatform --ozone-platform=wayland --user-data-dir=/home/${configH.ghaf.users.accounts.user}/.config/chromium/Default --ignore-certificate-errors-spki-list=Bq49YmAq1CG6FuBzp8nsyRXumW7Dmkp7QQ/F82azxGU=" - else "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no chromium-vm run-waypipe chromium --enable-features=UseOzonePlatform --ozone-platform=wayland"; - icon = "${pkgs.icon-pack}/chromium.svg"; - } - - { - name = "GALA"; - path = "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no gala-vm run-waypipe gala --enable-features=UseOzonePlatform --ozone-platform=wayland"; - icon = "${pkgs.icon-pack}/distributor-logo-android.svg"; - } - - { - name = "PDF Viewer"; - path = "${pkgs.openssh}/bin/ssh -i ${privateSshKeyPath} -o StrictHostKeyChecking=no zathura-vm run-waypipe zathura"; - icon = "${pkgs.icon-pack}/document-viewer.svg"; - } - - { - name = "Element"; - path = "${pkgs.openssh}/bin/ssh -i ${configH.ghaf.security.sshKeys.sshKeyPath} -o StrictHostKeyChecking=no element-vm run-waypipe element-desktop --enable-features=UseOzonePlatform --ozone-platform=wayland"; - icon = "${pkgs.icon-pack}/element-desktop.svg"; - } - - { - name = "AppFlowy"; - path = "${pkgs.openssh}/bin/ssh -i ${configH.ghaf.security.sshKeys.sshKeyPath} -o StrictHostKeyChecking=no appflowy-vm run-waypipe appflowy"; - icon = "${pkgs.appflowy}/opt/data/flutter_assets/assets/images/flowy_logo.svg"; - } - - { - name = "Windows"; - path = "${pkgs.virt-viewer}/bin/remote-viewer -f spice://${winConfig.spice-host}:${toString winConfig.spice-port}"; - icon = "${pkgs.icon-pack}/distributor-logo-windows.svg"; - } - - { - name = "Network Settings"; - path = "${pkgs.nm-launcher}/bin/nm-launcher"; - icon = "${pkgs.icon-pack}/preferences-system-network.svg"; - } - - { - name = "Shutdown"; - path = "${powerControl.makePowerOffCommand { - inherit hostAddress; - inherit privateSshKeyPath; - }}"; - icon = "${pkgs.icon-pack}/system-shutdown.svg"; - } - - { - name = "Reboot"; - path = "${powerControl.makeRebootCommand { - inherit hostAddress; - inherit privateSshKeyPath; - }}"; - icon = "${pkgs.icon-pack}/system-reboot.svg"; - } - - # Temporarly disabled as it fails to turn off display when suspended - # { - # name = "Suspend"; - # path = "${powerControl.makeSuspendCommand { - # inherit hostAddress; - # inherit privateSshKeyPath; - # }}"; - # icon = "${pkgs.icon-pack}/system-suspend.svg"; - # } - - # Temporarly disabled as it doesn't work at all - # { - # name = "Hibernate"; - # path = "${powerControl.makeHibernateCommand { - # inherit hostAddress; - # inherit privateSshKeyPath; - # }}"; - # icon = "${pkgs.icon-pack}/system-suspend-hibernate.svg"; - # } - ]; - }; - }; - - time.timeZone = configH.time.timeZone; - - # PDF XDG handler service receives a PDF file path from the chromium-vm and executes the openpdf script - systemd.user = { - sockets."pdf" = { - unitConfig = { - Description = "PDF socket"; - }; - socketConfig = { - ListenStream = "${toString xdgPdfPort}"; - Accept = "yes"; - }; - wantedBy = ["sockets.target"]; - }; - - services."pdf@" = { - description = "PDF opener"; - serviceConfig = { - ExecStart = "${openPdf}/bin/openPdf"; - StandardInput = "socket"; - StandardOutput = "journal"; - StandardError = "journal"; - }; - }; - }; - - # Enable all firmware for graphics firmware - hardware = { - enableRedistributableFirmware = true; - enableAllFirmware = true; - }; - - # Early KMS needed for ui to start work inside GuiVM - boot = { - initrd.kernelModules = ["i915"]; - kernelParams = ["earlykms"]; - }; - - # Open TCP port for the PDF XDG socket. - networking.firewall.allowedTCPPorts = [xdgPdfPort]; - - microvm.qemu.extraArgs = - configH.ghaf.hardware.passthrough.guivmQemuExtraArgs - ++ lib.optionals configH.ghaf.services.fprint.enable configH.ghaf.services.fprint.qemuExtraArgs; - }; - - inherit (configH.ghaf.hardware.passthrough) guivmPCIPassthroughModule guivmVirtioInputHostEvdevModule; -in - [ - guivmPCIPassthroughModule - guivmVirtioInputHostEvdevModule - guivmExtraConfigurations - self.nixosModules.reference-programs - ] - ++ lib.optionals configH.ghaf.services.fprint.enable [configH.ghaf.services.fprint.extraConfigurations] diff --git a/targets/nvidia-jetson-orin/flake-module.nix b/targets/nvidia-jetson-orin/flake-module.nix index d6cf5d149..307e90f80 100644 --- a/targets/nvidia-jetson-orin/flake-module.nix +++ b/targets/nvidia-jetson-orin/flake-module.nix @@ -92,7 +92,7 @@ release.enable = variant == "release"; debug.enable = variant == "debug"; }; - windows-launcher.enable = true; + reference.programs.windows-launcher.enable = true; graphics.labwc.renderer = "egl2"; # To enable screen locking set to true graphics.labwc.autolock.enable = false;