1{ config, pkgs, lib, ... }:
2
3let
4 cfg = config.virtualisation.vmware.host;
5 wrapperDir = "/run/vmware/bin"; # Perfectly fits as /usr/local/bin
6 parentWrapperDir = dirOf wrapperDir;
7 vmwareWrappers = # Needed as hardcoded paths workaround
8 let mkVmwareSymlink =
9 program:
10 ''
11 ln -s "${config.security.wrapperDir}/${program}" $wrapperDir/${program}
12 '';
13 in
14 [
15 (mkVmwareSymlink "pkexec")
16 (mkVmwareSymlink "mount")
17 (mkVmwareSymlink "umount")
18 ];
19in
20{
21 options = with lib; {
22 virtualisation.vmware.host = {
23 enable = mkEnableOption (lib.mdDoc "VMware") // {
24 description = lib.mdDoc ''
25 This enables VMware host virtualisation for running VMs.
26
27 ::: {.important}
28 `vmware-vmx` will cause kcompactd0 due to
29 `Transparent Hugepages` feature in kernel.
30 Apply `[ "transparent_hugepage=never" ]` in
31 option {option}`boot.kernelParams` to disable them.
32 :::
33
34 ::: {.note}
35 If that didn't work disable `TRANSPARENT_HUGEPAGE`,
36 `COMPACTION` configs and recompile kernel.
37 :::
38 '';
39 };
40 package = mkOption {
41 type = types.package;
42 default = pkgs.vmware-workstation;
43 defaultText = literalExpression "pkgs.vmware-workstation";
44 description = lib.mdDoc "VMware host virtualisation package to use";
45 };
46 extraPackages = mkOption {
47 type = with types; listOf package;
48 default = with pkgs; [ ];
49 description = lib.mdDoc "Extra packages to be used with VMware host.";
50 example = "with pkgs; [ ntfs3g ]";
51 };
52 extraConfig = mkOption {
53 type = types.lines;
54 default = "";
55 description = lib.mdDoc "Add extra config to /etc/vmware/config";
56 example = ''
57 # Allow unsupported device's OpenGL and Vulkan acceleration for guest vGPU
58 mks.gl.allowUnsupportedDrivers = "TRUE"
59 mks.vk.allowUnsupportedDevices = "TRUE"
60 '';
61 };
62 };
63 };
64
65 config = lib.mkIf cfg.enable {
66 boot.extraModulePackages = [ config.boot.kernelPackages.vmware ];
67 boot.extraModprobeConfig = "alias char-major-10-229 fuse";
68 boot.kernelModules = [ "vmw_pvscsi" "vmw_vmci" "vmmon" "vmnet" "fuse" ];
69
70 environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
71 services.printing.drivers = [ cfg.package ];
72
73 environment.etc."vmware/config".text = ''
74 ${builtins.readFile "${cfg.package}/etc/vmware/config"}
75 ${cfg.extraConfig}
76 '';
77
78 environment.etc."vmware/bootstrap".source = "${cfg.package}/etc/vmware/bootstrap";
79 environment.etc."vmware/icu".source = "${cfg.package}/etc/vmware/icu";
80 environment.etc."vmware-installer".source = "${cfg.package}/etc/vmware-installer";
81
82 # SUID wrappers
83
84 security.wrappers = {
85 vmware-vmx = {
86 setuid = true;
87 owner = "root";
88 group = "root";
89 source = "${cfg.package}/lib/vmware/bin/.vmware-vmx-wrapped";
90 };
91 };
92
93 ###### wrappers activation script
94
95 system.activationScripts.vmwareWrappers =
96 lib.stringAfter [ "specialfs" "users" ]
97 ''
98 mkdir -p "${parentWrapperDir}"
99 chmod 755 "${parentWrapperDir}"
100 # We want to place the tmpdirs for the wrappers to the parent dir.
101 wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
102 chmod a+rx "$wrapperDir"
103 ${lib.concatStringsSep "\n" (vmwareWrappers)}
104 if [ -L ${wrapperDir} ]; then
105 # Atomically replace the symlink
106 # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
107 old=$(readlink -f ${wrapperDir})
108 if [ -e "${wrapperDir}-tmp" ]; then
109 rm --force --recursive "${wrapperDir}-tmp"
110 fi
111 ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
112 mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
113 rm --force --recursive "$old"
114 else
115 # For initial setup
116 ln --symbolic "$wrapperDir" "${wrapperDir}"
117 fi
118 '';
119
120 # Services
121
122 systemd.services."vmware-authdlauncher" = {
123 description = "VMware Authentication Daemon";
124 serviceConfig = {
125 Type = "forking";
126 ExecStart = [ "${cfg.package}/bin/vmware-authdlauncher" ];
127 };
128 wantedBy = [ "multi-user.target" ];
129 };
130
131 systemd.services."vmware-networks-configuration" = {
132 description = "VMware Networks Configuration Generation";
133 unitConfig.ConditionPathExists = "!/etc/vmware/networking";
134 serviceConfig = {
135 UMask = "0077";
136 ExecStart = [
137 "${cfg.package}/bin/vmware-networks --postinstall vmware-player,0,1"
138 ];
139 Type = "oneshot";
140 RemainAfterExit = "yes";
141 };
142 wantedBy = [ "multi-user.target" ];
143 };
144
145 systemd.services."vmware-networks" = {
146 description = "VMware Networks";
147 after = [ "vmware-networks-configuration.service" ];
148 requires = [ "vmware-networks-configuration.service" ];
149 serviceConfig = {
150 Type = "forking";
151 ExecCondition = [ "${pkgs.kmod}/bin/modprobe vmnet" ];
152 ExecStart = [ "${cfg.package}/bin/vmware-networks --start" ];
153 ExecStop = [ "${cfg.package}/bin/vmware-networks --stop" ];
154 };
155 wantedBy = [ "multi-user.target" ];
156 };
157
158 systemd.services."vmware-usbarbitrator" = {
159 description = "VMware USB Arbitrator";
160 serviceConfig = {
161 ExecStart = [ "${cfg.package}/bin/vmware-usbarbitrator -f" ];
162 };
163 wantedBy = [ "multi-user.target" ];
164 };
165 };
166}