diff options
Diffstat (limited to 'systems/dilion/vms.nix')
-rw-r--r-- | systems/dilion/vms.nix | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/systems/dilion/vms.nix b/systems/dilion/vms.nix new file mode 100644 index 0000000..189e5ff --- /dev/null +++ b/systems/dilion/vms.nix | |||
@@ -0,0 +1,200 @@ | |||
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 | } | ||