nixos-container: Rewrite in Perl
Also fix race condition when multiple containers are created simultaneously (as NixOps tends to do).
This commit is contained in:
parent
bdb658d033
commit
6da72a4456
@ -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
|
||||
|
198
nixos/modules/virtualisation/nixos-container.pl
Normal file
198
nixos/modules/virtualisation/nixos-container.pl
Normal file
@ -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";
|
||||
}
|
@ -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
|
Loading…
Reference in New Issue
Block a user