diff --git a/nixos/default.nix b/nixos/default.nix index d1bc2e3..1ff2390 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -5,5 +5,5 @@ let args = { inherit config pkgs lib vmixLib; }; in { - imports = [ (import ./network args) ]; + imports = [ (import ./network args) (import ./vm args) ]; } \ No newline at end of file diff --git a/nixos/vm/config.nix b/nixos/vm/config.nix new file mode 100644 index 0000000..8563e4c --- /dev/null +++ b/nixos/vm/config.nix @@ -0,0 +1,140 @@ +{ config, pkgs, lib, vmixLib, ... }: +with lib; +with vmixLib.network; +let + vmixCfg = config.vmix; + + mkServices4aVM = name: cfg: + let + vmCfg = cfg // { inherit name; }; + netName = head (attrNames vmCfg.network.vmix); + netCfg = vmCfg.network.vmix.${netName} // { name = netName; }; + + mkTap4aLan = lanName: tapCfg: + let + tapInterfaceName = "vt-${vmCfg.name}-${lanName}"; + lanInterfaceName = "brx-${lanName}"; + in + { + name = lanName; + iface = tapInterfaceName; + create = '' + ip tuntap add dev ${tapInterfaceName} mode tap + ip link set dev ${tapInterfaceName} up + ip link set dev ${tapInterfaceName} master ${lanInterfaceName} + ''; + delete = '' + ip link del ${tapInterfaceName} + ''; + }; + + mkMacvtap = macvtapName: macvtapCfg: + let + macvtapNetworkCfg = config.vmix.networks.${netCfg.name}.bridges.macvtaps.${macvtapName}; + macvtapInterfaceName = "mt-${vmCfg.name}-${macvtapNetworkCfg.uplink.iface}"; + in + { + name = macvtapName; + iface = macvtapInterfaceName; + create = '' + ip link add link ${macvtapNetworkCfg.uplink.iface} name ${macvtapInterfaceName} type macvtap mode bridge + ip link set ${macvtapInterfaceName} netns ${netName}.vmix + ''; + delete = '' + ip netns exec ${netName}.vmix ip link del ${macvtapInterfaceName} + ''; + }; + + allTaps = (mapAttrsToList mkTap4aLan netCfg.lans); + allMacvtaps = (mapAttrsToList mkMacvtap netCfg.macvtaps); + + createTapsforLansScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" ( + concatStringsSep "\n" (builtins.map (tap: tap.create) allTaps) + ); + + deleteTapsforLansScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" ( + concatStringsSep "\n" (builtins.map (tap: tap.delete) allTaps) + ); + + createMacvapsScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" ( + concatStringsSep "\n" (builtins.map (macvtap: macvtap.create) allMacvtaps) + ); + + deleteMacvapsScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" ( + concatStringsSep "\n" (builtins.map (macvtap: macvtap.delete) allMacvtaps) + ); + + osImage = vmixLib.customizeImage vmCfg.disks.os.file { name = vmCfg.name; }; + + qemuStartVMScript = pkgs.writeShellScript "${vmCfg.name}-qemu-vmix" '' + exec qemu-system-${vmCfg.arch} \ + -nographic \ + ${optionalString vmCfg.kvm "-accel kvm"} \ + -name ${vmCfg.name} \ + -m ${toString vmCfg.mem.size} \ + -smp cores=${toString vmCfg.cpu.cores} \ + -cpu ${vmCfg.cpu.model} \ + -machine type=${vmCfg.pc.type} \ + ${optionalString vmCfg.bios.efi "-bios ${pkgs.OVMF.fd}/FV/OVMF.fd"} \ + ${optionalString vmCfg.bios.tpm "-chardev socket,id=chrtpm,path=/tmp/mytpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"} \ + -drive file=${toString osImage},format=qcow2,if=virtio${optionalString (vmCfg.disks.os.persist == false) ",snapshot=on"} \ + ${optionalString (vmCfg.disks.iso.file != null) "-drive file=${toString vmCfg.disks.iso.file},media=cdrom,readonly=on"} \ + ${concatMapStrings (diskCfg: '' + -drive file=${diskCfg.file},format=qcow2,if=${toString vmCfg.disks.bus} \ + '') (attrValues vmCfg.disks.add)} \ + ${concatMapStrings (shareCfg: '' + -virtfs local,path=${toString shareCfg.source},security_model=passthrough,mount_tag=${shareCfg.target} \ + '') (attrValues vmCfg.shares)} \ + ${concatMapStrings (tapCfg: '' + -device virtio-net-pci,netdev=lan-${tapCfg.name} \ + -netdev tap,id=lan-${tapCfg.name},ifname=${tapCfg.iface},script=no,downscript=no \ + '') allTaps} \ + ${optionalString cfg.network.user.enable " + -netdev user,id=user \ + -device virtio-net-pci,netdev=user \ + "} \ + ${optionalString (vmCfg.boot.menu == true) "-boot menu=on"} \ + #${optionalString (length vmCfg.boot.order > 0) "-boot order=${concatStringsSep "," vmCfg.boot.order}"} + + # ${concatMapStrings (macvtap: '' + # -device virtio-net-pci,netdev=macvtap-${macvtap.name} \ + # -netdev tap,id=macvtap-${macvtap.name},ifname=${macvtap.iface},script=no,downscript=no \ + # '') allMacvtaps} \ + ''; + in + { + "vm.vmix@${vmCfg.name}" = rec { + requires = [ "net.vmix@${netCfg.name}.target" "macvtaps.vm.vmix@${vmCfg.name}.service" ]; + unitConfig.JoinsNamespaceOf = "ns.net.vmix@${netCfg.name}.service"; + after = requires; + path = with pkgs; [ iproute2 qemu ]; + serviceConfig = { + ExecStartPre = createTapsforLansScript; + ExecStart = qemuStartVMScript; + ExecStopPost = deleteTapsforLansScript; + PrivateTmp = true; + ProtectSystem = true; + ProtectHome = true; + PrivateNetwork = true; + }; + }; + + "macvtaps.vm.vmix@${vmCfg.name}" = rec { + bindsTo = [ "net.vmix@${netCfg.name}.target" ]; + after = bindsTo; + partOf = [ "vm.vmix@${vmCfg.name}.service" ]; + path = with pkgs; [ iproute2 ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = createMacvapsScript; + ExecStop = deleteMacvapsScript; + }; + }; + }; + + vmServices = concatMapAttrs mkServices4aVM vmixCfg.vms; +in +{ + config.systemd.services = vmServices; +} \ No newline at end of file diff --git a/nixos/vm/default.nix b/nixos/vm/default.nix new file mode 100644 index 0000000..8ed8f96 --- /dev/null +++ b/nixos/vm/default.nix @@ -0,0 +1,11 @@ +args@{ config, pkgs, lib, vmixLib, ... }: +with lib; +{ + options.vmix.vms = mkOption { + type = types.attrsOf + (types.submodule (import ./options.nix args)); + default = { }; + }; + + imports = [ (import ./config.nix args) ]; +} \ No newline at end of file diff --git a/nixos/vm/options.nix b/nixos/vm/options.nix index 5f930d8..6a982a9 100644 --- a/nixos/vm/options.nix +++ b/nixos/vm/options.nix @@ -1,125 +1,166 @@ { config, pkgs, lib, ... }: with lib; -let - networkModule = import ./network.nix { inherit config pkgs lib ;}; -in { options = { - cpu = - mkOption { - type = types.str; - default = "host"; - description = '' - The CPU model to emulate. - ''; - }; - - smp = - mkOption { - type = types.ints.positive; - default = 1; - description = '' - Specify the number of cores the guest is permitted to use. - The number can be higher than the available cores on the - host system. - ''; - }; - - memSize = - mkOption { - type = types.ints.positive; - default = 1024; - description = '' - The memory size in megabytes of the virtual machine. - ''; - }; - - diskSize = - mkOption { - type = types.str; - default = ""; - description = '' - The disk size in qemu params. - ''; - }; - - osImage = - mkOption { - type = types.package; - description = '' - OS image stored in the nix store, either built with vmixLib.images or fetched from the internet. - ''; - }; - - persistantOverlayImagePath = - mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path where the persistant overlay is stored. If path is non-existant, a qcow2 image backed by the original osImage is created. - ''; - }; - - startOnBoot = - mkOption { - type = types.bool; - description = '' - Whether to start the VM automatically on system boot. - ''; - default = true; - }; - - sharedDirectories = - lib.mkOption { - type = types.attrsOf - (types.submodule { - options = { - source = lib.mkOption { - type = types.str; - description = "The path of the directory to share, can be a shell variable"; - }; - target = lib.mkOption { - type = types.str; - description = "The mount point of the directory inside the virtual machine"; - }; - }; - }); - example = { - my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; + cpu.cores = mkOption { + type = types.int; + default = 2; + description = "Number of CPU cores."; + }; + cpu.model = mkOption { + type = types.str; + default = "host"; + description = "CPU model."; + }; + kvm = mkOption { + type = types.bool; + default = true; + description = "Enable KVM."; + }; + arch = mkOption { + type = types.str; + default = "x86_64"; + description = "Architecture of the VM."; + }; + pc.type = mkOption { + type = types.str; + default = "q35"; + description = "PC type."; + }; + bios.efi = mkOption { + type = types.bool; + default = true; + description = "Enable EFI BIOS."; + }; + bios.tpm = mkOption { + type = types.bool; + default = false; + description = "Enable TPM BIOS."; + }; + mem.size = mkOption { + type = types.int; + default = 1024; + description = "Memory size in MB."; + }; + mem.balloon = mkOption { + type = types.bool; + default = false; + description = "Enable memory ballooning."; + }; + disks.os.file = mkOption { + type = types.path; + description = "Path to the OS disk image."; + }; + disks.os.persist = mkOption { + type = types.bool; + default = false; + description = "Persist OS disk changes."; + }; + disks.iso.file = mkOption { + type = types.nullOr types.path; + description = "Path to the ISO file."; + default = null; + }; + disks.add = mkOption { + default = {}; + type = types.attrsOf (types.submodule { + options = { + file = mkOption { + type = types.path; + description = "Path to the additional disk."; + }; + mounts = mkOption { + type = types.attrsOf types.str; + description = "Mount points for the additional disk."; + }; + opts = mkOption { + type = types.str; + description = "additional options in QEMU args for this disk"; + }; }; - default = { }; - }; - - networks = lib.mkOption { - type = types.attrsOf - (types.submodule networkModule); - example = { - my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; + }); + description = "Additional disks."; + }; + shares = mkOption { + default = {}; + type = types.attrsOf (types.submodule { + options = { + source = mkOption { + type = types.path; + description = "Source path for the shared directory."; + }; + target = mkOption { + type = types.str; + description = "Target path inside the VM for the shared directory."; + }; }; - default = { }; - }; - }; + }); + description = "Shared directories."; + }; - functions.mkVMService = name: cfg: - let - vmCfg = cfg // { inherit name }; - mkDiskImageFrom - diskImage = - qemuArgs = '' - --name ${vmCfg.name} \ - --cpu ${vmCfg.cpu} \ - --smp ${vmCfg.cores} \ - -- - ''; - in - { - - }; -} + disks.bus = mkOption { + type = types.str; + default = "virtio"; + description = "Bus type for the disks."; + }; + boot.order = mkOption { + type = types.listOf types.str; + description = "Boot order."; + default = [ "os" "iso" ]; + }; + boot.menu = mkOption { + type = types.bool; + default = false; + description = "Enable boot menu."; + }; -//storage -// -//networking - DNS, VPNs, Bridges, NATs, isolation, connecting with others,host -//run adhoc -//run and die -// \ No newline at end of file + network.user.enable = mkOption { + type = types.bool; + default = false; + description = "enable qemu user networking"; + }; + + network.vmix = mkOption { + default = {}; + type = types.attrsOf (types.submodule { + options = { + lans = mkOption { + default = {}; + type = types.attrsOf (types.submodule { + options = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the LAN interface."; + }; + mac = mkOption { + type = types.str; + description = "MAC address for the LAN interface."; + }; + }; + }); + description = "LAN interfaces."; + }; + macvtaps = mkOption { + default = {}; + type = types.attrsOf (types.submodule { + options = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the MACVTap interface."; + }; + mac = mkOption { + type = types.str; + description = "MAC address for the MACVTap interface."; + }; + }; + }); + description = "MACVTap interfaces."; + }; + }; + }); + description = "Network interfaces."; + }; + }; +} \ No newline at end of file