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