#!/usr/bin/perl -w use strict; use warnings; use Getopt::Long; use PVE::DAB; $ENV{'LC_ALL'} = 'C'; my $commands = { 'init' => '', 'bootstrap' => '[--exim] [--include ] --exclude [] [--minimal]', 'finalize' => '[--keepmycnf] [--compressor ]', 'veid' => '', 'basedir' => '', 'packagefile' => '', 'list' => '[--verbose]', 'task' => ' [--version] [--password] [--memlimit]', 'install' => ' ...', 'exec' => ' ...', 'enter' => '', 'clean' => '', 'dist-clean' => '', 'help' => '', }; sub print_usage { print STDERR "USAGE: dab [parameters]\n\n"; for my $cmd (sort keys %$commands) { if (my $opts = $commands->{$cmd}) { print STDERR " dab $cmd $opts\n"; } else { print STDERR " dab $cmd\n"; } } } sub fatal_usage { my ($msg) = @_; print STDERR "\nERROR: $msg\n\n" if $msg; print_usage(); exit (-1); } if (scalar (@ARGV) == 0) { fatal_usage("no command specified"); } my $cmdline = join (' ', @ARGV); my $cmd = shift @ARGV; if (!$cmd) { fatal_usage("no command specified"); } elsif (!exists $commands->{$cmd}) { fatal_usage("unknown command '$cmd'"); } elsif ($cmd eq 'help') { print_usage(); exit (0); } my $dab; sub dab() { $dab = PVE::DAB->new() if !$dab; return $dab; } dab->writelog ("dab: $cmdline\n"); $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; }; eval { if ($cmd eq 'init') { die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; dab->initialize(); } elsif ($cmd eq 'bootstrap') { my $opts = {}; if (!GetOptions ($opts, 'exim', 'minimal', 'include=s', 'exclude=s')) { fatal_usage(); } die "command 'bootstrap' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->ve_init(); $dab->bootstrap ($opts); } elsif ($cmd eq 'finalize') { my $opts = {}; if (!GetOptions ($opts, 'keepmycnf', 'compressor=s')) { fatal_usage(); } die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->finalize($opts); } elsif ($cmd eq 'veid') { die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; print $dab->{veid} . "\n"; } elsif ($cmd eq 'basedir') { die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; print $dab->{rootfs} . "\n"; } elsif ($cmd eq 'packagefile') { die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; print "$dab->{targetname}.tar.gz\n"; } elsif ($cmd eq 'list') { my $verbose; if (!GetOptions ('verbose' =>\$verbose)) { fatal_usage(); } die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; my $instpkgs = $dab->read_installed (); foreach my $pkg (sort keys %$instpkgs) { if ($verbose) { my $version = $instpkgs->{$pkg}->{version}; print "$pkg $version\n"; } else { print "$pkg\n"; } } } elsif ($cmd eq 'task') { my $task = shift @ARGV; if (!$task) { fatal_usage("no task specified"); } my $opts = {}; if ($task eq 'mysql') { if (!GetOptions ($opts, 'password=s', 'start')) { fatal_usage(); } die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->task_mysql ($opts); } elsif ($task eq 'postgres') { if (!GetOptions ($opts, 'version=s', 'start')) { fatal_usage(); } die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->task_postgres ($opts); } elsif ($task eq 'php') { if (!GetOptions ($opts, 'memlimit=i')) { fatal_usage(); } die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->task_php ($opts); } else { fatal_usage("unknown task '$task'"); } } elsif ($cmd eq 'install' || $cmd eq 'unpack') { my $required; foreach my $arg (@ARGV) { if ($arg =~ m/\.pkglist$/) { open (TMP, $arg) || die "cant open package list '$arg' - $!"; while (defined (my $line = )) { chomp $line; next if $line =~ m/^\s*$/; next if $line =~ m/\#/; if ($line =~ m/^\s*(\S+)\s*$/) { push @$required, $1; } else { die "invalid package name in '$arg' - $line\n"; } } } else { push @$required, $arg; } close (TMP); } $dab->install ($required, $cmd eq 'unpack'); } elsif ($cmd eq 'exec') { $dab->ve_exec (@ARGV); } elsif ($cmd eq 'enter') { die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->enter(); } elsif ($cmd eq 'clean') { die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->cleanup(0); } elsif ($cmd eq 'dist-clean') { die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0; $dab->cleanup(1); } else { fatal_usage("invalid command '$cmd'"); } }; if (my $err = $@) { $dab->logmsg ($@); die ($@); } exit 0; __END__ =head1 NAME dab - Debian LXC Appliance Builder =head1 SYNOPSIS =over =item B I I<[OPTIONS]> =item B Downloads the package descriptions form the repository. Also truncates the C. =item B Bootstrap a debian system and allocate a temporary container (we use IDs 90000 and above). =over =item I<--exim> Use exim as MTA (dab selects postfix by default) =item I<--minimal> Do not auto-select packages with standard priority for installation. =item I<--include A comma-separated list of packages to always include in bootstrap. Note that no transitive dependency resolution is done, you may need to specify those yourself. =item I<--exclude A comma-separated list of packages to exlcude in bootstrap. Note that no transitive dependency resolution is done for others to get excluded, you may need to specify those yourself. =back =item B Print used container ID. =item B Print container private directory. =item B Print the appliance file name. =item B> Install one or more packages. I can also refer to a file named C which contains a list of packages. All dependencies are automatically installed. =item B> Unpack one or more packages. I can also refer to a file named C which contains a list of packages. All dependencies are automatically unpacked. =item B I> Executes command CMD inside the container. =item B Calls C - this is for debugging only. =item B Install a mysql database server. During appliance generation we use C as mysql root password (also stored in /root/.my.cnf). =over =item I<--password=XXX> Specify the mysql root password. The special value C can be use to generate a random root password when the appliance is started first time (stored in /root/.my.cnf) =item I<--start> Start the mysql server (if you want to execute sql commands during appliance generation). =back =item B Install a postgres database server. =over =item I<--version=XXX> Select Postgres version. Posible values are C<7.4>, C<8.1> and C<8.3> (depends on the selected suite). =item I<--start> Start the postgres server (if you want to execute sql commands during appliance generation). =back =item B Install php5. =over =item I<--memlimit=i> Set the php I. =back =item B Cleanup everything inside the container and generate the final appliance package. =over =item I<--keepmycnf> Do not delete file C (mysql). =item I<--compressor ]> Select the compressor to process the rootfs archive with. C is a good choice to make the archive also available on older systems, but using C or even C results in a higher compression ration while keeping decompression very fast and highly efficient. Note that C uses the highest compression ratio without any decompression performance hit possible, it will use as many threads as there are onlince CPU threads and may thus increase the system load significantly for tens of seconds up to minutes. =back =item B List installed packages. =over =item I<--verbose> Also print package versions. =back =item B Remove all temporary files and destroy the container. =item B Like clean, but also removes the package cache (except when you specified your own cache directory in the config file) =back =head1 DESCRIPTION dab is a script to automate the creation of LXC appliances. It is basically a rewrite of debootstrap in perl, but uses LXC instead of chroot and generates LXC templates. Another difference is that it supports multi-stage building of templates. That way you can execute arbitrary scripts between to accomplish what you want. Furthermore some common tasks are fully automated, like setting up a database server (mysql or postgres). To accomplish minimal template creation time, packages are cached to a local directory, so you do not need a local debian mirror (although this would speed up the first run). See http://pve.proxmox.com/wiki/Debian_Appliance_Builder for examples. This script need to be run as root, so it is not recommended to start it on a production machine with running containers. So many people run Proxmox VE inside a KVM or VMWare 64bit virtual machine to build appliances. All generated templates includes an appliance description file. Those can be used to build appliance repositories. =head1 CONFIGURATION Configuration is read from the file C inside the current working directory. The files contains key value pairs, separated by colon. =over 2 =item B I The Debian or Ubuntu suite. =item B I Defines a source location. By default we use the following for debian: Source: http://ftp.debian.org/debian SUITE main contrib Source: http://security.debian.org SUITE/updates main contrib Note: SUITE is a variable and will be substituted. There are also reasonable defaults for Ubuntu. If you do not specify any source the defaults are used. =item B I Debian like package dependencies. This can be used to make sure that speific package versions are available. =item B: I Allows you to specify the directory where downloaded packages are cached. =item B I => I Define a mirror location. for example: Mirror: http://ftp.debian.org/debian => ftp://mirror/debian =back All other settings in this files are also included into the appliance description file. =over 2 =item B I The name of the appliance. Appliance names must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character. =item B I Target architecture. =item B I The version number of an appliance. =item: B I
This field specifies an application area into which the appliance has been classified. Currently we use the following section names: system, mail =item B I> The appliance maintainer's name and email address. The name should come first, then the email address inside angle brackets <> (in RFC822 format). =item B I Link to web page containing more informations about this appliance. =item B I extended description over several lines (indended by space) may follow. =back =head1 Appliance description file All generated templates includes an appliance description file called /etc/appliance.info this is the first file inside the tar archive. That way it can be easily exctracted without scanning the whole archive. The file itself contains informations like a debian C file. It can be used to build appliance repositories. Most fields are directly copied from the configuration file C. Additionally there are some auto-generated files: =over =item B I It gives the total amount of disk space required to install the named appliance. The disk space is represented in megabytes as a simple decimal number. =item B I This is always C. =item B I<[debian-4.0|debian-5.0|ubuntu-8.0]> Operation system. =back Appliance repositories usually add additional fields: =over =item B I MD5 checksum =back =head1 FILES The following files are created inside your working directory: dab.conf appliance configuration file logfile contains installation logs .veid stores the used container ID cache/* default package cache directory info/* package information cache =head1 AUTHOR Dietmar Maurer Thomas Lamprecht Many thanks to Proxmox Server Solutions (www.proxmox.com) for sponsoring this work. =head1 COPYRIGHT AND DISCLAIMER Copyright (C) 2007-2021 Proxmox Server Solutions GmbH Copyright: dab is under GNU GPL, the GNU General Public License. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 dated June, 1991. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.