]> git.proxmox.com Git - dab.git/blob - dab
add code cleanup fatal_usage helper
[dab.git] / dab
1 #!/usr/bin/perl -w
2
3 use strict;
4 use warnings;
5
6 use Getopt::Long;
7
8 use PVE::DAB;
9
10 $ENV{'LC_ALL'} = 'C';
11
12 my $commands = {
13 'init' => '',
14 'bootstrap' => '[--exim] [--minimal]',
15 'finalize' => '[--keepmycnf] [--compressor <gz (default)|zst>]',
16 'veid' => '',
17 'basedir' => '',
18 'packagefile' => '',
19 'list' => '[--verbose]',
20 'task' => '<postgres|mysql|php> [--version] [--password] [--memlimit]',
21 'install' => '<package or *.pkglist file> ...',
22 'exec' => '<cmd> ...',
23 'enter' => '',
24 'clean' => '',
25 'dist-clean' => '',
26 'help' => '',
27 };
28
29 sub print_usage {
30 print STDERR "USAGE: dab <command> [parameters]\n\n";
31
32 for my $cmd (sort keys %$commands) {
33 if (my $opts = $commands->{$cmd}) {
34 print STDERR " dab $cmd $opts\n";
35 } else {
36 print STDERR " dab $cmd\n";
37 }
38 }
39 }
40
41 sub fatal_usage {
42 my ($msg) = @_;
43
44 print STDERR "\nERROR: $msg\n\n" if $msg;
45 print_usage();
46
47 exit (-1);
48 }
49
50 if (scalar (@ARGV) == 0) {
51 fatal_usage("no command specified");
52 }
53
54 my $cmdline = join (' ', @ARGV);
55 my $cmd = shift @ARGV;
56
57 if (!$cmd) {
58 fatal_usage("no command specified");
59 } elsif (!exists $commands->{$cmd}) {
60 fatal_usage("unknown command '$cmd'");
61 } elsif ($cmd eq 'help') {
62 print_usage();
63 exit (0);
64 }
65
66 my $dab;
67 sub dab() {
68 $dab = PVE::DAB->new() if !$dab;
69 return $dab;
70 }
71
72 dab->writelog ("dab: $cmdline\n");
73
74 $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
75 die "interrupted by signal\n";
76 };
77
78 eval {
79 if ($cmd eq 'init') {
80 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
81
82 dab->initialize();
83
84 } elsif ($cmd eq 'bootstrap') {
85 my $opts = {};
86 if (!GetOptions ($opts, 'exim', 'minimal')) {
87 fatal_usage();
88 }
89 die "command 'bootstrap' expects no arguments.\n" if scalar (@ARGV) != 0;
90
91 $dab->ve_init();
92 $dab->bootstrap ($opts);
93
94 } elsif ($cmd eq 'finalize') {
95 my $opts = {};
96 if (!GetOptions ($opts, 'keepmycnf', 'compressor=s')) {
97 fatal_usage();
98 }
99 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
100
101 $dab->finalize($opts);
102
103 } elsif ($cmd eq 'veid') {
104 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
105
106 print $dab->{veid} . "\n";
107
108 } elsif ($cmd eq 'basedir') {
109 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
110
111 print $dab->{rootfs} . "\n";
112
113 } elsif ($cmd eq 'packagefile') {
114 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
115
116 print "$dab->{targetname}.tar.gz\n";
117
118 } elsif ($cmd eq 'list') {
119 my $verbose;
120 if (!GetOptions ('verbose' =>\$verbose)) {
121 fatal_usage();
122 }
123 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
124
125 my $instpkgs = $dab->read_installed ();
126
127 foreach my $pkg (sort keys %$instpkgs) {
128 if ($verbose) {
129 my $version = $instpkgs->{$pkg}->{version};
130 print "$pkg $version\n";
131 } else {
132 print "$pkg\n";
133 }
134 }
135
136 } elsif ($cmd eq 'task') {
137 my $task = shift @ARGV;
138 if (!$task) {
139 fatal_usage("no task specified");
140 }
141
142 my $opts = {};
143 if ($task eq 'mysql') {
144 if (!GetOptions ($opts, 'password=s', 'start')) {
145 fatal_usage();
146 }
147 die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0;
148
149 $dab->task_mysql ($opts);
150
151 } elsif ($task eq 'postgres') {
152 if (!GetOptions ($opts, 'version=s', 'start')) {
153 fatal_usage();
154 }
155 die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0;
156
157 $dab->task_postgres ($opts);
158
159 } elsif ($task eq 'php') {
160 if (!GetOptions ($opts, 'memlimit=i')) {
161 fatal_usage();
162 }
163 die "task '$task' expects no arguments.\n" if scalar (@ARGV) != 0;
164
165 $dab->task_php ($opts);
166
167 } else {
168 fatal_usage("unknown task '$task'");
169 }
170
171 } elsif ($cmd eq 'install' || $cmd eq 'unpack') {
172 my $required;
173 foreach my $arg (@ARGV) {
174 if ($arg =~ m/\.pkglist$/) {
175 open (TMP, $arg) ||
176 die "cant open package list '$arg' - $!";
177 while (defined (my $line = <TMP>)) {
178 chomp $line;
179 next if $line =~ m/^\s*$/;
180 next if $line =~ m/\#/;
181 if ($line =~ m/^\s*(\S+)\s*$/) {
182 push @$required, $1;
183 } else {
184 die "invalid package name in '$arg' - $line\n";
185 }
186 }
187 } else {
188 push @$required, $arg;
189 }
190
191 close (TMP);
192 }
193
194 $dab->install ($required, $cmd eq 'unpack');
195
196 } elsif ($cmd eq 'exec') {
197
198 $dab->ve_exec (@ARGV);
199
200 } elsif ($cmd eq 'enter') {
201 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
202
203 $dab->enter();
204
205 } elsif ($cmd eq 'clean') {
206 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
207
208 $dab->cleanup(0);
209
210 } elsif ($cmd eq 'dist-clean') {
211 die "command '$cmd' expects no arguments.\n" if scalar (@ARGV) != 0;
212
213 $dab->cleanup(1);
214
215 } else {
216 fatal_usage("invalid command '$cmd'");
217 }
218
219 };
220 if (my $err = $@) {
221 $dab->logmsg ($@);
222 die ($@);
223 }
224
225 exit 0;
226
227 __END__
228
229 =head1 NAME
230
231 dab - Debian LXC Appliance Builder
232
233 =head1 SYNOPSIS
234
235 =over
236
237 =item B<dab> I<command> I<[OPTIONS]>
238
239 =item B<dab init>
240
241 Downloads the package descriptions form the repository. Also truncates the
242 C<logfile>.
243
244 =item B<dab bootstrap>
245
246 Bootstrap a debian system and allocate a temporary container (we use IDs 90000
247 and above).
248
249 =over
250
251 =item I<--exim>
252
253 Use exim as MTA (we use postfix by default)
254
255 =item I<--minimal>
256
257 Do not install standard packages.
258
259 =back
260
261 =item B<dab veid>
262
263 Print used container ID.
264
265 =item B<dab basedir>
266
267 Print container private directory.
268
269 =item B<dab packagefile>
270
271 Print the appliance file name.
272
273 =item B<dab install I<pkg ...>>
274
275 Install one or more packages. I<pkg> can also refer to a file named
276 C<xyz.pkglist> which contains a list of packages. All dependencies are
277 automatically installed.
278
279 =item B<dab unpack I<pkg ...>>
280
281 Unpack one or more packages. I<pkg> can also refer to a file named
282 C<xyz.pkglist> which contains a list of packages. All dependencies are
283 automatically unpacked.
284
285 =item B<dab exec I<CMD> I<ARGS>>
286
287 Executes command CMD inside the container.
288
289 =item B<dab enter>
290
291 Calls C<lxc-attach> - this is for debugging only.
292
293 =item B<dab task mysql>
294
295 Install a mysql database server. During appliance generation we use C<admin> as
296 mysql root password (also stored in /root/.my.cnf).
297
298 =over
299
300 =item I<--password=XXX>
301
302 Specify the mysql root password. The special value C<random> can be use to
303 generate a random root password when the appliance is started first time
304 (stored in /root/.my.cnf)
305
306 =item I<--start>
307
308 Start the mysql server (if you want to execute sql commands during
309 appliance generation).
310
311 =back
312
313 =item B<dab task postgres>
314
315 Install a postgres database server.
316
317 =over
318
319 =item I<--version=XXX>
320
321 Select Postgres version. Posible values are C<7.4>, C<8.1> and C<8.3> (depends
322 on the selected suite).
323
324 =item I<--start>
325
326 Start the postgres server (if you want to execute sql commands during appliance
327 generation).
328
329 =back
330
331 =item B<dab task php>
332
333 Install php5.
334
335 =over
336
337 =item I<--memlimit=i>
338
339 Set the php I<memory_limit>.
340
341 =back
342
343 =item B<dab finalize>
344
345 Cleanup everything inside the container and generate the final appliance
346 package.
347
348 =over
349
350 =item I<--keepmycnf>
351
352 Do not delete file C</root/.my.cfg> (mysql).
353
354 =back
355
356 =item B<dab list>
357
358 List installed packages.
359
360 =over
361
362 =item I<--verbose>
363
364 Also print package versions.
365
366 =back
367
368 =item B<dab clean>
369
370 Remove all temporary files and destroy the container.
371
372 =item B<dab dist-clean>
373
374 Like clean, but also removes the package cache (except when you specified your
375 own cache directory in the config file)
376
377 =back
378
379 =head1 DESCRIPTION
380
381 dab is a script to automate the creation of LXC appliances. It is basically a
382 rewrite of debootstrap in perl, but uses LXC instead of chroot and generates
383 LXC templates. Another difference is that it supports multi-stage building of
384 templates. That way you can execute arbitrary scripts between to accomplish
385 what you want.
386
387 Furthermore some common tasks are fully automated, like setting up a database
388 server (mysql or postgres).
389
390 To accomplish minimal template creation time, packages are cached to a local
391 directory, so you do not need a local debian mirror (although this would speed
392 up the first run).
393
394 See http://pve.proxmox.com/wiki/Debian_Appliance_Builder for examples.
395
396 This script need to be run as root, so it is not recommended to start it on a
397 production machine with running containers. So many people run Proxmox VE
398 inside a KVM or VMWare 64bit virtual machine to build appliances.
399
400 All generated templates includes an appliance description file. Those can be
401 used to build appliance repositories.
402
403 =head1 CONFIGURATION
404
405 Configuration is read from the file C<dab.conf> inside the current working
406 directory. The files contains key value pairs, separated by colon.
407
408 =over 2
409
410 =item B<Suite:> I<squeeze|wheezy|jessie|trusty|vivid>
411
412 The Debian or Ubuntu suite.
413
414 =item B<Source:> I<URL [components]>
415
416 Defines a source location. By default we use the following for debian:
417
418 Source: http://ftp.debian.org/debian SUITE main contrib
419 Source: http://security.debian.org SUITE/updates main contrib
420
421 Note: SUITE is a variable and will be substituted.
422
423 There are also reasonable defaults for Ubuntu. If you do not specify any source
424 the defaults are used.
425
426 =item B<Depends:> I<dependencies>
427
428 Debian like package dependencies. This can be used to make sure that speific
429 package versions are available.
430
431 =item B<CacheDir>: I<path>
432
433 Allows you to specify the directory where downloaded packages are cached.
434
435 =item B<Mirror:> I<SRCURL> => I<DSTURL>
436
437 Define a mirror location. for example:
438
439 Mirror: http://ftp.debian.org/debian => ftp://mirror/debian
440
441 =back
442
443 All other settings in this files are also included into the appliance
444 description file.
445
446 =over 2
447
448 =item B<Name:> I<name>
449
450 The name of the appliance.
451
452 Appliance names must consist only of lower case letters (a-z), digits (0-9),
453 plus (+) and minus (-) signs, and periods (.). They must be at least two
454 characters long and must start with an alphanumeric character.
455
456 =item B<Architecture:> I<i386|amd64>
457
458 Target architecture.
459
460 =item B<Version:> I<upstream_version[-build_revision]>
461
462 The version number of an appliance.
463
464 =item: B<Section:> I<section>
465
466 This field specifies an application area into which the appliance has been
467 classified. Currently we use the following section names: system, mail
468
469 =item B<Maintainer:> I<name <email>>
470
471 The appliance maintainer's name and email address. The name should come first,
472 then the email address inside angle brackets <> (in RFC822 format).
473
474 =item B<Infopage:> I<URL>
475
476 Link to web page containing more informations about this appliance.
477
478 =item B<Description:> I<single line synopsis>
479
480 extended description over several lines (indended by space) may follow.
481
482 =back
483
484 =head1 Appliance description file
485
486 All generated templates includes an appliance description file called
487
488 /etc/appliance.info
489
490 this is the first file inside the tar archive. That way it can be easily
491 exctracted without scanning the whole archive. The file itself contains
492 informations like a debian C<control> file. It can be used to build appliance
493 repositories.
494
495 Most fields are directly copied from the configuration file C<dab.conf>.
496
497 Additionally there are some auto-generated files:
498
499 =over
500
501 =item B<Installed-Size:> I<bytes>
502
503 It gives the total amount of disk space required to install the named
504 appliance. The disk space is represented in megabytes as a simple decimal
505 number.
506
507 =item B<Type:> I<type>
508
509 This is always C<lxc>.
510
511 =item B<OS:> I<[debian-4.0|debian-5.0|ubuntu-8.0]>
512
513 Operation system.
514
515 =back
516
517 Appliance repositories usually add additional fields:
518
519 =over
520
521 =item B<md5sum:> I<md5sum>
522
523 MD5 checksum
524
525 =back
526
527 =head1 FILES
528
529 The following files are created inside your working directory:
530
531 dab.conf appliance configuration file
532
533 logfile contains installation logs
534
535 .veid stores the used container ID
536
537 cache/* default package cache directory
538
539 info/* package information cache
540
541 =head1 AUTHOR
542
543 Dietmar Maurer <dietmar@proxmox.com>
544
545 Many thanks to Proxmox Server Solutions (www.proxmox.com) for sponsoring this
546 work.
547
548 =head1 COPYRIGHT AND DISCLAIMER
549
550 Copyright (C) 2007-2020 Proxmox Server Solutions GmbH
551
552 Copyright: dab is under GNU GPL, the GNU General Public License.
553
554 This program is free software; you can redistribute it and/or modify
555 it under the terms of the GNU General Public License as published by
556 the Free Software Foundation; version 2 dated June, 1991.
557
558 This program is distributed in the hope that it will be useful,
559 but WITHOUT ANY WARRANTY; without even the implied warranty of
560 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
561 GNU General Public License for more details.
562
563 You should have received a copy of the GNU General Public License
564 along with this program; if not, write to the
565 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
566 MA 02110-1301, USA.