aboutsummaryrefslogtreecommitdiff
path: root/modules/private/system/dilion
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2021-06-24 22:24:15 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2021-06-24 22:24:15 +0200
commit200690c9aecec1f38c1a62a65916df2950e1afe7 (patch)
tree6aa365dd4c7164016837ac1e728d7bb25a7ce2be /modules/private/system/dilion
parent6689bca19502aa8823dfc0fd3948e8e0a7cb9976 (diff)
downloadNix-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.nix146
-rw-r--r--modules/private/system/dilion/vms/base_configuration.nix21
-rw-r--r--modules/private/system/dilion/vms/base_image.nix94
-rw-r--r--modules/private/system/dilion/vms/buildbot_configuration.nix67
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:
3let
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";
43in
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 @@
1configuration_file: { pkgs ? import <nixpkgs> {}, system ? builtins.currentSystem, myEnv, ... }:
2let
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;
25in 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}