X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FLXC%2FSetup.pm;h=aa8bc45dd5d1969029d46e6132b68fdb0e8fc895;hb=153747ff1df83c0d4fb7fe3dc2fd21e85ff7c5db;hp=8958ff02d3ee9995cecdde30ed0e4b470a37cccd;hpb=feb7383bf109a1fe50c21a2231f89133e1662b05;p=pve-container.git diff --git a/src/PVE/LXC/Setup.pm b/src/PVE/LXC/Setup.pm index 8958ff0..aa8bc45 100644 --- a/src/PVE/LXC/Setup.pm +++ b/src/PVE/LXC/Setup.pm @@ -7,18 +7,33 @@ use PVE::Tools; use PVE::LXC::Setup::Debian; use PVE::LXC::Setup::Ubuntu; -use PVE::LXC::Setup::Redhat; +use PVE::LXC::Setup::CentOS; +use PVE::LXC::Setup::Fedora; +use PVE::LXC::Setup::SUSE; use PVE::LXC::Setup::ArchLinux; +use PVE::LXC::Setup::Alpine; +use PVE::LXC::Setup::Gentoo; my $plugins = { debian => 'PVE::LXC::Setup::Debian', ubuntu => 'PVE::LXC::Setup::Ubuntu', - redhat => 'PVE::LXC::Setup::Redhat', + centos => 'PVE::LXC::Setup::CentOS', + fedora => 'PVE::LXC::Setup::Fedora', + opensuse => 'PVE::LXC::Setup::SUSE', archlinux => 'PVE::LXC::Setup::ArchLinux', + arch => 'PVE::LXC::Setup::ArchLinux', + alpine => 'PVE::LXC::Setup::Alpine', + gentoo => 'PVE::LXC::Setup::Gentoo', }; my $autodetect_type = sub { - my ($rootdir) = @_; + my ($self, $rootdir, $os_release) = @_; + + if (my $id = $os_release->{ID}) { + return $id if $id =~ /^(?:alpine|arch|centos|debian|fedora|gentoo|opensuse|ubuntu)$/; + } + + # fallback compatibility checks my $lsb_fn = "$rootdir/etc/lsb-release"; if (-f $lsb_fn) { @@ -26,14 +41,24 @@ my $autodetect_type = sub { if ($data =~ m/^DISTRIB_ID=Ubuntu$/im) { return 'ubuntu'; } - } elsif (-f "$rootdir/etc/debian_version") { + } + + if (-f "$rootdir/etc/debian_version") { return "debian"; - } elsif (-f "$rootdir/etc/redhat-release") { - return "redhat"; + } elsif (-f "$rootdir/etc/SuSE-brand" || -f "$rootdir/etc/SuSE-release") { + return "opensuse"; + } elsif (-f "$rootdir/etc/fedora-release") { + return "fedora"; + } elsif (-f "$rootdir/etc/centos-release" || -f "$rootdir/etc/redhat-release") { + return "centos"; } elsif (-f "$rootdir/etc/arch-release") { return "archlinux"; + } elsif (-f "$rootdir/etc/alpine-release") { + return "alpine"; + } elsif (-f "$rootdir/etc/gentoo-release") { + return "gentoo"; } - die "unable to detect OS disribution\n"; + die "unable to detect OS distribution\n"; }; sub new { @@ -43,24 +68,41 @@ sub new { my $self = bless { conf => $conf, rootdir => $rootdir}; - if (!defined($type)) { + my $os_release = $self->get_ct_os_release(); + + if ($conf->{ostype} && $conf->{ostype} eq 'unmanaged') { + return $self; + } elsif (!defined($type)) { # try to autodetect type - $type = &$autodetect_type($rootdir); + $type = &$autodetect_type($self, $rootdir, $os_release); + my $expected_type = $conf->{ostype} || $type; + + die "got unexpected ostype ($type != $expected_type)\n" + if $type ne $expected_type; } - + my $plugin_class = $plugins->{$type} || "no such OS type '$type'\n"; - my $plugin = $plugin_class->new($conf, $rootdir); + my $plugin = $plugin_class->new($conf, $rootdir, $os_release); $self->{plugin} = $plugin; $self->{in_chroot} = 0; # Cache some host files we need access to: $plugin->{host_resolv_conf} = PVE::INotify::read_file('resolvconf'); + + # pass on user namespace information: + my ($id_map, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); + if (@$id_map) { + $plugin->{id_map} = $id_map; + $plugin->{rootuid} = $rootuid; + $plugin->{rootgid} = $rootgid; + } return $self; } +# Forks into a chroot and executes $sub sub protected_call { my ($self, $sub) = @_; @@ -72,17 +114,23 @@ sub protected_call { die "failed to create temporary /dev directory: $!\n"; } + pipe(my $res_in, my $res_out) or die "pipe failed: $!\n"; + my $child = fork(); die "fork failed: $!\n" if !defined($child); if (!$child) { + close($res_in); # avoid recursive forks $self->{in_chroot} = 1; - $self->{plugin}->{in_chroot} = 1; eval { chroot($rootdir) or die "failed to change root to: $rootdir: $!\n"; chdir('/') or die "failed to change to root directory\n"; - $sub->(); + my $res = $sub->(); + if (defined($res)) { + print {$res_out} "$res"; + $res_out->flush(); + } }; if (my $err = $@) { warn $err; @@ -90,13 +138,21 @@ sub protected_call { } POSIX::_exit(0); } + close($res_out); + my $result = do { local $/ = undef; <$res_in> }; while (waitpid($child, 0) != $child) {} - die "setup error" if $? != 0; + if ($? != 0) { + my $method = (caller(1))[3]; + die "error in setup task $method\n"; + } + return $result; } sub template_fixup { my ($self) = @_; + return if !$self->{plugin}; # unmanaged + my $code = sub { $self->{plugin}->template_fixup($self->{conf}); }; @@ -106,6 +162,8 @@ sub template_fixup { sub setup_network { my ($self) = @_; + return if !$self->{plugin}; # unmanaged + my $code = sub { $self->{plugin}->setup_network($self->{conf}); }; @@ -115,6 +173,8 @@ sub setup_network { sub set_hostname { my ($self) = @_; + return if !$self->{plugin}; # unmanaged + my $code = sub { $self->{plugin}->set_hostname($self->{conf}); }; @@ -124,6 +184,8 @@ sub set_hostname { sub set_dns { my ($self) = @_; + return if !$self->{plugin}; # unmanaged + my $code = sub { $self->{plugin}->set_dns($self->{conf}); }; @@ -133,6 +195,8 @@ sub set_dns { sub setup_init { my ($self) = @_; + return if !$self->{plugin}; # unmanaged + my $code = sub { $self->{plugin}->setup_init($self->{conf}); }; @@ -141,7 +205,9 @@ sub setup_init { sub set_user_password { my ($self, $user, $pw) = @_; - + + return if !$self->{plugin}; # unmanaged + my $code = sub { $self->{plugin}->set_user_password($self->{conf}, $user, $pw); }; @@ -151,6 +217,8 @@ sub set_user_password { sub rewrite_ssh_host_keys { my ($self) = @_; + return if !$self->{plugin}; # unmanaged + my $conf = $self->{conf}; my $plugin = $self->{plugin}; my $rootdir = $self->{rootdir}; @@ -158,7 +226,6 @@ sub rewrite_ssh_host_keys { return if ! -d "$rootdir/etc/ssh"; my $keynames = { - rsa1 => 'ssh_host_key', rsa => 'ssh_host_rsa_key', dsa => 'ssh_host_dsa_key', ecdsa => 'ssh_host_ecdsa_key', @@ -167,19 +234,27 @@ sub rewrite_ssh_host_keys { my $hostname = $conf->{hostname} || 'localhost'; $hostname =~ s/\..*$//; + my $ssh_comment = "root\@$hostname"; - # Create temporary keys in /tmp on the host + my $keygen_outfunc = sub { + my $line = shift; + print "done: $line\n" + if $line =~ m/^(?:[0-9a-f]{2}:)+[0-9a-f]{2}\s+\Q$ssh_comment\E$/i || + $line =~ m/^SHA256:[0-9a-z+\/]{43}\s+\Q$ssh_comment\E$/i; + }; + + # Create temporary keys in /tmp on the host my $keyfiles = {}; foreach my $keytype (keys %$keynames) { my $basename = $keynames->{$keytype}; my $file = "/tmp/$$.$basename"; print "Creating SSH host key '$basename' - this may take some time ...\n"; - my $cmd = ['ssh-keygen', '-q', '-f', $file, '-t', $keytype, - '-N', '', '-C', "root\@$hostname"]; - PVE::Tools::run_command($cmd); - $keyfiles->{"/etc/ssh/$basename"} = PVE::Tools::file_get_contents($file); - $keyfiles->{"/etc/ssh/$basename.pub"} = PVE::Tools::file_get_contents("$file.pub"); + my $cmd = ['ssh-keygen', '-f', $file, '-t', $keytype, + '-N', '', '-E', 'sha256', '-C', $ssh_comment]; + PVE::Tools::run_command($cmd, outfunc => $keygen_outfunc); + $keyfiles->{"/etc/ssh/$basename"} = [PVE::Tools::file_get_contents($file), 0600]; + $keyfiles->{"/etc/ssh/$basename.pub"} = [PVE::Tools::file_get_contents("$file.pub"), 0644]; unlink $file; unlink "$file.pub"; } @@ -188,7 +263,7 @@ sub rewrite_ssh_host_keys { my $code = sub { foreach my $file (keys %$keyfiles) { - $plugin->ct_file_set_contents($file, $keyfiles->{$file}); + $plugin->ct_file_set_contents($file, @{$keyfiles->{$file}}); } }; $self->protected_call($code); @@ -197,6 +272,8 @@ sub rewrite_ssh_host_keys { sub pre_start_hook { my ($self) = @_; + return if !$self->{plugin}; # unmanaged + my $code = sub { # Create /fastboot to skip run fsck $self->{plugin}->ct_file_set_contents('/fastboot', ''); @@ -207,13 +284,64 @@ sub pre_start_hook { } sub post_create_hook { - my ($self, $root_password) = @_; + my ($self, $root_password, $ssh_keys) = @_; + + return if !$self->{plugin}; # unmanaged my $code = sub { - $self->{plugin}->post_create_hook($self->{conf}, $root_password); + $self->{plugin}->post_create_hook($self->{conf}, $root_password, $ssh_keys); }; $self->protected_call($code); $self->rewrite_ssh_host_keys(); } +# os-release(5): +# (...) a newline-separated list of environment-like shell-compatible +# variable assignments. (...) beyond mere variable assignments, no shell +# features are supported (this means variable expansion is explicitly not +# supported) (...). Variable assignment values must be enclosed in double or +# single quotes *if* they include spaces, semicolons or other special +# characters outside of A-Z, a-z, 0-9. Shell special characters ("$", quotes, +# backslash, backtick) must be escaped with backslashes (...). All strings +# should be in UTF-8 format, and non-printable characters should not be used. +# It is not supported to concatenate multiple individually quoted strings. +# Lines beginning with "#" shall be ignored as comments. +my $parse_os_release = sub { + my ($data) = @_; + my $variables = {}; + while (defined($data) && $data =~ /^(.+)$/gm) { + next if $1 !~ /^\s*([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$/; + my ($var, $content) = ($1, $2); + chomp $content; + + if ($content =~ /^'([^']*)'/) { + $variables->{$var} = $1; + } elsif ($content =~ /^"((?:[^"\\]|\\.)*)"/) { + my $s = $1; + $s =~ s/(\\["'`nt\$\\])/"\"$1\""/eeg; + $variables->{$var} = $s; + } elsif ($content =~ /^([A-Za-z0-9]*)/) { + $variables->{$var} = $1; + } + } + return $variables; +}; + +sub get_ct_os_release { + my ($self) = @_; + + my $code = sub { + if (-f '/etc/os-release') { + return PVE::Tools::file_get_contents('/etc/os-release'); + } elsif (-f '/usr/lib/os-release') { + return PVE::Tools::file_get_contents('/usr/lib/os-release'); + } + return undef; + }; + + my $data = $self->protected_call($code); + + return &$parse_os_release($data); +} + 1;