{ config, lib, pkgs, ... }: with lib; let cfg = config.networking.netnsd; hasNamespaces = cfg.namespaces != {}; format = pkgs.formats.toml {}; configFile = format.generate "netnsd.toml" { namespace = mapAttrsToList (_: value: value) cfg.namespaces; }; in { options.networking.netnsd = { package = mkOption { type = types.package; description = "package to use"; }; logLevel = mkOption { type = types.str; default = "info"; description = "Log level"; }; namespaces = mkOption { type = types.attrsOf (types.submodule ({name, ...}: { options = { name = mkOption { type = types.str; default = name; description = "target port inside the namespace"; }; forward = mkOption { type = types.listOf (types.submodule ({config, ...}: { options = { source = mkOption { type = types.oneOf [types.port types.str]; default = config.target; defaultText = ""; description = "source port, address or socket outside the namespace"; }; target = mkOption { type = types.oneOf [types.port types.str]; description = "target port or address inside the namespace"; }; }; })); description = "ports to forward into the namespace"; default = []; }; }; })); description = "namespaces to setup"; default = {}; }; }; config = mkIf hasNamespaces { # symlink instead of passing `configFile` directly to netnsd to allow changing the config without changing the path environment.etc."netnsd/netnsd.toml".source = configFile; environment.systemPackages = with pkgs; [cfg.package]; systemd.services.netnsd = { reloadTriggers = [configFile]; wantedBy = ["multi-user.target"]; before = ["network.target"]; environment = { RUST_LOG = cfg.logLevel; }; serviceConfig = { Restart = "on-failure"; Type = "notify-reload"; ExecStart = "${getExe cfg.package} daemon -c /etc/netnsd/netnsd.toml"; NoNewPrivileges = true; }; }; }; }