]>
git.proxmox.com Git - aab.git/blob - AAB.pm
5bab20013c609df454e4913d92fb625d99c99565
14 my @BASE_PACKAGES = qw(base openssh);
15 my @BASE_EXCLUDES = qw(e2fsprogs
25 my $PKGDIR = "/var/cache/pacman/pkg";
27 my ($aablibdir, $fake_init);
29 sub setup_defaults
($) {
32 $fake_init = "$aablibdir/scripts/init.bash";
35 setup_defaults
('/usr/lib/aab');
38 my ($data, $file, $perm) = @_;
40 die "no filename" if !$file;
43 my $fh = IO
::File-
>new ($file, O_WRONLY
| O_CREAT
, $perm) ||
44 die "unable to open file '$file'";
52 copy
($a, $b) or die "failed to copy $a => $b: $!";
57 rename($a, $b) or die "failed to rename $a => $b: $!";
62 symlink($a, $b) or die "failed to symlink $a => $b: $!";
73 my $fd = $self->{logfd
};
82 my $fh = IO
::File-
>new ("<$filename") || return $res;
85 while (defined (my $line = <$fh>)) {
86 next if $line =~ m/^\#/;
87 next if $line =~ m/^\s*$/;
97 if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) {
98 $res->{headline
} = $1;
99 chomp $res->{headline
};
102 $res->{description
} = $long;
103 chomp $res->{description
};
104 } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) {
105 my ($key, $value) = (lc ($1), $2);
106 if ($key eq 'source' || $key eq 'mirror') {
107 push @{$res->{$key}}, $value;
109 die "duplicate key '$key'\n" if defined ($res->{$key});
110 $res->{$key} = $value;
113 die "unable to parse config file: $rec";
117 die "unable to parse config file" if $rec;
123 my ($class, $config) = @_;
125 $config = read_config
('aab.conf') if !$config;
126 my $version = $config->{version
};
127 die "no 'version' specified\n" if !$version;
128 die "no 'section' specified\n" if !$config->{section
};
129 die "no 'description' specified\n" if !$config->{headline
};
130 die "no 'maintainer' specified\n" if !$config->{maintainer
};
132 my $name = $config->{name
} || die "no 'name' specified\n";
133 $name =~ m/^[a-z][0-9a-z\-\*\.]+$/ ||
134 die "illegal characters in name '$name'\n";
137 if ($name =~ m/^archlinux/) {
138 $targetname = "${name}_${version}_$config->{architecture}";
140 $targetname = "archlinux-${name}_${version}_$config->{architecture}";
143 my $self = { logfile
=> 'logfile',
145 targetname
=> $targetname,
146 incl
=> [@BASE_PACKAGES],
147 excl
=> [@BASE_EXCLUDES],
150 $self->{logfd
} = IO
::File-
>new($self->{logfile
}, O_WRONLY
| O_APPEND
| O_CREAT
)
151 or die "unable to open log file";
155 $self->__allocate_ve();
160 sub __sample_config
{
163 my $arch = $self->{config
}->{architecture
};
167 lxc.include = /usr/share/lxc/config/archlinux.common.conf
168 lxc.utsname = localhost
169 lxc.rootfs = $self->{rootfs}
170 lxc.mount.entry = $self->{pkgcache} $self->{pkgdir} none bind 0 0
178 if (my $fd = IO
::File-
>new(".veid")) {
185 $self->{working_dir
} = getcwd
;
186 $self->{veconffile
} = "$self->{working_dir}/config";
187 $self->{rootfs
} = "$self->{working_dir}/rootfs";
188 $self->{pkgdir
} = "$self->{working_dir}/rootfs/$PKGDIR";
189 $self->{pkgcache
} = "$self->{working_dir}/pkgcache";
190 $self->{'pacman.conf'} = "$self->{working_dir}/pacman.conf";
193 $self->{veid
} = $cid;
199 UUID
::generate
($uuid);
200 UUID
::unparse
($uuid, $uuid_str);
201 $self->{veid
} = $uuid_str;
203 my $fd = IO
::File-
>new (">.veid") ||
204 die "unable to write '.veid'\n";
205 print $fd "$self->{veid}\n";
207 $self->logmsg("allocated VE $self->{veid}\n");
213 my $config = $self->{config
};
215 $self->{logfd
} = IO
::File-
>new($self->{logfile
}, O_WRONLY
| O_TRUNC
| O_CREAT
)
216 or die "unable to open log file";
218 my $cdata = $self->__sample_config();
220 my $fh = IO
::File-
>new($self->{veconffile
}, O_WRONLY
|O_CREAT
|O_EXCL
) ||
221 die "unable to write lxc config file '$self->{veconffile}' - $!";
225 if (!$config->{source
} && !$config->{mirror
}) {
226 die "no sources/mirrors specified";
229 $config->{source
} //= [];
230 $config->{mirror
} //= [];
232 my $servers = "Server = "
233 . join("\nServer = ", @{$config->{source
}}, @{$config->{mirror
}})
236 $fh = IO
::File-
>new($self->{'pacman.conf'}, O_WRONLY
|O_CREAT
|O_EXCL
) ||
237 die "unable to write pacman config file $self->{'pacman.conf'} - $!";
240 HoldPkg = pacman glibc
241 Architecture = $config->{architecture}
255 mkdir $self->{rootfs
} || die "unable to create rootfs - $!";
257 $self->logmsg("configured VE $self->{veid}\n");
263 my $veid = $self->{veid
};
265 my $res = { running
=> 0 };
267 $res->{exist
} = 1 if -d
"$self->{rootfs}/usr";
269 my $filename = "/proc/net/unix";
271 # similar test is used by lcxcontainers.c: list_active_containers
272 my $fh = IO
::File-
>new ($filename, "r");
275 while (defined(my $line = <$fh>)) {
276 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
278 if ($path =~ m!^@/\S+/$veid/command$!) {
291 my $veid = $self->{veid
}; # fixme
293 my $vestat = $self->ve_status();
294 if ($vestat->{running
}) {
295 $self->stop_container();
298 rmtree
$self->{rootfs
};
299 unlink $self->{veconffile
};
306 my $veid = $self->{veid
};
308 $self->logmsg ("initialize VE $veid\n");
310 my $vestat = $self->ve_status();
311 if ($vestat->{running
}) {
312 $self->run_command ("lxc-stop -n $veid --kill");
315 rmtree
$self->{rootfs
};
316 mkpath
$self->{rootfs
};
320 my ($self, $cmd, $input) = @_;
322 my $veid = $self->{veid
};
324 if (ref ($cmd) eq 'ARRAY') {
325 unshift @$cmd, 'lxc-attach', '-n', $veid, '--clear-env', '--';
326 $self->run_command ($cmd, $input);
328 $self->run_command ("lxc-attach -n $veid --clear-env -- $cmd", $input);
333 my ($self, @cmd) = @_;
335 my $veid = $self->{veid
};
338 my $pid = open2
($reader, "<&STDIN", 'lxc-attach', '-n', $veid, '--', @cmd)
339 or die "unable to exec command";
341 while (defined (my $line = <$reader>)) {
342 $self->logmsg ($line);
348 die "ve_exec failed - status $rc\n" if $rc != 0;
352 my ($self, $cmd, $input, $getoutput) = @_;
354 my $reader = IO
::File-
>new();
355 my $writer = IO
::File-
>new();
356 my $error = IO
::File-
>new();
360 my $cmdstr = ref ($cmd) eq 'ARRAY' ?
join (' ', @$cmd) : $cmd;
364 if (ref ($cmd) eq 'ARRAY') {
365 $pid = open3
($writer, $reader, $error, @$cmd) || die $!;
367 $pid = open3
($writer, $reader, $error, $cmdstr) || die $!;
374 if ($orig_pid != $$) {
375 $self->logmsg ("ERROR: command '$cmdstr' failed - fork failed\n");
382 print $writer $input if defined $input;
385 my $select = new IO
::Select
;
386 $select->add ($reader);
387 $select->add ($error);
390 my $logfd = $self->{logfd
};
392 while ($select->count) {
393 my @handles = $select->can_read ();
395 foreach my $h (@handles) {
397 my $count = sysread ($h, $buf, 4096);
398 if (!defined ($count)) {
400 die "command '$cmdstr' failed: $!";
402 $select->remove ($h) if !$count;
406 $res .= $buf if $getoutput;
413 die "command '$cmdstr' failed with exit code $ec\n" if $ec;
418 sub start_container
{
420 my $veid = $self->{veid
};
421 $self->run_command(['lxc-start', '-n', $veid, '-f', $self->{veconffile
}, '/usr/bin/aab_fake_init']);
426 my $veid = $self->{veid
};
427 $self->run_command ("lxc-stop -n $veid --kill");
432 my $root = $self->{rootfs
};
433 return ('/usr/bin/pacman',
435 '--cachedir', $self->{pkgcache
},
440 my ($self, $packages) = @_;
441 my $root = $self->{rootfs
};
443 my @pacman = $self->pacman_command();
444 $self->run_command([@pacman, '-Sw', '--', @$packages]);
448 my ($self, $include, $exclude) = @_;
449 my $root = $self->{rootfs
};
451 my @pacman = $self->pacman_command();
453 print "Fetching package database...\n";
454 mkpath
$self->{pkgcache
};
455 mkpath
$self->{pkgdir
};
456 mkpath
"$root/var/lib/pacman";
457 $self->run_command([@pacman, '-Sy']);
459 print "Figuring out what to install...\n";
460 my $incl = { map { $_ => 1 } @{$self->{incl
}} };
461 my $excl = { map { $_ => 1 } @{$self->{excl
}} };
463 foreach my $addinc (@$include) {
464 $incl->{$addinc} = 1;
465 delete $excl->{$addinc};
467 foreach my $addexc (@$exclude) {
468 $excl->{$addexc} = 1;
469 delete $incl->{$addexc};
474 foreach my $inc (keys %$lst) {
476 eval { $group = $self->run_command([@pacman, '-Sqg', $inc], undef, 1); };
480 $lst->{$_} = 1 foreach split(/\s+/, $group);
488 my $packages = [ grep { !$excl->{$_} } keys %$incl ];
490 print "Setting up basic environment...\n";
492 mkpath
"$root/usr/bin";
494 my $data = "# UNCONFIGURED FSTAB FOR BASE SYSTEM\n";
495 write_file
($data, "$root/etc/fstab", 0644);
497 write_file
("", "$root/etc/resolv.conf", 0644);
498 write_file
("localhost\n", "$root/etc/hostname", 0644);
499 $self->run_command(['install', '-m0755', $fake_init, "$root/usr/bin/aab_fake_init"]);
501 unlink "$root/etc/localtime";
502 symln
'/usr/share/zoneinfo/UTC', "$root/etc/localtime";
504 print "Caching packages...\n";
505 $self->cache_packages($packages);
506 #$self->copy_packages();
508 print "Installing package manager and essentials...\n";
509 # inetutils for 'hostname' for our init
510 $self->run_command([@pacman, '-S', 'pacman', 'inetutils', 'archlinux-keyring']);
512 print "Setting up pacman for installation from cache...\n";
513 my $file = "$root/etc/pacman.d/mirrorlist";
514 my $backup = "${file}.aab_orig";
516 rename_file
($file, $backup);
517 write_file
("Server = file://$PKGDIR\n", $file);
520 print "Populating keyring...\n";
521 $self->run_command(['mount', '-t', 'devtmpfs', '-o', 'mode=0755,nosuid', 'udev', "$root/dev"]);
522 $self->run_command(['unshare', '--fork', '--pid', 'chroot', "$root", 'pacman-key', '--init']);
523 $self->run_command(['unshare', '--fork', '--pid', 'chroot', "$root", 'pacman-key', '--populate']);
524 $self->run_command(['umount', "$root/dev"]);
526 print "Starting container...\n";
527 $self->start_container();
529 print "Installing packages...\n";
530 $self->ve_command(['pacman', '-S', '--needed', '--noconfirm', '--', @$packages]);
534 my ($self, $pkglist) = @_;
536 $self->cache_packages($pkglist);
537 $self->ve_command(['pacman', '-S', '--needed', '--noconfirm', '--', @$pkglist]);
541 my ($self, $filename, $size) = @_;
543 my $config = $self->{config
};
547 $data .= "Name: $config->{name}\n";
548 $data .= "Version: $config->{version}\n";
549 $data .= "Type: lxc\n";
550 $data .= "OS: archlinux\n";
551 $data .= "Section: $config->{section}\n";
552 $data .= "Maintainer: $config->{maintainer}\n";
553 $data .= "Architecture: $config->{architecture}\n";
554 $data .= "Installed-Size: $size\n";
557 $data .= "Infopage: $config->{infopage}\n" if $config->{infopage
};
558 $data .= "ManageUrl: $config->{manageurl}\n" if $config->{manageurl
};
559 $data .= "Certified: $config->{certified}\n" if $config->{certified
};
562 $data .= "Description: $config->{headline}\n";
563 $data .= "$config->{description}\n" if $config->{description
};
565 write_file
($data, $filename, 0644);
570 my $rootdir = $self->{rootfs
};
572 print "Stopping container...\n";
573 $self->stop_container();
575 print "Rolling back mirrorlist changes...\n";
576 my $file = "$rootdir/etc/pacman.d/mirrorlist";
578 rename_file
($file.'.aab_orig', $file);
580 my $sizestr = $self->run_command("du -sm $rootdir", undef, 1);
582 if ($sizestr =~ m/^(\d+)\s+\Q$rootdir\E$/) {
585 die "unable to detect size\n";
587 $self->logmsg ("$size MB\n");
589 $self->write_config ("$rootdir/etc/appliance.info", $size);
591 $self->logmsg ("creating final appliance archive\n");
593 my $target = "$self->{targetname}.tar";
597 $self->run_command ("tar cpf $target --numeric-owner -C '$rootdir' ./etc/appliance.info");
598 $self->run_command ("tar rpf $target --numeric-owner -C '$rootdir' --exclude ./etc/appliance.info .");
599 $self->run_command ("gzip $target");
604 my $veid = $self->{veid
};
606 my $vestat = $self->ve_status();
607 if (!$vestat->{exist
}) {
608 $self->logmsg ("Please create the appliance first (bootstrap)");
612 if (!$vestat->{running
}) {
613 $self->start_container();
616 system ("lxc-attach -n $veid --clear-env");
620 my ($self, $all) = @_;
622 unlink $self->{logfile
};
623 unlink $self->{'pacman.conf'};
626 rmtree
$self->{pkgcache
} if $all;