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