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