summary refs log tree commit diff
path: root/nixos/modules/virtualisation
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2014-03-31 19:21:36 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2014-03-31 19:49:15 +0200
commit6da72a4456ea7cc12219c962739f279e5060fd66 (patch)
treea4c8cab0bc7918287eabdba9123cd5d58562b952 /nixos/modules/virtualisation
parentbdb658d033df47c97a0cdbe9628fdcf8c086e149 (diff)
downloadnixlib-6da72a4456ea7cc12219c962739f279e5060fd66.tar
nixlib-6da72a4456ea7cc12219c962739f279e5060fd66.tar.gz
nixlib-6da72a4456ea7cc12219c962739f279e5060fd66.tar.bz2
nixlib-6da72a4456ea7cc12219c962739f279e5060fd66.tar.lz
nixlib-6da72a4456ea7cc12219c962739f279e5060fd66.tar.xz
nixlib-6da72a4456ea7cc12219c962739f279e5060fd66.tar.zst
nixlib-6da72a4456ea7cc12219c962739f279e5060fd66.zip
nixos-container: Rewrite in Perl
Also fix race condition when multiple containers are created
simultaneously (as NixOps tends to do).
Diffstat (limited to 'nixos/modules/virtualisation')
-rw-r--r--nixos/modules/virtualisation/containers.nix5
-rw-r--r--nixos/modules/virtualisation/nixos-container.pl198
-rw-r--r--nixos/modules/virtualisation/nixos-container.sh171
3 files changed, 201 insertions, 173 deletions
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 <<EOF;
+Usage: nixos-container list
+       nixos-container create <container-name> [--config <string>] [--ensure-unique-name]
+       nixos-container destroy <container-name>
+       nixos-container start <container-name>
+       nixos-container stop <container-name>
+       nixos-container login <container-name>
+       nixos-container root-shell <container-name>
+       nixos-container set-root-password <container-name> <password>
+       nixos-container show-ip <container-name>
+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 = <<EOF;
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{ boot.isContainer = true;
+  security.initialRootPassword = mkDefault "!";
+  networking.hostName = mkDefault "$containerName";
+  networking.useDHCP = false;
+  imports = [ <nixpkgs/nixos/modules/virtualisation/container-login.nix> ];
+  $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", "<nixpkgs/nixos>",
+           "--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 <container-name> [--config <string>] [--ensure-unique-name]" >&2
-    echo "       $0 update <container-name>" >&2
-    echo "       $0 destroy <container-name>" >&2
-    echo "       $0 login <container-name>" >&2
-    echo "       $0 root-shell <container-name>" >&2
-    echo "       $0 set-root-password <container-name> <password>" >&2
-    echo "       $0 show-ip <container-name>" >&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 = [ <nixpkgs/nixos/modules/virtualisation/container-login.nix> ];
-  $extraConfig
-}"
-    configFile="$root/etc/nixos/configuration.nix"
-    echo "$config" > "$configFile"
-
-    nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '<nixpkgs/nixos>' --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" <<EOF
-PRIVATE_NETWORK=1
-HOST_ADDRESS=$hostAddress
-LOCAL_ADDRESS=$localAddress
-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
-
-    configFile="$root/etc/nixos/configuration.nix"
-    profileDir="/nix/var/nix/profiles/per-container/$container"
-
-    nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '<nixpkgs/nixos>' --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