]> git.proxmox.com Git - pve-installer.git/blob - proxinstall
detect kernel api version dynamically
[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 mkdir "/etc/pmg";
1604 # save admin email
1605 write_config ("#Unix Superuser\nroot:1:0::root:${mailto}:::\n",
1606 "/etc/pmg/user.conf");
1607
1608 } elsif ($setup->{product} eq 'pve') {
1609
1610 # create pmxcfs DB
1611
1612 my $tmpdir = "$targetdir/tmp/pve";
1613 mkdir $tmpdir;
1614
1615 # write vnc keymap to datacenter.cfg
1616 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
1617 write_config ("keyboard: $vnckmap\n",
1618 "$tmpdir/datacenter.cfg");
1619
1620 # save admin email
1621 write_config ("user:root\@pam:1:0:::${mailto}::\n",
1622 "$tmpdir/user.cfg");
1623
1624 # write storage.cfg
1625 my $strorage_cfg_fn = "$tmpdir/storage.cfg";
1626 if ($use_zfs) {
1627 write_config ($storage_cfg_zfs, $strorage_cfg_fn);
1628 } elsif ($use_btrfs) {
1629 write_config ($storage_cfg_btrfs, $strorage_cfg_fn);
1630 } else {
1631 write_config ($storage_cfg_lvmthin, $strorage_cfg_fn);
1632 }
1633
1634 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1635
1636 syscmd ("rm -rf $tmpdir");
1637 }
1638 };
1639
1640 my $err = $@;
1641
1642 update_progress (1, 0, 1, "");
1643
1644 print $err if $err;
1645
1646 if ($opt_testmode) {
1647 my $elapsed = Time::HiRes::tv_interval($starttime);
1648 print "Elapsed extract time: $elapsed\n";
1649
1650 syscmd ("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
1651 }
1652
1653 syscmd ("umount $targetdir/tmp");
1654 syscmd ("umount $targetdir/proc");
1655 syscmd ("umount $targetdir/sys");
1656
1657 if ($use_zfs) {
1658 syscmd ("zfs umount -a") == 0 ||
1659 die "unable to unmount zfs\n";
1660 } else {
1661 syscmd ("umount -d $targetdir");
1662 }
1663
1664 if (!$err && $use_zfs) {
1665 syscmd ("zfs set sync=standard $zfspoolname") == 0 ||
1666 die "unable to set zfs properties\n";
1667
1668 syscmd ("zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname") == 0 ||
1669 die "zfs set mountpoint failed\n";
1670
1671 syscmd ("zpool set bootfs=$zfspoolname/ROOT/$zfsrootvolname $zfspoolname") == 0 ||
1672 die "zfs set bootfs failed\n";
1673 syscmd ("zpool export $zfspoolname");
1674 }
1675
1676 die $err if $err;
1677 }
1678
1679 sub display_html {
1680 my ($filename) = @_;
1681
1682 my $path = "${proxmox_libdir}/html/$filename";
1683
1684 my $url = "file://$path";
1685
1686 my $data = file_get_contents($path);
1687
1688 if ($filename eq 'license.htm') {
1689 my $license = decode('utf8', file_get_contents("${proxmox_cddir}/EULA"));
1690 my $title = "END USER LICENSE AGREEMENT (EULA)";
1691 $data =~ s/__LICENSE__/$license/;
1692 $data =~ s/__LICENSE_TITLE__/$title/;
1693 }
1694
1695 $htmlview->load_html_string($data, $url);
1696 }
1697
1698 sub set_next {
1699 my ($text, $fctn) = @_;
1700
1701 $next_fctn = $fctn;
1702 $text = "_Next" if !$text;
1703 $next->set_label ($text);
1704
1705 $next->grab_focus ();
1706 }
1707
1708 sub create_main_window {
1709
1710 $window = Gtk3::Window->new ();
1711 $window->set_default_size (1024, 768);
1712 $window->set_has_resize_grip(0);
1713 $window->set_decorated (0) if !$opt_testmode;
1714
1715 my $vbox = Gtk3::VBox->new (0, 0);
1716
1717 my $image = Gtk3::Image->new_from_file ("${proxmox_libdir}/proxlogo.png");
1718 $vbox->pack_start ($image, 0, 0, 0);
1719
1720 my $hbox = Gtk3::HBox->new (0, 0);
1721 $vbox->pack_start ($hbox, 1, 1, 0);
1722
1723 # my $f1 = Gtk3::Frame->new ('test');
1724 # $f1->set_shadow_type ('none');
1725 # $hbox->pack_start ($f1, 1, 1, 0);
1726
1727 my $sep1 = Gtk3::HSeparator->new;
1728 $vbox->pack_start ($sep1, 0, 0, 0);
1729
1730 $cmdbox = Gtk3::HBox->new ();
1731 $vbox->pack_start ($cmdbox, 0, 0, 10);
1732
1733 $next = Gtk3::Button->new ('_Next');
1734 $next->signal_connect (clicked => sub { &$next_fctn (); });
1735 $cmdbox->pack_end ($next, 0, 0, 10);
1736 my $abort = Gtk3::Button->new ('_Abort');
1737 $abort->set_can_focus (0);
1738 $cmdbox->pack_start ($abort, 0, 0, 10);
1739 $abort->signal_connect (clicked => sub { exit (-1); });
1740
1741 my $vbox2 = Gtk3::VBox->new (0, 0);
1742 $hbox->add ($vbox2);
1743
1744 $htmlview = Gtk3::WebKit::WebView->new();
1745 my $scrolls = Gtk3::ScrolledWindow->new();
1746 $scrolls->add($htmlview);
1747
1748 my $hbox2 = Gtk3::HBox->new (0, 0);
1749 $hbox2->pack_start ($scrolls, 1, 1, 0);
1750
1751 $vbox2->pack_start ($hbox2, 1, 1, 0);
1752
1753 my $vbox3 = Gtk3::VBox->new (0, 0);
1754 $vbox2->pack_start ($vbox3, 0, 0, 0);
1755
1756 my $sep2 = Gtk3::HSeparator->new;
1757 $vbox3->pack_start ($sep2, 0, 0, 0);
1758
1759 $inbox = Gtk3::HBox->new (0, 0);
1760 $vbox3->pack_start ($inbox, 0, 0, 0);
1761
1762 $window->add ($vbox);
1763
1764 $window->show_all;
1765 $window->realize ();
1766 }
1767
1768 sub cleanup_view {
1769 $inbox->foreach(sub {
1770 my $child = shift;
1771 $inbox->remove ($child);
1772 });
1773 }
1774
1775 # fixme: newer GTK3 has special properties to handle numbers with Entry
1776 # only allow floating point numbers with Gtk3::Entry
1777
1778 sub check_float {
1779 my ($entry, $event) = @_;
1780
1781 return check_number($entry, $event, 1);
1782 }
1783
1784 sub check_int {
1785 my ($entry, $event) = @_;
1786
1787 return check_number($entry, $event, 0);
1788 }
1789
1790 sub check_number {
1791 my ($entry, $event, $float) = @_;
1792
1793 my $val = $event->get_keyval;
1794
1795 if (($float && $val == ord '.') ||
1796 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
1797 $val == Gtk3::Gdk::KEY_Shift_L ||
1798 $val == Gtk3::Gdk::KEY_Tab ||
1799 $val == Gtk3::Gdk::KEY_Left ||
1800 $val == Gtk3::Gdk::KEY_Right ||
1801 $val == Gtk3::Gdk::KEY_BackSpace ||
1802 $val == Gtk3::Gdk::KEY_Delete ||
1803 ($val >= ord '0' && $val <= ord '9') ||
1804 ($val >= Gtk3::Gdk::KEY_KP_0 &&
1805 $val <= Gtk3::Gdk::KEY_KP_9)) {
1806 return undef;
1807 }
1808
1809 return 1;
1810 }
1811
1812 sub create_text_input {
1813 my ($default, $text) = @_;
1814
1815 my $hbox = Gtk3::HBox->new (0, 0);
1816
1817 my $label = Gtk3::Label->new ($text);
1818 $label->set_size_request (150, -1);
1819 $label->set_alignment (1, 0.5);
1820 $hbox->pack_start ($label, 0, 0, 10);
1821 my $e1 = Gtk3::Entry->new ();
1822 $e1->set_width_chars (30);
1823 $hbox->pack_start ($e1, 0, 0, 0);
1824 $e1->set_text ($default);
1825
1826 return ($hbox, $e1);
1827 }
1828
1829 sub get_ip_config {
1830
1831 my $ifaces = {};
1832 my $default;
1833
1834 my $links = `ip -o l`;
1835 foreach my $l (split /\n/,$links) {
1836 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
1837 next if !$name || $name eq 'lo';
1838
1839 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
1840 $driver =~ s!^.*/!!;
1841
1842 $ifaces->{"$index"} = {
1843 name => $name,
1844 driver => $driver,
1845 flags => $flags,
1846 state => $state,
1847 mac => $mac,
1848 };
1849
1850 my $addresses = `ip -o a s $name`;
1851 foreach my $a (split /\n/,$addresses) {
1852 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
1853 next if !$ip;
1854 next if $a =~ /scope\s+link/; # ignore link local
1855
1856 my $mask = $prefix;
1857
1858 if ($family eq 'inet') {
1859 next if !$ip =~ /$IPV4RE/;
1860 next if $prefix < 8 || $prefix > 32;
1861 $mask = @$ipv4_reverse_mask[$prefix];
1862 } else {
1863 next if !$ip =~ /$IPV6RE/;
1864 }
1865
1866 $default = $index if !$default;
1867
1868 $ifaces->{"$index"}->{"$family"} = {
1869 mask => $mask,
1870 addr => $ip,
1871 };
1872 }
1873 }
1874
1875
1876 my $route = `ip route`;
1877 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
1878
1879 my $resolvconf = `cat /etc/resolv.conf`;
1880 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
1881 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
1882
1883 return {
1884 default => $default,
1885 ifaces => $ifaces,
1886 gateway => $gateway,
1887 dnsserver => $dnsserver,
1888 domain => $domain,
1889 }
1890 }
1891
1892 sub display_message {
1893 my ($msg) = @_;
1894
1895 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
1896 'info', 'ok', $msg);
1897 $dialog->run();
1898 $dialog->destroy();
1899 }
1900
1901 sub display_error {
1902 my ($msg) = @_;
1903
1904 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
1905 'error', 'ok', $msg);
1906 $dialog->run();
1907 $dialog->destroy();
1908 }
1909
1910 my $ipconf_first_view = 1;
1911
1912 sub create_ipconf_view {
1913
1914 cleanup_view ();
1915 display_html ("ipconf.htm");
1916
1917 my $vbox = Gtk3::VBox->new (0, 0);
1918 $inbox->pack_start ($vbox, 1, 0, 0);
1919 my $hbox = Gtk3::HBox->new (0, 0);
1920 $vbox->pack_start ($hbox, 0, 0, 10);
1921 my $vbox2 = Gtk3::VBox->new (0, 0);
1922 $hbox->add ($vbox2);
1923
1924 my $ipbox;
1925 ($ipbox, $ipconf_entry_addr) =
1926 create_text_input ("192.168.100.2", 'IP Address:');
1927
1928 my $maskbox;
1929 ($maskbox, $ipconf_entry_mask) =
1930 create_text_input ("255.255.255.0", 'Netmask:');
1931
1932 my $device_cb = Gtk3::ComboBoxText->new();
1933 $device_cb->set_active(0);
1934 $device_cb->set_visible(1);
1935
1936 my $get_device_desc = sub {
1937 my $iface = shift;
1938 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
1939 };
1940
1941 my $device_active_map = {};
1942
1943 my $device_change_handler = sub {
1944 my $current = shift;
1945 $ipconf->{selected} = $device_active_map->{$current->get_active()};
1946 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
1947 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
1948 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
1949 $ipconf_entry_mask->set_text($iface->{inet}->{mask} || $iface->{inet6}->{mask})
1950 if $iface->{inet}->{mask} || $iface->{inet6}->{mask};
1951 };
1952
1953 my $i = 0;
1954 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
1955 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
1956 $device_active_map->{$i} = $index;
1957 if ($ipconf_first_view && $index == $ipconf->{default}) {
1958 $device_cb->set_active($i);
1959 &$device_change_handler($device_cb);
1960 $ipconf_first_view = 0;
1961 }
1962 $device_cb->signal_connect ('changed' => $device_change_handler);
1963 $i++;
1964 }
1965
1966 $device_cb->set_active(0)
1967 if !($ipconf->{selected});
1968
1969 my $devicebox = Gtk3::HBox->new (0, 0);
1970 my $label = Gtk3::Label->new ("Management Interface:");
1971 $label->set_size_request (150, -1);
1972 $label->set_alignment (1, 0.5);
1973 $devicebox->pack_start ($label, 0, 0, 10);
1974 $devicebox->pack_start ($device_cb, 0, 0, 0);
1975
1976 $vbox2->pack_start ($devicebox, 0, 0, 2);
1977
1978 my $hn = $ipconf->{domain} ?
1979 "$setup->{product}.$ipconf->{domain}" : "$setup->{product}.example.invalid";
1980
1981 my ($hostbox, $hostentry) =
1982 create_text_input ($hn, 'Hostname (FQDN):');
1983 $vbox2->pack_start ($hostbox, 0, 0, 2);
1984
1985 $vbox2->pack_start ($ipbox, 0, 0, 2);
1986
1987 $vbox2->pack_start ($maskbox, 0, 0, 2);
1988
1989 $gateway = $ipconf->{gateway} || '192.168.100.1';
1990
1991 my $gwbox;
1992 ($gwbox, $ipconf_entry_gw) =
1993 create_text_input ($gateway, 'Gateway:');
1994
1995 $vbox2->pack_start ($gwbox, 0, 0, 2);
1996
1997 $dnsserver = $ipconf->{dnsserver} || $gateway;
1998
1999 my $dnsbox;
2000 ($dnsbox, $ipconf_entry_dns) =
2001 create_text_input ($dnsserver, 'DNS Server:');
2002
2003 $vbox2->pack_start ($dnsbox, 0, 0, 0);
2004
2005 $inbox->show_all;
2006 set_next (undef, sub {
2007
2008 # verify hostname
2009
2010 my $text = $hostentry->get_text();
2011
2012 $text =~ s/^\s+//;
2013 $text =~ s/\s+$//;
2014
2015 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
2016
2017 # Debian does not support purely numeric hostnames
2018 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2019 display_message("Purely numeric hostnames are not allowed.");
2020 $hostentry->grab_focus();
2021 return;
2022 }
2023
2024 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
2025 $text =~ m/^([^\.]+)\.(\S+)$/) {
2026 $hostname = $1;
2027 $domain = $2;
2028 } else {
2029 display_message ("Hostname does not look like a fully qualified domain name.");
2030 $hostentry->grab_focus();
2031 return;
2032 }
2033
2034 # verify ip address
2035
2036 $text = $ipconf_entry_addr->get_text();
2037 $text =~ s/^\s+//;
2038 $text =~ s/\s+$//;
2039 if ($text =~ m!^($IPV4RE)$!) {
2040 $ipaddress = $text;
2041 $ipversion = 4;
2042 } elsif ($text =~ m!^($IPV6RE)$!) {
2043 $ipaddress = $text;
2044 $ipversion = 6;
2045 } else {
2046 display_message ("IP address is not valid.");
2047 $ipconf_entry_addr->grab_focus();
2048 return;
2049 }
2050
2051 $text = $ipconf_entry_mask->get_text();
2052 $text =~ s/^\s+//;
2053 $text =~ s/\s+$//;
2054 if (($ipversion == 6) && ($text =~ m/^(\d+)$/) && ($1 >= 8) && ($1 <= 126)) {
2055 $netmask = $text;
2056 } elsif (($ipversion == 4) && defined($ipv4_mask_hash->{$text})) {
2057 $netmask = $text;
2058 } else {
2059 display_message ("Netmask is not valid.");
2060 $ipconf_entry_mask->grab_focus();
2061 return;
2062 }
2063
2064 $text = $ipconf_entry_gw->get_text();
2065 $text =~ s/^\s+//;
2066 $text =~ s/\s+$//;
2067 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2068 $gateway = $text;
2069 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
2070 $gateway = $text;
2071 } else {
2072 display_message ("Gateway is not valid.");
2073 $ipconf_entry_gw->grab_focus();
2074 return;
2075 }
2076
2077 $text = $ipconf_entry_dns->get_text();
2078 $text =~ s/^\s+//;
2079 $text =~ s/\s+$//;
2080 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2081 $dnsserver = $text;
2082 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
2083 $dnsserver = $text;
2084 } else {
2085 display_message ("DNS server is not valid.");
2086 $ipconf_entry_dns->grab_focus();
2087 return;
2088 }
2089
2090 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
2091
2092 create_extract_view ();
2093 });
2094
2095 $hostentry->grab_focus();
2096 }
2097
2098 sub get_device_desc {
2099 my ($devname, $size, $model) = @_;
2100
2101 if ($size && ($size > 0)) {
2102 $size = int($size/2048); # size in MB, from 512B "sectors"
2103
2104 my $text = "$devname (";
2105 if ($size >= 1024) {
2106 $size = int($size/1024); # size in GB
2107 $text .= "${size}GB";
2108 } else {
2109 $text .= "${size}MB";
2110 }
2111
2112 $text .= ", $model" if $model;
2113 $text .= ")";
2114
2115 } else {
2116 return $devname;
2117 }
2118 }
2119
2120 sub update_layout {
2121 my ($cb, $kmap) = @_;
2122
2123 my $ind;
2124 my $def;
2125 my $i = 0;
2126 my $kmaphash = $cmap->{kmaphash};
2127 foreach my $layout (sort keys %$kmaphash) {
2128 $def = $i if $kmaphash->{$layout} eq 'en-us';
2129 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2130 $i++;
2131 }
2132
2133 $cb->set_active ($ind || $def || 0);
2134 }
2135
2136 my $lastzonecb;
2137 sub update_zonelist {
2138 my ($box, $cc) = @_;
2139
2140 my $cczones = $cmap->{cczones};
2141 my $zones = $cmap->{zones};
2142
2143 my $sel;
2144 if ($lastzonecb) {
2145 $sel = $lastzonecb->get_active_text();
2146 $box->remove ($lastzonecb);
2147 } else {
2148 $sel = $timezone; # used once to select default
2149 }
2150
2151 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
2152 $cb->set_size_request (200, -1);
2153
2154 $cb->signal_connect ('changed' => sub {
2155 $timezone = $cb->get_active_text();
2156 });
2157
2158 my @za;
2159 if ($cc && defined ($cczones->{$cc})) {
2160 @za = keys %{$cczones->{$cc}};
2161 } else {
2162 @za = keys %$zones;
2163 }
2164 my $ind;
2165 my $i = 0;
2166 foreach my $zone (sort @za) {
2167 $ind = $i if $sel && $zone eq $sel;
2168 $cb->append_text ($zone);
2169 $i++;
2170 }
2171
2172 $cb->set_active ($ind || 0);
2173
2174 $cb->show;
2175 $box->pack_start ($cb, 0, 0, 0);
2176 }
2177
2178 sub create_password_view {
2179
2180 cleanup_view ();
2181
2182 my $vbox2 = Gtk3::VBox->new (0, 0);
2183 $inbox->pack_start ($vbox2, 1, 0, 0);
2184 my $vbox = Gtk3::VBox->new (0, 0);
2185 $vbox2->pack_start ($vbox, 0, 0, 10);
2186
2187 my $hbox1 = Gtk3::HBox->new (0, 0);
2188 my $label = Gtk3::Label->new ("Password");
2189 $label->set_size_request (150, -1);
2190 $label->set_alignment (1, 0.5);
2191 $hbox1->pack_start ($label, 0, 0, 10);
2192 my $pwe1 = Gtk3::Entry->new ();
2193 $pwe1->set_visibility (0);
2194 $pwe1->set_size_request (200, -1);
2195 $hbox1->pack_start ($pwe1, 0, 0, 0);
2196
2197 my $hbox2 = Gtk3::HBox->new (0, 0);
2198 $label = Gtk3::Label->new ("Confirm");
2199 $label->set_size_request (150, -1);
2200 $label->set_alignment (1, 0.5);
2201 $hbox2->pack_start ($label, 0, 0, 10);
2202 my $pwe2 = Gtk3::Entry->new ();
2203 $pwe2->set_visibility (0);
2204 $pwe2->set_size_request (200, -1);
2205 $hbox2->pack_start ($pwe2, 0, 0, 0);
2206
2207 my $hbox3 = Gtk3::HBox->new (0, 0);
2208 $label = Gtk3::Label->new ("E-Mail");
2209 $label->set_size_request (150, -1);
2210 $label->set_alignment (1, 0.5);
2211 $hbox3->pack_start ($label, 0, 0, 10);
2212 my $eme = Gtk3::Entry->new ();
2213 $eme->set_size_request (200, -1);
2214 $eme->set_text('mail@example.invalid');
2215 $hbox3->pack_start ($eme, 0, 0, 0);
2216
2217
2218 $vbox->pack_start ($hbox1, 0, 0, 5);
2219 $vbox->pack_start ($hbox2, 0, 0, 5);
2220 $vbox->pack_start ($hbox3, 0, 0, 15);
2221
2222 $inbox->show_all;
2223
2224 display_html ("passwd.htm");
2225
2226 set_next (undef, sub {
2227
2228 my $t1 = $pwe1->get_text;
2229 my $t2 = $pwe2->get_text;
2230
2231 if (length ($t1) < 5) {
2232 display_message ("Password is too short.");
2233 $pwe1->grab_focus();
2234 return;
2235 }
2236
2237 if ($t1 ne $t2) {
2238 display_message ("Password does not match.");
2239 $pwe1->grab_focus();
2240 return;
2241 }
2242
2243 my $t3 = $eme->get_text;
2244 if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
2245 display_message ("E-Mail does not look like a valid address" .
2246 " (user\@domain.tld)");
2247 $eme->grab_focus();
2248 return;
2249 }
2250
2251 if ($t3 eq 'mail@example.invalid') {
2252 display_message ("Please enter a valid E-Mail address");
2253 $eme->grab_focus();
2254 return;
2255 }
2256
2257 $password = $t1;
2258 $mailto = $t3;
2259
2260 create_ipconf_view();
2261 });
2262
2263 $pwe1->grab_focus();
2264
2265 }
2266
2267 sub create_country_view {
2268
2269 cleanup_view ();
2270
2271 my $countryhash = $cmap->{countryhash};
2272 my $ctr = $cmap->{country};
2273
2274 my $vbox2 = Gtk3::VBox->new (0, 0);
2275 $inbox->pack_start ($vbox2, 1, 0, 0);
2276 my $vbox = Gtk3::VBox->new (0, 0);
2277 $vbox2->pack_start ($vbox, 0, 0, 10);
2278
2279 my $w = Gtk3::Entry->new ();
2280 $w->set_size_request (200, -1);
2281
2282 my $c = Gtk3::EntryCompletion->new ();
2283 $c->set_text_column (0);
2284 $c->set_minimum_key_length(0);
2285 $c->set_popup_set_width (1);
2286 $c->set_inline_completion (1);
2287
2288 my $hbox2 = Gtk3::HBox->new (0, 0);
2289 my $label = Gtk3::Label->new ("Time zone");
2290 $label->set_size_request (150, -1);
2291 $label->set_alignment (1, 0.5);
2292 $hbox2->pack_start ($label, 0, 0, 10);
2293 update_zonelist ($hbox2);
2294
2295 my $hbox3 = Gtk3::HBox->new (0, 0);
2296 $label = Gtk3::Label->new ("Keyboard Layout");
2297 $label->set_size_request (150, -1);
2298 $label->set_alignment (1, 0.5);
2299 $hbox3->pack_start ($label, 0, 0, 10);
2300
2301 my $kmapcb = Gtk3::ComboBoxText->new();
2302 $kmapcb->set_size_request (200, -1);
2303 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2304 $kmapcb->append_text ($layout);
2305 }
2306
2307 update_layout ($kmapcb);
2308 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2309
2310 $kmapcb->signal_connect ('changed' => sub {
2311 my $sel = $kmapcb->get_active_text();
2312 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2313 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2314 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
2315 syscmd ("setxkbmap $xkmap $xvar") if !$opt_testmode;
2316 $keymap = $kmap;
2317 }
2318 });
2319
2320 $w->signal_connect ('changed' => sub {
2321 my ($entry, $event) = @_;
2322 my $text = $entry->get_text;
2323
2324 if (my $cc = $countryhash->{lc($text)}) {
2325 update_zonelist ($hbox2, $cc);
2326 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
2327 update_layout ($kmapcb, $kmap);
2328 }
2329 });
2330
2331 $w->signal_connect (key_press_event => sub {
2332 my ($entry, $event) = @_;
2333 my $text = $entry->get_text;
2334
2335 my $val = $event->get_keyval;
2336
2337 if ($val == Gtk3::Gdk::KEY_Tab) {
2338 my $cc = $countryhash->{lc($text)};
2339
2340 my $found = 0;
2341 my $compl;
2342
2343 if ($cc) {
2344 $found = 1;
2345 $compl = $ctr->{$cc}->{name};
2346 } else {
2347 foreach my $cc (keys %$ctr) {
2348 my $ct = $ctr->{$cc}->{name};
2349 if ($ct =~ m/^\Q$text\E.*$/i) {
2350 $found++;
2351 $compl = $ct;
2352 }
2353 last if $found > 1;
2354 }
2355 }
2356
2357 if ($found == 1) {
2358 $entry->set_text($compl);
2359 $c->complete();
2360 return undef;
2361 } else {
2362 #Gtk3::Gdk::beep();
2363 print chr(7); # beep ?
2364 }
2365
2366 $c->complete();
2367
2368 my $buf = $w->get_buffer();
2369 $buf->insert_text(-1, '', -1); # popup selection
2370
2371 return 1;
2372 }
2373
2374 return undef;
2375 });
2376
2377 my $ls = Gtk3::ListStore->new('Glib::String');
2378 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2379 my $iter = $ls->append();
2380 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2381 }
2382 $c->set_model ($ls);
2383
2384 $w->set_completion ($c);
2385
2386 my $hbox = Gtk3::HBox->new (0, 0);
2387
2388 $label = Gtk3::Label->new ("Country");
2389 $label->set_alignment (1, 0.5);
2390 $label->set_size_request (150, -1);
2391 $hbox->pack_start ($label, 0, 0, 10);
2392 $hbox->pack_start ($w, 0, 0, 0);
2393
2394 $vbox->pack_start ($hbox, 0, 0, 5);
2395 $vbox->pack_start ($hbox2, 0, 0, 5);
2396 $vbox->pack_start ($hbox3, 0, 0, 5);
2397
2398 if ($country && $ctr->{$country}) {
2399 $w->set_text ($ctr->{$country}->{name});
2400 }
2401
2402 $inbox->show_all;
2403
2404 display_html ("country.htm");
2405 set_next (undef, sub {
2406
2407 my $text = $w->get_text;
2408
2409 if (my $cc = $countryhash->{lc($text)}) {
2410 $country = $cc;
2411 create_password_view();
2412 return;
2413 } else {
2414 display_message ("Please select a country first.");
2415 $w->grab_focus();
2416 }
2417 });
2418
2419 $w->grab_focus();
2420 }
2421
2422 my $target_hd_combo;
2423 my $target_hd_label;
2424
2425 my $hdopion_first_setup = 1;
2426
2427 my $create_basic_grid = sub {
2428 my $grid = Gtk3::Grid->new();
2429 $grid->set_visible(1);
2430 $grid->set_column_spacing(10);
2431 $grid->set_row_spacing(10);
2432 $grid->set_hexpand(1);
2433
2434 $grid->set_margin_start(5);
2435 $grid->set_margin_end(5);
2436 $grid->set_margin_top(5);
2437 $grid->set_margin_bottom(5);
2438
2439 return $grid;
2440 };
2441
2442 my $create_label_widget_grid = sub {
2443 my ($labeled_widgets) = @_;
2444
2445 my $grid = &$create_basic_grid();
2446 my $row = 0;
2447
2448 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2449 my $widget = @$labeled_widgets[$i+1];
2450 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2451 $label->set_visible(1);
2452 $label->set_alignment (1, 0.5);
2453 $grid->attach($label, 0, $row, 1, 1);
2454 $widget->set_visible(1);
2455 $grid->attach($widget, 1, $row, 1, 1);
2456 $row++;
2457 }
2458
2459 return $grid;
2460 };
2461
2462 my $create_raid_disk_grid = sub {
2463 my $disk_labeled_widgets = [];
2464 for (my $i = 0; $i < @$hds; $i++) {
2465 my $disk_selector = Gtk3::ComboBoxText->new();
2466 $disk_selector->append_text("-- do not use --");
2467 $disk_selector->set_active(0);
2468 $disk_selector->set_visible(1);
2469 foreach my $hd (@$hds) {
2470 my ($disk, $devname, $size, $model) = @$hd;
2471 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2472 $disk_selector->{pve_disk_id} = $i;
2473 $disk_selector->signal_connect (changed => sub {
2474 my $w = shift;
2475 my $diskid = $w->{pve_disk_id};
2476 my $a = $w->get_active - 1;
2477 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2478 });
2479 }
2480
2481 if ($hdopion_first_setup) {
2482 $disk_selector->set_active ($i+1) if $hds->[$i];
2483 } else {
2484 my $hdind = 0;
2485 if (my $cur_hd = $config_options->{"disksel$i"}) {
2486 foreach my $hd (@$hds) {
2487 if (@$hd[1] eq @$cur_hd[1]) {
2488 $disk_selector->set_active($hdind+1);
2489 last;
2490 }
2491 $hdind++;
2492 }
2493 }
2494 }
2495
2496 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2497 }
2498
2499 my $scrolled_window = Gtk3::ScrolledWindow->new();
2500 $scrolled_window->set_hexpand(1);
2501 $scrolled_window->set_propagate_natural_height(1) if @$hds > 4;
2502 $scrolled_window->add(&$create_label_widget_grid($disk_labeled_widgets));
2503 $scrolled_window->set_policy('never', 'automatic');
2504
2505 return $scrolled_window;
2506 # &$create_label_widget_grid($disk_labeled_widgets)
2507 };
2508
2509 my $create_raid_advanced_grid = sub {
2510 my $labeled_widgets = [];
2511 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2512 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2513 $spinbutton_ashift->signal_connect ("value-changed" => sub {
2514 my $w = shift;
2515 $config_options->{ashift} = $w->get_value_as_int();
2516 });
2517 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
2518 $spinbutton_ashift->set_value($config_options->{ashift});
2519 push @$labeled_widgets, "ashift";
2520 push @$labeled_widgets, $spinbutton_ashift;
2521
2522 my $combo_compress = Gtk3::ComboBoxText->new();
2523 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
2524 # note: gzip / lze not allowed for bootfs vdevs
2525 my $comp_opts = ["on","off","lzjb","lz4"];
2526 foreach my $opt (@$comp_opts) {
2527 $combo_compress->append($opt, $opt);
2528 }
2529 $config_options->{compress} = "on" if !defined($config_options->{compress});
2530 $combo_compress->set_active_id($config_options->{compress});
2531 $combo_compress->signal_connect (changed => sub {
2532 my $w = shift;
2533 $config_options->{compress} = $w->get_active_text();
2534 });
2535 push @$labeled_widgets, "compress";
2536 push @$labeled_widgets, $combo_compress;
2537
2538 my $combo_checksum = Gtk3::ComboBoxText->new();
2539 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2540 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2541 foreach my $opt (@$csum_opts) {
2542 $combo_checksum->append($opt, $opt);
2543 }
2544 $config_options->{checksum} = "on" if !($config_options->{checksum});
2545 $combo_checksum->set_active_id($config_options->{checksum});
2546 $combo_checksum->signal_connect (changed => sub {
2547 my $w = shift;
2548 $config_options->{checksum} = $w->get_active_text();
2549 });
2550 push @$labeled_widgets, "checksum";
2551 push @$labeled_widgets, $combo_checksum;
2552
2553 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2554 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2555 $spinbutton_copies->signal_connect ("value-changed" => sub {
2556 my $w = shift;
2557 $config_options->{copies} = $w->get_value_as_int();
2558 });
2559 $config_options->{copies} = 1 if !defined($config_options->{copies});
2560 $spinbutton_copies->set_value($config_options->{copies});
2561 push @$labeled_widgets, "copies", $spinbutton_copies;
2562
2563 return &$create_label_widget_grid($labeled_widgets);;
2564 };
2565
2566 sub create_hdoption_view {
2567
2568 my $dialog = Gtk3::Dialog->new();
2569
2570 $dialog->set_title("Harddisk options");
2571
2572 $dialog->add_button("_OK", 1);
2573
2574 my $contarea = $dialog->get_content_area();
2575
2576 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2577 $contarea->pack_start($hbox2, 1, 1, 10);
2578
2579 my $grid = Gtk3::Grid->new();
2580 $grid->set_column_spacing(10);
2581 $grid->set_row_spacing(10);
2582
2583 $hbox2->pack_start($grid, 1, 0, 10);
2584
2585 my $row = 0;
2586
2587 # Filesystem type
2588
2589 my $label0 = Gtk3::Label->new ("Filesystem");
2590 $label0->set_alignment (1, 0.5);
2591 $grid->attach($label0, 0, $row, 1, 1);
2592
2593 my $fstypecb = Gtk3::ComboBoxText->new();
2594
2595 my $fstype = ['ext3', 'ext4', 'xfs',
2596 'zfs (RAID0)', 'zfs (RAID1)',
2597 'zfs (RAID10)', 'zfs (RAIDZ-1)',
2598 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2599
2600 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
2601 if $setup->{enable_btrfs};
2602
2603 my $tcount = 0;
2604 foreach my $tmp (@$fstype) {
2605 $fstypecb->append_text($tmp);
2606 $fstypecb->set_active ($tcount)
2607 if $config_options->{filesys} eq $tmp;
2608 $tcount++;
2609 }
2610
2611 $grid->attach($fstypecb, 1, $row, 1, 1);
2612
2613 $hbox2->show_all();
2614
2615 $row++;
2616
2617 my $sep = Gtk3::HSeparator->new();
2618 $sep->set_visible(1);
2619 $grid->attach($sep, 0, $row, 2, 1);
2620 $row++;
2621
2622 my $hdsize_labeled_widgets = [];
2623
2624 # size compute
2625 my $hdsize = 0;
2626 if ( -b $target_hd) {
2627 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
2628 } elsif ($target_hd) {
2629 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
2630 }
2631
2632 my $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
2633 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2634 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2635 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
2636
2637 my $entry_swapsize = Gtk3::Entry->new();
2638 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
2639 $entry_swapsize->signal_connect (key_press_event => \&check_float);
2640 $entry_swapsize->set_text($config_options->{swapsize}) if $config_options->{swapsize};
2641 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
2642
2643 my $entry_maxroot = Gtk3::Entry->new();
2644 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
2645 $entry_maxroot->signal_connect (key_press_event => \&check_float);
2646 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
2647 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
2648
2649 my $entry_minfree = Gtk3::Entry->new();
2650 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
2651 $entry_minfree->signal_connect (key_press_event => \&check_float);
2652 $entry_minfree->set_text($config_options->{minfree}) if $config_options->{minfree};
2653 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
2654
2655 my $entry_maxvz = Gtk3::Entry->new();
2656 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
2657 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2658 $entry_maxvz->set_text($config_options->{maxvz}) if $config_options->{maxvz};
2659 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
2660
2661 my $options_stack = Gtk3::Stack->new();
2662 $options_stack->set_visible(1);
2663 $options_stack->set_hexpand(1);
2664 $options_stack->set_vexpand(1);
2665 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
2666 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
2667 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
2668 $options_stack->set_visible_child_name("raiddisk");
2669 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2670 $options_stack_switcher->set_halign('center');
2671 $options_stack_switcher->set_stack($options_stack);
2672 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
2673 $row++;
2674 $grid->attach($options_stack, 0, $row, 2, 1);
2675 $row++;
2676
2677 $hdopion_first_setup = 0;
2678
2679 my $switch_view = sub {
2680 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
2681 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
2682
2683 $target_hd_combo->set_visible(!$raid);
2684 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
2685 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
2686 $options_stack_switcher->set_visible($enable_zfs_opts);
2687 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
2688 if ($raid) {
2689 $target_hd_label->set_text("Target: $config_options->{filesys} ");
2690 $options_stack->set_visible_child_name("raiddisk");
2691 } else {
2692 $target_hd_label->set_text("Target Harddisk: ");
2693 }
2694 my (undef, $pref_width) = $dialog->get_preferred_width();
2695 my (undef, $pref_height) = $dialog->get_preferred_height();
2696 $pref_height = 750 if $pref_height > 750;
2697 $dialog->resize($pref_width, $pref_height);
2698 };
2699
2700 &$switch_view();
2701
2702 $fstypecb->signal_connect (changed => sub {
2703 $config_options->{filesys} = $fstypecb->get_active_text();
2704 &$switch_view();
2705 });
2706
2707 $dialog->show();
2708
2709 $dialog->run();
2710
2711 my $get_float = sub {
2712 my ($entry) = @_;
2713
2714 my $text = $entry->get_text();
2715 return undef if !defined($text);
2716
2717 $text =~ s/^\s+//;
2718 $text =~ s/\s+$//;
2719
2720 return undef if $text !~ m/^\d+(\.\d+)?$/;
2721
2722 return $text;
2723 };
2724
2725 my $tmp;
2726
2727 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
2728 $config_options->{hdsize} = $tmp;
2729 } else {
2730 delete $config_options->{hdsize};
2731 }
2732
2733 if (defined($tmp = &$get_float($entry_swapsize))) {
2734 $config_options->{swapsize} = $tmp;
2735 } else {
2736 delete $config_options->{swapsize};
2737 }
2738
2739 if (defined($tmp = &$get_float($entry_maxroot))) {
2740 $config_options->{maxroot} = $tmp;
2741 } else {
2742 delete $config_options->{maxroot};
2743 }
2744
2745 if (defined($tmp = &$get_float($entry_minfree))) {
2746 $config_options->{minfree} = $tmp;
2747 } else {
2748 delete $config_options->{minfree};
2749 }
2750
2751 if (defined($tmp = &$get_float($entry_maxvz))) {
2752 $config_options->{maxvz} = $tmp;
2753 } else {
2754 delete $config_options->{maxvz};
2755 }
2756
2757 $dialog->destroy();
2758 }
2759
2760 my $get_raid_devlist = sub {
2761
2762 my $dev_name_hash = {};
2763
2764 my $devlist = [];
2765 for (my $i = 0; $i < @$hds; $i++) {
2766 if (my $hd = $config_options->{"disksel$i"}) {
2767 my ($disk, $devname, $size, $model) = @$hd;
2768 die "device '$devname' is used more than once\n"
2769 if $dev_name_hash->{$devname};
2770 $dev_name_hash->{$devname} = $hd;
2771 push @$devlist, $hd;
2772 }
2773 }
2774
2775 return $devlist;
2776 };
2777
2778 sub zfs_mirror_size_check {
2779 my ($expected, $actual) = @_;
2780
2781 die "mirrored disks must have same size\n"
2782 if abs($expected - $actual) > $expected / 10;
2783 }
2784
2785 sub get_zfs_raid_setup {
2786
2787 my $filesys = $config_options->{filesys};
2788
2789 my $devlist = &$get_raid_devlist();
2790
2791 my $diskcount = scalar(@$devlist);
2792 die "$filesys needs at least one device\n" if $diskcount < 1;
2793
2794 my $bootdevlist = [];
2795
2796 my $cmd= '';
2797 if ($filesys eq 'zfs (RAID0)') {
2798 push @$bootdevlist, @$devlist[0];
2799 foreach my $hd (@$devlist) {
2800 $cmd .= " @$hd[1]";
2801 }
2802 } elsif ($filesys eq 'zfs (RAID1)') {
2803 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
2804 $cmd .= ' mirror ';
2805 my $hd = @$devlist[0];
2806 my $expected_size = @$hd[2]; # all disks need approximately same size
2807 foreach $hd (@$devlist) {
2808 zfs_mirror_size_check($expected_size, @$hd[2]);
2809 $cmd .= " @$hd[1]";
2810 push @$bootdevlist, $hd;
2811 }
2812 } elsif ($filesys eq 'zfs (RAID10)') {
2813 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
2814 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
2815
2816 push @$bootdevlist, @$devlist[0], @$devlist[1];
2817
2818 for (my $i = 0; $i < $diskcount; $i+=2) {
2819 my $hd1 = @$devlist[$i];
2820 my $hd2 = @$devlist[$i+1];
2821 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
2822 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
2823 }
2824
2825 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
2826 my $level = $1;
2827 my $mindisks = 2 + $level;
2828 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
2829 my $hd = @$devlist[0];
2830 my $expected_size = @$hd[2]; # all disks need approximately same size
2831 $cmd .= " raidz$level";
2832 foreach $hd (@$devlist) {
2833 zfs_mirror_size_check($expected_size, @$hd[2]);
2834 $cmd .= " @$hd[1]";
2835 push @$bootdevlist, $hd;
2836 }
2837 } else {
2838 die "unknown zfs mode '$filesys'\n";
2839 }
2840
2841 return ($devlist, $bootdevlist, $cmd);
2842 }
2843
2844 sub get_btrfs_raid_setup {
2845
2846 my $filesys = $config_options->{filesys};
2847
2848 my $devlist = &$get_raid_devlist();
2849
2850 my $diskcount = scalar(@$devlist);
2851 die "$filesys needs at least one device\n" if $diskcount < 1;
2852
2853 my $mode;
2854
2855 if ($diskcount == 1) {
2856 $mode = 'single';
2857 } else {
2858 if ($filesys eq 'btrfs (RAID0)') {
2859 $mode = 'raid0';
2860 } elsif ($filesys eq 'btrfs (RAID1)') {
2861 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
2862 $mode = 'raid1';
2863 } elsif ($filesys eq 'btrfs (RAID10)') {
2864 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
2865 $mode = 'raid10';
2866 } else {
2867 die "unknown btrfs mode '$filesys'\n";
2868 }
2869 }
2870
2871 return ($devlist, $mode);
2872 }
2873
2874 sub create_hdsel_view {
2875
2876 cleanup_view ();
2877
2878 my $vbox = Gtk3::VBox->new (0, 0);
2879 $inbox->pack_start ($vbox, 1, 0, 0);
2880 my $hbox = Gtk3::HBox->new (0, 0);
2881 $vbox->pack_start ($hbox, 0, 0, 10);
2882
2883 my ($disk, $devname, $size, $model) = @{@$hds[0]};
2884 $target_hd = $devname;
2885
2886 $target_hd_label = Gtk3::Label->new ("Target Harddisk: ");
2887 $hbox->pack_start ($target_hd_label, 0, 0, 0);
2888
2889 $target_hd_combo = Gtk3::ComboBoxText->new();
2890
2891 foreach my $hd (@$hds) {
2892 ($disk, $devname, $size, $model) = @$hd;
2893 $target_hd_combo->append_text (get_device_desc ($devname, $size, $model));
2894 }
2895
2896 $target_hd_combo->set_active (0);
2897 $target_hd_combo->signal_connect (changed => sub {
2898 $a = shift->get_active;
2899 my ($disk, $devname) = @{@$hds[$a]};
2900 $target_hd = $devname;
2901 });
2902
2903 $hbox->pack_start ($target_hd_combo, 0, 0, 10);
2904
2905 my $options = Gtk3::Button->new ('_Options');
2906 $options->signal_connect (clicked => \&create_hdoption_view);
2907 $hbox->pack_start ($options, 0, 0, 0);
2908
2909
2910 $inbox->show_all;
2911
2912 display_html ("page1.htm");
2913
2914 set_next (undef, sub {
2915
2916 if ($config_options->{filesys} =~ m/zfs/) {
2917 eval { get_zfs_raid_setup(); };
2918 if (my $err = $@) {
2919 display_message ("Warning: $err\n" .
2920 "Please fix ZFS setup first.");
2921 } else {
2922 create_country_view();
2923 }
2924 } elsif ($config_options->{filesys} =~ m/btrfs/) {
2925 eval { get_btrfs_raid_setup(); };
2926 if (my $err = $@) {
2927 display_message ("Warning: $err\n" .
2928 "Please fix BTRFS setup first.");
2929 } else {
2930 create_country_view();
2931 }
2932 } else {
2933 create_country_view();
2934 }
2935 });
2936 }
2937
2938 sub create_extract_view {
2939
2940 cleanup_view ();
2941
2942 display_html ("extract1-license.htm");
2943 $next->set_sensitive (0);
2944
2945 my $vbox = Gtk3::VBox->new (0, 0);
2946 $inbox->pack_start ($vbox, 1, 0, 0);
2947 my $hbox = Gtk3::HBox->new (0, 0);
2948 $vbox->pack_start ($hbox, 0, 0, 10);
2949
2950 my $vbox2 = Gtk3::VBox->new (0, 0);
2951 $hbox->pack_start ($vbox2, 0, 0, 0);
2952
2953 $progress_status = Gtk3::Label->new ('');
2954 $vbox2->pack_start ($progress_status, 1, 1, 0);
2955
2956 $progress = Gtk3::ProgressBar->new;
2957 $progress->set_show_text(1);
2958 $progress->set_size_request (600, -1);
2959
2960 $vbox2->pack_start ($progress, 0, 0, 0);
2961
2962 $inbox->show_all;
2963
2964 my $tdir = $opt_testmode ? "target" : "/target";
2965 mkdir $tdir;
2966 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
2967
2968 eval { extract_data ($base, $tdir); };
2969 my $err = $@;
2970
2971 $next->set_sensitive (1);
2972
2973 set_next ("_Reboot", sub { exit (0); } );
2974
2975 if ($err) {
2976 display_html ("fail.htm");
2977 display_error ($err);
2978 } else {
2979 cleanup_view ();
2980 display_html ("success.htm");
2981 }
2982 }
2983
2984 sub create_intro_view {
2985
2986 cleanup_view ();
2987
2988 if ($setup->{product} eq 'pve') {
2989 eval {
2990 my $cpuinfo = file_get_contents('/proc/cpuinfo');
2991 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
2992 display_error("No support for KVM virtualisation detected.\n\n" .
2993 "Check BIOS settings for Intel VT / AMD-V / SVM.")
2994 }
2995 };
2996 }
2997
2998 display_html ("license.htm");
2999
3000 set_next ("I a_gree", \&create_hdsel_view);
3001 }
3002
3003 $ipconf = get_ip_config ();
3004
3005 $country = detect_country() if $ipconf->{default} || $opt_testmode;
3006
3007 # read country, kmap and timezone infos
3008 $cmap = read_cmap ();
3009
3010 if (!defined($cmap->{country}->{$country})) {
3011 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3012 $country = undef;
3013 }
3014
3015 create_main_window ();
3016
3017 my $initial_error = 0;
3018
3019 if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3020 print "no hardisks found\n";
3021 $initial_error = 1;
3022 display_html ("nohds.htm");
3023 set_next ("Reboot", sub { exit (0); } );
3024 } else {
3025 foreach my $hd (@$hds) {
3026 my ($disk, $devname) = @$hd;
3027 next if $devname =~ m|^/dev/md\d+$|;
3028 print "found Disk$disk N:$devname\n";
3029 }
3030 }
3031
3032 if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3033 print "no network interfaces found\n";
3034 $initial_error = 1;
3035 display_html ("nonics.htm");
3036 set_next ("Reboot", sub { exit (0); } );
3037 }
3038
3039 create_intro_view () if !$initial_error;
3040
3041 Gtk3->main;
3042
3043 exit 0;