{ config, pkgs, ... }: with pkgs.lib; let runInNetns = pkgs.stdenv.mkDerivation { name = "run-in-netns"; unpackPhase = "true"; buildPhase = '' mkdir -p $out/bin gcc ${./run-in-netns.c} -o $out/bin/run-in-netns ''; installPhase = "true"; }; nixos-container = pkgs.substituteAll { name = "nixos-container"; dir = "bin"; isExecutable = true; src = ./nixos-container.pl; perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl"; inherit (pkgs) socat; }; in { options = { boot.isContainer = mkOption { type = types.bool; default = false; description = '' Whether this NixOS machine is a lightweight container running in another NixOS system. ''; }; containers = mkOption { type = types.attrsOf (types.submodule ( { config, options, name, ... }: { options = { config = mkOption { description = '' A specification of the desired configuration of this container, as a NixOS module. ''; }; path = mkOption { type = types.path; example = "/nix/var/nix/profiles/containers/webserver"; description = '' As an alternative to specifying , you can specify the path to the evaluated NixOS system configuration, typically a symlink to a system profile. ''; }; privateNetwork = mkOption { type = types.bool; default = false; description = '' Whether to give the container its own private virtual Ethernet interface. The interface is called eth0, and is hooked up to the interface c-container-name on the host. If this option is not set, then the container shares the network interfaces of the host, and can bind to any port on any interface. ''; }; hostAddress = mkOption { type = types.nullOr types.string; default = null; example = "10.231.136.1"; description = '' The IPv4 address assigned to the host interface. ''; }; localAddress = mkOption { type = types.nullOr types.string; default = null; example = "10.231.136.2"; description = '' The IPv4 address assigned to eth0 in the container. ''; }; }; config = mkMerge [ (mkIf options.config.isDefined { path = (import ../../lib/eval-config.nix { modules = let extraConfig = { boot.isContainer = true; security.initialRootPassword = mkDefault "!"; networking.hostName = mkDefault name; networking.useDHCP = false; }; in [ extraConfig config.config ]; prefix = [ "containers" name ]; }).config.system.build.toplevel; }) ]; })); default = {}; example = literalExample '' { webserver = { path = "/nix/var/nix/profiles/webserver"; }; database = { config = { config, pkgs, ... }: { services.postgresql.enable = true; services.postgresql.package = pkgs.postgresql92; }; }; } ''; description = '' A set of NixOS system configurations to be run as lightweight containers. Each container appears as a service container-name on the host system, allowing it to be started and stopped via systemctl . ''; }; }; config = { systemd.services."container@" = { description = "Container '%i'"; unitConfig.RequiresMountsFor = [ "/var/lib/containers/%i" ]; path = [ pkgs.iproute ]; environment.INSTANCE = "%i"; environment.root = "/var/lib/containers/%i"; preStart = '' mkdir -p -m 0755 $root/var/lib # Create a named pipe to get a signal when the container # has finished booting. rm -f $root/var/lib/startup-done mkfifo $root/var/lib/startup-done ''; script = '' mkdir -p -m 0755 "$root/etc" "$root/var/lib" if ! [ -e "$root/etc/os-release" ]; then touch "$root/etc/os-release" fi mkdir -p -m 0755 \ "/nix/var/nix/profiles/per-container/$INSTANCE" \ "/nix/var/nix/gcroots/per-container/$INSTANCE" SYSTEM_PATH=/nix/var/nix/profiles/system if [ -f "/etc/containers/$INSTANCE.conf" ]; then . "/etc/containers/$INSTANCE.conf" fi # Cleanup from last time. ifaceHost=c-$INSTANCE ifaceCont=ctmp-$INSTANCE ns=net-$INSTANCE ip netns del $ns 2> /dev/null || true ip link del $ifaceHost 2> /dev/null || true ip link del $ifaceCont 2> /dev/null || true if [ "$PRIVATE_NETWORK" = 1 ]; then # Create a pair of virtual ethernet devices. On the host, # we get ‘c- /dev/null; then break; fi sleep 1 done fi ''; restartIfChanged = false; #reloadIfChanged = true; # FIXME serviceConfig.ExecReload = pkgs.writeScript "reload-container" '' #! ${pkgs.stdenv.shell} -e SYSTEM_PATH=/nix/var/nix/profiles/system if [ -f "/etc/containers/$INSTANCE.conf" ]; then . "/etc/containers/$INSTANCE.conf" fi echo $SYSTEM_PATH/bin/switch-to-configuration test | \ ${pkgs.socat}/bin/socat unix:$root/var/lib/root-shell.socket - ''; serviceConfig.SyslogIdentifier = "container %i"; }; # Generate a configuration file in /etc/containers for each # container so that container@.target can get the container # configuration. environment.etc = mapAttrs' (name: cfg: nameValuePair "containers/${name}.conf" { text = '' SYSTEM_PATH=${cfg.path} ${optionalString cfg.privateNetwork '' PRIVATE_NETWORK=1 ${optionalString (cfg.hostAddress != null) '' HOST_ADDRESS=${cfg.hostAddress} ''} ${optionalString (cfg.localAddress != null) '' LOCAL_ADDRESS=${cfg.localAddress} ''} ''} ''; }) config.containers; # Generate /etc/hosts entries for the containers. networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null) '' ${cfg.localAddress} ${name}.containers '') config.containers); environment.systemPackages = [ nixos-container ]; }; }