Initial networking VM

Also general improvements around VMs
This commit is contained in:
Jack O'Sullivan 2022-05-16 00:05:02 +01:00
parent 5563d1be46
commit 009dec03cf
18 changed files with 487 additions and 111 deletions

2
.envrc
View File

@ -1,2 +1,2 @@
nix_direnv_watch_file devshell/{default,commands,install}.nix nix_direnv_watch_file devshell/{default,commands,install,vm-tasks}.nix
use flake use flake

View File

@ -46,7 +46,13 @@ in
name = "qemu-genmac"; name = "qemu-genmac";
category = "utilities"; category = "utilities";
help = "Generate MAC address suitable for QEMU"; help = "Generate MAC address suitable for QEMU";
command = ''printf "52:54:00:ab:%02x:%02x\n" $((RANDOM%256)) $((RANDOM%256))''; command = ''printf "52:54:00:%02x:%02x:%02x\n" $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256))'';
}
{
name = "ssh-get-ed25519";
category = "utilities";
help = "Print the ed25519 pubkey for a host";
command = "${pkgs.openssh}/bin/ssh-keyscan -t ed25519 \"$1\" 2> /dev/null | awk '{ print $2 \" \" $3 }'";
} }
{ {

View File

@ -3,7 +3,7 @@ let
inherit (lib.my) attrsToNVList; inherit (lib.my) attrsToNVList;
in in
{ {
imports = [ ./commands.nix ./install.nix ]; imports = [ ./commands.nix ./install.nix ./vm-tasks.nix ];
env = attrsToNVList { env = attrsToNVList {
# starship will show this # starship will show this
@ -13,6 +13,8 @@ in
'' ''
experimental-features = nix-command flakes ca-derivations experimental-features = nix-command flakes ca-derivations
''); '');
INSTALLER_SSH_OPTS = "-i .keys/deploy.key";
}; };
packages = with pkgs; [ packages = with pkgs; [

View File

@ -1,7 +1,7 @@
{ lib, pkgs, config, ... }: { lib, pkgs, config, ... }:
let let
inherit (lib) mapAttrsToList concatMapStringsSep; inherit (lib) mapAttrsToList;
inherit (lib.my) mkOpt' attrsToNVList; inherit (lib.my) mkOpt';
parseArgs = opts: parseArgs = opts:
'' ''
@ -45,10 +45,10 @@ let
log "[\e[36;1minfo\e[0m]: \e[36m$*\e[0m" log "[\e[36;1minfo\e[0m]: \e[36m$*\e[0m"
} }
warn() { warn() {
log "[\e[33;1minfo\e[0m]: \e[33m$*\e[0m" log "[\e[33;1mwarn\e[0m]: \e[33m$*\e[0m"
} }
error() { error() {
log "[\e[31;1minfo\e[0m]: \e[31m$*\e[0m" log "[\e[31;1merror\e[0m]: \e[31m$*\e[0m"
} }
die() { die() {
error "$@" error "$@"

128
devshell/vm-tasks.nix Normal file
View File

@ -0,0 +1,128 @@
{ lib, pkgs, config, ... }:
let
inherit (lib) mapAttrsToList;
inherit (lib.my) mkOpt;
parseArgs = opts:
''
POSITIONAL_ARGS=()
while [ $# -gt 0 ]; do
# shellcheck disable=SC2221,SC2222
case $1 in
${opts}
-*|--*)
die "Unknown option $1"
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
esac
done
set -- "''${POSITIONAL_ARGS[@]}" # restore positional parameters
'';
common = pkgs.writeShellApplication {
name = "vm-tasks-common.sh";
runtimeInputs = with pkgs; [
openssh
];
text =
''
: "''${VM_SSH_OPTS:=}"
IFS=" " read -ra SSH_OPTS <<< "$VM_SSH_OPTS"
SSH_OPTS+=(-N)
HOST="''${1:-}"
VM="''${2:-}"
if [ -z "$HOST" ] || [ -z "$VM" ]; then
echo "usage: $0 <host> <vm> ..." >&2
exit 1
fi
SOCKS_DIR="$(mktemp -d --tmpdir vm-socks.XXXXXX)"
cleanup() {
rm -rf "$SOCKS_DIR"
}
trap cleanup EXIT
SOCKS=()
closeSocks() {
for p in "''${SOCK_PIDS[@]}"; do
kill "$p"
done
}
openSock() {
local s="$SOCKS_DIR"/"$1".sock
ssh "''${SSH_OPTS[@]}" -L "$s":/run/vms/"$VM"/"$1".sock "$HOST" &
SOCKS+=($!)
echo "$s"
}
'';
};
vmTaskOpts = with lib.types; {
options = {
help = mkOpt str null;
script = mkOpt lines "";
packages = mkOpt (listOf package) [ ];
};
};
in
{
options.my.vmTasks = with lib.types;
mkOpt (attrsOf (submodule vmTaskOpts)) { };
config = {
my.vmTasks = {
vm-tty = {
help = "Access remote VM's TTY";
packages = with pkgs; [ minicom ];
script =
''
sock="$(openSock tty)"
minicom -D unix#"$sock"
closeSocks
'';
};
vm-monitor = {
help = "Access remote VM's QEMU monitor";
packages = with pkgs; [ minicom ];
script =
''
sock="$(openSock monitor)"
minicom -D unix#"$sock"
closeSocks
'';
};
vm-viewer = {
help = "Access remote VM's display with virt-viewer";
packages = with pkgs; [ virt-viewer ];
script =
''
sock=$(openSock spice)
remote-viewer spice+unix://"$sock"
closeSocks
'';
};
};
commands = mapAttrsToList (name: cmd: {
inherit name;
inherit (cmd) help;
category = "vm-tasks";
package = pkgs.writeShellApplication {
inherit name;
runtimeInputs = cmd.packages;
text =
''
# shellcheck disable=SC1091
source "${common}/bin/vm-tasks-common.sh"
${cmd.script}
'';
};
}) config.my.vmTasks;
};
}

View File

@ -95,6 +95,7 @@
nixos/installer.nix nixos/installer.nix
nixos/boxes/colony.nix nixos/boxes/colony.nix
nixos/vms/estuary.nix
nixos/containers/vaultwarden.nix nixos/containers/vaultwarden.nix
# Homes # Homes
@ -138,7 +139,7 @@
} // } //
(eachDefaultSystem (system: (eachDefaultSystem (system:
let let
pkgs = pkgs'.unstable.${system}; pkgs = pkgs'.mine.${system};
lib = pkgs.lib; lib = pkgs.lib;
in in
# Stuff for each platform # Stuff for each platform

View File

@ -6,43 +6,112 @@
configuration = { lib, pkgs, modulesPath, config, systems, ... }: configuration = { lib, pkgs, modulesPath, config, systems, ... }:
let let
inherit (lib) mkIf; inherit (lib) mkIf mapAttrs;
wanBDF =
if config.my.build.isDevVM then "00:02.0" else "01:00.0";
in in
{ {
imports = [ "${modulesPath}/profiles/qemu-guest.nix" ]; imports = [ "${modulesPath}/profiles/qemu-guest.nix" ];
boot.kernelParams = [ "intel_iommu=on" ];
boot.loader.systemd-boot.configurationLimit = 20;
fileSystems = {
"/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
"/nix" = {
device = "/dev/ssds/colony-nix";
fsType = "ext4";
};
"/persist" = {
device = "/dev/ssds/colony-persist";
fsType = "ext4";
neededForBoot = true;
};
};
services = {
lvm = {
boot.thin.enable = true;
dmeventd.enable = true;
};
};
environment.systemPackages = with pkgs; [
pciutils
];
networking = {
interfaces = mkIf (!config.my.build.isDevVM) {
enp10s0.useDHCP = true;
};
};
systemd = {
network = {
netdevs."25-base-bridge".netdevConfig = {
Name = "base";
Kind = "bridge";
};
networks."80-base-bridge" = {
matchConfig = {
Name = "base";
Driver = "bridge";
};
DHCP = "ipv4";
networkConfig = {
IPv6AcceptRA = true;
};
};
};
services."vm@estuary" = rec {
# Bind to the interface, networkd wait-online would deadlock...
requires = [ "sys-subsystem-net-devices-base.device" ];
bindsTo = requires;
};
};
#environment.etc."udev/udev.conf".text = "udev_log=debug";
#systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
virtualisation = {
cores = 8;
memorySize = 8192;
qemu.options = [
"-machine q35"
"-accel kvm,kernel-irqchip=split"
"-device intel-iommu,intremap=on,caching-mode=on"
];
};
my = { my = {
#deploy.generate.system.mode = "boot";
secrets = { secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINkqdN5t3UKwrNOOPKlbnG1WYhnkV5H9luAzMotr8SbT"; key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKp5WDdDr/1NS3SJIDOKwcCNZDFOxqPAD7cbZWAP7EkX";
files."test.txt" = {}; files."test.txt" = {};
}; };
firewall = {
trustedInterfaces = [ "virtual" ];
nat = {
externalInterface = "eth0";
forwardPorts = [
{
proto = "tcp";
sourcePort = 2222;
destination = "127.0.0.1:22";
}
];
};
};
server.enable = true; server.enable = true;
containers = { network = {
instances.vaultwarden = { ipv6 = "2a0e:97c0:4d1:0::2";
networking.bridge = "virtual"; ipv4 = "10.110.0.2";
};
}; };
firewall = {
trustedInterfaces = [ "base" ];
};
#containers = {
# instances.vaultwarden = {
# networking.bridge = "virtual";
# };
#};
vms = { vms = {
instances.test = { instances.estuary = {
uuid = "59f51efb-7e6d-477b-a263-ed9620dbc87b"; uuid = "59f51efb-7e6d-477b-a263-ed9620dbc87b";
networks.virtual.mac = "52:54:00:ab:f1:52"; networks.base.mac = "52:54:00:ab:f1:52";
drives = { drives = {
disk = { installer = {
backend = { backend = {
driver = "file"; driver = "file";
filename = "${systems.installer.configuration.config.my.buildAs.iso}/iso/nixos.iso"; filename = "${systems.installer.configuration.config.my.buildAs.iso}/iso/nixos.iso";
@ -50,62 +119,31 @@
}; };
format.driver = "raw"; format.driver = "raw";
frontend = "ide-cd"; frontend = "ide-cd";
frontendOpts = {
bootindex = 1;
};
};
disk = {
backend = {
driver = "host_device";
filename = "/dev/ssds/vm-estuary";
# It appears this needs to be set on the backend _and_ the format
discard = "unmap";
};
format = {
driver = "raw";
discard = "unmap";
};
frontend = "virtio-blk";
frontendOpts = { frontendOpts = {
bootindex = 0; bootindex = 0;
}; };
}; };
}; };
hostDevices."${wanBDF}" = { };
}; };
}; };
}; };
fileSystems = {
"/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
"/nix" = {
device = "/dev/disk/by-label/nix";
fsType = "ext4";
};
"/persist" = {
device = "/dev/disk/by-label/persist";
fsType = "ext4";
neededForBoot = true;
};
};
networking = {
interfaces = mkIf (!config.my.build.isDevVM) {
enp1s0.useDHCP = true;
};
};
systemd.network = {
netdevs."25-virtual-bridge".netdevConfig = {
Name = "virtual";
Kind = "bridge";
};
networks."80-virtual-bridge" = {
matchConfig = {
Name = "virtual";
Driver = "bridge";
};
networkConfig = {
Address = "172.16.137.1/24";
DHCPServer = true;
# TODO: Configuration for routed IPv6 (and maybe IPv4)
IPMasquerade = "both";
IPv6SendRA = true;
};
};
};
#systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
virtualisation = {
cores = 8;
memorySize = 8192;
};
}; };
}; };
} }

View File

@ -13,14 +13,14 @@
in in
{ {
imports = [ imports = [
# Lots of kernel modules and firmware
"${modulesPath}/profiles/all-hardware.nix"
# Useful tools to have # Useful tools to have
"${modulesPath}/profiles/base.nix" "${modulesPath}/profiles/base.nix"
]; ];
config = { config = {
my = { my = {
# Lots of kernel modules and firmware
build.allHardware = true;
# Whatever installer mechanism is chosen will provide an appropriate `/` # Whatever installer mechanism is chosen will provide an appropriate `/`
tmproot.enable = false; tmproot.enable = false;
firewall.nat.enable = false; firewall.nat.enable = false;
@ -72,9 +72,6 @@
''; '';
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
# We disable networking.useDHCP, so bring these in for the user
# dhcpcd probably has more features, but dhclient actually seems a bit more simple
(pkgs.writeShellScriptBin "dhclient" ''exec ${pkgs.dhcp}/bin/dhclient -v "$@"'')
dhcpcd dhcpcd
]; ];
@ -99,6 +96,7 @@
# download-using-manifests.pl from forking even if there is # download-using-manifests.pl from forking even if there is
# plenty of free memory. # plenty of free memory.
boot.kernel.sysctl."vm.overcommit_memory" = "1"; boot.kernel.sysctl."vm.overcommit_memory" = "1";
services.lvm.boot.thin.enable = true;
}; };
}; };
}; };

View File

@ -11,5 +11,6 @@
secrets = ./secrets.nix; secrets = ./secrets.nix;
containers = ./containers.nix; containers = ./containers.nix;
vms = ./vms.nix; vms = ./vms.nix;
network = ./network.nix;
}; };
} }

View File

@ -80,6 +80,7 @@ in
sharedDirectories = dummyOption; sharedDirectories = dummyOption;
cores = dummyOption; cores = dummyOption;
memorySize = dummyOption; memorySize = dummyOption;
qemu.options = dummyOption;
}; };
}; };

View File

@ -1,6 +1,6 @@
{ lib, pkgs, pkgs', inputs, config, ... }: { lib, pkgs, pkgs', inputs, config, ... }:
let let
inherit (lib) flatten optional mkIf mkDefault mkMerge; inherit (lib) mkIf mkDefault mkMerge;
inherit (lib.my) mkBoolOpt' dummyOption; inherit (lib.my) mkBoolOpt' dummyOption;
in in
{ {
@ -95,17 +95,11 @@ in
}; };
}; };
networking = {
domain = mkDefault "int.nul.ie";
useDHCP = false;
enableIPv6 = mkDefault true;
useNetworkd = mkDefault true;
};
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
bash-completion bash-completion
vim vim
ldns ldns
minicom
]; ];
programs = { programs = {
@ -141,14 +135,6 @@ in
}) })
]; ];
}) })
(mkIf config.my.build.isDevVM {
networking.interfaces.eth0.useDHCP = mkDefault true;
virtualisation = {
forwardPorts = flatten [
(optional config.services.openssh.openFirewall { from = "host"; host.port = 2222; guest.port = 22; })
];
};
})
]; ];
meta.buildDocsInSandbox = false; meta.buildDocsInSandbox = false;

View File

@ -1,11 +1,24 @@
{ lib, pkgs, config, systems, ... }: { lib, pkgs, config, systems, ... }:
let let
inherit (builtins) head attrNames; inherit (builtins) head attrNames;
inherit (lib) mkMerge mkIf mkDefault optionalAttrs mapAttrs'; inherit (lib) mkMerge mkIf mkDefault optionalAttrs mapAttrs' optionalString;
inherit (lib.my) mkOpt' mkBoolOpt'; inherit (lib.my) mkOpt' mkBoolOpt';
cfg = config.my.deploy; cfg = config.my.deploy;
# Based on https://github.com/serokell/deploy-rs/blob/master/flake.nix
nixosActivate = mode: base: (pkgs.deploy-rs.lib.activate.custom // { dryActivate = "$PROFILE/bin/switch-to-configuration dry-activate"; }) base.config.system.build.toplevel ''
# work around https://github.com/NixOS/nixpkgs/issues/73404
cd /tmp
$PROFILE/bin/switch-to-configuration ${mode}
# https://github.com/serokell/deploy-rs/issues/31
${with base.config.boot.loader;
optionalString ((mode == "switch" || mode == "boot") && systemd-boot.enable)
"sed -i '/^default /d' ${efi.efiSysMountPoint}/loader/loader.conf"}
'';
ctrProfiles = optionalAttrs cfg.generate.containers.enable (mapAttrs' (n: c: ctrProfiles = optionalAttrs cfg.generate.containers.enable (mapAttrs' (n: c:
let let
ctrConfig = systems."${n}".configuration.config; ctrConfig = systems."${n}".configuration.config;
@ -34,7 +47,10 @@ in
inherit (lib.my.deploy-rs) node; inherit (lib.my.deploy-rs) node;
generate = { generate = {
system.enable = mkBoolOpt' true "Whether to generate a deploy-rs profile for this system's config."; system = {
enable = mkBoolOpt' true "Whether to generate a deploy-rs profile for this system's config.";
mode = mkOpt' str "switch" "switch-to-configuration mode.";
};
containers.enable = mkBoolOpt' true "Whether to generate deploy-rs profiles for this system's containers."; containers.enable = mkBoolOpt' true "Whether to generate deploy-rs profiles for this system's containers.";
}; };
}; };
@ -49,7 +65,7 @@ in
profilesOrder = [ "system" ] ++ (attrNames ctrProfiles); profilesOrder = [ "system" ] ++ (attrNames ctrProfiles);
profiles = { profiles = {
system = mkIf cfg.generate.system.enable { system = mkIf cfg.generate.system.enable {
path = pkgs.deploy-rs.lib.activate.nixos { inherit config; }; path = nixosActivate cfg.generate.system.mode { inherit config; };
user = "root"; user = "root";
}; };

35
nixos/modules/network.nix Normal file
View File

@ -0,0 +1,35 @@
{ lib, config, ... }:
let
inherit (lib) flatten optional mkIf mkDefault mkMerge;
inherit (lib.my) mkOpt' mkBoolOpt';
cfg = config.my.network;
in
{
options = with lib.types; {
my.network = {
ipv4 = mkOpt' str null "Internal network IPv4 address.";
ipv6 = mkOpt' str null "Internal network IPv6 address.";
};
};
config = mkMerge [
{
networking = {
domain = mkDefault "int.nul.ie";
useDHCP = false;
enableIPv6 = mkDefault true;
useNetworkd = mkDefault true;
};
}
(mkIf config.my.build.isDevVM {
networking.interfaces.eth0.useDHCP = mkDefault true;
virtualisation = {
forwardPorts = flatten [
(optional config.services.openssh.openFirewall { from = "host"; host.port = 2222; guest.port = 22; })
];
};
})
];
}

View File

@ -22,15 +22,14 @@ in
# agenix sets this as a default but adding any custom extras will _replace_ the list (different priority) # agenix sets this as a default but adding any custom extras will _replace_ the list (different priority)
identityPaths = identityPaths =
mkIf config.services.openssh.enable mkIf config.services.openssh.enable
(map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys)); (map
# Use the persit dir to grab the keys instead, otherwise they might not be ready. We can't really make
# agenix depend on impermanence, since users depends on agenix (to decrypt passwords) and impermanence
# depends on users
(e: let pDir = config.my.tmproot.persistence.dir; in if pDir != null then "${pDir}/${e.path}" else e.path)
(lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys));
}; };
} }
(mkIf (config.age.secrets != { }) {
system.activationScripts.agenixMountSecrets.deps = mkIf (config.my.tmproot.persistence.dir != null) [
# The key used to decrypt is not going to exist!
"persist-files"
];
})
(mkIf config.my.build.isDevVM { (mkIf config.my.build.isDevVM {
age.identityPaths = [ cfg.vmKeyPath ]; age.identityPaths = [ cfg.vmKeyPath ];
}) })

View File

@ -1,6 +1,9 @@
{ lib, pkgs, config, ... }: { lib, pkgs, config, ... }:
let let
inherit (lib) optional optionals optionalString flatten concatStringsSep mapAttrsToList mapAttrs' mkIf mkDefault; inherit (builtins) filter any attrNames attrValues fetchGit;
inherit (lib)
unique optional optionals optionalString flatten concatStringsSep
concatMapStringsSep mapAttrsToList mapAttrs' mkIf mkDefault;
inherit (lib.my) mkOpt' mkBoolOpt'; inherit (lib.my) mkOpt' mkBoolOpt';
flattenQEMUOpts = attrs: flattenQEMUOpts = attrs:
@ -38,6 +41,29 @@ let
pass pass
''; '';
# TODO: Upstream or something...
vfio-pci-bind = pkgs.stdenv.mkDerivation rec {
pname = "vfio-pci-bind";
version = "b41e4545b21de434fc51a34a9bf1d72e3ac66cc8";
src = fetchGit {
url = "https://github.com/andre-richter/vfio-pci-bind";
rev = version;
};
prePatch = ''
substituteInPlace vfio-pci-bind.sh \
--replace modprobe ${pkgs.kmod}/bin/modprobe
substituteInPlace 25-vfio-pci-bind.rules \
--replace vfio-pci-bind.sh "$out"/bin/vfio-pci-bind.sh
'';
installPhase = ''
mkdir -p "$out"/bin/ "$out"/lib/udev/rules.d
cp vfio-pci-bind.sh "$out"/bin/
cp 25-vfio-pci-bind.rules "$out"/lib/udev/rules.d/
'';
};
cfg = config.my.vms; cfg = config.my.vms;
netOpts = with lib.types; { name, ... }: { netOpts = with lib.types; { name, ... }: {
@ -61,6 +87,12 @@ let
}; };
}; };
hostDevOpts = with lib.types; {
options = {
bindVFIO = mkBoolOpt' true "Whether to automatically bind the device to vfio-pci.";
};
};
vmOpts = with lib.types; { name, ... }: { vmOpts = with lib.types; { name, ... }: {
options = { options = {
qemuBin = mkOpt' path "${pkgs.qemu_kvm}/bin/qemu-kvm" "Path to QEMU executable."; qemuBin = mkOpt' path "${pkgs.qemu_kvm}/bin/qemu-kvm" "Path to QEMU executable.";
@ -85,9 +117,16 @@ let
spice.enable = mkBoolOpt' true "Whether to enable SPICE."; spice.enable = mkBoolOpt' true "Whether to enable SPICE.";
networks = mkOpt' (attrsOf (submodule netOpts)) { } "Networks to attach VM to."; networks = mkOpt' (attrsOf (submodule netOpts)) { } "Networks to attach VM to.";
drives = mkOpt' (attrsOf (submodule driveOpts)) { } "Drives to attach to VM."; drives = mkOpt' (attrsOf (submodule driveOpts)) { } "Drives to attach to VM.";
hostDevices = mkOpt' (attrsOf (submodule hostDevOpts)) { } "Host PCI devices to pass to the VM.";
}; };
}; };
allHostDevs =
flatten
(map
(i: mapAttrsToList (bdf: c: { inherit bdf; inherit (c) bindVFIO; }) i.hostDevices)
(attrValues cfg.instances));
mkQemuCommand = n: i: mkQemuCommand = n: i:
let let
flags = flags =
@ -122,7 +161,8 @@ let
"blockdev node-name=${dn}-backend,${c.backend}" "blockdev node-name=${dn}-backend,${c.backend}"
"blockdev node-name=${dn}-format,${c.formatBackendProp}=${dn}-backend,${c.format}" "blockdev node-name=${dn}-format,${c.formatBackendProp}=${dn}-backend,${c.format}"
("device ${c.frontend},id=${dn},drive=${dn}-format" + (extraQEMUOpts c.frontendOpts)) ("device ${c.frontend},id=${dn},drive=${dn}-format" + (extraQEMUOpts c.frontendOpts))
]) i.drives)); ]) i.drives)) ++
(map (bdf: "device vfio-pci,host=${bdf}") (attrNames i.hostDevices));
args = map (v: "-${v}") flags; args = map (v: "-${v}") flags;
in in
concatStringsSep " " ([ i.qemuBin ] ++ args); concatStringsSep " " ([ i.qemuBin ] ++ args);
@ -134,6 +174,30 @@ in
}; };
config = mkIf (cfg.instances != { }) { config = mkIf (cfg.instances != { }) {
assertions = [
{
assertion = let bdfs = map (d: d.bdf) allHostDevs; in (unique bdfs) == bdfs;
message = "VMs cannot share host devices!";
}
];
services.udev = {
packages =
optionals
(any (d: d.bindVFIO) allHostDevs)
[
vfio-pci-bind
(pkgs.writeTextDir
"etc/udev/rules.d/20-vfio-tags.rules"
(concatMapStringsSep
"\n"
(d: ''ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:${d.bdf}", TAG="vfio-pci-bind"'')
(filter (d: d.bindVFIO) allHostDevs)))
];
};
my.tmproot.persistence.config.directories = [ "/var/lib/vms" ];
# qemu-bridge-helper will fail otherwise # qemu-bridge-helper will fail otherwise
environment.etc."qemu/bridge.conf".text = "allow all"; environment.etc."qemu/bridge.conf".text = "allow all";
systemd = { systemd = {

101
nixos/vms/estuary.nix Normal file
View File

@ -0,0 +1,101 @@
{
nixos.systems.estuary = {
system = "x86_64-linux";
nixpkgs = "mine";
home-manager = "unstable";
configuration = { lib, pkgs, modulesPath, config, systems, ... }:
let
inherit (lib) mkIf mkMerge;
in
{
imports = [ "${modulesPath}/profiles/qemu-guest.nix" ];
config = mkMerge [
{
boot.kernelParams = [ "console=ttyS0,115200n8" ];
fileSystems = {
"/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
"/nix" = {
device = "/dev/main/nix";
fsType = "ext4";
};
"/persist" = {
device = "/dev/main/persist";
fsType = "ext4";
neededForBoot = true;
};
};
services = {
lvm = {
dmeventd.enable = true;
};
};
systemd.network = {
links = {
"10-wan" = {
matchConfig.MACAddress = "52:54:00:a1:b2:5f";
linkConfig.Name = "wan";
};
"10-base" = {
matchConfig.MACAddress = "52:54:00:ab:f1:52";
linkConfig.Name = "base";
};
};
networks = {
#"80-wan" = {
# matchConfig.Name = "wan";
# address = [
# "1.2.3.4/24"
# "2a00::2/64"
# ];
#};
"80-wan" = {
matchConfig.Name = "wan";
DHCP = "ipv4";
};
"80-base" = {
matchConfig.Name = "base";
address = with config.my.network; [ "${ipv4}/24" "${ipv6}/64" ];
networkConfig = {
DHCPServer = true;
IPv6SendRA = true;
IPMasquerade = "both";
};
};
};
};
my = {
server.enable = true;
network = {
ipv6 = "2a0e:97c0:4d1:0::1";
ipv4 = "10.110.0.1";
};
firewall = {
trustedInterfaces = [ "base" ];
nat = {
enable = true;
externalInterface = "wan";
};
};
};
}
(mkIf config.my.build.isDevVM {
systemd.network = {
netdevs."05-dummy-base".netdevConfig = {
Name = "base";
Kind = "dummy";
};
};
})
];
};
};
}

Binary file not shown.

Binary file not shown.