]> git.proxmox.com Git - pve-installer.git/blame_incremental - proxinstall
fix postfix installation with no active network
[pve-installer.git] / proxinstall
... / ...
CommitLineData
1#!/usr/bin/perl -w
2
3$ENV{DEBIAN_FRONTEND} = 'noninteractive';
4$ENV{LC_ALL} = 'C';
5
6use strict;
7use Getopt::Long;
8use IPC::Open2;
9use IPC::Open3;
10use IO::File;
11use IO::Select;
12use Cwd 'abs_path';
13use Gtk3 '-init';
14use Gtk3::WebKit;
15use Encode;
16use String::ShellQuote;
17use Data::Dumper;
18use File::Basename;
19use Time::HiRes;
20
21my $release = '4.4';
22
23my $kapi = `uname -r`;
24chomp $kapi;
25
26my $enable_btrfs = 0;
27
28my $opt_testmode;
29
30if (!$ENV{G_SLICE} || $ENV{G_SLICE} ne "always-malloc") {
31 die "do not use slice allocator (run with 'G_SLICE=always-malloc ./proxinstall ...')\n";
32}
33
34if (!GetOptions ('testmode=s' => \$opt_testmode)) {
35 die "usage error\n";
36 exit (-1);
37}
38
39my $zfstestpool = "test_rpool";
40my $zfspoolname = $opt_testmode ? $zfstestpool : 'rpool';
41
42my $storage_cfg_zfs = <<__EOD__;
43dir: local
44 path /var/lib/vz
45 content iso,vztmpl,backup
46
47zfspool: local-zfs
48 pool $zfspoolname/data
49 sparse
50 content images,rootdir
51__EOD__
52
53my $storage_cfg_btrfs = <<__EOD__;
54dir: local
55 path /var/lib/vz
56 content iso,vztmpl,backup
57 disabled
58
59btrfs: local-btrfs
60 path /var/lib/pve/local-btrfs
61 content iso,vztmpl,backup,images,rootdir
62__EOD__
63
64my $storage_cfg_lvmthin = <<__EOD__;
65dir: local
66 path /var/lib/vz
67 content iso,vztmpl,backup
68
69lvmthin: local-lvm
70 thinpool data
71 vgname pve
72 content rootdir,images
73__EOD__
74
75
76sub file_read_firstline {
77 my ($filename) = @_;
78
79 my $fh = IO::File->new ($filename, "r");
80 return undef if !$fh;
81 my $res = <$fh>;
82 chomp $res if $res;
83 $fh->close;
84 return $res;
85}
86
87my $logfd = IO::File->new (">/tmp/install.log");
88
89my $proxmox_dir = $opt_testmode ? Cwd::cwd() : "/var/lib/pve-installer";
90
91my $grub_plattform = "pc"; # pc, efi-amd64 or efi-ia32
92
93$grub_plattform = "efi-amd64" if -d "/sys/firmware/efi";
94
95my $IPV4OCTET = "(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9])?[0-9])";
96my $IPV4RE = "(?:(?:$IPV4OCTET\\.){3}$IPV4OCTET)";
97my $IPV6H16 = "(?:[0-9a-fA-F]{1,4})";
98my $IPV6LS32 = "(?:(?:$IPV4RE|$IPV6H16:$IPV6H16))";
99
100my $IPV6RE = "(?:" .
101 "(?:(?:" . "(?:$IPV6H16:){6})$IPV6LS32)|" .
102 "(?:(?:" . "::(?:$IPV6H16:){5})$IPV6LS32)|" .
103 "(?:(?:(?:" . "$IPV6H16)?::(?:$IPV6H16:){4})$IPV6LS32)|" .
104 "(?:(?:(?:(?:$IPV6H16:){0,1}$IPV6H16)?::(?:$IPV6H16:){3})$IPV6LS32)|" .
105 "(?:(?:(?:(?:$IPV6H16:){0,2}$IPV6H16)?::(?:$IPV6H16:){2})$IPV6LS32)|" .
106 "(?:(?:(?:(?:$IPV6H16:){0,3}$IPV6H16)?::(?:$IPV6H16:){1})$IPV6LS32)|" .
107 "(?:(?:(?:(?:$IPV6H16:){0,4}$IPV6H16)?::" . ")$IPV6LS32)|" .
108 "(?:(?:(?:(?:$IPV6H16:){0,5}$IPV6H16)?::" . ")$IPV6H16)|" .
109 "(?:(?:(?:(?:$IPV6H16:){0,6}$IPV6H16)?::" . ")))";
110
111my $IPRE = "(?:$IPV4RE|$IPV6RE)";
112
113
114my $ipv4_mask_hash = {
115 '128.0.0.0' => 1,
116 '192.0.0.0' => 2,
117 '224.0.0.0' => 3,
118 '240.0.0.0' => 4,
119 '248.0.0.0' => 5,
120 '252.0.0.0' => 6,
121 '254.0.0.0' => 7,
122 '255.0.0.0' => 8,
123 '255.128.0.0' => 9,
124 '255.192.0.0' => 10,
125 '255.224.0.0' => 11,
126 '255.240.0.0' => 12,
127 '255.248.0.0' => 13,
128 '255.252.0.0' => 14,
129 '255.254.0.0' => 15,
130 '255.255.0.0' => 16,
131 '255.255.128.0' => 17,
132 '255.255.192.0' => 18,
133 '255.255.224.0' => 19,
134 '255.255.240.0' => 20,
135 '255.255.248.0' => 21,
136 '255.255.252.0' => 22,
137 '255.255.254.0' => 23,
138 '255.255.255.0' => 24,
139 '255.255.255.128' => 25,
140 '255.255.255.192' => 26,
141 '255.255.255.224' => 27,
142 '255.255.255.240' => 28,
143 '255.255.255.248' => 29,
144 '255.255.255.252' => 30
145};
146
147my $ipv4_reverse_mask = [
148 '0.0.0.0',
149 '128.0.0.0',
150 '192.0.0.0',
151 '224.0.0.0',
152 '240.0.0.0',
153 '248.0.0.0',
154 '252.0.0.0',
155 '254.0.0.0',
156 '255.0.0.0',
157 '255.128.0.0',
158 '255.192.0.0',
159 '255.224.0.0',
160 '255.240.0.0',
161 '255.248.0.0',
162 '255.252.0.0',
163 '255.254.0.0',
164 '255.255.0.0',
165 '255.255.128.0',
166 '255.255.192.0',
167 '255.255.224.0',
168 '255.255.240.0',
169 '255.255.248.0',
170 '255.255.252.0',
171 '255.255.254.0',
172 '255.255.255.0',
173 '255.255.255.128',
174 '255.255.255.192',
175 '255.255.255.224',
176 '255.255.255.240',
177 '255.255.255.248',
178 '255.255.255.252',
179 '255.255.255.254',
180 '255.255.255.255',
181];
182
183my ($window, $cmdbox, $inbox, $htmlview);
184my ($next, $next_fctn, $target_hd);
185my ($progress, $progress_status);
186my ($ipversion, $ipaddress, $ipconf_entry_addr);
187my ($netmask, $ipconf_entry_mask);
188my ($gateway, $ipconf_entry_gw);
189my ($dnsserver, $ipconf_entry_dns);
190my $hostname = 'proxmox';
191my $domain = 'domain.tld';
192my $cmdline = file_read_firstline("/proc/cmdline");
193my $ipconf;
194my $country;
195my $timezone = 'Europe/Vienna';
196my $password;
197my $mailto;
198my $keymap = 'en-us';
199my $cmap;
200
201# parse command line args
202
203my $config_options = {};
204
205if ($cmdline =~ m/\s(ext3|ext4|xfs)(\s.*)?$/) {
206 $config_options->{filesys} = $1;
207} else {
208 $config_options->{filesys} = 'ext4';
209}
210
211if ($cmdline =~ m/hdsize=(\d+(\.\d+)?)[\s\n]/i) {
212 $config_options->{hdsize} = $1;
213}
214
215if ($cmdline =~ m/swapsize=(\d+(\.\d+)?)[\s\n]/i) {
216 $config_options->{swapsize} = $1;
217}
218
219if ($cmdline =~ m/maxroot=(\d+(\.\d+)?)[\s\n]/i) {
220 $config_options->{maxroot} = $1;
221}
222
223if ($cmdline =~ m/minfree=(\d+(\.\d+)?)[\s\n]/i) {
224 $config_options->{minfree} = $1;
225}
226
227if ($cmdline =~ m/maxvz=(\d+(\.\d+)?)[\s\n]/i) {
228 $config_options->{maxvz} = $1;
229}
230
231my $postfix_main_cf = <<_EOD;
232# See /usr/share/postfix/main.cf.dist for a commented, more complete version
233
234myhostname=__FQDN__
235
236smtpd_banner = \$myhostname ESMTP \$mail_name (Debian/GNU)
237biff = no
238
239# appending .domain is the MUA's job.
240append_dot_mydomain = no
241
242# Uncomment the next line to generate "delayed mail" warnings
243#delay_warning_time = 4h
244
245alias_maps = hash:/etc/aliases
246alias_database = hash:/etc/aliases
247mydestination = \$myhostname, localhost.\$mydomain, localhost
248relayhost =
249mynetworks = 127.0.0.0/8
250inet_interfaces = loopback-only
251recipient_delimiter = +
252
253_EOD
254
255sub shellquote {
256 my $str = shift;
257
258 return String::ShellQuote::shell_quote($str);
259}
260
261sub cmd2string {
262 my ($cmd) = @_;
263
264 die "no arguments" if !$cmd;
265
266 return $cmd if !ref($cmd);
267
268 my @qa = ();
269 foreach my $arg (@$cmd) { push @qa, shellquote($arg); }
270
271 return join (' ', @qa);
272}
273
274sub syscmd {
275 my ($cmd) = @_;
276
277 return run_command ($cmd, undef, undef, 1);
278}
279
280sub run_command {
281 my ($cmd, $func, $input, $noout) = @_;
282
283 my $cmdstr;
284 if (!ref($cmd)) {
285 $cmdstr = $cmd;
286 if ($cmd =~ m/|/) {
287 # see 'man bash' for option pipefail
288 $cmd = [ '/bin/bash', '-c', "set -o pipefail && $cmd" ];
289 } else {
290 $cmd = [ $cmd ];
291 }
292 } else {
293 $cmdstr = cmd2string($cmd);
294 }
295
296 my $cmdtxt;
297 if ($input && ($cmdstr !~ m/chpasswd/)) {
298 $cmdtxt = "# $cmdstr <<EOD\n$input";
299 chomp $cmdtxt;
300 $cmdtxt .= "\nEOD\n";
301 } else {
302 $cmdtxt = "# $cmdstr\n";
303 }
304
305 if ($opt_testmode) {
306 print $cmdtxt;
307 STDOUT->flush();
308 }
309
310 print $logfd $cmdtxt;
311
312 my $reader = IO::File->new();
313 my $writer = IO::File->new();
314 my $error = IO::File->new();
315
316 my $orig_pid = $$;
317
318 my $pid;
319 eval {
320 $pid = open3 ($writer, $reader, $error, @$cmd) || die $!;
321 };
322
323 my $err = $@;
324
325 # catch exec errors
326 if ($orig_pid != $$) {
327 POSIX::_exit (1);
328 kill ('KILL', $$);
329 }
330
331 die $err if $err;
332
333 print $writer $input if defined $input;
334 close $writer;
335
336 my $select = new IO::Select;
337 $select->add ($reader);
338 $select->add ($error);
339
340 my ($ostream, $logout) = ('', '', '');
341
342 while ($select->count) {
343 my @handles = $select->can_read (0.2);
344
345 Gtk3::main_iteration() while Gtk3::events_pending();
346
347 next if !scalar (@handles); # timeout
348
349 foreach my $h (@handles) {
350 my $buf = '';
351 my $count = sysread ($h, $buf, 4096);
352 if (!defined ($count)) {
353 my $err = $!;
354 kill (9, $pid);
355 waitpid ($pid, 0);
356 die "command '$cmd' failed: $err";
357 }
358 $select->remove ($h) if !$count;
359 if ($h eq $reader) {
360 $ostream .= $buf if !($noout || $func);
361 $logout .= $buf;
362 while ($logout =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) {
363 my $line = $1;
364 &$func($line) if $func;
365 }
366
367 } elsif ($h eq $error) {
368 $ostream .= $buf if !($noout || $func);
369 }
370 print $buf;
371 STDOUT->flush();
372 print $logfd $buf;
373 }
374 }
375
376 &$func($logout) if $func;
377
378 my $rv = waitpid ($pid, 0);
379
380 return $? if $noout; # behave like standard system();
381
382 my $ec = ($? >> 8);
383
384 if ($ec) {
385 die "command '$cmdstr' failed with exit code $ec";
386 }
387
388 return $ostream;
389}
390
391sub detect_country {
392
393 print "trying to detect country...\n";
394 my $cpid = open2(\*TMP, undef, "traceroute -N 1 -q 1 -n 8.8.8.8");
395 return undef if !$cpid;
396
397 my $country;
398
399 my $previous_alarm = alarm (10);
400 eval {
401 local $SIG{ALRM} = sub { die "timed out!\n" };
402 my $line;
403 while (defined ($line = <TMP>)) {
404 print $logfd "DC TRACEROUTE: $line";
405 if ($line =~ m/\s*\d+\s+(\d+\.\d+\.\d+\.\d+)\s/) {
406 my $geoip = `geoiplookup $1`;
407 print $logfd "DC GEOIP: $geoip";
408 if ($geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) {
409 $country = lc ($1);
410 print $logfd "DC FOUND: $country\n";
411 last;
412 }
413 }
414 }
415 };
416
417 my $err = $@;
418
419 alarm ($previous_alarm);
420
421 close (TMP);
422
423 if ($err) {
424 print "unable to detect country - $err\n";
425 } elsif ($country) {
426 print "detected country: " . uc($country) . "\n";
427 } else {
428 print "unable to detect country\n";
429 }
430
431 return $country;
432}
433
434sub get_memtotal {
435
436 open (MEMINFO, "/proc/meminfo");
437
438 my $res = 512; # default to 512 if something goes wrong
439 while (my $line = <MEMINFO>) {
440 if ($line =~ m/^MemTotal:\s+(\d+)\s*kB/i) {
441 $res = int ($1 / 1024);
442 }
443 }
444
445 close (MEMINFO);
446
447 return $res;
448}
449
450my $total_memory = get_memtotal();
451
452sub link_points_to {
453 my ($src, $dest) = @_;
454
455 my ($dev1,$ino1) = stat ($src);
456 my ($dev2,$ino2) = stat ($dest);
457
458 return 0 if !($dev1 && $dev2 && $ino1 && $ino2);
459
460 return $ino1 == $ino2 && $dev1 == $dev2;
461}
462
463sub find_stable_path {
464 my ($stabledir, $bdev) = @_;
465
466 foreach my $path (<$stabledir/*>) {
467 if (link_points_to ($path, $bdev)) {
468 return wantarray ? ($path, basename($path)) : $path;
469 }
470 }
471}
472
473sub find_dev_by_uuid {
474 my $bdev = shift;
475
476 my ($full_path, $name) = find_stable_path ("/dev/disk/by-uuid", $bdev);
477
478 return $name;
479}
480
481sub hd_list {
482
483 my $res = ();
484
485 if ($opt_testmode) {
486 push @$res, [-1, $opt_testmode, int((-s $opt_testmode)/512), "TESTDISK"];
487 return $res;
488 }
489
490 my $count = 0;
491
492 foreach my $bd (</sys/block/*>) {
493 next if $bd =~ m|^/sys/block/ram\d+$|;
494 next if $bd =~ m|^/sys/block/loop\d+$|;
495 next if $bd =~ m|^/sys/block/md\d+$|;
496 next if $bd =~ m|^/sys/block/dm-.*$|;
497 next if $bd =~ m|^/sys/block/fd\d+$|;
498 next if $bd =~ m|^/sys/block/sr\d+$|;
499
500 my $dev = file_read_firstline("$bd/dev");
501 chomp $dev;
502
503 next if !$dev;
504
505 my $info = `udevadm info --path $bd --query all`;
506 next if !$info;
507
508 next if $info !~ m/^E: DEVTYPE=disk$/m;
509
510 next if $info =~ m/^E: ID_CDROM/m;
511
512 my ($name) = $info =~ m/^N: (\S+)$/m;
513
514 if ($name) {
515 my $real_name = "/dev/$name";
516
517 my $size = file_read_firstline("$bd/size");
518 chomp $size;
519 $size = undef if !($size && $size =~ m/^\d+$/);
520
521 my $model = file_read_firstline("$bd/device/model") || '';
522 $model =~ s/^\s+//;
523 $model =~ s/\s+$//;
524 if (length ($model) > 30) {
525 $model = substr ($model, 0, 30);
526 }
527 push @$res, [$count++, $real_name, $size, $model] if $size;
528 } else {
529 print STDERR "ERROR: unable to map device $dev ($bd)\n";
530 }
531 }
532
533 return $res;
534}
535
536sub read_cmap {
537 my $countryfn = $opt_testmode ? "/usr/share/pve-manager/country.dat" :
538 "/cdrom/proxmox/country.dat";
539 open (TMP, "<$countryfn") || die "unable to open '$countryfn' - $!\n";
540 my $line;
541 my $country = {};
542 my $countryhash = {};
543 my $kmap = {};
544 my $kmaphash = {};
545 while (defined ($line = <TMP>)) {
546 if ($line =~ m|^map:([^\s:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]*):$|) {
547 $kmap->{$1} = {
548 name => $2,
549 kvm => $3,
550 console => $4,
551 x11 => $5,
552 x11var => $6,
553 };
554 $kmaphash->{$2} = $1;
555 } elsif ($line =~ m|^([a-z]{2}):([^:]+):([^:]*):([^:]*):$|) {
556 $country->{$1} = {
557 name => $2,
558 kmap => $3,
559 mirror => $4,
560 };
561 $countryhash->{lc($2)} = $1;
562 } else {
563 warn "unable to parse 'country.dat' line: $line";
564 }
565 }
566 close (TMP);
567
568 my $zones = {};
569 my $cczones = {};
570 my $zonefn = "/usr/share/zoneinfo/zone.tab";
571 open (TMP, "<$zonefn") || die "unable to open '$zonefn' - $!\n";
572 while (defined ($line = <TMP>)) {
573 next if $line =~ m/^\#/;
574 next if $line =~ m/^\s*$/;
575 if ($line =~ m|^([A-Z][A-Z])\s+\S+\s+(([^/]+)/\S+)\s|) {
576 my $cc = lc($1);
577 $cczones->{$cc}->{$2} = 1;
578 $country->{$cc}->{zone} = $2 if !defined ($country->{$cc}->{zone});
579 $zones->{$2} = 1;
580
581 }
582 }
583 close (TMP);
584
585 return {
586 zones => $zones,
587 cczones => $cczones,
588 country => $country,
589 countryhash => $countryhash,
590 kmap => $kmap,
591 kmaphash => $kmaphash,
592 }
593}
594
595# search for Harddisks
596my $hds = hd_list ();
597
598sub hd_size {
599 my ($dev) = @_;
600
601 foreach my $hd (@$hds) {
602 my ($disk, $devname, $size, $model) = @$hd;
603 # size is always in 512B "sectors"! convert to KB
604 return int($size/2) if $devname eq $dev;
605 }
606
607 die "no such device '$dev'\n";
608}
609
610sub get_partition_dev {
611 my ($dev, $partnum) = @_;
612
613 if ($dev =~ m|^/dev/[hxsev]d[a-z]$|) {
614 return "${dev}$partnum";
615 } elsif ($dev =~ m|^/dev/[^/]+/c\d+d\d+$|) {
616 return "${dev}p$partnum";
617 } elsif ($dev =~ m|^/dev/[^/]+/d\d+$|) {
618 return "${dev}p$partnum";
619 } elsif ($dev =~ m|^/dev/[^/]+/hd[a-z]$|) {
620 return "${dev}$partnum";
621 } elsif ($dev =~ m|^/dev/nvme\d+n\d+$|) {
622 return "${dev}p$partnum";
623 } else {
624 die "unable to get device for partition $partnum on device $dev\n";
625 }
626
627}
628
629sub file_get_contents {
630 my ($filename, $max) = @_;
631
632 my $fh = IO::File->new($filename, "r") ||
633 die "can't open '$filename' - $!\n";
634
635 local $/; # slurp mode
636
637 my $content = <$fh>;
638
639 close $fh;
640
641 return $content;
642}
643
644sub write_config {
645 my ($text, $filename) = @_;
646
647 my $fd = IO::File->new (">$filename") ||
648 die "unable to open file '$filename' - $!\n";
649 print $fd $text;
650 $fd->close();
651}
652
653sub update_progress {
654 my ($frac, $start, $end, $text) = @_;
655
656 my $part = $end - $start;
657 my $res = $start + $frac*$part;
658
659 $progress->set_fraction ($res);
660 $progress->set_text (sprintf ("%d%%", int ($res*100)));
661 $progress_status->set_text ($text) if defined ($text);
662
663 Gtk3::main_iteration() while Gtk3::events_pending();
664}
665
666my $fssetup = {
667 ext3 => {
668 mkfs => 'mkfs.ext3 -F',
669 mkfs_root_opt => '',
670 mkfs_data_opt => '-m 0',
671 root_mountopt => 'errors=remount-ro',
672 },
673 ext4 => {
674 mkfs => 'mkfs.ext4 -F',
675 mkfs_root_opt => '',
676 mkfs_data_opt => '-m 0',
677 root_mountopt => 'errors=remount-ro',
678 },
679 xfs => {
680 mkfs => 'mkfs.xfs -f',
681 mkfs_root_opt => '',
682 mkfs_data_opt => '',
683 root_mountopt => '',
684 },
685};
686
687sub create_filesystem {
688 my ($dev, $name, $type, $start, $end, $fs, $fe) = @_;
689
690 my $range = $end - $start;
691 my $rs = $start + $range*$fs;
692 my $re = $start + $range*$fe;
693 my $max = 0;
694
695 my $fsdata = $fssetup->{$type} || die "internal error - unknown file system '$type'";
696 my $opts = $name eq 'root' ? $fsdata->{mkfs_root_opt} : $fsdata->{mkfs_data_opt};
697
698 update_progress (0, $rs, $re, "creating $name filesystem");
699
700 run_command ("$fsdata->{mkfs} $opts $dev", sub {
701 my $line = shift;
702
703 if ($line =~ m/Writing inode tables:\s+(\d+)\/(\d+)/) {
704 $max = $2;
705 } elsif ($max && $line =~ m/(\d+)\/$max/) {
706 update_progress (($1/$max)*0.9, $rs, $re);
707 } elsif ($line =~ m/Creating journal.*done/) {
708 update_progress (0.95, $rs, $re);
709 } elsif ($line =~ m/Writing superblocks and filesystem.*done/) {
710 update_progress (1, $rs, $re);
711 }
712 });
713}
714
715sub debconfig_set {
716 my ($targetdir, $dcdata) = @_;
717
718 my $cfgfile = "/tmp/debconf.txt";
719 write_config ($dcdata, "$targetdir/$cfgfile");
720 syscmd ("chroot $targetdir debconf-set-selections $cfgfile");
721 unlink "$targetdir/$cfgfile";
722}
723
724sub diversion_add {
725 my ($targetdir, $cmd, $new_cmd) = @_;
726
727 syscmd ("chroot $targetdir dpkg-divert --package proxmox " .
728 "--add --rename $cmd") == 0 ||
729 die "unable to exec dpkg-divert\n";
730
731 syscmd ("ln -sf ${new_cmd} $targetdir/$cmd") == 0 ||
732 die "unable to link diversion to ${new_cmd}\n";
733}
734
735sub diversion_remove {
736 my ($targetdir, $cmd) = @_;
737
738 syscmd ("mv $targetdir/${cmd}.distrib $targetdir/${cmd};") == 0 ||
739 die "unable to remove $cmd diversion\n";
740
741 syscmd ("chroot $targetdir dpkg-divert --remove $cmd") == 0 ||
742 die "unable to remove $cmd diversion\n";
743}
744
745sub btrfs_create {
746 my ($partitions, $mode) = @_;
747
748 die "unknown btrfs mode '$mode'"
749 if !($mode eq 'single' || $mode eq 'raid0' ||
750 $mode eq 'raid1' || $mode eq 'raid10');
751
752 my $cmd = ['mkfs.btrfs', '-f'];
753
754 push @$cmd, '-d', $mode, '-m', $mode;
755
756 push @$cmd, @$partitions;
757
758 syscmd($cmd);
759}
760
761sub zfs_create_rpool {
762 my ($vdev) = @_;
763
764 my $cmd = "zpool create -f -o cachefile=none";
765
766 $cmd .= " -o ashift=$config_options->{ashift}"
767 if defined($config_options->{ashift});
768
769 syscmd ("$cmd $zfspoolname $vdev") == 0 ||
770 die "unable to create zfs root pool\n";
771
772 syscmd ("zfs create $zfspoolname/ROOT") == 0 ||
773 die "unable to create zfs $zfspoolname/ROOT volume\n";
774
775 syscmd ("zfs create $zfspoolname/data") == 0 ||
776 die "unable to create zfs $zfspoolname/data volume\n";
777
778 syscmd ("zfs create $zfspoolname/ROOT/pve-1") == 0 ||
779 die "unable to create zfs $zfspoolname/ROOT/pve-1 volume\n";
780
781 # disable atime during insatll
782 syscmd ("zfs set atime=off $zfspoolname") == 0 ||
783 die "unable to set zfs properties\n";
784
785 my $value = $config_options->{compress};
786 syscmd ("zfs set compression=$value $zfspoolname")
787 if defined($value) && $value ne 'off';
788
789 $value = $config_options->{checksum};
790 syscmd ("zfs set checksum=$value $zfspoolname")
791 if defined($value) && $value ne 'on';
792
793 $value = $config_options->{copies};
794 syscmd ("zfs set copies=$value $zfspoolname")
795 if defined($value) && $value != 1;
796}
797
798sub zfs_create_swap {
799 my ($swapsize) = @_;
800
801 my $cmd = "zfs create -V ${swapsize}K -b 4K";
802
803 $cmd .= " -o com.sun:auto-snapshot=false";
804
805 # copies for swap does not make sense
806 $cmd .= " -o copies=1";
807
808 # reduces memory pressure
809 $cmd .= " -o sync=always";
810
811 # cheapest compression to drop zero pages
812 $cmd .= " -o compression=zle";
813
814 # skip log devices
815 $cmd .= " -o logbias=throughput";
816 # only cache metadata in RAM (caching swap content does not make sense)
817 $cmd .= " -o primarycache=metadata";
818 # don't cache anything in L2ARC
819 $cmd .= " -o secondarycache=none";
820
821 $cmd .= " $zfspoolname/swap";
822 syscmd ($cmd) == 0 ||
823 die "unable to create zfs swap device\n";
824
825 return "/dev/zvol/$zfspoolname/swap";
826}
827
828my $udevadm_trigger_block = sub {
829 my ($nowait) = @_;
830
831 sleep(1) if !$nowait; # give kernel time to reread part table
832
833 # trigger udev to create /dev/disk/by-uuid
834 syscmd ("udevadm trigger --subsystem-match block");
835 syscmd ("udevadm settle --timeout 10");
836};
837
838my $clean_disk = sub {
839 my ($disk) = @_;
840
841 my $partitions = `lsblk --output kname --noheadings --path --list $disk`;
842 foreach my $part (split "\n", $partitions) {
843 next if $part eq $disk;
844 next if $part !~ /^\Q$disk\E/;
845 eval { syscmd("pvremove -ff -y $part"); };
846 eval { syscmd("dd if=/dev/zero of=$part bs=1M count=16"); };
847 }
848};
849
850sub partition_bootable_disk {
851 my ($target_dev, $maxhdsize, $ptype) = @_;
852
853 die "too dangerous" if $opt_testmode;
854
855 die "unknown partition type '$ptype'"
856 if !($ptype eq '8E00' || $ptype eq '8300');
857
858 syscmd("sgdisk -Z ${target_dev}");
859 my $hdsize = hd_size($target_dev); # size in KB (1024 bytes)
860
861 my $restricted_hdsize_mb = 0; # 0 ==> end of partition
862 if ($maxhdsize && ($maxhdsize < $hdsize)) {
863 $hdsize = $maxhdsize;
864 $restricted_hdsize_mb = int($hdsize/1024) . 'M';
865 }
866
867 my $hdgb = int($hdsize/(1024*1024));
868 die "hardisk '$target_dev' too small (${hdsize}GB)\n" if $hdgb < 8;
869
870 # 1 - BIOS boot partition (Grub Stage2): first free 1M
871 # 2 - EFI ESP: next free 256M
872 # 3 - OS/Data partition: rest, up to $maxhdsize in MB
873
874 my $grubbootdev = get_partition_dev($target_dev, 1);
875 my $efibootdev = get_partition_dev($target_dev, 2);
876 my $osdev = get_partition_dev ($target_dev, 3);
877
878 my $pcmd = ['sgdisk'];
879
880 my $pnum = 1;
881 push @$pcmd, "-n${pnum}:1M:+1M", "-t$pnum:EF02";
882
883 $pnum = 2;
884 push @$pcmd, "-n${pnum}:2M:+256M", "-t$pnum:EF00";
885
886 $pnum = 3;
887 push @$pcmd, "-n${pnum}:258M:${restricted_hdsize_mb}", "-t$pnum:$ptype";
888
889 push @$pcmd, $target_dev;
890
891 my $os_size = $hdsize - 258*1024; # 256M + 1M + 1M alignment
892
893 syscmd($pcmd) == 0 ||
894 die "unable to partition harddisk '${target_dev}'\n";
895
896 &$udevadm_trigger_block();
897
898 foreach my $part ($efibootdev, $osdev) {
899 syscmd("dd if=/dev/zero of=$part bs=1M count=256") if -b $part;
900 }
901
902 return ($os_size, $osdev, $efibootdev);
903}
904
905# ZFS has this use_whole_disk concept, so we try to partition the same
906# way as zfs does by default. There is room at start of disk to insert
907# a grub boot partition. But adding a EFI ESP is not possible.
908#
909# Note: zfs people think this is just a waste of space an not
910# required. Instead, you should put the ESP on another disk (log,
911# ..).
912
913sub partition_bootable_zfs_disk {
914 my ($target_dev) = @_;
915
916 die "too dangerous" if $opt_testmode;
917
918 syscmd("sgdisk -Z ${target_dev}");
919 my $hdsize = hd_size($target_dev); # size in blocks (1024 bytes)
920
921 my $hdgb = int($hdsize/(1024*1024));
922 die "hardisk '$target_dev' too small (${hdsize}GB)\n" if $hdgb < 8;
923
924 # 1 - GRUB boot partition: 1M
925 # 2 - OS/Data partition
926 # 9 - ZFS reserved partition
927
928 my $grubbootdev = get_partition_dev($target_dev, 1);
929 my $osdev = get_partition_dev ($target_dev, 2);
930
931 my $pcmd = ['sgdisk', '-a1'];
932
933 my $pnum = 1;
934 push @$pcmd, "-n$pnum:34:2047", "-t$pnum:EF02";
935
936 $pnum = 9;
937 push @$pcmd, "-n$pnum:-8M:0", "-t$pnum:BF07";
938
939 $pnum = 2;
940 push @$pcmd, "-n$pnum:2048:0", "-t$pnum:BF01", '-c', "$pnum:zfs";
941
942 push @$pcmd, $target_dev;
943
944 my $os_size = $hdsize - 1024 - 1024*8;
945
946 syscmd($pcmd) == 0 ||
947 die "unable to partition harddisk '${target_dev}'\n";
948
949 &$udevadm_trigger_block();
950
951 syscmd("dd if=/dev/zero of=$osdev bs=1M count=16") if -b $osdev;
952
953 return ($os_size, $osdev);
954}
955
956sub create_lvm_volumes {
957 my ($lvmdev, $os_size, $swap_size) = @_;
958
959 my $rootdev = '/dev/pve/root';
960 my $datadev = '/dev/pve/data';
961 my $swapfile = '/dev/pve/swap';
962
963 # we use --metadatasize 250k, which reseults in "pe_start = 512"
964 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
965 syscmd ("/sbin/pvcreate --metadatasize 250k -y -ff $lvmdev") == 0 ||
966 die "unable to initialize physical volume $lvmdev\n";
967 syscmd ("/sbin/vgcreate pve $lvmdev") == 0 ||
968 die "unable to create volume group 'pve'\n";
969
970 my $hdgb = int($os_size/(1024*1024));
971 my $space = (($hdgb > 128) ? 16 : ($hdgb/8))*1024*1024;
972
973 my $maxroot;
974 if ($config_options->{maxroot}) {
975 $maxroot = $config_options->{maxroot};
976 } else {
977 $maxroot = 96;
978 }
979
980 my $rootsize = (($hdgb > ($maxroot*4)) ? $maxroot : $hdgb/4)*1024*1024;
981
982 my $rest = $os_size - $swap_size - $rootsize; # in KB
983
984 my $minfree;
985 if ($config_options->{minfree}) {
986 $minfree = (($config_options->{minfree}*1024*1024) >= $rest ) ? $space :
987 $config_options->{minfree}*1024*1024 ;
988 } else {
989 $minfree = $space;
990 }
991
992 $rest = $rest - $minfree;
993
994 if ($config_options->{maxvz}) {
995 $rest = (($config_options->{maxvz}*1024*1024) <= $rest) ?
996 $config_options->{maxvz}*1024*1024 : $rest;
997 }
998
999 syscmd ("/sbin/lvcreate -L${swap_size}K -nswap pve") == 0 ||
1000 die "unable to create swap volume\n";
1001
1002 syscmd ("/sbin/lvcreate -L${rootsize}K -nroot pve") == 0 ||
1003 die "unable to create root volume\n";
1004
1005 syscmd ("/sbin/lvcreate -L${rest}K -ndata pve") == 0 ||
1006 die "unable to create data volume\n";
1007
1008 syscmd ("/sbin/lvconvert --yes --type thin-pool pve/data") == 0 ||
1009 die "unable to create data thin-pool\n";
1010
1011 syscmd ("/sbin/vgchange -a y pve") == 0 ||
1012 die "unable to activate volume group\n";
1013
1014 return ($rootdev, $datadev, $swapfile);
1015}
1016
1017sub compute_swapsize {
1018 my ($hdsize) = @_;
1019
1020 my $hdgb = int($hdsize/(1024*1024));
1021
1022 my $swapsize;
1023 if ($config_options->{swapsize}) {
1024 $swapsize = $config_options->{swapsize}*1024*1024;
1025 } else {
1026 my $ss = int ($total_memory / 1024);
1027 $ss = 4 if $ss < 4;
1028 $ss = ($hdgb/8) if $ss > ($hdgb/8);
1029 $ss = 8 if $ss > 8;
1030 $swapsize = $ss*1024*1024;
1031 }
1032
1033 return $swapsize;
1034}
1035
1036
1037sub extract_data {
1038 my ($basefile, $targetdir) = @_;
1039
1040 die "target '$targetdir' does not exist\n" if ! -d $targetdir;
1041
1042 my $starttime = [Time::HiRes::gettimeofday];
1043
1044 my $bootdevinfo = [];
1045
1046 my $datadev;
1047 my $swapfile;
1048 my $rootdev;
1049
1050 my $use_zfs = 0;
1051 my $use_btrfs = 0;
1052
1053 my $filesys = $config_options->{filesys};
1054
1055 if ($filesys =~ m/zfs/) {
1056 $target_hd = undef; # do not use this config
1057 $use_zfs = 1;
1058 $targetdir = "/$zfspoolname/ROOT/pve-1";
1059 } elsif ($filesys =~ m/btrfs/) {
1060 $target_hd = undef; # do not use this config
1061 $use_btrfs = 1;
1062 }
1063
1064 if ($use_zfs) {
1065 my $i;
1066 for ($i = 5; $i > 0; $i--) {
1067 syscmd("modprobe zfs");
1068 last if -c "/dev/zfs";
1069 sleep(1);
1070 }
1071
1072 die "unable to load zfs kernel module\n" if !$i;
1073 }
1074
1075 eval {
1076
1077
1078 my $maxper = 0.25;
1079
1080 update_progress (0, 0, $maxper, "create partitions");
1081
1082 syscmd("vgchange -an") if !$opt_testmode; # deactivate all detected VGs
1083
1084 if ($opt_testmode) {
1085
1086 $rootdev = abs_path($opt_testmode);
1087 syscmd("umount $rootdev");
1088
1089 if ($use_btrfs) {
1090
1091 die "unsupported btrfs mode (for testing environment)\n"
1092 if $filesys ne 'btrfs (RAID0)';
1093
1094 btrfs_create([$rootdev], 'single');
1095
1096 } elsif ($use_zfs) {
1097
1098 die "unsupported zfs mode (for testing environment)\n"
1099 if $filesys ne 'zfs (RAID0)';
1100
1101 syscmd ("zpool destroy $zfstestpool");
1102
1103 zfs_create_rpool($rootdev);
1104
1105 } else {
1106
1107 # nothing to do
1108 }
1109
1110 } elsif ($use_btrfs) {
1111
1112 my ($devlist, $btrfs_mode) = get_btrfs_raid_setup();
1113 my $btrfs_partitions = [];
1114 my $disksize;
1115 foreach my $hd (@$devlist) {
1116 my $devname = @$hd[1];
1117 &$clean_disk($devname);
1118 my ($size, $osdev, $efidev) =
1119 partition_bootable_disk($devname, undef, '8300');
1120 $rootdev = $osdev if !defined($rootdev); # simply point to first disk
1121 my $by_id = find_stable_path("/dev/disk/by-id", $devname);
1122 push @$bootdevinfo, { esp => $efidev, devname => $devname,
1123 osdev => $osdev, by_id => $by_id };
1124 push @$btrfs_partitions, $osdev;
1125 $disksize = $size;
1126 }
1127
1128 &$udevadm_trigger_block();
1129
1130 btrfs_create($btrfs_partitions, $btrfs_mode);
1131
1132 } elsif ($use_zfs) {
1133
1134 my ($devlist, $bootdevlist, $vdev) = get_zfs_raid_setup();
1135
1136 my $disksize;
1137 foreach my $hd (@$devlist) {
1138 &$clean_disk(@$hd[1]);
1139 }
1140 foreach my $hd (@$bootdevlist) {
1141 my $devname = @$hd[1];
1142 my ($size, $osdev) =
1143 partition_bootable_zfs_disk($devname);
1144 zfs_mirror_size_check($disksize, $size) if $disksize;
1145 push @$bootdevinfo, { devname => $devname, osdev => $osdev};
1146 $disksize = $size;
1147 }
1148
1149 &$udevadm_trigger_block();
1150
1151 foreach my $di (@$bootdevinfo) {
1152 my $devname = $di->{devname};
1153 $di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
1154
1155 # Note: using /dev/disk/by-id/ does not work for unknown reason, we get
1156 # cannot create 'rpool': no such pool or dataset
1157 #my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
1158
1159 my $osdev = $di->{osdev};
1160 $vdev =~ s/ $devname/ $osdev/;
1161 }
1162
1163 zfs_create_rpool($vdev);
1164
1165 my $swap_size = compute_swapsize($disksize);
1166 $swapfile = zfs_create_swap($swap_size);
1167
1168 } else {
1169
1170 die "target '$target_hd' is not a valid block device\n" if ! -b $target_hd;
1171
1172 my $maxhdsize;
1173 if ($config_options->{hdsize}) {
1174 # max hdsize passed on cmdline (GB)
1175 $maxhdsize = $config_options->{hdsize}*1024*1024;
1176 }
1177
1178 &$clean_disk($target_hd);
1179
1180 my ($os_size, $osdev, $efidev);
1181 ($os_size, $osdev, $efidev) =
1182 partition_bootable_disk($target_hd, $maxhdsize, '8E00');
1183
1184 &$udevadm_trigger_block();
1185
1186 my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
1187 push @$bootdevinfo, { esp => $efidev, devname => $target_hd,
1188 osdev => $osdev, by_id => $by_id };
1189
1190 my $swap_size = compute_swapsize($os_size);
1191 ($rootdev, $datadev, $swapfile) =
1192 create_lvm_volumes($osdev, $os_size, $swap_size);
1193
1194 # trigger udev to create /dev/disk/by-uuid
1195 &$udevadm_trigger_block(1);
1196 }
1197
1198 if ($use_zfs) {
1199 # to be fast during installation
1200 syscmd ("zfs set sync=disabled $zfspoolname") == 0 ||
1201 die "unable to set zfs properties\n";
1202 }
1203
1204 update_progress (0.03, 0, $maxper, "create swap space");
1205 if ($swapfile) {
1206 syscmd ("mkswap -f $swapfile") == 0 ||
1207 die "unable to create swap space\n";
1208 }
1209
1210 update_progress (0.05, 0, $maxper, "creating filesystems");
1211
1212 foreach my $di (@$bootdevinfo) {
1213 next if !$di->{esp};
1214 syscmd ("mkfs.vfat -F32 $di->{esp}") == 0 ||
1215 die "unable to initialize EFI ESP on device $di->{esp}\n";
1216 }
1217
1218 if ($use_zfs) {
1219 # do nothing
1220 } elsif ($use_btrfs) {
1221 # do nothing
1222 } else {
1223 create_filesystem ($rootdev, 'root', $filesys, 0.05, $maxper, 0, 1);
1224 }
1225
1226 update_progress (1, 0.05, $maxper, "mounting target $rootdev");
1227
1228 if ($use_zfs) {
1229 # do nothing
1230 } else {
1231 my $mount_opts = 'noatime';
1232 $mount_opts .= ',nobarrier'
1233 if $use_btrfs || $filesys =~ /^ext\d$/;
1234
1235 syscmd("mount -n $rootdev -o $mount_opts $targetdir") == 0 ||
1236 die "unable to mount $rootdev\n";
1237 }
1238
1239 mkdir "$targetdir/boot";
1240 mkdir "$targetdir/boot/efi";
1241
1242 mkdir "$targetdir/var";
1243 mkdir "$targetdir/var/lib";
1244 mkdir "$targetdir/var/lib/vz";
1245 mkdir "$targetdir/var/lib/pve";
1246
1247 if ($use_btrfs) {
1248 syscmd("btrfs subvolume create $targetdir/var/lib/pve/local-btrfs") == 0 ||
1249 die "unable to create btrfs subvolume\n";
1250 }
1251
1252 display_html ("extract2-rulesystem.htm");
1253 update_progress (1, 0.05, $maxper, "extracting base system");
1254
1255 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
1256 $ino || die "unable to open file '$basefile' - $!\n";
1257
1258 my $files;
1259 if ($opt_testmode) {
1260 $files = file_read_firstline("/pve/$release/install/pve-base.cnt");
1261 } else {
1262 $files = file_read_firstline("/cdrom/proxmox/pve-base.cnt");
1263 }
1264
1265 my $per = 0;
1266 my $count = 0;
1267
1268 run_command ("unsquashfs -f -dest $targetdir -i $basefile", sub {
1269 my $line = shift;
1270 return if $line !~ m/^$targetdir/;
1271 $count++;
1272 my $nper = int (($count *100)/$files);
1273 if ($nper != $per) {
1274 $per = $nper;
1275 my $frac = $per > 100 ? 1 : $per/100;
1276 update_progress ($frac, $maxper, 0.5);
1277 }
1278 });
1279
1280 syscmd ("mount -n -t tmpfs tmpfs $targetdir/tmp") == 0 ||
1281 die "unable to mount tmpfs on $targetdir/tmp\n";
1282 syscmd ("mount -n -t proc proc $targetdir/proc") == 0 ||
1283 die "unable to mount proc on $targetdir/proc\n";
1284 syscmd ("mount -n -t sysfs sysfs $targetdir/sys") == 0 ||
1285 die "unable to mount sysfs on $targetdir/sys\n";
1286
1287 display_html ("extract3-spam.htm");
1288 update_progress (1, $maxper, 0.5, "configuring base system");
1289
1290 # configure hosts
1291
1292 my $hosts =
1293 "127.0.0.1 localhost.localdomain localhost\n" .
1294 "$ipaddress $hostname.$domain $hostname pvelocalhost\n\n" .
1295 "# The following lines are desirable for IPv6 capable hosts\n\n" .
1296 "::1 ip6-localhost ip6-loopback\n" .
1297 "fe00::0 ip6-localnet\n" .
1298 "ff00::0 ip6-mcastprefix\n" .
1299 "ff02::1 ip6-allnodes\n" .
1300 "ff02::2 ip6-allrouters\n" .
1301 "ff02::3 ip6-allhosts\n";
1302
1303 write_config ($hosts, "$targetdir/etc/hosts");
1304
1305 write_config ("$hostname\n", "$targetdir/etc/hostname");
1306
1307 syscmd ("/bin/hostname $hostname") if !$opt_testmode;
1308
1309 # configure interfaces
1310
1311 my $ifaces = "auto lo\niface lo inet loopback\n\n";
1312
1313 my $ntype = $ipversion == 4 ? 'inet' : 'inet6';
1314
1315 my $bridge_port = $ipconf->{ifaces}->{$ipconf->{selected}}->{name};
1316
1317 $ifaces .= "iface $bridge_port $ntype manual\n";
1318
1319 $ifaces .=
1320 "\nauto vmbr0\niface vmbr0 $ntype static\n" .
1321 "\taddress $ipaddress\n" .
1322 "\tnetmask $netmask\n" .
1323 "\tgateway $gateway\n" .
1324 "\tbridge_ports $bridge_port\n" .
1325 "\tbridge_stp off\n" .
1326 "\tbridge_fd 0\n";
1327
1328 foreach my $iface (sort keys %{$ipconf->{ifaces}}) {
1329 my $name = $ipconf->{ifaces}->{$iface}->{name};
1330 next if $name eq $bridge_port;
1331
1332 $ifaces .= "\niface $name $ntype manual\n";
1333 }
1334
1335 write_config ($ifaces, "$targetdir/etc/network/interfaces");
1336
1337 # configure dns
1338
1339 my $resolvconf = "search $domain\nnameserver $dnsserver\n";
1340 write_config ($resolvconf, "$targetdir/etc/resolv.conf");
1341
1342 # configure fstab
1343
1344 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass>\n";
1345
1346 if ($use_zfs) {
1347 # do nothing
1348 } elsif ($use_btrfs) {
1349 my $fsuuid;
1350 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev";
1351 run_command($cmd, sub {
1352 my $line = shift;
1353
1354 if ($line =~ m/^UUID=([A-Fa-f0-9\-]+)$/) {
1355 $fsuuid = $1;
1356 }
1357 });
1358
1359 die "unable to detect FS UUID" if !defined($fsuuid);
1360
1361 $fstab .= "UUID=$fsuuid / btrfs defaults 0 1\n";
1362 } else {
1363 my $root_mountopt = $fssetup->{$filesys}->{root_mountopt} || 'defaults';
1364 $fstab .= "$rootdev / $filesys ${root_mountopt} 0 1\n";
1365 }
1366
1367 # mount /boot/efi
1368 # Note: this is required by current grub, but really dangerous, because
1369 # vfat does not have journaling, so it triggers manual fsck after each crash
1370 # so we only mount /boot/efi if really required (efi systems).
1371 if ($grub_plattform =~ m/^efi-/) {
1372 if (scalar(@$bootdevinfo)) {
1373 my $di = @$bootdevinfo[0]; # simply use first disk
1374 if ($di->{esp}) {
1375 my $efi_boot_uuid = $di->{esp};
1376 if (my $uuid = find_dev_by_uuid ($di->{esp})) {
1377 $efi_boot_uuid = "UUID=$uuid";
1378 }
1379
1380 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1\n";
1381 }
1382 }
1383 }
1384
1385
1386 $fstab .= "$swapfile none swap sw 0 0\n" if $swapfile;
1387
1388 $fstab .= "proc /proc proc defaults 0 0\n";
1389
1390 write_config ($fstab, "$targetdir/etc/fstab");
1391 write_config ("", "$targetdir/etc/mtab");
1392
1393 syscmd ("cp ${proxmox_dir}/policy-disable-rc.d " .
1394 "$targetdir/usr/sbin/policy-rc.d") == 0 ||
1395 die "unable to copy policy-rc.d\n";
1396 syscmd ("cp ${proxmox_dir}/fake-start-stop-daemon " .
1397 "$targetdir/sbin/") == 0 ||
1398 die "unable to copy start-stop-daemon\n";
1399
1400 diversion_add ($targetdir, "/sbin/start-stop-daemon", "/sbin/fake-start-stop-daemon");
1401 diversion_add ($targetdir, "/usr/sbin/update-grub", "/bin/true");
1402 diversion_add ($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
1403
1404 syscmd ("touch $targetdir/proxmox_install_mode");
1405
1406 my $grub_install_devices_txt = '';
1407 foreach my $di (@$bootdevinfo) {
1408 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt;
1409 $grub_install_devices_txt .= $di->{by_id} || $di->{devname};
1410 }
1411
1412 # Note: debian-installer/locale is used by console-setup
1413
1414 debconfig_set ($targetdir, <<_EOD);
1415locales locales/default_environment_locale select en_US.UTF-8
1416locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1417samba-common samba-common/dhcp boolean false
1418samba-common samba-common/workgroup string WORKGROUP
1419postfix postfix/main_mailer_type select Local only
1420keyboard-configuration keyboard-configuration/xkb-keymap select $keymap
1421d-i debian-installer/locale select en_US.UTF-8
1422grub-pc grub-pc/install_devices select $grub_install_devices_txt
1423_EOD
1424
1425 my $pkgdir = $opt_testmode ? "packages" : "/cdrom/proxmox/packages";
1426 my $pkg_count = 0;
1427 while (<$pkgdir/*.deb>) { $pkg_count++ };
1428
1429 # btrfs/dpkg is extremely slow without --force-unsafe-io
1430 my $dpkg_opts = $use_btrfs ? "--force-unsafe-io" : "";
1431
1432 $count = 0;
1433 while (<$pkgdir/*.deb>) {
1434 chomp;
1435 my $path = $_;
1436 my ($deb) = $path =~ m/$pkgdir\/(.*\.deb)/;
1437# if ($deb =~ m/^grub-efi-/ && $deb !~ m/^grub-${grub_plattform}/) {
1438# $count++;
1439# next;
1440# }
1441 update_progress ($count/$pkg_count, 0.5, 0.75, "extracting $deb");
1442 print "extracting: $deb\n";
1443 syscmd ("cp $path $targetdir/tmp/$deb") == 0 ||
1444 die "installation of package $deb failed\n";
1445 syscmd ("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/$deb") == 0 ||
1446 die "installation of package $deb failed\n";
1447 update_progress ((++$count)/$pkg_count, 0.5, 0.75);
1448 }
1449
1450 display_html ("extract4-virus.htm");
1451
1452 # needed for postfix postinst in case no other NIC is active
1453 syscmd("chroot $targetdir ifup lo");
1454
1455 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a";
1456 $count = 0;
1457 run_command ($cmd, sub {
1458 my $line = shift;
1459 if ($line =~ m/Setting up\s+(\S+)/) {
1460 update_progress ((++$count)/$pkg_count, 0.75, 0.95,
1461 "configuring $1");
1462 }
1463 });
1464
1465 debconfig_set ($targetdir, <<_EOD);
1466postfix postfix/main_mailer_type select No configuration
1467_EOD
1468
1469 unlink "$targetdir/etc/mailname";
1470 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
1471 write_config ($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
1472
1473 # make sure we have all postfix directories
1474 syscmd ("chroot $targetdir /usr/sbin/postfix check");
1475 # cleanup mail queue
1476 syscmd ("chroot $targetdir /usr/sbin/postsuper -d ALL");
1477
1478 # enable NTP (timedatectl set-ntp true does not work without DBUS)
1479 syscmd ("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
1480
1481 unlink "$targetdir/proxmox_install_mode";
1482
1483 # set timezone
1484 unlink ("$targetdir/etc/localtime");
1485 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
1486 write_config ("$timezone\n", "$targetdir/etc/timezone");
1487
1488 # set apt mirror
1489 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1490 my $fn = "$targetdir/etc/apt/sources.list";
1491 syscmd ("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
1492 }
1493
1494 # create extended_states for apt (avoid cron job warning if that
1495 # file does not exist)
1496 write_config ('', "$targetdir/var/lib/apt/extended_states");
1497
1498 # allow ssh root login
1499 syscmd ("sed -i 's/^PermitRootLogin.*/PermitRootLogin yes/' '$targetdir/etc/ssh/sshd_config'");
1500
1501 # save installer settings
1502 my $ucc = uc ($country);
1503 debconfig_set ($targetdir, <<_EOD);
1504pve-manager pve-manager/country string $ucc
1505_EOD
1506
1507 update_progress (0.8, 0.95, 1, "make system bootable");
1508
1509 if ($use_zfs) {
1510 syscmd ("sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT\\/pve-1 boot=zfs\"/' $targetdir/etc/default/grub") == 0 ||
1511 die "unable to update /etc/default/grub\n";
1512
1513 }
1514
1515 diversion_remove ($targetdir, "/usr/sbin/update-grub");
1516 diversion_remove ($targetdir, "/usr/sbin/update-initramfs");
1517
1518 if (!$opt_testmode) {
1519
1520 unlink ("$targetdir/etc/mtab");
1521 symlink ("/proc/mounts", "$targetdir/etc/mtab");
1522 syscmd ("mount -n --bind /dev $targetdir/dev");
1523
1524 syscmd ("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1525 die "unable to install initramfs\n";
1526
1527 foreach my $di (@$bootdevinfo) {
1528 my $dev = $di->{devname};
1529 syscmd ("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1530 die "unable to install the i386-pc boot loader on '$dev'\n";
1531
1532 if ($di->{esp}) {
1533 syscmd ("mount -n $di->{esp} -t vfat $targetdir/boot/efi") == 0 ||
1534 die "unable to mount $di->{esp}\n";
1535 my $rc = syscmd ("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
1536 if ($rc != 0) {
1537 if (-d '/sys/firmware/efi') {
1538 die "unable to install the EFI boot loader on '$dev'\n";
1539 } else {
1540 warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
1541 }
1542 }
1543 # also install fallback boot file (OVMF does not boot without)
1544 mkdir("$targetdir/boot/efi/EFI/BOOT");
1545 syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
1546 die "unable to copy efi boot loader\n";
1547
1548 syscmd ("umount $targetdir/boot/efi") == 0 ||
1549 die "unable to umount $targetdir/boot/efi\n";
1550 }
1551 }
1552
1553 syscmd ("chroot $targetdir /usr/sbin/update-grub") == 0 ||
1554 die "unable to update boot loader config\n";
1555
1556 syscmd ("umount $targetdir/dev");
1557 }
1558
1559 # cleanup
1560
1561 # hack: remove dead.letter from sshd installation
1562 syscmd ("rm -rf $targetdir/dead.letter");
1563
1564 unlink "$targetdir/usr/sbin/policy-rc.d";
1565
1566 diversion_remove ($targetdir, "/sbin/start-stop-daemon");
1567
1568 # set root password
1569 my $octets = encode("utf-8", $password);
1570 run_command ("chroot $targetdir /usr/sbin/chpasswd", undef,
1571 "root:$octets\n");
1572
1573 # create pmxcfs DB
1574
1575 my $tmpdir = "$targetdir/tmp/pve";
1576 mkdir $tmpdir;
1577
1578 # write vnc keymap to datacenter.cfg
1579 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
1580 write_config ("keyboard: $vnckmap\n",
1581 "$tmpdir/datacenter.cfg");
1582
1583 # save admin email
1584 write_config ("user:root\@pam:1:0:::${mailto}::\n",
1585 "$tmpdir/user.cfg");
1586
1587 # write storage.cfg
1588 my $strorage_cfg_fn = "$tmpdir/storage.cfg";
1589 if ($use_zfs) {
1590 write_config ($storage_cfg_zfs, $strorage_cfg_fn);
1591 } elsif ($use_btrfs) {
1592 write_config ($storage_cfg_btrfs, $strorage_cfg_fn);
1593 } else {
1594 write_config ($storage_cfg_lvmthin, $strorage_cfg_fn);
1595 }
1596
1597 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1598
1599 syscmd ("rm -rf $tmpdir");
1600 };
1601
1602 my $err = $@;
1603
1604 update_progress (1, 0, 1, "");
1605
1606 print $err if $err;
1607
1608 if ($opt_testmode) {
1609 my $elapsed = Time::HiRes::tv_interval($starttime);
1610 print "Elapsed extract time: $elapsed\n";
1611
1612 syscmd ("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> pve-final.pkglist");
1613 }
1614
1615 syscmd ("umount $targetdir/var/lib/vz");
1616 syscmd ("umount $targetdir/tmp");
1617 syscmd ("umount $targetdir/proc");
1618 syscmd ("umount $targetdir/sys");
1619
1620 if ($use_zfs) {
1621 syscmd ("zfs umount -a") == 0 ||
1622 die "unable to unmount zfs\n";
1623 } else {
1624 syscmd ("umount -d $targetdir");
1625 }
1626
1627 if (!$err && $use_zfs) {
1628 syscmd ("zfs set sync=standard $zfspoolname") == 0 ||
1629 die "unable to set zfs properties\n";
1630
1631 syscmd ("zfs set mountpoint=/ $zfspoolname/ROOT/pve-1") == 0 ||
1632 die "zfs set mountpoint failed\n";
1633
1634 syscmd ("zpool set bootfs=$zfspoolname/ROOT/pve-1 $zfspoolname") == 0 ||
1635 die "zfs set bootfs failed\n";
1636 }
1637
1638 die $err if $err;
1639}
1640
1641sub display_html {
1642 my ($filename) = @_;
1643
1644 my $path = "${proxmox_dir}/html/$filename";
1645 my $url = "file://$path";
1646
1647 my $data = file_get_contents($path);
1648
1649 if ($filename eq 'license.htm') {
1650 my $licensefn = -f "/EULA" ? "/EULA" :
1651 $opt_testmode ? "./copyright" :
1652 "/usr/share/doc/pve-installer/copyright";
1653 my $license = decode('utf8', file_get_contents($licensefn));
1654 $license =~ m/^\s+(.+\n)/;
1655 my $title = $licensefn eq '/EULA' ? "END USER LICENSE AGREEMENT (EULA)": $1;
1656 $data =~ s/__LICENSE__/$license/;
1657 $data =~ s/__LICENSE_TITLE__/$title/;
1658 }
1659
1660 $htmlview->load_html_string($data, $url);
1661}
1662
1663sub set_next {
1664 my ($text, $fctn) = @_;
1665
1666 $next_fctn = $fctn;
1667 $text = "_Next" if !$text;
1668 $next->set_label ($text);
1669
1670 $next->grab_focus ();
1671}
1672
1673sub url_requested {
1674 my ($doc, $url, $stream) = @_;
1675
1676 $stream->set_cancel_func (sub {}); # hack: avoid warning
1677
1678 my $path = "${proxmox_dir}/html/$url";
1679
1680 if (-f $path) {
1681 open (HTMLTMP, $path) ||
1682 die "unable to open file '$path' - $!\n";
1683 my $buf;
1684 while (my $i = read (HTMLTMP, $buf, 4096)) {
1685 $stream->write ($buf);
1686 Gtk3::main_iteration() while Gtk3::events_pending();
1687 }
1688 close (HTMLTMP);
1689 }
1690
1691 #$stream->close(); # hack: dont close - avoid crash
1692}
1693
1694sub create_main_window {
1695
1696 $window = Gtk3::Window->new ();
1697 $window->set_default_size (1024, 768);
1698 $window->set_has_resize_grip(0);
1699 $window->set_decorated (0) if !$opt_testmode;
1700
1701 my $vbox = Gtk3::VBox->new (0, 0);
1702
1703 my $image = Gtk3::Image->new_from_file ("${proxmox_dir}/proxlogo.png");
1704 $vbox->pack_start ($image, 0, 0, 0);
1705
1706 my $hbox = Gtk3::HBox->new (0, 0);
1707 $vbox->pack_start ($hbox, 1, 1, 0);
1708
1709 # my $f1 = Gtk3::Frame->new ('test');
1710 # $f1->set_shadow_type ('none');
1711 # $hbox->pack_start ($f1, 1, 1, 0);
1712
1713 my $sep1 = Gtk3::HSeparator->new;
1714 $vbox->pack_start ($sep1, 0, 0, 0);
1715
1716 $cmdbox = Gtk3::HBox->new ();
1717 $vbox->pack_start ($cmdbox, 0, 0, 10);
1718
1719 $next = Gtk3::Button->new ('_Next');
1720 $next->signal_connect (clicked => sub { &$next_fctn (); });
1721 $cmdbox->pack_end ($next, 0, 0, 10);
1722 my $abort = Gtk3::Button->new ('_Abort');
1723 $abort->set_can_focus (0);
1724 $cmdbox->pack_start ($abort, 0, 0, 10);
1725 $abort->signal_connect (clicked => sub { exit (-1); });
1726
1727 my $vbox2 = Gtk3::VBox->new (0, 0);
1728 $hbox->add ($vbox2);
1729
1730 $htmlview = Gtk3::WebKit::WebView->new();
1731 my $scrolls = Gtk3::ScrolledWindow->new();
1732 $scrolls->add($htmlview);
1733
1734 my $hbox2 = Gtk3::HBox->new (0, 0);
1735 $hbox2->pack_start ($scrolls, 1, 1, 0);
1736
1737 $vbox2->pack_start ($hbox2, 1, 1, 0);
1738
1739 my $vbox3 = Gtk3::VBox->new (0, 0);
1740 $vbox2->pack_start ($vbox3, 0, 0, 0);
1741
1742 my $sep2 = Gtk3::HSeparator->new;
1743 $vbox3->pack_start ($sep2, 0, 0, 0);
1744
1745 $inbox = Gtk3::HBox->new (0, 0);
1746 $vbox3->pack_start ($inbox, 0, 0, 0);
1747
1748 $window->add ($vbox);
1749
1750 $window->show_all;
1751 $window->realize ();
1752}
1753
1754sub cleanup_view {
1755 $inbox->foreach(sub {
1756 my $child = shift;
1757 $inbox->remove ($child);
1758 });
1759}
1760
1761# fixme: newer GTK3 has special properties to handle numbers with Entry
1762# only allow floating point numbers with Gtk3::Entry
1763
1764sub check_float {
1765 my ($entry, $event) = @_;
1766
1767 return check_number($entry, $event, 1);
1768}
1769
1770sub check_int {
1771 my ($entry, $event) = @_;
1772
1773 return check_number($entry, $event, 0);
1774}
1775
1776sub check_number {
1777 my ($entry, $event, $float) = @_;
1778
1779 my $val = $event->get_keyval;
1780
1781 if (($float && $val == ord '.') ||
1782 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
1783 $val == Gtk3::Gdk::KEY_Shift_L ||
1784 $val == Gtk3::Gdk::KEY_Tab ||
1785 $val == Gtk3::Gdk::KEY_Left ||
1786 $val == Gtk3::Gdk::KEY_Right ||
1787 $val == Gtk3::Gdk::KEY_BackSpace ||
1788 $val == Gtk3::Gdk::KEY_Delete ||
1789 ($val >= ord '0' && $val <= ord '9') ||
1790 ($val >= Gtk3::Gdk::KEY_KP_0 &&
1791 $val <= Gtk3::Gdk::KEY_KP_9)) {
1792 return undef;
1793 }
1794
1795 return 1;
1796}
1797
1798sub create_text_input {
1799 my ($default, $text) = @_;
1800
1801 my $hbox = Gtk3::HBox->new (0, 0);
1802
1803 my $label = Gtk3::Label->new ($text);
1804 $label->set_size_request (150, -1);
1805 $label->set_alignment (1, 0.5);
1806 $hbox->pack_start ($label, 0, 0, 10);
1807 my $e1 = Gtk3::Entry->new ();
1808 $e1->set_width_chars (30);
1809 $hbox->pack_start ($e1, 0, 0, 0);
1810 $e1->set_text ($default);
1811
1812 return ($hbox, $e1);
1813}
1814
1815sub get_ip_config {
1816
1817 my $ifaces = {};
1818 my $default;
1819
1820 my $links = `ip -o l`;
1821 foreach my $l (split /\n/,$links) {
1822 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
1823 next if !$name || $name eq 'lo';
1824
1825 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
1826 $driver =~ s!^.*/!!;
1827
1828 $ifaces->{"$index"} = {
1829 name => $name,
1830 driver => $driver,
1831 flags => $flags,
1832 state => $state,
1833 mac => $mac,
1834 };
1835
1836 my $addresses = `ip -o a s $name`;
1837 foreach my $a (split /\n/,$addresses) {
1838 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
1839 next if !$ip;
1840 next if $a =~ /scope\s+link/; # ignore link local
1841
1842 my $mask = $prefix;
1843
1844 if ($family eq 'inet') {
1845 next if !$ip =~ /$IPV4RE/;
1846 next if $prefix < 8 || $prefix > 32;
1847 $mask = @$ipv4_reverse_mask[$prefix];
1848 } else {
1849 next if !$ip =~ /$IPV6RE/;
1850 }
1851
1852 $default = $index if !$default;
1853
1854 $ifaces->{"$index"}->{"$family"} = {
1855 mask => $mask,
1856 addr => $ip,
1857 };
1858 }
1859 }
1860
1861
1862 my $route = `ip route`;
1863 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
1864
1865 my $resolvconf = `cat /etc/resolv.conf`;
1866 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
1867 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
1868
1869 return {
1870 default => $default,
1871 ifaces => $ifaces,
1872 gateway => $gateway,
1873 dnsserver => $dnsserver,
1874 domain => $domain,
1875 }
1876}
1877
1878sub display_message {
1879 my ($msg) = @_;
1880
1881 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
1882 'info', 'ok', $msg);
1883 $dialog->run();
1884 $dialog->destroy();
1885}
1886
1887sub display_error {
1888 my ($msg) = @_;
1889
1890 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
1891 'error', 'ok', $msg);
1892 $dialog->run();
1893 $dialog->destroy();
1894}
1895
1896my $ipconf_first_view = 1;
1897
1898sub create_ipconf_view {
1899
1900 cleanup_view ();
1901 display_html ("ipconf.htm");
1902
1903 my $vbox = Gtk3::VBox->new (0, 0);
1904 $inbox->pack_start ($vbox, 1, 0, 0);
1905 my $hbox = Gtk3::HBox->new (0, 0);
1906 $vbox->pack_start ($hbox, 0, 0, 10);
1907 my $vbox2 = Gtk3::VBox->new (0, 0);
1908 $hbox->add ($vbox2);
1909
1910 my $ipbox;
1911 ($ipbox, $ipconf_entry_addr) =
1912 create_text_input ("192.168.100.2", 'IP Address:');
1913
1914 my $maskbox;
1915 ($maskbox, $ipconf_entry_mask) =
1916 create_text_input ("255.255.255.0", 'Netmask:');
1917
1918 my $device_cb = Gtk3::ComboBoxText->new();
1919 $device_cb->set_active(0);
1920 $device_cb->set_visible(1);
1921
1922 my $get_device_desc = sub {
1923 my $iface = shift;
1924 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
1925 };
1926
1927 my $device_active_map = {};
1928
1929 my $device_change_handler = sub {
1930 my $current = shift;
1931 $ipconf->{selected} = $device_active_map->{$current->get_active()};
1932 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
1933 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
1934 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
1935 $ipconf_entry_mask->set_text($iface->{inet}->{mask} || $iface->{inet6}->{mask})
1936 if $iface->{inet}->{mask} || $iface->{inet6}->{mask};
1937 };
1938
1939 my $i = 0;
1940 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
1941 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
1942 $device_active_map->{$i} = $index;
1943 if ($ipconf_first_view && $index == $ipconf->{default}) {
1944 $device_cb->set_active($i);
1945 &$device_change_handler($device_cb);
1946 $ipconf_first_view = 0;
1947 }
1948 $device_cb->signal_connect ('changed' => $device_change_handler);
1949 $i++;
1950 }
1951
1952 $device_cb->set_active(0)
1953 if !($ipconf->{selected});
1954
1955 my $devicebox = Gtk3::HBox->new (0, 0);
1956 my $label = Gtk3::Label->new ("Management Interface:");
1957 $label->set_size_request (150, -1);
1958 $label->set_alignment (1, 0.5);
1959 $devicebox->pack_start ($label, 0, 0, 10);
1960 $devicebox->pack_start ($device_cb, 0, 0, 0);
1961
1962 $vbox2->pack_start ($devicebox, 0, 0, 2);
1963
1964 my $hn = $ipconf->{domain} ? "pve.$ipconf->{domain}" : 'pve.example.invalid';
1965
1966 my ($hostbox, $hostentry) =
1967 create_text_input ($hn, 'Hostname (FQDN):');
1968 $vbox2->pack_start ($hostbox, 0, 0, 2);
1969
1970 $vbox2->pack_start ($ipbox, 0, 0, 2);
1971
1972 $vbox2->pack_start ($maskbox, 0, 0, 2);
1973
1974 $gateway = $ipconf->{gateway} || '192.168.100.1';
1975
1976 my $gwbox;
1977 ($gwbox, $ipconf_entry_gw) =
1978 create_text_input ($gateway, 'Gateway:');
1979
1980 $vbox2->pack_start ($gwbox, 0, 0, 2);
1981
1982 $dnsserver = $ipconf->{dnsserver} || $gateway;
1983
1984 my $dnsbox;
1985 ($dnsbox, $ipconf_entry_dns) =
1986 create_text_input ($dnsserver, 'DNS Server:');
1987
1988 $vbox2->pack_start ($dnsbox, 0, 0, 0);
1989
1990 $inbox->show_all;
1991 set_next (undef, sub {
1992
1993 # verify hostname
1994
1995 my $text = $hostentry->get_text();
1996
1997 $text =~ s/^\s+//;
1998 $text =~ s/\s+$//;
1999
2000 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
2001
2002 # Debian does not support purely numeric hostnames
2003 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2004 display_message("Purely numeric hostnames are not allowed.");
2005 $hostentry->grab_focus();
2006 return;
2007 }
2008
2009 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
2010 $text =~ m/^([^\.]+)\.(\S+)$/) {
2011 $hostname = $1;
2012 $domain = $2;
2013 } else {
2014 display_message ("Hostname does not look like a fully qualified domain name.");
2015 $hostentry->grab_focus();
2016 return;
2017 }
2018
2019 # verify ip address
2020
2021 $text = $ipconf_entry_addr->get_text();
2022 $text =~ s/^\s+//;
2023 $text =~ s/\s+$//;
2024 if ($text =~ m!^($IPV4RE)$!) {
2025 $ipaddress = $text;
2026 $ipversion = 4;
2027 } elsif ($text =~ m!^($IPV6RE)$!) {
2028 $ipaddress = $text;
2029 $ipversion = 6;
2030 } else {
2031 display_message ("IP address is not valid.");
2032 $ipconf_entry_addr->grab_focus();
2033 return;
2034 }
2035
2036 $text = $ipconf_entry_mask->get_text();
2037 $text =~ s/^\s+//;
2038 $text =~ s/\s+$//;
2039 if (($ipversion == 6) && ($text =~ m/^(\d+)$/) && ($1 >= 8) && ($1 <= 126)) {
2040 $netmask = $text;
2041 } elsif (($ipversion == 4) && defined($ipv4_mask_hash->{$text})) {
2042 $netmask = $text;
2043 } else {
2044 display_message ("Netmask is not valid.");
2045 $ipconf_entry_mask->grab_focus();
2046 return;
2047 }
2048
2049 $text = $ipconf_entry_gw->get_text();
2050 $text =~ s/^\s+//;
2051 $text =~ s/\s+$//;
2052 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2053 $gateway = $text;
2054 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
2055 $gateway = $text;
2056 } else {
2057 display_message ("Gateway is not valid.");
2058 $ipconf_entry_gw->grab_focus();
2059 return;
2060 }
2061
2062 $text = $ipconf_entry_dns->get_text();
2063 $text =~ s/^\s+//;
2064 $text =~ s/\s+$//;
2065 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2066 $dnsserver = $text;
2067 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
2068 $dnsserver = $text;
2069 } else {
2070 display_message ("DNS server is not valid.");
2071 $ipconf_entry_dns->grab_focus();
2072 return;
2073 }
2074
2075 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
2076
2077 create_extract_view ();
2078 });
2079
2080 $hostentry->grab_focus();
2081}
2082
2083sub get_device_desc {
2084 my ($devname, $size, $model) = @_;
2085
2086 if ($size && ($size > 0)) {
2087 $size = int($size/2048); # size in MB, from 512B "sectors"
2088
2089 my $text = "$devname (";
2090 if ($size >= 1024) {
2091 $size = int($size/1024); # size in GB
2092 $text .= "${size}GB";
2093 } else {
2094 $text .= "${size}MB";
2095 }
2096
2097 $text .= ", $model" if $model;
2098 $text .= ")";
2099
2100 } else {
2101 return $devname;
2102 }
2103}
2104
2105sub update_layout {
2106 my ($cb, $kmap) = @_;
2107
2108 my $ind;
2109 my $def;
2110 my $i = 0;
2111 my $kmaphash = $cmap->{kmaphash};
2112 foreach my $layout (sort keys %$kmaphash) {
2113 $def = $i if $kmaphash->{$layout} eq 'en-us';
2114 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2115 $i++;
2116 }
2117
2118 $cb->set_active ($ind || $def || 0);
2119}
2120
2121my $lastzonecb;
2122sub update_zonelist {
2123 my ($box, $cc) = @_;
2124
2125 my $cczones = $cmap->{cczones};
2126 my $zones = $cmap->{zones};
2127
2128 my $sel;
2129 if ($lastzonecb) {
2130 $sel = $lastzonecb->get_active_text();
2131 $box->remove ($lastzonecb);
2132 } else {
2133 $sel = $timezone; # used once to select default
2134 }
2135
2136 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
2137 $cb->set_size_request (200, -1);
2138
2139 $cb->signal_connect ('changed' => sub {
2140 $timezone = $cb->get_active_text();
2141 });
2142
2143 my @za;
2144 if ($cc && defined ($cczones->{$cc})) {
2145 @za = keys %{$cczones->{$cc}};
2146 } else {
2147 @za = keys %$zones;
2148 }
2149 my $ind;
2150 my $i = 0;
2151 foreach my $zone (sort @za) {
2152 $ind = $i if $sel && $zone eq $sel;
2153 $cb->append_text ($zone);
2154 $i++;
2155 }
2156
2157 $cb->set_active ($ind || 0);
2158
2159 $cb->show;
2160 $box->pack_start ($cb, 0, 0, 0);
2161}
2162
2163sub create_password_view {
2164
2165 cleanup_view ();
2166
2167 my $vbox2 = Gtk3::VBox->new (0, 0);
2168 $inbox->pack_start ($vbox2, 1, 0, 0);
2169 my $vbox = Gtk3::VBox->new (0, 0);
2170 $vbox2->pack_start ($vbox, 0, 0, 10);
2171
2172 my $hbox1 = Gtk3::HBox->new (0, 0);
2173 my $label = Gtk3::Label->new ("Password");
2174 $label->set_size_request (150, -1);
2175 $label->set_alignment (1, 0.5);
2176 $hbox1->pack_start ($label, 0, 0, 10);
2177 my $pwe1 = Gtk3::Entry->new ();
2178 $pwe1->set_visibility (0);
2179 $pwe1->set_size_request (200, -1);
2180 $hbox1->pack_start ($pwe1, 0, 0, 0);
2181
2182 my $hbox2 = Gtk3::HBox->new (0, 0);
2183 $label = Gtk3::Label->new ("Confirm");
2184 $label->set_size_request (150, -1);
2185 $label->set_alignment (1, 0.5);
2186 $hbox2->pack_start ($label, 0, 0, 10);
2187 my $pwe2 = Gtk3::Entry->new ();
2188 $pwe2->set_visibility (0);
2189 $pwe2->set_size_request (200, -1);
2190 $hbox2->pack_start ($pwe2, 0, 0, 0);
2191
2192 my $hbox3 = Gtk3::HBox->new (0, 0);
2193 $label = Gtk3::Label->new ("E-Mail");
2194 $label->set_size_request (150, -1);
2195 $label->set_alignment (1, 0.5);
2196 $hbox3->pack_start ($label, 0, 0, 10);
2197 my $eme = Gtk3::Entry->new ();
2198 $eme->set_size_request (200, -1);
2199 $eme->set_text('mail@example.invalid');
2200 $hbox3->pack_start ($eme, 0, 0, 0);
2201
2202
2203 $vbox->pack_start ($hbox1, 0, 0, 5);
2204 $vbox->pack_start ($hbox2, 0, 0, 5);
2205 $vbox->pack_start ($hbox3, 0, 0, 15);
2206
2207 $inbox->show_all;
2208
2209 display_html ("passwd.htm");
2210
2211 set_next (undef, sub {
2212
2213 my $t1 = $pwe1->get_text;
2214 my $t2 = $pwe2->get_text;
2215
2216 if (length ($t1) < 5) {
2217 display_message ("Password is too short.");
2218 $pwe1->grab_focus();
2219 return;
2220 }
2221
2222 if ($t1 ne $t2) {
2223 display_message ("Password does not match.");
2224 $pwe1->grab_focus();
2225 return;
2226 }
2227
2228 my $t3 = $eme->get_text;
2229 if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
2230 display_message ("E-Mail does not look like a valid address" .
2231 " (user\@domain.tld)");
2232 $eme->grab_focus();
2233 return;
2234 }
2235
2236 if ($t3 eq 'mail@example.invalid') {
2237 display_message ("Please enter a valid E-Mail address");
2238 $eme->grab_focus();
2239 return;
2240 }
2241
2242 $password = $t1;
2243 $mailto = $t3;
2244
2245 create_ipconf_view();
2246 });
2247
2248 $pwe1->grab_focus();
2249
2250}
2251
2252sub create_country_view {
2253
2254 cleanup_view ();
2255
2256 my $countryhash = $cmap->{countryhash};
2257 my $ctr = $cmap->{country};
2258
2259 my $vbox2 = Gtk3::VBox->new (0, 0);
2260 $inbox->pack_start ($vbox2, 1, 0, 0);
2261 my $vbox = Gtk3::VBox->new (0, 0);
2262 $vbox2->pack_start ($vbox, 0, 0, 10);
2263
2264 my $w = Gtk3::Entry->new ();
2265 $w->set_size_request (200, -1);
2266
2267 my $c = Gtk3::EntryCompletion->new ();
2268 $c->set_text_column (0);
2269 $c->set_minimum_key_length(0);
2270 $c->set_popup_set_width (1);
2271 $c->set_inline_completion (1);
2272
2273 my $hbox2 = Gtk3::HBox->new (0, 0);
2274 my $label = Gtk3::Label->new ("Time zone");
2275 $label->set_size_request (150, -1);
2276 $label->set_alignment (1, 0.5);
2277 $hbox2->pack_start ($label, 0, 0, 10);
2278 update_zonelist ($hbox2);
2279
2280 my $hbox3 = Gtk3::HBox->new (0, 0);
2281 $label = Gtk3::Label->new ("Keyboard Layout");
2282 $label->set_size_request (150, -1);
2283 $label->set_alignment (1, 0.5);
2284 $hbox3->pack_start ($label, 0, 0, 10);
2285
2286 my $kmapcb = Gtk3::ComboBoxText->new();
2287 $kmapcb->set_size_request (200, -1);
2288 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2289 $kmapcb->append_text ($layout);
2290 }
2291
2292 update_layout ($kmapcb);
2293 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2294
2295 $kmapcb->signal_connect ('changed' => sub {
2296 my $sel = $kmapcb->get_active_text();
2297 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2298 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2299 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
2300 syscmd ("setxkbmap $xkmap $xvar") if !$opt_testmode;
2301 $keymap = $kmap;
2302 }
2303 });
2304
2305 $w->signal_connect ('changed' => sub {
2306 my ($entry, $event) = @_;
2307 my $text = $entry->get_text;
2308
2309 if (my $cc = $countryhash->{lc($text)}) {
2310 update_zonelist ($hbox2, $cc);
2311 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
2312 update_layout ($kmapcb, $kmap);
2313 }
2314 });
2315
2316 $w->signal_connect (key_press_event => sub {
2317 my ($entry, $event) = @_;
2318 my $text = $entry->get_text;
2319
2320 my $val = $event->get_keyval;
2321
2322 if ($val == Gtk3::Gdk::KEY_Tab) {
2323 my $cc = $countryhash->{lc($text)};
2324
2325 my $found = 0;
2326 my $compl;
2327
2328 if ($cc) {
2329 $found = 1;
2330 $compl = $ctr->{$cc}->{name};
2331 } else {
2332 foreach my $cc (keys %$ctr) {
2333 my $ct = $ctr->{$cc}->{name};
2334 if ($ct =~ m/^\Q$text\E.*$/i) {
2335 $found++;
2336 $compl = $ct;
2337 }
2338 last if $found > 1;
2339 }
2340 }
2341
2342 if ($found == 1) {
2343 $entry->set_text($compl);
2344 $c->complete();
2345 return undef;
2346 } else {
2347 #Gtk3::Gdk::beep();
2348 print chr(7); # beep ?
2349 }
2350
2351 $c->complete();
2352
2353 my $buf = $w->get_buffer();
2354 $buf->insert_text(-1, '', -1); # popup selection
2355
2356 return 1;
2357 }
2358
2359 return undef;
2360 });
2361
2362 my $ls = Gtk3::ListStore->new('Glib::String');
2363 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2364 my $iter = $ls->append();
2365 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2366 }
2367 $c->set_model ($ls);
2368
2369 $w->set_completion ($c);
2370
2371 my $hbox = Gtk3::HBox->new (0, 0);
2372
2373 $label = Gtk3::Label->new ("Country");
2374 $label->set_alignment (1, 0.5);
2375 $label->set_size_request (150, -1);
2376 $hbox->pack_start ($label, 0, 0, 10);
2377 $hbox->pack_start ($w, 0, 0, 0);
2378
2379 $vbox->pack_start ($hbox, 0, 0, 5);
2380 $vbox->pack_start ($hbox2, 0, 0, 5);
2381 $vbox->pack_start ($hbox3, 0, 0, 5);
2382
2383 if ($country) {
2384 $w->set_text ($ctr->{$country}->{name});
2385 }
2386
2387 $inbox->show_all;
2388
2389 display_html ("country.htm");
2390 set_next (undef, sub {
2391
2392 my $text = $w->get_text;
2393
2394 if (my $cc = $countryhash->{lc($text)}) {
2395 $country = $cc;
2396 create_password_view();
2397 return;
2398 } else {
2399 display_message ("Please select a country first.");
2400 $w->grab_focus();
2401 }
2402 });
2403
2404 $w->grab_focus();
2405}
2406
2407my $target_hd_combo;
2408my $target_hd_label;
2409
2410my $hdopion_first_setup = 1;
2411
2412my $create_basic_grid = sub {
2413 my $grid = Gtk3::Grid->new();
2414 $grid->set_visible(1);
2415 $grid->set_column_spacing(10);
2416 $grid->set_row_spacing(10);
2417 $grid->set_hexpand(1);
2418
2419 $grid->set_margin_start(5);
2420 $grid->set_margin_end(5);
2421 $grid->set_margin_top(5);
2422 $grid->set_margin_bottom(5);
2423
2424 return $grid;
2425};
2426
2427my $create_label_widget_grid = sub {
2428 my ($labeled_widgets) = @_;
2429
2430 my $grid = &$create_basic_grid();
2431 my $row = 0;
2432
2433 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2434 my $widget = @$labeled_widgets[$i+1];
2435 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2436 $label->set_visible(1);
2437 $label->set_alignment (1, 0.5);
2438 $grid->attach($label, 0, $row, 1, 1);
2439 $widget->set_visible(1);
2440 $grid->attach($widget, 1, $row, 1, 1);
2441 $row++;
2442 }
2443
2444 return $grid;
2445};
2446
2447my $create_raid_disk_grid = sub {
2448 my $disk_labeled_widgets = [];
2449 for (my $i = 0; $i < @$hds; $i++) {
2450 my $disk_selector = Gtk3::ComboBoxText->new();
2451 $disk_selector->append_text("-- do not use --");
2452 $disk_selector->set_active(0);
2453 $disk_selector->set_visible(1);
2454 foreach my $hd (@$hds) {
2455 my ($disk, $devname, $size, $model) = @$hd;
2456 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2457 $disk_selector->{pve_disk_id} = $i;
2458 $disk_selector->signal_connect (changed => sub {
2459 my $w = shift;
2460 my $diskid = $w->{pve_disk_id};
2461 my $a = $w->get_active - 1;
2462 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2463 });
2464 }
2465
2466 if ($hdopion_first_setup) {
2467 $disk_selector->set_active ($i+1) if $hds->[$i];
2468 } else {
2469 my $hdind = 0;
2470 if (my $cur_hd = $config_options->{"disksel$i"}) {
2471 foreach my $hd (@$hds) {
2472 if (@$hd[1] eq @$cur_hd[1]) {
2473 $disk_selector->set_active($hdind+1);
2474 last;
2475 }
2476 $hdind++;
2477 }
2478 }
2479 }
2480
2481 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2482 }
2483
2484 my $scrolled_window = Gtk3::ScrolledWindow->new();
2485 $scrolled_window->set_hexpand(1);
2486 $scrolled_window->set_propagate_natural_height(1) if @$hds > 4;
2487 $scrolled_window->add(&$create_label_widget_grid($disk_labeled_widgets));
2488 $scrolled_window->set_policy('never', 'automatic');
2489
2490 return $scrolled_window;
2491# &$create_label_widget_grid($disk_labeled_widgets)
2492};
2493
2494my $create_raid_advanced_grid = sub {
2495 my $labeled_widgets = [];
2496 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2497 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2498 $spinbutton_ashift->signal_connect ("value-changed" => sub {
2499 my $w = shift;
2500 $config_options->{ashift} = $w->get_value_as_int();
2501 });
2502 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
2503 $spinbutton_ashift->set_value($config_options->{ashift});
2504 push @$labeled_widgets, "ashift";
2505 push @$labeled_widgets, $spinbutton_ashift;
2506
2507 my $combo_compress = Gtk3::ComboBoxText->new();
2508 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
2509 # note: gzip / lze not allowed for bootfs vdevs
2510 my $comp_opts = ["on","off","lzjb","lz4"];
2511 foreach my $opt (@$comp_opts) {
2512 $combo_compress->append($opt, $opt);
2513 }
2514 $config_options->{compress} = "on" if !defined($config_options->{compress});
2515 $combo_compress->set_active_id($config_options->{compress});
2516 $combo_compress->signal_connect (changed => sub {
2517 my $w = shift;
2518 $config_options->{compress} = $w->get_active_text();
2519 });
2520 push @$labeled_widgets, "compress";
2521 push @$labeled_widgets, $combo_compress;
2522
2523 my $combo_checksum = Gtk3::ComboBoxText->new();
2524 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2525 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2526 foreach my $opt (@$csum_opts) {
2527 $combo_checksum->append($opt, $opt);
2528 }
2529 $config_options->{checksum} = "on" if !($config_options->{checksum});
2530 $combo_checksum->set_active_id($config_options->{checksum});
2531 $combo_checksum->signal_connect (changed => sub {
2532 my $w = shift;
2533 $config_options->{checksum} = $w->get_active_text();
2534 });
2535 push @$labeled_widgets, "checksum";
2536 push @$labeled_widgets, $combo_checksum;
2537
2538 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2539 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2540 $spinbutton_copies->signal_connect ("value-changed" => sub {
2541 my $w = shift;
2542 $config_options->{copies} = $w->get_value_as_int();
2543 });
2544 $config_options->{copies} = 1 if !defined($config_options->{copies});
2545 $spinbutton_copies->set_value($config_options->{copies});
2546 push @$labeled_widgets, "copies", $spinbutton_copies;
2547
2548 return &$create_label_widget_grid($labeled_widgets);;
2549};
2550
2551sub create_hdoption_view {
2552
2553 my $dialog = Gtk3::Dialog->new();
2554
2555 $dialog->set_title("Harddisk options");
2556
2557 $dialog->add_button("_OK", 1);
2558
2559 my $contarea = $dialog->get_content_area();
2560
2561 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2562 $contarea->pack_start($hbox2, 1, 1, 10);
2563
2564 my $grid = Gtk3::Grid->new();
2565 $grid->set_column_spacing(10);
2566 $grid->set_row_spacing(10);
2567
2568 $hbox2->pack_start($grid, 1, 0, 10);
2569
2570 my $row = 0;
2571
2572 # Filesystem type
2573
2574 my $label0 = Gtk3::Label->new ("Filesystem");
2575 $label0->set_alignment (1, 0.5);
2576 $grid->attach($label0, 0, $row, 1, 1);
2577
2578 my $fstypecb = Gtk3::ComboBoxText->new();
2579
2580 my $fstype = ['ext3', 'ext4', 'xfs',
2581 'zfs (RAID0)', 'zfs (RAID1)',
2582 'zfs (RAID10)', 'zfs (RAIDZ-1)',
2583 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2584
2585 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
2586 if $enable_btrfs;
2587
2588 my $tcount = 0;
2589 foreach my $tmp (@$fstype) {
2590 $fstypecb->append_text($tmp);
2591 $fstypecb->set_active ($tcount)
2592 if $config_options->{filesys} eq $tmp;
2593 $tcount++;
2594 }
2595
2596 $grid->attach($fstypecb, 1, $row, 1, 1);
2597
2598 $hbox2->show_all();
2599
2600 $row++;
2601
2602 my $sep = Gtk3::HSeparator->new();
2603 $sep->set_visible(1);
2604 $grid->attach($sep, 0, $row, 2, 1);
2605 $row++;
2606
2607 my $hdsize_labeled_widgets = [];
2608
2609 # size compute
2610 my $hdsize = 0;
2611 if ( -b $target_hd) {
2612 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
2613 } elsif ($target_hd) {
2614 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
2615 }
2616
2617 my $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
2618 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2619 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2620 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
2621
2622 my $entry_swapsize = Gtk3::Entry->new();
2623 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
2624 $entry_swapsize->signal_connect (key_press_event => \&check_float);
2625 $entry_swapsize->set_text($config_options->{swapsize}) if $config_options->{swapsize};
2626 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
2627
2628 my $entry_maxroot = Gtk3::Entry->new();
2629 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
2630 $entry_maxroot->signal_connect (key_press_event => \&check_float);
2631 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
2632 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
2633
2634 my $entry_minfree = Gtk3::Entry->new();
2635 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
2636 $entry_minfree->signal_connect (key_press_event => \&check_float);
2637 $entry_minfree->set_text($config_options->{minfree}) if $config_options->{minfree};
2638 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
2639
2640 my $entry_maxvz = Gtk3::Entry->new();
2641 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
2642 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2643 $entry_maxvz->set_text($config_options->{maxvz}) if $config_options->{maxvz};
2644 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
2645
2646 my $options_stack = Gtk3::Stack->new();
2647 $options_stack->set_visible(1);
2648 $options_stack->set_hexpand(1);
2649 $options_stack->set_vexpand(1);
2650 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
2651 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
2652 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
2653 $options_stack->set_visible_child_name("raiddisk");
2654 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2655 $options_stack_switcher->set_halign('center');
2656 $options_stack_switcher->set_stack($options_stack);
2657 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
2658 $row++;
2659 $grid->attach($options_stack, 0, $row, 2, 1);
2660 $row++;
2661
2662 $hdopion_first_setup = 0;
2663
2664 my $switch_view = sub {
2665 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
2666 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
2667
2668 $target_hd_combo->set_visible(!$raid);
2669 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
2670 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
2671 $options_stack_switcher->set_visible($enable_zfs_opts);
2672 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
2673 if ($raid) {
2674 $target_hd_label->set_text("Target: $config_options->{filesys} ");
2675 $options_stack->set_visible_child_name("raiddisk");
2676 } else {
2677 $target_hd_label->set_text("Target Harddisk: ");
2678 }
2679 my (undef, $pref_width) = $dialog->get_preferred_width();
2680 my (undef, $pref_height) = $dialog->get_preferred_height();
2681 $pref_height = 750 if $pref_height > 750;
2682 $dialog->resize($pref_width, $pref_height);
2683 };
2684
2685 &$switch_view();
2686
2687 $fstypecb->signal_connect (changed => sub {
2688 $config_options->{filesys} = $fstypecb->get_active_text();
2689 &$switch_view();
2690 });
2691
2692 $dialog->show();
2693
2694 $dialog->run();
2695
2696 my $get_float = sub {
2697 my ($entry) = @_;
2698
2699 my $text = $entry->get_text();
2700 return undef if !defined($text);
2701
2702 $text =~ s/^\s+//;
2703 $text =~ s/\s+$//;
2704
2705 return undef if $text !~ m/^\d+(\.\d+)?$/;
2706
2707 return $text;
2708 };
2709
2710 my $tmp;
2711
2712 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
2713 $config_options->{hdsize} = $tmp;
2714 } else {
2715 delete $config_options->{hdsize};
2716 }
2717
2718 if (defined($tmp = &$get_float($entry_swapsize))) {
2719 $config_options->{swapsize} = $tmp;
2720 } else {
2721 delete $config_options->{swapsize};
2722 }
2723
2724 if (defined($tmp = &$get_float($entry_maxroot))) {
2725 $config_options->{maxroot} = $tmp;
2726 } else {
2727 delete $config_options->{maxroot};
2728 }
2729
2730 if (defined($tmp = &$get_float($entry_minfree))) {
2731 $config_options->{minfree} = $tmp;
2732 } else {
2733 delete $config_options->{minfree};
2734 }
2735
2736 if (defined($tmp = &$get_float($entry_maxvz))) {
2737 $config_options->{maxvz} = $tmp;
2738 } else {
2739 delete $config_options->{maxvz};
2740 }
2741
2742 $dialog->destroy();
2743}
2744
2745my $get_raid_devlist = sub {
2746
2747 my $dev_name_hash = {};
2748
2749 my $devlist = [];
2750 for (my $i = 0; $i < @$hds; $i++) {
2751 if (my $hd = $config_options->{"disksel$i"}) {
2752 my ($disk, $devname, $size, $model) = @$hd;
2753 die "device '$devname' is used more than once\n"
2754 if $dev_name_hash->{$devname};
2755 $dev_name_hash->{$devname} = $hd;
2756 push @$devlist, $hd;
2757 }
2758 }
2759
2760 return $devlist;
2761};
2762
2763sub zfs_mirror_size_check {
2764 my ($expected, $actual) = @_;
2765
2766 die "mirrored disks must have same size\n"
2767 if abs($expected - $actual) > $expected / 10;
2768}
2769
2770sub get_zfs_raid_setup {
2771
2772 my $filesys = $config_options->{filesys};
2773
2774 my $devlist = &$get_raid_devlist();
2775
2776 my $diskcount = scalar(@$devlist);
2777 die "$filesys needs at least one device\n" if $diskcount < 1;
2778
2779 my $bootdevlist = [];
2780
2781 my $cmd= '';
2782 if ($filesys eq 'zfs (RAID0)') {
2783 push @$bootdevlist, @$devlist[0];
2784 foreach my $hd (@$devlist) {
2785 $cmd .= " @$hd[1]";
2786 }
2787 } elsif ($filesys eq 'zfs (RAID1)') {
2788 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
2789 $cmd .= ' mirror ';
2790 my $hd = @$devlist[0];
2791 my $expected_size = @$hd[2]; # all disks need approximately same size
2792 foreach $hd (@$devlist) {
2793 zfs_mirror_size_check($expected_size, @$hd[2]);
2794 $cmd .= " @$hd[1]";
2795 push @$bootdevlist, $hd;
2796 }
2797 } elsif ($filesys eq 'zfs (RAID10)') {
2798 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
2799 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
2800
2801 push @$bootdevlist, @$devlist[0], @$devlist[1];
2802
2803 for (my $i = 0; $i < $diskcount; $i+=2) {
2804 my $hd1 = @$devlist[$i];
2805 my $hd2 = @$devlist[$i+1];
2806 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
2807 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
2808 }
2809
2810 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
2811 my $level = $1;
2812 my $mindisks = 2 + $level;
2813 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
2814 my $hd = @$devlist[0];
2815 my $expected_size = @$hd[2]; # all disks need approximately same size
2816 $cmd .= " raidz$level";
2817 foreach $hd (@$devlist) {
2818 zfs_mirror_size_check($expected_size, @$hd[2]);
2819 $cmd .= " @$hd[1]";
2820 push @$bootdevlist, $hd;
2821 }
2822 } else {
2823 die "unknown zfs mode '$filesys'\n";
2824 }
2825
2826 return ($devlist, $bootdevlist, $cmd);
2827}
2828
2829sub get_btrfs_raid_setup {
2830
2831 my $filesys = $config_options->{filesys};
2832
2833 my $devlist = &$get_raid_devlist();
2834
2835 my $diskcount = scalar(@$devlist);
2836 die "$filesys needs at least one device\n" if $diskcount < 1;
2837
2838 my $mode;
2839
2840 if ($diskcount == 1) {
2841 $mode = 'single';
2842 } else {
2843 if ($filesys eq 'btrfs (RAID0)') {
2844 $mode = 'raid0';
2845 } elsif ($filesys eq 'btrfs (RAID1)') {
2846 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
2847 $mode = 'raid1';
2848 } elsif ($filesys eq 'btrfs (RAID10)') {
2849 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
2850 $mode = 'raid10';
2851 } else {
2852 die "unknown btrfs mode '$filesys'\n";
2853 }
2854 }
2855
2856 return ($devlist, $mode);
2857}
2858
2859sub create_hdsel_view {
2860
2861 cleanup_view ();
2862
2863 my $vbox = Gtk3::VBox->new (0, 0);
2864 $inbox->pack_start ($vbox, 1, 0, 0);
2865 my $hbox = Gtk3::HBox->new (0, 0);
2866 $vbox->pack_start ($hbox, 0, 0, 10);
2867
2868 my ($disk, $devname, $size, $model) = @{@$hds[0]};
2869 $target_hd = $devname;
2870
2871 $target_hd_label = Gtk3::Label->new ("Target Harddisk: ");
2872 $hbox->pack_start ($target_hd_label, 0, 0, 0);
2873
2874 $target_hd_combo = Gtk3::ComboBoxText->new();
2875
2876 foreach my $hd (@$hds) {
2877 ($disk, $devname, $size, $model) = @$hd;
2878 $target_hd_combo->append_text (get_device_desc ($devname, $size, $model));
2879 }
2880
2881 $target_hd_combo->set_active (0);
2882 $target_hd_combo->signal_connect (changed => sub {
2883 $a = shift->get_active;
2884 my ($disk, $devname) = @{@$hds[$a]};
2885 $target_hd = $devname;
2886 });
2887
2888 $hbox->pack_start ($target_hd_combo, 0, 0, 10);
2889
2890 my $options = Gtk3::Button->new ('_Options');
2891 $options->signal_connect (clicked => \&create_hdoption_view);
2892 $hbox->pack_start ($options, 0, 0, 0);
2893
2894
2895 $inbox->show_all;
2896
2897 display_html ("page1.htm");
2898
2899 set_next (undef, sub {
2900
2901 if ($config_options->{filesys} =~ m/zfs/) {
2902 eval { get_zfs_raid_setup(); };
2903 if (my $err = $@) {
2904 display_message ("Warning: $err\n" .
2905 "Please fix ZFS setup first.");
2906 } else {
2907 create_country_view();
2908 }
2909 } elsif ($config_options->{filesys} =~ m/btrfs/) {
2910 eval { get_btrfs_raid_setup(); };
2911 if (my $err = $@) {
2912 display_message ("Warning: $err\n" .
2913 "Please fix BTRFS setup first.");
2914 } else {
2915 create_country_view();
2916 }
2917 } else {
2918 create_country_view();
2919 }
2920 });
2921}
2922
2923sub create_extract_view {
2924
2925 cleanup_view ();
2926
2927 display_html ("extract1-license.htm");
2928 $next->set_sensitive (0);
2929
2930 my $vbox = Gtk3::VBox->new (0, 0);
2931 $inbox->pack_start ($vbox, 1, 0, 0);
2932 my $hbox = Gtk3::HBox->new (0, 0);
2933 $vbox->pack_start ($hbox, 0, 0, 10);
2934
2935 my $vbox2 = Gtk3::VBox->new (0, 0);
2936 $hbox->pack_start ($vbox2, 0, 0, 0);
2937
2938 $progress_status = Gtk3::Label->new ('');
2939 $vbox2->pack_start ($progress_status, 1, 1, 0);
2940
2941 $progress = Gtk3::ProgressBar->new;
2942 $progress->set_show_text(1);
2943 $progress->set_size_request (600, -1);
2944
2945 $vbox2->pack_start ($progress, 0, 0, 0);
2946
2947 $inbox->show_all;
2948
2949 my $tdir = $opt_testmode ? "target" : "/target";
2950 mkdir $tdir;
2951 my $base = $opt_testmode ? "/pve/$release/install/pve-base.squashfs" : "/cdrom/pve-base.squashfs";
2952
2953 eval { extract_data ($base, $tdir); };
2954 my $err = $@;
2955
2956 $next->set_sensitive (1);
2957
2958 set_next ("_Reboot", sub { exit (0); } );
2959
2960 if ($err) {
2961 display_html ("fail.htm");
2962 display_error ($err);
2963 } else {
2964 cleanup_view ();
2965 display_html ("success.htm");
2966 }
2967}
2968
2969sub create_intro_view {
2970
2971 cleanup_view ();
2972
2973 display_html ("license.htm");
2974
2975 set_next ("I a_gree", \&create_hdsel_view);
2976}
2977
2978$ipconf = get_ip_config ();
2979
2980$country = detect_country() if $ipconf->{default} || $opt_testmode;;
2981
2982# read country, kmap and timezone infos
2983$cmap = read_cmap ();
2984
2985create_main_window ();
2986
2987my $initial_error = 0;
2988
2989if (!defined ($hds) || (scalar (@$hds) <= 0)) {
2990 print "no hardisks found\n";
2991 $initial_error = 1;
2992 display_html ("nohds.htm");
2993 set_next ("Reboot", sub { exit (0); } );
2994} else {
2995 foreach my $hd (@$hds) {
2996 my ($disk, $devname) = @$hd;
2997 next if $devname =~ m|^/dev/md\d+$|;
2998 print "found Disk$disk N:$devname\n";
2999 }
3000}
3001
3002if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3003 print "no network interfaces found\n";
3004 $initial_error = 1;
3005 display_html ("nonics.htm");
3006 set_next ("Reboot", sub { exit (0); } );
3007}
3008
3009create_intro_view () if !$initial_error;
3010
3011Gtk3->main;
3012
3013exit 0;