]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
init: do not sync on actual reboot
[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 {
c6ed3b24 1295 my $maxper = 0.25;
89a12446 1296
408dc55f 1297 update_progress(0, 0, $maxper, "cleanup root-disks");
c6ed3b24 1298
857c43a9
FG
1299 syscmd("vgchange -an") if !$opt_testmode; # deactivate all detected VGs
1300
c6ed3b24 1301 if ($opt_testmode) {
89a12446 1302
6b900321
DM
1303 $rootdev = abs_path($opt_testmode);
1304 syscmd("umount $rootdev");
121ebc59 1305
6b900321 1306 if ($use_btrfs) {
121ebc59 1307
1464c7c9 1308 die "unsupported btrfs mode (for testing environment)\n"
121ebc59
DM
1309 if $filesys ne 'btrfs (RAID0)';
1310
1311 btrfs_create([$rootdev], 'single');
5c06ced5 1312
121ebc59 1313 } elsif ($use_zfs) {
5c06ced5 1314
121ebc59 1315 die "unsupported zfs mode (for testing environment)\n"
c6ed3b24
DM
1316 if $filesys ne 'zfs (RAID0)';
1317
71590b6a 1318 syscmd("zpool destroy $zfstestpool");
5c06ced5 1319
5fd81672 1320 zfs_create_rpool($rootdev);
1464c7c9 1321
121ebc59
DM
1322 } else {
1323
6b900321 1324 # nothing to do
121ebc59
DM
1325 }
1326
1327 } elsif ($use_btrfs) {
1328
1329 my ($devlist, $btrfs_mode) = get_btrfs_raid_setup();
408dc55f
TL
1330
1331 foreach my $hd (@$devlist) {
1332 $clean_disk->(@$hd[1]);
1333 }
1334
1335 update_progress(0, 0.02, $maxper, "create partitions");
1336
121ebc59
DM
1337 my $btrfs_partitions = [];
1338 my $disksize;
1339 foreach my $hd (@$devlist) {
1340 my $devname = @$hd[1];
5ea943cf
SI
1341 my $logical_bsize = @$hd[4];
1342
121ebc59
DM
1343 my ($size, $osdev, $efidev) =
1344 partition_bootable_disk($devname, undef, '8300');
1345 $rootdev = $osdev if !defined($rootdev); # simply point to first disk
1346 my $by_id = find_stable_path("/dev/disk/by-id", $devname);
5ff5a8d0
SI
1347 push @$bootdevinfo, {
1348 esp => $efidev,
1349 devname => $devname,
1350 osdev => $osdev,
1351 by_id => $by_id,
5ea943cf 1352 logical_bsize => $logical_bsize,
5ff5a8d0 1353 };
121ebc59
DM
1354 push @$btrfs_partitions, $osdev;
1355 $disksize = $size;
5c06ced5 1356 }
c6ed3b24 1357
89560d15 1358 $udevadm_trigger_block->();
121ebc59 1359
408dc55f
TL
1360 update_progress(0, 0.03, $maxper, "create btrfs");
1361
121ebc59
DM
1362 btrfs_create($btrfs_partitions, $btrfs_mode);
1363
c6ed3b24
DM
1364 } elsif ($use_zfs) {
1365
82695821 1366 my ($devlist, $vdev) = get_zfs_raid_setup();
c6ed3b24 1367
857c43a9 1368 foreach my $hd (@$devlist) {
89560d15 1369 $clean_disk->(@$hd[1]);
857c43a9 1370 }
4fb6ac60 1371
408dc55f
TL
1372 update_progress(0, 0.02, $maxper, "create partitions");
1373
82695821 1374 # install esp/boot part on all, we can only win!
4fb6ac60 1375 my $disksize;
82695821 1376 for my $hd (@$devlist) {
c6ed3b24 1377 my $devname = @$hd[1];
5ea943cf 1378 my $logical_bsize = @$hd[4];
118d4f40 1379
e38884af 1380 my ($size, $osdev, $efidev) =
d6e919d7 1381 partition_bootable_disk($devname, $config_options->{hdsize}, 'BF01');
4fb6ac60 1382
14aacec8 1383 zfs_mirror_size_check($disksize, $size) if $disksize;
4fb6ac60
TL
1384
1385 push @$bootdevinfo, {
1386 esp => $efidev,
1387 devname => $devname,
5ea943cf
SI
1388 osdev => $osdev,
1389 logical_bsize => $logical_bsize,
4fb6ac60 1390 };
c6ed3b24 1391 $disksize = $size;
c6ed3b24
DM
1392 }
1393
89560d15 1394 $udevadm_trigger_block->();
c6ed3b24 1395
35c6f89c
DM
1396 foreach my $di (@$bootdevinfo) {
1397 my $devname = $di->{devname};
1398 $di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
1464c7c9 1399
e1fdd3d0 1400 my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
c6ed3b24 1401
35c6f89c
DM
1402 $vdev =~ s/ $devname/ $osdev/;
1403 }
1404
e1b49086
SI
1405 foreach my $hd (@$devlist) {
1406 my $devname = @$hd[1];
1407 my $by_id = find_stable_path ("/dev/disk/by-id", $devname);
1408
f0830a59 1409 $vdev =~ s/ $devname/ $by_id/ if $by_id;
e1b49086
SI
1410 }
1411
408dc55f
TL
1412 update_progress(0, 0.03, $maxper, "create rpool");
1413
5fd81672 1414 zfs_create_rpool($vdev);
1464c7c9 1415
c6ed3b24
DM
1416 } else {
1417
1418 die "target '$target_hd' is not a valid block device\n" if ! -b $target_hd;
1419
408dc55f
TL
1420 $clean_disk->($target_hd);
1421
1422 update_progress(0, 0.02, $maxper, "create partitions");
857c43a9 1423
5ea943cf
SI
1424 my $logical_bsize = logical_blocksize($target_hd);
1425
1464c7c9
DM
1426 my ($os_size, $osdev, $efidev);
1427 ($os_size, $osdev, $efidev) =
d6e919d7 1428 partition_bootable_disk($target_hd, $config_options->{hdsize}, '8E00');
c6ed3b24 1429
121ebc59 1430 &$udevadm_trigger_block();
c6ed3b24 1431
35c6f89c 1432 my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
5ff5a8d0
SI
1433 push @$bootdevinfo, {
1434 esp => $efidev,
1435 devname => $target_hd,
1436 osdev => $osdev,
1437 by_id => $by_id,
5ea943cf 1438 logical_bsize => $logical_bsize,
5ff5a8d0 1439 };
c6ed3b24 1440
408dc55f
TL
1441 update_progress(0, 0.03, $maxper, "create LVs");
1442
35c6f89c 1443 my $swap_size = compute_swapsize($os_size);
e2c51d7c 1444 ($rootdev, $swapfile, $datadev) =
35c6f89c 1445 create_lvm_volumes($osdev, $os_size, $swap_size);
c6ed3b24 1446
35c6f89c 1447 # trigger udev to create /dev/disk/by-uuid
121ebc59 1448 &$udevadm_trigger_block(1);
89a12446
DM
1449 }
1450
481671c3
DM
1451 if ($use_zfs) {
1452 # to be fast during installation
71590b6a 1453 syscmd("zfs set sync=disabled $zfspoolname") == 0 ||
481671c3
DM
1454 die "unable to set zfs properties\n";
1455 }
1456
408dc55f 1457 update_progress(0.04, 0, $maxper, "create swap space");
89a12446 1458 if ($swapfile) {
71590b6a 1459 syscmd("mkswap -f $swapfile") == 0 ||
89a12446
DM
1460 die "unable to create swap space\n";
1461 }
1462
408dc55f 1463 update_progress(0.045, 0, $maxper, "creating root filesystems");
89a12446 1464
c6ed3b24 1465 foreach my $di (@$bootdevinfo) {
f810f5d0 1466 next if !$di->{esp};
57a03069 1467 # FIXME remove '-s1' once https://github.com/dosfstools/dosfstools/issues/111 is fixed
5ea943cf
SI
1468 my $vfat_extra_opts = ($di->{logical_bsize} == 4096) ? '-s1' : '';
1469 syscmd("mkfs.vfat $vfat_extra_opts -F32 $di->{esp}") == 0 ||
c6ed3b24
DM
1470 die "unable to initialize EFI ESP on device $di->{esp}\n";
1471 }
1472
121ebc59
DM
1473 if ($use_zfs) {
1474 # do nothing
1475 } elsif ($use_btrfs) {
1476 # do nothing
1477 } else {
71590b6a 1478 create_filesystem($rootdev, 'root', $filesys, 0.05, $maxper, 0, 1);
89a12446
DM
1479 }
1480
71590b6a 1481 update_progress(1, 0.05, $maxper, "mounting target $rootdev");
89a12446 1482
121ebc59
DM
1483 if ($use_zfs) {
1484 # do nothing
121ebc59 1485 } else {
6e56032e
FG
1486 my $mount_opts = 'noatime';
1487 $mount_opts .= ',nobarrier'
1488 if $use_btrfs || $filesys =~ /^ext\d$/;
1489
1490 syscmd("mount -n $rootdev -o $mount_opts $targetdir") == 0 ||
35c6f89c
DM
1491 die "unable to mount $rootdev\n";
1492 }
89a12446 1493
35c6f89c
DM
1494 mkdir "$targetdir/boot";
1495 mkdir "$targetdir/boot/efi";
89a12446 1496
5fd81672
DM
1497 mkdir "$targetdir/var";
1498 mkdir "$targetdir/var/lib";
121ebc59 1499
f7d18efd
DM
1500 if ($setup->{product} eq 'pve') {
1501 mkdir "$targetdir/var/lib/vz";
1502 mkdir "$targetdir/var/lib/pve";
1503
1504 if ($use_btrfs) {
1505 syscmd("btrfs subvolume create $targetdir/var/lib/pve/local-btrfs") == 0 ||
1506 die "unable to create btrfs subvolume\n";
1507 }
121ebc59 1508 }
89a12446 1509
8d7ddbde
TL
1510 mkdir "$targetdir/mnt";
1511 mkdir "$targetdir/mnt/hostrun";
1512 syscmd("mount --bind /run $targetdir/mnt/hostrun") == 0 ||
1513 die "unable to bindmount run on $targetdir/mnt/hostrun\n";
1514
71590b6a 1515 update_progress(1, 0.05, $maxper, "extracting base system");
89a12446 1516
fafc616c
DM
1517 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
1518 $ino || die "unable to open file '$basefile' - $!\n";
968fa90b 1519
c437cef5
DM
1520 my $files = file_read_firstline("${proxmox_cddir}/proxmox/$setup->{product}-base.cnt") ||
1521 die "unable to read base file count\n";
89a12446
DM
1522
1523 my $per = 0;
1524 my $count = 0;
1525
71590b6a 1526 run_command("unsquashfs -f -dest $targetdir -i $basefile", sub {
89a12446 1527 my $line = shift;
fafc616c 1528 return if $line !~ m/^$targetdir/;
89a12446
DM
1529 $count++;
1530 my $nper = int (($count *100)/$files);
1531 if ($nper != $per) {
1532 $per = $nper;
0f3d1edd 1533 my $frac = $per > 100 ? 1 : $per/100;
71590b6a 1534 update_progress($frac, $maxper, 0.5);
89a12446
DM
1535 }
1536 });
1537
000f289d 1538 syscmd("mount -n -t tmpfs tmpfs $targetdir/tmp") == 0 || die "unable to mount tmpfs on $targetdir/tmp\n";
f9b3baff
TL
1539
1540 mkdir "$targetdir/tmp/pkg";
1541 syscmd("mount -n --bind '$proxmox_pkgdir' '$targetdir/tmp/pkg'") == 0
1542 || die "unable to bind-mount packages on $targetdir/tmp/pkg\n";
000f289d
TL
1543 syscmd("mount -n -t proc proc $targetdir/proc") == 0 || die "unable to mount proc on $targetdir/proc\n";
1544 syscmd("mount -n -t sysfs sysfs $targetdir/sys") == 0 || die "unable to mount sysfs on $targetdir/sys\n";
f238dd03 1545 if ($boot_type eq 'efi') {
dea730ea 1546 syscmd("mount -n -t efivarfs efivarfs $targetdir/sys/firmware/efi/efivars") == 0 ||
f238dd03
TL
1547 die "unable to mount efivarfs on $targetdir/sys/firmware/efi/efivars: $!\n";
1548 }
8d7ddbde
TL
1549 syscmd("chroot $targetdir mount --bind /mnt/hostrun /run") == 0 ||
1550 die "unable to re-bindmount hostrun on /run in chroot\n";
89a12446 1551
71590b6a 1552 update_progress(1, $maxper, 0.5, "configuring base system");
89a12446
DM
1553
1554 # configure hosts
1555
968fa90b 1556 my $hosts =
89a12446 1557 "127.0.0.1 localhost.localdomain localhost\n" .
57cd2e0f 1558 "$ipaddress $hostname.$domain $hostname\n\n" .
89a12446
DM
1559 "# The following lines are desirable for IPv6 capable hosts\n\n" .
1560 "::1 ip6-localhost ip6-loopback\n" .
1561 "fe00::0 ip6-localnet\n" .
1562 "ff00::0 ip6-mcastprefix\n" .
1563 "ff02::1 ip6-allnodes\n" .
1564 "ff02::2 ip6-allrouters\n" .
1565 "ff02::3 ip6-allhosts\n";
1566
71590b6a 1567 write_config($hosts, "$targetdir/etc/hosts");
89a12446 1568
71590b6a 1569 write_config("$hostname\n", "$targetdir/etc/hostname");
89a12446 1570
71590b6a 1571 syscmd("/bin/hostname $hostname") if !$opt_testmode;
89a12446
DM
1572
1573 # configure interfaces
1574
b6200603
DM
1575 my $ifaces = "auto lo\niface lo inet loopback\n\n";
1576
1577 my $ntype = $ipversion == 4 ? 'inet' : 'inet6';
1578
f5439194 1579 my $ethdev = $ipconf->{ifaces}->{$ipconf->{selected}}->{name};
4a0331ab
DM
1580
1581 if ($setup->{bridged_network}) {
f5439194 1582 $ifaces .= "iface $ethdev $ntype manual\n";
4a0331ab
DM
1583
1584 $ifaces .=
1585 "\nauto vmbr0\niface vmbr0 $ntype static\n" .
b1838e1e 1586 "\taddress $cidr\n" .
4a0331ab 1587 "\tgateway $gateway\n" .
f5439194 1588 "\tbridge-ports $ethdev\n" .
2b8fdf3d
FG
1589 "\tbridge-stp off\n" .
1590 "\tbridge-fd 0\n";
4a0331ab 1591 } else {
f5439194
TL
1592 $ifaces .= "auto $ethdev\n" .
1593 "iface $ethdev $ntype static\n" .
b1838e1e 1594 "\taddress $cidr\n" .
4a0331ab
DM
1595 "\tgateway $gateway\n";
1596 }
89a12446 1597
fe44bd92
FG
1598 foreach my $iface (sort keys %{$ipconf->{ifaces}}) {
1599 my $name = $ipconf->{ifaces}->{$iface}->{name};
f5439194 1600 next if $name eq $ethdev;
fe44bd92
FG
1601
1602 $ifaces .= "\niface $name $ntype manual\n";
1603 }
1604
71590b6a 1605 write_config($ifaces, "$targetdir/etc/network/interfaces");
89a12446
DM
1606
1607 # configure dns
1608
39a0f44b 1609 my $resolvconf = "search $domain\nnameserver $dnsserver\n";
71590b6a 1610 write_config($resolvconf, "$targetdir/etc/resolv.conf");
89a12446 1611
5c06ced5
DM
1612 # configure fstab
1613
1614 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass>\n";
1615
121ebc59
DM
1616 if ($use_zfs) {
1617 # do nothing
1618 } elsif ($use_btrfs) {
1619 my $fsuuid;
1620 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev";
1621 run_command($cmd, sub {
1622 my $line = shift;
1623
1624 if ($line =~ m/^UUID=([A-Fa-f0-9\-]+)$/) {
1625 $fsuuid = $1;
1626 }
1627 });
1628
1629 die "unable to detect FS UUID" if !defined($fsuuid);
1630
1631 $fstab .= "UUID=$fsuuid / btrfs defaults 0 1\n";
1632 } else {
80090926
DM
1633 my $root_mountopt = $fssetup->{$filesys}->{root_mountopt} || 'defaults';
1634 $fstab .= "$rootdev / $filesys ${root_mountopt} 0 1\n";
7bc4f6bd 1635 }
a84ea010
DM
1636
1637 # mount /boot/efi
1638 # Note: this is required by current grub, but really dangerous, because
1639 # vfat does not have journaling, so it triggers manual fsck after each crash
1640 # so we only mount /boot/efi if really required (efi systems).
4fb6ac60 1641 if ($boot_type eq 'efi' && !$use_zfs) {
a84ea010 1642 if (scalar(@$bootdevinfo)) {
f810f5d0 1643 my $di = @$bootdevinfo[0]; # simply use first disk
4fb6ac60
TL
1644
1645 if ($di->{esp}) {
f810f5d0
DM
1646 my $efi_boot_uuid = $di->{esp};
1647 if (my $uuid = find_dev_by_uuid ($di->{esp})) {
1648 $efi_boot_uuid = "UUID=$uuid";
1649 }
1464c7c9 1650
f810f5d0
DM
1651 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1\n";
1652 }
a84ea010 1653 }
84761f93
DM
1654 }
1655
c1cfbb1c 1656
89a12446
DM
1657 $fstab .= "$swapfile none swap sw 0 0\n" if $swapfile;
1658
1659 $fstab .= "proc /proc proc defaults 0 0\n";
1660
71590b6a
OB
1661 write_config($fstab, "$targetdir/etc/fstab");
1662 write_config("", "$targetdir/etc/mtab");
968fa90b 1663
c1cfbb1c
SI
1664 syscmd("cp ${proxmox_libdir}/policy-disable-rc.d " .
1665 "$targetdir/usr/sbin/policy-rc.d") == 0 ||
1666 die "unable to copy policy-rc.d\n";
1667 syscmd("cp ${proxmox_libdir}/fake-start-stop-daemon " .
1668 "$targetdir/sbin/") == 0 ||
89a12446
DM
1669 die "unable to copy start-stop-daemon\n";
1670
71590b6a
OB
1671 diversion_add($targetdir, "/sbin/start-stop-daemon", "/sbin/fake-start-stop-daemon");
1672 diversion_add($targetdir, "/usr/sbin/update-grub", "/bin/true");
1673 diversion_add($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
89a12446 1674
72d2dcd0
SI
1675 my $machine_id = run_command("systemd-id128 new");
1676 die "unable to create a new machine-id\n" if ! $machine_id;
1677 write_config($machine_id, "$targetdir/etc/machine-id");
1678
a346a962
SI
1679 syscmd("cp /etc/hostid $targetdir/etc/") == 0 ||
1680 die "unable to copy hostid\n";
1681
71590b6a 1682 syscmd("touch $targetdir/proxmox_install_mode");
89a12446 1683
e35d5efb 1684 my $grub_install_devices_txt = '';
3573c046 1685 foreach my $di (@$bootdevinfo) {
e35d5efb 1686 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt;
ff863262 1687 $grub_install_devices_txt .= $di->{by_id} || $di->{devname};
3573c046
DM
1688 }
1689
b1293fcb
FG
1690 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1691 my $xkmap = $cmap->{kmap}->{$keymap}->{x11} // 'us';
1464c7c9 1692
89a12446
DM
1693 debconfig_set ($targetdir, <<_EOD);
1694locales locales/default_environment_locale select en_US.UTF-8
1695locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1696samba-common samba-common/dhcp boolean false
1697samba-common samba-common/workgroup string WORKGROUP
e953719f 1698postfix postfix/main_mailer_type select No configuration
b1293fcb 1699keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
814f5c39 1700d-i debian-installer/locale select en_US.UTF-8
3573c046 1701grub-pc grub-pc/install_devices select $grub_install_devices_txt
89a12446
DM
1702_EOD
1703
89a12446 1704 my $pkg_count = 0;
97980bf2 1705 while (<${proxmox_pkgdir}/*.deb>) { $pkg_count++ };
89a12446 1706
121ebc59
DM
1707 # btrfs/dpkg is extremely slow without --force-unsafe-io
1708 my $dpkg_opts = $use_btrfs ? "--force-unsafe-io" : "";
1709
89a12446 1710 $count = 0;
97980bf2 1711 while (<${proxmox_pkgdir}/*.deb>) {
89a12446
DM
1712 chomp;
1713 my $path = $_;
97980bf2 1714 my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/;
71590b6a 1715 update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb");
89a12446 1716 print "extracting: $deb\n";
f9b3baff
TL
1717 syscmd("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/pkg/$deb") == 0
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+)/) {
84d08198 1730 update_progress((++$count)/$pkg_count, 0.75, 0.95, "configuring $1");
89a12446
DM
1731 }
1732 });
968fa90b 1733
89a12446
DM
1734 unlink "$targetdir/etc/mailname";
1735 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
71590b6a 1736 write_config($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
89a12446
DM
1737
1738 # make sure we have all postfix directories
71590b6a 1739 syscmd("chroot $targetdir /usr/sbin/postfix check");
89a12446 1740 # cleanup mail queue
71590b6a 1741 syscmd("chroot $targetdir /usr/sbin/postsuper -d ALL");
29da2d42
SI
1742 # create /etc/aliases.db (/etc/aliases is shipped in the base squashfs)
1743 syscmd("chroot $targetdir /usr/bin/newaliases");
89a12446 1744
6b5dc3d0 1745 # enable NTP (timedatectl set-ntp true does not work without DBUS)
71590b6a 1746 syscmd("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
6b5dc3d0 1747
89a12446
DM
1748 unlink "$targetdir/proxmox_install_mode";
1749
968fa90b 1750 # set timezone
89a12446
DM
1751 unlink ("$targetdir/etc/localtime");
1752 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
71590b6a 1753 write_config("$timezone\n", "$targetdir/etc/timezone");
89a12446 1754
89a12446
DM
1755 # set apt mirror
1756 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1757 my $fn = "$targetdir/etc/apt/sources.list";
71590b6a 1758 syscmd("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
89a12446
DM
1759 }
1760
19edf8b7
DM
1761 # create extended_states for apt (avoid cron job warning if that
1762 # file does not exist)
71590b6a 1763 write_config('', "$targetdir/var/lib/apt/extended_states");
19edf8b7 1764
c2657b8b 1765 # allow ssh root login
abcadb95 1766 syscmd(['sed', '-i', 's/^#\?PermitRootLogin.*/PermitRootLogin yes/', "$targetdir/etc/ssh/sshd_config"]);
861a26d4
DM
1767
1768 if ($setup->{product} eq 'pmg') {
1769 # install initial clamav DB
1770 my $srcdir = "${proxmox_cddir}/proxmox/clamav";
05eb99e2 1771 foreach my $fn ("main.cvd", "bytecode.cvd", "daily.cvd", "safebrowsing.cvd") {
71590b6a 1772 syscmd("cp \"$srcdir/$fn\" \"$targetdir/var/lib/clamav\"") == 0 ||
861a26d4
DM
1773 die "installation of clamav db file '$fn' failed\n";
1774 }
1775 syscmd("chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav") == 0 ||
1776 die "unable to set owner for clamav database files\n";
1777 }
1778
58a09baa
DM
1779 if ($setup->{product} eq 'pve') {
1780 # save installer settings
1781 my $ucc = uc ($country);
1782 debconfig_set($targetdir, "pve-manager pve-manager/country string $ucc\n");
1783 }
89a12446 1784
71590b6a 1785 update_progress(0.8, 0.95, 1, "make system bootable");
89a12446 1786
5c06ced5 1787 if ($use_zfs) {
b13dac4b
FG
1788 # add ZFS options while preserving existing kernel cmdline
1789 my $zfs_snippet = "GRUB_CMDLINE_LINUX=\"\$GRUB_CMDLINE_LINUX root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs\"";
1790 write_config($zfs_snippet, "$targetdir/etc/default/grub.d/zfs.cfg");
4fb6ac60 1791
d8530b74 1792 write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs", "$targetdir/etc/kernel/cmdline");
1464c7c9 1793
5c06ced5 1794 }
23c337f5 1795
71590b6a
OB
1796 diversion_remove($targetdir, "/usr/sbin/update-grub");
1797 diversion_remove($targetdir, "/usr/sbin/update-initramfs");
89a12446 1798
56207f2a
DM
1799 my $kapi;
1800 foreach my $fn (<$targetdir/lib/modules/*>) {
1801 if ($fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$!) {
1802 die "found multiple kernels\n" if defined($kapi);
1803 $kapi = $1;
1804 }
1805 }
1806 die "unable to detect kernel version\n" if !defined($kapi);
1807
c6ed3b24 1808 if (!$opt_testmode) {
89a12446
DM
1809
1810 unlink ("$targetdir/etc/mtab");
1811 symlink ("/proc/mounts", "$targetdir/etc/mtab");
71590b6a 1812 syscmd("mount -n --bind /dev $targetdir/dev");
89a12446 1813
1f8429eb
FG
1814 my $bootloader_err_list = [];
1815 eval {
1816 syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1817 die "unable to install initramfs\n";
1818
5ea943cf
SI
1819 my $native_4k_disk_bootable = 0;
1820 foreach my $di (@$bootdevinfo) {
1821 $native_4k_disk_bootable |= ($di->{logical_bsize} == 4096);
1822 }
1823
1f8429eb
FG
1824 foreach my $di (@$bootdevinfo) {
1825 my $dev = $di->{devname};
d58ef02c
SI
1826 if ($use_zfs) {
1827 prepare_proxmox_boot_esp($di->{esp}, $targetdir);
1828 } else {
1829 if (!$native_4k_disk_bootable) {
1830 eval {
1831 syscmd("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1832 die "unable to install the i386-pc boot loader on '$dev'\n";
1833 };
1834 push @$bootloader_err_list, $@ if $@;
1835 }
1f8429eb
FG
1836
1837 eval {
1838 if (my $esp = $di->{esp}) {
1f8429eb
FG
1839 prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
1840 }
1841 }
1842 };
1843 push @$bootloader_err_list, $@ if $@;
1e61f3d8 1844 }
89a12446 1845
1f8429eb
FG
1846 syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
1847 die "unable to update boot loader config\n";
1f8429eb
FG
1848 };
1849 push @$bootloader_err_list, $@ if $@;
1850
1851 if (scalar(@$bootloader_err_list) > 0) {
1852 $bootloader_err = "bootloader setup errors:\n";
1853 map { $bootloader_err .= "- $_" } @$bootloader_err_list;
1854 warn $bootloader_err;
f2afc0fc 1855 }
03c686b7 1856
71590b6a 1857 syscmd("umount $targetdir/dev");
89a12446
DM
1858 }
1859
968fa90b 1860 # cleanup
89a12446 1861
89a12446
DM
1862 unlink "$targetdir/usr/sbin/policy-rc.d";
1863
71590b6a 1864 diversion_remove($targetdir, "/sbin/start-stop-daemon");
89a12446
DM
1865
1866 # set root password
968fa90b 1867 my $octets = encode("utf-8", $password);
71590b6a
OB
1868 run_command("chroot $targetdir /usr/sbin/chpasswd", undef,
1869 "root:$octets\n");
7053f98b 1870
038552a1 1871 if ($setup->{product} eq 'pmg') {
038552a1 1872 # save admin email
71590b6a
OB
1873 write_config("section: admin\n\temail ${mailto}\n",
1874 "$targetdir/etc/pmg/pmg.conf");
038552a1
DM
1875
1876 } elsif ($setup->{product} eq 'pve') {
7053f98b 1877
8acc47b5 1878 # create pmxcfs DB
7053f98b 1879
8acc47b5
DM
1880 my $tmpdir = "$targetdir/tmp/pve";
1881 mkdir $tmpdir;
7053f98b 1882
8acc47b5
DM
1883 # write vnc keymap to datacenter.cfg
1884 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
71590b6a
OB
1885 write_config("keyboard: $vnckmap\n",
1886 "$tmpdir/datacenter.cfg");
968fa90b 1887
8acc47b5 1888 # save admin email
e32b8d2d 1889 write_config("user:root\@pam:1:0:::${mailto}::\n", "$tmpdir/user.cfg");
5fd81672 1890
8acc47b5 1891 # write storage.cfg
d8c697d4 1892 my $storage_cfg_fn = "$tmpdir/storage.cfg";
8acc47b5 1893 if ($use_zfs) {
71590b6a 1894 write_config($storage_cfg_zfs, $storage_cfg_fn);
8acc47b5 1895 } elsif ($use_btrfs) {
71590b6a 1896 write_config($storage_cfg_btrfs, $storage_cfg_fn);
e2c51d7c 1897 } elsif ($datadev) {
71590b6a 1898 write_config($storage_cfg_lvmthin, $storage_cfg_fn);
e2c51d7c 1899 } else {
71590b6a 1900 write_config($storage_cfg_local, $storage_cfg_fn);
8acc47b5 1901 }
7053f98b 1902
8acc47b5
DM
1903 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1904
71590b6a 1905 syscmd("rm -rf $tmpdir");
ca74501d
TL
1906 } elsif ($setup->{product} eq 'pbs') {
1907 my $base_cfg_path = "/etc/proxmox-backup";
e32b8d2d 1908 mkdir "$targetdir/$base_cfg_path";
341a93b7
TL
1909
1910 chroot_chown($targetdir, $base_cfg_path, user => 'backup', recursive => 1);
1911 chroot_chmod($targetdir, $base_cfg_path, mode => '0700');
e32b8d2d
TL
1912
1913 my $user_cfg_fn = "$base_cfg_path/user.cfg";
1914 write_config("user: root\@pam\n\temail ${mailto}\n", "$targetdir/$user_cfg_fn");
1915 chroot_chown($targetdir, $user_cfg_fn, user => 'root', group => 'backup');
1916 chroot_chmod($targetdir, $user_cfg_fn, mode => '0640');
8acc47b5 1917 }
89a12446
DM
1918 };
1919
1920 my $err = $@;
1921
71590b6a 1922 update_progress(1, 0, 1, "");
89a12446
DM
1923
1924 print $err if $err;
1925
1926 if ($opt_testmode) {
121ebc59
DM
1927 my $elapsed = Time::HiRes::tv_interval($starttime);
1928 print "Elapsed extract time: $elapsed\n";
1929
71590b6a 1930 syscmd("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
89a12446
DM
1931 }
1932
8d7ddbde
TL
1933 syscmd("umount $targetdir/run");
1934 syscmd("umount $targetdir/mnt/hostrun");
f9b3baff 1935 syscmd("umount $targetdir/tmp/pkg");
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;