1# Systemd services for libvirtd.
2
3{ config, lib, pkgs, ... }:
4
5with lib;
6
7let
8
9 cfg = config.virtualisation.libvirtd;
10 vswitch = config.virtualisation.vswitch;
11 configFile = pkgs.writeText "libvirtd.conf" ''
12 unix_sock_group = "libvirtd"
13 unix_sock_rw_perms = "0770"
14 auth_unix_ro = "none"
15 auth_unix_rw = "none"
16 ${cfg.extraConfig}
17 '';
18
19in
20
21{
22 ###### interface
23
24 options = {
25
26 virtualisation.libvirtd.enable =
27 mkOption {
28 type = types.bool;
29 default = false;
30 description =
31 ''
32 This option enables libvirtd, a daemon that manages
33 virtual machines. Users in the "libvirtd" group can interact with
34 the daemon (e.g. to start or stop VMs) using the
35 <command>virsh</command> command line tool, among others.
36 '';
37 };
38
39 virtualisation.libvirtd.enableKVM =
40 mkOption {
41 type = types.bool;
42 default = true;
43 description =
44 ''
45 This option enables support for QEMU/KVM in libvirtd.
46 '';
47 };
48
49 virtualisation.libvirtd.extraConfig =
50 mkOption {
51 type = types.lines;
52 default = "";
53 description =
54 ''
55 Extra contents appended to the libvirtd configuration file,
56 libvirtd.conf.
57 '';
58 };
59
60 virtualisation.libvirtd.extraOptions =
61 mkOption {
62 type = types.listOf types.str;
63 default = [ ];
64 example = [ "--verbose" ];
65 description =
66 ''
67 Extra command line arguments passed to libvirtd on startup.
68 '';
69 };
70
71 virtualisation.libvirtd.onShutdown =
72 mkOption {
73 type = types.enum ["shutdown" "suspend" ];
74 default = "suspend";
75 description =
76 ''
77 When shutting down / restarting the host what method should
78 be used to gracefully halt the guests. Setting to "shutdown"
79 will cause an ACPI shutdown of each guest. "suspend" will
80 attempt to save the state of the guests ready to restore on boot.
81 '';
82 };
83
84
85 };
86
87
88 ###### implementation
89
90 config = mkIf cfg.enable {
91
92 environment.systemPackages =
93 [ pkgs.libvirt pkgs.netcat-openbsd ]
94 ++ optional cfg.enableKVM pkgs.qemu_kvm;
95
96 boot.kernelModules = [ "tun" ];
97
98 systemd.services.libvirtd =
99 { description = "Libvirt Virtual Machine Management Daemon";
100
101 wantedBy = [ "multi-user.target" ];
102 after = [ "systemd-udev-settle.service" ]
103 ++ optional vswitch.enable "vswitchd.service";
104
105 path = [
106 pkgs.bridge-utils
107 pkgs.dmidecode
108 pkgs.dnsmasq
109 pkgs.ebtables
110 ]
111 ++ optional cfg.enableKVM pkgs.qemu_kvm
112 ++ optional vswitch.enable vswitch.package;
113
114 preStart =
115 ''
116 mkdir -p /var/log/libvirt/qemu -m 755
117 rm -f /var/run/libvirtd.pid
118
119 mkdir -p /var/lib/libvirt
120 mkdir -p /var/lib/libvirt/dnsmasq
121
122 chmod 755 /var/lib/libvirt
123 chmod 755 /var/lib/libvirt/dnsmasq
124
125 # Libvirt unfortunately writes mutable state (such as
126 # runtime changes to VM, network or filter configurations)
127 # to /etc. So we can't use environment.etc to make the
128 # default network and filter definitions available, since
129 # libvirt will then modify the originals in the Nix store.
130 # So here we copy them instead. Ugly.
131 for i in $(cd ${pkgs.libvirt}/etc && echo \
132 libvirt/qemu/networks/*.xml libvirt/qemu/networks/autostart/*.xml \
133 libvirt/nwfilter/*.xml );
134 do
135 mkdir -p /etc/$(dirname $i) -m 755
136 cp -fpd ${pkgs.libvirt}/etc/$i /etc/$i
137 done
138
139 # libvirtd puts the full path of the emulator binary in the machine
140 # config file. But this path can unfortunately be garbage collected
141 # while still being used by the virtual machine. So update the
142 # emulator path on each startup to something valid (re-scan $PATH).
143 for file in /etc/libvirt/qemu/*.xml /etc/libvirt/lxc/*.xml; do
144 test -f "$file" || continue
145 # get (old) emulator path from config file
146 emulator=$(grep "^[[:space:]]*<emulator>" "$file" | sed 's,^[[:space:]]*<emulator>\(.*\)</emulator>.*,\1,')
147 # get a (definitely) working emulator path by re-scanning $PATH
148 new_emulator=$(PATH=${pkgs.libvirt}/libexec:$PATH command -v $(basename "$emulator"))
149 # write back
150 sed -i "s,^[[:space:]]*<emulator>.*, <emulator>$new_emulator</emulator> <!-- WARNING: emulator dirname is auto-updated by the nixos libvirtd module -->," "$file"
151 done
152 ''; # */
153
154 serviceConfig.ExecStart = ''@${pkgs.libvirt}/sbin/libvirtd libvirtd --config "${configFile}" --daemon ${concatStringsSep " " cfg.extraOptions}'';
155 serviceConfig.Type = "forking";
156 serviceConfig.KillMode = "process"; # when stopping, leave the VMs alone
157
158 # Wait until libvirtd is ready to accept requests.
159 postStart =
160 ''
161 for ((i = 0; i < 60; i++)); do
162 if ${pkgs.libvirt}/bin/virsh list > /dev/null; then exit 0; fi
163 sleep 1
164 done
165 exit 1 # !!! seems to be ignored
166 '';
167 };
168
169 jobs."libvirt-guests" =
170 { description = "Libvirt Virtual Machines";
171
172 wantedBy = [ "multi-user.target" ];
173 wants = [ "libvirtd.service" ];
174 after = [ "libvirtd.service" ];
175
176 restartIfChanged = false;
177
178 path = [ pkgs.gettext pkgs.libvirt pkgs.gawk ];
179
180 preStart =
181 ''
182 mkdir -p /var/lock/subsys -m 755
183 ${pkgs.libvirt}/etc/rc.d/init.d/libvirt-guests start || true
184 '';
185
186 postStop =
187 ''
188 export PATH=${pkgs.gettext}/bin:$PATH
189 export ON_SHUTDOWN=${cfg.onShutdown}
190 ${pkgs.libvirt}/etc/rc.d/init.d/libvirt-guests stop
191 '';
192
193 serviceConfig.Type = "oneshot";
194 serviceConfig.RemainAfterExit = true;
195 };
196
197 users.extraGroups.libvirtd.gid = config.ids.gids.libvirtd;
198
199 };
200
201}