From: Dietmar Maurer Date: Tue, 10 Apr 2012 08:50:58 +0000 (+0200) Subject: initial import from internal svn repository X-Git-Url: https://git.proxmox.com/?a=commitdiff_plain;h=8ab34b8799dba20d3a832803d1ab2de0ad19baa0;p=dab.git initial import from internal svn repository --- 8ab34b8799dba20d3a832803d1ab2de0ad19baa0 diff --git a/DAB.pm b/DAB.pm new file mode 100644 index 0000000..4afa7e6 --- /dev/null +++ b/DAB.pm @@ -0,0 +1,1643 @@ +package PVE::DAB; + +use strict; +use warnings; +use IO::File; +use File::Path; +use File::Basename; +use IO::Select; +use IPC::Open2; +use IPC::Open3; +use POSIX qw (LONG_MAX); + +# fixme: lock container ? + +my $dablibdir = "/usr/lib/dab"; +my $devicetar = "$dablibdir/devices.tar.gz"; +my $default_env = "$dablibdir/scripts/defenv"; +my $fake_init = "$dablibdir/scripts/init.pl"; +my $script_ssh_init = "$dablibdir/scripts/ssh_gen_host_keys"; +my $script_mysql_randompw = "$dablibdir/scripts/mysql_randompw"; +my $script_init_urandom = "$dablibdir/scripts/init_urandom"; + +my $postfix_main_cf = <logmsg ("download: $url\n"); + my $tmpfn = "$path.tmp$$"; + eval { + $self->run_command ("wget -q '$url' -O '$tmpfn'"); + }; + + my $err = $@; + if ($err) { + unlink $tmpfn; + die $err; + } + + rename ($tmpfn, $path); +} + +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 read_file { + my ($file) = @_; + + die "no filename" if !$file; + + my $fh = IO::File->new ($file) || + die "unable to open file '$file'"; + + local $/; # slurp mode + + my $data = <$fh>; + + $fh->close; + + return $data; +} + +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 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 logmsg { + my $self = shift; + print STDERR @_; + $self->writelog (@_); +} + +sub writelog { + my $self = shift; + my $fd = $self->{logfd}; + print $fd @_; +} + +sub __sample_config { + my ($self, $mem) = @_; + + my $max = LONG_MAX; + my $nolimit = "\"$max:$max\""; + + my $defaults = { + 128 => {}, + 256 => {}, + 512 => {}, + 1024 => {}, + 2048 => {}, + }; + + die "unknown memory size" if !defined ($defaults->{$mem}); + + my $data = ''; + + $data .= "# DAB default config for ${mem}MB RAM\n\n"; + + $data .= "ONBOOT=\"no\"\n"; + + $data .= "\n# Primary parameters\n"; + $data .= "NUMPROC=\"1024:1024\"\n"; + $data .= "NUMTCPSOCK=$nolimit\n"; + $data .= "NUMOTHERSOCK=$nolimit\n"; + + my $vmguarpages = int ($mem*1024/4); + $data .= "VMGUARPAGES=\"$vmguarpages:$max\"\n"; + + $data .= "\n# Secondary parameters\n"; + + $data .= "KMEMSIZE=$nolimit\n"; + + my $privmax = int ($vmguarpages*1.1); + $privmax = $vmguarpages + 12500 if ($privmax-$vmguarpages) > 12500; + $data .= "OOMGUARPAGES=\"$vmguarpages:$max\"\n"; + $data .= "PRIVVMPAGES=\"$vmguarpages:$privmax\"\n"; + + $data .= "TCPSNDBUF=$nolimit\n"; + $data .= "TCPRCVBUF=$nolimit\n"; + $data .= "OTHERSOCKBUF=$nolimit\n"; + $data .= "DGRAMRCVBUF=$nolimit\n"; + + $data .= "\n# Auxiliary parameters\n"; + $data .= "NUMFILE=$nolimit\n"; + $data .= "NUMFLOCK=$nolimit\n"; + $data .= "NUMPTY=\"255:255\"\n"; + $data .= "NUMSIGINFO=\"1024:1024\"\n"; + $data .= "DCACHESIZE=$nolimit\n"; + $data .= "LOCKEDPAGES=$nolimit\n"; + $data .= "SHMPAGES=$nolimit\n"; + $data .= "NUMIPTENT=$nolimit\n"; + $data .= "PHYSPAGES=\"0:$max\"\n"; + + $data .= "\n# Disk quota parameters\n"; + $data .= "DISK_QUOTA=\"no\"\n"; + $data .= "DISKSPACE=$nolimit\n"; + $data .= "DISKINODES=$nolimit\n"; + $data .= "QUOTATIME=\"0\"\n"; + $data .= "QUOTAUGIDLIMIT=\"0\"\n"; + + $data .= "\n# CPU fair sheduler parameter\n"; + $data .= "CPUUNITS=\"1000\"\n\n"; + + $data .= "\n# Template parameter\n"; + $data .= "OSTEMPLATE=\"$self->{targetname}\"\n"; + $data .= "HOSTNAME=\"localhost\"\n"; + + return $data; +} + +sub __allocate_ve { + my ($self) = @_; + + my $cid; + if (my $fd = IO::File->new (".veid")) { + $cid = <$fd>; + chomp $cid; + close ($fd); + } + + my $cfgdir = "/etc/vz/conf"; + + if ($cid) { + $self->{veid} = $cid; + $self->{veconffile} = "$cfgdir/$cid.conf"; + return $cid; + } + + my $cdata = $self->__sample_config (1024); + + my $veid; + my $startid = 90000; + for (my $id = $startid; $id < ($startid + 100); $id++) { + + my $tmpfn = "$cfgdir/$id.conf.tmp$$"; + my $target = "$cfgdir/$id.conf"; + + next if -f $target; + + my $fh = IO::File->new ($target, O_WRONLY | O_CREAT | O_EXCL, 0644); + + next if !$fh; + + print $fh $cdata; + close ($fh); + $veid = $id; + last; + } + + die "unable to allocate VE\n" if !$veid; + + my $fd = IO::File->new (">.veid") || + die "unable to write '.veid'\n"; + print $fd "$veid\n"; + close ($fd); + + $self->logmsg ("allocated VE $veid\n"); + + $self->{veid} = $veid; + $self->{veconffile} = "$cfgdir/$veid.conf"; + + return $veid; +} + +sub new { + my ($class, $config) = @_; + + $class = ref ($class) || $class; + + my $self = {}; + + $config = read_config ('dab.conf') if !$config; + + $self->{config} = $config; + + bless $self, $class; + + $self->{logfile} = "logfile"; + $self->{logfd} = IO::File->new (">>$self->{logfile}") || + die "unable to open log file"; + + my $arch = $config->{architecture}; + die "no 'architecture' specified\n" if !$arch; + + die "unsupported architecture '$arch'\n" + if $arch !~ m/^(i386|amd64)$/; + + my $suite = $config->{suite} || die "no 'suite' specified\n"; + if ($suite eq 'squeeze') { + $config->{ostype} = "debian-6.0"; + } elsif ($suite eq 'lenny') { + $config->{ostype} = "debian-5.0"; + } elsif ($suite eq 'etch') { + $config->{ostype} = "debian-4.0"; + } elsif ($suite eq 'hardy') { + $config->{ostype} = "ubuntu-8.04"; + } elsif ($suite eq 'intrepid') { + $config->{ostype} = "ubuntu-8.10"; + } elsif ($suite eq 'jaunty') { + $config->{ostype} = "ubuntu-9.04"; + } else { + die "unsupported debian suite '$suite'\n"; + } + + my $name = $config->{name} || die "no 'name' specified\n"; + + $name =~ m/^[a-z][0-9a-z\-\*\.]+$/ || + die "illegal characters in name '$name'\n"; + + 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}; + + if ($name =~ m/^$config->{ostype}/) { + $self->{targetname} = "${name}_${version}_$config->{architecture}"; + } else { + $self->{targetname} = "$config->{ostype}-${name}_" . + "${version}_$config->{architecture}"; + } + + if (!$config->{source}) { + if ($suite eq 'etch' || $suite eq 'lenny' || $suite eq 'squeeze') { + push @{$config->{source}}, "http://ftp.debian.org/debian SUITE main contrib"; + push @{$config->{source}}, "http://ftp.debian.org/debian SUITE-updates main contrib" + if ($suite eq 'squeeze'); + push @{$config->{source}}, "http://security.debian.org SUITE/updates main contrib"; + } elsif ($suite eq 'hardy' || $suite eq 'intrepid' || $suite eq 'jaunty') { + my $comp = "main restricted universe multiverse"; + push @{$config->{source}}, "http://archive.ubuntu.com/ubuntu SUITE $comp"; + push @{$config->{source}}, "http://archive.ubuntu.com/ubuntu SUITE-updates $comp"; + push @{$config->{source}}, "http://archive.ubuntu.com/ubuntu SUITE-security $comp"; + } + } + + my $sources = undef; + + foreach my $s (@{$config->{source}}) { + if ($s =~ m@^\s*((http|ftp)://\S+)\s+(\S+)((\s+(\S+))+)$@) { + my ($url, $su, $components) = ($1, $3, $4); + $su =~ s/SUITE/$suite/; + $components =~ s/^\s+//; + $components =~ s/\s+$//; + my $ca; + foreach my $co (split (/\s+/, $components)) { + push @$ca, $co; + } + $ca = ['main'] if !$ca; + + push @$sources, { + source => $url, + comp => $ca, + suite => $su, + }; + } else { + die "syntax error in source spezification '$s'\n"; + } + } + + foreach my $m (@{$config->{mirror}}) { + if ($m =~ m@^\s*((http|ftp)://\S+)\s*=>\s*((http|ftp)://\S+)\s*$@) { + my ($ms, $md) = ($1, $3); + my $found; + foreach my $ss (@$sources) { + if ($ss->{source} eq $ms) { + $found = 1; + $ss->{mirror} = $md; + last; + } + } + die "unusable mirror $ms\n" if !$found; + } else { + die "syntax error in mirror spezification '$m'\n"; + } + } + + $self->{sources} = $sources; + + $self->{infodir} = "info"; + + $self->__allocate_ve (); + + $self->{cachedir} = ($config->{cachedir} || 'cache') . "/$suite";; + + my $incl = [qw (less ssh openssh-server logrotate)]; + + my $excl = [qw (modutils reiserfsprogs ppp pppconfig pppoe + pppoeconf nfs-common mtools ntp)]; + + # ubuntu has too many dependencies on udev, so + # we cannot exclude it (instead we disable udevd) + if ($suite eq 'hardy') { + push @$excl, qw(kbd); + push @$excl, qw(apparmor apparmor-utils ntfs-3g + friendly-recovery); + } elsif($suite eq 'intrepid' || $suite eq 'jaunty') { + push @$excl, qw(apparmor apparmor-utils libapparmor1 libapparmor-perl + libntfs-3g28 ntfs-3g friendly-recovery); + } else { + push @$excl, qw(udev module-init-tools pciutils hdparm + memtest86+ parted); + } + + $self->{incl} = $incl; + $self->{excl} = $excl; + + return $self; +} + +sub initialize { + my ($self) = @_; + + my $infodir = $self->{infodir}; + my $arch = $self->{config}->{architecture}; + + rmtree $infodir; + mkpath $infodir; + + # truncate log + my $logfd = $self->{logfd} = IO::File->new (">$self->{logfile}") || + die "unable to open log file"; + + foreach my $ss (@{$self->{sources}}) { + my $src = $ss->{mirror} || $ss->{source}; + my $path = "dists/$ss->{suite}/Release"; + my $url = "$src/$path"; + my $target = __url_to_filename ("$ss->{source}/$path"); + eval { + $self->download ($url, "$infodir/$target"); + $self->download ("$url.gpg", "$infodir/$target.gpg"); + # fixme: impl. verify (needs --keyring option) + }; + if (my $err = $@) { + print $logfd $@; + warn "Release info ignored\n"; + }; + foreach my $comp (@{$ss->{comp}}) { + $path = "dists/$ss->{suite}/$comp/binary-$arch/Packages.gz"; + $target = "$infodir/" . __url_to_filename ("$ss->{source}/$path"); + my $pkgsrc = "$src/$path"; + $self->download ($pkgsrc, $target); + $self->run_command ("gzip -d '$target'"); + } + } +} + +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: openvz\n"; + $data .= "OS: $config->{ostype}\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, $opts) = @_; + + my $suite = $self->{config}->{suite}; + my $infodir = $self->{infodir}; + my $arch = $self->{config}->{architecture}; + + my $instpkgs = $self->read_installed (); + my $pkginfo = $self->pkginfo(); + my $veid = $self->{veid}; + my $rootdir = $self->vz_root_dir(); + + my $vestat = $self->ve_status(); + die "ve not running - unable to finalize\n" if !$vestat->{running}; + + # cleanup mysqld + if (-f "$rootdir/etc/init.d/mysql") { + $self->ve_command ("/etc/init.d/mysql stop"); + } + + if (!($opts->{keepmycnf} || (-f "$rootdir/etc/init.d/mysql_randompw"))) { + unlink "$rootdir/root/.my.cnf"; + } + + if ($suite eq 'etch') { + # enable apache2 startup + if ($instpkgs->{apache2}) { + write_file ("NO_START=0\n", "$rootdir/etc/default/apache2"); + } else { + unlink "$rootdir/etc/default/apache2"; + } + } + $self->logmsg ("cleanup package status\n"); + # prevent auto selection of all standard, required or important + # packages which are not installed + foreach my $pkg (keys %$pkginfo) { + my $pri = $pkginfo->{$pkg}->{priority}; + if ($pri && ($pri eq 'required' || $pri eq 'important' + || $pri eq 'standard')) { + if (!$instpkgs->{$pkg}) { + $self->ve_dpkg_set_selection ($pkg, 'purge'); + } + } + } + + $self->ve_command ("apt-get clean"); + + $self->logmsg ("update available package list\n"); + + $self->ve_command ("dpkg --clear-avail"); + foreach my $ss (@{$self->{sources}}) { + my $relsrc = __url_to_filename ("$ss->{source}/dists/$ss->{suite}/Release"); + if (-f "$infodir/$relsrc" && -f "$infodir/$relsrc.gpg") { + $self->run_command ("cp '$infodir/$relsrc' '$rootdir/var/lib/apt/lists/$relsrc'"); + $self->run_command ("cp '$infodir/$relsrc.gpg' '$rootdir/var/lib/apt/lists/$relsrc.gpg'"); + } + foreach my $comp (@{$ss->{comp}}) { + my $src = __url_to_filename ("$ss->{source}/dists/$ss->{suite}/" . + "$comp/binary-$arch/Packages"); + my $target = "/var/lib/apt/lists/$src"; + $self->run_command ("cp '$infodir/$src' '$rootdir/$target'"); + $self->ve_command ("dpkg --merge-avail '$target'"); + } + } + + # set dselect default method + write_file ("apt apt\n", "$rootdir/var/lib/dpkg/cmethopt"); + + $self->ve_divert_remove ("/usr/sbin/policy-rc.d"); + + $self->ve_divert_remove ("/sbin/start-stop-daemon"); + + $self->ve_divert_remove ("/sbin/init"); + + # finally stop the VE + $self->run_command ("vzctl stop $veid --fast"); + $rootdir = $self->vz_priv_dir(); + + unlink "$rootdir/sbin/defenv"; + + unlink <$rootdir/root/dead.letter*>; + + unlink "$rootdir/var/log/init.log"; + + unlink "$rootdir/aquota.group"; + + unlink "$rootdir/aquota.user"; + + write_file ("", "$rootdir/var/log/syslog"); + + $self->logmsg ("detecting final size: "); + + 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 read_installed { + my ($self) = @_; + + my $rootdir = $self->vz_priv_dir(); + + my $pkgfilelist = "$rootdir/var/lib/dpkg/status"; + local $/ = ''; + open (PKGLST, "<$pkgfilelist") || + die "unable to open '$pkgfilelist'"; + + my $pkglist = {}; + + while (my $rec = ) { + chomp $rec; + $rec =~ s/\n\s+/ /g; + $rec .= "\n"; + my $res = {}; + + while ($rec =~ s/^([^:]+):\s+(.*)\s*\n//) { + $res->{lc $1} = $2; + } + + my $pkg = $res->{'package'}; + if (my $status = $res->{status}) { + my @sa = split (/\s+/, $status); + my $stat = $sa[0]; + if ($stat && ($stat ne 'purge')) { + $pkglist->{$pkg} = $res; + } + } + } + + close (PKGLST); + + return $pkglist; +} + +sub vz_root_dir { + my ($self) = @_; + + my $veid = $self->{veid}; + + return "/var/lib/vz/root/$veid"; +} + +sub vz_priv_dir { + my ($self) = @_; + + my $veid = $self->{veid}; + + return "/var/lib/vz/private/$veid"; +} + +sub ve_status { + my ($self) = @_; + + my $veid = $self->{veid}; + + my $res = $self->run_command ("vzctl status $veid", undef, 1); + chomp $res; + + if ($res =~ m/^CTID\s+$veid\s+(exist|deleted)\s+(mounted|unmounted)\s+(running|down)$/) { + return { + exist => $1 eq 'exist', + mounted => $2 eq 'mounted', + running => $3 eq 'running', + }; + } else { + die "unable to parse ve status"; + } +} + +sub ve_command { + my ($self, $cmd, $input) = @_; + + my $veid = $self->{veid}; + + if (ref ($cmd) eq 'ARRAY') { + unshift @$cmd, 'vzctl', 'exec2', $veid, 'defenv'; + $self->run_command ($cmd, $input); + } else { + $self->run_command ("vzctl exec2 $veid defenv $cmd", $input); + } +} + +# like ve_command, but pipes stdin correctly +sub ve_exec { + my ($self, @cmd) = @_; + + my $veid = $self->{veid}; + + my $reader; + my $pid = open2($reader, "<&STDIN", 'vzctl', 'exec2', $veid, + 'defenv', @cmd) || 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 ve_divert_add { + my ($self, $filename) = @_; + + $self->ve_command ("dpkg-divert --add --divert '$filename.distrib' " . + "--rename '$filename'"); +} +sub ve_divert_remove { + my ($self, $filename) = @_; + + my $rootdir = $self->vz_root_dir(); + + unlink "$rootdir/$filename"; + $self->ve_command ("dpkg-divert --remove --rename '$filename'"); +} + +sub ve_debconfig_set { + my ($self, $dcdata) = @_; + + my $rootdir = $self->vz_root_dir(); + my $cfgfile = "/tmp/debconf.txt"; + write_file ($dcdata, "$rootdir/$cfgfile"); + $self->ve_command ("debconf-set-selections $cfgfile"); + unlink "$rootdir/$cfgfile"; +} + +sub ve_dpkg_set_selection { + my ($self, $pkg, $status) = @_; + + $self->ve_command ("dpkg --set-selections", "$pkg $status"); +} + +sub ve_dpkg { + my ($self, $cmd, @pkglist) = @_; + + return if !scalar (@pkglist); + + my $pkginfo = $self->pkginfo(); + + my $rootdir = $self->vz_root_dir(); + my $cachedir = $self->{cachedir}; + + my @files; + + foreach my $pkg (@pkglist) { + my $filename = $self->getpkgfile ($pkg); + $self->run_command ("cp '$cachedir/$filename' '$rootdir/$filename'"); + push @files, "/$filename"; + $self->logmsg ("$cmd: $pkg\n"); + } + + my $fl = join (' ', @files); + + if ($cmd eq 'install') { + $self->ve_command ("dpkg --force-depends --force-confold --install $fl"); + } elsif ($cmd eq 'unpack') { + $self->ve_command ("dpkg --force-depends --unpack $fl"); + } else { + die "internal error"; + } + + foreach my $fn (@files) { unlink "$rootdir$fn"; } +} + +sub ve_destroy { + my ($self) = @_; + + my $veid = $self->{veid}; # fixme + + my $vestat = $self->ve_status(); + if ($vestat->{running}) { + $self->run_command ("vzctl stop $veid --fast"); + } elsif ($vestat->{mounted}) { + $self->run_command ("vzctl umount $veid"); + } + if ($vestat->{exist}) { + $self->run_command ("vzctl destroy $veid"); + } else { + unlink $self->{veconffile}; + } +} + +sub ve_init { + my ($self) = @_; + + my $root = $self->vz_root_dir(); + my $priv = $self->vz_priv_dir(); + + my $veid = $self->{veid}; # fixme + + $self->logmsg ("initialize VE $veid\n"); + + while (1) { + my $vestat = $self->ve_status(); + if ($vestat->{running}) { + $self->run_command ("vzctl stop $veid --fast"); + } elsif ($vestat->{mounted}) { + $self->run_command ("vzctl umount $veid"); + } else { + last; + } + sleep (1); + } + + rmtree $root; + rmtree $priv; + mkpath $root; + mkpath $priv; + + $self->run_command ("vzctl mount $veid"); +} + +sub __deb_version_cmp { + my ($cur, $op, $new) = @_; + + if (system("dpkg", "--compare-versions", $cur, $op, $new) == 0) { + return 1; + } + + return 0; +} + +sub __parse_packages { + my ($pkginfo, $filename, $src) = @_; + + local $/ = ''; + open (PKGLST, "<$filename") || + die "unable to open '$filename'"; + + while (my $rec = ) { + $rec =~ s/\n\s+/ /g; + chomp $rec; + $rec .= "\n"; + + my $res = {}; + + while ($rec =~ s/^([^:]+):\s+(.*)\s*\n//) { + $res->{lc $1} = $2; + } + + my $pkg = $res->{'package'}; + if ($pkg && $res->{'filename'}) { + my $cur; + if (my $info = $pkginfo->{$pkg}) { + $cur = $info->{version}; + } + my $new = $res->{version}; + if (!$cur || __deb_version_cmp ($cur, 'lt', $new)) { + if ($src) { + $res->{url} = "$src/$res->{'filename'}"; + } else { + die "no url for package '$pkg'" if !$res->{url}; + } + $pkginfo->{$pkg} = $res; + } + } + } + + close (PKGLST); +} + +sub pkginfo { + my ($self) = @_; + + return $self->{pkginfo} if $self->{pkginfo}; + + my $infodir = $self->{infodir}; + my $arch = $self->{config}->{architecture}; + + my $availfn = "$infodir/available"; + + my $pkginfo = {}; + my $pkgcount = 0; + + # reading 'available' is faster, because it only contains latest version + # (no need to do slow version compares) + if (-f $availfn) { + __parse_packages ($pkginfo, $availfn); + $self->{pkginfo} = $pkginfo; + return $pkginfo; + } + + $self->logmsg ("generating available package list\n"); + + foreach my $ss (@{$self->{sources}}) { + foreach my $comp (@{$ss->{comp}}) { + my $url = "$ss->{source}/dists/$ss->{suite}/$comp/binary-$arch/Packages"; + my $pkgfilelist = "$infodir/" . __url_to_filename ($url); + + my $src = $ss->{mirror} || $ss->{source}; + + __parse_packages ($pkginfo, $pkgfilelist, $src); + } + } + + if (my $dep = $self->{config}->{depends}) { + foreach my $d (split (/,/, $dep)) { + if ($d =~ m/^\s*(\S+)\s*(\((\S+)\s+(\S+)\)\s*)?$/) { + my ($pkg, $op, $rver) = ($1, $3, $4); + $self->logmsg ("checking dependencies: $d\n"); + my $info = $pkginfo->{$pkg}; + die "package '$pkg' not available\n" if !$info; + if ($op) { + my $cver = $info->{version}; + if (!__deb_version_cmp ($cver, $op, $rver)) { + die "detected wrong version '$cver'\n"; + } + } + } else { + die "syntax error in depends field"; + } + } + } + + $self->{pkginfo} = $pkginfo; + + my $tmpfn = "$availfn.tmp$$"; + my $fd = IO::File->new (">$tmpfn"); + foreach my $pkg (sort keys %$pkginfo) { + my $info = $pkginfo->{$pkg}; + print $fd "package: $pkg\n"; + foreach my $k (sort keys %$info) { + next if $k eq 'description'; + next if $k eq 'package'; + my $v = $info->{$k}; + print $fd "$k: $v\n" if $v; + } + print $fd "description: $info->{description}\n" if $info->{description}; + print $fd "\n"; + } + close ($fd); + + rename ($tmpfn, $availfn); + + return $pkginfo; +} + +sub __record_provides { + my ($pkginfo, $closure, $list, $skipself) = @_; + + foreach my $pname (@$list) { + my $info = $pkginfo->{$pname}; + # fixme: if someone install packages directly using dpkg, there + # is no entry in 'available', only in 'status'. In that case, we + # should extract info from $instpkgs + if (!$info) { + warn "hint: ignoring provides for '$pname' - package not in 'available' list.\n"; + next; + } + if (my $prov = $info->{provides}) { + my @pl = split (',', $prov); + foreach my $p (@pl) { + $p =~ m/\s*(\S+)/; + if (!($skipself && (grep { $1 eq $_ } @$list))) { + $closure->{$1} = 1; + } + } + } + $closure->{$pname} = 1 if !$skipself; + } +} + +sub closure { + my ($self, $closure, $list) = @_; + + my $pkginfo = $self->pkginfo(); + + # first, record provided packages + __record_provides ($pkginfo, $closure, $list, 1); + + my $pkgs = {}; + + # then resolve dependencies + foreach my $pname (@$list) { + __closure_single ($pkginfo, $closure, $pkgs, $pname, $self->{excl}); + } + + return [ keys %$pkgs ]; +} + +sub __closure_single { + my ($pkginfo, $closure, $pkgs, $pname, $excl) = @_; + + $pname =~ s/^\s+//; + $pname =~ s/\s+$//; + + return if $closure->{$pname}; + + my $info = $pkginfo->{$pname} || die "no such package '$pname'"; + + my $dep = $info->{depends}; + my $predep = $info->{'pre-depends'}; + + my $size = $info->{size}; + my $url = $info->{url}; + + $url || die "$pname: no url for package '$pname'"; + + $pkgs->{$pname} = 1; + + __record_provides ($pkginfo, $closure, [$pname]) if $info->{provides}; + + $closure->{$pname} = 1; + + #print "$url\n"; + + my @l; + + push @l, split (/,/, $predep) if $predep; + push @l, split (/,/, $dep) if $dep; + + DEPEND: foreach my $p (@l) { + my @l1 = split (/\|/, $p); + foreach my $p1 (@l1) { + if ($p1 =~ m/^\s*(\S+).*/) { + #printf (STDERR "$pname: $p --> $1\n"); + if ($closure->{$1}) { + next DEPEND; # dependency already met + } + } + } + # search for non-excluded alternative + my $found; + foreach my $p1 (@l1) { + if ($p1 =~ m/^\s*(\S+).*/) { + next if grep { $1 eq $_ } @$excl; + $found = $1; + last; + } + } + die "package '$pname' depends on exclusion '$p'\n" if !$found; + + #printf (STDERR "$pname: $p --> $found\n"); + + __closure_single ($pkginfo, $closure, $pkgs, $found, $excl); + } +} + +sub cache_packages { + my ($self, $pkglist) = @_; + + foreach my $pkg (@$pkglist) { + $self->getpkgfile ($pkg); + } +} + +sub getpkgfile { + my ($self, $pkg) = @_; + + my $pkginfo = $self->pkginfo(); + my $info = $pkginfo->{$pkg} || die "no such package '$pkg'"; + my $cachedir = $self->{cachedir}; + + my $url = $info->{url}; + + my $filename; + if ($url =~ m|/([^/]+.deb)$|) { + $filename = $1; + } else { + die "internal error"; + } + + return $filename if -f "$cachedir/$filename"; + + mkpath $cachedir; + + $self->download ($url, "$cachedir/$filename"); + + return $filename; +} + +sub install_init_script { + my ($self, $script, $runlevel, $prio) = @_; + + my $suite = $self->{config}->{suite}; + my $rootdir = $self->vz_root_dir(); + + my $base = basename ($script); + my $target = "$rootdir/etc/init.d/$base"; + + $self->run_command ("install -m 0755 '$script' '$target'"); + if ($suite eq 'etch' || $suite eq 'lenny') { + $self->ve_command ("update-rc.d $base start $prio $runlevel ."); + } else { + $self->ve_command ("insserv $base"); + } + + return $target; +} + +sub bootstrap { + my ($self, $opts) = @_; + + my $pkginfo = $self->pkginfo(); + my $veid = $self->{veid}; + my $suite = $self->{config}->{suite}; + + my $important = [ @{$self->{incl}} ]; + my $required; + my $standard; + + my $mta = $opts->{exim} ? 'exim' : 'postfix'; + + if ($mta eq 'postfix') { + push @$important, "postfix"; + } + + foreach my $p (keys %$pkginfo) { + next if grep { $p eq $_ } @{$self->{excl}}; + my $pri = $pkginfo->{$p}->{priority}; + next if !$pri; + next if $mta ne 'exim' && $p =~ m/exim/; + next if $p =~ m/(selinux|semanage|policycoreutils)/; + + push @$required, $p if $pri eq 'required'; + push @$important, $p if $pri eq 'important'; + push @$standard, $p if $pri eq 'standard' && !$opts->{minimal}; + } + + my $closure = {}; + $required = $self->closure ($closure, $required); + $important = $self->closure ($closure, $important); + + if (!$opts->{minimal}) { + push @$standard, 'xbase-clients'; + $standard = $self->closure ($closure, $standard); + } + + # test if we have all 'ubuntu-minimal' and 'ubuntu-standard' packages + # except those explicitly excluded + if ($suite eq 'hardy' || $suite eq 'intrepid' || $suite eq 'jaunty') { + my $mdeps = $pkginfo->{'ubuntu-minimal'}->{depends}; + foreach my $d (split (/,/, $mdeps)) { + if ($d =~ m/^\s*(\S+)$/) { + my $pkg = $1; + next if $closure->{$pkg}; + next if grep { $pkg eq $_ } @{$self->{excl}}; + die "missing ubuntu-minimal package '$pkg'\n"; + } + } + if (!$opts->{minimal}) { + $mdeps = $pkginfo->{'ubuntu-standard'}->{depends}; + foreach my $d (split (/,/, $mdeps)) { + if ($d =~ m/^\s*(\S+)$/) { + my $pkg = $1; + next if $closure->{$pkg}; + next if grep { $pkg eq $_ } @{$self->{excl}}; + die "missing ubuntu-standard package '$pkg'\n"; + } + } + } + } + + # download/cache all files first + $self->cache_packages ($required); + $self->cache_packages ($important); + $self->cache_packages ($standard); + + my $rootdir = $self->vz_priv_dir(); + + # extract required packages first + $self->logmsg ("create basic environment\n"); + foreach my $p (@$required) { + my $filename = $self->getpkgfile ($p); + $self->run_command ("ar -p '$self->{cachedir}/$filename' data.tar.gz | zcat | tar -C '$rootdir' -xf -"); + } + + # fake dpkg status + my $data = "Package: dpkg\n" . + "Version: $pkginfo->{dpkg}->{version}\n" . + "Status: install ok installed\n"; + + write_file ($data, "$rootdir/var/lib/dpkg/status"); + write_file ("", "$rootdir/var/lib/dpkg/info/dpkg.list"); + write_file ("", "$rootdir/var/lib/dpkg/available"); + + $data = ''; + foreach my $ss (@{$self->{sources}}) { + my $url = $ss->{source}; + my $comp = join (' ', @{$ss->{comp}}); + $data .= "deb $url $ss->{suite} $comp\n\n"; + } + + write_file ($data, "$rootdir/etc/apt/sources.list"); + + $data = "# UNCONFIGURED FSTAB FOR BASE SYSTEM\n"; + write_file ($data, "$rootdir/etc/fstab", 0644); + + write_file ("localhost\n", "$rootdir/etc/hostname", 0644); + + # avoid warnings about non-existent resolv.conf + write_file ("", "$rootdir/etc/resolv.conf", 0644); + + $data = "auto lo\niface lo inet loopback\n"; + write_file ($data, "$rootdir/etc/network/interfaces", 0644); + + # setup devices + $self->run_command ("tar xzf '$devicetar' -C '$rootdir'"); + + # avoid warnings about missing default locale + write_file ("LANG=\"C\"\n", "$rootdir/etc/default/locale", 0644); + + # fake init + rename ("$rootdir/sbin/init", "$rootdir/sbin/init.org"); + $self->run_command ("cp '$fake_init' '$rootdir/sbin/init'"); + + $self->run_command ("cp '$default_env' '$rootdir/sbin/defenv'"); + + $self->run_command ("vzctl start $veid"); + $rootdir = $self->vz_root_dir(); + + $self->logmsg ("initialize ld cache\n"); + $self->ve_command ("/sbin/ldconfig"); + $self->run_command ("ln -sf mawk '$rootdir/usr/bin/awk'"); + + $self->logmsg ("installing packages\n"); + + $self->ve_dpkg ('install', 'base-files', 'base-passwd'); + + $self->ve_dpkg ('install', 'dpkg'); + + $self->run_command ("ln -sf /usr/share/zoneinfo/UTC '$rootdir/etc/localtime'"); + + $self->ve_dpkg ('install', 'libc6'); + $self->ve_dpkg ('install', 'perl-base'); + + unlink "$rootdir/usr/bin/awk"; + + $self->ve_dpkg ('install', 'mawk'); + $self->ve_dpkg ('install', 'debconf'); + + # unpack required packages + foreach my $p (@$required) { + $self->ve_dpkg ('unpack', $p); + } + + rename ("$rootdir/sbin/init.org", "$rootdir/sbin/init"); + $self->ve_divert_add ("/sbin/init"); + $self->run_command ("cp '$fake_init' '$rootdir/sbin/init'"); + + # disable service activation + $self->ve_divert_add ("/usr/sbin/policy-rc.d"); + $data = "#!/bin/sh\nexit 101\n"; + write_file ($data, "$rootdir/usr/sbin/policy-rc.d", 755); + + # disable start-stop-daemon + $self->ve_divert_add ("/sbin/start-stop-daemon"); + $data = <ve_divert_add ("/sbin/udevd"); + + if ($suite eq 'etch') { + # disable apache2 startup + write_file ("NO_START=1\n", "$rootdir/etc/default/apache2"); + } + + $self->logmsg ("configure required packages\n"); + $self->ve_command ("dpkg --force-confold --skip-same-version --configure -a"); + + # set postfix defaults + if ($mta eq 'postfix') { + $data = "postfix postfix/main_mailer_type select Local only\n"; + $self->ve_debconfig_set ($data); + + $data = "postmaster: root\nwebmaster: root\n"; + write_file ($data, "$rootdir/etc/aliases"); + } + + if ($suite eq 'jaunty') { + # jaunty does not create /var/run/network, so network startup fails. + # so we do not use tmpfs for /var/run and /var/lock + $self->run_command ("sed -e 's/RAMRUN=yes/RAMRUN=no/' -e 's/RAMLOCK=yes/RAMLOCK=no/' -i $rootdir/etc/default/rcS"); + # and create the directory here + $self->run_command ("mkdir $rootdir/var/run/network"); + } + + # unpack base packages + foreach my $p (@$important) { + $self->ve_dpkg ('unpack', $p); + } + + # start loopback + $self->ve_command ("ifconfig lo up"); + + $self->logmsg ("configure important packages\n"); + $self->ve_command ("dpkg --force-confold --skip-same-version --configure -a"); + + if (-d "$rootdir/etc/event.d") { + unlink <$rootdir/etc/event.d/tty*>; + } + + if (-f "$rootdir/etc/inittab") { + $self->run_command ("sed -i -e '/getty/d' '$rootdir/etc/inittab'"); + } + + # Link /etc/mtab to /proc/mounts, so df and friends will work: + unlink "$rootdir/etc/mtab"; + $self->ve_command ("ln -s /proc/mounts /etc/mtab"); + + # reset password + $self->ve_command ("usermod -L root"); + + # regenerate sshd host keys + $self->install_init_script ($script_ssh_init, 2, 14); + + if ($mta eq 'postfix') { + $data = "postfix postfix/main_mailer_type select No configuration\n"; + $self->ve_debconfig_set ($data); + + unlink "$rootdir/etc/mailname"; + write_file ($postfix_main_cf, "$rootdir/etc/postfix/main.cf"); + } + + if (!$opts->{minimal}) { + # unpack standard packages + foreach my $p (@$standard) { + $self->ve_dpkg ('unpack', $p); + } + + $self->logmsg ("configure standard packages\n"); + $self->ve_command ("dpkg --force-confold --skip-same-version --configure -a"); + } + + # disable HWCLOCK access + $self->run_command ("echo 'HWCLOCKACCESS=no' >> '$rootdir/etc/default/rcS'"); + + # disable hald + $self->ve_divert_add ("/usr/sbin/hald"); + + # disable /dev/urandom init + $self->run_command ("install -m 0755 '$script_init_urandom' '$rootdir/etc/init.d/urandom'"); + + if ($suite eq 'etch' || $suite eq 'hardy' || $suite eq 'intrepid' || $suite eq 'jaunty') { + # avoid klogd start + $self->ve_divert_add ("/sbin/klogd"); + } + + # remove unnecessays sysctl entries to avoid warnings + my $cmd = 'sed'; + $cmd .= ' -e \'s/^\(kernel\.printk.*\)/#\1/\''; + $cmd .= ' -e \'s/^\(kernel\.maps_protect.*\)/#\1/\''; + $cmd .= ' -e \'s/^\(fs\.inotify\.max_user_watches.*\)/#\1/\''; + $cmd .= ' -e \'s/^\(vm\.mmap_min_addr.*\)/#\1/\''; + $cmd .= " -i '$rootdir/etc/sysctl.conf'"; + $self->run_command ($cmd); + + my $bindv6only = "$rootdir/etc/sysctl.d/bindv6only.conf"; + if (-f $bindv6only) { + $cmd = 'sed'; + $cmd .= ' -e \'s/^\(net\.ipv6\.bindv6only.*\)/#\1/\''; + $cmd .= " -i '$bindv6only'"; + $self->run_command ($cmd); + } + + if ($suite eq 'hardy' || $suite eq 'intrepid' || $suite eq 'jaunty') { + # disable tty init (console-setup) + my $cmd = 'sed'; + $cmd .= ' -e \'s/^\(ACTIVE_CONSOLES=.*\)/ACTIVE_CONSOLES=/\''; + $cmd .= " -i '$rootdir/etc/default/console-setup'"; + $self->run_command ($cmd); + } + + if ($suite eq 'intrepid') { + # remove sysctl setup (avoid warnings at startup) + my $filelist = "$rootdir/etc/sysctl.d/10-console-messages.conf"; + $filelist .= " $rootdir/etc/sysctl.d/10-process-security.conf"; + $filelist .= " $rootdir/etc/sysctl.d/10-network-security.conf"; + $self->run_command ("rm $filelist"); + } + if ($suite eq 'jaunty') { + # remove sysctl setup (avoid warnings at startup) + my $filelist = "$rootdir/etc/sysctl.d/10-console-messages.conf"; + $filelist .= " $rootdir/etc/sysctl.d/10-network-security.conf"; + $self->run_command ("rm $filelist"); + } +} + +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->run_command ("vzctl start $veid"); + } + + system ("vzctl enter $veid"); +} + +sub ve_mysql_command { + my ($self, $sql, $password) = @_; + + #my $bootstrap = "/usr/sbin/mysqld --bootstrap --user=mysql --skip-grant-tables " . + #"--skip-bdb --skip-innodb --skip-ndbcluster"; + + $self->ve_command ("mysql", $sql); +} + +sub ve_mysql_bootstrap { + my ($self, $sql, $password) = @_; + + my $cmd; + + my $suite = $self->{config}->{suite}; + + if ($suite eq 'squeeze') { + $cmd = "/usr/sbin/mysqld --bootstrap --user=mysql --skip-grant-tables"; + + } else { + $cmd = "/usr/sbin/mysqld --bootstrap --user=mysql --skip-grant-tables " . + "--skip-bdb --skip-innodb --skip-ndbcluster"; + } + + $self->ve_command ($cmd, $sql); +} + +sub compute_required { + my ($self, $pkglist) = @_; + + my $pkginfo = $self->pkginfo(); + my $instpkgs = $self->read_installed (); + + my $closure = {}; + __record_provides ($pkginfo, $closure, [keys %$instpkgs]); + + return $self->closure ($closure, $pkglist); +} + +sub task_postgres { + my ($self, $opts) = @_; + + my @supp = ('7.4', '8.1'); + my $pgversion = '8.1'; + + my $suite = $self->{config}->{suite}; + + if ($suite eq 'lenny' || $suite eq 'hardy' || $suite eq 'intrepid' || $suite eq 'jaunty') { + @supp = ('8.3'); + $pgversion = '8.3'; + } elsif ($suite eq 'squeeze') { + @supp = ('8.4'); + $pgversion = '8.4'; + } + + $pgversion = $opts->{version} if $opts->{version}; + + die "unsupported postgres version '$pgversion'\n" + if !grep { $pgversion eq $_; } @supp; + + my $rootdir = $self->vz_root_dir(); + + my $required = $self->compute_required (["postgresql-$pgversion"]); + + $self->cache_packages ($required); + + $self->ve_dpkg ('install', @$required); + + my $iscript = $suite eq 'squeeze' ? 'postgresql' : "postgresql-$pgversion"; + + $self->ve_command ("/etc/init.d/$iscript start") if $opts->{start}; +} + +sub task_mysql { + my ($self, $opts) = @_; + + my $password = $opts->{password}; + my $rootdir = $self->vz_root_dir(); + + my $suite = $self->{config}->{suite}; + + my $ver = $suite eq 'squeeze' ? '5.1' : '5.0'; + + my $required = $self->compute_required (['mysql-common', "mysql-server-$ver"]); + + $self->cache_packages ($required); + + $self->ve_dpkg ('install', @$required); + + # fix security (see /usr/bin/mysql_secure_installation) + my $sql = "DELETE FROM mysql.user WHERE User='';\n" . + "DELETE FROM mysql.user WHERE User='root' AND Host!='localhost';\n" . + "FLUSH PRIVILEGES;\n"; + $self->ve_mysql_bootstrap ($sql); + + if ($password) { + + my $rpw = $password eq 'random' ? 'admin' : $password; + + my $sql = "USE mysql;\n" . + "UPDATE user SET password=PASSWORD(\"$rpw\") WHERE user='root';\n" . + "FLUSH PRIVILEGES;\n"; + $self->ve_mysql_bootstrap ($sql); + + write_file ("[client]\nuser=root\npassword=\"$rpw\"\n", "$rootdir/root/.my.cnf", 0600); + if ($password eq 'random') { + $self->install_init_script ($script_mysql_randompw, 2, 20); + } + } + + $self->ve_command ("/etc/init.d/mysql start") if $opts->{start}; +} + +sub task_php { + my ($self, $opts) = @_; + + my $memlimit = $opts->{memlimit}; + my $rootdir = $self->vz_root_dir(); + + my $required = $self->compute_required ([qw (php5 php5-cli libapache2-mod-php5 php5-gd)]); + + $self->cache_packages ($required); + + $self->ve_dpkg ('install', @$required); + + if ($memlimit) { + $self->run_command ("sed -e 's/^\\s*memory_limit\\s*=.*;/memory_limit = ${memlimit}M;/' -i $rootdir/etc/php5/apache2/php.ini"); + } +} + +sub install { + my ($self, $pkglist, $unpack) = @_; + + my $required = $self->compute_required ($pkglist); + + $self->cache_packages ($required); + + $self->ve_dpkg ($unpack ? 'unpack' : 'install', @$required); +} + +sub cleanup { + my ($self, $distclean) = @_; + + unlink $self->{logfile}; + unlink "$self->{targetname}.tar"; + unlink "$self->{targetname}.tar.gz"; + + $self->ve_destroy (); + unlink ".veid"; + + rmtree $self->{cachedir} if $distclean && !$self->{config}->{cachedir}; + + rmtree $self->{infodir}; + +} + +1; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..902c853 --- /dev/null +++ b/Makefile @@ -0,0 +1,65 @@ +VERSION=1.1 +PACKAGE=dab +PKGREL=15 + + +SCRIPTS= \ + scripts/init.pl \ + scripts/defenv \ + scripts/mysql_randompw \ + scripts/init_urandom \ + scripts/ssh_gen_host_keys + +DEB=${PACKAGE}_${VERSION}-${PKGREL}_all.deb + +DESTDIR= +PREFIX=/usr +DATADIR=${PREFIX}/lib/${PACKAGE} +SBINDIR=${PREFIX}/sbin +MANDIR=${PREFIX}/share/man +DOCDIR=${PREFIX}/share/doc/${PACKAGE} +MAN1DIR=${MANDIR}/man1/ +PERLDIR=${PREFIX}/share/perl5/ + +all: ${DEB} + +.PHONY: install +install: dab dab.1 DAB.pm devices.tar.gz ${SCRIPTS} + install -d ${DESTDIR}${SBINDIR} + install -m 0755 dab ${DESTDIR}${SBINDIR} + install -d ${DESTDIR}${MAN1DIR} + install -m 0644 dab.1 ${DESTDIR}${MAN1DIR} + gzip -f9 ${DESTDIR}${MAN1DIR}/dab.1 + install -D -m 0644 DAB.pm ${DESTDIR}${PERLDIR}/PVE/DAB.pm + install -d ${DESTDIR}${DATADIR}/scripts + install -m 0755 ${SCRIPTS} ${DESTDIR}${DATADIR}/scripts + install -m 0644 devices.tar.gz ${DESTDIR}${DATADIR} + +.PHONY: deb +deb ${DEB}: dab dab.1 DAB.pm control changelog.Debian + rm -rf debian + mkdir debian + make DESTDIR=debian install + install -d -m 0755 debian/DEBIAN + sed -e s/@@VERSION@@/${VERSION}/ -e s/@@PKGRELEASE@@/${PKGREL}/ debian/DEBIAN/control + install -D -m 0644 copyright debian/${DOCDIR}/copyright + install -m 0644 changelog.Debian debian/${DOCDIR} + gzip -9 debian/${DOCDIR}/changelog.Debian + dpkg-deb --build debian + mv debian.deb ${DEB} + rm -rf debian + lintian ${DEB} + + +dab.pdf: dab.1 + groff -man dab.1 |ps2pdf - > dab.pdf + +dab.1: dab + rm -f dab.1 + pod2man -n $< -s 1 -r ${VERSION} <$< >$@ + + +.PHONY: clean +clean: + rm -f ${DEB} dab.1 dab.pdf *~ + diff --git a/README b/README new file mode 100644 index 0000000..ccbf810 --- /dev/null +++ b/README @@ -0,0 +1,2 @@ +TODO + diff --git a/changelog.Debian b/changelog.Debian new file mode 100644 index 0000000..a795810 --- /dev/null +++ b/changelog.Debian @@ -0,0 +1,114 @@ +dab (1.1-15) unstable; urgency=low + + * fix typo in scripts/mysql_randompw + + -- Proxmox Support Team Mon, 15 Aug 2011 05:56:52 +0200 + +dab (1.1-14) unstable; urgency=low + + * include SUITE-updates for squeeze + + -- Proxmox Support Team Mon, 09 May 2011 10:57:08 +0200 + +dab (1.1-13) unstable; urgency=low + + * use insserv instead of update-rc.d (for newer versions) + + -- Proxmox Support Team Fri, 06 May 2011 06:59:25 +0200 + +dab (1.1-12) unstable; urgency=low + + * use UPASSWD instead of PASSWD inside shell scripts (avoid problems + with new 'dash') + + -- Proxmox Support Team Thu, 03 Mar 2011 06:41:31 +0100 + +dab (1.1-11) unstable; urgency=low + + * use HNAME instead of HOSTNAME inside shell scripts (avoid problems + with new 'dash') + + -- Proxmox Support Team Wed, 02 Mar 2011 07:00:47 +0100 + +dab (1.1-10) unstable; urgency=low + + * use '=' instead of '==' to compare strings in shell scripts + + -- Proxmox Support Team Thu, 16 Dec 2010 06:37:54 +0100 + +dab (1.1-9) unstable; urgency=low + + * correct update-rc.d invocation (add '.' after runlevel) + + -- Proxmox Support Team Mon, 04 Oct 2010 09:27:36 +0200 + +dab (1.1-8) unstable; urgency=low + + * fix postgres startup script for squeeze + + -- Proxmox Support Team Thu, 09 Sep 2010 11:19:27 +0200 + +dab (1.1-7) unstable; urgency=low + + * support dependency based startup (squeeze) + + * use postgresql 8.4 for squeeze + + -- Proxmox Support Team Thu, 09 Sep 2010 10:37:10 +0200 + +dab (1.1-6) unstable; urgency=low + + * use mysql-server-5.1 for squeezs + + -- Proxmox Support Team Thu, 19 Aug 2010 08:32:57 +0200 + +dab (1.1-5) unstable; urgency=low + + * bug fix: only print a warning for packages installed directly with + 'dpkg' (when there is no information in 'available' list) + + * only mount /proc/ if not already mounted (avoid problems with vzctl + 3.0.24) + + -- Proxmox Support Team Mon, 09 Aug 2010 10:18:28 +0200 + +dab (1.1-4) unstable; urgency=low + + * add support for debian squeeze (debian 6.0) + + * ve_exec - query return status + + -- Proxmox Support Team Tue, 12 Jan 2010 12:22:33 +0100 + +dab (1.1-3) unstable; urgency=low + + * support intrepid and jaunty (patch from Sebastiaan Blommers) + + * apache NO_START is only used by etch + + -- Proxmox Support Team Tue, 19 May 2009 10:30:54 +0200 + +dab (1.1-2) unstable; urgency=low + + * DAB.pm (finalize): remove aquota.group and aquota.user + + * ignore non-existent Release[.gpg] files + + * __record_provides(): correct skipself implementation + + -- Proxmox Support Team Fri, 23 Jan 2009 15:22:02 +0100 + +dab (1.1-1) unstable; urgency=low + + * first public release + + * new manual page + + -- Proxmox Support Team Fri, 16 Jan 2009 11:42:51 +0100 + +dab (1.0-1) unstable; urgency=low + + * first package build + + -- Proxmox Support Team Fri, 28 Nov 2008 10:46:34 +0100 + diff --git a/control b/control new file mode 100644 index 0000000..a52b555 --- /dev/null +++ b/control @@ -0,0 +1,10 @@ +Package: dab +Version: @@VERSION@@-@@PKGRELEASE@@ +Section: admin +Priority: optional +Architecture: all +Depends: perl (>= 5.6.0-16), wget, make, binutils, vzctl +Maintainer: Proxmox Support Team +Description: Debian OpenVZ Appliance Builder + This package contains scripts to automate the creation of openvz + appliances. diff --git a/copyright b/copyright new file mode 100644 index 0000000..eab3164 --- /dev/null +++ b/copyright @@ -0,0 +1,21 @@ + + Copyright (C) 2007 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. + +The complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. diff --git a/dab b/dab new file mode 100755 index 0000000..3afd5bc --- /dev/null +++ b/dab @@ -0,0 +1,579 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Long; +use PVE::DAB; + +$ENV{'LC_ALL'} = 'C'; + +sub print_usage { + my ($msg) = @_; + + if ($msg) { + print STDERR "ERROR: $msg\n"; + } + print STDERR "dab [parameters]\n"; +} + + +if (scalar (@ARGV) == 0) { + print_usage (); + exit (-1); +} + +my $cmdline = join (' ', @ARGV); + +my $cmd = shift @ARGV; + +if (!$cmd) { + print_usage("no command specified"); + exit (-1); +} + +my $dab = PVE::DAB->new(); + +$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')) { + print_usage (); + exit (-1); + } + + 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')) { + print_usage (); + exit (-1); + } + + 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->vz_priv_dir() . "\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)) { + print_usage (); + exit (-1); + } + + 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) { + print_usage ("no task specified"); + exit (-1); + } + + my $opts = {}; + + if ($task eq 'mysql') { + + if (!GetOptions ($opts, 'password=s', 'start')) { + print_usage (); + exit (-1); + } + + 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')) { + print_usage (); + exit (-1); + } + + die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0; + + $dab->task_postgres ($opts); + + } elsif ($task eq 'php') { + + if (!GetOptions ($opts, 'memlimit=i')) { + print_usage (); + exit (-1); + } + + die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0; + + $dab->task_php ($opts); + + } else { + + print_usage ("unknown task '$task'"); + exit (-1); + + } + + } 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 { + print_usage ("invalid command '$cmd'"); + exit (-1); + } + +}; + +if (my $err = $@) { + $dab->logmsg ($@); + die ($@); +} + +exit 0; + +__END__ + +=head1 NAME + +dab - Debian OpenVZ 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 (we use postfix by default) + +=item I<--minimal> + +Do not install standard packages. + +=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). + +=back + +=item B + +List installed packages. + +=over + +=item I<--verbose> + +Also print package versions. + +=back + +=item B + +Remove all temporary file and destroy the used OpenVZ container. + +=item B + +Like clean, but also removes the package cache (except when you +specified your own cache directory in the config file) + +=back + +=back + +=head1 DESCRIPTION + +dab is a script to automate the creation of OpenVZ appliances. It is +basically a rewrite of debootstrap in perl, but uses OpenVZ instead of +chroot and generates OpenVZ 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 container. Proxmox VE +(http://pve.proxmox.com) is the preferred environment, because it is +able to log the console output when a container starts. You wont be +able to detect errors during container startup when running on +standard OpenVZ. 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, +admin, www + +=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 + +Many thanks to Proxmox Server Solutions (www.proxmox.com) for sponsoring +this work. + +=head1 COPYRIGHT AND DISCLAIMER + +Copyright (C) 2007-2009 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. diff --git a/devices.tar.gz b/devices.tar.gz new file mode 100644 index 0000000..da98831 Binary files /dev/null and b/devices.tar.gz differ diff --git a/scripts/defenv b/scripts/defenv new file mode 100755 index 0000000..93e7882 --- /dev/null +++ b/scripts/defenv @@ -0,0 +1,10 @@ +#!/bin/sh + +export DEBIAN_FRONTEND="noninteractive" +export DEBCONF_NONINTERACTIVE_SEEN="true" + +export HOME=/root +export USER=root +export PATH=/usr/sbin:/usr/bin:/sbin:/bin + +exec $@ diff --git a/scripts/init.pl b/scripts/init.pl new file mode 100644 index 0000000..b111c5f --- /dev/null +++ b/scripts/init.pl @@ -0,0 +1,80 @@ +#!/usr/bin/perl -w + +use strict; +use POSIX qw (:sys_wait_h strftime); +use POSIX qw(EINTR); +use IO::Socket::UNIX; + +$SIG{CHLD} = sub { + 1 while waitpid(-1, WNOHANG) > 0; +}; +$SIG{INT} = sub { + print "stopping init\n"; + exit (0); +}; + +mkdir "/dev"; +mkdir "/var/"; +mkdir "/var/log"; + +my $logfile = "/var/log/init.log"; + +close (STDOUT); +open (STDOUT, ">>$logfile"); +close (STDERR); +open STDERR, ">&STDOUT"; + +select STDERR; $| = 1; # make unbuffered +select STDOUT; $| = 1; # make unbuffered + +my $args = join (" ", @ARGV); + +if ($$ != 1) { + my $l = shift @ARGV; + + if (defined ($l) && $l eq '0') { + print "initctl $args\n"; + kill 2, 1; + } else { + print "initctl $args (ignored)\n"; + } + + exit (0); +} + +print "starting init $args\n"; + +# only start once when pid == 1 +# ignore runlevel change requests +exit (0) if $$ != 1; + +if (! -d "/proc/$$") { + system ("mount -t proc proc /proc") == 0 || + die "unable to mount proc filesystem\n"; +} + +system ("hostname localhost") == 0 || + die "unable to set hostname\n"; + + +# provide simple syslog + +my $sock = IO::Socket::UNIX->new (Local => "/dev/log", Listen => 5); + +while ((my $fd = $sock->accept()) ||($! == EINTR)) { + + next if !$fd; # EINTR + + while (defined (my $line = <$fd>)) { + $line =~ s/\0/\n/g; + chomp $line; + $line =~ s/^<\d+>//mg; + next if $line =~ m/^\s*$/; + print "$line\n"; + } + + close ($fd); +} + +print "exit init\n"; +exit (0); diff --git a/scripts/init_urandom b/scripts/init_urandom new file mode 100755 index 0000000..f5d2128 --- /dev/null +++ b/scripts/init_urandom @@ -0,0 +1,40 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: urandom +# Required-Start: $local_fs +# Required-Stop: $local_fs +# Default-Start: S +# Default-Stop: 0 6 +# Short-Description: Save and restore random seed between restarts. +# Description This script saves the random seed between restarts. +# It is called from the boot, halt and reboot scripts. +### END INIT INFO + +[ -c /dev/urandom ] || exit 0 + +PATH=/sbin:/usr/sbin:/bin:/usr/bin + +. /lib/init/vars.sh + +. /lib/lsb/init-functions + +# Modified version for OpenVZ containers + +case "$1" in + start|"") + # nothing to do inside container + ;; + stop) + # nothing to do inside container + ;; + restart|reload|force-reload) + echo "Error: argument '$1' not supported" >&2 + exit 3 + ;; + *) + echo "Usage: urandom start|stop" >&2 + exit 3 + ;; +esac + +: diff --git a/scripts/mysql_randompw b/scripts/mysql_randompw new file mode 100644 index 0000000..4ba1202 --- /dev/null +++ b/scripts/mysql_randompw @@ -0,0 +1,44 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: mysql_randompw +# Required-Start: $local_fs mysql +# Required-Stop: +# X-Start-Before: +# Default-Start: 2 +# Default-Stop: +# Short-Description: Generate random MySQL root password +# Description: Generate and set a random MySQL root password +### END INIT INFO + +set -e + +HNAME=`head -n 1 /etc/hostname|awk '{ print $1; }'` + +if [ "X${HNAME}" = "Xlocalhost" ] ; then + exit 0; +fi + +echo "Generate random MySQL root password" + +# set HOME dir (for .my.cfg) +export HOME=/root +export USER=root + +UPASSWD=`openssl rand -base64 9` +mysqladmin password "${UPASSWD}" + +cat < /root/.my.cnf +[client] +user=root +password="${UPASSWD}" +EOF + +chmod 0600 /root/.my.cnf + +if [ -x /sbin/insserv ] ; then + /sbin/insserv -r mysql_randompw + rm -f /etc/init.d/mysql_randompw +else + rm -f /etc/init.d/mysql_randompw + update-rc.d -f mysql_randompw remove +fi diff --git a/scripts/ssh_gen_host_keys b/scripts/ssh_gen_host_keys new file mode 100755 index 0000000..ed32fe7 --- /dev/null +++ b/scripts/ssh_gen_host_keys @@ -0,0 +1,35 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: ssh_gen_host_keys +# Required-Start: $local_fs +# Required-Stop: +# X-Start-Before: sshd +# Default-Start: 2 +# Default-Stop: +# Short-Description: Regenerate SSH keys +# Description: Regenerate container SSH keys for uniqueness. +### END INIT INFO + +set -e + +HNAME=`head -n 1 /etc/hostname|awk '{ print $1; }'` + +if [ "X${HNAME}" = "Xlocalhost" ] ; then + exit 0; +fi + +echo "generating ssh host keys" + +rm -f /etc/ssh/ssh_host_rsa_key +ssh-keygen -q -f /etc/ssh/ssh_host_rsa_key -t rsa -N '' + +rm -f /etc/ssh/ssh_host_dsa_key +ssh-keygen -q -f /etc/ssh/ssh_host_dsa_key -t dsa -N '' + +if [ -x /sbin/insserv ] ; then + /sbin/insserv -r ssh_gen_host_keys + rm -f /etc/init.d/ssh_gen_host_keys +else + rm -f /etc/init.d/ssh_gen_host_keys + update-rc.d -f ssh_gen_host_keys remove +fi