1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.virtualisation.virtualbox.host;
7
8 virtualbox = cfg.package.override {
9 inherit (cfg) enableHardening headless enableWebService enableKvm;
10 extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null;
11 };
12
13 kernelModules = config.boot.kernelPackages.virtualbox.override {
14 inherit virtualbox;
15 };
16
17in
18
19{
20 options.virtualisation.virtualbox.host = {
21 enable = mkEnableOption "VirtualBox" // {
22 description = ''
23 Whether to enable VirtualBox.
24
25 ::: {.note}
26 In order to pass USB devices from the host to the guests, the user
27 needs to be in the `vboxusers` group.
28 :::
29 '';
30 };
31
32 enableExtensionPack = mkEnableOption "VirtualBox extension pack" // {
33 description = ''
34 Whether to install the Oracle Extension Pack for VirtualBox.
35
36 ::: {.important}
37 You must set `nixpkgs.config.allowUnfree = true` in
38 order to use this. This requires you accept the VirtualBox PUEL.
39 :::
40 '';
41 };
42
43 package = mkPackageOption pkgs "virtualbox" { };
44
45 addNetworkInterface = mkOption {
46 type = types.bool;
47 default = true;
48 description = ''
49 Automatically set up a vboxnet0 host-only network interface.
50 '';
51 };
52
53 enableHardening = mkOption {
54 type = types.bool;
55 default = true;
56 description = ''
57 Enable hardened VirtualBox, which ensures that only the binaries in the
58 system path get access to the devices exposed by the kernel modules
59 instead of all users in the vboxusers group.
60
61 ::: {.important}
62 Disabling this can put your system's security at risk, as local users
63 in the vboxusers group can tamper with the VirtualBox device files.
64 :::
65 '';
66 };
67
68 headless = mkOption {
69 type = types.bool;
70 default = false;
71 description = ''
72 Use VirtualBox installation without GUI and Qt dependency. Useful to enable on servers
73 and when virtual machines are controlled only via SSH.
74 '';
75 };
76
77 enableWebService = mkOption {
78 type = types.bool;
79 default = false;
80 description = ''
81 Build VirtualBox web service tool (vboxwebsrv) to allow managing VMs via other webpage frontend tools. Useful for headless servers.
82 '';
83 };
84
85 enableKvm = mkOption {
86 type = types.bool;
87 default = false;
88 description = ''
89 Enable KVM support for VirtualBox. This increases compatibility with Linux kernel versions, because the VirtualBox kernel modules
90 are not required.
91
92 This option is incompatible with `enableHardening` and `addNetworkInterface`.
93
94 Note: This is experimental. Please check https://github.com/cyberus-technology/virtualbox-kvm/issues.
95 '';
96 };
97 };
98
99 config = mkIf cfg.enable (mkMerge [{
100 warnings = mkIf (pkgs.config.virtualbox.enableExtensionPack or false)
101 ["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"];
102 environment.systemPackages = [ virtualbox ];
103
104 security.wrappers = let
105 mkSuid = program: {
106 source = "${virtualbox}/libexec/virtualbox/${program}";
107 owner = "root";
108 group = "vboxusers";
109 setuid = true;
110 };
111 executables = [
112 "VBoxHeadless"
113 "VBoxNetAdpCtl"
114 "VBoxNetDHCP"
115 "VBoxNetNAT"
116 "VBoxVolInfo"
117 ] ++ (lib.optionals (!cfg.headless) [
118 "VBoxSDL"
119 "VirtualBoxVM"
120 ]);
121 in mkIf cfg.enableHardening
122 (builtins.listToAttrs (map (x: { name = x; value = mkSuid x; }) executables));
123
124 users.groups.vboxusers.gid = config.ids.gids.vboxusers;
125
126 services.udev.extraRules =
127 ''
128 SUBSYSTEM=="usb_device", ACTION=="add", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
129 SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
130 SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
131 SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
132 '';
133 } (mkIf cfg.enableKvm {
134 assertions = [
135 {
136 assertion = !cfg.addNetworkInterface;
137 message = "VirtualBox KVM only supports standard NAT networking for VMs. Please turn off virtualisation.virtualbox.host.addNetworkInferface.";
138 }
139
140 {
141 assertion = !cfg.enableHardening;
142 message = "VirtualBox KVM is not compatible with hardening: Please turn off virtualisation.virtualbox.host.enableHardening.";
143 }
144 ];
145
146 warnings = [
147 ''
148 KVM support in VirtualBox is experimental. Not all security features are available yet.
149 See: https://github.com/cyberus-technology/virtualbox-kvm/issues/12
150 ''
151 ];
152 }) (mkIf (!cfg.enableKvm) {
153 boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
154 boot.extraModulePackages = [ kernelModules ];
155
156 services.udev.extraRules =
157 ''
158 KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
159 KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666", TAG+="systemd"
160 KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
161 '';
162
163 # Since we lack the right setuid/setcap binaries, set up a host-only network by default.
164 }) (mkIf cfg.addNetworkInterface {
165 systemd.services.vboxnet0 =
166 { description = "VirtualBox vboxnet0 Interface";
167 requires = [ "dev-vboxnetctl.device" ];
168 after = [ "dev-vboxnetctl.device" ];
169 wantedBy = [ "network.target" "sys-subsystem-net-devices-vboxnet0.device" ];
170 path = [ virtualbox ];
171 serviceConfig.RemainAfterExit = true;
172 serviceConfig.Type = "oneshot";
173 serviceConfig.PrivateTmp = true;
174 environment.VBOX_USER_HOME = "/tmp";
175 script =
176 ''
177 if ! [ -e /sys/class/net/vboxnet0 ]; then
178 VBoxManage hostonlyif create
179 cat /tmp/VBoxSVC.log >&2
180 fi
181 '';
182 postStop =
183 ''
184 VBoxManage hostonlyif remove vboxnet0
185 '';
186 };
187
188 networking.interfaces.vboxnet0.ipv4.addresses = [{ address = "192.168.56.1"; prefixLength = 24; }];
189 # Make sure NetworkManager won't assume this interface being up
190 # means we have internet access.
191 networking.networkmanager.unmanaged = ["vboxnet0"];
192 }) (mkIf config.networking.useNetworkd {
193 systemd.network.networks."40-vboxnet0".extraConfig = ''
194 [Link]
195 RequiredForOnline=no
196 '';
197 })
198
199]);
200}