From 0d506aa712cf088343dff5863e9e58eb1228c3b0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 17 Mar 2014 14:04:39 +0100 Subject: Provide a simple way to log into containers On the host, you can run $ socat unix:/var/lib/login.socket -,echo=0,raw to get a login prompt. So this allows logging in even if the container has no SSH access enabled. You can also do $ socat unix:/var/lib/root-shell.socket - to get a plain root shell. (This socket is only accessible by root, obviously.) This makes it easy to execute commands in the container, e.g. $ echo reboot | socat unix:/var/lib/root-shell.socket - --- nixos/modules/virtualisation/container-login.nix | 56 ++++++++++++++++++++++++ nixos/modules/virtualisation/containers.nix | 1 + 2 files changed, 57 insertions(+) create mode 100644 nixos/modules/virtualisation/container-login.nix (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/container-login.nix b/nixos/modules/virtualisation/container-login.nix new file mode 100644 index 000000000000..56e772fb797d --- /dev/null +++ b/nixos/modules/virtualisation/container-login.nix @@ -0,0 +1,56 @@ +{ config, pkgs, ... }: + +{ + + config = { + + # Provide a login prompt on /var/lib/login.socket. On the host, + # you can connect to it by running ‘socat + # unix:/var/lib/login.socket -,echo=0,raw’. + systemd.sockets.login = + { description = "Login Socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = + { ListenStream = "/var/lib/login.socket"; + SocketMode = "0600"; # only root can connect, obviously + Accept = true; + }; + }; + + systemd.services."login@" = + { description = "Login %i"; + environment.TERM = "linux"; + serviceConfig = + { Type = "simple"; + StandardInput = "socket"; + ExecStart = "${pkgs.socat}/bin/socat -t0 - exec:${pkgs.shadow}/bin/login,pty,setsid,setpgid,stderr,ctty"; + TimeoutStopSec = 1; # FIXME + }; + }; + + # Provide a non-interactive login root shell on + # /var/lib/root-shell.socket. On the host, you can connect to it + # by running ‘socat unix:/var/lib/root-shell.socket -’. + systemd.sockets.root-shell = + { description = "Root Shell Socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = + { ListenStream = "/var/lib/root-shell.socket"; + SocketMode = "0666"; + Accept = true; + }; + }; + + systemd.services."root-shell@" = + { description = "Root Shell %i"; + serviceConfig = + { Type = "simple"; + StandardInput = "socket"; + ExecStart = "${pkgs.bash}/bin/bash --login"; + TimeoutStopSec = 1; # FIXME + }; + }; + + }; + +} diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index d87284de4fc1..f1fcc18f1f99 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -57,6 +57,7 @@ with pkgs.lib; { boot.isContainer = true; security.initialRootPassword = mkDefault "!"; networking.hostName = mkDefault name; + imports = [ ./container-login.nix ]; }; in [ extraConfig config.config ]; prefix = [ "systemd" "containers" name ]; -- cgit 1.4.1 From ef8e0266a2583eb45444b1faa0512f99d18fca6c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 17 Mar 2014 15:03:29 +0100 Subject: Don't reboot a container when its configuration changes Instead, just run "switch-to-configuration" inside the container. --- nixos/modules/virtualisation/containers.nix | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index f1fcc18f1f99..034ebe84b42f 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -132,6 +132,14 @@ with pkgs.lib; done fi ''; + + reloadIfChanged = true; + + serviceConfig.ExecReload = + "${pkgs.bash}/bin/bash -c '" + + "echo ${container.path}/bin/switch-to-configuration test " + + "| ${pkgs.socat}/bin/socat unix:${container.root}/var/lib/root-shell.socket -'"; + }) config.systemd.containers; }; -- cgit 1.4.1 From ac215779dd1fdfe58d75bcc22cb6a5b0a58744a4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 17 Mar 2014 15:23:20 +0100 Subject: Give containers a writable /nix/var/nix/{profiles,gcroots} These are stored on the host in /nix/var/nix/{profiles,gcroots}/per-container/ to ensure that container profiles/roots are not garbage-collected. --- nixos/modules/virtualisation/containers.nix | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 034ebe84b42f..6a4833e1e215 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -110,10 +110,19 @@ with pkgs.lib; if ! [ -e ${container.root}/etc/os-release ]; then touch ${container.root}/etc/os-release fi + + mkdir -p -m 0755 \ + /nix/var/nix/profiles/per-container/${name} \ + /nix/var/nix/gcroots/per-container/${name} ''; serviceConfig.ExecStart = - "${config.systemd.package}/bin/systemd-nspawn -M ${name} -D ${container.root} --bind-ro=/nix ${container.path}/init"; + "${config.systemd.package}/bin/systemd-nspawn" + + " -M ${name} -D ${container.root}" + + " --bind-ro=/nix/store --bind-ro=/nix/var/nix/db --bind-ro=/nix/var/nix/daemon-socket" + + " --bind=/nix/var/nix/profiles/per-container/${name}:/nix/var/nix/profiles" + + " --bind=/nix/var/nix/gcroots/per-container/${name}:/nix/var/nix/gcroots" + + " ${container.path}/init"; preStop = '' -- cgit 1.4.1 From 895bcdd1cb9f98ba032c78d996f3ebc89fd60bc2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Mar 2014 10:49:25 +0100 Subject: Add support for running a container with a private network interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For example, the following sets up a container named ‘foo’. The container will have a single network interface eth0, with IP address 10.231.136.2. The host will have an interface c-foo with IP address 10.231.136.1. systemd.containers.foo = { privateNetwork = true; hostAddress = "10.231.136.1"; localAddress = "10.231.136.2"; config = { services.openssh.enable = true; }; }; With ‘privateNetwork = true’, the container has the CAP_NET_ADMIN capability, allowing it to do arbitrary network configuration, such as setting up firewall rules. This is secure because it cannot touch the interfaces of the host. The helper program ‘run-in-netns’ is needed at the moment because ‘ip netns exec’ doesn't quite do the right thing (it remounts /sys without bind-mounting the original /sys/fs/cgroups). --- nixos/modules/services/networking/dhcpcd.nix | 5 +- nixos/modules/virtualisation/containers.nix | 113 ++++++++++++++++++++++++--- nixos/modules/virtualisation/run-in-netns.c | 50 ++++++++++++ 3 files changed, 155 insertions(+), 13 deletions(-) create mode 100644 nixos/modules/virtualisation/run-in-netns.c (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index d4ec96a18f6f..38a21a723d97 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -34,8 +34,9 @@ let # Ignore peth* devices; on Xen, they're renamed physical # Ethernet cards used for bridging. Likewise for vif* and tap* - # (Xen) and virbr* and vnet* (libvirt). - denyinterfaces ${toString ignoredInterfaces} peth* vif* tap* tun* virbr* vnet* vboxnet* + # (Xen) and virbr* and vnet* (libvirt) and c-* and ctmp-* (NixOS + # containers). + denyinterfaces ${toString ignoredInterfaces} peth* vif* tap* tun* virbr* vnet* vboxnet* c-* ctmp-* ${config.networking.dhcpcd.extraConfig} ''; diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 6a4833e1e215..49046975d83e 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -2,6 +2,20 @@ 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"; + }; + +in + { options = { @@ -45,6 +59,39 @@ with pkgs.lib; ''; }; + 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 @@ -97,32 +144,70 @@ with pkgs.lib; config = { - systemd.services = mapAttrs' (name: container: nameValuePair "container-${name}" - { description = "Container '${name}'"; + systemd.services = mapAttrs' (name: cfg: + let + # FIXME: interface names have a maximum length. + ifaceHost = "c-${name}"; + ifaceCont = "ctmp-${name}"; + ns = "net-${name}"; + in + nameValuePair "container-${name}" { + description = "Container '${name}'"; wantedBy = [ "multi-user.target" ]; - unitConfig.RequiresMountsFor = [ container.root ]; + unitConfig.RequiresMountsFor = [ cfg.root ]; + + path = [ pkgs.iproute ]; preStart = '' - mkdir -p -m 0755 ${container.root}/etc - if ! [ -e ${container.root}/etc/os-release ]; then - touch ${container.root}/etc/os-release + mkdir -p -m 0755 ${cfg.root}/etc + if ! [ -e ${cfg.root}/etc/os-release ]; then + touch ${cfg.root}/etc/os-release fi mkdir -p -m 0755 \ /nix/var/nix/profiles/per-container/${name} \ /nix/var/nix/gcroots/per-container/${name} + '' + + + optionalString cfg.privateNetwork '' + # Cleanup from last time. + ip netns del ${ns} 2> /dev/null || true + ip link del ${ifaceHost} 2> /dev/null || true + ip link del ${ifaceCont} 2> /dev/null || true + + # Create a pair of virtual ethernet devices. On the host, + # we get ‘c- +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char * * argv) +{ + if (argc < 3) { + fprintf(stderr, "%s: missing arguments\n", argv[0]); + return 1; + } + + char nsPath[PATH_MAX]; + + sprintf(nsPath, "/run/netns/%s", argv[1]); + + int fd = open(nsPath, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "%s: opening network namespace: %s\n", argv[0], strerror(errno)); + return 1; + } + + if (setns(fd, CLONE_NEWNET) == -1) { + fprintf(stderr, "%s: setting network namespace: %s\n", argv[0], strerror(errno)); + return 1; + } + + umount2(nsPath, MNT_DETACH); + if (unlink(nsPath) == -1) { + fprintf(stderr, "%s: unlinking network namespace: %s\n", argv[0], strerror(errno)); + return 1; + } + + /* FIXME: Remount /sys so that /sys/class/net reflects the + interfaces visible in the network namespace. This requires + bind-mounting /sys/fs/cgroups etc. */ + + execv(argv[2], argv + 2); + fprintf(stderr, "%s: running command: %s\n", argv[0], strerror(errno)); + return 1; +} -- cgit 1.4.1 From 7b82d1ee27a60157418e241152ef1aa3f909ad7b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Mar 2014 11:04:54 +0100 Subject: Ensure that the container root can always be accessed via /var/lib/containers --- nixos/modules/virtualisation/containers.nix | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 49046975d83e..ff17fcc1221d 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -172,6 +172,10 @@ in /nix/var/nix/gcroots/per-container/${name} '' + + optionalString (cfg.root != "/var/lib/containers/${name}") '' + ln -sfn "${cfg.root}" "/var/lib/containers/${name}" + '' + + optionalString cfg.privateNetwork '' # Cleanup from last time. ip netns del ${ns} 2> /dev/null || true -- cgit 1.4.1 From 11c4c4ae54bbef647358d2b6d6c3ddf0457f81f7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Mar 2014 11:36:03 +0100 Subject: Add command ‘nixos-container-shell’ for logging into a container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/modules/virtualisation/containers.nix | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index ff17fcc1221d..28ee78e3fcce 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -14,6 +14,58 @@ let installPhase = "true"; }; + nixos-container-shell = pkgs.writeScriptBin "nixos-container-shell" + '' + #! ${pkgs.bash}/bin/sh -e + + usage() { + echo "Usage: $0 " >&2 + echo " $0 (-r|--root-shell) " >&2 + } + + args="`getopt --options 'r' -l help -- "$@"`" + eval "set -- $args" + rootShell= + while [ $# -gt 0 ]; do + case "$1" in + (--help) usage; exit 0;; + (-r|--root-shell) rootShell=1;; + (--) shift; break;; + (*) break;; + esac + shift + done + + container="$1" + if [ -z "$container" ]; then + usage + exit 1 + fi + shift + + root="/var/lib/containers/$container" + if ! [ -d "$root" ]; then + echo "$0: container ‘$container’ does not exist" >&2 + exit 1 + fi + + if [ -n "$rootShell" ]; then + socket="$root/var/lib/root-shell.socket" + else + socket="$root/var/lib/login.socket" + fi + if ! [ -S "$socket" ]; then + echo "$0: socket ‘$socket’ does not exist" >&2 + exit 1 + fi + + if [ -n "$rootShell" ]; then + exec ${pkgs.socat}/bin/socat "unix:$socket" - + else + exec ${pkgs.socat}/bin/socat "unix:$socket" -,echo=0,raw + fi + ''; + in { @@ -246,5 +298,7 @@ in ${cfg.localAddress} ${name}.containers '') config.systemd.containers); + environment.systemPackages = optional (config.systemd.containers != {}) nixos-container-shell; + }; } -- cgit 1.4.1 From 5b10ea1f99edf1855d0735330634a463771a5ee2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Mar 2014 11:39:51 +0100 Subject: Don't run dhcpcd in containers --- nixos/modules/virtualisation/containers.nix | 1 + 1 file changed, 1 insertion(+) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 28ee78e3fcce..b8388e7f206d 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -156,6 +156,7 @@ in { boot.isContainer = true; security.initialRootPassword = mkDefault "!"; networking.hostName = mkDefault name; + networking.useDHCP = false; imports = [ ./container-login.nix ]; }; in [ extraConfig config.config ]; -- cgit 1.4.1 From 7ee31c7f94d121f5e57779c0c1478aee30d80cb1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Mar 2014 18:04:38 +0100 Subject: Fix permissions --- nixos/modules/virtualisation/container-login.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/container-login.nix b/nixos/modules/virtualisation/container-login.nix index 56e772fb797d..09eaf90ae658 100644 --- a/nixos/modules/virtualisation/container-login.nix +++ b/nixos/modules/virtualisation/container-login.nix @@ -12,7 +12,7 @@ wantedBy = [ "sockets.target" ]; socketConfig = { ListenStream = "/var/lib/login.socket"; - SocketMode = "0600"; # only root can connect, obviously + SocketMode = "0666"; Accept = true; }; }; @@ -36,7 +36,7 @@ wantedBy = [ "sockets.target" ]; socketConfig = { ListenStream = "/var/lib/root-shell.socket"; - SocketMode = "0666"; + SocketMode = "0600"; # only root can connect, obviously Accept = true; }; }; -- cgit 1.4.1 From 2ace7edb81509189a15a0b8e8d0ee1886be9e725 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Mar 2014 18:18:35 +0100 Subject: Rename systemd.containers -> containers That NixOS containers use systemd-nspawn is just an implementation detail (which we could change in the future). --- nixos/modules/virtualisation/containers.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index b8388e7f206d..cdd9e729dc16 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -80,7 +80,7 @@ in ''; }; - systemd.containers = mkOption { + containers = mkOption { type = types.attrsOf (types.submodule ( { config, options, name, ... }: { @@ -160,7 +160,7 @@ in imports = [ ./container-login.nix ]; }; in [ extraConfig config.config ]; - prefix = [ "systemd" "containers" name ]; + prefix = [ "containers" name ]; }).config.system.build.toplevel; }) ]; @@ -291,15 +291,15 @@ in + "echo ${cfg.path}/bin/switch-to-configuration test " + "| ${pkgs.socat}/bin/socat unix:${cfg.root}/var/lib/root-shell.socket -'"; - }) config.systemd.containers; + }) config.containers; # Generate /etc/hosts entries for the containers. networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null) '' ${cfg.localAddress} ${name}.containers - '') config.systemd.containers); + '') config.containers); - environment.systemPackages = optional (config.systemd.containers != {}) nixos-container-shell; + environment.systemPackages = optional (config.containers != {}) nixos-container-shell; }; } -- cgit 1.4.1 From 0cca0f477f168cee994b2c90d20b713f2bb67d85 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Mar 2014 10:15:57 +0100 Subject: nixos-container-shell -> nixos-container { login | root-shell } --- nixos/modules/virtualisation/containers.nix | 60 ++++--------------------- nixos/modules/virtualisation/nixos-container.sh | 54 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 52 deletions(-) create mode 100644 nixos/modules/virtualisation/nixos-container.sh (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index cdd9e729dc16..e718398815d2 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -14,57 +14,13 @@ let installPhase = "true"; }; - nixos-container-shell = pkgs.writeScriptBin "nixos-container-shell" - '' - #! ${pkgs.bash}/bin/sh -e - - usage() { - echo "Usage: $0 " >&2 - echo " $0 (-r|--root-shell) " >&2 - } - - args="`getopt --options 'r' -l help -- "$@"`" - eval "set -- $args" - rootShell= - while [ $# -gt 0 ]; do - case "$1" in - (--help) usage; exit 0;; - (-r|--root-shell) rootShell=1;; - (--) shift; break;; - (*) break;; - esac - shift - done - - container="$1" - if [ -z "$container" ]; then - usage - exit 1 - fi - shift - - root="/var/lib/containers/$container" - if ! [ -d "$root" ]; then - echo "$0: container ‘$container’ does not exist" >&2 - exit 1 - fi - - if [ -n "$rootShell" ]; then - socket="$root/var/lib/root-shell.socket" - else - socket="$root/var/lib/login.socket" - fi - if ! [ -S "$socket" ]; then - echo "$0: socket ‘$socket’ does not exist" >&2 - exit 1 - fi - - if [ -n "$rootShell" ]; then - exec ${pkgs.socat}/bin/socat "unix:$socket" - - else - exec ${pkgs.socat}/bin/socat "unix:$socket" -,echo=0,raw - fi - ''; + nixos-container = pkgs.substituteAll { + name = "nixos-container"; + dir = "bin"; + isExecutable = true; + src = ./nixos-container.sh; + inherit (pkgs) bash socat; + }; in @@ -299,7 +255,7 @@ in ${cfg.localAddress} ${name}.containers '') config.containers); - environment.systemPackages = optional (config.containers != {}) nixos-container-shell; + environment.systemPackages = optional (config.containers != {}) nixos-container; }; } diff --git a/nixos/modules/virtualisation/nixos-container.sh b/nixos/modules/virtualisation/nixos-container.sh new file mode 100644 index 000000000000..f4e34588d6eb --- /dev/null +++ b/nixos/modules/virtualisation/nixos-container.sh @@ -0,0 +1,54 @@ +#! @bash@/bin/sh -e + +usage() { + echo "Usage: $0 login " >&2 + echo " $0 root-shell " >&2 +} + +args="`getopt --options '' -l help -- "$@"`" +eval "set -- $args" +while [ $# -gt 0 ]; do + case "$1" in + (--help) usage; exit 0;; + (--) shift; break;; + (*) break;; + esac + shift +done + +action="$1" +if [ -z "$action" ]; then usage; exit 1; fi +shift + +getContainerRoot() { + root="/var/lib/containers/$container" + if ! [ -d "$root" ]; then + echo "$0: container ‘$container’ does not exist" >&2 + exit 1 + fi +} + +if [ $action = login ]; then + + container="$1" + if [ -z "$container" ]; then usage; exit 1; fi + shift + + getContainerRoot + + exec @socat@/bin/socat "unix:$root/var/lib/login.socket" -,echo=0,raw + +elif [ $action = root-shell ]; then + + container="$1" + if [ -z "$container" ]; then usage; exit 1; fi + shift + + getContainerRoot + + exec @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - + +else + echo "$0: unknown action ‘$action’" >&2 + exit 1 +fi -- cgit 1.4.1 From ba88db3cd332e439dd2090b64abb7b9942b5fc94 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Mar 2014 19:55:05 +0100 Subject: Add support for imperative container management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The command nixos-container can now create containers. For instance, the following creates and starts a container named ‘database’: $ nixos-container create database The configuration of the container is stored in /var/lib/containers//etc/nixos/configuration.nix. After editing the configuration, you can make the changes take effect by doing $ nixos-container update database The container can also be destroyed: $ nixos-container destroy database Containers are now executed using a template unit, ‘container@.service’, so the unit in this example would be ‘container@database.service’. --- nixos/modules/virtualisation/containers.nix | 166 +++++++++++++----------- nixos/modules/virtualisation/nixos-container.sh | 106 +++++++++++++-- 2 files changed, 185 insertions(+), 87 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index e718398815d2..573a3d1c5848 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -42,13 +42,6 @@ in { options = { - root = mkOption { - type = types.path; - description = '' - The root directory of the container. - ''; - }; - config = mkOption { description = '' A specification of the desired configuration of this @@ -103,9 +96,7 @@ in }; config = mkMerge - [ { root = mkDefault "/var/lib/containers/${name}"; - } - (mkIf options.config.isDefined { + [ (mkIf options.config.isDefined { path = (import ../../lib/eval-config.nix { modules = let extraConfig = @@ -126,12 +117,10 @@ in example = literalExample '' { webserver = - { root = "/containers/webserver"; - path = "/nix/var/nix/profiles/webserver"; + { path = "/nix/var/nix/profiles/webserver"; }; database = - { root = "/containers/database"; - config = + { config = { config, pkgs, ... }: { services.postgresql.enable = true; services.postgresql.package = pkgs.postgresql92; @@ -153,78 +142,76 @@ in config = { - systemd.services = mapAttrs' (name: cfg: - let - # FIXME: interface names have a maximum length. - ifaceHost = "c-${name}"; - ifaceCont = "ctmp-${name}"; - ns = "net-${name}"; - in - nameValuePair "container-${name}" { - description = "Container '${name}'"; - - wantedBy = [ "multi-user.target" ]; + systemd.services."container@" = + { description = "Container '%I'"; - unitConfig.RequiresMountsFor = [ cfg.root ]; + unitConfig.RequiresMountsFor = [ "/var/lib/containers/%I" ]; path = [ pkgs.iproute ]; - preStart = + environment.INSTANCE = "%I"; + + script = '' - mkdir -p -m 0755 ${cfg.root}/etc - if ! [ -e ${cfg.root}/etc/os-release ]; then - touch ${cfg.root}/etc/os-release + root="/var/lib/containers/$INSTANCE" + mkdir -p -m 0755 "$root/etc" + if ! [ -e "$root/etc/os-release" ]; then + touch "$root/etc/os-release" fi mkdir -p -m 0755 \ - /nix/var/nix/profiles/per-container/${name} \ - /nix/var/nix/gcroots/per-container/${name} - '' + "/nix/var/nix/profiles/per-container/$INSTANCE" \ + "/nix/var/nix/gcroots/per-container/$INSTANCE" - + optionalString (cfg.root != "/var/lib/containers/${name}") '' - ln -sfn "${cfg.root}" "/var/lib/containers/${name}" - '' + SYSTEM_PATH=/nix/var/nix/profiles/system + if [ -f "/etc/containers/$INSTANCE.conf" ]; then + . "/etc/containers/$INSTANCE.conf" + fi - + optionalString cfg.privateNetwork '' # Cleanup from last time. - ip netns del ${ns} 2> /dev/null || true - ip link del ${ifaceHost} 2> /dev/null || true - ip link del ${ifaceCont} 2> /dev/null || true - - # Create a pair of virtual ethernet devices. On the host, - # we get ‘c- /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-" >&2 + echo "Usage: $0 create [--config ]" >&2 + echo " $0 update " >&2 + echo " $0 destroy " >&2 + echo " $0 login " >&2 echo " $0 root-shell " >&2 + echo " $0 set-root-password " >&2 } -args="`getopt --options '' -l help -- "$@"`" +args="`getopt --options '' -l help -l config: -- "$@"`" eval "set -- $args" +extraConfigFile= while [ $# -gt 0 ]; do case "$1" in (--help) usage; exit 0;; + (--config) shift; extraConfigFile=$1;; (--) shift; break;; (*) break;; esac @@ -28,26 +34,104 @@ getContainerRoot() { fi } -if [ $action = login ]; then +container="$1" +if [ -z "$container" ]; then usage; exit 1; fi +shift - container="$1" - if [ -z "$container" ]; then usage; exit 1; fi - shift +if [ $action = create ]; then + + confFile="/etc/containers/$container.conf" + root="/var/lib/containers/$container" + + if [ -e "$confFile" -o -e "$root/nix" ]; then + echo "$0: container ‘$container’ already exists" >&2 + exit 1 + fi + + profileDir="/nix/var/nix/profiles/per-container/$container" + mkdir -m 0755 -p "$root/etc/nixos" "$profileDir" + + config=" +{ config, pkgs, ... }: + +with pkgs.lib; + +{ boot.isContainer = true; + security.initialRootPassword = mkDefault \"!\"; + networking.hostName = mkDefault \"$container\"; + networking.useDHCP = false; + imports = [ $extraConfigFile ]; +}" + configFile="$root/etc/nixos/configuration.nix" + echo "$config" > "$configFile" + + nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '' --set -A system + + # Allocate a new /8 network in the 10.233.* range. + network="$(sed -e 's/.*_ADDRESS=10\.233\.\(.*\)\..*/\1/; t; d' /etc/containers/*.conf | sort -n | tail -n1)" + if [ -z "$network" ]; then network=0; else : $((network++)); fi + + hostAddress="10.233.$network.1" + localAddress="10.233.$network.2" + echo "host IP is $hostAddress, container IP is $localAddress" >&2 + + cat > "$confFile" <&2 + systemctl start "container@$container.service" + +elif [ $action = update ]; then getContainerRoot - exec @socat@/bin/socat "unix:$root/var/lib/login.socket" -,echo=0,raw + configFile="$root/etc/nixos/configuration.nix" + profileDir="/nix/var/nix/profiles/per-container/$container" -elif [ $action = root-shell ]; then + nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '' --set -A system - container="$1" - if [ -z "$container" ]; then usage; exit 1; fi - shift + echo "reloading container@$container.service..." >&2 + systemctl reload "container@$container.service" + +elif [ $action = destroy ]; then getContainerRoot + confFile="/etc/containers/$container.conf" + if [ ! -w "$confFile" ]; then + echo "$0: cannot destroy declarative container (remove it from your configuration.nix instead)" + exit 1 + fi + + if systemctl show "container@$container.service" | grep -q ActiveState=active; then + echo "stopping container@$container.service..." >&2 + systemctl stop "container@$container.service" + fi + + rm -f "$confFile" + +elif [ $action = login ]; then + + getContainerRoot + exec @socat@/bin/socat "unix:$root/var/lib/login.socket" -,echo=0,raw + +elif [ $action = root-shell ]; then + + getContainerRoot exec @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - +elif [ $action = set-root-password ]; then + + password="$1" + if [ -z "$password" ]; then usage; exit 1; fi + + # FIXME: not very secure. + getContainerRoot + (echo "passwd"; echo "$password"; echo "$password") | @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - + else echo "$0: unknown action ‘$action’" >&2 exit 1 -- cgit 1.4.1 From 29c469b88db31d56acf02478fffea14f15372b1f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Mar 2014 15:09:38 +0100 Subject: Allow dashes in container names --- nixos/modules/virtualisation/containers.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 573a3d1c5848..7e45d9f8b22f 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -143,13 +143,13 @@ in config = { systemd.services."container@" = - { description = "Container '%I'"; + { description = "Container '%i'"; - unitConfig.RequiresMountsFor = [ "/var/lib/containers/%I" ]; + unitConfig.RequiresMountsFor = [ "/var/lib/containers/%i" ]; path = [ pkgs.iproute ]; - environment.INSTANCE = "%I"; + environment.INSTANCE = "%i"; script = '' -- cgit 1.4.1 From 6010b0e8868b5a8058302a7b7839cddb21272043 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Mar 2014 15:09:53 +0100 Subject: nixos-container: NixOps helper functions --- nixos/modules/virtualisation/nixos-container.sh | 46 ++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/nixos-container.sh b/nixos/modules/virtualisation/nixos-container.sh index c72b6fb92814..a3fd9db56d82 100644 --- a/nixos/modules/virtualisation/nixos-container.sh +++ b/nixos/modules/virtualisation/nixos-container.sh @@ -1,21 +1,25 @@ #! @bash@/bin/sh -e usage() { - echo "Usage: $0 create [--config ]" >&2 + echo "Usage: $0 list" >&2 + echo " $0 create [--config ] [--ensure-unique-name]" >&2 echo " $0 update " >&2 echo " $0 destroy " >&2 echo " $0 login " >&2 echo " $0 root-shell " >&2 echo " $0 set-root-password " >&2 + echo " $0 show-ip " >&2 } -args="`getopt --options '' -l help -l config: -- "$@"`" +args="`getopt --options '' -l help -l config: -l ensure-unique-name -- "$@"`" eval "set -- $args" extraConfigFile= +ensureUniqueName= while [ $# -gt 0 ]; do case "$1" in (--help) usage; exit 0;; (--config) shift; extraConfigFile=$1;; + (--ensure-unique-name) ensureUniqueName=1;; (--) shift; break;; (*) break;; esac @@ -34,12 +38,28 @@ getContainerRoot() { fi } +if [ $action = list ]; then + for i in $(cd /etc/containers && echo *.conf); do + echo "$(basename "$i" .conf)" + done + exit 0 +fi + container="$1" if [ -z "$container" ]; then usage; exit 1; fi shift if [ $action = create ]; then + if [ -n "$ensureUniqueName" ]; then + # FIXME: race + nr=0 + while [ -e "/etc/containers/$container-$nr.conf" -o -e "/var/lib/containers/$container-$nr" ]; do + : $((nr++)) + done + container="$container-$nr" + fi + confFile="/etc/containers/$container.conf" root="/var/lib/containers/$container" @@ -61,13 +81,19 @@ with pkgs.lib; networking.hostName = mkDefault \"$container\"; networking.useDHCP = false; imports = [ $extraConfigFile ]; + services.openssh.enable = true; + services.openssh.extraConfig = + '' + UseDNS no + ''; + users.extraUsers.root.openssh.authorizedKeys.keys = [ \"ssh-dss AAAAB3NzaC1kc3MAAACBAOo3foMFsYvc+LEVVTAeXpaxdOFG6O2NE9coxZYN6UtwE477GwkvZ4uKymAekq3TB8I6dDg4QFfE27fIip/rQHJ/Rus+KsxwnTbwPzE0WcZVpkKQsepsoqLkfwMpiPfn5/oxcnJsimwRY/E95aJmmOHdGaYWrc0t4ARa+6teUgdFAAAAFQCSQq2Wil0/X4hDypGGUKlKvYyaWQAAAIAy/0fSDnz1tZOQBGq7q78y406HfWghErrVlrW9g+foJQG5pgXXcdJs9JCIrlaKivUKITDsYnQaCjrZaK8eHnc4ksbkSLfDOxFnR5814ulCftrgEDOv9K1UU3pYketjFMvQCA2U48lR6jG/99CPNXPH55QEFs8H97cIsdLQw9wM4gAAAIEAmzWZlXLzIf3eiHQggXqvw3+C19QvxQITcYHYVTx/XYqZi1VZ/fkY8bNmdcJsWFyOHgEhpEca+xM/SNvH/14rXDmt0wtclLEx/4GVLi59hQCnnKqv7HzJg8RF4v6XTiROBAEEdb4TaFuFn+JCvqPzilTzXTexvZKJECOvfYcY+10= eelco.dolstra@logicblox.com\" ]; }" configFile="$root/etc/nixos/configuration.nix" echo "$config" > "$configFile" nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '' --set -A system - # Allocate a new /8 network in the 10.233.* range. + # Allocate a new /8 network in the 10.233.* range. FIXME: race network="$(sed -e 's/.*_ADDRESS=10\.233\.\(.*\)\..*/\1/; t; d' /etc/containers/*.conf | sort -n | tail -n1)" if [ -z "$network" ]; then network=0; else : $((network++)); fi @@ -84,6 +110,11 @@ EOF echo "starting container@$container.service..." >&2 systemctl start "container@$container.service" + # Print generated container name on stdout. + if [ -n "$ensureUniqueName" ]; then + echo "$container" + fi + elif [ $action = update ]; then getContainerRoot @@ -101,7 +132,7 @@ elif [ $action = destroy ]; then getContainerRoot confFile="/etc/containers/$container.conf" - if [ ! -w "$confFile" ]; then + if [ -e "$confFile" -a ! -w "$confFile" ]; then echo "$0: cannot destroy declarative container (remove it from your configuration.nix instead)" exit 1 fi @@ -112,6 +143,7 @@ elif [ $action = destroy ]; then fi rm -f "$confFile" + rm -rf "$root" elif [ $action = login ]; then @@ -132,6 +164,12 @@ elif [ $action = set-root-password ]; then getContainerRoot (echo "passwd"; echo "$password"; echo "$password") | @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - +elif [ $action = show-ip ]; then + + getContainerRoot + . "/etc/containers/$container.conf" + echo "$LOCAL_ADDRESS" + else echo "$0: unknown action ‘$action’" >&2 exit 1 -- cgit 1.4.1 From 07adfae5519ae12ce1a06d2b57f027c97288d0fb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Mar 2014 09:26:42 +0100 Subject: Remove hard-coded SSH key --- nixos/modules/virtualisation/nixos-container.sh | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/nixos-container.sh b/nixos/modules/virtualisation/nixos-container.sh index a3fd9db56d82..47abf96072f3 100644 --- a/nixos/modules/virtualisation/nixos-container.sh +++ b/nixos/modules/virtualisation/nixos-container.sh @@ -2,7 +2,7 @@ usage() { echo "Usage: $0 list" >&2 - echo " $0 create [--config ] [--ensure-unique-name]" >&2 + echo " $0 create [--config ] [--ensure-unique-name]" >&2 echo " $0 update " >&2 echo " $0 destroy " >&2 echo " $0 login " >&2 @@ -13,12 +13,12 @@ usage() { args="`getopt --options '' -l help -l config: -l ensure-unique-name -- "$@"`" eval "set -- $args" -extraConfigFile= +extraConfig= ensureUniqueName= while [ $# -gt 0 ]; do case "$1" in (--help) usage; exit 0;; - (--config) shift; extraConfigFile=$1;; + (--config) shift; extraConfig=$1;; (--ensure-unique-name) ensureUniqueName=1;; (--) shift; break;; (*) break;; @@ -80,13 +80,8 @@ with pkgs.lib; security.initialRootPassword = mkDefault \"!\"; networking.hostName = mkDefault \"$container\"; networking.useDHCP = false; - imports = [ $extraConfigFile ]; - services.openssh.enable = true; - services.openssh.extraConfig = - '' - UseDNS no - ''; - users.extraUsers.root.openssh.authorizedKeys.keys = [ \"ssh-dss AAAAB3NzaC1kc3MAAACBAOo3foMFsYvc+LEVVTAeXpaxdOFG6O2NE9coxZYN6UtwE477GwkvZ4uKymAekq3TB8I6dDg4QFfE27fIip/rQHJ/Rus+KsxwnTbwPzE0WcZVpkKQsepsoqLkfwMpiPfn5/oxcnJsimwRY/E95aJmmOHdGaYWrc0t4ARa+6teUgdFAAAAFQCSQq2Wil0/X4hDypGGUKlKvYyaWQAAAIAy/0fSDnz1tZOQBGq7q78y406HfWghErrVlrW9g+foJQG5pgXXcdJs9JCIrlaKivUKITDsYnQaCjrZaK8eHnc4ksbkSLfDOxFnR5814ulCftrgEDOv9K1UU3pYketjFMvQCA2U48lR6jG/99CPNXPH55QEFs8H97cIsdLQw9wM4gAAAIEAmzWZlXLzIf3eiHQggXqvw3+C19QvxQITcYHYVTx/XYqZi1VZ/fkY8bNmdcJsWFyOHgEhpEca+xM/SNvH/14rXDmt0wtclLEx/4GVLi59hQCnnKqv7HzJg8RF4v6XTiROBAEEdb4TaFuFn+JCvqPzilTzXTexvZKJECOvfYcY+10= eelco.dolstra@logicblox.com\" ]; + imports = [ ]; + $extraConfig }" configFile="$root/etc/nixos/configuration.nix" echo "$config" > "$configFile" -- cgit 1.4.1 From 7ebd856a3801d35872c60b112fce51132b45aff5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 31 Mar 2014 19:16:52 +0200 Subject: Provide nixos-container unconditionally --- nixos/modules/virtualisation/containers.nix | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 7e45d9f8b22f..16df108c21ec 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -267,9 +267,7 @@ in ${cfg.localAddress} ${name}.containers '') config.containers); - environment.systemPackages = optional (config.containers != {}) nixos-container; - - system.build.foo = nixos-container; + environment.systemPackages = [ nixos-container ]; }; } -- cgit 1.4.1 From 6da72a4456ea7cc12219c962739f279e5060fd66 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 31 Mar 2014 19:21:36 +0200 Subject: nixos-container: Rewrite in Perl Also fix race condition when multiple containers are created simultaneously (as NixOps tends to do). --- nixos/modules/virtualisation/containers.nix | 5 +- nixos/modules/virtualisation/nixos-container.pl | 198 ++++++++++++++++++++++++ nixos/modules/virtualisation/nixos-container.sh | 171 -------------------- 3 files changed, 201 insertions(+), 173 deletions(-) create mode 100644 nixos/modules/virtualisation/nixos-container.pl delete mode 100644 nixos/modules/virtualisation/nixos-container.sh (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 16df108c21ec..9964cd431cd7 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -18,8 +18,9 @@ let name = "nixos-container"; dir = "bin"; isExecutable = true; - src = ./nixos-container.sh; - inherit (pkgs) bash socat; + src = ./nixos-container.pl; + perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl"; + inherit (pkgs) socat; }; in diff --git a/nixos/modules/virtualisation/nixos-container.pl b/nixos/modules/virtualisation/nixos-container.pl new file mode 100644 index 000000000000..dfc856e8b667 --- /dev/null +++ b/nixos/modules/virtualisation/nixos-container.pl @@ -0,0 +1,198 @@ +#! @perl@ + +use strict; +use File::Path; +use File::Slurp; +use Fcntl ':flock'; +use Getopt::Long qw(:config gnu_getopt); + +my $socat = '@socat@/bin/socat'; + +# Parse the command line. + +sub showHelp { + print < [--config ] [--ensure-unique-name] + nixos-container destroy + nixos-container start + nixos-container stop + nixos-container login + nixos-container root-shell + nixos-container set-root-password + nixos-container show-ip +EOF + exit 0; +} + +my $ensureUniqueName = 0; +my $extraConfig = ""; + +GetOptions( + "help" => sub { showHelp() }, + "ensure-unique-name" => \$ensureUniqueName, + "config=s" => \$extraConfig + ) or exit 1; + +my $action = $ARGV[0] or die "$0: no action specified\n"; + + +# Execute the selected action. + +mkpath("/etc/containers", 0, 0755); +mkpath("/var/lib/containers", 0, 0700); + +if ($action eq "list") { + foreach my $confFile (glob "/etc/containers/*.conf") { + $confFile =~ /\/([^\/]+).conf$/ or next; + print "$1\n"; + } + exit 0; +} + +my $containerName = $ARGV[1] or die "$0: no container name specified\n"; +$containerName =~ /^[a-zA-Z0-9\-]+$/ or die "$0: invalid container name\n"; + +if ($action eq "create") { + # Acquire an exclusive lock to prevent races with other + # invocations of ‘nixos-container create’. + my $lockFN = "/run/lock/nixos-container"; + open(my $lock, '>>', $lockFN) or die "$0: opening $lockFN: $!"; + flock($lock, LOCK_EX) or die "$0: could not lock $lockFN: $!"; + + my $confFile = "/etc/containers/$containerName.conf"; + my $root = "/var/lib/containers/$containerName"; + + # Maybe generate a unique name. + if ($ensureUniqueName) { + my $base = $containerName; + for (my $nr = 0; ; $nr++) { + $containerName = "$base-$nr"; + $confFile = "/etc/containers/$containerName.conf"; + $root = "/var/lib/containers/$containerName"; + last unless -e $confFile || -e $root; + } + } + + die "$0: container ‘$containerName’ already exists\n" if -e $confFile; + + # Get an unused IP address. + my %usedIPs; + foreach my $confFile2 (glob "/etc/containers/*.conf") { + my $s = read_file($confFile2) or die; + $usedIPs{$1} = 1 if $s =~ /^HOST_ADDRESS=([0-9\.]+)$/m; + $usedIPs{$1} = 1 if $s =~ /^LOCAL_ADDRESS=([0-9\.]+)$/m; + } + + my ($ipPrefix, $hostAddress, $localAddress); + for (my $nr = 1; $nr < 255; $nr++) { + $ipPrefix = "10.233.$nr"; + $hostAddress = "$ipPrefix.1"; + $localAddress = "$ipPrefix.2"; + last unless $usedIPs{$hostAddress} || $usedIPs{$localAddress}; + $ipPrefix = undef; + } + + die "$0: out of IP addresses\n" unless defined $ipPrefix; + + my @conf; + push @conf, "PRIVATE_NETWORK=1\n"; + push @conf, "HOST_ADDRESS=$hostAddress\n"; + push @conf, "LOCAL_ADDRESS=$localAddress\n"; + write_file($confFile, \@conf); + + close($lock); + + print STDERR "host IP is $hostAddress, container IP is $localAddress\n"; + + mkpath("$root/etc/nixos", 0, 0755); + + my $nixosConfig = < ]; + $extraConfig +} +EOF + my $nixosConfigFile = "$root/etc/nixos/configuration.nix"; + write_file($nixosConfigFile, $nixosConfig); + + # The per-container directory is restricted to prevent users on + # the host from messing with guest users who happen to have the + # same uid. + my $profileDir = "/nix/var/nix/profiles/per-container"; + mkpath($profileDir, 0, 0700); + $profileDir = "$profileDir/$containerName"; + mkpath($profileDir, 0, 0755); + + system("nix-env", "-p", "$profileDir/system", + "-I", "nixos-config=$nixosConfigFile", "-f", "", + "--set", "-A", "system") == 0 + or die "$0: failed to build initial container configuration\n"; + + print "$containerName\n" if $ensureUniqueName; + exit 0; +} + +my $confFile = "/etc/containers/$containerName.conf"; +die "$0: container ‘$containerName’ does not exist\n" if !-e $confFile; + +sub stopContainer { + system("systemctl", "stop", "container\@$containerName") == 0 + or die "$0: failed to stop container\n"; +} + +if ($action eq "destroy") { + my $root = "/var/lib/containers/$containerName"; + my $profileDir = "/nix/var/nix/profiles/per-container/$containerName"; + + my $status = `systemctl show 'container\@$containerName'`; + stopContainer if $status =~ /ActiveState=active/; + + rmtree($profileDir) if -e $profileDir; + rmtree($root) if -e $root; + unlink($confFile) or die; +} + +elsif ($action eq "start") { + system("systemctl", "start", "container\@$containerName") == 0 + or die "$0: failed to start container\n"; +} + +elsif ($action eq "stop") { + stopContainer; +} + +elsif ($action eq "login") { + exec($socat, "unix:/var/lib/containers/$containerName/var/lib/login.socket", "-,echo=0,raw"); +} + +elsif ($action eq "root-shell") { + exec($socat, "unix:/var/lib/containers/$containerName/var/lib/root-shell.socket", "-"); +} + +elsif ($action eq "set-root-password") { + # FIXME: don't get password from the command line. + my $password = $ARGV[2] or die "$0: no password given\n"; + open(SOCAT, "|-", $socat, "unix:/var/lib/containers/$containerName/var/lib/root-shell.socket", "-"); + print SOCAT "passwd\n"; + print SOCAT "$password\n"; + print SOCAT "$password\n"; + close(SOCAT); +} + +elsif ($action eq "show-ip") { + my $s = read_file($confFile) or die; + $s =~ /^LOCAL_ADDRESS=([0-9\.]+)$/m or die "$0: cannot get IP address\n"; + print "$1\n"; +} + +else { + die "$0: unknown action ‘$action’\n"; +} diff --git a/nixos/modules/virtualisation/nixos-container.sh b/nixos/modules/virtualisation/nixos-container.sh deleted file mode 100644 index 47abf96072f3..000000000000 --- a/nixos/modules/virtualisation/nixos-container.sh +++ /dev/null @@ -1,171 +0,0 @@ -#! @bash@/bin/sh -e - -usage() { - echo "Usage: $0 list" >&2 - echo " $0 create [--config ] [--ensure-unique-name]" >&2 - echo " $0 update " >&2 - echo " $0 destroy " >&2 - echo " $0 login " >&2 - echo " $0 root-shell " >&2 - echo " $0 set-root-password " >&2 - echo " $0 show-ip " >&2 -} - -args="`getopt --options '' -l help -l config: -l ensure-unique-name -- "$@"`" -eval "set -- $args" -extraConfig= -ensureUniqueName= -while [ $# -gt 0 ]; do - case "$1" in - (--help) usage; exit 0;; - (--config) shift; extraConfig=$1;; - (--ensure-unique-name) ensureUniqueName=1;; - (--) shift; break;; - (*) break;; - esac - shift -done - -action="$1" -if [ -z "$action" ]; then usage; exit 1; fi -shift - -getContainerRoot() { - root="/var/lib/containers/$container" - if ! [ -d "$root" ]; then - echo "$0: container ‘$container’ does not exist" >&2 - exit 1 - fi -} - -if [ $action = list ]; then - for i in $(cd /etc/containers && echo *.conf); do - echo "$(basename "$i" .conf)" - done - exit 0 -fi - -container="$1" -if [ -z "$container" ]; then usage; exit 1; fi -shift - -if [ $action = create ]; then - - if [ -n "$ensureUniqueName" ]; then - # FIXME: race - nr=0 - while [ -e "/etc/containers/$container-$nr.conf" -o -e "/var/lib/containers/$container-$nr" ]; do - : $((nr++)) - done - container="$container-$nr" - fi - - confFile="/etc/containers/$container.conf" - root="/var/lib/containers/$container" - - if [ -e "$confFile" -o -e "$root/nix" ]; then - echo "$0: container ‘$container’ already exists" >&2 - exit 1 - fi - - profileDir="/nix/var/nix/profiles/per-container/$container" - mkdir -m 0755 -p "$root/etc/nixos" "$profileDir" - - config=" -{ config, pkgs, ... }: - -with pkgs.lib; - -{ boot.isContainer = true; - security.initialRootPassword = mkDefault \"!\"; - networking.hostName = mkDefault \"$container\"; - networking.useDHCP = false; - imports = [ ]; - $extraConfig -}" - configFile="$root/etc/nixos/configuration.nix" - echo "$config" > "$configFile" - - nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '' --set -A system - - # Allocate a new /8 network in the 10.233.* range. FIXME: race - network="$(sed -e 's/.*_ADDRESS=10\.233\.\(.*\)\..*/\1/; t; d' /etc/containers/*.conf | sort -n | tail -n1)" - if [ -z "$network" ]; then network=0; else : $((network++)); fi - - hostAddress="10.233.$network.1" - localAddress="10.233.$network.2" - echo "host IP is $hostAddress, container IP is $localAddress" >&2 - - cat > "$confFile" <&2 - systemctl start "container@$container.service" - - # Print generated container name on stdout. - if [ -n "$ensureUniqueName" ]; then - echo "$container" - fi - -elif [ $action = update ]; then - - getContainerRoot - - configFile="$root/etc/nixos/configuration.nix" - profileDir="/nix/var/nix/profiles/per-container/$container" - - nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '' --set -A system - - echo "reloading container@$container.service..." >&2 - systemctl reload "container@$container.service" - -elif [ $action = destroy ]; then - - getContainerRoot - - confFile="/etc/containers/$container.conf" - if [ -e "$confFile" -a ! -w "$confFile" ]; then - echo "$0: cannot destroy declarative container (remove it from your configuration.nix instead)" - exit 1 - fi - - if systemctl show "container@$container.service" | grep -q ActiveState=active; then - echo "stopping container@$container.service..." >&2 - systemctl stop "container@$container.service" - fi - - rm -f "$confFile" - rm -rf "$root" - -elif [ $action = login ]; then - - getContainerRoot - exec @socat@/bin/socat "unix:$root/var/lib/login.socket" -,echo=0,raw - -elif [ $action = root-shell ]; then - - getContainerRoot - exec @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - - -elif [ $action = set-root-password ]; then - - password="$1" - if [ -z "$password" ]; then usage; exit 1; fi - - # FIXME: not very secure. - getContainerRoot - (echo "passwd"; echo "$password"; echo "$password") | @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - - -elif [ $action = show-ip ]; then - - getContainerRoot - . "/etc/containers/$container.conf" - echo "$LOCAL_ADDRESS" - -else - echo "$0: unknown action ‘$action’" >&2 - exit 1 -fi -- cgit 1.4.1 From fee81c37398f547faddfff7ee10d945bb4513f09 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 1 Apr 2014 15:42:18 +0200 Subject: Always enable container logins --- nixos/modules/module-list.nix | 1 + nixos/modules/virtualisation/container-login.nix | 6 ++++-- nixos/modules/virtualisation/containers.nix | 1 - nixos/modules/virtualisation/nixos-container.pl | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 1c2fca1f88b5..a7bf69cfc792 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -294,6 +294,7 @@ ./tasks/scsi-link-power-management.nix ./tasks/swraid.nix ./testing/service-runner.nix + ./virtualisation/container-login.nix ./virtualisation/containers.nix ./virtualisation/libvirtd.nix #./virtualisation/nova.nix diff --git a/nixos/modules/virtualisation/container-login.nix b/nixos/modules/virtualisation/container-login.nix index 09eaf90ae658..fb5e333b32ab 100644 --- a/nixos/modules/virtualisation/container-login.nix +++ b/nixos/modules/virtualisation/container-login.nix @@ -1,8 +1,10 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: + +with lib; { - config = { + config = mkIf config.boot.isContainer { # Provide a login prompt on /var/lib/login.socket. On the host, # you can connect to it by running ‘socat diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 9964cd431cd7..6c8a6f876c8d 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -105,7 +105,6 @@ in security.initialRootPassword = mkDefault "!"; networking.hostName = mkDefault name; networking.useDHCP = false; - imports = [ ./container-login.nix ]; }; in [ extraConfig config.config ]; prefix = [ "containers" name ]; diff --git a/nixos/modules/virtualisation/nixos-container.pl b/nixos/modules/virtualisation/nixos-container.pl index dfc856e8b667..b08ed0766583 100644 --- a/nixos/modules/virtualisation/nixos-container.pl +++ b/nixos/modules/virtualisation/nixos-container.pl @@ -116,7 +116,6 @@ with pkgs.lib; security.initialRootPassword = mkDefault "!"; networking.hostName = mkDefault "$containerName"; networking.useDHCP = false; - imports = [ ]; $extraConfig } EOF -- cgit 1.4.1 From 1ad9a654be1120a6844c9eb7520188e874178ebe Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 1 Apr 2014 16:02:53 +0200 Subject: Make starting a container synchronous So now "systemctl start container@foo" will only return after the container has reached multi-user.target. --- nixos/modules/virtualisation/container-config.nix | 72 +++++++++++++++++++++++ nixos/modules/virtualisation/container-login.nix | 58 ------------------ nixos/modules/virtualisation/containers.nix | 25 +++++++- 3 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 nixos/modules/virtualisation/container-config.nix delete mode 100644 nixos/modules/virtualisation/container-login.nix (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix new file mode 100644 index 000000000000..3d107899e4fe --- /dev/null +++ b/nixos/modules/virtualisation/container-config.nix @@ -0,0 +1,72 @@ +{ config, pkgs, lib, ... }: + +with lib; + +{ + + config = mkIf config.boot.isContainer { + + # Provide a login prompt on /var/lib/login.socket. On the host, + # you can connect to it by running ‘socat + # unix:/var/lib/login.socket -,echo=0,raw’. + systemd.sockets.login = + { description = "Login Socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = + { ListenStream = "/var/lib/login.socket"; + SocketMode = "0666"; + Accept = true; + }; + }; + + systemd.services."login@" = + { description = "Login %i"; + environment.TERM = "linux"; + serviceConfig = + { Type = "simple"; + StandardInput = "socket"; + ExecStart = "${pkgs.socat}/bin/socat -t0 - exec:${pkgs.shadow}/bin/login,pty,setsid,setpgid,stderr,ctty"; + TimeoutStopSec = 1; # FIXME + }; + }; + + # Provide a non-interactive login root shell on + # /var/lib/root-shell.socket. On the host, you can connect to it + # by running ‘socat unix:/var/lib/root-shell.socket -’. + systemd.sockets.root-shell = + { description = "Root Shell Socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = + { ListenStream = "/var/lib/root-shell.socket"; + SocketMode = "0600"; # only root can connect, obviously + Accept = true; + }; + }; + + systemd.services."root-shell@" = + { description = "Root Shell %i"; + serviceConfig = + { Type = "simple"; + StandardInput = "socket"; + ExecStart = "${pkgs.bash}/bin/bash --login"; + TimeoutStopSec = 1; # FIXME + }; + }; + + systemd.services.container-startup-done = + { description = "Container Startup Notification"; + wantedBy = [ "multi-user.target" ]; + after = [ "multi-user.target" ]; + script = + '' + if [ -p /var/lib/startup-done ]; then + echo done > /var/lib/startup-done + fi + ''; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + }; + + }; + +} diff --git a/nixos/modules/virtualisation/container-login.nix b/nixos/modules/virtualisation/container-login.nix deleted file mode 100644 index fb5e333b32ab..000000000000 --- a/nixos/modules/virtualisation/container-login.nix +++ /dev/null @@ -1,58 +0,0 @@ -{ config, pkgs, lib, ... }: - -with lib; - -{ - - config = mkIf config.boot.isContainer { - - # Provide a login prompt on /var/lib/login.socket. On the host, - # you can connect to it by running ‘socat - # unix:/var/lib/login.socket -,echo=0,raw’. - systemd.sockets.login = - { description = "Login Socket"; - wantedBy = [ "sockets.target" ]; - socketConfig = - { ListenStream = "/var/lib/login.socket"; - SocketMode = "0666"; - Accept = true; - }; - }; - - systemd.services."login@" = - { description = "Login %i"; - environment.TERM = "linux"; - serviceConfig = - { Type = "simple"; - StandardInput = "socket"; - ExecStart = "${pkgs.socat}/bin/socat -t0 - exec:${pkgs.shadow}/bin/login,pty,setsid,setpgid,stderr,ctty"; - TimeoutStopSec = 1; # FIXME - }; - }; - - # Provide a non-interactive login root shell on - # /var/lib/root-shell.socket. On the host, you can connect to it - # by running ‘socat unix:/var/lib/root-shell.socket -’. - systemd.sockets.root-shell = - { description = "Root Shell Socket"; - wantedBy = [ "sockets.target" ]; - socketConfig = - { ListenStream = "/var/lib/root-shell.socket"; - SocketMode = "0600"; # only root can connect, obviously - Accept = true; - }; - }; - - systemd.services."root-shell@" = - { description = "Root Shell %i"; - serviceConfig = - { Type = "simple"; - StandardInput = "socket"; - ExecStart = "${pkgs.bash}/bin/bash --login"; - TimeoutStopSec = 1; # FIXME - }; - }; - - }; - -} diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 6c8a6f876c8d..9be79cec3695 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -150,11 +150,21 @@ in 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 = '' - root="/var/lib/containers/$INSTANCE" - mkdir -p -m 0755 "$root/etc" + mkdir -p -m 0755 "$root/etc" "$root/var/lib" if ! [ -e "$root/etc/os-release" ]; then touch "$root/etc/os-release" fi @@ -209,6 +219,13 @@ in "$SYSTEM_PATH/init" ''; + postStart = + '' + # This blocks until the container-startup-done service + # writes something to this pipe. + read x < $root/var/lib/startup-done + ''; + preStop = '' pid="$(cat /sys/fs/cgroup/systemd/machine/$INSTANCE.nspawn/system/tasks 2> /dev/null)" @@ -238,8 +255,10 @@ in . "/etc/containers/$INSTANCE.conf" fi echo $SYSTEM_PATH/bin/switch-to-configuration test | \ - ${pkgs.socat}/bin/socat unix:/var/lib/containers/$INSTANCE/var/lib/root-shell.socket - + ${pkgs.socat}/bin/socat unix:$root/var/lib/root-shell.socket - ''; + + serviceConfig.SyslogIdentifier = "container %i"; }; # Generate a configuration file in /etc/containers for each -- cgit 1.4.1 From b0b3fa928ad572c67bc3545c2b160bd8aef79d7a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 1 Apr 2014 16:35:11 +0200 Subject: Disable container support in containers Systemd-nspawn doesn't support nesting, so providing nixos-container inside a container doesn't make sense. --- nixos/modules/virtualisation/containers.nix | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 9be79cec3695..097dd3993eb5 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -140,7 +140,7 @@ in }; - config = { + config = mkIf (!config.boot.isContainer) { systemd.services."container@" = { description = "Container '%i'"; @@ -222,7 +222,8 @@ in postStart = '' # This blocks until the container-startup-done service - # writes something to this pipe. + # writes something to this pipe. FIXME: it also hangs + # until the start timeout expires if systemd-nspawn exits. read x < $root/var/lib/startup-done ''; -- cgit 1.4.1 From 1e4fa227fe434c2042a2c690f62a7072afa36e93 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Apr 2014 16:25:21 +0200 Subject: nixos-container: Don't destroy declarative containers --- nixos/modules/virtualisation/nixos-container.pl | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/nixos-container.pl b/nixos/modules/virtualisation/nixos-container.pl index b08ed0766583..e42a3edd0243 100644 --- a/nixos/modules/virtualisation/nixos-container.pl +++ b/nixos/modules/virtualisation/nixos-container.pl @@ -1,6 +1,7 @@ #! @perl@ use strict; +use POSIX; use File::Path; use File::Slurp; use Fcntl ':flock'; @@ -148,6 +149,9 @@ sub stopContainer { } if ($action eq "destroy") { + die "$0: cannot destroy declarative container (remove it from your configuration.nix instead)\n" + unless POSIX::access($confFile, &POSIX::W_OK); + my $root = "/var/lib/containers/$containerName"; my $profileDir = "/nix/var/nix/profiles/per-container/$containerName"; -- cgit 1.4.1 From 3dca6b98cb6549ef7ba0195a6ac1bbc811f8db66 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Apr 2014 10:53:49 +0200 Subject: Fix permissions on /var/lib/startup-done --- nixos/modules/virtualisation/containers.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 097dd3993eb5..9d54ddb9948e 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -159,7 +159,7 @@ in # 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 + mkfifo -m 0600 $root/var/lib/startup-done ''; script = -- cgit 1.4.1 From da4f180252e9a8f539a019569efc82000ffe5440 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Apr 2014 11:32:50 +0200 Subject: Bring back ‘nixos-container update’ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/modules/virtualisation/nixos-container.pl | 71 +++++++++++++++++-------- 1 file changed, 50 insertions(+), 21 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/nixos-container.pl b/nixos/modules/virtualisation/nixos-container.pl index e42a3edd0243..b6919852b285 100644 --- a/nixos/modules/virtualisation/nixos-container.pl +++ b/nixos/modules/virtualisation/nixos-container.pl @@ -54,6 +54,25 @@ if ($action eq "list") { my $containerName = $ARGV[1] or die "$0: no container name specified\n"; $containerName =~ /^[a-zA-Z0-9\-]+$/ or die "$0: invalid container name\n"; +sub writeNixOSConfig { + my ($nixosConfigFile) = @_; + + my $nixosConfig = <", + "--set", "-A", "system") == 0 + or die "$0: failed to build container configuration\n"; + + if (isContainerRunning) { + print STDERR "reloading container...\n"; + system("systemctl", "reload", "container\@$containerName") == 0 + or die "$0: failed to reload container\n"; + } +} + elsif ($action eq "login") { - exec($socat, "unix:/var/lib/containers/$containerName/var/lib/login.socket", "-,echo=0,raw"); + exec($socat, "unix:$root/var/lib/login.socket", "-,echo=0,raw"); } elsif ($action eq "root-shell") { - exec($socat, "unix:/var/lib/containers/$containerName/var/lib/root-shell.socket", "-"); + exec($socat, "unix:$root/var/lib/root-shell.socket", "-"); } elsif ($action eq "set-root-password") { # FIXME: don't get password from the command line. my $password = $ARGV[2] or die "$0: no password given\n"; - open(SOCAT, "|-", $socat, "unix:/var/lib/containers/$containerName/var/lib/root-shell.socket", "-"); + open(SOCAT, "|-", $socat, "unix:$root/var/lib/root-shell.socket", "-"); print SOCAT "passwd\n"; print SOCAT "$password\n"; print SOCAT "$password\n"; -- cgit 1.4.1 From ac8c924c0931237461266c2780e744c63880180c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Apr 2014 13:12:34 +0200 Subject: nixos-container: Add ‘run’ and ‘root-login’ commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And remove ‘root-shell’. --- nixos/modules/virtualisation/container-config.nix | 51 ++++++++++++++++++----- nixos/modules/virtualisation/containers.nix | 2 +- nixos/modules/virtualisation/nixos-container.pl | 16 +++++-- 3 files changed, 54 insertions(+), 15 deletions(-) (limited to 'nixos/modules/virtualisation') diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix index 3d107899e4fe..21e64c8c0957 100644 --- a/nixos/modules/virtualisation/container-config.nix +++ b/nixos/modules/virtualisation/container-config.nix @@ -30,27 +30,58 @@ with lib; }; }; - # Provide a non-interactive login root shell on - # /var/lib/root-shell.socket. On the host, you can connect to it - # by running ‘socat unix:/var/lib/root-shell.socket -’. - systemd.sockets.root-shell = - { description = "Root Shell Socket"; + # Also provide a root login prompt on /var/lib/root-login.socket + # that doesn't ask for a password. This socket can only be used by + # root on the host. + systemd.sockets.root-login = + { description = "Root Login Socket"; wantedBy = [ "sockets.target" ]; socketConfig = - { ListenStream = "/var/lib/root-shell.socket"; - SocketMode = "0600"; # only root can connect, obviously + { ListenStream = "/var/lib/root-login.socket"; + SocketMode = "0600"; Accept = true; }; }; - systemd.services."root-shell@" = - { description = "Root Shell %i"; + systemd.services."root-login@" = + { description = "Root Login %i"; + environment.TERM = "linux"; + serviceConfig = + { Type = "simple"; + StandardInput = "socket"; + ExecStart = "${pkgs.socat}/bin/socat -t0 - \"exec:${pkgs.shadow}/bin/login -f root,pty,setsid,setpgid,stderr,ctty\""; + TimeoutStopSec = 1; # FIXME + }; + }; + + # Provide a daemon on /var/lib/run-command.socket that reads a + # command from stdin and executes it. + systemd.sockets.run-command = + { description = "Run Command Socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = + { ListenStream = "/var/lib/run-command.socket"; + SocketMode = "0600"; # only root can connect + Accept = true; + }; + }; + + systemd.services."run-command@" = + { description = "Run Command %i"; + environment.TERM = "linux"; serviceConfig = { Type = "simple"; StandardInput = "socket"; - ExecStart = "${pkgs.bash}/bin/bash --login"; TimeoutStopSec = 1; # FIXME }; + script = + '' + #! ${pkgs.stdenv.shell} -e + source /etc/bashrc + read c + eval "command=($c)" + exec "''${command[@]}" + ''; }; systemd.services.container-startup-done = diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 9d54ddb9948e..fbdd3f9034c6 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -256,7 +256,7 @@ in . "/etc/containers/$INSTANCE.conf" fi echo $SYSTEM_PATH/bin/switch-to-configuration test | \ - ${pkgs.socat}/bin/socat unix:$root/var/lib/root-shell.socket - + ${pkgs.socat}/bin/socat unix:$root/var/lib/run-command.socket - ''; serviceConfig.SyslogIdentifier = "container %i"; diff --git a/nixos/modules/virtualisation/nixos-container.pl b/nixos/modules/virtualisation/nixos-container.pl index b6919852b285..d7e8c7339b6d 100644 --- a/nixos/modules/virtualisation/nixos-container.pl +++ b/nixos/modules/virtualisation/nixos-container.pl @@ -19,7 +19,8 @@ Usage: nixos-container list nixos-container start nixos-container stop nixos-container login - nixos-container root-shell + nixos-container root-login + nixos-container run -- args... nixos-container set-root-password nixos-container show-ip EOF @@ -205,14 +206,21 @@ elsif ($action eq "login") { exec($socat, "unix:$root/var/lib/login.socket", "-,echo=0,raw"); } -elsif ($action eq "root-shell") { - exec($socat, "unix:$root/var/lib/root-shell.socket", "-"); +elsif ($action eq "root-login") { + exec($socat, "unix:$root/var/lib/root-login.socket", "-,echo=0,raw"); +} + +elsif ($action eq "run") { + shift @ARGV; shift @ARGV; + open(SOCAT, "|-", $socat, "unix:$root/var/lib/run-command.socket", "-"); + print SOCAT join(' ', map { "'$_'" } @ARGV), "\n"; + close(SOCAT); } elsif ($action eq "set-root-password") { # FIXME: don't get password from the command line. my $password = $ARGV[2] or die "$0: no password given\n"; - open(SOCAT, "|-", $socat, "unix:$root/var/lib/root-shell.socket", "-"); + open(SOCAT, "|-", $socat, "unix:$root/var/lib/run-command.socket", "-"); print SOCAT "passwd\n"; print SOCAT "$password\n"; print SOCAT "$password\n"; -- cgit 1.4.1 From 6a7a8a144fae43fae51232703bf742c8bcee8d67 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Apr 2014 14:57:40 +0200 Subject: Document NixOS containers --- nixos/doc/manual/containers.xml | 242 ++++++++++++++++++++++++++++ nixos/doc/manual/manual.xml | 1 + nixos/modules/virtualisation/containers.nix | 2 + 3 files changed, 245 insertions(+) create mode 100644 nixos/doc/manual/containers.xml (limited to 'nixos/modules/virtualisation') diff --git a/nixos/doc/manual/containers.xml b/nixos/doc/manual/containers.xml new file mode 100644 index 000000000000..b8f170fc614f --- /dev/null +++ b/nixos/doc/manual/containers.xml @@ -0,0 +1,242 @@ + + +Containers + +NixOS allows you to easily run other NixOS instances as +containers. Containers are a light-weight +approach to virtualisation that runs software in the container at the +same speed as in the host system. NixOS containers share the Nix store +of the host, making container creation very efficient. + +Currently, NixOS containers are not perfectly isolated +from the host system. This means that a user with root access to the +container can do things that affect the host. So you should not give +container root access to untrusted users. + +NixOS containers can be created in two ways: imperatively, using +the command nixos-container, and declaratively, by +specifying them in your configuration.nix. The +declarative approach implies that containers get upgraded along with +your host system when you run nixos-rebuild, which +is often not what you want. By contrast, in the imperative approach, +containers are configured and updated independently from the host +system. + + +
Imperative container management + +We’ll cover imperative container management using +nixos-container first. You create a container with +identifier foo as follows: + + +$ nixos-container create foo + + +This creates the container’s root directory in +/var/lib/containers/foo and a small configuration +file in /etc/containers/foo.conf. It also builds +the container’s initial system configuration and stores it in +/nix/var/nix/profiles/per-container/foo/system. You +can modify the initial configuration of the container on the command +line. For instance, to create a container that has +sshd running, with the given public key for +root: + + +$ nixos-container create foo --config 'services.openssh.enable = true; \ + users.extraUsers.root.openssh.authorizedKeys.keys = ["ssh-dss AAAAB3N…"];' + + + + +Creating a container does not start it. To start the container, +run: + + +$ nixos-container start foo + + +This command will return as soon as the container has booted and has +reached multi-user.target. On the host, the +container runs within a systemd unit called +container@container-name.service. +Thus, if something went wrong, you can get status info using +systemctl: + + +$ systemctl status container@foo + + + + +If the container has started succesfully, you can log in as +root using the root-login operation: + + +$ nixos-container root-login foo +[root@foo:~]# + + +Note that only root on the host can do this (since there is no +authentication). You can also get a regular login prompt using the +login operation, which is available to all users on +the host: + + +$ nixos-container login foo +foo login: alice +Password: *** + + +With nixos-container run, you can execute arbitrary +commands in the container: + + +$ nixos-container run foo -- uname -a +Linux foo 3.4.82 #1-NixOS SMP Thu Mar 20 14:44:05 UTC 2014 x86_64 GNU/Linux + + + + +There are several ways to change the configuration of the +container. First, on the host, you can edit +/var/lib/container/name/etc/nixos/configuration.nix, +and run + + +$ nixos-container update foo + + +This will build and activate the new configuration. You can also +specify a new configuration on the command line: + + +$ nixos-container update foo --config 'services.httpd.enable = true; \ + services.httpd.adminAddr = "foo@example.org";' + +$ curl http://$(nixos-container show-ip foo)/ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">… + + +However, note that this will overwrite the container’s +/etc/nixos/configuration.nix. + +Alternatively, you can change the configuration from within the +container itself by running nixos-rebuild switch +inside the container. Note that the container by default does not have +a copy of the NixOS channel, so you should run nix-channel +--update first. + +Containers can be stopped and started using +nixos-container stop and nixos-container +start, respectively, or by using +systemctl on the container’s service unit. To +destroy a container, including its file system, do + + +$ nixos-container destroy foo + + + + +
+ + +
Declarative container specification + +You can also specify containers and their configuration in the +host’s configuration.nix. For example, the +following specifies that there shall be a container named +database running PostgreSQL: + + +containers.database = + { config = + { config, pkgs, ... }: + { services.postgresql.enable = true; + services.postgresql.package = pkgs.postgresql92; + }; + }; + + +If you run nixos-rebuild switch, the container will +be built and started. If the container was already running, it will be +updated in place, without rebooting. + +By default, declarative containers share the network namespace +of the host, meaning that they can listen on (privileged) +ports. However, they cannot change the network configuration. You can +give a container its own network as follows: + + +containers.database = + { privateNetwork = true; + hostAddress = "192.168.100.10"; + localAddress = "192.168.100.11"; + }; + + +This gives the container a private virtual Ethernet interface with IP +address 192.168.100.11, which is hooked up to a +virtual Ethernet interface on the host with IP address +192.168.100.10. (See the next section for details +on container networking.) + +To disable the container, just remove it from +configuration.nix and run nixos-rebuild +switch. Note that this will not delete the root directory of +the container in /var/lib/containers. + +
+ + +
Networking + +When you create a container using nixos-container +create, it gets it own private IPv4 address in the range +10.233.0.0/16. You can get the container’s IPv4 +address as follows: + + +$ nixos-container show-ip foo +10.233.4.2 + +$ ping -c1 10.233.4.2 +64 bytes from 10.233.4.2: icmp_seq=1 ttl=64 time=0.106 ms + + + + +Networking is implemented using a pair of virtual Ethernet +devices. The network interface in the container is called +eth0, while the matching interface in the host is +called c-container-name +(e.g., c-foo). The container has its own network +namespace and the CAP_NET_ADMIN capability, so it +can perform arbitrary network configuration such as setting up +firewall rules, without affecting or having access to the host’s +network. + +By default, containers cannot talk to the outside network. If +you want that, you should set up Network Address Translation (NAT) +rules on the host to rewrite container traffic to use your external +IP address. This can be accomplished using the following configuration +on the host: + + +networking.nat.enable = true; +networking.nat.internalInterfaces = ["c-+"]; +networking.nat.externalInterface = "eth0"; + +where eth0 should be replaced with the desired +external interface. Note that c-+ is a wildcard +that matches all container interfaces. + +
+ + +
+ diff --git a/nixos/doc/manual/manual.xml b/nixos/doc/manual/manual.xml index f9775f4f0170..5753a8ff9e74 100644 --- a/nixos/doc/manual/manual.xml +++ b/nixos/doc/manual/manual.xml @@ -54,6 +54,7 @@ + diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index fbdd3f9034c6..c53bd7d3509d 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -281,6 +281,8 @@ in ''; }) config.containers; + # FIXME: auto-start containers. + # Generate /etc/hosts entries for the containers. networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null) '' -- cgit 1.4.1