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