mirror of
https://github.com/CHN-beta/nixos.git
synced 2026-01-12 01:29:24 +08:00
334 lines
13 KiB
Nix
334 lines
13 KiB
Nix
inputs:
|
|
{
|
|
options.nixos.services.nixvirt = let inherit (inputs.lib) mkOption types; in mkOption
|
|
{
|
|
type = types.nullOr (types.submodule { options =
|
|
{
|
|
subnet = mkOption { type = types.ints.unsigned; default = 122; };
|
|
instance = mkOption
|
|
{
|
|
type = types.attrsOf (types.submodule (submoduleInputs: { options =
|
|
let
|
|
hash = builtins.hashString "sha256" submoduleInputs.config._module.args.name;
|
|
createString = separator: parts: builtins.concatStringsSep separator
|
|
(builtins.map (p: builtins.substring (builtins.head p) (builtins.elemAt p 1) hash) parts);
|
|
defaultUuid = createString "-" [ [ 0 8 ] [ 8 4 ] [ 12 4 ] [ 16 4 ] [ 20 12 ] ];
|
|
defaultMac = "02:${createString ":" [ [ 0 2 ] [ 2 2 ] [ 4 2 ] [ 6 2 ] [ 8 2 ] ]}";
|
|
in
|
|
{
|
|
uuid = mkOption { type = types.nonEmptyStr; default = defaultUuid; };
|
|
owner = mkOption { type = types.nonEmptyStr; default = submoduleInputs.config._module.args.name; };
|
|
storage =
|
|
{
|
|
name = mkOption { type = types.nonEmptyStr; default = submoduleInputs.config._module.args.name; };
|
|
mountFrom = mkOption { type = types.nullOr types.nonEmptyStr; default = null; };
|
|
iso = mkOption { type = types.nullOr types.nonEmptyStr; default = null; };
|
|
};
|
|
memory =
|
|
{
|
|
sizeMB = mkOption { type = types.ints.unsigned; };
|
|
dedicated = mkOption { type = types.bool; default = false; };
|
|
};
|
|
cpu =
|
|
{
|
|
count = mkOption { type = types.ints.unsigned; };
|
|
hyprthread = mkOption { type = types.bool; default = false; };
|
|
set = mkOption { type = types.nullOr (types.nonEmptyListOf types.nonEmptyStr); default = null; };
|
|
};
|
|
network =
|
|
{
|
|
mac = mkOption { type = types.nonEmptyStr; default = defaultMac; };
|
|
address = mkOption { type = types.nullOr types.ints.unsigned; default = null; };
|
|
bridge = mkOption { type = types.bool; default = false; };
|
|
vnc =
|
|
{
|
|
port = mkOption
|
|
{ type = types.ints.unsigned; default = 15900 + submoduleInputs.config.network.address; };
|
|
openFirewall = mkOption { type = types.bool; default = true; };
|
|
};
|
|
portForward = rec
|
|
{
|
|
tcp = mkOption
|
|
{
|
|
type = types.listOf (types.submodule { options = rec
|
|
{ host = mkOption { type = types.ints.unsigned; }; guest = host; };});
|
|
default = [];
|
|
};
|
|
udp = tcp;
|
|
web = rec
|
|
{
|
|
httpsProxy = mkOption { type = types.listOf types.nonEmptyStr; default = []; };
|
|
httpProxy = httpsProxy;
|
|
httpRedirect = httpsProxy;
|
|
};
|
|
};
|
|
};
|
|
};}));
|
|
default = {};
|
|
};
|
|
};});
|
|
default = null;
|
|
};
|
|
config = let inherit (inputs.config.nixos.services) nixvirt; in inputs.lib.mkIf (nixvirt != null)
|
|
{
|
|
assertions = builtins.map
|
|
(vm:
|
|
{
|
|
assertion = vm.value.cpu.set != null -> builtins.length vm.value.cpu.set == vm.value.cpu.count;
|
|
message = "nixvirt.instance.${vm.name}.cpu.set must have the same length as cpu.count";
|
|
})
|
|
(inputs.localLib.attrsToList nixvirt.instance);
|
|
virtualisation =
|
|
{
|
|
libvirt =
|
|
{
|
|
enable = true;
|
|
verbose = true;
|
|
connections."qemu:///system" = let inherit (inputs.topInputs.nixvirt) lib; in
|
|
{
|
|
domains = builtins.map
|
|
(vm:
|
|
{
|
|
definition = inputs.config.nixos.system.sops.templates."nixvirt/${vm.name}.xml".path;
|
|
active = true;
|
|
restart = false;
|
|
})
|
|
(inputs.localLib.attrsToList nixvirt.instance);
|
|
networks =
|
|
[{
|
|
definition =
|
|
let
|
|
base = lib.network.templates.bridge
|
|
{ uuid = "8f403474-f8d6-4fa7-991a-f62f40d51191"; subnet_byte = nixvirt.subnet; };
|
|
host = builtins.map
|
|
(vm:
|
|
{
|
|
inherit (vm.network) mac;
|
|
ip = "192.168.${builtins.toString nixvirt.subnet}.${builtins.toString vm.network.address}";
|
|
})
|
|
(builtins.filter (vm: vm.network.address != null) (builtins.attrValues nixvirt.instance));
|
|
in lib.network.writeXML (base // { ip = base.ip // { dhcp = base.ip.dhcp // { inherit host; }; }; });
|
|
active = true;
|
|
# never restart the network
|
|
# when adding a new VM, add dhcp resolve manually, by:
|
|
# sudo virsh net-update default add ip-dhcp-host "<host mac='' ip='192.168.122.' />" --live
|
|
restart = false;
|
|
}];
|
|
# do not use it to define disk, since it is not declartive
|
|
# create disk manually, by:
|
|
# sudo qemu-img create -f raw /var/lib/libvirt/images/test.img 20G
|
|
# sudo chown qemu-libvirt:qemu-libvirt /var/lib/libvirt/images/test.img
|
|
# sudo chmod 600 /var/lib/libvirt/images/test.img
|
|
pools = [];
|
|
};
|
|
};
|
|
libvirtd.qemu.verbatimConfig =
|
|
''
|
|
namespaces = []
|
|
vnc_listen = "0.0.0.0"
|
|
'';
|
|
};
|
|
nixos =
|
|
{
|
|
system.sops =
|
|
{
|
|
templates = inputs.lib.mapAttrs'
|
|
(n: v: inputs.lib.nameValuePair "nixvirt/${n}.xml"
|
|
{
|
|
content = inputs.topInputs.nixvirt.lib.domain.getXML
|
|
# port from 8bcc23e27a62297254d0e9c87281e650ff777132
|
|
{
|
|
name = n;
|
|
inherit (v) uuid;
|
|
type = "kvm";
|
|
vcpu = { placement = "static"; count = v.cpu.count; };
|
|
cputune = inputs.lib.optionalAttrs (v.cpu.set != null)
|
|
{
|
|
vcpupin = builtins.genList (cpu: { vcpu = cpu; cpuset = builtins.elemAt v.cpu.set cpu; }) v.cpu.count;
|
|
};
|
|
memory =
|
|
{
|
|
count = v.memory.sizeMB;
|
|
unit = "MiB";
|
|
nosharepages = v.memory.dedicated;
|
|
locked = v.memory.dedicated;
|
|
};
|
|
os =
|
|
{
|
|
type = "hvm";
|
|
arch = "x86_64";
|
|
machine = "q35";
|
|
bootmenu = { enable = true; timeout = 15000; };
|
|
loader = { readonly = true; type = "pflash"; path = "/run/libvirt/nix-ovmf/OVMF_CODE.fd"; };
|
|
nvram =
|
|
{
|
|
template = "/run/libvirt/nix-ovmf/OVMF_VARS.fd";
|
|
path = "/var/lib/libvirt/qemu/nvram/${n}_VARS.fd";
|
|
templateFormat = "raw";
|
|
format = "raw";
|
|
};
|
|
};
|
|
features = { acpi = {}; apic = {}; };
|
|
cpu =
|
|
{
|
|
mode = "host-passthrough";
|
|
topology =
|
|
{
|
|
sockets = 1;
|
|
dies = 1;
|
|
cores = if v.cpu.hyprthread then v.cpu.count / 2 else v.cpu.count;
|
|
threads = if v.cpu.hyprthread then 2 else 1;
|
|
};
|
|
};
|
|
clock =
|
|
{
|
|
offset = "utc";
|
|
timer =
|
|
[
|
|
{ name = "rtc"; tickpolicy = "catchup"; }
|
|
{ name = "pit"; tickpolicy = "delay"; }
|
|
{ name = "hpet"; present = false; }
|
|
];
|
|
};
|
|
devices =
|
|
{
|
|
emulator = "${inputs.config.virtualisation.libvirtd.qemu.package}/bin/qemu-system-x86_64";
|
|
disk =
|
|
[
|
|
{
|
|
type = "file";
|
|
device = "disk";
|
|
driver = { name = "qemu"; type = "raw"; cache = "writeback"; discard = "unmap"; };
|
|
source.file = builtins.concatStringsSep ""
|
|
[
|
|
(if (v.storage.mountFrom != null) then "/nix/${v.storage.mountFrom}" else "")
|
|
"/var/lib/libvirt/images/"
|
|
"${v.storage.name}.img"
|
|
];
|
|
target = { dev = "vda"; bus = "virtio"; };
|
|
boot.order = 1;
|
|
}
|
|
{
|
|
type = "file";
|
|
device = "cdrom";
|
|
driver = { name = "qemu"; type = "raw"; };
|
|
source.file =
|
|
if v.storage.iso == null then "${inputs.topInputs.self.src.iso.netboot}" else v.storage.iso;
|
|
target = { dev = "sdc"; bus = "sata"; };
|
|
readonly = true;
|
|
boot.order = 10;
|
|
}
|
|
];
|
|
interface =
|
|
{
|
|
type = "bridge";
|
|
model.type = "virtio";
|
|
mac.address = v.network.mac;
|
|
source.bridge = if v.network.bridge then "nixvirt" else "virbr0";
|
|
};
|
|
input =
|
|
[
|
|
{ type = "tablet"; bus = "usb"; }
|
|
{ type = "mouse"; bus = "ps2"; }
|
|
{ type = "keyboard"; bus = "ps2"; }
|
|
];
|
|
graphics =
|
|
{
|
|
type = "vnc";
|
|
autoport = false;
|
|
port = v.network.vnc.port;
|
|
listen.type = "address";
|
|
passwd = inputs.config.sops.placeholder."nixvirt/${n}";
|
|
};
|
|
video.model = { type = "qxl"; ram = 65536; vram = 65536; vgamem = 16384; heads = 1; primary = true; };
|
|
rng = { model = "virtio"; backend = { model = "random"; source = /dev/urandom; }; };
|
|
};
|
|
};
|
|
})
|
|
nixvirt.instance;
|
|
secrets = inputs.lib.mapAttrs' (n: _: inputs.lib.nameValuePair "nixvirt/${n}" {}) nixvirt.instance;
|
|
};
|
|
services =
|
|
{
|
|
nginx = inputs.lib.mkMerge (builtins.map
|
|
(vm: let ip = "192.168.${builtins.toString nixvirt.subnet}.${builtins.toString vm.network.address}"; in
|
|
{
|
|
transparentProxy.map = builtins.listToAttrs (builtins.map
|
|
(host: inputs.lib.nameValuePair host "${ip}:443")
|
|
vm.network.portForward.web.httpsProxy);
|
|
http = inputs.lib.mkMerge
|
|
[
|
|
(builtins.listToAttrs (builtins.map
|
|
(host: inputs.lib.nameValuePair host { proxy.upstream = "http://${ip}" + ":80"; })
|
|
vm.network.portForward.web.httpProxy))
|
|
(builtins.listToAttrs (builtins.map
|
|
(host: inputs.lib.nameValuePair host { rewriteHttps = {}; })
|
|
vm.network.portForward.web.httpRedirect))
|
|
];
|
|
})
|
|
(builtins.attrValues nixvirt.instance or {}));
|
|
kvm = {};
|
|
};
|
|
};
|
|
security.wrappers.vm =
|
|
{
|
|
source =
|
|
let vm = inputs.pkgs.localPackages.vm.override
|
|
{
|
|
vmConfig = inputs.pkgs.writeText "vm.yaml" (builtins.toJSON
|
|
({
|
|
virsh = "${inputs.pkgs.libvirt}/bin/virsh";
|
|
vm =
|
|
let vms = builtins.groupBy (vm: vm.value.owner) (inputs.localLib.attrsToList nixvirt.instance);
|
|
in builtins.listToAttrs (builtins.map (owner:
|
|
{
|
|
name = builtins.toString inputs.config.nixos.user.uid.${owner.name};
|
|
value = builtins.map (vm: vm.name) owner.value;
|
|
})
|
|
(inputs.localLib.attrsToList vms));
|
|
}));
|
|
};
|
|
in "${vm}/bin/vm";
|
|
program = "vm";
|
|
owner = "root";
|
|
group = "root";
|
|
setuid = true;
|
|
};
|
|
networking =
|
|
{
|
|
firewall.allowedTCPPorts = builtins.map (vm: vm.network.vnc.port)
|
|
(builtins.filter (vm: vm.network.vnc.openFirewall) (builtins.attrValues nixvirt.instance));
|
|
nftables.tables.nixvirt =
|
|
{
|
|
family = "inet";
|
|
content =
|
|
let nftRules = builtins.concatLists (builtins.concatLists (builtins.map
|
|
(vm: builtins.map
|
|
(protocol: builtins.map
|
|
(port: "${protocol} dport ${builtins.toString port.host} fib daddr type local counter dnat ip to "
|
|
+ "192.168.${builtins.toString nixvirt.subnet}.${builtins.toString vm.network.address}"
|
|
+ ":${builtins.toString port.guest}")
|
|
vm.network.portForward.${protocol})
|
|
[ "tcp" "udp" ])
|
|
(builtins.attrValues nixvirt.instance)));
|
|
in
|
|
''
|
|
chain prerouting {
|
|
type nat hook prerouting priority dstnat; policy accept;
|
|
${builtins.concatStringsSep "\n" nftRules}
|
|
}
|
|
chain output {
|
|
type nat hook output priority dstnat; policy accept;
|
|
${builtins.concatStringsSep "\n" nftRules}
|
|
}
|
|
'';
|
|
};
|
|
};
|
|
boot.kernelParams =
|
|
let cpusets = builtins.concatLists (builtins.map
|
|
(vm: vm.cpu.set)
|
|
(builtins.filter (vm: vm.cpu.set != null) (builtins.attrValues nixvirt.instance)));
|
|
in inputs.lib.mkIf (cpusets != []) [ "isolcpus=${builtins.concatStringsSep "," cpusets}" ];
|
|
};
|
|
}
|