]> git.proxmox.com Git - aab.git/blobdiff - PVE/AAB.pm
mask systemd-journald-audit.socket
[aab.git] / PVE / AAB.pm
index 6af92ee4cf57c25d575dd61179015a363e0afb6f..14a65da98d9f46c942228638b37b39aaecdd39a7 100644 (file)
@@ -7,20 +7,23 @@ use File::Path;
 use File::Copy;
 use IO::File;
 use IO::Select;
+use IPC::Open2;
 use IPC::Open3;
 use UUID;
 use Cwd;
-
-my @BASE_PACKAGES = qw(base openssh);
-my @BASE_EXCLUDES = qw(e2fsprogs
-                       jfsutils
-                       linux
-                       lvm2
-                       mdadm
-                       netctl
-                       pcmciautils
-                       reiserfsprogs
-                       xfsprogs);
+my @BASE_PACKAGES = qw(base openssh vi nano);
+my @BASE_EXCLUDES = qw(
+    e2fsprogs
+    jfsutils
+    linux
+    linux-firmware
+    lvm2
+    mdadm
+    netctl
+    pcmciautils
+    reiserfsprogs
+    xfsprogs
+);
 
 my $PKGDIR = "/var/cache/pacman/pkg";
 
@@ -47,6 +50,17 @@ sub write_file {
     $fh->close;
 }
 
+sub read_file {
+    my ($filename) = @_;
+
+    my $fh = IO::File->new ("<$filename") or die "failed to read $filename - $!\n";
+    my $rec = '';
+    while (defined (my $line = <$fh>)) {
+       $rec .= $line;
+    };
+    return $rec;
+}
+
 sub copy_file {
     my ($a, $b) = @_;
     copy($a, $b) or die "failed to copy $a => $b: $!";
@@ -100,7 +114,7 @@ sub read_config {
            my $long = $2;
            $long =~ s/^\s+/ /;
            $res->{description} = $long;
-           chomp $res->{description};      
+           chomp $res->{description};
        } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) {
            my ($key, $value) = (lc ($1), $2);
            if ($key eq 'source' || $key eq 'mirror') {
@@ -116,6 +130,8 @@ sub read_config {
 
     die "unable to parse config file" if $rec;
 
+    $res->{architecture} = 'amd64' if $res->{architecture} eq 'x86_64';
+
     return $res;
 }
 
@@ -130,7 +146,7 @@ sub new {
     die "no 'maintainer' specified\n" if !$config->{maintainer};
 
     my $name = $config->{name} || die "no 'name' specified\n";
-    $name =~ m/^[a-z][0-9a-z\-\*\.]+$/ || 
+    $name =~ m/^[a-z][0-9a-z\-\*\.]+$/ ||
        die "illegal characters in name '$name'\n";
 
     my $targetname;
@@ -165,8 +181,8 @@ sub __sample_config {
     return <<"CFG";
 lxc.arch = $arch
 lxc.include = /usr/share/lxc/config/archlinux.common.conf
-lxc.utsname = localhost
-lxc.rootfs = $self->{rootfs}
+lxc.uts.name = localhost
+lxc.rootfs.path = $self->{rootfs}
 lxc.mount.entry = $self->{pkgcache} $self->{pkgdir} none bind 0 0
 CFG
 }
@@ -226,21 +242,36 @@ sub initialize {
        die "no sources/mirrors specified";
     }
 
+    $self->write_pacman_conf();
+
+    $self->logmsg("configured VE $self->{veid}\n");
+}
+
+sub write_pacman_conf {
+    my ($self, $config_fn, $siglevel) = @_;
+
+    my $config = $self->{config};
+
     $config->{source} //= [];
     $config->{mirror} //= [];
 
-    my $servers = "Server = "
-                . join("\nServer = ", @{$config->{source}}, @{$config->{mirror}})
-                . "\n";
+    $siglevel ||= "Never";
+    $config_fn ||= $self->{'pacman.conf'};
+
+    my $servers = "Server = ".join("\nServer = ", @{$config->{source}}, @{$config->{mirror}}) ."\n";
+
+    my $fh = IO::File->new($config_fn, O_WRONLY | O_CREAT | O_EXCL)
+        or die "unable to write pacman config file $self->{'pacman.conf'} - $!";
+
+    my $arch = $config->{architecture};
+    $arch = 'x86_64' if $arch eq 'amd64';
 
-    $fh = IO::File->new($self->{'pacman.conf'}, O_WRONLY|O_CREAT|O_EXCL) ||
-       die "unable to write pacman config file $self->{'pacman.conf'} - $!";
     print $fh <<"EOF";
 [options]
 HoldPkg = pacman glibc
-Architecture = $config->{architecture}
+Architecture = $arch
 CheckSpace
-SigLevel = Never
+SigLevel = $siglevel
 
 [core]
 $servers
@@ -250,13 +281,9 @@ $servers
 $servers
 EOF
 
-    if ($config->{architecture} eq 'x86_64') {
-       print $fh "[multilib]\n$servers\n";
-    }
+    print $fh "[multilib]\n$servers\n" if $config->{architecture} eq 'x86_64';
 
-    mkdir $self->{rootfs} || die "unable to create rootfs - $!";
-
-    $self->logmsg("configured VE $self->{veid}\n");
+    close($fh);
 }
 
 sub ve_status {
@@ -283,7 +310,7 @@ sub ve_status {
        }
     }
     close($fh);
-    
+
     return $res;
 }
 
@@ -306,28 +333,30 @@ sub ve_init {
 
 
     my $veid = $self->{veid};
+    my $conffile = $self->{veconffile};
 
     $self->logmsg ("initialize VE $veid\n");
 
     my $vestat = $self->ve_status();
     if ($vestat->{running}) {
-       $self->run_command ("lxc-stop -n $veid --kill");
-    } 
+       $self->run_command ("lxc-stop -n $veid --rcfile $conffile --kill");
+    }
 
     rmtree $self->{rootfs};
-    mkpath $self->{rootfs};
+    mkpath "$self->{rootfs}/dev";
 }
 
 sub ve_command {
     my ($self, $cmd, $input) = @_;
 
     my $veid = $self->{veid};
+    my $conffile = $self->{veconffile};
 
     if (ref ($cmd) eq 'ARRAY') {
-       unshift @$cmd, 'lxc-attach', '-n', $veid, '--clear-env', '--';
+       unshift @$cmd, 'lxc-attach', '-n', $veid, '--rcfile', $conffile,'--clear-env', '--';
        $self->run_command ($cmd, $input);
     } else {
-       $self->run_command ("lxc-attach -n $veid --clear-env -- $cmd", $input);
+       $self->run_command ("lxc-attach -n $veid --rcfile $conffile --clear-env -- $cmd", $input);
     }
 }
 
@@ -335,9 +364,10 @@ sub ve_exec {
     my ($self, @cmd) = @_;
 
     my $veid = $self->{veid};
+    my $conffile = $self->{veconffile};
 
     my $reader;
-    my $pid = open2($reader, "<&STDIN", 'lxc-attach', '-n', $veid,  '--', @cmd)
+    my $pid = open2($reader, "<&STDIN", 'lxc-attach', '-n', $veid, '--rcfile', $conffile, '--', @cmd)
        or die "unable to exec command";
 
     while (defined (my $line = <$reader>)) {
@@ -351,7 +381,7 @@ sub ve_exec {
 }
 
 sub run_command {
-    my ($self, $cmd, $input, $getoutput) = @_;
+    my ($self, $cmd, $input, $getoutput, $noerr) = @_;
 
     my $reader = IO::File->new();
     my $writer = IO::File->new();
@@ -375,8 +405,8 @@ sub run_command {
     # catch exec errors
     if ($orig_pid != $$) {
        $self->logmsg ("ERROR: command '$cmdstr' failed - fork failed\n");
-       POSIX::_exit (1); 
-       kill ('KILL', $$); 
+       POSIX::_exit (1);
+       kill ('KILL', $$);
     }
 
     die $err if $err;
@@ -412,9 +442,9 @@ sub run_command {
     waitpid ($pid, 0);
     my $ec = ($? >> 8);
 
-    die "command '$cmdstr' failed with exit code $ec\n" if $ec;
+    die "command '$cmdstr' failed with exit code $ec\n" if $ec && !$noerr;
 
-    return $res;
+    return wantarray ? ($res, $ec) : $res;
 }
 
 sub start_container {
@@ -426,25 +456,37 @@ sub start_container {
 sub stop_container {
     my ($self) = @_;
     my $veid = $self->{veid};
-    $self->run_command ("lxc-stop -n $veid --kill");
+    my $conffile = $self->{veconffile};
+    $self->run_command ("lxc-stop -n $veid --rcfile $conffile --kill");
 }
 
 sub pacman_command {
-    my ($self) = @_;
+    my ($self, $config_fn) = @_;
     my $root = $self->{rootfs};
-    return ('/usr/bin/pacman',
-            '--root', $root,
-            '--config', $self->{'pacman.conf'},
-            '--cachedir', $self->{pkgcache},
-            '--noconfirm');
+    return (
+        '/usr/bin/pacman',
+        '--root', $root,
+        '--config', $config_fn || $self->{'pacman.conf'},
+        '--cachedir', $self->{pkgcache},
+        '--noconfirm',
+    );
 }
 
 sub cache_packages {
     my ($self, $packages) = @_;
     my $root = $self->{rootfs};
 
-    my @pacman = $self->pacman_command();
-    $self->run_command([@pacman, '-Sw', '--', @$packages]);
+    $self->write_pacman_conf('pacman.caching.conf', "Optional");
+    my @pacman = $self->pacman_command('pacman.caching.conf');
+    my ($_res, $ec) = $self->run_command([@pacman, '-Sw', '--', @$packages], undef, undef, 1);
+    $self->logmsg("ignore bad exit $ec due to unavailable keyring, the CT will verify that later.\n")
+       if $ec;
+}
+
+sub mask_systemd_unit {
+    my ($self, $unit) = @_;
+    my $root = $self->{rootfs};
+    symln '/dev/null', "$root/etc/systemd/system/$unit";
 }
 
 sub bootstrap {
@@ -457,7 +499,7 @@ sub bootstrap {
     mkpath $self->{pkgcache};
     mkpath $self->{pkgdir};
     mkpath "$root/var/lib/pacman";
-    $self->run_command([@pacman, '-Sy']);
+    $self->run_command([@pacman, '-Syy']);
 
     print "Figuring out what to install...\n";
     my $incl = { map { $_ => 1 } @{$self->{incl}} };
@@ -508,6 +550,9 @@ sub bootstrap {
     $self->cache_packages($packages);
     #$self->copy_packages();
 
+    print "Creating device nodes for package manager...\n";
+    $self->create_dev();
+
     print "Installing package manager and essentials...\n";
     # inetutils for 'hostname' for our init
     $self->run_command([@pacman, '-S', 'pacman', 'inetutils', 'archlinux-keyring']);
@@ -521,16 +566,64 @@ sub bootstrap {
     }
 
     print "Populating keyring...\n";
-    $self->run_command(['mount', '-t', 'devtmpfs', '-o', 'mode=0755,nosuid', 'udev', "$root/dev"]);
-    $self->run_command(['unshare', '--fork', '--pid', 'chroot', "$root", 'pacman-key', '--init']);
-    $self->run_command(['unshare', '--fork', '--pid', 'chroot', "$root", 'pacman-key', '--populate']);
-    $self->run_command(['umount', "$root/dev"]);
+    $self->populate_keyring();
+
+    print "Removing device nodes...\n";
+    $self->cleanup_dev();
 
     print "Starting container...\n";
     $self->start_container();
 
     print "Installing packages...\n";
     $self->ve_command(['pacman', '-S', '--needed', '--noconfirm', '--', @$packages]);
+
+    print "Masking problematic systemd units...\n";
+    for  my $unit (qw(sys-kernel-config.mount sys-kernel-debug.mount systemd-journald-audit.socket)) {
+       $self->mask_systemd_unit($unit);
+    }
+}
+
+# devices needed for gnupg to function:
+my $devs = {
+    '/dev/null'    => ['c', '1', '3'],
+    '/dev/random'  => ['c', '1', '9'], # fake /dev/random (really urandom)
+    '/dev/urandom' => ['c', '1', '9'],
+    '/dev/tty'     => ['c', '5', '0'],
+};
+
+sub cleanup_dev {
+    my ($self) = @_;
+    my $root = $self->{rootfs};
+
+    # remove temporary device files
+    unlink "${root}$_" foreach keys %$devs;
+}
+
+sub create_dev {
+    my ($self) = @_;
+    my $root = $self->{rootfs};
+
+    local $SIG{INT} = $SIG{TERM} = sub { $self->cleanup_dev; };
+
+    # we want to replace /dev/random, so delete devices first
+    $self->cleanup_dev();
+
+    foreach my $dev (keys %$devs) {
+       my ($type, $major, $minor) = @{$devs->{$dev}};
+       system('mknod', "${root}${dev}", $type, $major, $minor);
+    }
+}
+
+sub populate_keyring {
+    my ($self) = @_;
+    my $root = $self->{rootfs};
+
+    # generate weak master key and populate the keyring
+    system('unshare', '--fork', '--pid', 'chroot', "$root", 'pacman-key', '--init') == 0
+       or die "failed to initialize keyring: $?";
+    system('unshare', '--fork', '--pid', 'chroot', "$root", 'pacman-key', '--populate') == 0
+       or die "failed to populate keyring: $?";
+
 }
 
 sub install {
@@ -554,6 +647,7 @@ sub write_config {
     $data .= "Section: $config->{section}\n";
     $data .= "Maintainer: $config->{maintainer}\n";
     $data .= "Architecture: $config->{architecture}\n";
+    $data .= "Infopage: https://www.archlinux.org\n";
     $data .= "Installed-Size: $size\n";
 
     # optional
@@ -569,7 +663,19 @@ sub write_config {
 }
 
 sub finalize {
-    my ($self) = @_;
+    my ($self, $compressor) = @_;
+
+    my $use_zstd = 1;
+    if (defined($compressor)) {
+       if ($compressor =~ /^\s*--zstd?\s*$/) {
+           $use_zstd = 1;
+       } elsif ($compressor =~ /^\s*--(?:gz|gzip)\s*$/) {
+           $use_zstd = 0; # just boolean for now..
+       } else {
+           die "finalize: unknown compressor '$compressor'!\n";
+       }
+    }
+
     my $rootdir = $self->{rootfs};
 
     print "Stopping container...\n";
@@ -580,6 +686,14 @@ sub finalize {
     unlink $file;
     rename_file($file.'.aab_orig', $file);
 
+    # experienced user can change it anytime and others do well to start out with an updatable system..
+    my $mirrors = eval { read_file($file) } // '';
+    $mirrors = "\nServer = https://geo.mirror.pkgbuild.com/\$repo/os/\$arch\n\n" . $mirrors;
+    write_file($mirrors, $file, 0644);
+
+    print "Removing weak temporary pacman keyring...\n";
+    rmtree("$rootdir/etc/pacman.d/gnupg");
+
     my $sizestr = $self->run_command("du -sm $rootdir", undef, 1);
     my $size;
     if ($sizestr =~ m/^(\d+)\s+\Q$rootdir\E$/) {
@@ -587,24 +701,36 @@ sub finalize {
     } else {
        die "unable to detect size\n";
     }
-    $self->logmsg ("$size MB\n");
+    $self->logmsg ("uncompressed size: $size MB\n");
 
     $self->write_config ("$rootdir/etc/appliance.info", $size);
 
     $self->logmsg ("creating final appliance archive\n");
 
+    my $compressor_ext = $use_zstd ? 'zst' : 'gz';
+
     my $target = "$self->{targetname}.tar";
     unlink $target;
-    unlink "$target.gz";
+    unlink "$target.$compressor_ext";
 
     $self->run_command ("tar cpf $target --numeric-owner -C '$rootdir' ./etc/appliance.info");
     $self->run_command ("tar rpf $target --numeric-owner -C '$rootdir' --exclude ./etc/appliance.info .");
-    $self->run_command ("gzip $target");
+
+    $self->logmsg ("compressing archive ($compressor_ext)\n");
+    if ($use_zstd) {
+       $self->run_command ("zstd -19 --rm $target");
+    } else {
+       $self->run_command ("gzip -9 $target");
+    }
+
+    my $target_size = int(-s "$target.$compressor_ext") >> 20;
+    $self->logmsg ("created '$target.$compressor_ext' with size: $target_size MB\n");
 }
 
 sub enter {
     my ($self) = @_;
     my $veid = $self->{veid};
+    my $conffile = $self->{veconffile};
 
     my $vestat = $self->ve_status();
     if (!$vestat->{exist}) {
@@ -616,16 +742,18 @@ sub enter {
        $self->start_container();
     }
 
-    system ("lxc-attach -n $veid --clear-env");
+    system ("lxc-attach -n $veid --rcfile $conffile --clear-env");
 }
 
 sub clean {
     my ($self, $all) = @_;
 
     unlink $self->{logfile};
-    unlink $self->{'pacman.conf'};
+    unlink $self->{'pacman.conf'}, 'pacman.caching.conf';
     $self->ve_destroy();
     unlink '.veid';
+    unlink $self->{veconffile};
+
     rmtree $self->{pkgcache} if $all;
 }