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