at 24.11-pre 12 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.virtualisation.incus; 10 preseedFormat = pkgs.formats.yaml { }; 11 12 serverBinPath = ''/run/wrappers/bin:${pkgs.qemu_kvm}/libexec:${ 13 lib.makeBinPath ( 14 with pkgs; 15 [ 16 cfg.package 17 18 acl 19 attr 20 bash 21 btrfs-progs 22 cdrkit 23 coreutils 24 criu 25 dnsmasq 26 e2fsprogs 27 findutils 28 getent 29 gnugrep 30 gnused 31 gnutar 32 gptfdisk 33 gzip 34 iproute2 35 iptables 36 iw 37 kmod 38 libnvidia-container 39 libxfs 40 lvm2 41 minio 42 minio-client 43 nftables 44 qemu-utils 45 qemu_kvm 46 rsync 47 squashfs-tools-ng 48 squashfsTools 49 sshfs 50 swtpm 51 systemd 52 thin-provisioning-tools 53 util-linux 54 virtiofsd 55 xdelta 56 xz 57 ] 58 ++ lib.optionals config.security.apparmor.enable [ 59 apparmor-bin-utils 60 61 (writeShellScriptBin "apparmor_parser" '' 62 exec '${apparmor-parser}/bin/apparmor_parser' -I '${apparmor-profiles}/etc/apparmor.d' "$@" 63 '') 64 ] 65 ++ lib.optionals config.services.ceph.client.enable [ ceph-client ] 66 ++ lib.optionals config.virtualisation.vswitch.enable [ config.virtualisation.vswitch.package ] 67 ++ lib.optionals config.boot.zfs.enabled [ 68 config.boot.zfs.package 69 "${config.boot.zfs.package}/lib/udev" 70 ] 71 ) 72 }''; 73 74 # https://github.com/lxc/incus/blob/cff35a29ee3d7a2af1f937cbb6cf23776941854b/internal/server/instance/drivers/driver_qemu.go#L123 75 OVMF2MB = pkgs.OVMF.override { 76 secureBoot = true; 77 fdSize2MB = true; 78 }; 79 ovmf-prefix = if pkgs.stdenv.hostPlatform.isAarch64 then "AAVMF" else "OVMF"; 80 ovmf = pkgs.linkFarm "incus-ovmf" [ 81 # 2MB must remain the default or existing VMs will fail to boot. New VMs will prefer 4MB 82 { 83 name = "OVMF_CODE.fd"; 84 path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_CODE.fd"; 85 } 86 { 87 name = "OVMF_VARS.fd"; 88 path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_VARS.fd"; 89 } 90 { 91 name = "OVMF_VARS.ms.fd"; 92 path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_VARS.fd"; 93 } 94 95 { 96 name = "OVMF_CODE.4MB.fd"; 97 path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_CODE.fd"; 98 } 99 { 100 name = "OVMF_VARS.4MB.fd"; 101 path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_VARS.fd"; 102 } 103 { 104 name = "OVMF_VARS.4MB.ms.fd"; 105 path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_VARS.fd"; 106 } 107 ]; 108 109 environment = lib.mkMerge [ 110 { 111 INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config"; 112 INCUS_OVMF_PATH = ovmf; 113 INCUS_USBIDS_PATH = "${pkgs.hwdata}/share/hwdata/usb.ids"; 114 PATH = lib.mkForce serverBinPath; 115 } 116 (lib.mkIf (cfg.ui.enable) { "INCUS_UI" = cfg.ui.package; }) 117 ]; 118 119 incus-startup = pkgs.writeShellScript "incus-startup" '' 120 case "$1" in 121 start) 122 systemctl is-active incus.service -q && exit 0 123 exec incusd activateifneeded 124 ;; 125 126 stop) 127 systemctl is-active incus.service -q || exit 0 128 exec incusd shutdown 129 ;; 130 131 *) 132 echo "unknown argument \`$1'" >&2 133 exit 1 134 ;; 135 esac 136 137 exit 0 138 ''; 139in 140{ 141 meta = { 142 maintainers = lib.teams.lxc.members; 143 }; 144 145 options = { 146 virtualisation.incus = { 147 enable = lib.mkEnableOption '' 148 incusd, a daemon that manages containers and virtual machines. 149 150 Users in the "incus-admin" group can interact with 151 the daemon (e.g. to start or stop containers) using the 152 {command}`incus` command line tool, among others. 153 ''; 154 155 package = lib.mkPackageOption pkgs "incus-lts" { }; 156 157 lxcPackage = lib.mkOption { 158 type = lib.types.package; 159 default = config.virtualisation.lxc.package; 160 defaultText = lib.literalExpression "config.virtualisation.lxc.package"; 161 description = "The lxc package to use."; 162 }; 163 164 clientPackage = lib.mkOption { 165 type = lib.types.package; 166 default = cfg.package.client; 167 defaultText = lib.literalExpression "config.virtualisation.incus.package.client"; 168 description = "The incus client package to use. This package is added to PATH."; 169 }; 170 171 softDaemonRestart = lib.mkOption { 172 type = lib.types.bool; 173 default = true; 174 description = '' 175 Allow for incus.service to be stopped without affecting running instances. 176 ''; 177 }; 178 179 preseed = lib.mkOption { 180 type = lib.types.nullOr (lib.types.submodule { freeformType = preseedFormat.type; }); 181 182 default = null; 183 184 description = '' 185 Configuration for Incus preseed, see 186 <https://linuxcontainers.org/incus/docs/main/howto/initialize/#non-interactive-configuration> 187 for supported values. 188 189 Changes to this will be re-applied to Incus which will overwrite existing entities or create missing ones, 190 but entities will *not* be removed by preseed. 191 ''; 192 193 example = { 194 networks = [ 195 { 196 name = "incusbr0"; 197 type = "bridge"; 198 config = { 199 "ipv4.address" = "10.0.100.1/24"; 200 "ipv4.nat" = "true"; 201 }; 202 } 203 ]; 204 profiles = [ 205 { 206 name = "default"; 207 devices = { 208 eth0 = { 209 name = "eth0"; 210 network = "incusbr0"; 211 type = "nic"; 212 }; 213 root = { 214 path = "/"; 215 pool = "default"; 216 size = "35GiB"; 217 type = "disk"; 218 }; 219 }; 220 } 221 ]; 222 storage_pools = [ 223 { 224 name = "default"; 225 driver = "dir"; 226 config = { 227 source = "/var/lib/incus/storage-pools/default"; 228 }; 229 } 230 ]; 231 }; 232 }; 233 234 socketActivation = lib.mkEnableOption ('' 235 socket-activation for starting incus.service. Enabling this option 236 will stop incus.service from starting automatically on boot. 237 ''); 238 239 startTimeout = lib.mkOption { 240 type = lib.types.ints.unsigned; 241 default = 600; 242 apply = toString; 243 description = '' 244 Time to wait (in seconds) for incusd to become ready to process requests. 245 If incusd does not reply within the configured time, `incus.service` will be 246 considered failed and systemd will attempt to restart it. 247 ''; 248 }; 249 250 ui = { 251 enable = lib.mkEnableOption "(experimental) Incus UI"; 252 253 package = lib.mkPackageOption pkgs [ 254 "incus" 255 "ui" 256 ] { }; 257 }; 258 }; 259 }; 260 261 config = lib.mkIf cfg.enable { 262 assertions = [ 263 { 264 assertion = 265 !( 266 config.networking.firewall.enable 267 && !config.networking.nftables.enable 268 && config.virtualisation.incus.enable 269 ); 270 message = "Incus on NixOS is unsupported using iptables. Set `networking.nftables.enable = true;`"; 271 } 272 ]; 273 274 # https://github.com/lxc/incus/blob/f145309929f849b9951658ad2ba3b8f10cbe69d1/doc/reference/server_settings.md 275 boot.kernel.sysctl = { 276 "fs.aio-max-nr" = lib.mkDefault 524288; 277 "fs.inotify.max_queued_events" = lib.mkDefault 1048576; 278 "fs.inotify.max_user_instances" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix 279 "fs.inotify.max_user_watches" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix 280 "kernel.dmesg_restrict" = lib.mkDefault 1; 281 "kernel.keys.maxbytes" = lib.mkDefault 2000000; 282 "kernel.keys.maxkeys" = lib.mkDefault 2000; 283 "net.core.bpf_jit_limit" = lib.mkDefault 1000000000; 284 "net.ipv4.neigh.default.gc_thresh3" = lib.mkDefault 8192; 285 "net.ipv6.neigh.default.gc_thresh3" = lib.mkDefault 8192; 286 # vm.max_map_count is set higher in nixos/modules/config/sysctl.nix 287 }; 288 289 boot.kernelModules = [ 290 "veth" 291 "xt_comment" 292 "xt_CHECKSUM" 293 "xt_MASQUERADE" 294 "vhost_vsock" 295 ] ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ]; 296 297 environment.systemPackages = [ 298 cfg.clientPackage 299 300 # gui console support 301 pkgs.spice-gtk 302 ]; 303 304 # Note: the following options are also declared in virtualisation.lxc, but 305 # the latter can't be simply enabled to reuse the formers, because it 306 # does a bunch of unrelated things. 307 systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ]; 308 309 security.apparmor = { 310 packages = [ cfg.lxcPackage ]; 311 policies = { 312 "bin.lxc-start".profile = '' 313 include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start 314 ''; 315 "lxc-containers".profile = '' 316 include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers 317 ''; 318 }; 319 }; 320 321 systemd.services.incus = { 322 description = "Incus Container and Virtual Machine Management Daemon"; 323 324 inherit environment; 325 326 wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ]; 327 after = [ 328 "network-online.target" 329 "lxcfs.service" 330 "incus.socket" 331 ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ]; 332 333 requires = [ 334 "lxcfs.service" 335 "incus.socket" 336 ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ]; 337 338 wants = [ "network-online.target" ]; 339 340 serviceConfig = { 341 ExecStart = "${cfg.package}/bin/incusd --group incus-admin"; 342 ExecStartPost = "${cfg.package}/bin/incusd waitready --timeout=${cfg.startTimeout}"; 343 ExecStop = lib.optionalString (!cfg.softDaemonRestart) "${cfg.package}/bin/incus admin shutdown"; 344 345 KillMode = "process"; # when stopping, leave the containers alone 346 Delegate = "yes"; 347 LimitMEMLOCK = "infinity"; 348 LimitNOFILE = "1048576"; 349 LimitNPROC = "infinity"; 350 TasksMax = "infinity"; 351 352 Restart = "on-failure"; 353 TimeoutStartSec = "${cfg.startTimeout}s"; 354 TimeoutStopSec = "30s"; 355 }; 356 }; 357 358 systemd.services.incus-startup = lib.mkIf cfg.softDaemonRestart { 359 description = "Incus Instances Startup/Shutdown"; 360 361 inherit environment; 362 363 after = [ 364 "incus.service" 365 "incus.socket" 366 ]; 367 requires = [ "incus.socket" ]; 368 369 serviceConfig = { 370 ExecStart = "${incus-startup} start"; 371 ExecStop = "${incus-startup} stop"; 372 RemainAfterExit = true; 373 TimeoutStartSec = "600s"; 374 TimeoutStopSec = "600s"; 375 Type = "oneshot"; 376 }; 377 }; 378 379 systemd.sockets.incus = { 380 description = "Incus UNIX socket"; 381 wantedBy = [ "sockets.target" ]; 382 383 socketConfig = { 384 ListenStream = "/var/lib/incus/unix.socket"; 385 SocketMode = "0660"; 386 SocketGroup = "incus-admin"; 387 }; 388 }; 389 390 systemd.services.incus-preseed = lib.mkIf (cfg.preseed != null) { 391 description = "Incus initialization with preseed file"; 392 393 wantedBy = [ "incus.service" ]; 394 after = [ "incus.service" ]; 395 bindsTo = [ "incus.service" ]; 396 partOf = [ "incus.service" ]; 397 398 script = '' 399 ${cfg.package}/bin/incus admin init --preseed <${preseedFormat.generate "incus-preseed.yaml" cfg.preseed} 400 ''; 401 402 serviceConfig = { 403 Type = "oneshot"; 404 RemainAfterExit = true; 405 }; 406 }; 407 408 users.groups.incus-admin = { }; 409 410 users.users.root = { 411 # match documented default ranges https://linuxcontainers.org/incus/docs/main/userns-idmap/#allowed-ranges 412 subUidRanges = [ 413 { 414 startUid = 1000000; 415 count = 1000000000; 416 } 417 ]; 418 subGidRanges = [ 419 { 420 startGid = 1000000; 421 count = 1000000000; 422 } 423 ]; 424 }; 425 426 virtualisation.lxc.lxcfs.enable = true; 427 }; 428}