2021-02-24 20:53:45 +01:00
|
|
|
|
#! @perl@/bin/perl
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
|
|
|
|
use strict;
|
2014-04-03 16:25:21 +02:00
|
|
|
|
use POSIX;
|
2014-03-31 19:21:36 +02:00
|
|
|
|
use File::Path;
|
|
|
|
|
use File::Slurp;
|
|
|
|
|
use Fcntl ':flock';
|
2020-06-30 13:42:48 +02:00
|
|
|
|
use Getopt::Long qw(:config gnu_getopt no_bundling);
|
2016-06-24 16:02:26 +02:00
|
|
|
|
use Cwd 'abs_path';
|
2017-12-11 04:05:15 +01:00
|
|
|
|
use Time::HiRes;
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2020-03-26 01:17:32 +01:00
|
|
|
|
my $nsenter = "@utillinux@/bin/nsenter";
|
2017-03-21 16:45:47 +01:00
|
|
|
|
my $su = "@su@";
|
|
|
|
|
|
2020-05-08 15:40:00 +02:00
|
|
|
|
my $configurationDirectory = "@configurationDirectory@";
|
|
|
|
|
my $stateDirectory = "@stateDirectory@";
|
|
|
|
|
|
2014-05-09 13:24:42 +02:00
|
|
|
|
# Ensure a consistent umask.
|
|
|
|
|
umask 0022;
|
|
|
|
|
|
2017-03-22 14:59:25 +01:00
|
|
|
|
# Ensure $NIXOS_CONFIG is not set.
|
|
|
|
|
$ENV{"NIXOS_CONFIG"} = "";
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
# Parse the command line.
|
|
|
|
|
|
|
|
|
|
sub showHelp {
|
|
|
|
|
print <<EOF;
|
|
|
|
|
Usage: nixos-container list
|
2019-09-19 18:55:21 +02:00
|
|
|
|
nixos-container create <container-name>
|
|
|
|
|
[--nixos-path <path>]
|
|
|
|
|
[--system-path <path>]
|
|
|
|
|
[--config <string>]
|
|
|
|
|
[--config-file <path>]
|
|
|
|
|
[--flake <flakeref>]
|
|
|
|
|
[--ensure-unique-name]
|
|
|
|
|
[--auto-start]
|
|
|
|
|
[--bridge <iface>]
|
|
|
|
|
[--port <port>]
|
|
|
|
|
[--host-address <string>]
|
|
|
|
|
[--local-address <string>]
|
2014-03-31 19:21:36 +02:00
|
|
|
|
nixos-container destroy <container-name>
|
2023-07-21 00:23:09 +02:00
|
|
|
|
nixos-container restart <container-name>
|
2014-03-31 19:21:36 +02:00
|
|
|
|
nixos-container start <container-name>
|
|
|
|
|
nixos-container stop <container-name>
|
2016-08-03 18:54:19 +02:00
|
|
|
|
nixos-container terminate <container-name>
|
2014-08-18 13:46:54 +02:00
|
|
|
|
nixos-container status <container-name>
|
2019-09-19 18:55:21 +02:00
|
|
|
|
nixos-container update <container-name>
|
|
|
|
|
[--config <string>]
|
|
|
|
|
[--config-file <path>]
|
|
|
|
|
[--flake <flakeref>]
|
2020-02-10 15:12:00 +01:00
|
|
|
|
[--nixos-path <path>]
|
2014-03-31 19:21:36 +02:00
|
|
|
|
nixos-container login <container-name>
|
2014-04-10 13:12:34 +02:00
|
|
|
|
nixos-container root-login <container-name>
|
|
|
|
|
nixos-container run <container-name> -- args...
|
2014-03-31 19:21:36 +02:00
|
|
|
|
nixos-container show-ip <container-name>
|
2014-08-19 16:57:02 +02:00
|
|
|
|
nixos-container show-host-key <container-name>
|
2014-03-31 19:21:36 +02:00
|
|
|
|
EOF
|
|
|
|
|
exit 0;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-19 15:47:51 +02:00
|
|
|
|
my $systemPath;
|
2016-05-31 18:29:54 +02:00
|
|
|
|
my $nixosPath;
|
2014-03-31 19:21:36 +02:00
|
|
|
|
my $ensureUniqueName = 0;
|
2014-08-18 15:56:27 +02:00
|
|
|
|
my $autoStart = 0;
|
2016-11-27 11:37:50 +01:00
|
|
|
|
my $bridge;
|
2016-12-02 22:21:03 +01:00
|
|
|
|
my $port;
|
2014-06-05 18:38:38 +02:00
|
|
|
|
my $extraConfig;
|
2016-07-20 20:43:34 +02:00
|
|
|
|
my $signal;
|
2016-06-24 16:02:26 +02:00
|
|
|
|
my $configFile;
|
2019-04-22 17:42:45 +02:00
|
|
|
|
my $hostAddress;
|
|
|
|
|
my $localAddress;
|
2019-09-19 18:55:21 +02:00
|
|
|
|
my $flake;
|
2019-09-20 18:37:17 +02:00
|
|
|
|
my $flakeAttr = "container";
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2020-06-30 13:42:48 +02:00
|
|
|
|
# Nix passthru flags.
|
|
|
|
|
my @nixFlags;
|
|
|
|
|
my @nixFlags2;
|
|
|
|
|
|
|
|
|
|
sub copyNixFlags0 { push @nixFlags, "--$_[0]"; }
|
|
|
|
|
sub copyNixFlags1 { push @nixFlags, "--$_[0]", $_[1]; }
|
|
|
|
|
|
|
|
|
|
# Ugly hack to handle flags that take two arguments, like --option.
|
|
|
|
|
sub copyNixFlags2 {
|
|
|
|
|
if (scalar(@nixFlags2) % 3 == 0) {
|
|
|
|
|
push @nixFlags2, "--$_[0]", $_[1];
|
|
|
|
|
} else {
|
|
|
|
|
push @nixFlags2, $_[1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
GetOptions(
|
|
|
|
|
"help" => sub { showHelp() },
|
|
|
|
|
"ensure-unique-name" => \$ensureUniqueName,
|
2014-08-18 15:56:27 +02:00
|
|
|
|
"auto-start" => \$autoStart,
|
2016-11-27 11:37:50 +01:00
|
|
|
|
"bridge=s" => \$bridge,
|
2016-12-02 22:21:03 +01:00
|
|
|
|
"port=s" => \$port,
|
2014-08-19 15:47:51 +02:00
|
|
|
|
"system-path=s" => \$systemPath,
|
2016-08-07 14:28:23 +02:00
|
|
|
|
"signal=s" => \$signal,
|
2016-05-31 18:29:54 +02:00
|
|
|
|
"nixos-path=s" => \$nixosPath,
|
2016-06-24 16:02:26 +02:00
|
|
|
|
"config=s" => \$extraConfig,
|
2019-04-22 17:42:45 +02:00
|
|
|
|
"config-file=s" => \$configFile,
|
|
|
|
|
"host-address=s" => \$hostAddress,
|
|
|
|
|
"local-address=s" => \$localAddress,
|
2019-09-19 18:55:21 +02:00
|
|
|
|
"flake=s" => \$flake,
|
2020-06-30 13:42:48 +02:00
|
|
|
|
# Nix passthru options.
|
|
|
|
|
"log-format=s" => \©NixFlags1,
|
|
|
|
|
"option=s{2}" => \©NixFlags2,
|
|
|
|
|
"impure" => \©NixFlags0,
|
|
|
|
|
"update-input=s" => \©NixFlags1,
|
|
|
|
|
"override-input=s{2}" => \©NixFlags2,
|
|
|
|
|
"commit-lock-file" => \©NixFlags0,
|
|
|
|
|
"no-registries" => \©NixFlags0,
|
|
|
|
|
"no-update-lock-file" => \©NixFlags0,
|
|
|
|
|
"no-write-lock-file" => \©NixFlags0,
|
|
|
|
|
"no-allow-dirty" => \©NixFlags0,
|
|
|
|
|
"recreate-lock-file" => \©NixFlags0,
|
2014-03-31 19:21:36 +02:00
|
|
|
|
) or exit 1;
|
|
|
|
|
|
2020-06-30 13:42:48 +02:00
|
|
|
|
push @nixFlags, @nixFlags2;
|
|
|
|
|
|
2019-04-22 17:42:45 +02:00
|
|
|
|
if (defined $hostAddress and !defined $localAddress or defined $localAddress and !defined $hostAddress) {
|
|
|
|
|
die "With --host-address set, --local-address is required as well!";
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
my $action = $ARGV[0] or die "$0: no action specified\n";
|
|
|
|
|
|
2016-06-24 16:02:26 +02:00
|
|
|
|
if (defined $configFile and defined $extraConfig) {
|
|
|
|
|
die "--config and --config-file are mutually incompatible. " .
|
2020-02-11 00:40:57 +01:00
|
|
|
|
"Please define one or the other, but not both";
|
2016-06-24 16:02:26 +02:00
|
|
|
|
}
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2019-09-20 18:37:17 +02:00
|
|
|
|
if (defined $flake && $flake =~ /^(.*)#([^#"]+)$/) {
|
|
|
|
|
$flake = $1;
|
|
|
|
|
$flakeAttr = $2;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
# Execute the selected action.
|
|
|
|
|
|
2020-05-08 15:40:00 +02:00
|
|
|
|
mkpath("$configurationDirectory", 0, 0755);
|
|
|
|
|
mkpath("$stateDirectory", 0, 0700);
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2020-05-08 16:23:11 +02:00
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
if ($action eq "list") {
|
2020-05-08 15:40:00 +02:00
|
|
|
|
foreach my $confFile (glob "$configurationDirectory/*.conf") {
|
2020-05-08 16:23:11 +02:00
|
|
|
|
# Filter libpod configuration files
|
|
|
|
|
# From 22.05 and onwards this is not an issue any more as directories dont clash
|
|
|
|
|
if($confFile eq "/etc/containers/libpod.conf" || $confFile eq "/etc/containers/containers.conf" || $confFile eq "/etc/containers/registries.conf") {
|
|
|
|
|
next
|
|
|
|
|
}
|
2014-03-31 19:21:36 +02:00
|
|
|
|
$confFile =~ /\/([^\/]+).conf$/ or next;
|
|
|
|
|
print "$1\n";
|
|
|
|
|
}
|
|
|
|
|
exit 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $containerName = $ARGV[1] or die "$0: no container name specified\n";
|
2017-03-22 15:10:54 +01:00
|
|
|
|
$containerName =~ /^[a-zA-Z0-9_-]+$/ or die "$0: invalid container name\n";
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2014-04-10 11:32:50 +02:00
|
|
|
|
sub writeNixOSConfig {
|
|
|
|
|
my ($nixosConfigFile) = @_;
|
|
|
|
|
|
2016-06-24 16:02:26 +02:00
|
|
|
|
my $localExtraConfig = "";
|
|
|
|
|
|
|
|
|
|
if ($extraConfig) {
|
|
|
|
|
$localExtraConfig = $extraConfig
|
|
|
|
|
} elsif ($configFile) {
|
|
|
|
|
my $resolvedFile = abs_path($configFile);
|
|
|
|
|
$localExtraConfig = "imports = [ $resolvedFile ];"
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 11:32:50 +02:00
|
|
|
|
my $nixosConfig = <<EOF;
|
2014-04-14 16:26:48 +02:00
|
|
|
|
{ config, lib, pkgs, ... }:
|
2014-04-10 11:32:50 +02:00
|
|
|
|
|
2014-04-14 16:26:48 +02:00
|
|
|
|
with lib;
|
2014-04-10 11:32:50 +02:00
|
|
|
|
|
|
|
|
|
{ boot.isContainer = true;
|
|
|
|
|
networking.hostName = mkDefault "$containerName";
|
|
|
|
|
networking.useDHCP = false;
|
2016-06-24 16:02:26 +02:00
|
|
|
|
$localExtraConfig
|
2014-04-10 11:32:50 +02:00
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
write_file($nixosConfigFile, $nixosConfig);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 18:55:21 +02:00
|
|
|
|
sub buildFlake {
|
2020-06-30 13:42:48 +02:00
|
|
|
|
system("nix", "build", "-o", "$systemPath.tmp", @nixFlags, "--",
|
2019-09-20 18:37:17 +02:00
|
|
|
|
"$flake#nixosConfigurations.\"$flakeAttr\".config.system.build.toplevel") == 0
|
2019-09-19 18:55:21 +02:00
|
|
|
|
or die "$0: failed to build container from flake '$flake'\n";
|
|
|
|
|
$systemPath = readlink("$systemPath.tmp") or die;
|
|
|
|
|
unlink("$systemPath.tmp");
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 02:21:24 +01:00
|
|
|
|
sub clearContainerState {
|
|
|
|
|
my ($profileDir, $gcRootsDir, $root, $configFile) = @_;
|
|
|
|
|
|
|
|
|
|
safeRemoveTree($profileDir) if -e $profileDir;
|
|
|
|
|
safeRemoveTree($gcRootsDir) if -e $gcRootsDir;
|
|
|
|
|
system("chattr", "-i", "$root/var/empty") if -e "$root/var/empty";
|
|
|
|
|
safeRemoveTree($root) if -e $root;
|
|
|
|
|
unlink($configFile) or die;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02: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: $!";
|
|
|
|
|
|
2020-05-08 15:40:00 +02:00
|
|
|
|
my $confFile = "$configurationDirectory/$containerName.conf";
|
|
|
|
|
my $root = "$stateDirectory/$containerName";
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
|
|
|
|
# Maybe generate a unique name.
|
|
|
|
|
if ($ensureUniqueName) {
|
|
|
|
|
my $base = $containerName;
|
|
|
|
|
for (my $nr = 0; ; $nr++) {
|
2020-05-08 15:40:00 +02:00
|
|
|
|
$confFile = "$configurationDirectory/$containerName.conf";
|
|
|
|
|
$root = "$stateDirectory/$containerName";
|
2014-03-31 19:21:36 +02:00
|
|
|
|
last unless -e $confFile || -e $root;
|
2015-11-18 18:47:25 +01:00
|
|
|
|
$containerName = "$base-$nr";
|
2014-03-31 19:21:36 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
die "$0: container ‘$containerName’ already exists\n" if -e $confFile;
|
|
|
|
|
|
2015-01-28 16:12:05 +01:00
|
|
|
|
# Due to interface name length restrictions, container names must
|
|
|
|
|
# be restricted too.
|
|
|
|
|
die "$0: container name ‘$containerName’ is too long\n" if length $containerName > 11;
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
# Get an unused IP address.
|
|
|
|
|
my %usedIPs;
|
2020-05-08 15:40:00 +02:00
|
|
|
|
foreach my $confFile2 (glob "$configurationDirectory/*.conf") {
|
2020-05-08 16:23:11 +02:00
|
|
|
|
# Filter libpod configuration files
|
|
|
|
|
# From 22.05 and onwards this is not an issue any more as directories dont clash
|
|
|
|
|
if($confFile2 eq "/etc/containers/libpod.conf" || $confFile2 eq "/etc/containers/containers.conf" || $confFile2 eq "/etc/containers/registries.conf") {
|
|
|
|
|
next
|
|
|
|
|
}
|
2014-03-31 19:21:36 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 17:42:45 +02:00
|
|
|
|
unless (defined $hostAddress) {
|
|
|
|
|
my $ipPrefix;
|
|
|
|
|
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;
|
|
|
|
|
}
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2019-04-22 17:42:45 +02:00
|
|
|
|
die "$0: out of IP addresses\n" unless defined $ipPrefix;
|
|
|
|
|
}
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
|
|
|
|
my @conf;
|
|
|
|
|
push @conf, "PRIVATE_NETWORK=1\n";
|
|
|
|
|
push @conf, "HOST_ADDRESS=$hostAddress\n";
|
|
|
|
|
push @conf, "LOCAL_ADDRESS=$localAddress\n";
|
2016-11-27 11:37:50 +01:00
|
|
|
|
push @conf, "HOST_BRIDGE=$bridge\n";
|
2016-12-02 22:21:03 +01:00
|
|
|
|
push @conf, "HOST_PORT=$port\n";
|
2014-08-18 15:56:27 +02:00
|
|
|
|
push @conf, "AUTO_START=$autoStart\n";
|
2019-09-19 18:55:21 +02:00
|
|
|
|
push @conf, "FLAKE=$flake\n" if defined $flake;
|
2014-03-31 19:21:36 +02:00
|
|
|
|
write_file($confFile, \@conf);
|
|
|
|
|
|
|
|
|
|
close($lock);
|
|
|
|
|
|
|
|
|
|
print STDERR "host IP is $hostAddress, container IP is $localAddress\n";
|
|
|
|
|
|
|
|
|
|
# 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);
|
|
|
|
|
|
2014-08-19 15:47:51 +02:00
|
|
|
|
# Build/set the initial configuration.
|
2019-09-19 18:55:21 +02:00
|
|
|
|
if (defined $flake) {
|
|
|
|
|
buildFlake();
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-19 15:47:51 +02:00
|
|
|
|
if (defined $systemPath) {
|
|
|
|
|
system("nix-env", "-p", "$profileDir/system", "--set", $systemPath) == 0
|
2020-02-10 02:21:24 +01:00
|
|
|
|
or do {
|
|
|
|
|
clearContainerState($profileDir, "$profileDir/$containerName", $root, $confFile);
|
|
|
|
|
die "$0: failed to set initial container configuration\n";
|
|
|
|
|
};
|
2014-08-19 15:47:51 +02:00
|
|
|
|
} else {
|
|
|
|
|
mkpath("$root/etc/nixos", 0, 0755);
|
|
|
|
|
|
2016-05-31 18:29:54 +02:00
|
|
|
|
my $nixenvF = $nixosPath // "<nixpkgs/nixos>";
|
2014-08-19 15:47:51 +02:00
|
|
|
|
my $nixosConfigFile = "$root/etc/nixos/configuration.nix";
|
|
|
|
|
writeNixOSConfig $nixosConfigFile;
|
|
|
|
|
|
|
|
|
|
system("nix-env", "-p", "$profileDir/system",
|
2016-05-31 18:29:54 +02:00
|
|
|
|
"-I", "nixos-config=$nixosConfigFile", "-f", "$nixenvF",
|
2020-06-30 13:42:48 +02:00
|
|
|
|
"--set", "-A", "system", @nixFlags) == 0
|
2020-02-10 02:21:24 +01:00
|
|
|
|
or do {
|
|
|
|
|
clearContainerState($profileDir, "$profileDir/$containerName", $root, $confFile);
|
|
|
|
|
die "$0: failed to build initial container configuration\n"
|
|
|
|
|
};
|
2014-08-19 15:47:51 +02:00
|
|
|
|
}
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
|
|
|
|
print "$containerName\n" if $ensureUniqueName;
|
|
|
|
|
exit 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-08 15:40:00 +02:00
|
|
|
|
my $root = "$stateDirectory/$containerName";
|
2014-04-10 11:32:50 +02:00
|
|
|
|
my $profileDir = "/nix/var/nix/profiles/per-container/$containerName";
|
2014-08-15 01:19:17 +02:00
|
|
|
|
my $gcRootsDir = "/nix/var/nix/gcroots/per-container/$containerName";
|
2020-05-08 15:40:00 +02:00
|
|
|
|
my $confFile = "$configurationDirectory/$containerName.conf";
|
2014-08-15 03:35:55 +02:00
|
|
|
|
if (!-e $confFile) {
|
2014-08-18 13:46:54 +02:00
|
|
|
|
if ($action eq "destroy") {
|
|
|
|
|
exit 0;
|
2014-08-19 16:57:02 +02:00
|
|
|
|
} elsif ($action eq "status") {
|
2014-08-18 13:46:54 +02:00
|
|
|
|
print "gone\n";
|
|
|
|
|
}
|
2014-08-15 03:35:55 +02:00
|
|
|
|
die "$0: container ‘$containerName’ does not exist\n" ;
|
|
|
|
|
}
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2017-12-11 04:05:15 +01:00
|
|
|
|
# Return the PID of the init process of the container.
|
|
|
|
|
sub getLeader {
|
|
|
|
|
my $s = `machinectl show "$containerName" -p Leader`;
|
|
|
|
|
chomp $s;
|
|
|
|
|
$s =~ /^Leader=(\d+)$/ or die "unable to get container's main PID\n";
|
|
|
|
|
return int($1);
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 11:32:50 +02:00
|
|
|
|
sub isContainerRunning {
|
|
|
|
|
my $status = `systemctl show 'container\@$containerName'`;
|
|
|
|
|
return $status =~ /ActiveState=active/;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-03 18:54:19 +02:00
|
|
|
|
sub terminateContainer {
|
2017-12-11 04:05:15 +01:00
|
|
|
|
my $leader = getLeader;
|
2016-08-03 18:54:19 +02:00
|
|
|
|
system("machinectl", "terminate", $containerName) == 0
|
|
|
|
|
or die "$0: failed to terminate container\n";
|
2017-12-11 04:05:15 +01:00
|
|
|
|
# Wait for the leader process to exit
|
|
|
|
|
# TODO: As for any use of PIDs for process control where the process is
|
|
|
|
|
# not a direct child of ours, this can go wrong when the pid gets
|
|
|
|
|
# recycled after a PID overflow.
|
|
|
|
|
# Relying entirely on some form of UUID provided by machinectl
|
|
|
|
|
# instead of PIDs would remove this risk.
|
|
|
|
|
# See https://github.com/NixOS/nixpkgs/pull/32992#discussion_r158586048
|
|
|
|
|
while ( kill 0, $leader ) { Time::HiRes::sleep(0.1) }
|
2016-07-20 20:43:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-20 05:07:07 +02:00
|
|
|
|
sub startContainer {
|
|
|
|
|
system("systemctl", "start", "container\@$containerName") == 0
|
|
|
|
|
or die "$0: failed to start container\n";
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
sub stopContainer {
|
|
|
|
|
system("systemctl", "stop", "container\@$containerName") == 0
|
|
|
|
|
or die "$0: failed to stop container\n";
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-20 05:07:07 +02:00
|
|
|
|
sub restartContainer {
|
|
|
|
|
stopContainer;
|
|
|
|
|
startContainer;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-21 16:45:47 +01:00
|
|
|
|
# Run a command in the container.
|
|
|
|
|
sub runInContainer {
|
|
|
|
|
my @args = @_;
|
2020-03-26 01:17:32 +01:00
|
|
|
|
my $leader = getLeader;
|
|
|
|
|
exec($nsenter, "-t", $leader, "-m", "-u", "-i", "-n", "-p", "--", @args);
|
|
|
|
|
die "cannot run ‘nsenter’: $!\n";
|
2017-03-21 16:45:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-11 18:03:45 +02:00
|
|
|
|
# Remove a directory while recursively unmounting all mounted filesystems within
|
|
|
|
|
# that directory and unmounting/removing that directory afterwards as well.
|
|
|
|
|
#
|
|
|
|
|
# NOTE: If the specified path is a mountpoint, its contents will be removed,
|
|
|
|
|
# only mountpoints underneath that path will be unmounted properly.
|
|
|
|
|
sub safeRemoveTree {
|
|
|
|
|
my ($path) = @_;
|
|
|
|
|
system("find", $path, "-mindepth", "1", "-xdev",
|
|
|
|
|
"(", "-type", "d", "-exec", "mountpoint", "-q", "{}", ";", ")",
|
|
|
|
|
"-exec", "umount", "-fR", "{}", "+");
|
|
|
|
|
system("rm", "--one-file-system", "-rf", $path);
|
|
|
|
|
if (-e $path) {
|
|
|
|
|
system("umount", "-fR", $path);
|
|
|
|
|
system("rm", "--one-file-system", "-rf", $path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
if ($action eq "destroy") {
|
2014-04-03 16:25:21 +02:00
|
|
|
|
die "$0: cannot destroy declarative container (remove it from your configuration.nix instead)\n"
|
|
|
|
|
unless POSIX::access($confFile, &POSIX::W_OK);
|
|
|
|
|
|
2016-08-03 18:54:19 +02:00
|
|
|
|
terminateContainer if (isContainerRunning);
|
2014-03-31 19:21:36 +02:00
|
|
|
|
|
2020-02-10 02:21:24 +01:00
|
|
|
|
clearContainerState($profileDir, $gcRootsDir, $root, $confFile);
|
2014-03-31 19:21:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-20 05:07:07 +02:00
|
|
|
|
elsif ($action eq "restart") {
|
|
|
|
|
restartContainer;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
elsif ($action eq "start") {
|
2018-04-20 05:07:07 +02:00
|
|
|
|
startContainer;
|
2014-03-31 19:21:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($action eq "stop") {
|
|
|
|
|
stopContainer;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-03 18:54:19 +02:00
|
|
|
|
elsif ($action eq "terminate") {
|
|
|
|
|
terminateContainer;
|
2016-07-20 20:43:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-08-18 13:46:54 +02:00
|
|
|
|
elsif ($action eq "status") {
|
|
|
|
|
print isContainerRunning() ? "up" : "down", "\n";
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 11:32:50 +02:00
|
|
|
|
elsif ($action eq "update") {
|
|
|
|
|
|
2019-09-19 18:55:21 +02:00
|
|
|
|
# Unless overriden on the command line, rebuild the flake recorded
|
|
|
|
|
# in the container config file. FIXME: read the container config
|
|
|
|
|
# in a more sensible way.
|
|
|
|
|
if (!defined $flake && !defined $configFile && !defined $extraConfig) {
|
|
|
|
|
my $s = read_file($confFile);
|
|
|
|
|
$s =~ /^FLAKE=(.*)$/m;
|
|
|
|
|
$flake = $1;
|
2016-06-24 16:02:26 +02:00
|
|
|
|
}
|
2014-04-10 11:32:50 +02:00
|
|
|
|
|
2019-09-19 18:55:21 +02:00
|
|
|
|
if (defined $flake) {
|
|
|
|
|
buildFlake();
|
|
|
|
|
system("nix-env", "-p", "$profileDir/system", "--set", $systemPath) == 0
|
|
|
|
|
or die "$0: failed to set container configuration\n";
|
|
|
|
|
} else {
|
2020-02-10 15:12:00 +01:00
|
|
|
|
|
2019-09-19 18:55:21 +02:00
|
|
|
|
my $nixosConfigFile = "$root/etc/nixos/configuration.nix";
|
|
|
|
|
|
|
|
|
|
# FIXME: may want to be more careful about clobbering the existing
|
|
|
|
|
# configuration.nix.
|
|
|
|
|
if ((defined $extraConfig && $extraConfig ne "") ||
|
|
|
|
|
(defined $configFile && $configFile ne "")) {
|
|
|
|
|
writeNixOSConfig $nixosConfigFile;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 15:12:00 +01:00
|
|
|
|
my $nixenvF = $nixosPath // "<nixpkgs/nixos>";
|
2019-09-19 18:55:21 +02:00
|
|
|
|
system("nix-env", "-p", "$profileDir/system",
|
2020-02-10 15:12:00 +01:00
|
|
|
|
"-I", "nixos-config=$nixosConfigFile", "-f", $nixenvF,
|
2020-06-30 13:42:48 +02:00
|
|
|
|
"--set", "-A", "system", @nixFlags) == 0
|
2019-09-19 18:55:21 +02:00
|
|
|
|
or die "$0: failed to build container configuration\n";
|
|
|
|
|
}
|
2014-04-10 11:32:50 +02:00
|
|
|
|
|
|
|
|
|
if (isContainerRunning) {
|
|
|
|
|
print STDERR "reloading container...\n";
|
|
|
|
|
system("systemctl", "reload", "container\@$containerName") == 0
|
|
|
|
|
or die "$0: failed to reload container\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
elsif ($action eq "login") {
|
2014-04-18 20:47:31 +02:00
|
|
|
|
exec("machinectl", "login", "--", $containerName);
|
2014-03-31 19:21:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 13:12:34 +02:00
|
|
|
|
elsif ($action eq "root-login") {
|
2017-03-21 16:45:47 +01:00
|
|
|
|
runInContainer("@su@", "root", "-l");
|
2014-04-10 13:12:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($action eq "run") {
|
|
|
|
|
shift @ARGV; shift @ARGV;
|
2014-08-27 21:12:18 +02:00
|
|
|
|
# Escape command.
|
|
|
|
|
my $s = join(' ', map { s/'/'\\''/g; "'$_'" } @ARGV);
|
2017-03-21 16:45:47 +01:00
|
|
|
|
runInContainer("@su@", "root", "-l", "-c", "exec " . $s);
|
2014-03-31 19:21:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($action eq "show-ip") {
|
|
|
|
|
my $s = read_file($confFile) or die;
|
2021-02-17 14:17:28 +01:00
|
|
|
|
$s =~ /^LOCAL_ADDRESS=([0-9\.]+)(\/[0-9]+)?$/m
|
|
|
|
|
or $s =~ /^LOCAL_ADDRESS6=([0-9a-f:]+)(\/[0-9]+)?$/m
|
|
|
|
|
or die "$0: cannot get IP address\n";
|
2014-03-31 19:21:36 +02:00
|
|
|
|
print "$1\n";
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-19 16:57:02 +02:00
|
|
|
|
elsif ($action eq "show-host-key") {
|
2015-09-08 15:57:04 +02:00
|
|
|
|
my $fn = "$root/etc/ssh/ssh_host_ed25519_key.pub";
|
|
|
|
|
$fn = "$root/etc/ssh/ssh_host_ecdsa_key.pub" unless -e $fn;
|
2014-08-19 16:57:02 +02:00
|
|
|
|
exit 1 if ! -f $fn;
|
|
|
|
|
print read_file($fn);
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-31 19:21:36 +02:00
|
|
|
|
else {
|
|
|
|
|
die "$0: unknown action ‘$action’\n";
|
|
|
|
|
}
|