2014-03-31 18:21:36 +01:00
|
|
|
|
#! @perl@
|
|
|
|
|
|
|
|
|
|
use strict;
|
2014-04-03 15:25:21 +01:00
|
|
|
|
use POSIX;
|
2014-03-31 18:21:36 +01:00
|
|
|
|
use File::Path;
|
|
|
|
|
use File::Slurp;
|
|
|
|
|
use Fcntl ':flock';
|
|
|
|
|
use Getopt::Long qw(:config gnu_getopt);
|
|
|
|
|
|
|
|
|
|
my $socat = '@socat@/bin/socat';
|
|
|
|
|
|
2014-05-09 12:24:42 +01:00
|
|
|
|
# Ensure a consistent umask.
|
|
|
|
|
umask 0022;
|
|
|
|
|
|
2014-03-31 18:21:36 +01:00
|
|
|
|
# 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>
|
2014-04-10 12:12:34 +01:00
|
|
|
|
nixos-container root-login <container-name>
|
|
|
|
|
nixos-container run <container-name> -- args...
|
2014-03-31 18:21:36 +01:00
|
|
|
|
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";
|
|
|
|
|
|
2014-04-10 10:32:50 +01:00
|
|
|
|
sub writeNixOSConfig {
|
|
|
|
|
my ($nixosConfigFile) = @_;
|
|
|
|
|
|
|
|
|
|
my $nixosConfig = <<EOF;
|
2014-04-14 15:26:48 +01:00
|
|
|
|
{ config, lib, pkgs, ... }:
|
2014-04-10 10:32:50 +01:00
|
|
|
|
|
2014-04-14 15:26:48 +01:00
|
|
|
|
with lib;
|
2014-04-10 10:32:50 +01:00
|
|
|
|
|
|
|
|
|
{ boot.isContainer = true;
|
|
|
|
|
networking.hostName = mkDefault "$containerName";
|
|
|
|
|
networking.useDHCP = false;
|
|
|
|
|
$extraConfig
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
write_file($nixosConfigFile, $nixosConfig);
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 18:21:36 +01:00
|
|
|
|
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 $nixosConfigFile = "$root/etc/nixos/configuration.nix";
|
2014-04-10 10:32:50 +01:00
|
|
|
|
writeNixOSConfig $nixosConfigFile;
|
2014-03-31 18:21:36 +01:00
|
|
|
|
|
|
|
|
|
# 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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 10:32:50 +01:00
|
|
|
|
my $root = "/var/lib/containers/$containerName";
|
|
|
|
|
my $profileDir = "/nix/var/nix/profiles/per-container/$containerName";
|
2014-03-31 18:21:36 +01:00
|
|
|
|
my $confFile = "/etc/containers/$containerName.conf";
|
|
|
|
|
die "$0: container ‘$containerName’ does not exist\n" if !-e $confFile;
|
|
|
|
|
|
2014-04-10 10:32:50 +01:00
|
|
|
|
sub isContainerRunning {
|
|
|
|
|
my $status = `systemctl show 'container\@$containerName'`;
|
|
|
|
|
return $status =~ /ActiveState=active/;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 18:21:36 +01:00
|
|
|
|
sub stopContainer {
|
|
|
|
|
system("systemctl", "stop", "container\@$containerName") == 0
|
|
|
|
|
or die "$0: failed to stop container\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($action eq "destroy") {
|
2014-04-03 15:25:21 +01:00
|
|
|
|
die "$0: cannot destroy declarative container (remove it from your configuration.nix instead)\n"
|
|
|
|
|
unless POSIX::access($confFile, &POSIX::W_OK);
|
|
|
|
|
|
2014-04-10 10:32:50 +01:00
|
|
|
|
stopContainer if isContainerRunning;
|
2014-03-31 18:21:36 +01:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 10:32:50 +01:00
|
|
|
|
elsif ($action eq "update") {
|
|
|
|
|
my $nixosConfigFile = "$root/etc/nixos/configuration.nix";
|
|
|
|
|
|
|
|
|
|
# FIXME: may want to be more careful about clobbering the existing
|
|
|
|
|
# configuration.nix.
|
|
|
|
|
writeNixOSConfig $nixosConfigFile if defined $extraConfig;
|
|
|
|
|
|
|
|
|
|
system("nix-env", "-p", "$profileDir/system",
|
|
|
|
|
"-I", "nixos-config=$nixosConfigFile", "-f", "<nixpkgs/nixos>",
|
|
|
|
|
"--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";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 18:21:36 +01:00
|
|
|
|
elsif ($action eq "login") {
|
2014-04-18 19:47:31 +01:00
|
|
|
|
exec("machinectl", "login", "--", $containerName);
|
2014-03-31 18:21:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 12:12:34 +01:00
|
|
|
|
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;
|
2014-04-25 16:04:51 +01:00
|
|
|
|
my $pid = open(SOCAT, "|-", $socat, "-t0", "-", "unix:$root/var/lib/run-command.socket") or die "$0: cannot start $socat: $!\n";
|
2014-04-10 12:12:34 +01:00
|
|
|
|
print SOCAT join(' ', map { "'$_'" } @ARGV), "\n";
|
2014-04-25 16:04:51 +01:00
|
|
|
|
flush SOCAT;
|
|
|
|
|
waitpid($pid, 0);
|
2014-04-10 12:12:34 +01:00
|
|
|
|
close(SOCAT);
|
2014-03-31 18:21:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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";
|
2014-04-25 16:04:51 +01:00
|
|
|
|
my $pid = open(SOCAT, "|-", $socat, "-t0", "-", "unix:$root/var/lib/run-command.socket") or die "$0: cannot start $socat: $!\n";
|
2014-03-31 18:21:36 +01:00
|
|
|
|
print SOCAT "passwd\n";
|
|
|
|
|
print SOCAT "$password\n";
|
|
|
|
|
print SOCAT "$password\n";
|
2014-04-25 16:04:51 +01:00
|
|
|
|
flush SOCAT;
|
|
|
|
|
waitpid($pid, 0);
|
2014-03-31 18:21:36 +01:00
|
|
|
|
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";
|
|
|
|
|
}
|