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";
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') {
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;
die "no sources/mirrors specified";
}
+ $self->write_pacman_conf();
+
+ $self->logmsg("configured VE $self->{veid}\n");
+}
+
+sub write_pacman_conf {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
$config->{source} //= [];
$config->{mirror} //= [];
- my $servers = "Server = "
- . join("\nServer = ", @{$config->{source}}, @{$config->{mirror}})
- . "\n";
+ my $servers = "Server = ".join("\nServer = ", @{$config->{source}}, @{$config->{mirror}}) ."\n";
+
+ my $fh = IO::File->new($self->{'pacman.conf'}, O_WRONLY | O_CREAT | O_EXCL)
+ or die "unable to write pacman config file $self->{'pacman.conf'} - $!";
- $fh = IO::File->new($self->{'pacman.conf'}, O_WRONLY|O_CREAT|O_EXCL) ||
- die "unable to write pacman config file $self->{'pacman.conf'} - $!";
my $arch = $config->{architecture};
$arch = 'x86_64' if $arch eq 'amd64';
+
print $fh <<"EOF";
[options]
HoldPkg = pacman glibc
$servers
EOF
- if ($config->{architecture} eq 'x86_64') {
- print $fh "[multilib]\n$servers\n";
- }
-
- mkdir $self->{rootfs} || die "unable to create rootfs - $!";
+ print $fh "[multilib]\n$servers\n" if $config->{architecture} eq 'x86_64';
- $self->logmsg("configured VE $self->{veid}\n");
+ close($fh);
}
sub ve_status {
}
}
close($fh);
-
+
return $res;
}
my $vestat = $self->ve_status();
if ($vestat->{running}) {
$self->run_command ("lxc-stop -n $veid --rcfile $conffile --kill");
- }
+ }
rmtree $self->{rootfs};
- mkpath $self->{rootfs};
+ mkpath "$self->{rootfs}/dev";
}
sub ve_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;
$self->run_command([@pacman, '-Sw', '--', @$packages]);
}
+sub mask_systemd_unit {
+ my ($self, $unit) = @_;
+ my $root = $self->{rootfs};
+ symln '/dev/null', "$root/etc/systemd/system/$unit";
+}
+
sub bootstrap {
my ($self, $include, $exclude) = @_;
my $root = $self->{rootfs};
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}} };
$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']);
print "Populating keyring...\n";
$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)) {
+ $self->mask_systemd_unit($unit);
+ }
}
-sub populate_keyring {
+# 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};
- # 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'],
- };
+ # remove temporary device files
+ unlink "${root}$_" foreach keys %$devs;
+}
- my $cleanup_dev = sub {
- # remove temporary device files
- unlink "${root}$_" foreach keys %$devs;
- };
- local $SIG{INT} = $SIG{TERM} = $cleanup_dev;
+sub create_dev {
+ my ($self) = @_;
+ my $root = $self->{rootfs};
- # at least /dev/null exists as regular file after installing the filesystem package,
- # and we want to replace /dev/random, so delete devices first
- &$cleanup_dev();
+ 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
system('unshare', '--fork', '--pid', 'chroot', "$root", 'pacman-key', '--populate') == 0
or die "failed to populate keyring: $?";
- &$cleanup_dev();
- # reset to original state
- system('touch', "$root/dev/null");
}
sub install {
}
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 "unkown compressor '$compressor'!\n";
+ }
+ }
+
my $rootdir = $self->{rootfs};
print "Stopping container...\n";
} 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 {
unlink $self->{'pacman.conf'};
$self->ve_destroy();
unlink '.veid';
+ unlink $self->{veconffile};
+
rmtree $self->{pkgcache} if $all;
}