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 "VBoxHeadless"
124 "VBoxNetAdpCtl"
125 "VBoxNetDHCP"
126 "VBoxNetNAT"
127 "VBoxVolInfo"
128 ]
129 ++ (lib.optionals (!cfg.headless) [
130 "VBoxSDL"
131 "VirtualBoxVM"
132 ]);
133 in
134 lib.mkIf cfg.enableHardening (
135 builtins.listToAttrs (
136 map (x: {
137 name = x;
138 value = mkSuid x;
139 }) executables
140 )
141 );
142
143 users.groups.vboxusers.gid = config.ids.gids.vboxusers;
144
145 services.udev.extraRules = ''
146 SUBSYSTEM=="usb_device", ACTION=="add", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
147 SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
148 SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
149 SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
150 '';
151 }
152 (lib.mkIf cfg.enableKvm {
153 assertions = [
154 {
155 assertion = !cfg.addNetworkInterface;
156 message = "VirtualBox KVM only supports standard NAT networking for VMs. Please turn off virtualisation.virtualbox.host.addNetworkInterface.";
157 }
158 ];
159 })
160 (lib.mkIf (!cfg.enableKvm) {
161 boot.kernelModules = [
162 "vboxdrv"
163 "vboxnetadp"
164 "vboxnetflt"
165 ];
166 boot.extraModulePackages = [ kernelModules ];
167
168 services.udev.extraRules = ''
169 KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
170 KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666", TAG+="systemd"
171 KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
172 '';
173
174 # Since we lack the right setuid/setcap binaries, set up a host-only network by default.
175 })
176 (lib.mkIf cfg.addNetworkInterface {
177 systemd.services.vboxnet0 = {
178 description = "VirtualBox vboxnet0 Interface";
179 requires = [ "dev-vboxnetctl.device" ];
180 after = [ "dev-vboxnetctl.device" ];
181 wantedBy = [
182 "network.target"
183 "sys-subsystem-net-devices-vboxnet0.device"
184 ];
185 path = [ virtualbox ];
186 serviceConfig.RemainAfterExit = true;
187 serviceConfig.Type = "oneshot";
188 serviceConfig.PrivateTmp = true;
189 environment.VBOX_USER_HOME = "/tmp";
190 script = ''
191 if ! [ -e /sys/class/net/vboxnet0 ]; then
192 VBoxManage hostonlyif create
193 cat /tmp/VBoxSVC.log >&2
194 fi
195 '';
196 postStop = ''
197 VBoxManage hostonlyif remove vboxnet0
198 '';
199 };
200
201 networking.interfaces.vboxnet0.ipv4.addresses = [
202 {
203 address = "192.168.56.1";
204 prefixLength = 24;
205 }
206 ];
207 # Make sure NetworkManager won't assume this interface being up
208 # means we have internet access.
209 networking.networkmanager.unmanaged = [ "vboxnet0" ];
210 })
211 (lib.mkIf config.networking.useNetworkd {
212 systemd.network.networks."40-vboxnet0".extraConfig = ''
213 [Link]
214 RequiredForOnline=no
215 '';
216 })
217
218 ]
219 );
220}