diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2021-06-24 22:24:15 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2021-06-24 22:24:15 +0200 |
commit | 200690c9aecec1f38c1a62a65916df2950e1afe7 (patch) | |
tree | 6aa365dd4c7164016837ac1e728d7bb25a7ce2be /modules/private/system/dilion | |
parent | 6689bca19502aa8823dfc0fd3948e8e0a7cb9976 (diff) | |
download | Nix-200690c9aecec1f38c1a62a65916df2950e1afe7.tar.gz Nix-200690c9aecec1f38c1a62a65916df2950e1afe7.tar.zst Nix-200690c9aecec1f38c1a62a65916df2950e1afe7.zip |
First attempt at making declarative VMs
In order to make buildbot more secure, the builds need to happen inside
VMs so that they can be thrown out on demand when not needed.
This commit implements this facility on dilion, and also defines
declaratively some previous VMs which used to run on the machine.
Diffstat (limited to 'modules/private/system/dilion')
-rw-r--r-- | modules/private/system/dilion/vms.nix | 146 | ||||
-rw-r--r-- | modules/private/system/dilion/vms/base_configuration.nix | 21 | ||||
-rw-r--r-- | modules/private/system/dilion/vms/base_image.nix | 94 | ||||
-rw-r--r-- | modules/private/system/dilion/vms/buildbot_configuration.nix | 67 |
4 files changed, 328 insertions, 0 deletions
diff --git a/modules/private/system/dilion/vms.nix b/modules/private/system/dilion/vms.nix new file mode 100644 index 0000000..8d5a57b --- /dev/null +++ b/modules/private/system/dilion/vms.nix | |||
@@ -0,0 +1,146 @@ | |||
1 | # inspired from https://nixos.wiki/wiki/Virtualization_in_NixOS | ||
2 | { config, pkgs, lib, ... }@args: | ||
3 | let | ||
4 | networks = { | ||
5 | immae = { | ||
6 | bridgeNumber = "1"; | ||
7 | ipRange = "192.168.100"; | ||
8 | }; | ||
9 | }; | ||
10 | guests = { | ||
11 | caldance = { | ||
12 | pool = "zfspool"; | ||
13 | cpus = "1"; | ||
14 | memory = "2"; | ||
15 | network = "immae"; | ||
16 | diskSize = "10GiB"; | ||
17 | extraDevicesXML = '' | ||
18 | <filesystem type="mount"> | ||
19 | <source dir="/var/lib/caldance"/> | ||
20 | <target dir="home"/> | ||
21 | </filesystem> | ||
22 | ''; | ||
23 | }; | ||
24 | buildbot = { | ||
25 | pool = "zfspool"; | ||
26 | cpus = "1"; | ||
27 | memory = "3"; | ||
28 | network = "immae"; | ||
29 | diskSize = "10GiB"; | ||
30 | destroyVolumeOnExit = true; | ||
31 | preStart = '' | ||
32 | if ! ${pkgs.libvirt}/bin/virsh pool-info --pool niximages &> /dev/null; then | ||
33 | pool-create-as --name niximages --type dir --target /etc/libvirtd/base-images/ | ||
34 | fi | ||
35 | if ! ${pkgs.libvirt}/bin/virsh pool-info --pool buildbot-disks &> /dev/null; then | ||
36 | mkdir -p /var/lib/libvirt/images/buildbot-disks | ||
37 | pool-create-as --name buildbot-disks --type dir --target /var/lib/libvirt/images/buildbot-disks | ||
38 | fi | ||
39 | ''; | ||
40 | }; | ||
41 | }; | ||
42 | toImage = f: "${import ./vms/base_image.nix f (args // { myEnv = config.myEnv; })}/nixos.qcow2"; | ||
43 | in | ||
44 | { | ||
45 | environment.etc."libvirtd/base-images/nixos.qcow2".source = toImage ./vms/base_configuration.nix; | ||
46 | environment.etc."libvirtd/base-images/buildbot.qcow2".source = toImage ./vms/buildbot_configuration.nix; | ||
47 | systemd.services = lib.mapAttrs' (name: guest: lib.nameValuePair "libvirtd-guest-${name}" { | ||
48 | after = [ "libvirtd.service" "libvirtd-network-${guest.network}.service" ]; | ||
49 | requires = [ "libvirtd.service" "libvirtd-network-${guest.network}.service" ]; | ||
50 | wantedBy = [ "multi-user.target" ]; | ||
51 | serviceConfig = { | ||
52 | Type = "oneshot"; | ||
53 | RemainAfterExit = "yes"; | ||
54 | }; | ||
55 | script = | ||
56 | let | ||
57 | xml = pkgs.writeText "libvirt-guest-${name}.xml" | ||
58 | '' | ||
59 | <domain type="kvm"> | ||
60 | <name>${name}</name> | ||
61 | <uuid>UUID</uuid> | ||
62 | <memory unit="GiB">${guest.memory}</memory> | ||
63 | <vcpu>${guest.cpus}</vcpu> | ||
64 | <os> | ||
65 | <type arch="x86_64">hvm</type> | ||
66 | </os> | ||
67 | <devices> | ||
68 | <emulator>/run/current-system/sw/bin/qemu-system-x86_64</emulator> | ||
69 | <disk type="volume"> | ||
70 | <source pool="${guest.pool}" volume="guest-${name}" /> | ||
71 | <target dev="vda" bus="virtio"/> | ||
72 | </disk> | ||
73 | ${guest.extraDevicesXML or ""} | ||
74 | <input type="keyboard" bus="usb"/> | ||
75 | <graphics type="vnc" port="-1" autoport="yes"/> | ||
76 | <interface type="network"> | ||
77 | <source network="${guest.network}" /> | ||
78 | </interface> | ||
79 | </devices> | ||
80 | <features> | ||
81 | <acpi/> | ||
82 | </features> | ||
83 | </domain> | ||
84 | ''; | ||
85 | in | ||
86 | guest.preStart or "" + '' | ||
87 | if ! ${pkgs.libvirt}/bin/virsh vol-key 'guest-${name}' --pool ${guest.pool} &> /dev/null; then | ||
88 | ${pkgs.libvirt}/bin/virsh vol-create-as --pool ${guest.pool} --name 'guest-${name}' --capacity '${guest.diskSize}' | ||
89 | volume_path=$(${pkgs.libvirt}/bin/virsh vol-path --pool ${guest.pool} --vol 'guest-${name}') | ||
90 | ${pkgs.qemu}/bin/qemu-img convert /etc/libvirtd/base-images/nixos.qcow2 $volume_path | ||
91 | fi | ||
92 | uuid="$(${pkgs.libvirt}/bin/virsh domuuid '${name}' || true)" | ||
93 | ${pkgs.libvirt}/bin/virsh define <(sed "s/UUID/$uuid/" '${xml}') | ||
94 | ${pkgs.libvirt}/bin/virsh start '${name}' | ||
95 | ''; | ||
96 | preStop = '' | ||
97 | ${pkgs.libvirt}/bin/virsh shutdown '${name}' | ||
98 | let "timeout = $(date +%s) + 10" | ||
99 | while [ "$(${pkgs.libvirt}/bin/virsh list --name | grep --count '^${name}$')" -gt 0 ]; do | ||
100 | if [ "$(date +%s)" -ge "$timeout" ]; then | ||
101 | # Meh, we warned it... | ||
102 | ${pkgs.libvirt}/bin/virsh destroy '${name}' | ||
103 | else | ||
104 | # The machine is still running, let's give it some time to shut down | ||
105 | sleep 0.5 | ||
106 | fi | ||
107 | done | ||
108 | '' + lib.optionalString (guest.destroyVolumeOnExit or false) '' | ||
109 | if ${pkgs.libvirt}/bin/virsh vol-key 'guest-${name}' --pool ${guest.pool} &> /dev/null; then | ||
110 | ${pkgs.libvirt}/bin/virsh vol-wipe --pool ${guest.pool} --vol 'guest-${name}' || true | ||
111 | ${pkgs.libvirt}/bin/virsh vol-delete --pool ${guest.pool} --vol 'guest-${name}' | ||
112 | fi | ||
113 | ''; | ||
114 | }) guests // (lib.mapAttrs' (name: network: lib.nameValuePair "libvirtd-network-${name}" { | ||
115 | after = [ "libvirtd.service" ]; | ||
116 | requires = [ "libvirtd.service" ]; | ||
117 | wantedBy = [ "multi-user.target" ]; | ||
118 | serviceConfig = { | ||
119 | Type = "oneshot"; | ||
120 | RemainAfterExit = "yes"; | ||
121 | }; | ||
122 | script = let | ||
123 | xml = pkgs.writeText "libvirt-network-${name}.xml" '' | ||
124 | <network> | ||
125 | <name>${name}</name> | ||
126 | <uuid>UUID</uuid> | ||
127 | <forward mode='nat' /> | ||
128 | <bridge name='virbr${network.bridgeNumber}' /> | ||
129 | <domain name='${name}' localOnly='yes'/> | ||
130 | <ip address='${network.ipRange}.1' netmask='255.255.255.0'> | ||
131 | <dhcp> | ||
132 | <range start='${network.ipRange}.2' end='${network.ipRange}.254'/> | ||
133 | </dhcp> | ||
134 | </ip> | ||
135 | </network> | ||
136 | ''; | ||
137 | in '' | ||
138 | uuid="$(${pkgs.libvirt}/bin/virsh net-uuid '${name}' || true)" | ||
139 | ${pkgs.libvirt}/bin/virsh net-define <(sed "s/UUID/$uuid/" '${xml}') | ||
140 | ${pkgs.libvirt}/bin/virsh net-start '${name}' | ||
141 | ''; | ||
142 | preStop = '' | ||
143 | ${pkgs.libvirt}/bin/virsh net-destroy '${name}' | ||
144 | ''; | ||
145 | }) networks); | ||
146 | } | ||
diff --git a/modules/private/system/dilion/vms/base_configuration.nix b/modules/private/system/dilion/vms/base_configuration.nix new file mode 100644 index 0000000..e2caba2 --- /dev/null +++ b/modules/private/system/dilion/vms/base_configuration.nix | |||
@@ -0,0 +1,21 @@ | |||
1 | { lib, config, ... }@args: | ||
2 | { | ||
3 | options.myEnv = (import ../../../environment.nix (args // { name = "dummy"; })).options.myEnv; | ||
4 | config = { | ||
5 | fileSystems."/".device = "/dev/disk/by-label/nixos"; | ||
6 | boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" "virtio_balloon" "virtio_blk" "virtio_pci" "virtio_ring" ]; | ||
7 | boot.loader = { | ||
8 | grub = { | ||
9 | version = 2; | ||
10 | device = "/dev/vda"; | ||
11 | }; | ||
12 | timeout = 0; | ||
13 | }; | ||
14 | services.openssh.enable = true; | ||
15 | networking.firewall.allowedTCPPorts = [ 22 ]; | ||
16 | users = { | ||
17 | mutableUsers = false; | ||
18 | users.root.openssh.authorizedKeys.keys = [ config.myEnv.sshd.rootKeys.immae_dilion ]; | ||
19 | }; | ||
20 | }; | ||
21 | } | ||
diff --git a/modules/private/system/dilion/vms/base_image.nix b/modules/private/system/dilion/vms/base_image.nix new file mode 100644 index 0000000..8de8560 --- /dev/null +++ b/modules/private/system/dilion/vms/base_image.nix | |||
@@ -0,0 +1,94 @@ | |||
1 | configuration_file: { pkgs ? import <nixpkgs> {}, system ? builtins.currentSystem, myEnv, ... }: | ||
2 | let | ||
3 | config = (import <nixpkgs/nixos/lib/eval-config.nix> { | ||
4 | inherit system; | ||
5 | modules = [ { | ||
6 | myEnv = myEnv; | ||
7 | imports = [ configuration_file ]; | ||
8 | |||
9 | # We want our template image to be as small as possible, but the deployed image should be able to be | ||
10 | # of any size. Hence we resize on the first boot. | ||
11 | systemd.services.resize-main-fs = { | ||
12 | wantedBy = [ "multi-user.target" ]; | ||
13 | serviceConfig.Type = "oneshot"; | ||
14 | script = | ||
15 | '' | ||
16 | # Resize main partition to fill whole disk | ||
17 | echo ", +" | ${pkgs.utillinux}/bin/sfdisk /dev/vda --no-reread -N 1 | ||
18 | ${pkgs.parted}/bin/partprobe | ||
19 | # Resize filesystem | ||
20 | ${pkgs.e2fsprogs}/bin/resize2fs /dev/vda1 | ||
21 | ''; | ||
22 | }; | ||
23 | } ]; | ||
24 | }).config; | ||
25 | in pkgs.vmTools.runInLinuxVM ( | ||
26 | pkgs.runCommand "nixos-base-image" | ||
27 | { | ||
28 | memSize = 768; | ||
29 | preVM = | ||
30 | '' | ||
31 | mkdir $out | ||
32 | diskImage=image.qcow2 | ||
33 | ${pkgs.vmTools.qemu}/bin/qemu-img create -f qcow2 $diskImage 2G | ||
34 | mv closure xchg/ | ||
35 | ''; | ||
36 | postVM = | ||
37 | '' | ||
38 | echo compressing VM image... | ||
39 | ${pkgs.vmTools.qemu}/bin/qemu-img convert -c $diskImage -O qcow2 $out/nixos.qcow2 | ||
40 | ''; | ||
41 | buildInputs = [ pkgs.utillinux pkgs.perl pkgs.parted pkgs.e2fsprogs ]; | ||
42 | exportReferencesGraph = | ||
43 | [ "closure" config.system.build.toplevel ]; | ||
44 | } | ||
45 | '' | ||
46 | # Create the partition | ||
47 | parted /dev/vda mklabel msdos | ||
48 | parted /dev/vda -- mkpart primary ext4 1M -1s | ||
49 | |||
50 | # Format the partition | ||
51 | mkfs.ext4 -L nixos /dev/vda1 | ||
52 | mkdir /mnt | ||
53 | mount /dev/vda1 /mnt | ||
54 | |||
55 | for dir in dev proc sys; do | ||
56 | mkdir /mnt/$dir | ||
57 | mount --bind /$dir /mnt/$dir | ||
58 | done | ||
59 | |||
60 | storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure) | ||
61 | echo filling Nix store... | ||
62 | mkdir -p /mnt/nix/store | ||
63 | set -f | ||
64 | cp -prd $storePaths /mnt/nix/store | ||
65 | # The permissions will be set up incorrectly if the host machine is not running NixOS | ||
66 | chown -R 0:30000 /mnt/nix/store | ||
67 | |||
68 | mkdir -p /mnt/etc/nix | ||
69 | echo 'build-users-group = ' > /mnt/etc/nix/nix.conf | ||
70 | |||
71 | # Register the paths in the Nix database. | ||
72 | export USER=root | ||
73 | printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \ | ||
74 | chroot /mnt ${config.nix.package.out}/bin/nix-store --load-db | ||
75 | |||
76 | # Create the system profile to allow nixos-rebuild to work. | ||
77 | chroot /mnt ${config.nix.package.out}/bin/nix-env \ | ||
78 | -p /nix/var/nix/profiles/system --set ${config.system.build.toplevel} | ||
79 | |||
80 | # `nixos-rebuild' requires an /etc/NIXOS. | ||
81 | mkdir -p /mnt/etc/nixos | ||
82 | touch /mnt/etc/NIXOS | ||
83 | |||
84 | # `switch-to-configuration' requires a /bin/sh | ||
85 | mkdir -p /mnt/bin | ||
86 | ln -s ${config.system.build.binsh}/bin/sh /mnt/bin/sh | ||
87 | |||
88 | # Generate the GRUB menu. | ||
89 | chroot /mnt ${config.system.build.toplevel}/bin/switch-to-configuration boot | ||
90 | |||
91 | umount /mnt/{proc,dev,sys} | ||
92 | umount /mnt | ||
93 | '' | ||
94 | ) | ||
diff --git a/modules/private/system/dilion/vms/buildbot_configuration.nix b/modules/private/system/dilion/vms/buildbot_configuration.nix new file mode 100644 index 0000000..05b02d4 --- /dev/null +++ b/modules/private/system/dilion/vms/buildbot_configuration.nix | |||
@@ -0,0 +1,67 @@ | |||
1 | { pkgs, config, lib, ... }: | ||
2 | { | ||
3 | imports = [ | ||
4 | <nixpkgs/nixos/modules/profiles/qemu-guest.nix> | ||
5 | ./base_configuration.nix | ||
6 | ]; | ||
7 | systemd.services.buildbot-worker.serviceConfig.ExecStartPre = let | ||
8 | cfg = config.services.buildbot-worker; | ||
9 | script = pkgs.writeScript "decode-dmi" '' | ||
10 | #!${pkgs.stdenv.shell} | ||
11 | |||
12 | mkdir -vp "${cfg.buildbotDir}" | ||
13 | varfile=${cfg.buildbotDir}/variables | ||
14 | rm $varfile || true | ||
15 | echo "[DEFAULT]" > $varfile | ||
16 | strings=$(${pkgs.dmidecode}/bin/dmidecode --oem-string count) | ||
17 | for i in $(seq 1 $strings); do | ||
18 | ${pkgs.dmidecode}/bin/dmidecode --oem-string $i >> $varfile | ||
19 | done | ||
20 | chown -R ${cfg.user}:${cfg.group} ${cfg.buildbotDir} | ||
21 | ''; | ||
22 | in | ||
23 | lib.mkForce ["+${script}"]; | ||
24 | systemd.services.buildbot-worker.serviceConfig.ExecStart = let | ||
25 | cfg = config.services.buildbot-worker; | ||
26 | tacFile = pkgs.writeText "buildbot-worker.tac" '' | ||
27 | import os | ||
28 | from io import open | ||
29 | |||
30 | from buildbot_worker.bot import Worker | ||
31 | from twisted.application import service | ||
32 | |||
33 | basedir = '${cfg.buildbotDir}' | ||
34 | |||
35 | # note: this line is matched against to check that this is a worker | ||
36 | # directory; do not edit it. | ||
37 | application = service.Application('buildbot-worker') | ||
38 | |||
39 | import configparser | ||
40 | config = config = configparser.ConfigParser() | ||
41 | config.read("${cfg.buildbotDir}/variables") | ||
42 | master_url_split = config["DEFAULT"]["buildbot_master_url"].split(':') | ||
43 | buildmaster_host = master_url_split[0] | ||
44 | port = int(master_url_split[1]) | ||
45 | workername = config["DEFAULT"]["buildbot_worker_name"] | ||
46 | |||
47 | with open('${cfg.workerPassFile}', 'r', encoding='utf-8') as passwd_file: | ||
48 | passwd = passwd_file.read().strip('\r\n') | ||
49 | keepalive = ${toString cfg.keepalive} | ||
50 | umask = None | ||
51 | maxdelay = 300 | ||
52 | numcpus = None | ||
53 | allow_shutdown = None | ||
54 | |||
55 | s = Worker(buildmaster_host, port, workername, passwd, basedir, | ||
56 | keepalive, umask=umask, maxdelay=maxdelay, | ||
57 | numcpus=numcpus, allow_shutdown=allow_shutdown) | ||
58 | s.setServiceParent(application) | ||
59 | ''; | ||
60 | in | ||
61 | lib.mkForce "${cfg.package.pythonModule.pkgs.twisted}/bin/twistd --nodaemon --pidfile= --logfile - --python ${tacFile}"; | ||
62 | services.buildbot-worker = { | ||
63 | enable = true; | ||
64 | workerPass = config.myEnv.buildbot.workerPassword; | ||
65 | packages = [ pkgs.git pkgs.gzip pkgs.openssh ]; | ||
66 | }; | ||
67 | } | ||