{ lib, config, pkgs, ... }: with lib; let cfg = config.local.nspawn; in { options.local.nspawn.dmz = { enable = mkEnableOption "DMZ services in a container"; net = mkOption { type = with types; str; }; hostAddr = mkOption { type = with types; str; }; system = mkOption { type = with types; attrs; }; }; # Situación con os-release # # La idea aquí es poder hacer 'btrfs subvol create /var/lib/machines/foo' y # dejar que systemd-nspawn y el activation script creen todo lo demás. Esto # no sirve bien debido a la prueba barata que hace systemd para revisar si el # árbol parece contener una imagen de sistema operativo. Esta prueba falla en # dos momentos distintos: # # 1. Inmediatamente tras crear un árbol vacío, puesto que os-release no existe. # La solución naive es 'mkdir rootfs/etc && touch rootfs/etc/os-release'. # # 2. Luego de reiniciar el contenedor una vez que NixOS ha preparado /etc, ya que # systemd espera un archivo regular y no telera el symlink a la store. # # Resulta ser que systemd revisa tanto /etc/os-release como /usr/lib/os-release. # NixOS evidentemente no usa la segunda ruta por ser FHS, así que la duct tape # final es 'mkdir rootfs/usr/lib && touch rootfs/usr/lib/os-release'. config = mkIf cfg.dmz.enable { local.nspawn.dmz = { system = let containerModule = { ... }: { config.boot.isContainer = true; }; in pkgs.nixos [ ../dmz containerModule ]; net = "10.34.3.0/28"; hostAddr = "10.34.3.1/28"; }; systemd = { nspawn.dmz = { execConfig.PrivateUsers = "pick"; filesConfig.BindReadOnly = [ # idmap porque algunos hacks en nixpkgs (postfix-setup.service) # asumen que la store es de root "/nix/store:/nix/store:idmap" "${cfg.dmz.system.toplevel}/init:/sbin/init" ]; networkConfig.Port = [ "tcp:25" "tcp:80" "tcp:443" "tcp:587" ]; }; network.networks."40-ve-dmz" = { matchConfig = { Name = "ve-dmz"; Driver = "veth"; }; networkConfig = { Address = "${cfg.dmz.hostAddr}"; LinkLocalAddressing = "yes"; DHCPServer = "yes"; IPMasquerade = "both"; LLDP = "yes"; EmitLLDP = "customer-bridge"; IPv6SendRA = "yes"; }; # IP de contenedor fijada en hostAddr + 1 dhcpServerConfig = { PoolOffset = 2; PoolSize = 1; }; }; }; # DHCP networking.firewall.interfaces.ve-dmz.allowedUDPPorts = [ 67 ]; }; }