]> git.immae.eu Git - perso/Immae/Config/Nix.git/blob - systems/dilion/vms.nix
Squash changes containing private information
[perso/Immae/Config/Nix.git] / systems / dilion / vms.nix
1 # inspired from https://nixos.wiki/wiki/Virtualization_in_NixOS
2 { config, pkgs, lib, pkgs-no-overlay, ... }@args:
3 let
4 toImage = f: "${import ./vms/base_image.nix f (args // { myEnv = config.myEnv; })}/nixos.qcow2";
5 in
6 {
7 options = {
8 myServices.vms.libvirt-guests = lib.mkOption {
9 type = lib.types.attrsOf (lib.types.submodule {
10 options = {
11 network = lib.mkOption { type = lib.types.str; description = "network to attach the guest to"; };
12 pool = lib.mkOption { type = lib.types.str; description = "pool to attach the guest to"; };
13 cpus = lib.mkOption { type = lib.types.int; default = 1; description = "number of cpus to assign"; };
14 memory = lib.mkOption { type = lib.types.int; description = "memory in GiB to assign"; };
15 diskSize = lib.mkOption { type = lib.types.int; description = "disk size in GiB"; };
16 destroyVolumeOnExit = lib.mkOption { type = lib.types.bool; description = "Whether to destroy the volume on exit"; default = false; };
17 extraDevicesXML = lib.mkOption { type = lib.types.lines; description = "Extra device configuration"; default = ""; };
18 preStart = lib.mkOption { type = lib.types.lines; default = ""; description = "Script to run as prestart"; };
19 };
20 });
21 default = {};
22 description = "Libvirt guests to start";
23 };
24 myServices.vms.libvirt-networks = lib.mkOption {
25 type = lib.types.attrsOf (lib.types.submodule {
26 options = {
27 bridgeNumber = lib.mkOption { type = lib.types.int; description = "bridge interface to create virbr<nbr>"; };
28 ipRange = lib.mkOption { type = lib.types.str; example = "192.168.100"; description = "ip4 prefix to use"; };
29 };
30 });
31 description = "Libvirt networks to configure";
32 default = {};
33 };
34 myServices.vms.libvirt-pools = lib.mkOption {
35 type = lib.types.attrsOf (lib.types.submodule {
36 options = {
37 type = lib.mkOption { type = lib.types.enum [ "dir" "zfs" ]; description = "Pool type"; };
38 target = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "where to find images"; };
39 preStart = lib.mkOption { type = lib.types.lines; default = ""; description = "Script to run as prestart"; };
40 xml = lib.mkOption { type = lib.types.lines; default = ""; description = "Additional configuration"; };
41 };
42 });
43 };
44 myServices.vms.libvirt-images = lib.mkOption {
45 type = lib.types.attrsOf lib.types.path;
46 default = {};
47 description = "Attrs of images to create in /etc/libvirtd/base-images";
48 };
49 };
50 config = lib.mkMerge [
51 # Define images
52 {
53 environment.etc = lib.mapAttrs'
54 (n: v: lib.nameValuePair "libvirtd/base-images/${n}.qcow2" { source = toImage v; })
55 config.myServices.vms.libvirt-images;
56 }
57
58 # Define networks
59 {
60 systemd.services = lib.mapAttrs' (name: network: lib.nameValuePair "libvirtd-network-${name}" {
61 after = [ "libvirtd.service" ];
62 requires = [ "libvirtd.service" ];
63 wantedBy = [ "multi-user.target" ];
64 serviceConfig = {
65 Type = "oneshot";
66 RemainAfterExit = "yes";
67 };
68 path = [ config.boot.zfs.package ];
69 script = let
70 xml = pkgs.writeText "libvirt-network-${name}.xml" ''
71 <network>
72 <name>${name}</name>
73 <uuid>UUID</uuid>
74 <forward mode='nat' />
75 <bridge name='virbr${builtins.toString network.bridgeNumber}' />
76 <domain name='${name}' localOnly='yes'/>
77 <ip address='${network.ipRange}.1' netmask='255.255.255.0'>
78 <dhcp>
79 <range start='${network.ipRange}.2' end='${network.ipRange}.254'/>
80 </dhcp>
81 </ip>
82 </network>
83 '';
84 in ''
85 uuid="$(${pkgs.libvirt}/bin/virsh net-uuid '${name}' || true)"
86 ${pkgs.libvirt}/bin/virsh net-define <(sed "s/UUID/$uuid/" '${xml}')
87 ${pkgs.libvirt}/bin/virsh net-start '${name}'
88 '';
89 preStop = ''
90 ${pkgs.libvirt}/bin/virsh net-destroy '${name}'
91 '';
92 }) config.myServices.vms.libvirt-networks;
93 }
94
95 # Define pools
96 {
97 systemd.services = lib.mapAttrs' (name: pool: lib.nameValuePair "libvirtd-pool-${name}" {
98 after = [ "libvirtd.service" ];
99 requires = [ "libvirtd.service" ];
100 wantedBy = [ "multi-user.target" ];
101 serviceConfig = {
102 Type = "oneshot";
103 RemainAfterExit = "yes";
104 };
105 path = [ config.boot.zfs.package ];
106 script = let
107 xml = pkgs.writeText "libvirt-pool-${name}.xml" ''
108 <pool type="${pool.type}">
109 <name>${name}</name>
110 <uuid>UUID</uuid>
111 ${pool.xml}
112 ${if pool.target != null then ''
113 <target>
114 <path>${pool.target}</path>
115 </target>
116 '' else ""}
117 </pool>
118 '';
119 in pool.preStart + ''
120 uuid="$(${pkgs.libvirt}/bin/virsh pool-uuid '${name}' || true)"
121 ${pkgs.libvirt}/bin/virsh pool-define <(sed "s/UUID/$uuid/" '${xml}')
122 ${pkgs.libvirt}/bin/virsh pool-start '${name}' || true
123 '';
124 }) config.myServices.vms.libvirt-pools;
125 }
126
127 # Define guests
128 {
129 systemd.services = lib.mapAttrs' (name: guest: lib.nameValuePair "libvirtd-guest-${name}" {
130 after = [ "libvirtd.service" "libvirtd-pool-${guest.pool}.service" "libvirtd-network-${guest.network}.service" ];
131 requires = [ "libvirtd.service" "libvirtd-pool-${guest.pool}.service" "libvirtd-network-${guest.network}.service" ];
132 wantedBy = [ "multi-user.target" ];
133 serviceConfig = {
134 Type = "oneshot";
135 RemainAfterExit = "yes";
136 };
137 path = [ config.boot.zfs.package ];
138 script =
139 let
140 xml = pkgs.writeText "libvirt-guest-${name}.xml"
141 ''
142 <domain type="kvm">
143 <name>${name}</name>
144 <uuid>UUID</uuid>
145 <memory unit="GiB">${builtins.toString guest.memory}</memory>
146 <vcpu>${builtins.toString guest.cpus}</vcpu>
147 <os>
148 <type arch="x86_64">hvm</type>
149 </os>
150 <devices>
151 <emulator>/run/current-system/sw/bin/qemu-system-x86_64</emulator>
152 <disk type="volume">
153 <source pool="${guest.pool}" volume="guest-${name}" />
154 <target dev="vda" bus="virtio"/>
155 </disk>
156 ${guest.extraDevicesXML}
157 <input type="keyboard" bus="usb"/>
158 <graphics type="vnc" port="-1" autoport="yes"/>
159 <interface type="network">
160 <source network="${guest.network}" />
161 </interface>
162 </devices>
163 <features>
164 <acpi/>
165 </features>
166 </domain>
167 '';
168 in
169 guest.preStart + ''
170 if ! ${pkgs.libvirt}/bin/virsh vol-key 'guest-${name}' --pool ${guest.pool} &> /dev/null; then
171 ${pkgs.libvirt}/bin/virsh vol-create-as --pool ${guest.pool} --name 'guest-${name}' --capacity '${builtins.toString guest.diskSize}GiB'
172 volume_path=$(${pkgs.libvirt}/bin/virsh vol-path --pool ${guest.pool} --vol 'guest-${name}')
173 ${pkgs-no-overlay.qemu}/bin/qemu-img convert /etc/libvirtd/base-images/nixos.qcow2 $volume_path
174 fi
175 uuid="$(${pkgs.libvirt}/bin/virsh domuuid '${name}' || true)"
176 ${pkgs.libvirt}/bin/virsh define <(sed "s/UUID/$uuid/" '${xml}')
177 ${pkgs.libvirt}/bin/virsh start '${name}'
178 '';
179 preStop = ''
180 ${pkgs.libvirt}/bin/virsh shutdown '${name}'
181 let "timeout = $(date +%s) + 10"
182 while [ "$(${pkgs.libvirt}/bin/virsh list --name | grep --count '^${name}$')" -gt 0 ]; do
183 if [ "$(date +%s)" -ge "$timeout" ]; then
184 # Meh, we warned it...
185 ${pkgs.libvirt}/bin/virsh destroy '${name}'
186 else
187 # The machine is still running, let's give it some time to shut down
188 sleep 0.5
189 fi
190 done
191 '' + lib.optionalString guest.destroyVolumeOnExit ''
192 if ${pkgs.libvirt}/bin/virsh vol-key 'guest-${name}' --pool ${guest.pool} &> /dev/null; then
193 ${pkgs.libvirt}/bin/virsh vol-wipe --pool ${guest.pool} --vol 'guest-${name}' || true
194 ${pkgs.libvirt}/bin/virsh vol-delete --pool ${guest.pool} --vol 'guest-${name}'
195 fi
196 '';
197 }) config.myServices.vms.libvirt-guests;
198 }
199 ];
200 }