184 lines
5.6 KiB
Nix
184 lines
5.6 KiB
Nix
{ lib, config, pkgs, options, extendModules, modulesPath, ... }:
|
|
let
|
|
# Flatten the list of state folders into a single list
|
|
stateFolders = lib.flatten (
|
|
lib.mapAttrsToList
|
|
(_item: attrs: attrs.folders)
|
|
config.clanCore.state
|
|
);
|
|
|
|
# Ensure sane mount order by topo-sorting
|
|
sortedStateFolders =
|
|
let
|
|
sorted = lib.toposort lib.hasPrefix stateFolders;
|
|
in
|
|
sorted.result or (
|
|
throw ''
|
|
The state folders have a cyclic dependency.
|
|
This is not allowed.
|
|
The cyclic dependencies are:
|
|
- ${lib.concatStringsSep "\n - " sorted.loops}
|
|
''
|
|
);
|
|
|
|
vmModule = {
|
|
imports = [
|
|
(modulesPath + "/virtualisation/qemu-vm.nix")
|
|
./serial.nix
|
|
];
|
|
|
|
# required for issuing shell commands via qga
|
|
services.qemuGuest.enable = true;
|
|
|
|
boot.initrd.systemd.enable = true;
|
|
systemd.sysusers.enable = true;
|
|
system.etc.overlay.enable = true;
|
|
|
|
# currently needed for system.etc.overlay.enable
|
|
boot.kernelPackages = pkgs.linuxPackages_latest;
|
|
|
|
boot.initrd.systemd.storePaths = [ pkgs.util-linux pkgs.e2fsprogs ];
|
|
# Ensures, that all state paths will be persisted across reboots
|
|
# - Mounts the state.qcow2 disk to /vmstate.
|
|
# - Binds directories from /vmstate/{some-path} to /{some-path}.
|
|
boot.initrd.systemd.services.rw-etc-pre = {
|
|
unitConfig = {
|
|
DefaultDependencies = false;
|
|
RequiresMountsFor = "/sysroot /dev";
|
|
};
|
|
requiredBy = [ "rw-etc.service" ];
|
|
before = [ "rw-etc.service" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
};
|
|
script = ''
|
|
mkdir -p -m 0755 \
|
|
/sysroot/vmstate \
|
|
/sysroot/.rw-etc \
|
|
/sysroot/vmstate/.rw-etc
|
|
|
|
${pkgs.util-linux}/bin/blkid /dev/vdb || ${pkgs.e2fsprogs}/bin/mkfs.ext4 /dev/vdb
|
|
sync
|
|
mount /dev/vdb /sysroot/vmstate
|
|
|
|
mount --bind /sysroot/vmstate/.rw-etc /sysroot/.rw-etc
|
|
|
|
for folder in "${lib.concatStringsSep ''" "'' sortedStateFolders}"; do
|
|
mkdir -p -m 0755 "/sysroot/vmstate/$folder" "/sysroot/$folder"
|
|
mount --bind "/sysroot/vmstate/$folder" "/sysroot/$folder"
|
|
done
|
|
'';
|
|
};
|
|
virtualisation.fileSystems = {
|
|
${config.clanCore.secretsUploadDirectory} = lib.mkForce {
|
|
device = "secrets";
|
|
fsType = "9p";
|
|
neededForBoot = true;
|
|
options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ];
|
|
};
|
|
};
|
|
};
|
|
|
|
# We cannot simply merge the VM config into the current system config, because
|
|
# it is not necessarily a VM.
|
|
# Instead we use extendModules to create a second instance of the current
|
|
# system configuration, and then merge the VM config into that.
|
|
vmConfig = extendModules {
|
|
modules = [ vmModule ];
|
|
};
|
|
in
|
|
{
|
|
options = {
|
|
clan.virtualisation = {
|
|
cores = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 1;
|
|
description = lib.mdDoc ''
|
|
Specify the number of cores the guest is permitted to use.
|
|
The number can be higher than the available cores on the
|
|
host system.
|
|
'';
|
|
};
|
|
|
|
memorySize = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 1024;
|
|
description = lib.mdDoc ''
|
|
The memory size in megabytes of the virtual machine.
|
|
'';
|
|
};
|
|
|
|
graphics = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = lib.mdDoc ''
|
|
Whether to run QEMU with a graphics window, or in nographic mode.
|
|
Serial console will be enabled on both settings, but this will
|
|
change the preferred console.
|
|
'';
|
|
};
|
|
};
|
|
# All important VM config variables needed by the vm runner
|
|
# this is really just a remapping of values defined elsewhere
|
|
# and therefore not intended to be set by the user
|
|
clanCore.vm.inspect = {
|
|
clan_name = lib.mkOption {
|
|
type = lib.types.str;
|
|
internal = true;
|
|
readOnly = true;
|
|
description = ''
|
|
the name of the clan
|
|
'';
|
|
};
|
|
memory_size = lib.mkOption {
|
|
type = lib.types.int;
|
|
internal = true;
|
|
readOnly = true;
|
|
description = ''
|
|
the amount of memory to allocate to the vm
|
|
'';
|
|
};
|
|
cores = lib.mkOption {
|
|
type = lib.types.int;
|
|
internal = true;
|
|
readOnly = true;
|
|
description = ''
|
|
the number of cores to allocate to the vm
|
|
'';
|
|
};
|
|
graphics = lib.mkOption {
|
|
type = lib.types.bool;
|
|
internal = true;
|
|
readOnly = true;
|
|
description = ''
|
|
whether to enable graphics for the vm
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
config = {
|
|
# for clan vm inspect
|
|
clanCore.vm.inspect = {
|
|
clan_name = config.clanCore.clanName;
|
|
memory_size = config.clan.virtualisation.memorySize;
|
|
inherit (config.clan.virtualisation) cores graphics;
|
|
};
|
|
# for clan vm create
|
|
system.clan.vm = {
|
|
create = pkgs.writeText "vm.json" (builtins.toJSON {
|
|
initrd = "${vmConfig.config.system.build.initialRamdisk}/${vmConfig.config.system.boot.loader.initrdFile}";
|
|
toplevel = vmConfig.config.system.build.toplevel;
|
|
regInfo = (pkgs.closureInfo { rootPaths = vmConfig.config.virtualisation.additionalPaths; });
|
|
inherit (config.clan.virtualisation) memorySize cores graphics;
|
|
});
|
|
};
|
|
|
|
virtualisation = lib.optionalAttrs (options.virtualisation ? cores) {
|
|
memorySize = lib.mkDefault config.clan.virtualisation.memorySize;
|
|
graphics = lib.mkDefault config.clan.virtualisation.graphics;
|
|
cores = lib.mkDefault config.clan.virtualisation.cores;
|
|
};
|
|
};
|
|
}
|