#! @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 = <", "--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"; }