]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
handle maxvz=0 correctly
[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";
9bb301fb 975 my $swapfile;
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
2ba9752e 1013 if (defined($config_options->{maxvz})) {
b6e875ca
DM
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
9bb301fb
FG
1025 if ($swap_size) {
1026 syscmd ("/sbin/lvcreate -L${swap_size}K -nswap $vgname") == 0 ||
1027 die "unable to create swap volume\n";
1028
1029 $swapfile = "/dev/$vgname/swap";
1030 }
89a12446 1031
f7d18efd 1032 syscmd ("/sbin/lvcreate -L${rootsize}K -nroot $vgname") == 0 ||
eb4b1e56 1033 die "unable to create root volume\n";
89a12446 1034
d1969047
FG
1035 if ($datasize > 4*1024*1024) {
1036 my $metadatasize = $datasize/100; # default 1% of data
1037 $metadatasize = 1024*1024 if $metadatasize < 1024*1024; # but at least 1G
1038 $metadatasize = 16*1024*1024 if $metadatasize > 16*1024*1024; # but at most 16G
1039
1040 # otherwise the metadata is taken out of $minfree
1041 $datasize -= 2*$metadatasize;
1042
1043 # 1 4MB PE to allow for rounding
1044 $datasize -= 4*1024;
1045
b6e875ca
DM
1046 syscmd ("/sbin/lvcreate -L${datasize}K -ndata $vgname") == 0 ||
1047 die "unable to create data volume\n";
89a12446 1048
d1969047 1049 syscmd ("/sbin/lvconvert --yes --type thin-pool --poolmetadatasize ${metadatasize}K $vgname/data") == 0 ||
b6e875ca
DM
1050 die "unable to create data thin-pool\n";
1051 } else {
1052 $datadev = undef;
1053 }
5fd81672 1054
f7d18efd 1055 syscmd ("/sbin/vgchange -a y $vgname") == 0 ||
eb4b1e56 1056 die "unable to activate volume group\n";
7bc4f6bd 1057
b6e875ca 1058 return ($rootdev, $swapfile, $datadev);
c6ed3b24 1059}
7bc4f6bd 1060
c6ed3b24
DM
1061sub compute_swapsize {
1062 my ($hdsize) = @_;
89a12446 1063
c6ed3b24 1064 my $hdgb = int($hdsize/(1024*1024));
5c06ced5 1065
c6ed3b24 1066 my $swapsize;
9bb301fb 1067 if (defined($config_options->{swapsize})) {
c6ed3b24
DM
1068 $swapsize = $config_options->{swapsize}*1024*1024;
1069 } else {
1070 my $ss = int ($total_memory / 1024);
1071 $ss = 4 if $ss < 4;
1072 $ss = ($hdgb/8) if $ss > ($hdgb/8);
cbdfeb36 1073 $ss = 8 if $ss > 8;
c6ed3b24
DM
1074 $swapsize = $ss*1024*1024;
1075 }
d0d8ce3f
DM
1076
1077 return $swapsize;
c6ed3b24 1078}
5c06ced5 1079
121ebc59 1080
c6ed3b24 1081sub extract_data {
fafc616c 1082 my ($basefile, $targetdir) = @_;
89a12446 1083
c6ed3b24 1084 die "target '$targetdir' does not exist\n" if ! -d $targetdir;
89a12446 1085
121ebc59
DM
1086 my $starttime = [Time::HiRes::gettimeofday];
1087
c6ed3b24 1088 my $bootdevinfo = [];
84761f93 1089
c6ed3b24
DM
1090 my $swapfile;
1091 my $rootdev;
84761f93 1092
121ebc59
DM
1093 my $use_zfs = 0;
1094 my $use_btrfs = 0;
89092156 1095
c6ed3b24 1096 my $filesys = $config_options->{filesys};
89092156 1097
c6ed3b24
DM
1098 if ($filesys =~ m/zfs/) {
1099 $target_hd = undef; # do not use this config
1100 $use_zfs = 1;
5772392c 1101 $targetdir = "/$zfspoolname/ROOT/$zfsrootvolname";
121ebc59
DM
1102 } elsif ($filesys =~ m/btrfs/) {
1103 $target_hd = undef; # do not use this config
1104 $use_btrfs = 1;
c6ed3b24 1105 }
1464c7c9 1106
c6ed3b24
DM
1107 if ($use_zfs) {
1108 my $i;
1109 for ($i = 5; $i > 0; $i--) {
1110 syscmd("modprobe zfs");
1111 last if -c "/dev/zfs";
1112 sleep(1);
1113 }
89092156 1114
c6ed3b24
DM
1115 die "unable to load zfs kernel module\n" if !$i;
1116 }
89092156 1117
c6ed3b24 1118 eval {
89a12446 1119
89a12446 1120
c6ed3b24 1121 my $maxper = 0.25;
89a12446 1122
c6ed3b24
DM
1123 update_progress (0, 0, $maxper, "create partitions");
1124
857c43a9
FG
1125 syscmd("vgchange -an") if !$opt_testmode; # deactivate all detected VGs
1126
c6ed3b24 1127 if ($opt_testmode) {
89a12446 1128
6b900321
DM
1129 $rootdev = abs_path($opt_testmode);
1130 syscmd("umount $rootdev");
121ebc59 1131
6b900321 1132 if ($use_btrfs) {
121ebc59 1133
1464c7c9 1134 die "unsupported btrfs mode (for testing environment)\n"
121ebc59
DM
1135 if $filesys ne 'btrfs (RAID0)';
1136
1137 btrfs_create([$rootdev], 'single');
5c06ced5 1138
121ebc59 1139 } elsif ($use_zfs) {
5c06ced5 1140
121ebc59 1141 die "unsupported zfs mode (for testing environment)\n"
c6ed3b24
DM
1142 if $filesys ne 'zfs (RAID0)';
1143
6b900321 1144 syscmd ("zpool destroy $zfstestpool");
5c06ced5 1145
5fd81672 1146 zfs_create_rpool($rootdev);
1464c7c9 1147
121ebc59
DM
1148 } else {
1149
6b900321 1150 # nothing to do
121ebc59
DM
1151 }
1152
1153 } elsif ($use_btrfs) {
1154
1155 my ($devlist, $btrfs_mode) = get_btrfs_raid_setup();
1156 my $btrfs_partitions = [];
1157 my $disksize;
1158 foreach my $hd (@$devlist) {
1159 my $devname = @$hd[1];
857c43a9 1160 &$clean_disk($devname);
121ebc59
DM
1161 my ($size, $osdev, $efidev) =
1162 partition_bootable_disk($devname, undef, '8300');
1163 $rootdev = $osdev if !defined($rootdev); # simply point to first disk
1164 my $by_id = find_stable_path("/dev/disk/by-id", $devname);
1165 push @$bootdevinfo, { esp => $efidev, devname => $devname,
1166 osdev => $osdev, by_id => $by_id };
1167 push @$btrfs_partitions, $osdev;
1168 $disksize = $size;
5c06ced5 1169 }
c6ed3b24 1170
121ebc59
DM
1171 &$udevadm_trigger_block();
1172
1173 btrfs_create($btrfs_partitions, $btrfs_mode);
1174
c6ed3b24
DM
1175 } elsif ($use_zfs) {
1176
c6ed3b24
DM
1177 my ($devlist, $bootdevlist, $vdev) = get_zfs_raid_setup();
1178
1179 my $disksize;
857c43a9
FG
1180 foreach my $hd (@$devlist) {
1181 &$clean_disk(@$hd[1]);
1182 }
c6ed3b24
DM
1183 foreach my $hd (@$bootdevlist) {
1184 my $devname = @$hd[1];
f810f5d0
DM
1185 my ($size, $osdev) =
1186 partition_bootable_zfs_disk($devname);
14aacec8 1187 zfs_mirror_size_check($disksize, $size) if $disksize;
f810f5d0 1188 push @$bootdevinfo, { devname => $devname, osdev => $osdev};
c6ed3b24 1189 $disksize = $size;
c6ed3b24
DM
1190 }
1191
121ebc59 1192 &$udevadm_trigger_block();
c6ed3b24 1193
35c6f89c
DM
1194 foreach my $di (@$bootdevinfo) {
1195 my $devname = $di->{devname};
1196 $di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
1464c7c9 1197
35c6f89c
DM
1198 # Note: using /dev/disk/by-id/ does not work for unknown reason, we get
1199 # cannot create 'rpool': no such pool or dataset
1200 #my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
c6ed3b24 1201
35c6f89c
DM
1202 my $osdev = $di->{osdev};
1203 $vdev =~ s/ $devname/ $osdev/;
1204 }
1205
5fd81672 1206 zfs_create_rpool($vdev);
1464c7c9 1207
c6ed3b24 1208 my $swap_size = compute_swapsize($disksize);
9bb301fb 1209 $swapfile = zfs_create_swap($swap_size) if $swap_size;
c6ed3b24
DM
1210
1211 } else {
1212
1213 die "target '$target_hd' is not a valid block device\n" if ! -b $target_hd;
1214
1215 my $maxhdsize;
1216 if ($config_options->{hdsize}) {
1217 # max hdsize passed on cmdline (GB)
1218 $maxhdsize = $config_options->{hdsize}*1024*1024;
1219 }
1220
857c43a9
FG
1221 &$clean_disk($target_hd);
1222
1464c7c9
DM
1223 my ($os_size, $osdev, $efidev);
1224 ($os_size, $osdev, $efidev) =
121ebc59 1225 partition_bootable_disk($target_hd, $maxhdsize, '8E00');
c6ed3b24 1226
121ebc59 1227 &$udevadm_trigger_block();
c6ed3b24 1228
35c6f89c 1229 my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
1464c7c9 1230 push @$bootdevinfo, { esp => $efidev, devname => $target_hd,
35c6f89c 1231 osdev => $osdev, by_id => $by_id };
c6ed3b24 1232
35c6f89c 1233 my $swap_size = compute_swapsize($os_size);
b6e875ca 1234 ($rootdev, $swapfile) =
35c6f89c 1235 create_lvm_volumes($osdev, $os_size, $swap_size);
c6ed3b24 1236
35c6f89c 1237 # trigger udev to create /dev/disk/by-uuid
121ebc59 1238 &$udevadm_trigger_block(1);
89a12446
DM
1239 }
1240
481671c3
DM
1241 if ($use_zfs) {
1242 # to be fast during installation
1243 syscmd ("zfs set sync=disabled $zfspoolname") == 0 ||
1244 die "unable to set zfs properties\n";
1245 }
1246
89a12446
DM
1247 update_progress (0.03, 0, $maxper, "create swap space");
1248 if ($swapfile) {
7bc4f6bd 1249 syscmd ("mkswap -f $swapfile") == 0 ||
89a12446
DM
1250 die "unable to create swap space\n";
1251 }
1252
1253 update_progress (0.05, 0, $maxper, "creating filesystems");
1254
c6ed3b24 1255 foreach my $di (@$bootdevinfo) {
f810f5d0 1256 next if !$di->{esp};
1464c7c9 1257 syscmd ("mkfs.vfat -F32 $di->{esp}") == 0 ||
c6ed3b24
DM
1258 die "unable to initialize EFI ESP on device $di->{esp}\n";
1259 }
1260
121ebc59
DM
1261 if ($use_zfs) {
1262 # do nothing
1263 } elsif ($use_btrfs) {
1264 # do nothing
1265 } else {
1266 create_filesystem ($rootdev, 'root', $filesys, 0.05, $maxper, 0, 1);
89a12446
DM
1267 }
1268
1269 update_progress (1, 0.05, $maxper, "mounting target $rootdev");
1270
121ebc59
DM
1271 if ($use_zfs) {
1272 # do nothing
121ebc59 1273 } else {
6e56032e
FG
1274 my $mount_opts = 'noatime';
1275 $mount_opts .= ',nobarrier'
1276 if $use_btrfs || $filesys =~ /^ext\d$/;
1277
1278 syscmd("mount -n $rootdev -o $mount_opts $targetdir") == 0 ||
35c6f89c
DM
1279 die "unable to mount $rootdev\n";
1280 }
89a12446 1281
35c6f89c
DM
1282 mkdir "$targetdir/boot";
1283 mkdir "$targetdir/boot/efi";
89a12446 1284
5fd81672
DM
1285 mkdir "$targetdir/var";
1286 mkdir "$targetdir/var/lib";
121ebc59 1287
f7d18efd
DM
1288 if ($setup->{product} eq 'pve') {
1289 mkdir "$targetdir/var/lib/vz";
1290 mkdir "$targetdir/var/lib/pve";
1291
1292 if ($use_btrfs) {
1293 syscmd("btrfs subvolume create $targetdir/var/lib/pve/local-btrfs") == 0 ||
1294 die "unable to create btrfs subvolume\n";
1295 }
121ebc59 1296 }
89a12446 1297
89a12446
DM
1298 update_progress (1, 0.05, $maxper, "extracting base system");
1299
fafc616c
DM
1300 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
1301 $ino || die "unable to open file '$basefile' - $!\n";
968fa90b 1302
c437cef5
DM
1303 my $files = file_read_firstline("${proxmox_cddir}/proxmox/$setup->{product}-base.cnt") ||
1304 die "unable to read base file count\n";
89a12446
DM
1305
1306 my $per = 0;
1307 my $count = 0;
1308
fafc616c 1309 run_command ("unsquashfs -f -dest $targetdir -i $basefile", sub {
89a12446 1310 my $line = shift;
fafc616c 1311 return if $line !~ m/^$targetdir/;
89a12446
DM
1312 $count++;
1313 my $nper = int (($count *100)/$files);
1314 if ($nper != $per) {
1315 $per = $nper;
0f3d1edd 1316 my $frac = $per > 100 ? 1 : $per/100;
89a12446
DM
1317 update_progress ($frac, $maxper, 0.5);
1318 }
1319 });
1320
1321 syscmd ("mount -n -t tmpfs tmpfs $targetdir/tmp") == 0 ||
1322 die "unable to mount tmpfs on $targetdir/tmp\n";
1323 syscmd ("mount -n -t proc proc $targetdir/proc") == 0 ||
1324 die "unable to mount proc on $targetdir/proc\n";
1325 syscmd ("mount -n -t sysfs sysfs $targetdir/sys") == 0 ||
1326 die "unable to mount sysfs on $targetdir/sys\n";
1327
89a12446
DM
1328 update_progress (1, $maxper, 0.5, "configuring base system");
1329
1330 # configure hosts
1331
968fa90b 1332 my $hosts =
89a12446 1333 "127.0.0.1 localhost.localdomain localhost\n" .
968fa90b 1334 "$ipaddress $hostname.$domain $hostname pvelocalhost\n\n" .
89a12446
DM
1335 "# The following lines are desirable for IPv6 capable hosts\n\n" .
1336 "::1 ip6-localhost ip6-loopback\n" .
1337 "fe00::0 ip6-localnet\n" .
1338 "ff00::0 ip6-mcastprefix\n" .
1339 "ff02::1 ip6-allnodes\n" .
1340 "ff02::2 ip6-allrouters\n" .
1341 "ff02::3 ip6-allhosts\n";
1342
968fa90b 1343 write_config ($hosts, "$targetdir/etc/hosts");
89a12446 1344
968fa90b 1345 write_config ("$hostname\n", "$targetdir/etc/hostname");
89a12446
DM
1346
1347 syscmd ("/bin/hostname $hostname") if !$opt_testmode;
1348
1349 # configure interfaces
1350
b6200603
DM
1351 my $ifaces = "auto lo\niface lo inet loopback\n\n";
1352
1353 my $ntype = $ipversion == 4 ? 'inet' : 'inet6';
1354
4a0331ab
DM
1355 my $ethdev = $ipconf->{ifaces}->{$ipconf->{selected}}->{name};
1356
1357 if ($setup->{bridged_network}) {
1358 $ifaces .= "iface $ethdev $ntype manual\n";
1359
1360 $ifaces .=
1361 "\nauto vmbr0\niface vmbr0 $ntype static\n" .
1362 "\taddress $ipaddress\n" .
1363 "\tnetmask $netmask\n" .
1364 "\tgateway $gateway\n" .
1365 "\tbridge_ports $ethdev\n" .
1366 "\tbridge_stp off\n" .
1367 "\tbridge_fd 0\n";
1368 } else {
1369 $ifaces .= "auto $ethdev\n" .
1370 "iface $ethdev $ntype static\n" .
1371 "\taddress $ipaddress\n" .
1372 "\tnetmask $netmask\n" .
1373 "\tgateway $gateway\n";
1374 }
89a12446 1375
fe44bd92
FG
1376 foreach my $iface (sort keys %{$ipconf->{ifaces}}) {
1377 my $name = $ipconf->{ifaces}->{$iface}->{name};
4a0331ab 1378 next if $name eq $ethdev;
fe44bd92
FG
1379
1380 $ifaces .= "\niface $name $ntype manual\n";
1381 }
1382
89a12446
DM
1383 write_config ($ifaces, "$targetdir/etc/network/interfaces");
1384
1385 # configure dns
1386
39a0f44b
FG
1387 my $resolvconf = "search $domain\nnameserver $dnsserver\n";
1388 write_config ($resolvconf, "$targetdir/etc/resolv.conf");
89a12446 1389
5c06ced5
DM
1390 # configure fstab
1391
1392 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass>\n";
1393
121ebc59
DM
1394 if ($use_zfs) {
1395 # do nothing
1396 } elsif ($use_btrfs) {
1397 my $fsuuid;
1398 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev";
1399 run_command($cmd, sub {
1400 my $line = shift;
1401
1402 if ($line =~ m/^UUID=([A-Fa-f0-9\-]+)$/) {
1403 $fsuuid = $1;
1404 }
1405 });
1406
1407 die "unable to detect FS UUID" if !defined($fsuuid);
1408
1409 $fstab .= "UUID=$fsuuid / btrfs defaults 0 1\n";
1410 } else {
80090926
DM
1411 my $root_mountopt = $fssetup->{$filesys}->{root_mountopt} || 'defaults';
1412 $fstab .= "$rootdev / $filesys ${root_mountopt} 0 1\n";
7bc4f6bd 1413 }
a84ea010
DM
1414
1415 # mount /boot/efi
1416 # Note: this is required by current grub, but really dangerous, because
1417 # vfat does not have journaling, so it triggers manual fsck after each crash
1418 # so we only mount /boot/efi if really required (efi systems).
1419 if ($grub_plattform =~ m/^efi-/) {
1420 if (scalar(@$bootdevinfo)) {
f810f5d0
DM
1421 my $di = @$bootdevinfo[0]; # simply use first disk
1422 if ($di->{esp}) {
1423 my $efi_boot_uuid = $di->{esp};
1424 if (my $uuid = find_dev_by_uuid ($di->{esp})) {
1425 $efi_boot_uuid = "UUID=$uuid";
1426 }
1464c7c9 1427
f810f5d0
DM
1428 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1\n";
1429 }
a84ea010 1430 }
84761f93
DM
1431 }
1432
a84ea010 1433
89a12446
DM
1434 $fstab .= "$swapfile none swap sw 0 0\n" if $swapfile;
1435
1436 $fstab .= "proc /proc proc defaults 0 0\n";
1437
1438 write_config ($fstab, "$targetdir/etc/fstab");
1439 write_config ("", "$targetdir/etc/mtab");
968fa90b 1440
a04ac176 1441 syscmd ("cp ${proxmox_libdir}/policy-disable-rc.d " .
968fa90b 1442 "$targetdir/usr/sbin/policy-rc.d") == 0 ||
89a12446 1443 die "unable to copy policy-rc.d\n";
a04ac176 1444 syscmd ("cp ${proxmox_libdir}/fake-start-stop-daemon " .
968fa90b 1445 "$targetdir/sbin/") == 0 ||
89a12446
DM
1446 die "unable to copy start-stop-daemon\n";
1447
1448 diversion_add ($targetdir, "/sbin/start-stop-daemon", "/sbin/fake-start-stop-daemon");
1449 diversion_add ($targetdir, "/usr/sbin/update-grub", "/bin/true");
1450 diversion_add ($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
1451
1452 syscmd ("touch $targetdir/proxmox_install_mode");
1453
e35d5efb 1454 my $grub_install_devices_txt = '';
3573c046 1455 foreach my $di (@$bootdevinfo) {
e35d5efb 1456 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt;
ff863262 1457 $grub_install_devices_txt .= $di->{by_id} || $di->{devname};
3573c046
DM
1458 }
1459
b1293fcb
FG
1460 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1461 my $xkmap = $cmap->{kmap}->{$keymap}->{x11} // 'us';
1464c7c9 1462
89a12446
DM
1463 debconfig_set ($targetdir, <<_EOD);
1464locales locales/default_environment_locale select en_US.UTF-8
1465locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1466samba-common samba-common/dhcp boolean false
1467samba-common samba-common/workgroup string WORKGROUP
e953719f 1468postfix postfix/main_mailer_type select No configuration
b1293fcb 1469keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
814f5c39 1470d-i debian-installer/locale select en_US.UTF-8
3573c046 1471grub-pc grub-pc/install_devices select $grub_install_devices_txt
89a12446
DM
1472_EOD
1473
89a12446 1474 my $pkg_count = 0;
97980bf2 1475 while (<${proxmox_pkgdir}/*.deb>) { $pkg_count++ };
89a12446 1476
121ebc59
DM
1477 # btrfs/dpkg is extremely slow without --force-unsafe-io
1478 my $dpkg_opts = $use_btrfs ? "--force-unsafe-io" : "";
1479
89a12446 1480 $count = 0;
97980bf2 1481 while (<${proxmox_pkgdir}/*.deb>) {
89a12446
DM
1482 chomp;
1483 my $path = $_;
97980bf2 1484 my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/;
1e61f3d8
DM
1485# if ($deb =~ m/^grub-efi-/ && $deb !~ m/^grub-${grub_plattform}/) {
1486# $count++;
1487# next;
1488# }
89a12446
DM
1489 update_progress ($count/$pkg_count, 0.5, 0.75, "extracting $deb");
1490 print "extracting: $deb\n";
1491 syscmd ("cp $path $targetdir/tmp/$deb") == 0 ||
1492 die "installation of package $deb failed\n";
121ebc59 1493 syscmd ("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/$deb") == 0 ||
968fa90b 1494 die "installation of package $deb failed\n";
89a12446
DM
1495 update_progress ((++$count)/$pkg_count, 0.5, 0.75);
1496 }
1497
3b11dce4
FG
1498 # needed for postfix postinst in case no other NIC is active
1499 syscmd("chroot $targetdir ifup lo");
1500
121ebc59 1501 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a";
89a12446
DM
1502 $count = 0;
1503 run_command ($cmd, sub {
1504 my $line = shift;
1505 if ($line =~ m/Setting up\s+(\S+)/) {
1506 update_progress ((++$count)/$pkg_count, 0.75, 0.95,
1507 "configuring $1");
1508 }
1509 });
968fa90b 1510
89a12446
DM
1511 unlink "$targetdir/etc/mailname";
1512 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
1513 write_config ($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
1514
1515 # make sure we have all postfix directories
1516 syscmd ("chroot $targetdir /usr/sbin/postfix check");
1517 # cleanup mail queue
1518 syscmd ("chroot $targetdir /usr/sbin/postsuper -d ALL");
1519
6b5dc3d0
DM
1520 # enable NTP (timedatectl set-ntp true does not work without DBUS)
1521 syscmd ("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
1522
89a12446
DM
1523 unlink "$targetdir/proxmox_install_mode";
1524
968fa90b 1525 # set timezone
89a12446
DM
1526 unlink ("$targetdir/etc/localtime");
1527 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
1528 write_config ("$timezone\n", "$targetdir/etc/timezone");
1529
89a12446
DM
1530 # set apt mirror
1531 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1532 my $fn = "$targetdir/etc/apt/sources.list";
968fa90b 1533 syscmd ("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
89a12446
DM
1534 }
1535
19edf8b7
DM
1536 # create extended_states for apt (avoid cron job warning if that
1537 # file does not exist)
1538 write_config ('', "$targetdir/var/lib/apt/extended_states");
1539
c2657b8b 1540 # allow ssh root login
abcadb95 1541 syscmd(['sed', '-i', 's/^#\?PermitRootLogin.*/PermitRootLogin yes/', "$targetdir/etc/ssh/sshd_config"]);
861a26d4
DM
1542
1543 if ($setup->{product} eq 'pmg') {
1544 # install initial clamav DB
1545 my $srcdir = "${proxmox_cddir}/proxmox/clamav";
05eb99e2 1546 foreach my $fn ("main.cvd", "bytecode.cvd", "daily.cvd", "safebrowsing.cvd") {
861a26d4
DM
1547 syscmd ("cp \"$srcdir/$fn\" \"$targetdir/var/lib/clamav\"") == 0 ||
1548 die "installation of clamav db file '$fn' failed\n";
1549 }
1550 syscmd("chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav") == 0 ||
1551 die "unable to set owner for clamav database files\n";
1552 }
1553
58a09baa
DM
1554 if ($setup->{product} eq 'pve') {
1555 # save installer settings
1556 my $ucc = uc ($country);
1557 debconfig_set($targetdir, "pve-manager pve-manager/country string $ucc\n");
1558 }
89a12446
DM
1559
1560 update_progress (0.8, 0.95, 1, "make system bootable");
1561
5c06ced5 1562 if ($use_zfs) {
5772392c 1563 syscmd ("sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT\\/$zfsrootvolname boot=zfs\"/' $targetdir/etc/default/grub") == 0 ||
5c06ced5 1564 die "unable to update /etc/default/grub\n";
1464c7c9 1565
5c06ced5 1566 }
23c337f5 1567
89a12446
DM
1568 diversion_remove ($targetdir, "/usr/sbin/update-grub");
1569 diversion_remove ($targetdir, "/usr/sbin/update-initramfs");
1570
56207f2a
DM
1571 my $kapi;
1572 foreach my $fn (<$targetdir/lib/modules/*>) {
1573 if ($fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$!) {
1574 die "found multiple kernels\n" if defined($kapi);
1575 $kapi = $1;
1576 }
1577 }
1578 die "unable to detect kernel version\n" if !defined($kapi);
1579
c6ed3b24 1580 if (!$opt_testmode) {
89a12446
DM
1581
1582 unlink ("$targetdir/etc/mtab");
1583 symlink ("/proc/mounts", "$targetdir/etc/mtab");
1584 syscmd ("mount -n --bind /dev $targetdir/dev");
1585
1586 syscmd ("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1587 die "unable to install initramfs\n";
1588
c6ed3b24
DM
1589 foreach my $di (@$bootdevinfo) {
1590 my $dev = $di->{devname};
f810f5d0
DM
1591 syscmd ("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1592 die "unable to install the i386-pc boot loader on '$dev'\n";
1593
1594 if ($di->{esp}) {
83250a74 1595 syscmd ("mount -n $di->{esp} -t vfat $targetdir/boot/efi") == 0 ||
f810f5d0 1596 die "unable to mount $di->{esp}\n";
5e0d6dce
FG
1597 my $rc = syscmd ("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
1598 if ($rc != 0) {
1599 if (-d '/sys/firmware/efi') {
1600 die "unable to install the EFI boot loader on '$dev'\n";
1601 } else {
1602 warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
1603 }
1604 }
fd3ec787
DM
1605 # also install fallback boot file (OVMF does not boot without)
1606 mkdir("$targetdir/boot/efi/EFI/BOOT");
1607 syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
1608 die "unable to copy efi boot loader\n";
1609
f810f5d0
DM
1610 syscmd ("umount $targetdir/boot/efi") == 0 ||
1611 die "unable to umount $targetdir/boot/efi\n";
1e61f3d8 1612 }
c6ed3b24 1613 }
89a12446
DM
1614
1615 syscmd ("chroot $targetdir /usr/sbin/update-grub") == 0 ||
c6ed3b24 1616 die "unable to update boot loader config\n";
89a12446
DM
1617
1618 syscmd ("umount $targetdir/dev");
1619 }
1620
968fa90b 1621 # cleanup
89a12446 1622
968fa90b 1623 # hack: remove dead.letter from sshd installation
89a12446
DM
1624 syscmd ("rm -rf $targetdir/dead.letter");
1625
89a12446
DM
1626 unlink "$targetdir/usr/sbin/policy-rc.d";
1627
1628 diversion_remove ($targetdir, "/sbin/start-stop-daemon");
1629
1630 # set root password
968fa90b 1631 my $octets = encode("utf-8", $password);
89a12446
DM
1632 run_command ("chroot $targetdir /usr/sbin/chpasswd", undef,
1633 "root:$octets\n");
7053f98b 1634
038552a1 1635 if ($setup->{product} eq 'pmg') {
038552a1 1636 # save admin email
fe172016
DM
1637 write_config ("section: admin\n\temail ${mailto}\n",
1638 "$targetdir/etc/pmg/pmg.conf");
038552a1
DM
1639
1640 } elsif ($setup->{product} eq 'pve') {
7053f98b 1641
8acc47b5 1642 # create pmxcfs DB
7053f98b 1643
8acc47b5
DM
1644 my $tmpdir = "$targetdir/tmp/pve";
1645 mkdir $tmpdir;
7053f98b 1646
8acc47b5
DM
1647 # write vnc keymap to datacenter.cfg
1648 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
1649 write_config ("keyboard: $vnckmap\n",
1650 "$tmpdir/datacenter.cfg");
968fa90b 1651
8acc47b5
DM
1652 # save admin email
1653 write_config ("user:root\@pam:1:0:::${mailto}::\n",
1654 "$tmpdir/user.cfg");
5fd81672 1655
8acc47b5
DM
1656 # write storage.cfg
1657 my $strorage_cfg_fn = "$tmpdir/storage.cfg";
1658 if ($use_zfs) {
1659 write_config ($storage_cfg_zfs, $strorage_cfg_fn);
1660 } elsif ($use_btrfs) {
1661 write_config ($storage_cfg_btrfs, $strorage_cfg_fn);
1662 } else {
1663 write_config ($storage_cfg_lvmthin, $strorage_cfg_fn);
1664 }
7053f98b 1665
8acc47b5
DM
1666 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1667
1668 syscmd ("rm -rf $tmpdir");
1669 }
89a12446
DM
1670 };
1671
1672 my $err = $@;
1673
1674 update_progress (1, 0, 1, "");
1675
1676 print $err if $err;
1677
1678 if ($opt_testmode) {
121ebc59
DM
1679 my $elapsed = Time::HiRes::tv_interval($starttime);
1680 print "Elapsed extract time: $elapsed\n";
1681
ef5f4f86 1682 syscmd ("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
89a12446
DM
1683 }
1684
89a12446
DM
1685 syscmd ("umount $targetdir/tmp");
1686 syscmd ("umount $targetdir/proc");
1687 syscmd ("umount $targetdir/sys");
6fbd1fb1
DM
1688
1689 if ($use_zfs) {
1690 syscmd ("zfs umount -a") == 0 ||
1691 die "unable to unmount zfs\n";
1692 } else {
1693 syscmd ("umount -d $targetdir");
1694 }
89a12446 1695
5c06ced5 1696 if (!$err && $use_zfs) {
481671c3
DM
1697 syscmd ("zfs set sync=standard $zfspoolname") == 0 ||
1698 die "unable to set zfs properties\n";
1699
5772392c 1700 syscmd ("zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname") == 0 ||
5c06ced5 1701 die "zfs set mountpoint failed\n";
1464c7c9 1702
5772392c 1703 syscmd ("zpool set bootfs=$zfspoolname/ROOT/$zfsrootvolname $zfspoolname") == 0 ||
5c06ced5 1704 die "zfs set bootfs failed\n";
016679a2 1705 syscmd ("zpool export $zfspoolname");
5c06ced5
DM
1706 }
1707
89a12446
DM
1708 die $err if $err;
1709}
1710
550958aa
DM
1711my $last_display_change = 0;
1712
1713my $display_info_counter = 0;
1714
1715my $display_info_items = [
1716 "extract1-license.htm",
1717 "extract2-rulesystem.htm",
1718 "extract3-spam.htm",
1719 "extract4-virus.htm",
1720 ];
1721
1722sub display_info {
1723
1724 my $min_display_time = 15;
1725
1726 my $ctime = time();
1727
1728 return if ($ctime - $last_display_change) < $min_display_time;
1729
1730 my $page = $display_info_items->[$display_info_counter % scalar(@$display_info_items)];
1731
1732 $display_info_counter++;
1733
1734 display_html($page);
1735}
1736
89a12446
DM
1737sub display_html {
1738 my ($filename) = @_;
1739
a04ac176 1740 my $path = "${proxmox_libdir}/html/$filename";
c437cef5 1741
8a50920c
DM
1742 my $url = "file://$path";
1743
1744 my $data = file_get_contents($path);
1745
1746 if ($filename eq 'license.htm') {
3c866639
DM
1747 my $license = decode('utf8', file_get_contents("${proxmox_cddir}/EULA"));
1748 my $title = "END USER LICENSE AGREEMENT (EULA)";
f91c161b 1749 $data =~ s/__LICENSE__/$license/;
8a50920c
DM
1750 $data =~ s/__LICENSE_TITLE__/$title/;
1751 }
1752
1753 $htmlview->load_html_string($data, $url);
550958aa
DM
1754
1755 $last_display_change = time();
7becc472
DM
1756}
1757
89a12446
DM
1758sub set_next {
1759 my ($text, $fctn) = @_;
1760
1761 $next_fctn = $fctn;
1762 $text = "_Next" if !$text;
1763 $next->set_label ($text);
968fa90b 1764
89a12446
DM
1765 $next->grab_focus ();
1766}
89a12446
DM
1767
1768sub create_main_window {
1769
7becc472 1770 $window = Gtk3::Window->new ();
89a12446 1771 $window->set_default_size (1024, 768);
84761f93 1772 $window->set_has_resize_grip(0);
89a12446
DM
1773 $window->set_decorated (0) if !$opt_testmode;
1774
7becc472 1775 my $vbox = Gtk3::VBox->new (0, 0);
89a12446 1776
782b4acd
DM
1777 my $logofn = "$setup->{product}-banner.png";
1778 my $image = Gtk3::Image->new_from_file("${proxmox_libdir}/$logofn");
89a12446
DM
1779 $vbox->pack_start ($image, 0, 0, 0);
1780
7becc472 1781 my $hbox = Gtk3::HBox->new (0, 0);
89a12446
DM
1782 $vbox->pack_start ($hbox, 1, 1, 0);
1783
7becc472
DM
1784 # my $f1 = Gtk3::Frame->new ('test');
1785 # $f1->set_shadow_type ('none');
1786 # $hbox->pack_start ($f1, 1, 1, 0);
89a12446 1787
7becc472 1788 my $sep1 = Gtk3::HSeparator->new;
89a12446
DM
1789 $vbox->pack_start ($sep1, 0, 0, 0);
1790
7becc472 1791 $cmdbox = Gtk3::HBox->new ();
89a12446
DM
1792 $vbox->pack_start ($cmdbox, 0, 0, 10);
1793
7becc472 1794 $next = Gtk3::Button->new ('_Next');
550958aa 1795 $next->signal_connect (clicked => sub { $last_display_change = 0; &$next_fctn (); });
89a12446 1796 $cmdbox->pack_end ($next, 0, 0, 10);
7becc472
DM
1797 my $abort = Gtk3::Button->new ('_Abort');
1798 $abort->set_can_focus (0);
89a12446
DM
1799 $cmdbox->pack_start ($abort, 0, 0, 10);
1800 $abort->signal_connect (clicked => sub { exit (-1); });
1801
7becc472
DM
1802 my $vbox2 = Gtk3::VBox->new (0, 0);
1803 $hbox->add ($vbox2);
89a12446 1804
7becc472
DM
1805 $htmlview = Gtk3::WebKit::WebView->new();
1806 my $scrolls = Gtk3::ScrolledWindow->new();
1807 $scrolls->add($htmlview);
1464c7c9 1808
7becc472
DM
1809 my $hbox2 = Gtk3::HBox->new (0, 0);
1810 $hbox2->pack_start ($scrolls, 1, 1, 0);
89a12446
DM
1811
1812 $vbox2->pack_start ($hbox2, 1, 1, 0);
1813
7becc472 1814 my $vbox3 = Gtk3::VBox->new (0, 0);
89a12446
DM
1815 $vbox2->pack_start ($vbox3, 0, 0, 0);
1816
7becc472 1817 my $sep2 = Gtk3::HSeparator->new;
89a12446
DM
1818 $vbox3->pack_start ($sep2, 0, 0, 0);
1819
7becc472 1820 $inbox = Gtk3::HBox->new (0, 0);
89a12446
DM
1821 $vbox3->pack_start ($inbox, 0, 0, 0);
1822
1823 $window->add ($vbox);
1824
1825 $window->show_all;
1826 $window->realize ();
1827}
1828
1464c7c9 1829sub cleanup_view {
d2120e51
DM
1830 $inbox->foreach(sub {
1831 my $child = shift;
1464c7c9 1832 $inbox->remove ($child);
d2120e51 1833 });
89a12446
DM
1834}
1835
aed81ff0
DM
1836# fixme: newer GTK3 has special properties to handle numbers with Entry
1837# only allow floating point numbers with Gtk3::Entry
e73c5fcf 1838
aed81ff0
DM
1839sub check_float {
1840 my ($entry, $event) = @_;
1841
e73c5fcf
FG
1842 return check_number($entry, $event, 1);
1843}
1844
1845sub check_int {
1846 my ($entry, $event) = @_;
1847
1848 return check_number($entry, $event, 0);
1849}
1850
1851sub check_number {
1852 my ($entry, $event, $float) = @_;
aed81ff0
DM
1853
1854 my $val = $event->get_keyval;
1855
e73c5fcf 1856 if (($float && $val == ord '.') ||
aed81ff0
DM
1857 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
1858 $val == Gtk3::Gdk::KEY_Shift_L ||
1859 $val == Gtk3::Gdk::KEY_Tab ||
1860 $val == Gtk3::Gdk::KEY_Left ||
1861 $val == Gtk3::Gdk::KEY_Right ||
1862 $val == Gtk3::Gdk::KEY_BackSpace ||
1863 $val == Gtk3::Gdk::KEY_Delete ||
1864 ($val >= ord '0' && $val <= ord '9') ||
1865 ($val >= Gtk3::Gdk::KEY_KP_0 &&
1866 $val <= Gtk3::Gdk::KEY_KP_9)) {
1867 return undef;
1868 }
1869
1870 return 1;
1871}
1872
d2120e51 1873sub create_text_input {
89a12446
DM
1874 my ($default, $text) = @_;
1875
7becc472 1876 my $hbox = Gtk3::HBox->new (0, 0);
89a12446 1877
7becc472 1878 my $label = Gtk3::Label->new ($text);
89a12446
DM
1879 $label->set_size_request (150, -1);
1880 $label->set_alignment (1, 0.5);
1881 $hbox->pack_start ($label, 0, 0, 10);
7becc472 1882 my $e1 = Gtk3::Entry->new ();
89a12446
DM
1883 $e1->set_width_chars (30);
1884 $hbox->pack_start ($e1, 0, 0, 0);
1885 $e1->set_text ($default);
1886
1887 return ($hbox, $e1);
1888}
1889
89a12446
DM
1890sub get_ip_config {
1891
fe44bd92
FG
1892 my $ifaces = {};
1893 my $default;
89a12446 1894
fe44bd92
FG
1895 my $links = `ip -o l`;
1896 foreach my $l (split /\n/,$links) {
1897 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
1898 next if !$name || $name eq 'lo';
89a12446 1899
fe44bd92
FG
1900 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
1901 $driver =~ s!^.*/!!;
1902
1903 $ifaces->{"$index"} = {
1904 name => $name,
1905 driver => $driver,
1906 flags => $flags,
1907 state => $state,
1908 mac => $mac,
1909 };
1910
1911 my $addresses = `ip -o a s $name`;
1912 foreach my $a (split /\n/,$addresses) {
1913 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
1914 next if !$ip;
32b6fbcf 1915 next if $a =~ /scope\s+link/; # ignore link local
fe44bd92
FG
1916
1917 my $mask = $prefix;
1918
1919 if ($family eq 'inet') {
1920 next if !$ip =~ /$IPV4RE/;
1921 next if $prefix < 8 || $prefix > 32;
1922 $mask = @$ipv4_reverse_mask[$prefix];
1923 } else {
1924 next if !$ip =~ /$IPV6RE/;
1925 }
1926
1927 $default = $index if !$default;
1928
1929 $ifaces->{"$index"}->{"$family"} = {
1930 mask => $mask,
1931 addr => $ip,
1932 };
1933 }
1934 }
1935
1936
1937 my $route = `ip route`;
1938 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
89a12446
DM
1939
1940 my $resolvconf = `cat /etc/resolv.conf`;
1941 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
713790a4 1942 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
89a12446
DM
1943
1944 return {
fe44bd92
FG
1945 default => $default,
1946 ifaces => $ifaces,
89a12446
DM
1947 gateway => $gateway,
1948 dnsserver => $dnsserver,
713790a4 1949 domain => $domain,
89a12446
DM
1950 }
1951}
1952
1953sub display_message {
1954 my ($msg) = @_;
1955
7becc472 1956 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
89a12446
DM
1957 'info', 'ok', $msg);
1958 $dialog->run();
1959 $dialog->destroy();
1960}
1961
1962sub display_error {
1963 my ($msg) = @_;
1964
7becc472 1965 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
89a12446
DM
1966 'error', 'ok', $msg);
1967 $dialog->run();
1968 $dialog->destroy();
1969}
1970
fe44bd92
FG
1971my $ipconf_first_view = 1;
1972
89a12446
DM
1973sub create_ipconf_view {
1974
1975 cleanup_view ();
1976 display_html ("ipconf.htm");
1977
7becc472 1978 my $vbox = Gtk3::VBox->new (0, 0);
89a12446 1979 $inbox->pack_start ($vbox, 1, 0, 0);
7becc472 1980 my $hbox = Gtk3::HBox->new (0, 0);
53986d77 1981 $vbox->pack_start ($hbox, 0, 0, 10);
7becc472 1982 my $vbox2 = Gtk3::VBox->new (0, 0);
89a12446
DM
1983 $hbox->add ($vbox2);
1984
fe44bd92
FG
1985 my $ipbox;
1986 ($ipbox, $ipconf_entry_addr) =
1987 create_text_input ("192.168.100.2", 'IP Address:');
1988
1989 my $maskbox;
1990 ($maskbox, $ipconf_entry_mask) =
1991 create_text_input ("255.255.255.0", 'Netmask:');
1992
1993 my $device_cb = Gtk3::ComboBoxText->new();
1994 $device_cb->set_active(0);
1995 $device_cb->set_visible(1);
1996
1997 my $get_device_desc = sub {
1998 my $iface = shift;
1999 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
2000 };
2001
2002 my $device_active_map = {};
5b6ba737
FG
2003
2004 my $device_change_handler = sub {
2005 my $current = shift;
2006 $ipconf->{selected} = $device_active_map->{$current->get_active()};
2007 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
2008 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2009 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
2010 $ipconf_entry_mask->set_text($iface->{inet}->{mask} || $iface->{inet6}->{mask})
2011 if $iface->{inet}->{mask} || $iface->{inet6}->{mask};
2012 };
2013
fe44bd92
FG
2014 my $i = 0;
2015 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2016 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
2017 $device_active_map->{$i} = $index;
2018 if ($ipconf_first_view && $index == $ipconf->{default}) {
2019 $device_cb->set_active($i);
5b6ba737 2020 &$device_change_handler($device_cb);
fe44bd92
FG
2021 $ipconf_first_view = 0;
2022 }
5b6ba737 2023 $device_cb->signal_connect ('changed' => $device_change_handler);
fe44bd92
FG
2024 $i++;
2025 }
2026
5b6ba737
FG
2027 $device_cb->set_active(0)
2028 if !($ipconf->{selected});
2029
fe44bd92
FG
2030 my $devicebox = Gtk3::HBox->new (0, 0);
2031 my $label = Gtk3::Label->new ("Management Interface:");
2032 $label->set_size_request (150, -1);
2033 $label->set_alignment (1, 0.5);
2034 $devicebox->pack_start ($label, 0, 0, 10);
2035 $devicebox->pack_start ($device_cb, 0, 0, 0);
2036
2037 $vbox2->pack_start ($devicebox, 0, 0, 2);
968fa90b 2038
232309b6
DM
2039 my $hn = $ipconf->{domain} ?
2040 "$setup->{product}.$ipconf->{domain}" : "$setup->{product}.example.invalid";
1464c7c9 2041
968fa90b 2042 my ($hostbox, $hostentry) =
d2120e51 2043 create_text_input ($hn, 'Hostname (FQDN):');
89a12446
DM
2044 $vbox2->pack_start ($hostbox, 0, 0, 2);
2045
89a12446
DM
2046 $vbox2->pack_start ($ipbox, 0, 0, 2);
2047
89a12446
DM
2048 $vbox2->pack_start ($maskbox, 0, 0, 2);
2049
2050 $gateway = $ipconf->{gateway} || '192.168.100.1';
2051
2052 my $gwbox;
d2120e51
DM
2053 ($gwbox, $ipconf_entry_gw) =
2054 create_text_input ($gateway, 'Gateway:');
89a12446 2055
53986d77 2056 $vbox2->pack_start ($gwbox, 0, 0, 2);
89a12446
DM
2057
2058 $dnsserver = $ipconf->{dnsserver} || $gateway;
2059
2060 my $dnsbox;
d2120e51
DM
2061 ($dnsbox, $ipconf_entry_dns) =
2062 create_text_input ($dnsserver, 'DNS Server:');
89a12446
DM
2063
2064 $vbox2->pack_start ($dnsbox, 0, 0, 0);
2065
2066 $inbox->show_all;
968fa90b 2067 set_next (undef, sub {
d2120e51
DM
2068
2069 # verify hostname
1464c7c9 2070
89a12446 2071 my $text = $hostentry->get_text();
968fa90b 2072
89a12446
DM
2073 $text =~ s/^\s+//;
2074 $text =~ s/\s+$//;
2075
ac3757a9 2076 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2077
24973868
WB
2078 # Debian does not support purely numeric hostnames
2079 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2080 display_message("Purely numeric hostnames are not allowed.");
2081 $hostentry->grab_focus();
2082 return;
2083 }
2084
a39bc1f2 2085 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2086 $text =~ m/^([^\.]+)\.(\S+)$/) {
2087 $hostname = $1;
2088 $domain = $2;
d2120e51
DM
2089 } else {
2090 display_message ("Hostname does not look like a fully qualified domain name.");
2091 $hostentry->grab_focus();
89a12446
DM
2092 return;
2093 }
d2120e51
DM
2094
2095 # verify ip address
2096
2097 $text = $ipconf_entry_addr->get_text();
2098 $text =~ s/^\s+//;
2099 $text =~ s/\s+$//;
2100 if ($text =~ m!^($IPV4RE)$!) {
2101 $ipaddress = $text;
b6200603
DM
2102 $ipversion = 4;
2103 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2104 $ipaddress = $text;
b6200603 2105 $ipversion = 6;
d2120e51
DM
2106 } else {
2107 display_message ("IP address is not valid.");
2108 $ipconf_entry_addr->grab_focus();
2109 return;
2110 }
2111
2112 $text = $ipconf_entry_mask->get_text();
2113 $text =~ s/^\s+//;
2114 $text =~ s/\s+$//;
b6200603
DM
2115 if (($ipversion == 6) && ($text =~ m/^(\d+)$/) && ($1 >= 8) && ($1 <= 126)) {
2116 $netmask = $text;
2117 } elsif (($ipversion == 4) && defined($ipv4_mask_hash->{$text})) {
d2120e51
DM
2118 $netmask = $text;
2119 } else {
2120 display_message ("Netmask is not valid.");
2121 $ipconf_entry_mask->grab_focus();
2122 return;
2123 }
2124
2125 $text = $ipconf_entry_gw->get_text();
2126 $text =~ s/^\s+//;
2127 $text =~ s/\s+$//;
b6200603
DM
2128 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2129 $gateway = $text;
2130 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2131 $gateway = $text;
2132 } else {
2133 display_message ("Gateway is not valid.");
2134 $ipconf_entry_gw->grab_focus();
2135 return;
2136 }
1464c7c9 2137
d2120e51
DM
2138 $text = $ipconf_entry_dns->get_text();
2139 $text =~ s/^\s+//;
2140 $text =~ s/\s+$//;
b6200603
DM
2141 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2142 $dnsserver = $text;
2143 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2144 $dnsserver = $text;
2145 } else {
2146 display_message ("DNS server is not valid.");
2147 $ipconf_entry_dns->grab_focus();
2148 return;
2149 }
1464c7c9 2150
d2120e51 2151 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2152
d2120e51 2153 create_extract_view ();
89a12446
DM
2154 });
2155
2156 $hostentry->grab_focus();
2157}
2158
2159sub get_device_desc {
2160 my ($devname, $size, $model) = @_;
2161
d2120e51 2162 if ($size && ($size > 0)) {
1bd457bb 2163 $size = int($size/2048); # size in MB, from 512B "sectors"
89a12446 2164
d2120e51 2165 my $text = "$devname (";
89a12446
DM
2166 if ($size >= 1024) {
2167 $size = int($size/1024); # size in GB
d2120e51 2168 $text .= "${size}GB";
89a12446 2169 } else {
d2120e51 2170 $text .= "${size}MB";
89a12446
DM
2171 }
2172
d2120e51
DM
2173 $text .= ", $model" if $model;
2174 $text .= ")";
2175
89a12446
DM
2176 } else {
2177 return $devname;
2178 }
2179}
2180
2181sub update_layout {
2182 my ($cb, $kmap) = @_;
2183
2184 my $ind;
2185 my $def;
2186 my $i = 0;
2187 my $kmaphash = $cmap->{kmaphash};
2188 foreach my $layout (sort keys %$kmaphash) {
2189 $def = $i if $kmaphash->{$layout} eq 'en-us';
2190 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2191 $i++;
2192 }
2193
2194 $cb->set_active ($ind || $def || 0);
2195}
2196
2197my $lastzonecb;
2198sub update_zonelist {
2199 my ($box, $cc) = @_;
2200
2201 my $cczones = $cmap->{cczones};
2202 my $zones = $cmap->{zones};
2203
2204 my $sel;
2205 if ($lastzonecb) {
2206 $sel = $lastzonecb->get_active_text();
2207 $box->remove ($lastzonecb);
2208 } else {
2209 $sel = $timezone; # used once to select default
2210 }
2211
bcbfab6b 2212 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
89a12446
DM
2213 $cb->set_size_request (200, -1);
2214
2215 $cb->signal_connect ('changed' => sub {
2216 $timezone = $cb->get_active_text();
2217 });
2218
2219 my @za;
2220 if ($cc && defined ($cczones->{$cc})) {
2221 @za = keys %{$cczones->{$cc}};
2222 } else {
2223 @za = keys %$zones;
2224 }
2225 my $ind;
2226 my $i = 0;
2227 foreach my $zone (sort @za) {
2228 $ind = $i if $sel && $zone eq $sel;
2229 $cb->append_text ($zone);
2230 $i++;
2231 }
2232
2233 $cb->set_active ($ind || 0);
2234
2235 $cb->show;
2236 $box->pack_start ($cb, 0, 0, 0);
2237}
2238
2239sub create_password_view {
2240
2241 cleanup_view ();
2242
7becc472 2243 my $vbox2 = Gtk3::VBox->new (0, 0);
89a12446 2244 $inbox->pack_start ($vbox2, 1, 0, 0);
7becc472 2245 my $vbox = Gtk3::VBox->new (0, 0);
53986d77 2246 $vbox2->pack_start ($vbox, 0, 0, 10);
89a12446 2247
7becc472
DM
2248 my $hbox1 = Gtk3::HBox->new (0, 0);
2249 my $label = Gtk3::Label->new ("Password");
89a12446
DM
2250 $label->set_size_request (150, -1);
2251 $label->set_alignment (1, 0.5);
2252 $hbox1->pack_start ($label, 0, 0, 10);
7becc472 2253 my $pwe1 = Gtk3::Entry->new ();
89a12446
DM
2254 $pwe1->set_visibility (0);
2255 $pwe1->set_size_request (200, -1);
2256 $hbox1->pack_start ($pwe1, 0, 0, 0);
2257
7becc472
DM
2258 my $hbox2 = Gtk3::HBox->new (0, 0);
2259 $label = Gtk3::Label->new ("Confirm");
89a12446
DM
2260 $label->set_size_request (150, -1);
2261 $label->set_alignment (1, 0.5);
2262 $hbox2->pack_start ($label, 0, 0, 10);
7becc472 2263 my $pwe2 = Gtk3::Entry->new ();
89a12446
DM
2264 $pwe2->set_visibility (0);
2265 $pwe2->set_size_request (200, -1);
2266 $hbox2->pack_start ($pwe2, 0, 0, 0);
2267
7becc472
DM
2268 my $hbox3 = Gtk3::HBox->new (0, 0);
2269 $label = Gtk3::Label->new ("E-Mail");
89a12446
DM
2270 $label->set_size_request (150, -1);
2271 $label->set_alignment (1, 0.5);
2272 $hbox3->pack_start ($label, 0, 0, 10);
7becc472 2273 my $eme = Gtk3::Entry->new ();
89a12446 2274 $eme->set_size_request (200, -1);
a39bc1f2 2275 $eme->set_text('mail@example.invalid');
89a12446
DM
2276 $hbox3->pack_start ($eme, 0, 0, 0);
2277
2278
2279 $vbox->pack_start ($hbox1, 0, 0, 5);
2280 $vbox->pack_start ($hbox2, 0, 0, 5);
2281 $vbox->pack_start ($hbox3, 0, 0, 15);
2282
2283 $inbox->show_all;
2284
2285 display_html ("passwd.htm");
2286
2287 set_next (undef, sub {
2288
2289 my $t1 = $pwe1->get_text;
2290 my $t2 = $pwe2->get_text;
2291
2292 if (length ($t1) < 5) {
2293 display_message ("Password is too short.");
2294 $pwe1->grab_focus();
2295 return;
2296 }
2297
2298 if ($t1 ne $t2) {
2299 display_message ("Password does not match.");
2300 $pwe1->grab_focus();
2301 return;
2302 }
2303
2304 my $t3 = $eme->get_text;
2305 if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
034f75e4 2306 display_message ("E-Mail does not look like a valid address" .
89a12446
DM
2307 " (user\@domain.tld)");
2308 $eme->grab_focus();
2309 return;
a39bc1f2 2310 }
89a12446 2311
a39bc1f2
FG
2312 if ($t3 eq 'mail@example.invalid') {
2313 display_message ("Please enter a valid E-Mail address");
2314 $eme->grab_focus();
2315 return;
89a12446
DM
2316 }
2317
2318 $password = $t1;
2319 $mailto = $t3;
2320
2321 create_ipconf_view();
2322 });
2323
2324 $pwe1->grab_focus();
2325
2326}
2327
2328sub create_country_view {
2329
2330 cleanup_view ();
2331
2332 my $countryhash = $cmap->{countryhash};
2333 my $ctr = $cmap->{country};
2334
7becc472 2335 my $vbox2 = Gtk3::VBox->new (0, 0);
89a12446 2336 $inbox->pack_start ($vbox2, 1, 0, 0);
7becc472 2337 my $vbox = Gtk3::VBox->new (0, 0);
53986d77 2338 $vbox2->pack_start ($vbox, 0, 0, 10);
89a12446 2339
7becc472 2340 my $w = Gtk3::Entry->new ();
89a12446
DM
2341 $w->set_size_request (200, -1);
2342
7becc472 2343 my $c = Gtk3::EntryCompletion->new ();
89a12446
DM
2344 $c->set_text_column (0);
2345 $c->set_minimum_key_length(0);
2346 $c->set_popup_set_width (1);
4443aa27 2347 $c->set_inline_completion (1);
89a12446 2348
7becc472
DM
2349 my $hbox2 = Gtk3::HBox->new (0, 0);
2350 my $label = Gtk3::Label->new ("Time zone");
89a12446
DM
2351 $label->set_size_request (150, -1);
2352 $label->set_alignment (1, 0.5);
2353 $hbox2->pack_start ($label, 0, 0, 10);
2354 update_zonelist ($hbox2);
2355
7becc472
DM
2356 my $hbox3 = Gtk3::HBox->new (0, 0);
2357 $label = Gtk3::Label->new ("Keyboard Layout");
89a12446
DM
2358 $label->set_size_request (150, -1);
2359 $label->set_alignment (1, 0.5);
2360 $hbox3->pack_start ($label, 0, 0, 10);
2361
bcbfab6b 2362 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2363 $kmapcb->set_size_request (200, -1);
2364 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2365 $kmapcb->append_text ($layout);
2366 }
2367
2368 update_layout ($kmapcb);
2369 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2370
2371 $kmapcb->signal_connect ('changed' => sub {
2372 my $sel = $kmapcb->get_active_text();
2373 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2374 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2375 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
2376 syscmd ("setxkbmap $xkmap $xvar") if !$opt_testmode;
2377 $keymap = $kmap;
2378 }
2379 });
2380
2381 $w->signal_connect ('changed' => sub {
2382 my ($entry, $event) = @_;
2383 my $text = $entry->get_text;
2384
2385 if (my $cc = $countryhash->{lc($text)}) {
2386 update_zonelist ($hbox2, $cc);
2387 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
2388 update_layout ($kmapcb, $kmap);
2389 }
2390 });
2391
2392 $w->signal_connect (key_press_event => sub {
2393 my ($entry, $event) = @_;
2394 my $text = $entry->get_text;
2395
7becc472
DM
2396 my $val = $event->get_keyval;
2397
2398 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2399 my $cc = $countryhash->{lc($text)};
1464c7c9 2400
89a12446
DM
2401 my $found = 0;
2402 my $compl;
7becc472 2403
4443aa27
DM
2404 if ($cc) {
2405 $found = 1;
2406 $compl = $ctr->{$cc}->{name};
2407 } else {
2408 foreach my $cc (keys %$ctr) {
2409 my $ct = $ctr->{$cc}->{name};
2410 if ($ct =~ m/^\Q$text\E.*$/i) {
2411 $found++;
2412 $compl = $ct;
2413 }
2414 last if $found > 1;
89a12446 2415 }
89a12446 2416 }
4443aa27 2417
89a12446 2418 if ($found == 1) {
7becc472 2419 $entry->set_text($compl);
3df718ea 2420 $c->complete();
89a12446
DM
2421 return undef;
2422 } else {
7becc472
DM
2423 #Gtk3::Gdk::beep();
2424 print chr(7); # beep ?
89a12446
DM
2425 }
2426
3df718ea
DM
2427 $c->complete();
2428
7becc472
DM
2429 my $buf = $w->get_buffer();
2430 $buf->insert_text(-1, '', -1); # popup selection
2431
89a12446
DM
2432 return 1;
2433 }
2434
2435 return undef;
2436 });
1464c7c9 2437
7becc472 2438 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2439 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2440 my $iter = $ls->append();
2441 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2442 }
2443 $c->set_model ($ls);
2444
968fa90b 2445 $w->set_completion ($c);
89a12446 2446
7becc472 2447 my $hbox = Gtk3::HBox->new (0, 0);
89a12446 2448
7becc472 2449 $label = Gtk3::Label->new ("Country");
89a12446
DM
2450 $label->set_alignment (1, 0.5);
2451 $label->set_size_request (150, -1);
2452 $hbox->pack_start ($label, 0, 0, 10);
2453 $hbox->pack_start ($w, 0, 0, 0);
2454
2455 $vbox->pack_start ($hbox, 0, 0, 5);
2456 $vbox->pack_start ($hbox2, 0, 0, 5);
2457 $vbox->pack_start ($hbox3, 0, 0, 5);
2458
9d1f1ee3 2459 if ($country && $ctr->{$country}) {
89a12446
DM
2460 $w->set_text ($ctr->{$country}->{name});
2461 }
2462
2463 $inbox->show_all;
2464
2465 display_html ("country.htm");
2466 set_next (undef, sub {
2467
2468 my $text = $w->get_text;
2469
2470 if (my $cc = $countryhash->{lc($text)}) {
2471 $country = $cc;
2472 create_password_view();
2473 return;
2474 } else {
2475 display_message ("Please select a country first.");
2476 $w->grab_focus();
2477 }
2478 });
2479
2480 $w->grab_focus();
2481}
2482
c6ed3b24
DM
2483my $target_hd_combo;
2484my $target_hd_label;
2485
2486my $hdopion_first_setup = 1;
2487
c7779156
FG
2488my $create_basic_grid = sub {
2489 my $grid = Gtk3::Grid->new();
2490 $grid->set_visible(1);
2491 $grid->set_column_spacing(10);
2492 $grid->set_row_spacing(10);
2493 $grid->set_hexpand(1);
2494
2495 $grid->set_margin_start(5);
2496 $grid->set_margin_end(5);
2497 $grid->set_margin_top(5);
2498 $grid->set_margin_bottom(5);
2499
2500 return $grid;
2501};
2502
2503my $create_label_widget_grid = sub {
2504 my ($labeled_widgets) = @_;
2505
2506 my $grid = &$create_basic_grid();
2507 my $row = 0;
2508
2509 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2510 my $widget = @$labeled_widgets[$i+1];
2511 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2512 $label->set_visible(1);
2513 $label->set_alignment (1, 0.5);
2514 $grid->attach($label, 0, $row, 1, 1);
2515 $widget->set_visible(1);
2516 $grid->attach($widget, 1, $row, 1, 1);
2517 $row++;
2518 }
2519
2520 return $grid;
2521};
2522
2523my $create_raid_disk_grid = sub {
2524 my $disk_labeled_widgets = [];
2525 for (my $i = 0; $i < @$hds; $i++) {
2526 my $disk_selector = Gtk3::ComboBoxText->new();
2527 $disk_selector->append_text("-- do not use --");
2528 $disk_selector->set_active(0);
2529 $disk_selector->set_visible(1);
2530 foreach my $hd (@$hds) {
2531 my ($disk, $devname, $size, $model) = @$hd;
2532 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2533 $disk_selector->{pve_disk_id} = $i;
2534 $disk_selector->signal_connect (changed => sub {
2535 my $w = shift;
2536 my $diskid = $w->{pve_disk_id};
2537 my $a = $w->get_active - 1;
2538 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2539 });
2540 }
2541
2542 if ($hdopion_first_setup) {
2543 $disk_selector->set_active ($i+1) if $hds->[$i];
2544 } else {
2545 my $hdind = 0;
2546 if (my $cur_hd = $config_options->{"disksel$i"}) {
2547 foreach my $hd (@$hds) {
2548 if (@$hd[1] eq @$cur_hd[1]) {
2549 $disk_selector->set_active($hdind+1);
2550 last;
2551 }
2552 $hdind++;
2553 }
2554 }
2555 }
2556
2557 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2558 }
2559
2560 my $scrolled_window = Gtk3::ScrolledWindow->new();
2561 $scrolled_window->set_hexpand(1);
650a9aab 2562 $scrolled_window->set_propagate_natural_height(1) if @$hds > 4;
c7779156
FG
2563 $scrolled_window->add(&$create_label_widget_grid($disk_labeled_widgets));
2564 $scrolled_window->set_policy('never', 'automatic');
2565
2566 return $scrolled_window;
2567# &$create_label_widget_grid($disk_labeled_widgets)
2568};
2569
2570my $create_raid_advanced_grid = sub {
2571 my $labeled_widgets = [];
6c99667a
FG
2572 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2573 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2574 $spinbutton_ashift->signal_connect ("value-changed" => sub {
2575 my $w = shift;
2576 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
2577 });
2578 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 2579 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 2580 push @$labeled_widgets, "ashift";
6c99667a 2581 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
2582
2583 my $combo_compress = Gtk3::ComboBoxText->new();
2584 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
2585 # note: gzip / lze not allowed for bootfs vdevs
2586 my $comp_opts = ["on","off","lzjb","lz4"];
2587 foreach my $opt (@$comp_opts) {
2588 $combo_compress->append($opt, $opt);
2589 }
2590 $config_options->{compress} = "on" if !defined($config_options->{compress});
2591 $combo_compress->set_active_id($config_options->{compress});
2592 $combo_compress->signal_connect (changed => sub {
2593 my $w = shift;
2594 $config_options->{compress} = $w->get_active_text();
2595 });
2596 push @$labeled_widgets, "compress";
2597 push @$labeled_widgets, $combo_compress;
2598
2599 my $combo_checksum = Gtk3::ComboBoxText->new();
2600 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2601 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2602 foreach my $opt (@$csum_opts) {
2603 $combo_checksum->append($opt, $opt);
2604 }
2605 $config_options->{checksum} = "on" if !($config_options->{checksum});
2606 $combo_checksum->set_active_id($config_options->{checksum});
2607 $combo_checksum->signal_connect (changed => sub {
2608 my $w = shift;
2609 $config_options->{checksum} = $w->get_active_text();
2610 });
2611 push @$labeled_widgets, "checksum";
2612 push @$labeled_widgets, $combo_checksum;
2613
2614 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2615 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2616 $spinbutton_copies->signal_connect ("value-changed" => sub {
2617 my $w = shift;
2618 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
2619 });
2620 $config_options->{copies} = 1 if !defined($config_options->{copies});
2621 $spinbutton_copies->set_value($config_options->{copies});
2622 push @$labeled_widgets, "copies", $spinbutton_copies;
2623
2624 return &$create_label_widget_grid($labeled_widgets);;
2625};
2626
aed81ff0
DM
2627sub create_hdoption_view {
2628
2629 my $dialog = Gtk3::Dialog->new();
2630
2631 $dialog->set_title("Harddisk options");
2632
2633 $dialog->add_button("_OK", 1);
2634
2635 my $contarea = $dialog->get_content_area();
2636
2637 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2638 $contarea->pack_start($hbox2, 1, 1, 10);
2639
2640 my $grid = Gtk3::Grid->new();
2641 $grid->set_column_spacing(10);
2642 $grid->set_row_spacing(10);
1464c7c9 2643
aed81ff0 2644 $hbox2->pack_start($grid, 1, 0, 10);
c6ed3b24
DM
2645
2646 my $row = 0;
2647
aed81ff0
DM
2648 # Filesystem type
2649
2650 my $label0 = Gtk3::Label->new ("Filesystem");
2651 $label0->set_alignment (1, 0.5);
c6ed3b24 2652 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 2653
bcbfab6b 2654 my $fstypecb = Gtk3::ComboBoxText->new();
aed81ff0 2655
121ebc59
DM
2656 my $fstype = ['ext3', 'ext4', 'xfs',
2657 'zfs (RAID0)', 'zfs (RAID1)',
2658 'zfs (RAID10)', 'zfs (RAIDZ-1)',
6f52fc3d
DM
2659 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2660
2661 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 2662 if $setup->{enable_btrfs};
aed81ff0 2663
c6ed3b24
DM
2664 my $tcount = 0;
2665 foreach my $tmp (@$fstype) {
2666 $fstypecb->append_text($tmp);
2667 $fstypecb->set_active ($tcount)
2668 if $config_options->{filesys} eq $tmp;
2669 $tcount++;
2670 }
2671
2672 $grid->attach($fstypecb, 1, $row, 1, 1);
2673
2674 $hbox2->show_all();
2675
2676 $row++;
2677
c7779156
FG
2678 my $sep = Gtk3::HSeparator->new();
2679 $sep->set_visible(1);
2680 $grid->attach($sep, 0, $row, 2, 1);
2681 $row++;
aed81ff0 2682
c7779156 2683 my $hdsize_labeled_widgets = [];
aed81ff0 2684
c7779156 2685 # size compute
c6ed3b24 2686 my $hdsize = 0;
aed81ff0
DM
2687 if ( -b $target_hd) {
2688 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
c6ed3b24 2689 } elsif ($target_hd) {
aed81ff0
DM
2690 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
2691 }
2692
2693 my $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
2694 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2695 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
c7779156 2696 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
aed81ff0
DM
2697
2698 my $entry_swapsize = Gtk3::Entry->new();
2699 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
2700 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 2701 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 2702 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
2703
2704 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
2705 if ($setup->{product} eq 'pve') {
2706 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
2707 $entry_maxroot->signal_connect (key_press_event => \&check_float);
2708 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
2709 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
2710 }
aed81ff0
DM
2711
2712 my $entry_minfree = Gtk3::Entry->new();
034f75e4 2713 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0
DM
2714 $entry_minfree->signal_connect (key_press_event => \&check_float);
2715 $entry_minfree->set_text($config_options->{minfree}) if $config_options->{minfree};
c7779156 2716 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 2717
b6e875ca
DM
2718 my $entry_maxvz;
2719 if ($setup->{product} eq 'pve') {
2720 $entry_maxvz = Gtk3::Entry->new();
2721 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
2722 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 2723 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
2724 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
2725 }
c7779156
FG
2726
2727 my $options_stack = Gtk3::Stack->new();
2728 $options_stack->set_visible(1);
2729 $options_stack->set_hexpand(1);
2730 $options_stack->set_vexpand(1);
2731 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
2732 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
2733 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
2734 $options_stack->set_visible_child_name("raiddisk");
2735 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2736 $options_stack_switcher->set_halign('center');
2737 $options_stack_switcher->set_stack($options_stack);
2738 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
2739 $row++;
2740 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 2741 $row++;
aed81ff0 2742
c7779156
FG
2743 $hdopion_first_setup = 0;
2744
2745 my $switch_view = sub {
2746 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
2747 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 2748
c7779156
FG
2749 $target_hd_combo->set_visible(!$raid);
2750 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
2751 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
2752 $options_stack_switcher->set_visible($enable_zfs_opts);
2753 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
2754 if ($raid) {
c6ed3b24 2755 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 2756 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 2757 } else {
c6ed3b24
DM
2758 $target_hd_label->set_text("Target Harddisk: ");
2759 }
c7779156
FG
2760 my (undef, $pref_width) = $dialog->get_preferred_width();
2761 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 2762 $pref_height = 750 if $pref_height > 750;
c7779156 2763 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
2764 };
2765
c7779156 2766 &$switch_view();
f7b853d1
DM
2767
2768 $fstypecb->signal_connect (changed => sub {
2769 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 2770 &$switch_view();
f7b853d1
DM
2771 });
2772
c6ed3b24 2773 $dialog->show();
aed81ff0
DM
2774
2775 $dialog->run();
2776
2777 my $get_float = sub {
2778 my ($entry) = @_;
2779
2780 my $text = $entry->get_text();
2781 return undef if !defined($text);
2782
2783 $text =~ s/^\s+//;
2784 $text =~ s/\s+$//;
2785
2786 return undef if $text !~ m/^\d+(\.\d+)?$/;
2787
2788 return $text;
2789 };
2790
2791 my $tmp;
2792
2793 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
2794 $config_options->{hdsize} = $tmp;
2795 } else {
2796 delete $config_options->{hdsize};
2797 }
2798
2799 if (defined($tmp = &$get_float($entry_swapsize))) {
2800 $config_options->{swapsize} = $tmp;
2801 } else {
2802 delete $config_options->{swapsize};
2803 }
2804
2805 if (defined($tmp = &$get_float($entry_maxroot))) {
2806 $config_options->{maxroot} = $tmp;
2807 } else {
2808 delete $config_options->{maxroot};
2809 }
2810
2811 if (defined($tmp = &$get_float($entry_minfree))) {
2812 $config_options->{minfree} = $tmp;
2813 } else {
2814 delete $config_options->{minfree};
2815 }
2816
b6e875ca 2817 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
2818 $config_options->{maxvz} = $tmp;
2819 } else {
2820 delete $config_options->{maxvz};
2821 }
2822
2823 $dialog->destroy();
2824}
2825
121ebc59 2826my $get_raid_devlist = sub {
c6ed3b24
DM
2827
2828 my $dev_name_hash = {};
2829
2830 my $devlist = [];
5f8e86d5 2831 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24
DM
2832 if (my $hd = $config_options->{"disksel$i"}) {
2833 my ($disk, $devname, $size, $model) = @$hd;
1464c7c9 2834 die "device '$devname' is used more than once\n"
c6ed3b24
DM
2835 if $dev_name_hash->{$devname};
2836 $dev_name_hash->{$devname} = $hd;
2837 push @$devlist, $hd;
2838 }
2839 }
2840
121ebc59
DM
2841 return $devlist;
2842};
2843
14aacec8
FG
2844sub zfs_mirror_size_check {
2845 my ($expected, $actual) = @_;
2846
2847 die "mirrored disks must have same size\n"
2848 if abs($expected - $actual) > $expected / 10;
2849}
2850
121ebc59
DM
2851sub get_zfs_raid_setup {
2852
2853 my $filesys = $config_options->{filesys};
2854
2855 my $devlist = &$get_raid_devlist();
2856
224bb7b0 2857 my $diskcount = scalar(@$devlist);
0cfa502c 2858 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24 2859
121ebc59
DM
2860 my $bootdevlist = [];
2861
c6ed3b24
DM
2862 my $cmd= '';
2863 if ($filesys eq 'zfs (RAID0)') {
2864 push @$bootdevlist, @$devlist[0];
2865 foreach my $hd (@$devlist) {
2866 $cmd .= " @$hd[1]";
2867 }
2868 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 2869 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 2870 $cmd .= ' mirror ';
269c66a6 2871 my $hd = @$devlist[0];
14aacec8 2872 my $expected_size = @$hd[2]; # all disks need approximately same size
269c66a6 2873 foreach $hd (@$devlist) {
14aacec8 2874 zfs_mirror_size_check($expected_size, @$hd[2]);
c6ed3b24
DM
2875 $cmd .= " @$hd[1]";
2876 push @$bootdevlist, $hd;
2877 }
2878 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 2879 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 2880 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 2881
c6ed3b24
DM
2882 push @$bootdevlist, @$devlist[0], @$devlist[1];
2883
224bb7b0 2884 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
2885 my $hd1 = @$devlist[$i];
2886 my $hd2 = @$devlist[$i+1];
14aacec8 2887 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
c6ed3b24
DM
2888 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
2889 }
2890
2891 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
2892 my $level = $1;
2893 my $mindisks = 2 + $level;
0cfa502c 2894 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 2895 my $hd = @$devlist[0];
14aacec8 2896 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 2897 $cmd .= " raidz$level";
269c66a6 2898 foreach $hd (@$devlist) {
14aacec8 2899 zfs_mirror_size_check($expected_size, @$hd[2]);
c6ed3b24
DM
2900 $cmd .= " @$hd[1]";
2901 push @$bootdevlist, $hd;
2902 }
2903 } else {
2904 die "unknown zfs mode '$filesys'\n";
2905 }
2906
2907 return ($devlist, $bootdevlist, $cmd);
2908}
2909
121ebc59
DM
2910sub get_btrfs_raid_setup {
2911
2912 my $filesys = $config_options->{filesys};
2913
2914 my $devlist = &$get_raid_devlist();
2915
2916 my $diskcount = scalar(@$devlist);
0cfa502c 2917 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
2918
2919 my $mode;
2920
2921 if ($diskcount == 1) {
2922 $mode = 'single';
2923 } else {
2924 if ($filesys eq 'btrfs (RAID0)') {
2925 $mode = 'raid0';
2926 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 2927 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
2928 $mode = 'raid1';
2929 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 2930 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
2931 $mode = 'raid10';
2932 } else {
9d69f3d3 2933 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
2934 }
2935 }
2936
2937 return ($devlist, $mode);
2938}
2939
89a12446
DM
2940sub create_hdsel_view {
2941
2942 cleanup_view ();
2943
7becc472 2944 my $vbox = Gtk3::VBox->new (0, 0);
89a12446 2945 $inbox->pack_start ($vbox, 1, 0, 0);
7becc472 2946 my $hbox = Gtk3::HBox->new (0, 0);
53986d77 2947 $vbox->pack_start ($hbox, 0, 0, 10);
968fa90b 2948
89a12446
DM
2949 my ($disk, $devname, $size, $model) = @{@$hds[0]};
2950 $target_hd = $devname;
89a12446 2951
c6ed3b24
DM
2952 $target_hd_label = Gtk3::Label->new ("Target Harddisk: ");
2953 $hbox->pack_start ($target_hd_label, 0, 0, 0);
89a12446 2954
bcbfab6b 2955 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 2956
1aa5bd02
DM
2957 foreach my $hd (@$hds) {
2958 ($disk, $devname, $size, $model) = @$hd;
c6ed3b24 2959 $target_hd_combo->append_text (get_device_desc ($devname, $size, $model));
1aa5bd02 2960 }
89a12446 2961
c6ed3b24
DM
2962 $target_hd_combo->set_active (0);
2963 $target_hd_combo->signal_connect (changed => sub {
1aa5bd02
DM
2964 $a = shift->get_active;
2965 my ($disk, $devname) = @{@$hds[$a]};
2966 $target_hd = $devname;
1aa5bd02 2967 });
1464c7c9 2968
c6ed3b24 2969 $hbox->pack_start ($target_hd_combo, 0, 0, 10);
aed81ff0
DM
2970
2971 my $options = Gtk3::Button->new ('_Options');
2972 $options->signal_connect (clicked => \&create_hdoption_view);
2973 $hbox->pack_start ($options, 0, 0, 0);
2974
89a12446
DM
2975
2976 $inbox->show_all;
2977
2978 display_html ("page1.htm");
c6ed3b24
DM
2979
2980 set_next (undef, sub {
2981
2982 if ($config_options->{filesys} =~ m/zfs/) {
2983 eval { get_zfs_raid_setup(); };
2984 if (my $err = $@) {
2985 display_message ("Warning: $err\n" .
269c66a6 2986 "Please fix ZFS setup first.");
c6ed3b24
DM
2987 } else {
2988 create_country_view();
2989 }
121ebc59
DM
2990 } elsif ($config_options->{filesys} =~ m/btrfs/) {
2991 eval { get_btrfs_raid_setup(); };
2992 if (my $err = $@) {
2993 display_message ("Warning: $err\n" .
2994 "Please fix BTRFS setup first.");
2995 } else {
2996 create_country_view();
2997 }
c6ed3b24
DM
2998 } else {
2999 create_country_view();
3000 }
3001 });
89a12446
DM
3002}
3003
3004sub create_extract_view {
3005
89a12446
DM
3006 cleanup_view ();
3007
550958aa
DM
3008 display_info();
3009
89a12446
DM
3010 $next->set_sensitive (0);
3011
7becc472 3012 my $vbox = Gtk3::VBox->new (0, 0);
89a12446 3013 $inbox->pack_start ($vbox, 1, 0, 0);
7becc472 3014 my $hbox = Gtk3::HBox->new (0, 0);
53986d77 3015 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3016
7becc472 3017 my $vbox2 = Gtk3::VBox->new (0, 0);
89a12446
DM
3018 $hbox->pack_start ($vbox2, 0, 0, 0);
3019
7becc472 3020 $progress_status = Gtk3::Label->new ('');
89a12446 3021 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3022
7becc472 3023 $progress = Gtk3::ProgressBar->new;
45feca6f 3024 $progress->set_show_text(1);
7becc472 3025 $progress->set_size_request (600, -1);
89a12446
DM
3026
3027 $vbox2->pack_start ($progress, 0, 0, 0);
3028
3029 $inbox->show_all;
3030
3031 my $tdir = $opt_testmode ? "target" : "/target";
3032 mkdir $tdir;
97980bf2 3033 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446
DM
3034
3035 eval { extract_data ($base, $tdir); };
3036 my $err = $@;
3037
3038 $next->set_sensitive (1);
3039
3040 set_next ("_Reboot", sub { exit (0); } );
3041
296cf41f
DM
3042 if ($err) {
3043 display_html ("fail.htm");
3044 display_error ($err);
3045 } else {
3046 cleanup_view ();
3047 display_html ("success.htm");
3048 }
89a12446
DM
3049}
3050
89a12446
DM
3051sub create_intro_view {
3052
3053 cleanup_view ();
3054
bdeca872
DM
3055 if ($setup->{product} eq 'pve') {
3056 eval {
3057 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3058 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
3059 display_error("No support for KVM virtualisation detected.\n\n" .
3060 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3061 }
3062 };
3063 }
7fff0d85 3064
89a12446
DM
3065 display_html ("license.htm");
3066
3067 set_next ("I a_gree", \&create_hdsel_view);
3068}
3069
3070$ipconf = get_ip_config ();
3071
9d1f1ee3 3072$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3073
3074# read country, kmap and timezone infos
3075$cmap = read_cmap ();
3076
9d1f1ee3
FG
3077if (!defined($cmap->{country}->{$country})) {
3078 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3079 $country = undef;
3080}
3081
89a12446
DM
3082create_main_window ();
3083
ff2ce71c
FG
3084my $initial_error = 0;
3085
89a12446
DM
3086if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3087 print "no hardisks found\n";
ff2ce71c 3088 $initial_error = 1;
89a12446
DM
3089 display_html ("nohds.htm");
3090 set_next ("Reboot", sub { exit (0); } );
3091} else {
89a12446
DM
3092 foreach my $hd (@$hds) {
3093 my ($disk, $devname) = @$hd;
3094 next if $devname =~ m|^/dev/md\d+$|;
3095 print "found Disk$disk N:$devname\n";
3096 }
89a12446
DM
3097}
3098
72836708
FG
3099if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3100 print "no network interfaces found\n";
3101 $initial_error = 1;
3102 display_html ("nonics.htm");
3103 set_next ("Reboot", sub { exit (0); } );
3104}
3105
ff2ce71c
FG
3106create_intro_view () if !$initial_error;
3107
7becc472 3108Gtk3->main;
89a12446
DM
3109
3110exit 0;