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