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