]> git.proxmox.com Git - aab.git/commitdiff
move AAB.pm to PVE/
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Fri, 21 Aug 2015 08:02:48 +0000 (10:02 +0200)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Fri, 21 Aug 2015 08:02:48 +0000 (10:02 +0200)
AAB.pm [deleted file]
PVE/AAB.pm [new file with mode: 0644]

diff --git a/AAB.pm b/AAB.pm
deleted file mode 100644 (file)
index 5bab200..0000000
--- a/AAB.pm
+++ /dev/null
@@ -1,629 +0,0 @@
-package PVE::AAB;
-
-use strict;
-use warnings;
-
-use File::Path;
-use File::Copy;
-use IO::File;
-use IO::Select;
-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 $PKGDIR = "/var/cache/pacman/pkg";
-
-my ($aablibdir, $fake_init);
-
-sub setup_defaults($) {
-    my ($dir) = @_;
-    $aablibdir = $dir;
-    $fake_init = "$aablibdir/scripts/init.bash";
-}
-
-setup_defaults('/usr/lib/aab');
-
-sub write_file {
-    my ($data, $file, $perm) = @_;
-
-    die "no filename" if !$file;
-    unlink $file;
-
-    my $fh = IO::File->new ($file, O_WRONLY | O_CREAT, $perm) ||
-       die "unable to open file '$file'";
-
-    print $fh $data;
-    $fh->close;
-}
-
-sub copy_file {
-    my ($a, $b) = @_;
-    copy($a, $b) or die "failed to copy $a => $b: $!";
-}
-
-sub rename_file {
-    my ($a, $b) = @_;
-    rename($a, $b) or die "failed to rename $a => $b: $!";
-}
-
-sub symln {
-    my ($a, $b) = @_;
-    symlink($a, $b) or die "failed to symlink $a => $b: $!";
-}
-
-sub logmsg {
-    my $self = shift;
-    print STDERR @_;
-    $self->writelog (@_);
-}
-
-sub writelog {
-    my $self = shift;
-    my $fd = $self->{logfd};
-    print $fd @_;
-}
-
-sub read_config {
-    my ($filename) = @_;
-
-    my $res = {};
-
-    my $fh = IO::File->new ("<$filename") || return $res;
-    my $rec = '';
-
-    while (defined (my $line = <$fh>)) {
-       next if $line =~ m/^\#/;
-       next if $line =~ m/^\s*$/;
-       $rec .= $line;
-    };
-
-    close ($fh);
-
-    chomp $rec;
-    $rec .= "\n";
-
-    while ($rec) {
-       if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) {
-           $res->{headline} = $1;
-           chomp $res->{headline};
-           my $long = $2;
-           $long =~ s/^\s+/ /;
-           $res->{description} = $long;
-           chomp $res->{description};      
-       } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) {
-           my ($key, $value) = (lc ($1), $2);
-           if ($key eq 'source' || $key eq 'mirror') {
-               push @{$res->{$key}}, $value;
-           } else {
-               die "duplicate key '$key'\n" if defined ($res->{$key});
-               $res->{$key} = $value;
-           }
-       } else {
-           die "unable to parse config file: $rec";
-       }
-    }
-
-    die "unable to parse config file" if $rec;
-
-    return $res;
-}
-
-sub new {
-    my ($class, $config) = @_;
-
-    $config = read_config ('aab.conf') if !$config;
-    my $version = $config->{version};
-    die "no 'version' specified\n" if !$version;
-    die "no 'section' specified\n" if !$config->{section};
-    die "no 'description' specified\n" if !$config->{headline};
-    die "no 'maintainer' specified\n" if !$config->{maintainer};
-
-    my $name = $config->{name} || die "no 'name' specified\n";
-    $name =~ m/^[a-z][0-9a-z\-\*\.]+$/ || 
-       die "illegal characters in name '$name'\n";
-
-    my $targetname;
-    if ($name =~ m/^archlinux/) {
-       $targetname = "${name}_${version}_$config->{architecture}";
-    } else {
-       $targetname = "archlinux-${name}_${version}_$config->{architecture}";
-    }
-
-    my $self = { logfile => 'logfile',
-                 config => $config,
-                 targetname => $targetname,
-                 incl => [@BASE_PACKAGES],
-                 excl => [@BASE_EXCLUDES],
-               };
-
-    $self->{logfd} = IO::File->new($self->{logfile}, O_WRONLY | O_APPEND | O_CREAT)
-       or die "unable to open log file";
-
-    bless $self, $class;
-
-    $self->__allocate_ve();
-
-    return $self;
-}
-
-sub __sample_config {
-    my ($self) = @_;
-
-    my $arch = $self->{config}->{architecture};
-
-    return <<"CFG";
-lxc.arch = $arch
-lxc.include = /usr/share/lxc/config/archlinux.common.conf
-lxc.utsname = localhost
-lxc.rootfs = $self->{rootfs}
-lxc.mount.entry = $self->{pkgcache} $self->{pkgdir} none bind 0 0
-CFG
-}
-
-sub __allocate_ve {
-    my ($self) = @_;
-
-    my $cid;
-    if (my $fd = IO::File->new(".veid")) {
-       $cid = <$fd>;
-       chomp $cid;
-       close ($fd);
-    }
-
-
-    $self->{working_dir} = getcwd;
-    $self->{veconffile} = "$self->{working_dir}/config";
-    $self->{rootfs} = "$self->{working_dir}/rootfs";
-    $self->{pkgdir} = "$self->{working_dir}/rootfs/$PKGDIR";
-    $self->{pkgcache} = "$self->{working_dir}/pkgcache";
-    $self->{'pacman.conf'} = "$self->{working_dir}/pacman.conf";
-
-    if ($cid) {
-       $self->{veid} = $cid;
-       return $cid;
-    }
-
-    my $uuid;
-    my $uuid_str;
-    UUID::generate($uuid);
-    UUID::unparse($uuid, $uuid_str);
-    $self->{veid} = $uuid_str;
-
-    my $fd = IO::File->new (">.veid") ||
-       die "unable to write '.veid'\n";
-    print $fd "$self->{veid}\n";
-    close ($fd);
-    $self->logmsg("allocated VE $self->{veid}\n");
-}
-
-sub initialize {
-    my ($self) = @_;
-
-    my $config = $self->{config};
-
-    $self->{logfd} = IO::File->new($self->{logfile}, O_WRONLY | O_TRUNC | O_CREAT)
-       or die "unable to open log file";
-
-    my $cdata = $self->__sample_config();
-
-    my $fh = IO::File->new($self->{veconffile}, O_WRONLY|O_CREAT|O_EXCL) ||
-       die "unable to write lxc config file '$self->{veconffile}' - $!";
-    print $fh $cdata;
-    close ($fh);
-
-    if (!$config->{source} && !$config->{mirror}) {
-       die "no sources/mirrors specified";
-    }
-
-    $config->{source} //= [];
-    $config->{mirror} //= [];
-
-    my $servers = "Server = "
-                . join("\nServer = ", @{$config->{source}}, @{$config->{mirror}})
-                . "\n";
-
-    $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}
-CheckSpace
-SigLevel = Never
-
-[core]
-$servers
-[extra]
-$servers
-[community]
-$servers
-[multilib]
-$servers
-EOF
-
-    mkdir $self->{rootfs} || die "unable to create rootfs - $!";
-
-    $self->logmsg("configured VE $self->{veid}\n");
-}
-
-sub ve_status {
-    my ($self) = @_;
-
-    my $veid = $self->{veid};
-
-    my $res = { running => 0 };
-
-    $res->{exist} = 1 if -d "$self->{rootfs}/usr";
-
-    my $filename = "/proc/net/unix";
-
-    # similar test is used by lcxcontainers.c: list_active_containers
-    my $fh = IO::File->new ($filename, "r");
-    return $res if !$fh;
-
-    while (defined(my $line = <$fh>)) {
-       if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
-           my $path = $1;
-           if ($path =~ m!^@/\S+/$veid/command$!) {
-               $res->{running} = 1;
-           }
-       }
-    }
-    close($fh);
-    
-    return $res;
-}
-
-sub ve_destroy {
-    my ($self) = @_;
-
-    my $veid = $self->{veid}; # fixme
-
-    my $vestat = $self->ve_status();
-    if ($vestat->{running}) {
-       $self->stop_container();
-    }
-
-    rmtree $self->{rootfs};
-    unlink $self->{veconffile};
-}
-
-sub ve_init {
-    my ($self) = @_;
-
-
-    my $veid = $self->{veid};
-
-    $self->logmsg ("initialize VE $veid\n");
-
-    my $vestat = $self->ve_status();
-    if ($vestat->{running}) {
-       $self->run_command ("lxc-stop -n $veid --kill");
-    } 
-
-    rmtree $self->{rootfs};
-    mkpath $self->{rootfs};
-}
-
-sub ve_command {
-    my ($self, $cmd, $input) = @_;
-
-    my $veid = $self->{veid};
-
-    if (ref ($cmd) eq 'ARRAY') {
-       unshift @$cmd, 'lxc-attach', '-n', $veid, '--clear-env', '--';
-       $self->run_command ($cmd, $input);
-    } else {
-       $self->run_command ("lxc-attach -n $veid --clear-env -- $cmd", $input);
-    }
-}
-
-sub ve_exec {
-    my ($self, @cmd) = @_;
-
-    my $veid = $self->{veid};
-
-    my $reader;
-    my $pid = open2($reader, "<&STDIN", 'lxc-attach', '-n', $veid,  '--', @cmd)
-       or die "unable to exec command";
-
-    while (defined (my $line = <$reader>)) {
-       $self->logmsg ($line);
-    }
-
-    waitpid ($pid, 0);
-    my $rc = $? >> 8;
-
-    die "ve_exec failed - status $rc\n" if $rc != 0;
-}
-
-sub run_command {
-    my ($self, $cmd, $input, $getoutput) = @_;
-
-    my $reader = IO::File->new();
-    my $writer = IO::File->new();
-    my $error  = IO::File->new();
-
-    my $orig_pid = $$;
-
-    my $cmdstr = ref ($cmd) eq 'ARRAY' ? join (' ', @$cmd) : $cmd;
-
-    my $pid;
-    eval {
-       if (ref ($cmd) eq 'ARRAY') {
-           $pid = open3 ($writer, $reader, $error, @$cmd) || die $!;
-       } else {
-           $pid = open3 ($writer, $reader, $error, $cmdstr) || die $!;
-       }
-    };
-
-    my $err = $@;
-
-    # catch exec errors
-    if ($orig_pid != $$) {
-       $self->logmsg ("ERROR: command '$cmdstr' failed - fork failed\n");
-       POSIX::_exit (1); 
-       kill ('KILL', $$); 
-    }
-
-    die $err if $err;
-
-    print $writer $input if defined $input;
-    close $writer;
-
-    my $select = new IO::Select;
-    $select->add ($reader);
-    $select->add ($error);
-
-    my $res = '';
-    my $logfd = $self->{logfd};
-
-    while ($select->count) {
-       my @handles = $select->can_read ();
-
-       foreach my $h (@handles) {
-           my $buf = '';
-           my $count = sysread ($h, $buf, 4096);
-           if (!defined ($count)) {
-               waitpid ($pid, 0);
-               die "command '$cmdstr' failed: $!";
-           }
-           $select->remove ($h) if !$count;
-
-           print $logfd $buf;
-
-           $res .= $buf if $getoutput;
-       }
-    }
-
-    waitpid ($pid, 0);
-    my $ec = ($? >> 8);
-
-    die "command '$cmdstr' failed with exit code $ec\n" if $ec;
-
-    return $res;
-}
-
-sub start_container {
-    my ($self) = @_;
-    my $veid = $self->{veid};
-    $self->run_command(['lxc-start', '-n', $veid, '-f', $self->{veconffile}, '/usr/bin/aab_fake_init']);
-}
-
-sub stop_container {
-    my ($self) = @_;
-    my $veid = $self->{veid};
-    $self->run_command ("lxc-stop -n $veid --kill");
-}
-
-sub pacman_command {
-    my ($self) = @_;
-    my $root = $self->{rootfs};
-    return ('/usr/bin/pacman',
-            '--root', $root,
-            '--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]);
-}
-
-sub bootstrap {
-    my ($self, $include, $exclude) = @_;
-    my $root = $self->{rootfs};
-
-    my @pacman = $self->pacman_command();
-
-    print "Fetching package database...\n";
-    mkpath $self->{pkgcache};
-    mkpath $self->{pkgdir};
-    mkpath "$root/var/lib/pacman";
-    $self->run_command([@pacman, '-Sy']);
-
-    print "Figuring out what to install...\n";
-    my $incl = { map { $_ => 1 } @{$self->{incl}} };
-    my $excl = { map { $_ => 1 } @{$self->{excl}} };
-
-    foreach my $addinc (@$include) {
-       $incl->{$addinc} = 1;
-       delete $excl->{$addinc};
-    }
-    foreach my $addexc (@$exclude) {
-       $excl->{$addexc} = 1;
-       delete $incl->{$addexc};
-    }
-
-    my $expand = sub {
-       my ($lst) = @_;
-       foreach my $inc (keys %$lst) {
-           my $group;
-           eval { $group = $self->run_command([@pacman, '-Sqg', $inc], undef, 1); };
-           if ($group && !$@) {
-               # add the group
-               delete $lst->{$inc};
-               $lst->{$_} = 1 foreach split(/\s+/, $group);
-           }
-       }
-    };
-
-    $expand->($incl);
-    $expand->($excl);
-
-    my $packages = [ grep { !$excl->{$_} } keys %$incl ];
-
-    print "Setting up basic environment...\n";
-    mkpath "$root/etc";
-    mkpath "$root/usr/bin";
-
-    my $data = "# UNCONFIGURED FSTAB FOR BASE SYSTEM\n";
-    write_file ($data, "$root/etc/fstab", 0644);
-
-    write_file ("", "$root/etc/resolv.conf", 0644);
-    write_file("localhost\n", "$root/etc/hostname", 0644);
-    $self->run_command(['install', '-m0755', $fake_init, "$root/usr/bin/aab_fake_init"]);
-
-    unlink "$root/etc/localtime";
-    symln '/usr/share/zoneinfo/UTC', "$root/etc/localtime";
-
-    print "Caching packages...\n";
-    $self->cache_packages($packages);
-    #$self->copy_packages();
-
-    print "Installing package manager and essentials...\n";
-    # inetutils for 'hostname' for our init
-    $self->run_command([@pacman, '-S', 'pacman', 'inetutils', 'archlinux-keyring']);
-
-    print "Setting up pacman for installation from cache...\n";
-    my $file = "$root/etc/pacman.d/mirrorlist";
-    my $backup = "${file}.aab_orig";
-    if (!-f $backup) {
-       rename_file($file, $backup);
-       write_file("Server = file://$PKGDIR\n", $file);
-    }
-
-    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"]);
-
-    print "Starting container...\n";
-    $self->start_container();
-
-    print "Installing packages...\n";
-    $self->ve_command(['pacman', '-S', '--needed', '--noconfirm', '--', @$packages]);
-}
-
-sub install {
-    my ($self, $pkglist) = @_;
-
-    $self->cache_packages($pkglist);
-    $self->ve_command(['pacman', '-S', '--needed', '--noconfirm', '--', @$pkglist]);
-}
-
-sub write_config {
-    my ($self, $filename, $size) = @_;
-
-    my $config = $self->{config};
-
-    my $data = '';
-
-    $data .= "Name: $config->{name}\n";
-    $data .= "Version: $config->{version}\n";
-    $data .= "Type: lxc\n";
-    $data .= "OS: archlinux\n";
-    $data .= "Section: $config->{section}\n";
-    $data .= "Maintainer: $config->{maintainer}\n";
-    $data .= "Architecture: $config->{architecture}\n";
-    $data .= "Installed-Size: $size\n";
-
-    # optional
-    $data .= "Infopage: $config->{infopage}\n" if $config->{infopage};
-    $data .= "ManageUrl: $config->{manageurl}\n" if $config->{manageurl};
-    $data .= "Certified: $config->{certified}\n" if $config->{certified};
-
-    # description
-    $data .= "Description: $config->{headline}\n";
-    $data .= "$config->{description}\n" if $config->{description};
-
-    write_file ($data, $filename, 0644);
-}
-
-sub finalize {
-    my ($self) = @_;
-    my $rootdir = $self->{rootfs};
-
-    print "Stopping container...\n";
-    $self->stop_container();
-
-    print "Rolling back mirrorlist changes...\n";
-    my $file = "$rootdir/etc/pacman.d/mirrorlist";
-    unlink $file;
-    rename_file($file.'.aab_orig', $file);
-
-    my $sizestr = $self->run_command("du -sm $rootdir", undef, 1);
-    my $size;
-    if ($sizestr =~ m/^(\d+)\s+\Q$rootdir\E$/) {
-       $size = $1;
-    } else {
-       die "unable to detect size\n";
-    }
-    $self->logmsg ("$size MB\n");
-
-    $self->write_config ("$rootdir/etc/appliance.info", $size);
-
-    $self->logmsg ("creating final appliance archive\n");
-
-    my $target = "$self->{targetname}.tar";
-    unlink $target;
-    unlink "$target.gz";
-
-    $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");
-}
-
-sub enter {
-    my ($self) = @_;
-    my $veid = $self->{veid};
-
-    my $vestat = $self->ve_status();
-    if (!$vestat->{exist}) {
-       $self->logmsg ("Please create the appliance first (bootstrap)");
-       return;
-    }
-
-    if (!$vestat->{running}) {
-       $self->start_container();
-    }
-
-    system ("lxc-attach -n $veid --clear-env");
-}
-
-sub clean {
-    my ($self, $all) = @_;
-
-    unlink $self->{logfile};
-    unlink $self->{'pacman.conf'};
-    $self->ve_destroy();
-    unlink '.veid';
-    rmtree $self->{pkgcache} if $all;
-}
-
-1;
diff --git a/PVE/AAB.pm b/PVE/AAB.pm
new file mode 100644 (file)
index 0000000..5bab200
--- /dev/null
@@ -0,0 +1,629 @@
+package PVE::AAB;
+
+use strict;
+use warnings;
+
+use File::Path;
+use File::Copy;
+use IO::File;
+use IO::Select;
+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 $PKGDIR = "/var/cache/pacman/pkg";
+
+my ($aablibdir, $fake_init);
+
+sub setup_defaults($) {
+    my ($dir) = @_;
+    $aablibdir = $dir;
+    $fake_init = "$aablibdir/scripts/init.bash";
+}
+
+setup_defaults('/usr/lib/aab');
+
+sub write_file {
+    my ($data, $file, $perm) = @_;
+
+    die "no filename" if !$file;
+    unlink $file;
+
+    my $fh = IO::File->new ($file, O_WRONLY | O_CREAT, $perm) ||
+       die "unable to open file '$file'";
+
+    print $fh $data;
+    $fh->close;
+}
+
+sub copy_file {
+    my ($a, $b) = @_;
+    copy($a, $b) or die "failed to copy $a => $b: $!";
+}
+
+sub rename_file {
+    my ($a, $b) = @_;
+    rename($a, $b) or die "failed to rename $a => $b: $!";
+}
+
+sub symln {
+    my ($a, $b) = @_;
+    symlink($a, $b) or die "failed to symlink $a => $b: $!";
+}
+
+sub logmsg {
+    my $self = shift;
+    print STDERR @_;
+    $self->writelog (@_);
+}
+
+sub writelog {
+    my $self = shift;
+    my $fd = $self->{logfd};
+    print $fd @_;
+}
+
+sub read_config {
+    my ($filename) = @_;
+
+    my $res = {};
+
+    my $fh = IO::File->new ("<$filename") || return $res;
+    my $rec = '';
+
+    while (defined (my $line = <$fh>)) {
+       next if $line =~ m/^\#/;
+       next if $line =~ m/^\s*$/;
+       $rec .= $line;
+    };
+
+    close ($fh);
+
+    chomp $rec;
+    $rec .= "\n";
+
+    while ($rec) {
+       if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) {
+           $res->{headline} = $1;
+           chomp $res->{headline};
+           my $long = $2;
+           $long =~ s/^\s+/ /;
+           $res->{description} = $long;
+           chomp $res->{description};      
+       } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) {
+           my ($key, $value) = (lc ($1), $2);
+           if ($key eq 'source' || $key eq 'mirror') {
+               push @{$res->{$key}}, $value;
+           } else {
+               die "duplicate key '$key'\n" if defined ($res->{$key});
+               $res->{$key} = $value;
+           }
+       } else {
+           die "unable to parse config file: $rec";
+       }
+    }
+
+    die "unable to parse config file" if $rec;
+
+    return $res;
+}
+
+sub new {
+    my ($class, $config) = @_;
+
+    $config = read_config ('aab.conf') if !$config;
+    my $version = $config->{version};
+    die "no 'version' specified\n" if !$version;
+    die "no 'section' specified\n" if !$config->{section};
+    die "no 'description' specified\n" if !$config->{headline};
+    die "no 'maintainer' specified\n" if !$config->{maintainer};
+
+    my $name = $config->{name} || die "no 'name' specified\n";
+    $name =~ m/^[a-z][0-9a-z\-\*\.]+$/ || 
+       die "illegal characters in name '$name'\n";
+
+    my $targetname;
+    if ($name =~ m/^archlinux/) {
+       $targetname = "${name}_${version}_$config->{architecture}";
+    } else {
+       $targetname = "archlinux-${name}_${version}_$config->{architecture}";
+    }
+
+    my $self = { logfile => 'logfile',
+                 config => $config,
+                 targetname => $targetname,
+                 incl => [@BASE_PACKAGES],
+                 excl => [@BASE_EXCLUDES],
+               };
+
+    $self->{logfd} = IO::File->new($self->{logfile}, O_WRONLY | O_APPEND | O_CREAT)
+       or die "unable to open log file";
+
+    bless $self, $class;
+
+    $self->__allocate_ve();
+
+    return $self;
+}
+
+sub __sample_config {
+    my ($self) = @_;
+
+    my $arch = $self->{config}->{architecture};
+
+    return <<"CFG";
+lxc.arch = $arch
+lxc.include = /usr/share/lxc/config/archlinux.common.conf
+lxc.utsname = localhost
+lxc.rootfs = $self->{rootfs}
+lxc.mount.entry = $self->{pkgcache} $self->{pkgdir} none bind 0 0
+CFG
+}
+
+sub __allocate_ve {
+    my ($self) = @_;
+
+    my $cid;
+    if (my $fd = IO::File->new(".veid")) {
+       $cid = <$fd>;
+       chomp $cid;
+       close ($fd);
+    }
+
+
+    $self->{working_dir} = getcwd;
+    $self->{veconffile} = "$self->{working_dir}/config";
+    $self->{rootfs} = "$self->{working_dir}/rootfs";
+    $self->{pkgdir} = "$self->{working_dir}/rootfs/$PKGDIR";
+    $self->{pkgcache} = "$self->{working_dir}/pkgcache";
+    $self->{'pacman.conf'} = "$self->{working_dir}/pacman.conf";
+
+    if ($cid) {
+       $self->{veid} = $cid;
+       return $cid;
+    }
+
+    my $uuid;
+    my $uuid_str;
+    UUID::generate($uuid);
+    UUID::unparse($uuid, $uuid_str);
+    $self->{veid} = $uuid_str;
+
+    my $fd = IO::File->new (">.veid") ||
+       die "unable to write '.veid'\n";
+    print $fd "$self->{veid}\n";
+    close ($fd);
+    $self->logmsg("allocated VE $self->{veid}\n");
+}
+
+sub initialize {
+    my ($self) = @_;
+
+    my $config = $self->{config};
+
+    $self->{logfd} = IO::File->new($self->{logfile}, O_WRONLY | O_TRUNC | O_CREAT)
+       or die "unable to open log file";
+
+    my $cdata = $self->__sample_config();
+
+    my $fh = IO::File->new($self->{veconffile}, O_WRONLY|O_CREAT|O_EXCL) ||
+       die "unable to write lxc config file '$self->{veconffile}' - $!";
+    print $fh $cdata;
+    close ($fh);
+
+    if (!$config->{source} && !$config->{mirror}) {
+       die "no sources/mirrors specified";
+    }
+
+    $config->{source} //= [];
+    $config->{mirror} //= [];
+
+    my $servers = "Server = "
+                . join("\nServer = ", @{$config->{source}}, @{$config->{mirror}})
+                . "\n";
+
+    $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}
+CheckSpace
+SigLevel = Never
+
+[core]
+$servers
+[extra]
+$servers
+[community]
+$servers
+[multilib]
+$servers
+EOF
+
+    mkdir $self->{rootfs} || die "unable to create rootfs - $!";
+
+    $self->logmsg("configured VE $self->{veid}\n");
+}
+
+sub ve_status {
+    my ($self) = @_;
+
+    my $veid = $self->{veid};
+
+    my $res = { running => 0 };
+
+    $res->{exist} = 1 if -d "$self->{rootfs}/usr";
+
+    my $filename = "/proc/net/unix";
+
+    # similar test is used by lcxcontainers.c: list_active_containers
+    my $fh = IO::File->new ($filename, "r");
+    return $res if !$fh;
+
+    while (defined(my $line = <$fh>)) {
+       if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
+           my $path = $1;
+           if ($path =~ m!^@/\S+/$veid/command$!) {
+               $res->{running} = 1;
+           }
+       }
+    }
+    close($fh);
+    
+    return $res;
+}
+
+sub ve_destroy {
+    my ($self) = @_;
+
+    my $veid = $self->{veid}; # fixme
+
+    my $vestat = $self->ve_status();
+    if ($vestat->{running}) {
+       $self->stop_container();
+    }
+
+    rmtree $self->{rootfs};
+    unlink $self->{veconffile};
+}
+
+sub ve_init {
+    my ($self) = @_;
+
+
+    my $veid = $self->{veid};
+
+    $self->logmsg ("initialize VE $veid\n");
+
+    my $vestat = $self->ve_status();
+    if ($vestat->{running}) {
+       $self->run_command ("lxc-stop -n $veid --kill");
+    } 
+
+    rmtree $self->{rootfs};
+    mkpath $self->{rootfs};
+}
+
+sub ve_command {
+    my ($self, $cmd, $input) = @_;
+
+    my $veid = $self->{veid};
+
+    if (ref ($cmd) eq 'ARRAY') {
+       unshift @$cmd, 'lxc-attach', '-n', $veid, '--clear-env', '--';
+       $self->run_command ($cmd, $input);
+    } else {
+       $self->run_command ("lxc-attach -n $veid --clear-env -- $cmd", $input);
+    }
+}
+
+sub ve_exec {
+    my ($self, @cmd) = @_;
+
+    my $veid = $self->{veid};
+
+    my $reader;
+    my $pid = open2($reader, "<&STDIN", 'lxc-attach', '-n', $veid,  '--', @cmd)
+       or die "unable to exec command";
+
+    while (defined (my $line = <$reader>)) {
+       $self->logmsg ($line);
+    }
+
+    waitpid ($pid, 0);
+    my $rc = $? >> 8;
+
+    die "ve_exec failed - status $rc\n" if $rc != 0;
+}
+
+sub run_command {
+    my ($self, $cmd, $input, $getoutput) = @_;
+
+    my $reader = IO::File->new();
+    my $writer = IO::File->new();
+    my $error  = IO::File->new();
+
+    my $orig_pid = $$;
+
+    my $cmdstr = ref ($cmd) eq 'ARRAY' ? join (' ', @$cmd) : $cmd;
+
+    my $pid;
+    eval {
+       if (ref ($cmd) eq 'ARRAY') {
+           $pid = open3 ($writer, $reader, $error, @$cmd) || die $!;
+       } else {
+           $pid = open3 ($writer, $reader, $error, $cmdstr) || die $!;
+       }
+    };
+
+    my $err = $@;
+
+    # catch exec errors
+    if ($orig_pid != $$) {
+       $self->logmsg ("ERROR: command '$cmdstr' failed - fork failed\n");
+       POSIX::_exit (1); 
+       kill ('KILL', $$); 
+    }
+
+    die $err if $err;
+
+    print $writer $input if defined $input;
+    close $writer;
+
+    my $select = new IO::Select;
+    $select->add ($reader);
+    $select->add ($error);
+
+    my $res = '';
+    my $logfd = $self->{logfd};
+
+    while ($select->count) {
+       my @handles = $select->can_read ();
+
+       foreach my $h (@handles) {
+           my $buf = '';
+           my $count = sysread ($h, $buf, 4096);
+           if (!defined ($count)) {
+               waitpid ($pid, 0);
+               die "command '$cmdstr' failed: $!";
+           }
+           $select->remove ($h) if !$count;
+
+           print $logfd $buf;
+
+           $res .= $buf if $getoutput;
+       }
+    }
+
+    waitpid ($pid, 0);
+    my $ec = ($? >> 8);
+
+    die "command '$cmdstr' failed with exit code $ec\n" if $ec;
+
+    return $res;
+}
+
+sub start_container {
+    my ($self) = @_;
+    my $veid = $self->{veid};
+    $self->run_command(['lxc-start', '-n', $veid, '-f', $self->{veconffile}, '/usr/bin/aab_fake_init']);
+}
+
+sub stop_container {
+    my ($self) = @_;
+    my $veid = $self->{veid};
+    $self->run_command ("lxc-stop -n $veid --kill");
+}
+
+sub pacman_command {
+    my ($self) = @_;
+    my $root = $self->{rootfs};
+    return ('/usr/bin/pacman',
+            '--root', $root,
+            '--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]);
+}
+
+sub bootstrap {
+    my ($self, $include, $exclude) = @_;
+    my $root = $self->{rootfs};
+
+    my @pacman = $self->pacman_command();
+
+    print "Fetching package database...\n";
+    mkpath $self->{pkgcache};
+    mkpath $self->{pkgdir};
+    mkpath "$root/var/lib/pacman";
+    $self->run_command([@pacman, '-Sy']);
+
+    print "Figuring out what to install...\n";
+    my $incl = { map { $_ => 1 } @{$self->{incl}} };
+    my $excl = { map { $_ => 1 } @{$self->{excl}} };
+
+    foreach my $addinc (@$include) {
+       $incl->{$addinc} = 1;
+       delete $excl->{$addinc};
+    }
+    foreach my $addexc (@$exclude) {
+       $excl->{$addexc} = 1;
+       delete $incl->{$addexc};
+    }
+
+    my $expand = sub {
+       my ($lst) = @_;
+       foreach my $inc (keys %$lst) {
+           my $group;
+           eval { $group = $self->run_command([@pacman, '-Sqg', $inc], undef, 1); };
+           if ($group && !$@) {
+               # add the group
+               delete $lst->{$inc};
+               $lst->{$_} = 1 foreach split(/\s+/, $group);
+           }
+       }
+    };
+
+    $expand->($incl);
+    $expand->($excl);
+
+    my $packages = [ grep { !$excl->{$_} } keys %$incl ];
+
+    print "Setting up basic environment...\n";
+    mkpath "$root/etc";
+    mkpath "$root/usr/bin";
+
+    my $data = "# UNCONFIGURED FSTAB FOR BASE SYSTEM\n";
+    write_file ($data, "$root/etc/fstab", 0644);
+
+    write_file ("", "$root/etc/resolv.conf", 0644);
+    write_file("localhost\n", "$root/etc/hostname", 0644);
+    $self->run_command(['install', '-m0755', $fake_init, "$root/usr/bin/aab_fake_init"]);
+
+    unlink "$root/etc/localtime";
+    symln '/usr/share/zoneinfo/UTC', "$root/etc/localtime";
+
+    print "Caching packages...\n";
+    $self->cache_packages($packages);
+    #$self->copy_packages();
+
+    print "Installing package manager and essentials...\n";
+    # inetutils for 'hostname' for our init
+    $self->run_command([@pacman, '-S', 'pacman', 'inetutils', 'archlinux-keyring']);
+
+    print "Setting up pacman for installation from cache...\n";
+    my $file = "$root/etc/pacman.d/mirrorlist";
+    my $backup = "${file}.aab_orig";
+    if (!-f $backup) {
+       rename_file($file, $backup);
+       write_file("Server = file://$PKGDIR\n", $file);
+    }
+
+    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"]);
+
+    print "Starting container...\n";
+    $self->start_container();
+
+    print "Installing packages...\n";
+    $self->ve_command(['pacman', '-S', '--needed', '--noconfirm', '--', @$packages]);
+}
+
+sub install {
+    my ($self, $pkglist) = @_;
+
+    $self->cache_packages($pkglist);
+    $self->ve_command(['pacman', '-S', '--needed', '--noconfirm', '--', @$pkglist]);
+}
+
+sub write_config {
+    my ($self, $filename, $size) = @_;
+
+    my $config = $self->{config};
+
+    my $data = '';
+
+    $data .= "Name: $config->{name}\n";
+    $data .= "Version: $config->{version}\n";
+    $data .= "Type: lxc\n";
+    $data .= "OS: archlinux\n";
+    $data .= "Section: $config->{section}\n";
+    $data .= "Maintainer: $config->{maintainer}\n";
+    $data .= "Architecture: $config->{architecture}\n";
+    $data .= "Installed-Size: $size\n";
+
+    # optional
+    $data .= "Infopage: $config->{infopage}\n" if $config->{infopage};
+    $data .= "ManageUrl: $config->{manageurl}\n" if $config->{manageurl};
+    $data .= "Certified: $config->{certified}\n" if $config->{certified};
+
+    # description
+    $data .= "Description: $config->{headline}\n";
+    $data .= "$config->{description}\n" if $config->{description};
+
+    write_file ($data, $filename, 0644);
+}
+
+sub finalize {
+    my ($self) = @_;
+    my $rootdir = $self->{rootfs};
+
+    print "Stopping container...\n";
+    $self->stop_container();
+
+    print "Rolling back mirrorlist changes...\n";
+    my $file = "$rootdir/etc/pacman.d/mirrorlist";
+    unlink $file;
+    rename_file($file.'.aab_orig', $file);
+
+    my $sizestr = $self->run_command("du -sm $rootdir", undef, 1);
+    my $size;
+    if ($sizestr =~ m/^(\d+)\s+\Q$rootdir\E$/) {
+       $size = $1;
+    } else {
+       die "unable to detect size\n";
+    }
+    $self->logmsg ("$size MB\n");
+
+    $self->write_config ("$rootdir/etc/appliance.info", $size);
+
+    $self->logmsg ("creating final appliance archive\n");
+
+    my $target = "$self->{targetname}.tar";
+    unlink $target;
+    unlink "$target.gz";
+
+    $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");
+}
+
+sub enter {
+    my ($self) = @_;
+    my $veid = $self->{veid};
+
+    my $vestat = $self->ve_status();
+    if (!$vestat->{exist}) {
+       $self->logmsg ("Please create the appliance first (bootstrap)");
+       return;
+    }
+
+    if (!$vestat->{running}) {
+       $self->start_container();
+    }
+
+    system ("lxc-attach -n $veid --clear-env");
+}
+
+sub clean {
+    my ($self, $all) = @_;
+
+    unlink $self->{logfile};
+    unlink $self->{'pacman.conf'};
+    $self->ve_destroy();
+    unlink '.veid';
+    rmtree $self->{pkgcache} if $all;
+}
+
+1;