]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
hd size: defuse, rework and comment hd size computation
[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
9bc6376d
TL
903 # default to `relatime` on, fast enough for the installer and production
904 syscmd("zfs set atime=on relatime=on $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 1343 my ($size, $osdev, $efidev) =
679c813c 1344 partition_bootable_disk($devname, $config_options->{hdsize}, '8300');
121ebc59
DM
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
4fb6ac60
TL
1383 push @$bootdevinfo, {
1384 esp => $efidev,
1385 devname => $devname,
5ea943cf
SI
1386 osdev => $osdev,
1387 logical_bsize => $logical_bsize,
4fb6ac60 1388 };
c6ed3b24 1389 $disksize = $size;
c6ed3b24
DM
1390 }
1391
89560d15 1392 $udevadm_trigger_block->();
c6ed3b24 1393
35c6f89c
DM
1394 foreach my $di (@$bootdevinfo) {
1395 my $devname = $di->{devname};
1396 $di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
1464c7c9 1397
e1fdd3d0 1398 my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
c6ed3b24 1399
35c6f89c
DM
1400 $vdev =~ s/ $devname/ $osdev/;
1401 }
1402
e1b49086
SI
1403 foreach my $hd (@$devlist) {
1404 my $devname = @$hd[1];
1405 my $by_id = find_stable_path ("/dev/disk/by-id", $devname);
1406
f0830a59 1407 $vdev =~ s/ $devname/ $by_id/ if $by_id;
e1b49086
SI
1408 }
1409
408dc55f
TL
1410 update_progress(0, 0.03, $maxper, "create rpool");
1411
5fd81672 1412 zfs_create_rpool($vdev);
1464c7c9 1413
c6ed3b24
DM
1414 } else {
1415
1416 die "target '$target_hd' is not a valid block device\n" if ! -b $target_hd;
1417
408dc55f
TL
1418 $clean_disk->($target_hd);
1419
1420 update_progress(0, 0.02, $maxper, "create partitions");
857c43a9 1421
5ea943cf
SI
1422 my $logical_bsize = logical_blocksize($target_hd);
1423
1464c7c9
DM
1424 my ($os_size, $osdev, $efidev);
1425 ($os_size, $osdev, $efidev) =
d6e919d7 1426 partition_bootable_disk($target_hd, $config_options->{hdsize}, '8E00');
c6ed3b24 1427
121ebc59 1428 &$udevadm_trigger_block();
c6ed3b24 1429
35c6f89c 1430 my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
5ff5a8d0
SI
1431 push @$bootdevinfo, {
1432 esp => $efidev,
1433 devname => $target_hd,
1434 osdev => $osdev,
1435 by_id => $by_id,
5ea943cf 1436 logical_bsize => $logical_bsize,
5ff5a8d0 1437 };
c6ed3b24 1438
408dc55f
TL
1439 update_progress(0, 0.03, $maxper, "create LVs");
1440
35c6f89c 1441 my $swap_size = compute_swapsize($os_size);
e2c51d7c 1442 ($rootdev, $swapfile, $datadev) =
35c6f89c 1443 create_lvm_volumes($osdev, $os_size, $swap_size);
c6ed3b24 1444
35c6f89c 1445 # trigger udev to create /dev/disk/by-uuid
121ebc59 1446 &$udevadm_trigger_block(1);
89a12446
DM
1447 }
1448
481671c3
DM
1449 if ($use_zfs) {
1450 # to be fast during installation
71590b6a 1451 syscmd("zfs set sync=disabled $zfspoolname") == 0 ||
481671c3
DM
1452 die "unable to set zfs properties\n";
1453 }
1454
408dc55f 1455 update_progress(0.04, 0, $maxper, "create swap space");
89a12446 1456 if ($swapfile) {
71590b6a 1457 syscmd("mkswap -f $swapfile") == 0 ||
89a12446
DM
1458 die "unable to create swap space\n";
1459 }
1460
408dc55f 1461 update_progress(0.045, 0, $maxper, "creating root filesystems");
89a12446 1462
c6ed3b24 1463 foreach my $di (@$bootdevinfo) {
f810f5d0 1464 next if !$di->{esp};
57a03069 1465 # FIXME remove '-s1' once https://github.com/dosfstools/dosfstools/issues/111 is fixed
5ea943cf
SI
1466 my $vfat_extra_opts = ($di->{logical_bsize} == 4096) ? '-s1' : '';
1467 syscmd("mkfs.vfat $vfat_extra_opts -F32 $di->{esp}") == 0 ||
c6ed3b24
DM
1468 die "unable to initialize EFI ESP on device $di->{esp}\n";
1469 }
1470
121ebc59
DM
1471 if ($use_zfs) {
1472 # do nothing
1473 } elsif ($use_btrfs) {
1474 # do nothing
1475 } else {
71590b6a 1476 create_filesystem($rootdev, 'root', $filesys, 0.05, $maxper, 0, 1);
89a12446
DM
1477 }
1478
71590b6a 1479 update_progress(1, 0.05, $maxper, "mounting target $rootdev");
89a12446 1480
121ebc59
DM
1481 if ($use_zfs) {
1482 # do nothing
121ebc59 1483 } else {
6e56032e
FG
1484 my $mount_opts = 'noatime';
1485 $mount_opts .= ',nobarrier'
1486 if $use_btrfs || $filesys =~ /^ext\d$/;
1487
1488 syscmd("mount -n $rootdev -o $mount_opts $targetdir") == 0 ||
35c6f89c
DM
1489 die "unable to mount $rootdev\n";
1490 }
89a12446 1491
35c6f89c
DM
1492 mkdir "$targetdir/boot";
1493 mkdir "$targetdir/boot/efi";
89a12446 1494
5fd81672
DM
1495 mkdir "$targetdir/var";
1496 mkdir "$targetdir/var/lib";
121ebc59 1497
f7d18efd
DM
1498 if ($setup->{product} eq 'pve') {
1499 mkdir "$targetdir/var/lib/vz";
1500 mkdir "$targetdir/var/lib/pve";
1501
1502 if ($use_btrfs) {
1503 syscmd("btrfs subvolume create $targetdir/var/lib/pve/local-btrfs") == 0 ||
1504 die "unable to create btrfs subvolume\n";
1505 }
121ebc59 1506 }
89a12446 1507
8d7ddbde
TL
1508 mkdir "$targetdir/mnt";
1509 mkdir "$targetdir/mnt/hostrun";
1510 syscmd("mount --bind /run $targetdir/mnt/hostrun") == 0 ||
1511 die "unable to bindmount run on $targetdir/mnt/hostrun\n";
1512
71590b6a 1513 update_progress(1, 0.05, $maxper, "extracting base system");
89a12446 1514
fafc616c
DM
1515 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
1516 $ino || die "unable to open file '$basefile' - $!\n";
968fa90b 1517
c437cef5
DM
1518 my $files = file_read_firstline("${proxmox_cddir}/proxmox/$setup->{product}-base.cnt") ||
1519 die "unable to read base file count\n";
89a12446
DM
1520
1521 my $per = 0;
1522 my $count = 0;
1523
71590b6a 1524 run_command("unsquashfs -f -dest $targetdir -i $basefile", sub {
89a12446 1525 my $line = shift;
fafc616c 1526 return if $line !~ m/^$targetdir/;
89a12446
DM
1527 $count++;
1528 my $nper = int (($count *100)/$files);
1529 if ($nper != $per) {
1530 $per = $nper;
0f3d1edd 1531 my $frac = $per > 100 ? 1 : $per/100;
71590b6a 1532 update_progress($frac, $maxper, 0.5);
89a12446
DM
1533 }
1534 });
1535
000f289d 1536 syscmd("mount -n -t tmpfs tmpfs $targetdir/tmp") == 0 || die "unable to mount tmpfs on $targetdir/tmp\n";
f9b3baff
TL
1537
1538 mkdir "$targetdir/tmp/pkg";
1539 syscmd("mount -n --bind '$proxmox_pkgdir' '$targetdir/tmp/pkg'") == 0
1540 || die "unable to bind-mount packages on $targetdir/tmp/pkg\n";
000f289d
TL
1541 syscmd("mount -n -t proc proc $targetdir/proc") == 0 || die "unable to mount proc on $targetdir/proc\n";
1542 syscmd("mount -n -t sysfs sysfs $targetdir/sys") == 0 || die "unable to mount sysfs on $targetdir/sys\n";
f238dd03 1543 if ($boot_type eq 'efi') {
dea730ea 1544 syscmd("mount -n -t efivarfs efivarfs $targetdir/sys/firmware/efi/efivars") == 0 ||
f238dd03
TL
1545 die "unable to mount efivarfs on $targetdir/sys/firmware/efi/efivars: $!\n";
1546 }
8d7ddbde
TL
1547 syscmd("chroot $targetdir mount --bind /mnt/hostrun /run") == 0 ||
1548 die "unable to re-bindmount hostrun on /run in chroot\n";
89a12446 1549
71590b6a 1550 update_progress(1, $maxper, 0.5, "configuring base system");
89a12446
DM
1551
1552 # configure hosts
1553
968fa90b 1554 my $hosts =
89a12446 1555 "127.0.0.1 localhost.localdomain localhost\n" .
57cd2e0f 1556 "$ipaddress $hostname.$domain $hostname\n\n" .
89a12446
DM
1557 "# The following lines are desirable for IPv6 capable hosts\n\n" .
1558 "::1 ip6-localhost ip6-loopback\n" .
1559 "fe00::0 ip6-localnet\n" .
1560 "ff00::0 ip6-mcastprefix\n" .
1561 "ff02::1 ip6-allnodes\n" .
1562 "ff02::2 ip6-allrouters\n" .
1563 "ff02::3 ip6-allhosts\n";
1564
71590b6a 1565 write_config($hosts, "$targetdir/etc/hosts");
89a12446 1566
71590b6a 1567 write_config("$hostname\n", "$targetdir/etc/hostname");
89a12446 1568
71590b6a 1569 syscmd("/bin/hostname $hostname") if !$opt_testmode;
89a12446
DM
1570
1571 # configure interfaces
1572
b6200603
DM
1573 my $ifaces = "auto lo\niface lo inet loopback\n\n";
1574
1575 my $ntype = $ipversion == 4 ? 'inet' : 'inet6';
1576
f5439194 1577 my $ethdev = $ipconf->{ifaces}->{$ipconf->{selected}}->{name};
4a0331ab
DM
1578
1579 if ($setup->{bridged_network}) {
f5439194 1580 $ifaces .= "iface $ethdev $ntype manual\n";
4a0331ab
DM
1581
1582 $ifaces .=
1583 "\nauto vmbr0\niface vmbr0 $ntype static\n" .
b1838e1e 1584 "\taddress $cidr\n" .
4a0331ab 1585 "\tgateway $gateway\n" .
f5439194 1586 "\tbridge-ports $ethdev\n" .
2b8fdf3d
FG
1587 "\tbridge-stp off\n" .
1588 "\tbridge-fd 0\n";
4a0331ab 1589 } else {
f5439194
TL
1590 $ifaces .= "auto $ethdev\n" .
1591 "iface $ethdev $ntype static\n" .
b1838e1e 1592 "\taddress $cidr\n" .
4a0331ab
DM
1593 "\tgateway $gateway\n";
1594 }
89a12446 1595
fe44bd92
FG
1596 foreach my $iface (sort keys %{$ipconf->{ifaces}}) {
1597 my $name = $ipconf->{ifaces}->{$iface}->{name};
f5439194 1598 next if $name eq $ethdev;
fe44bd92
FG
1599
1600 $ifaces .= "\niface $name $ntype manual\n";
1601 }
1602
71590b6a 1603 write_config($ifaces, "$targetdir/etc/network/interfaces");
89a12446
DM
1604
1605 # configure dns
1606
39a0f44b 1607 my $resolvconf = "search $domain\nnameserver $dnsserver\n";
71590b6a 1608 write_config($resolvconf, "$targetdir/etc/resolv.conf");
89a12446 1609
5c06ced5
DM
1610 # configure fstab
1611
1612 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass>\n";
1613
121ebc59
DM
1614 if ($use_zfs) {
1615 # do nothing
1616 } elsif ($use_btrfs) {
1617 my $fsuuid;
1618 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev";
1619 run_command($cmd, sub {
1620 my $line = shift;
1621
1622 if ($line =~ m/^UUID=([A-Fa-f0-9\-]+)$/) {
1623 $fsuuid = $1;
1624 }
1625 });
1626
1627 die "unable to detect FS UUID" if !defined($fsuuid);
1628
1629 $fstab .= "UUID=$fsuuid / btrfs defaults 0 1\n";
1630 } else {
80090926
DM
1631 my $root_mountopt = $fssetup->{$filesys}->{root_mountopt} || 'defaults';
1632 $fstab .= "$rootdev / $filesys ${root_mountopt} 0 1\n";
7bc4f6bd 1633 }
a84ea010
DM
1634
1635 # mount /boot/efi
1636 # Note: this is required by current grub, but really dangerous, because
1637 # vfat does not have journaling, so it triggers manual fsck after each crash
1638 # so we only mount /boot/efi if really required (efi systems).
4fb6ac60 1639 if ($boot_type eq 'efi' && !$use_zfs) {
a84ea010 1640 if (scalar(@$bootdevinfo)) {
f810f5d0 1641 my $di = @$bootdevinfo[0]; # simply use first disk
4fb6ac60
TL
1642
1643 if ($di->{esp}) {
f810f5d0
DM
1644 my $efi_boot_uuid = $di->{esp};
1645 if (my $uuid = find_dev_by_uuid ($di->{esp})) {
1646 $efi_boot_uuid = "UUID=$uuid";
1647 }
1464c7c9 1648
f810f5d0
DM
1649 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1\n";
1650 }
a84ea010 1651 }
84761f93
DM
1652 }
1653
c1cfbb1c 1654
89a12446
DM
1655 $fstab .= "$swapfile none swap sw 0 0\n" if $swapfile;
1656
1657 $fstab .= "proc /proc proc defaults 0 0\n";
1658
71590b6a
OB
1659 write_config($fstab, "$targetdir/etc/fstab");
1660 write_config("", "$targetdir/etc/mtab");
968fa90b 1661
c1cfbb1c
SI
1662 syscmd("cp ${proxmox_libdir}/policy-disable-rc.d " .
1663 "$targetdir/usr/sbin/policy-rc.d") == 0 ||
1664 die "unable to copy policy-rc.d\n";
1665 syscmd("cp ${proxmox_libdir}/fake-start-stop-daemon " .
1666 "$targetdir/sbin/") == 0 ||
89a12446
DM
1667 die "unable to copy start-stop-daemon\n";
1668
71590b6a
OB
1669 diversion_add($targetdir, "/sbin/start-stop-daemon", "/sbin/fake-start-stop-daemon");
1670 diversion_add($targetdir, "/usr/sbin/update-grub", "/bin/true");
1671 diversion_add($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
89a12446 1672
72d2dcd0
SI
1673 my $machine_id = run_command("systemd-id128 new");
1674 die "unable to create a new machine-id\n" if ! $machine_id;
1675 write_config($machine_id, "$targetdir/etc/machine-id");
1676
a346a962
SI
1677 syscmd("cp /etc/hostid $targetdir/etc/") == 0 ||
1678 die "unable to copy hostid\n";
1679
71590b6a 1680 syscmd("touch $targetdir/proxmox_install_mode");
89a12446 1681
e35d5efb 1682 my $grub_install_devices_txt = '';
3573c046 1683 foreach my $di (@$bootdevinfo) {
e35d5efb 1684 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt;
ff863262 1685 $grub_install_devices_txt .= $di->{by_id} || $di->{devname};
3573c046
DM
1686 }
1687
b1293fcb
FG
1688 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1689 my $xkmap = $cmap->{kmap}->{$keymap}->{x11} // 'us';
1464c7c9 1690
89a12446
DM
1691 debconfig_set ($targetdir, <<_EOD);
1692locales locales/default_environment_locale select en_US.UTF-8
1693locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1694samba-common samba-common/dhcp boolean false
1695samba-common samba-common/workgroup string WORKGROUP
e953719f 1696postfix postfix/main_mailer_type select No configuration
b1293fcb 1697keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
814f5c39 1698d-i debian-installer/locale select en_US.UTF-8
3573c046 1699grub-pc grub-pc/install_devices select $grub_install_devices_txt
89a12446
DM
1700_EOD
1701
89a12446 1702 my $pkg_count = 0;
97980bf2 1703 while (<${proxmox_pkgdir}/*.deb>) { $pkg_count++ };
89a12446 1704
121ebc59
DM
1705 # btrfs/dpkg is extremely slow without --force-unsafe-io
1706 my $dpkg_opts = $use_btrfs ? "--force-unsafe-io" : "";
1707
89a12446 1708 $count = 0;
97980bf2 1709 while (<${proxmox_pkgdir}/*.deb>) {
89a12446
DM
1710 chomp;
1711 my $path = $_;
97980bf2 1712 my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/;
71590b6a 1713 update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb");
89a12446 1714 print "extracting: $deb\n";
f9b3baff
TL
1715 syscmd("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/pkg/$deb") == 0
1716 || die "installation of package $deb failed\n";
71590b6a 1717 update_progress((++$count)/$pkg_count, 0.5, 0.75);
89a12446
DM
1718 }
1719
3b11dce4
FG
1720 # needed for postfix postinst in case no other NIC is active
1721 syscmd("chroot $targetdir ifup lo");
1722
121ebc59 1723 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a";
89a12446 1724 $count = 0;
71590b6a 1725 run_command($cmd, sub {
89a12446
DM
1726 my $line = shift;
1727 if ($line =~ m/Setting up\s+(\S+)/) {
84d08198 1728 update_progress((++$count)/$pkg_count, 0.75, 0.95, "configuring $1");
89a12446
DM
1729 }
1730 });
968fa90b 1731
89a12446
DM
1732 unlink "$targetdir/etc/mailname";
1733 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
71590b6a 1734 write_config($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
89a12446
DM
1735
1736 # make sure we have all postfix directories
71590b6a 1737 syscmd("chroot $targetdir /usr/sbin/postfix check");
89a12446 1738 # cleanup mail queue
71590b6a 1739 syscmd("chroot $targetdir /usr/sbin/postsuper -d ALL");
29da2d42
SI
1740 # create /etc/aliases.db (/etc/aliases is shipped in the base squashfs)
1741 syscmd("chroot $targetdir /usr/bin/newaliases");
89a12446 1742
6b5dc3d0 1743 # enable NTP (timedatectl set-ntp true does not work without DBUS)
71590b6a 1744 syscmd("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
6b5dc3d0 1745
89a12446
DM
1746 unlink "$targetdir/proxmox_install_mode";
1747
968fa90b 1748 # set timezone
89a12446
DM
1749 unlink ("$targetdir/etc/localtime");
1750 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
71590b6a 1751 write_config("$timezone\n", "$targetdir/etc/timezone");
89a12446 1752
89a12446
DM
1753 # set apt mirror
1754 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1755 my $fn = "$targetdir/etc/apt/sources.list";
71590b6a 1756 syscmd("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
89a12446
DM
1757 }
1758
19edf8b7
DM
1759 # create extended_states for apt (avoid cron job warning if that
1760 # file does not exist)
71590b6a 1761 write_config('', "$targetdir/var/lib/apt/extended_states");
19edf8b7 1762
c2657b8b 1763 # allow ssh root login
abcadb95 1764 syscmd(['sed', '-i', 's/^#\?PermitRootLogin.*/PermitRootLogin yes/', "$targetdir/etc/ssh/sshd_config"]);
861a26d4
DM
1765
1766 if ($setup->{product} eq 'pmg') {
1767 # install initial clamav DB
1768 my $srcdir = "${proxmox_cddir}/proxmox/clamav";
05eb99e2 1769 foreach my $fn ("main.cvd", "bytecode.cvd", "daily.cvd", "safebrowsing.cvd") {
71590b6a 1770 syscmd("cp \"$srcdir/$fn\" \"$targetdir/var/lib/clamav\"") == 0 ||
861a26d4
DM
1771 die "installation of clamav db file '$fn' failed\n";
1772 }
1773 syscmd("chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav") == 0 ||
1774 die "unable to set owner for clamav database files\n";
1775 }
1776
58a09baa
DM
1777 if ($setup->{product} eq 'pve') {
1778 # save installer settings
1779 my $ucc = uc ($country);
1780 debconfig_set($targetdir, "pve-manager pve-manager/country string $ucc\n");
1781 }
89a12446 1782
71590b6a 1783 update_progress(0.8, 0.95, 1, "make system bootable");
89a12446 1784
5c06ced5 1785 if ($use_zfs) {
b13dac4b
FG
1786 # add ZFS options while preserving existing kernel cmdline
1787 my $zfs_snippet = "GRUB_CMDLINE_LINUX=\"\$GRUB_CMDLINE_LINUX root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs\"";
1788 write_config($zfs_snippet, "$targetdir/etc/default/grub.d/zfs.cfg");
4fb6ac60 1789
c9b4369c 1790 write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs\n", "$targetdir/etc/kernel/cmdline");
1464c7c9 1791
5c06ced5 1792 }
23c337f5 1793
71590b6a
OB
1794 diversion_remove($targetdir, "/usr/sbin/update-grub");
1795 diversion_remove($targetdir, "/usr/sbin/update-initramfs");
89a12446 1796
56207f2a
DM
1797 my $kapi;
1798 foreach my $fn (<$targetdir/lib/modules/*>) {
1799 if ($fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$!) {
1800 die "found multiple kernels\n" if defined($kapi);
1801 $kapi = $1;
1802 }
1803 }
1804 die "unable to detect kernel version\n" if !defined($kapi);
1805
c6ed3b24 1806 if (!$opt_testmode) {
89a12446
DM
1807
1808 unlink ("$targetdir/etc/mtab");
1809 symlink ("/proc/mounts", "$targetdir/etc/mtab");
71590b6a 1810 syscmd("mount -n --bind /dev $targetdir/dev");
89a12446 1811
1f8429eb
FG
1812 my $bootloader_err_list = [];
1813 eval {
1814 syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1815 die "unable to install initramfs\n";
1816
5ea943cf
SI
1817 my $native_4k_disk_bootable = 0;
1818 foreach my $di (@$bootdevinfo) {
1819 $native_4k_disk_bootable |= ($di->{logical_bsize} == 4096);
1820 }
1821
1f8429eb
FG
1822 foreach my $di (@$bootdevinfo) {
1823 my $dev = $di->{devname};
d58ef02c
SI
1824 if ($use_zfs) {
1825 prepare_proxmox_boot_esp($di->{esp}, $targetdir);
1826 } else {
1827 if (!$native_4k_disk_bootable) {
1828 eval {
1829 syscmd("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1830 die "unable to install the i386-pc boot loader on '$dev'\n";
1831 };
1832 push @$bootloader_err_list, $@ if $@;
1833 }
1f8429eb
FG
1834
1835 eval {
1836 if (my $esp = $di->{esp}) {
1f8429eb
FG
1837 prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
1838 }
1839 }
1840 };
1841 push @$bootloader_err_list, $@ if $@;
1e61f3d8 1842 }
89a12446 1843
1f8429eb
FG
1844 syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
1845 die "unable to update boot loader config\n";
1f8429eb
FG
1846 };
1847 push @$bootloader_err_list, $@ if $@;
1848
1849 if (scalar(@$bootloader_err_list) > 0) {
1850 $bootloader_err = "bootloader setup errors:\n";
1851 map { $bootloader_err .= "- $_" } @$bootloader_err_list;
1852 warn $bootloader_err;
f2afc0fc 1853 }
03c686b7 1854
71590b6a 1855 syscmd("umount $targetdir/dev");
89a12446
DM
1856 }
1857
968fa90b 1858 # cleanup
89a12446 1859
89a12446
DM
1860 unlink "$targetdir/usr/sbin/policy-rc.d";
1861
71590b6a 1862 diversion_remove($targetdir, "/sbin/start-stop-daemon");
89a12446
DM
1863
1864 # set root password
968fa90b 1865 my $octets = encode("utf-8", $password);
71590b6a
OB
1866 run_command("chroot $targetdir /usr/sbin/chpasswd", undef,
1867 "root:$octets\n");
7053f98b 1868
038552a1 1869 if ($setup->{product} eq 'pmg') {
038552a1 1870 # save admin email
71590b6a
OB
1871 write_config("section: admin\n\temail ${mailto}\n",
1872 "$targetdir/etc/pmg/pmg.conf");
038552a1
DM
1873
1874 } elsif ($setup->{product} eq 'pve') {
7053f98b 1875
8acc47b5 1876 # create pmxcfs DB
7053f98b 1877
8acc47b5
DM
1878 my $tmpdir = "$targetdir/tmp/pve";
1879 mkdir $tmpdir;
7053f98b 1880
8acc47b5
DM
1881 # write vnc keymap to datacenter.cfg
1882 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
71590b6a
OB
1883 write_config("keyboard: $vnckmap\n",
1884 "$tmpdir/datacenter.cfg");
968fa90b 1885
8acc47b5 1886 # save admin email
e32b8d2d 1887 write_config("user:root\@pam:1:0:::${mailto}::\n", "$tmpdir/user.cfg");
5fd81672 1888
8acc47b5 1889 # write storage.cfg
d8c697d4 1890 my $storage_cfg_fn = "$tmpdir/storage.cfg";
8acc47b5 1891 if ($use_zfs) {
71590b6a 1892 write_config($storage_cfg_zfs, $storage_cfg_fn);
8acc47b5 1893 } elsif ($use_btrfs) {
71590b6a 1894 write_config($storage_cfg_btrfs, $storage_cfg_fn);
e2c51d7c 1895 } elsif ($datadev) {
71590b6a 1896 write_config($storage_cfg_lvmthin, $storage_cfg_fn);
e2c51d7c 1897 } else {
71590b6a 1898 write_config($storage_cfg_local, $storage_cfg_fn);
8acc47b5 1899 }
7053f98b 1900
8acc47b5
DM
1901 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1902
71590b6a 1903 syscmd("rm -rf $tmpdir");
ca74501d
TL
1904 } elsif ($setup->{product} eq 'pbs') {
1905 my $base_cfg_path = "/etc/proxmox-backup";
e32b8d2d 1906 mkdir "$targetdir/$base_cfg_path";
341a93b7
TL
1907
1908 chroot_chown($targetdir, $base_cfg_path, user => 'backup', recursive => 1);
1909 chroot_chmod($targetdir, $base_cfg_path, mode => '0700');
e32b8d2d
TL
1910
1911 my $user_cfg_fn = "$base_cfg_path/user.cfg";
1912 write_config("user: root\@pam\n\temail ${mailto}\n", "$targetdir/$user_cfg_fn");
1913 chroot_chown($targetdir, $user_cfg_fn, user => 'root', group => 'backup');
1914 chroot_chmod($targetdir, $user_cfg_fn, mode => '0640');
8acc47b5 1915 }
89a12446
DM
1916 };
1917
1918 my $err = $@;
1919
71590b6a 1920 update_progress(1, 0, 1, "");
89a12446
DM
1921
1922 print $err if $err;
1923
1924 if ($opt_testmode) {
121ebc59
DM
1925 my $elapsed = Time::HiRes::tv_interval($starttime);
1926 print "Elapsed extract time: $elapsed\n";
1927
71590b6a 1928 syscmd("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
89a12446
DM
1929 }
1930
8d7ddbde
TL
1931 syscmd("umount $targetdir/run");
1932 syscmd("umount $targetdir/mnt/hostrun");
f9b3baff 1933 syscmd("umount $targetdir/tmp/pkg");
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
21b2e8ea 2282 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'info', 'ok', $msg);
89a12446
DM
2283 $dialog->run();
2284 $dialog->destroy();
2285}
2286
2287sub display_error {
2288 my ($msg) = @_;
2289
21b2e8ea 2290 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'error', 'ok', $msg);
89a12446
DM
2291 $dialog->run();
2292 $dialog->destroy();
2293}
2294
fe44bd92
FG
2295my $ipconf_first_view = 1;
2296
89a12446
DM
2297sub create_ipconf_view {
2298
201a5120
OB
2299 cleanup_view();
2300 display_html();
89a12446 2301
cc120d79
TL
2302 my $vcontainer = Gtk3::Box->new('vertical', 0);
2303 $inbox->pack_start($vcontainer, 1, 0, 0);
2304 my $hcontainer = Gtk3::Box->new('horizontal', 0);
2305 $vcontainer->pack_start($hcontainer, 0, 0, 10);
2306 my $vbox = Gtk3::Box->new('vertical', 0);
2307 $hcontainer->add($vbox);
89a12446 2308
ebc4f76f 2309 my $ipaddr_text = $config->{ipaddress} // "192.168.100.2";
cc120d79
TL
2310 my $netmask_text = $config->{netmask} // "24";
2311 my $cidr_box;
2312 ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) =
2313 create_cidr_inputs($ipaddr_text, $netmask_text);
fe44bd92
FG
2314
2315 my $device_cb = Gtk3::ComboBoxText->new();
2316 $device_cb->set_active(0);
2317 $device_cb->set_visible(1);
2318
2319 my $get_device_desc = sub {
2320 my $iface = shift;
2321 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
2322 };
2323
2324 my $device_active_map = {};
ebc4f76f 2325 my $device_active_reverse_map = {};
5b6ba737
FG
2326
2327 my $device_change_handler = sub {
2328 my $current = shift;
d6524c52
TL
2329
2330 my $new = $device_active_map->{$current->get_active()};
cd2d2a27 2331 return if defined($ipconf->{selected}) && $new eq $ipconf->{selected};
d6524c52
TL
2332
2333 $ipconf->{selected} = $new;
5b6ba737 2334 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
ebc4f76f 2335 $config->{mngmt_nic} = $iface->{name};
5b6ba737
FG
2336 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2337 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
cc120d79
TL
2338 $ipconf_entry_mask->set_text($iface->{inet}->{prefix} || $iface->{inet6}->{prefix})
2339 if $iface->{inet}->{prefix} || $iface->{inet6}->{prefix};
5b6ba737
FG
2340 };
2341
fe44bd92
FG
2342 my $i = 0;
2343 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2344 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
ebc4f76f
TL
2345 $device_active_map->{$i} = $index;
2346 $device_active_reverse_map->{$ipconf->{ifaces}->{$index}->{name}} = $i;
fe44bd92
FG
2347 if ($ipconf_first_view && $index == $ipconf->{default}) {
2348 $device_cb->set_active($i);
5b6ba737 2349 &$device_change_handler($device_cb);
fe44bd92
FG
2350 $ipconf_first_view = 0;
2351 }
71590b6a 2352 $device_cb->signal_connect('changed' => $device_change_handler);
fe44bd92
FG
2353 $i++;
2354 }
2355
ebc4f76f
TL
2356 if (my $nic = $config->{mngmt_nic}) {
2357 $device_cb->set_active($device_active_reverse_map->{$nic} // 0);
2358 } else {
2359 $device_cb->set_active(0);
2360 }
5b6ba737 2361
71590b6a
OB
2362 my $devicebox = Gtk3::HBox->new(0, 0);
2363 my $label = Gtk3::Label->new("Management Interface:");
2364 $label->set_size_request(150, -1);
2365 $label->set_alignment(1, 0.5);
2366 $devicebox->pack_start($label, 0, 0, 10);
2367 $devicebox->pack_start($device_cb, 0, 0, 0);
fe44bd92 2368
cc120d79 2369 $vbox->pack_start($devicebox, 0, 0, 2);
968fa90b 2370
ebc4f76f 2371 my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
1464c7c9 2372
cc120d79
TL
2373 my ($hostbox, $hostentry) = create_text_input($hn, 'Hostname (FQDN):');
2374 $vbox->pack_start($hostbox, 0, 0, 2);
89a12446 2375
cc120d79 2376 $vbox->pack_start($cidr_box, 0, 0, 2);
89a12446 2377
ebc4f76f 2378 $gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
89a12446
DM
2379
2380 my $gwbox;
d2120e51 2381 ($gwbox, $ipconf_entry_gw) =
71590b6a 2382 create_text_input($gateway, 'Gateway:');
89a12446 2383
cc120d79 2384 $vbox->pack_start($gwbox, 0, 0, 2);
89a12446 2385
ebc4f76f 2386 $dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
89a12446
DM
2387
2388 my $dnsbox;
d2120e51 2389 ($dnsbox, $ipconf_entry_dns) =
71590b6a 2390 create_text_input($dnsserver, 'DNS Server:');
89a12446 2391
cc120d79 2392 $vbox->pack_start($dnsbox, 0, 0, 0);
89a12446
DM
2393
2394 $inbox->show_all;
71590b6a 2395 set_next(undef, sub {
d2120e51
DM
2396
2397 # verify hostname
1464c7c9 2398
89a12446 2399 my $text = $hostentry->get_text();
968fa90b 2400
89a12446
DM
2401 $text =~ s/^\s+//;
2402 $text =~ s/\s+$//;
2403
ebc4f76f
TL
2404 $config->{fqdn} = $text;
2405
ac3757a9 2406 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2407
24973868
WB
2408 # Debian does not support purely numeric hostnames
2409 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2410 display_message("Purely numeric hostnames are not allowed.");
2411 $hostentry->grab_focus();
2412 return;
2413 }
2414
a39bc1f2 2415 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2416 $text =~ m/^([^\.]+)\.(\S+)$/) {
2417 $hostname = $1;
2418 $domain = $2;
d2120e51 2419 } else {
71590b6a 2420 display_message("Hostname does not look like a fully qualified domain name.");
d2120e51 2421 $hostentry->grab_focus();
89a12446
DM
2422 return;
2423 }
d2120e51
DM
2424
2425 # verify ip address
2426
2427 $text = $ipconf_entry_addr->get_text();
2428 $text =~ s/^\s+//;
2429 $text =~ s/\s+$//;
2430 if ($text =~ m!^($IPV4RE)$!) {
2431 $ipaddress = $text;
b6200603
DM
2432 $ipversion = 4;
2433 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2434 $ipaddress = $text;
b6200603 2435 $ipversion = 6;
d2120e51 2436 } else {
71590b6a 2437 display_message("IP address is not valid.");
d2120e51
DM
2438 $ipconf_entry_addr->grab_focus();
2439 return;
2440 }
ebc4f76f 2441 $config->{ipaddress} = $ipaddress;
d2120e51
DM
2442
2443 $text = $ipconf_entry_mask->get_text();
2444 $text =~ s/^\s+//;
2445 $text =~ s/\s+$//;
cc120d79 2446 if ($ipversion == 6 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 126) {
b6200603 2447 $netmask = $text;
cc120d79 2448 } elsif ($ipversion == 4 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 32) {
d2120e51 2449 $netmask = $text;
cc120d79
TL
2450 } elsif ($ipversion == 4 && defined($ipv4_mask_hash->{$text})) {
2451 # costs nothing to handle 255.x.y.z style masks, so continue to allow it
2452 $netmask = $ipv4_mask_hash->{$text};
d2120e51 2453 } else {
71590b6a 2454 display_message("Netmask is not valid.");
d2120e51
DM
2455 $ipconf_entry_mask->grab_focus();
2456 return;
2457 }
cc120d79 2458 $cidr = "$ipaddress/$netmask";
ebc4f76f 2459 $config->{netmask} = $netmask;
d2120e51
DM
2460
2461 $text = $ipconf_entry_gw->get_text();
2462 $text =~ s/^\s+//;
2463 $text =~ s/\s+$//;
b6200603
DM
2464 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2465 $gateway = $text;
2466 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2467 $gateway = $text;
2468 } else {
71590b6a 2469 display_message("Gateway is not valid.");
d2120e51
DM
2470 $ipconf_entry_gw->grab_focus();
2471 return;
2472 }
ebc4f76f 2473 $config->{gateway} = $gateway;
1464c7c9 2474
d2120e51
DM
2475 $text = $ipconf_entry_dns->get_text();
2476 $text =~ s/^\s+//;
2477 $text =~ s/\s+$//;
b6200603
DM
2478 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2479 $dnsserver = $text;
2480 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2481 $dnsserver = $text;
2482 } else {
71590b6a 2483 display_message("DNS server is not valid.");
d2120e51
DM
2484 $ipconf_entry_dns->grab_focus();
2485 return;
2486 }
ebc4f76f 2487 $config->{dnsserver} = $dnsserver;
1464c7c9 2488
d2120e51 2489 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2490
201a5120 2491 $step_number++;
2e33c3f0 2492 create_ack_view();
89a12446
DM
2493 });
2494
2495 $hostentry->grab_focus();
2496}
2497
2e33c3f0
OB
2498sub create_ack_view {
2499
2500 cleanup_view();
2501
dfc02f3c
TL
2502 my $vbox = Gtk3::VBox->new(0, 0);
2503 $inbox->pack_start($vbox, 1, 0, 0);
dfc02f3c
TL
2504
2505 my $reboot_checkbox = Gtk3::CheckButton->new('Automatically reboot after successful installation');
2506 $reboot_checkbox->set_active(1);
2507 $reboot_checkbox->signal_connect ("toggled" => sub {
2508 my $cb = shift;
2509 $config_options->{autoreboot} = $cb->get_active();
2510 });
2511 $vbox->pack_start($reboot_checkbox, 0, 0, 2);
2512
029fde30 2513 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
c2f72dd6 2514 my $ack_html = "${proxmox_libdir}/html/$setup->{product}/$steps[$step_number]->{html}";
2e33c3f0
OB
2515 my $html_data = file_get_contents($ack_template);
2516
2517 my %config_values = (
a7d40341 2518 __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
0470018e 2519 __target_fs__ => $config_options->{filesys},
0ddd2227 2520 __country__ => $cmap->{country}->{$country}->{name},
2e33c3f0
OB
2521 __timezone__ => $timezone,
2522 __keymap__ => $keymap,
2523 __mailto__ => $mailto,
2524 __interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
2525 __hostname__ => $hostname,
2526 __ip__ => $ipaddress,
b1838e1e 2527 __cidr__ => $cidr,
2e33c3f0
OB
2528 __netmask__ => $netmask,
2529 __gateway__ => $gateway,
2530 __dnsserver__ => $dnsserver,
2531 );
2532
029fde30 2533 while (my ($k, $v) = each %config_values) {
2e33c3f0
OB
2534 $html_data =~ s/$k/$v/g;
2535 }
2536
2537 write_config($html_data, $ack_html);
2538
2539 display_html();
2540
dfc02f3c
TL
2541 $inbox->show_all;
2542
2e33c3f0
OB
2543 set_next(undef, sub {
2544 $step_number++;
2545 create_extract_view();
2546 });
2547}
2548
89a12446
DM
2549sub get_device_desc {
2550 my ($devname, $size, $model) = @_;
2551
d2120e51 2552 if ($size && ($size > 0)) {
b04864ec 2553 $size = int($size/2048); # size in MiB, from 512B "sectors"
89a12446 2554
d2120e51 2555 my $text = "$devname (";
89a12446 2556 if ($size >= 1024) {
b04864ec 2557 $size = $size/1024; # size in GiB
ceabb291 2558 if ($size >= 1024) {
b04864ec
SI
2559 $size = $size/1024; # size in TiB
2560 $text .= sprintf("%.2f", $size) . "TiB";
ceabb291 2561 } else {
b04864ec 2562 $text .= sprintf("%.2f", $size) . "GiB";
ceabb291 2563 }
89a12446 2564 } else {
ceabb291 2565 $text .= "${size}MiB";
89a12446
DM
2566 }
2567
d2120e51
DM
2568 $text .= ", $model" if $model;
2569 $text .= ")";
b04864ec 2570 return $text;
d2120e51 2571
89a12446
DM
2572 } else {
2573 return $devname;
2574 }
2575}
2576
d92ada4e
SI
2577my $last_layout;
2578my $country_layout;
89a12446
DM
2579sub update_layout {
2580 my ($cb, $kmap) = @_;
2581
2582 my $ind;
2583 my $def;
2584 my $i = 0;
2585 my $kmaphash = $cmap->{kmaphash};
2586 foreach my $layout (sort keys %$kmaphash) {
2587 $def = $i if $kmaphash->{$layout} eq 'en-us';
2588 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2589 $i++;
2590 }
2591
d92ada4e
SI
2592 my $val = $ind || $def || 0;
2593
2594 if (!defined($kmap)) {
2595 $last_layout //= $val;
2596 } elsif (!defined($country_layout) || $country_layout != $val) {
2597 $last_layout = $country_layout = $val;
2598 }
2599 $cb->set_active($last_layout);
89a12446
DM
2600}
2601
2602my $lastzonecb;
2603sub update_zonelist {
2604 my ($box, $cc) = @_;
2605
2606 my $cczones = $cmap->{cczones};
2607 my $zones = $cmap->{zones};
2608
2609 my $sel;
2610 if ($lastzonecb) {
2611 $sel = $lastzonecb->get_active_text();
2612 $box->remove ($lastzonecb);
2613 } else {
2614 $sel = $timezone; # used once to select default
2615 }
2616
bcbfab6b 2617 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
71590b6a 2618 $cb->set_size_request(200, -1);
89a12446 2619
71590b6a 2620 $cb->signal_connect('changed' => sub {
89a12446
DM
2621 $timezone = $cb->get_active_text();
2622 });
2623
2624 my @za;
2625 if ($cc && defined ($cczones->{$cc})) {
2626 @za = keys %{$cczones->{$cc}};
2627 } else {
2628 @za = keys %$zones;
2629 }
2630 my $ind;
2631 my $i = 0;
2632 foreach my $zone (sort @za) {
2633 $ind = $i if $sel && $zone eq $sel;
71590b6a 2634 $cb->append_text($zone);
89a12446
DM
2635 $i++;
2636 }
2637
71590b6a 2638 $cb->set_active($ind || 0);
89a12446
DM
2639
2640 $cb->show;
71590b6a 2641 $box->pack_start($cb, 0, 0, 0);
89a12446
DM
2642}
2643
2644sub create_password_view {
2645
71590b6a 2646 cleanup_view();
89a12446 2647
71590b6a
OB
2648 my $vbox2 = Gtk3::VBox->new(0, 0);
2649 $inbox->pack_start($vbox2, 1, 0, 0);
2650 my $vbox = Gtk3::VBox->new(0, 0);
2651 $vbox2->pack_start($vbox, 0, 0, 10);
2652
2653 my $hbox1 = Gtk3::HBox->new(0, 0);
2654 my $label = Gtk3::Label->new("Password");
2655 $label->set_size_request(150, -1);
2656 $label->set_alignment(1, 0.5);
2657 $hbox1->pack_start($label, 0, 0, 10);
2658 my $pwe1 = Gtk3::Entry->new();
2659 $pwe1->set_visibility(0);
201a5120 2660 $pwe1->set_text($password) if $password;
71590b6a
OB
2661 $pwe1->set_size_request(200, -1);
2662 $hbox1->pack_start($pwe1, 0, 0, 0);
2663
2664 my $hbox2 = Gtk3::HBox->new(0, 0);
2665 $label = Gtk3::Label->new("Confirm");
2666 $label->set_size_request(150, -1);
2667 $label->set_alignment(1, 0.5);
2668 $hbox2->pack_start($label, 0, 0, 10);
2669 my $pwe2 = Gtk3::Entry->new();
2670 $pwe2->set_visibility(0);
201a5120 2671 $pwe2->set_text($password) if $password;
71590b6a
OB
2672 $pwe2->set_size_request(200, -1);
2673 $hbox2->pack_start($pwe2, 0, 0, 0);
2674
2675 my $hbox3 = Gtk3::HBox->new(0, 0);
b11c55ff 2676 $label = Gtk3::Label->new("Email");
71590b6a
OB
2677 $label->set_size_request(150, -1);
2678 $label->set_alignment(1, 0.5);
2679 $hbox3->pack_start($label, 0, 0, 10);
2680 my $eme = Gtk3::Entry->new();
2681 $eme->set_size_request(200, -1);
201a5120 2682 $eme->set_text($mailto);
71590b6a 2683 $hbox3->pack_start($eme, 0, 0, 0);
89a12446
DM
2684
2685
71590b6a
OB
2686 $vbox->pack_start($hbox1, 0, 0, 5);
2687 $vbox->pack_start($hbox2, 0, 0, 5);
2688 $vbox->pack_start($hbox3, 0, 0, 15);
89a12446
DM
2689
2690 $inbox->show_all;
2691
201a5120 2692 display_html();
89a12446
DM
2693
2694 set_next (undef, sub {
2695
2696 my $t1 = $pwe1->get_text;
2697 my $t2 = $pwe2->get_text;
2698
2699 if (length ($t1) < 5) {
71590b6a 2700 display_message("Password is too short.");
89a12446
DM
2701 $pwe1->grab_focus();
2702 return;
2703 }
2704
2705 if ($t1 ne $t2) {
71590b6a 2706 display_message("Password does not match.");
89a12446
DM
2707 $pwe1->grab_focus();
2708 return;
2709 }
2710
2711 my $t3 = $eme->get_text;
c82fffd8 2712 if ($t3 !~ m/^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
b11c55ff 2713 display_message("Email does not look like a valid address" .
89a12446
DM
2714 " (user\@domain.tld)");
2715 $eme->grab_focus();
2716 return;
a39bc1f2 2717 }
89a12446 2718
a39bc1f2 2719 if ($t3 eq 'mail@example.invalid') {
b11c55ff 2720 display_message("Please enter a valid Email address");
a39bc1f2
FG
2721 $eme->grab_focus();
2722 return;
89a12446
DM
2723 }
2724
2725 $password = $t1;
2726 $mailto = $t3;
2727
201a5120 2728 $step_number++;
89a12446
DM
2729 create_ipconf_view();
2730 });
2731
2732 $pwe1->grab_focus();
2733
2734}
2735
d92ada4e 2736my $installer_kmap;
89a12446
DM
2737sub create_country_view {
2738
71590b6a 2739 cleanup_view();
89a12446
DM
2740
2741 my $countryhash = $cmap->{countryhash};
2742 my $ctr = $cmap->{country};
2743
71590b6a
OB
2744 my $vbox2 = Gtk3::VBox->new(0, 0);
2745 $inbox->pack_start($vbox2, 1, 0, 0);
2746 my $vbox = Gtk3::VBox->new(0, 0);
2747 $vbox2->pack_start($vbox, 0, 0, 10);
89a12446 2748
71590b6a
OB
2749 my $w = Gtk3::Entry->new();
2750 $w->set_size_request(200, -1);
89a12446 2751
71590b6a
OB
2752 my $c = Gtk3::EntryCompletion->new();
2753 $c->set_text_column(0);
89a12446 2754 $c->set_minimum_key_length(0);
71590b6a
OB
2755 $c->set_popup_set_width(1);
2756 $c->set_inline_completion(1);
2757
2758 my $hbox2 = Gtk3::HBox->new(0, 0);
2759 my $label = Gtk3::Label->new("Time zone");
2760 $label->set_size_request(150, -1);
2761 $label->set_alignment(1, 0.5);
2762 $hbox2->pack_start($label, 0, 0, 10);
89a12446
DM
2763 update_zonelist ($hbox2);
2764
71590b6a
OB
2765 my $hbox3 = Gtk3::HBox->new(0, 0);
2766 $label = Gtk3::Label->new("Keyboard Layout");
2767 $label->set_size_request(150, -1);
2768 $label->set_alignment(1, 0.5);
2769 $hbox3->pack_start($label, 0, 0, 10);
89a12446 2770
bcbfab6b 2771 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2772 $kmapcb->set_size_request (200, -1);
2773 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2774 $kmapcb->append_text ($layout);
2775 }
2776
71590b6a 2777 update_layout($kmapcb);
89a12446
DM
2778 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2779
2780 $kmapcb->signal_connect ('changed' => sub {
2781 my $sel = $kmapcb->get_active_text();
d92ada4e 2782 $last_layout = $kmapcb->get_active();
89a12446
DM
2783 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2784 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2785 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
89a12446 2786 $keymap = $kmap;
07ec6825 2787
d92ada4e
SI
2788 return if (defined($installer_kmap) && $installer_kmap eq $kmap);
2789
2790 $installer_kmap = $keymap;
2791
07ec6825
SI
2792 if (! $opt_testmode) {
2793 syscmd ("setxkbmap $xkmap $xvar");
2663e171
SI
2794
2795 my $kbd_config = qq{
2796 XKBLAYOUT="$xkmap"
2797 XKBVARIANT="$xvar"
2798 BACKSPACE="guess"
2799 };
2800 $kbd_config =~ s/^\s+//gm;
2801
2802 run_in_background( sub {
2803 write_config($kbd_config, '/etc/default/keyboard');
2804 system("setupcon");
2805 });
07ec6825 2806 }
89a12446
DM
2807 }
2808 });
2809
2810 $w->signal_connect ('changed' => sub {
2811 my ($entry, $event) = @_;
2812 my $text = $entry->get_text;
2813
2814 if (my $cc = $countryhash->{lc($text)}) {
71590b6a 2815 update_zonelist($hbox2, $cc);
89a12446 2816 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
71590b6a 2817 update_layout($kmapcb, $kmap);
89a12446
DM
2818 }
2819 });
2820
2821 $w->signal_connect (key_press_event => sub {
2822 my ($entry, $event) = @_;
2823 my $text = $entry->get_text;
2824
7becc472
DM
2825 my $val = $event->get_keyval;
2826
2827 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2828 my $cc = $countryhash->{lc($text)};
1464c7c9 2829
89a12446
DM
2830 my $found = 0;
2831 my $compl;
7becc472 2832
4443aa27
DM
2833 if ($cc) {
2834 $found = 1;
2835 $compl = $ctr->{$cc}->{name};
2836 } else {
2837 foreach my $cc (keys %$ctr) {
2838 my $ct = $ctr->{$cc}->{name};
2839 if ($ct =~ m/^\Q$text\E.*$/i) {
2840 $found++;
2841 $compl = $ct;
2842 }
2843 last if $found > 1;
89a12446 2844 }
89a12446 2845 }
4443aa27 2846
89a12446 2847 if ($found == 1) {
7becc472 2848 $entry->set_text($compl);
3df718ea 2849 $c->complete();
89a12446
DM
2850 return undef;
2851 } else {
7becc472
DM
2852 #Gtk3::Gdk::beep();
2853 print chr(7); # beep ?
89a12446
DM
2854 }
2855
3df718ea
DM
2856 $c->complete();
2857
7becc472
DM
2858 my $buf = $w->get_buffer();
2859 $buf->insert_text(-1, '', -1); # popup selection
2860
89a12446
DM
2861 return 1;
2862 }
2863
2864 return undef;
2865 });
1464c7c9 2866
7becc472 2867 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2868 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2869 my $iter = $ls->append();
2870 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2871 }
2872 $c->set_model ($ls);
2873
968fa90b 2874 $w->set_completion ($c);
89a12446 2875
71590b6a 2876 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 2877
71590b6a
OB
2878 $label = Gtk3::Label->new("Country");
2879 $label->set_alignment(1, 0.5);
2880 $label->set_size_request(150, -1);
2881 $hbox->pack_start($label, 0, 0, 10);
2882 $hbox->pack_start($w, 0, 0, 0);
89a12446 2883
71590b6a
OB
2884 $vbox->pack_start($hbox, 0, 0, 5);
2885 $vbox->pack_start($hbox2, 0, 0, 5);
2886 $vbox->pack_start($hbox3, 0, 0, 5);
89a12446 2887
9d1f1ee3 2888 if ($country && $ctr->{$country}) {
89a12446
DM
2889 $w->set_text ($ctr->{$country}->{name});
2890 }
2891
2892 $inbox->show_all;
2893
201a5120 2894 display_html();
89a12446
DM
2895 set_next (undef, sub {
2896
2897 my $text = $w->get_text;
2898
2899 if (my $cc = $countryhash->{lc($text)}) {
2900 $country = $cc;
201a5120 2901 $step_number++;
89a12446
DM
2902 create_password_view();
2903 return;
2904 } else {
71590b6a 2905 display_message("Please select a country first.");
89a12446
DM
2906 $w->grab_focus();
2907 }
2908 });
2909
2910 $w->grab_focus();
2911}
2912
c6ed3b24
DM
2913my $target_hd_combo;
2914my $target_hd_label;
2915
bd3a2e26 2916my $hdoption_first_setup = 1;
c6ed3b24 2917
c7779156
FG
2918my $create_basic_grid = sub {
2919 my $grid = Gtk3::Grid->new();
2920 $grid->set_visible(1);
2921 $grid->set_column_spacing(10);
2922 $grid->set_row_spacing(10);
2923 $grid->set_hexpand(1);
2924
6d8b8564
TL
2925 $grid->set_margin_start(10);
2926 $grid->set_margin_end(20);
c7779156
FG
2927 $grid->set_margin_top(5);
2928 $grid->set_margin_bottom(5);
2929
2930 return $grid;
2931};
2932
2933my $create_label_widget_grid = sub {
2934 my ($labeled_widgets) = @_;
2935
2936 my $grid = &$create_basic_grid();
2937 my $row = 0;
2938
2939 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2940 my $widget = @$labeled_widgets[$i+1];
2941 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2942 $label->set_visible(1);
2943 $label->set_alignment (1, 0.5);
2944 $grid->attach($label, 0, $row, 1, 1);
2945 $widget->set_visible(1);
2946 $grid->attach($widget, 1, $row, 1, 1);
2947 $row++;
2948 }
2949
2950 return $grid;
2951};
2952
329a65a5 2953# only relevant for raid with its multipl diskX to diskY mappings.
c07739f4
SI
2954my $get_hdsize_adjustment = sub {
2955 my $hdsize = shift;
329a65a5
TL
2956 return $hdsize if defined($hdsize);
2957
2958 # compute the smallest disk size of the actually selected disks
2959 my $hd_count = scalar(@$hds);
2960 for (my $i = 0; $i < $hd_count; $i++) {
2961 my $cur_hd = $config_options->{"disksel$i"} // next;
2962 my $disksize = int(@$cur_hd[2] / (2 * 1024 * 1024.0)); # size in GB
2963 $hdsize //= $disksize;
2964 $hdsize = $disksize if $disksize < $hdsize;
c07739f4 2965 }
329a65a5 2966 $hdsize //= 0; # fall back to zero, e.g., if none is selected hdsize cannot be any size
c07739f4
SI
2967
2968 return Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
2969};
2970
e2b003a6 2971my sub get_hdsize_spin_button {
c07739f4
SI
2972 my $hdsize = shift;
2973
2974 my $hdsize_entry_buffer = Gtk3::EntryBuffer->new(undef, 1);
2975 my $hdsize_size_adj = $get_hdsize_adjustment->($hdsize);
2976
2977 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2978 $spinbutton_hdsize->set_buffer($hdsize_entry_buffer);
2979 $spinbutton_hdsize->set_adjustment($hdsize_size_adj);
2980 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2981 return $spinbutton_hdsize;
2982};
2983
c7779156 2984my $create_raid_disk_grid = sub {
679c813c 2985 my ($hdsize_buttons) = @_;
c2ca8ba8 2986
04fa0758 2987 my $hd_count = scalar(@$hds);
c7779156 2988 my $disk_labeled_widgets = [];
04fa0758 2989 for (my $i = 0; $i < $hd_count; $i++) {
c7779156
FG
2990 my $disk_selector = Gtk3::ComboBoxText->new();
2991 $disk_selector->append_text("-- do not use --");
2992 $disk_selector->set_active(0);
2993 $disk_selector->set_visible(1);
c2ca8ba8 2994
c7779156 2995 foreach my $hd (@$hds) {
17fd908e 2996 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c7779156 2997 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
c7779156
FG
2998 }
2999
8d1ca71a
TL
3000 $disk_selector->{pve_disk_id} = $i;
3001 $disk_selector->signal_connect(changed => sub {
3002 my $w = shift;
3003 my $diskid = $w->{pve_disk_id};
3004 my $a = $w->get_active - 1;
3005 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
3006 my $hdsize_adj = $get_hdsize_adjustment->();
3007 for my $btn (@$hdsize_buttons) {
3008 $btn->set_adjustment($hdsize_adj);
3009 }
3010 });
3011
bd3a2e26 3012 if ($hdoption_first_setup) {
c7779156
FG
3013 $disk_selector->set_active ($i+1) if $hds->[$i];
3014 } else {
3015 my $hdind = 0;
3016 if (my $cur_hd = $config_options->{"disksel$i"}) {
3017 foreach my $hd (@$hds) {
3018 if (@$hd[1] eq @$cur_hd[1]) {
3019 $disk_selector->set_active($hdind+1);
3020 last;
3021 }
3022 $hdind++;
3023 }
3024 }
3025 }
3026
3027 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
3028 }
3029
3235f39b
TL
3030 my $clear_all_button = Gtk3::Button->new('_Deselect All');
3031 if ($hd_count > 3) {
3032 $clear_all_button->signal_connect('clicked', sub {
3033 my $is_widget = 0;
3034 for my $disk_selector (@$disk_labeled_widgets) {
3035 $disk_selector->set_active(0) if $is_widget;
3036 $is_widget ^= 1;
3037 }
3038 });
3039 $clear_all_button->set_visible(1);
3040 }
3041
c7779156
FG
3042 my $scrolled_window = Gtk3::ScrolledWindow->new();
3043 $scrolled_window->set_hexpand(1);
04fa0758 3044 $scrolled_window->set_propagate_natural_height(1) if $hd_count > 4;
3235f39b
TL
3045
3046 my $diskgrid = $create_label_widget_grid->($disk_labeled_widgets);
3047
3048 $scrolled_window->add($diskgrid);
c7779156 3049 $scrolled_window->set_policy('never', 'automatic');
3235f39b 3050 $scrolled_window->set_visible(1);
0bc39c50 3051 $scrolled_window->set_min_content_height(190);
3235f39b
TL
3052
3053 my $vbox = Gtk3::Box->new('vertical', 0);
3054 $vbox->pack_start($scrolled_window, 1, 1, 10);
3055
3056 my $hbox = Gtk3::Box->new('horizontal', 0);
3057 $hbox->pack_end($clear_all_button, 0, 0, 20);
3058 $hbox->set_visible(1);
3059 $vbox->pack_end($hbox, 0, 0, 0);
c7779156 3060
3235f39b 3061 return $vbox;
c7779156
FG
3062};
3063
3064my $create_raid_advanced_grid = sub {
c07739f4 3065 my ($hdsize_btn) = @_;
c7779156 3066 my $labeled_widgets = [];
2cdba397 3067 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9, 13, 1);
6c99667a
FG
3068 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
3069 $spinbutton_ashift->signal_connect ("value-changed" => sub {
3070 my $w = shift;
3071 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
3072 });
3073 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 3074 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 3075 push @$labeled_widgets, "ashift";
6c99667a 3076 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
3077
3078 my $combo_compress = Gtk3::ComboBoxText->new();
3079 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
38aa09be 3080 my $comp_opts = ["on","off","lzjb","lz4", "lze", "gzip", "zstd"];
c7779156
FG
3081 foreach my $opt (@$comp_opts) {
3082 $combo_compress->append($opt, $opt);
3083 }
3084 $config_options->{compress} = "on" if !defined($config_options->{compress});
3085 $combo_compress->set_active_id($config_options->{compress});
3086 $combo_compress->signal_connect (changed => sub {
3087 my $w = shift;
3088 $config_options->{compress} = $w->get_active_text();
3089 });
3090 push @$labeled_widgets, "compress";
3091 push @$labeled_widgets, $combo_compress;
3092
3093 my $combo_checksum = Gtk3::ComboBoxText->new();
3094 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
3095 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
3096 foreach my $opt (@$csum_opts) {
3097 $combo_checksum->append($opt, $opt);
3098 }
3099 $config_options->{checksum} = "on" if !($config_options->{checksum});
3100 $combo_checksum->set_active_id($config_options->{checksum});
3101 $combo_checksum->signal_connect (changed => sub {
3102 my $w = shift;
3103 $config_options->{checksum} = $w->get_active_text();
3104 });
3105 push @$labeled_widgets, "checksum";
3106 push @$labeled_widgets, $combo_checksum;
3107
3108 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
3109 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
3110 $spinbutton_copies->signal_connect ("value-changed" => sub {
3111 my $w = shift;
3112 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
3113 });
3114 $config_options->{copies} = 1 if !defined($config_options->{copies});
3115 $spinbutton_copies->set_value($config_options->{copies});
3116 push @$labeled_widgets, "copies", $spinbutton_copies;
3117
c07739f4 3118 push @$labeled_widgets, "hdsize", $hdsize_btn;
2cdba397 3119 return $create_label_widget_grid->($labeled_widgets);;
c7779156
FG
3120};
3121
679c813c
SI
3122my $create_btrfs_raid_advanced_grid = sub {
3123 my ($hdsize_btn) = @_;
3124 my $labeled_widgets = [];
3125 push @$labeled_widgets, "hdsize", $hdsize_btn;
3126 return $create_label_widget_grid->($labeled_widgets);;
3127};
3128
aed81ff0 3129sub create_hdoption_view {
aed81ff0
DM
3130 my $dialog = Gtk3::Dialog->new();
3131
3132 $dialog->set_title("Harddisk options");
3133
3134 $dialog->add_button("_OK", 1);
3135
3136 my $contarea = $dialog->get_content_area();
3137
3138 my $hbox2 = Gtk3::Box->new('horizontal', 0);
6d8b8564 3139 $contarea->pack_start($hbox2, 1, 1, 5);
aed81ff0
DM
3140
3141 my $grid = Gtk3::Grid->new();
3142 $grid->set_column_spacing(10);
3143 $grid->set_row_spacing(10);
1464c7c9 3144
6d8b8564 3145 $hbox2->pack_start($grid, 1, 0, 5);
c6ed3b24
DM
3146
3147 my $row = 0;
3148
aed81ff0 3149 # Filesystem type
71590b6a 3150 my $label0 = Gtk3::Label->new("Filesystem");
aed81ff0 3151 $label0->set_alignment (1, 0.5);
c6ed3b24 3152 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 3153
bcbfab6b 3154 my $fstypecb = Gtk3::ComboBoxText->new();
2cdba397
TL
3155 my $fstype = [
3156 'ext4',
3157 'xfs',
3158 'zfs (RAID0)',
3159 'zfs (RAID1)',
3160 'zfs (RAID10)',
3161 'zfs (RAIDZ-1)',
3162 'zfs (RAIDZ-2)',
3163 'zfs (RAIDZ-3)',
3164 ];
6f52fc3d 3165 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 3166 if $setup->{enable_btrfs};
aed81ff0 3167
c6ed3b24
DM
3168 my $tcount = 0;
3169 foreach my $tmp (@$fstype) {
3170 $fstypecb->append_text($tmp);
2cdba397 3171 $fstypecb->set_active ($tcount) if $config_options->{filesys} eq $tmp;
c6ed3b24
DM
3172 $tcount++;
3173 }
3174
3175 $grid->attach($fstypecb, 1, $row, 1, 1);
3176
3177 $hbox2->show_all();
3178
3179 $row++;
3180
c7779156
FG
3181 my $sep = Gtk3::HSeparator->new();
3182 $sep->set_visible(1);
3183 $grid->attach($sep, 0, $row, 2, 1);
3184 $row++;
aed81ff0 3185
af35966c 3186 my $hw_raid_note = Gtk3::Label->new(""); # text will be set below, before making it visible
f0a0d90b
TL
3187 $hw_raid_note->set_line_wrap(1);
3188 $hw_raid_note->set_max_width_chars(30);
f0a0d90b
TL
3189 $hw_raid_note->set_visible(0);
3190 $grid->attach($hw_raid_note, 0, $row++, 2, 1);
3191
c7779156 3192 my $hdsize_labeled_widgets = [];
aed81ff0 3193
c7779156 3194 # size compute
c6ed3b24 3195 my $hdsize = 0;
aed81ff0 3196 if ( -b $target_hd) {
c2ca8ba8 3197 $hdsize = int(hd_size ($target_hd) / (1024 * 1024.0)); # size in GB
c6ed3b24 3198 } elsif ($target_hd) {
c2ca8ba8 3199 $hdsize = int((-s $target_hd) / (1024 * 1024 * 1024.0));
aed81ff0
DM
3200 }
3201
e2b003a6 3202 my $spinbutton_hdsize_nonraid = get_hdsize_spin_button($hdsize);
c07739f4
SI
3203 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize_nonraid;
3204 my $spinbutton_hdsize = $spinbutton_hdsize_nonraid;
aed81ff0
DM
3205
3206 my $entry_swapsize = Gtk3::Entry->new();
3207 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
3208 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 3209 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 3210 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
3211
3212 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
3213 if ($setup->{product} eq 'pve') {
3214 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
3215 $entry_maxroot->signal_connect (key_press_event => \&check_float);
3216 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
3217 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
3218 }
aed81ff0
DM
3219
3220 my $entry_minfree = Gtk3::Entry->new();
034f75e4 3221 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0 3222 $entry_minfree->signal_connect (key_press_event => \&check_float);
e093944c 3223 $entry_minfree->set_text($config_options->{minfree}) if defined($config_options->{minfree});
c7779156 3224 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 3225
b6e875ca
DM
3226 my $entry_maxvz;
3227 if ($setup->{product} eq 'pve') {
3228 $entry_maxvz = Gtk3::Entry->new();
3229 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
3230 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 3231 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
3232 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
3233 }
c7779156 3234
e2b003a6
TL
3235 my $spinbutton_hdsize_zfs = get_hdsize_spin_button($hdsize);
3236 my $spinbutton_hdsize_btrfs = get_hdsize_spin_button($hdsize);
679c813c 3237 my $hdsize_buttons = [ $spinbutton_hdsize_zfs, $spinbutton_hdsize_btrfs ];
c7779156
FG
3238 my $options_stack = Gtk3::Stack->new();
3239 $options_stack->set_visible(1);
3240 $options_stack->set_hexpand(1);
3241 $options_stack->set_vexpand(1);
679c813c 3242 $options_stack->add_titled(&$create_raid_disk_grid($hdsize_buttons), "raiddisk", "Disk Setup");
c7779156 3243 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
c07739f4 3244 $options_stack->add_titled(&$create_raid_advanced_grid($spinbutton_hdsize_zfs), "raidzfsadvanced", "Advanced Options");
679c813c 3245 $options_stack->add_titled(&$create_btrfs_raid_advanced_grid($spinbutton_hdsize_btrfs), "raidbtrfsadvanced", "Advanced Options");
c7779156
FG
3246 $options_stack->set_visible_child_name("raiddisk");
3247 my $options_stack_switcher = Gtk3::StackSwitcher->new();
3248 $options_stack_switcher->set_halign('center');
3249 $options_stack_switcher->set_stack($options_stack);
3250 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
3251 $row++;
3252 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 3253 $row++;
aed81ff0 3254
bd3a2e26 3255 $hdoption_first_setup = 0;
c7779156
FG
3256
3257 my $switch_view = sub {
3258 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
af35966c 3259 my $is_zfs = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 3260
c7779156
FG
3261 $target_hd_combo->set_visible(!$raid);
3262 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
3263 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
af35966c
TL
3264
3265 if ($raid) {
3266 my $msg = "<b>Note</b>: " . ($is_zfs
3267 ? "ZFS is not compatible with hardware RAID controllers, for details see the documentation."
78164ad6 3268 : "BTRFS integration in $setup->{fullname} is a technology preview!"
af35966c
TL
3269 );
3270 $hw_raid_note->set_markup($msg);
3271 }
f0a0d90b 3272 $hw_raid_note->set_visible($raid);
679c813c 3273 $options_stack_switcher->set_visible($raid);
af35966c 3274 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($is_zfs);
679c813c 3275 $options_stack->get_child_by_name("raidbtrfsadvanced")->set_visible(!$is_zfs);
c7779156 3276 if ($raid) {
c6ed3b24 3277 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 3278 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 3279 } else {
c6ed3b24
DM
3280 $target_hd_label->set_text("Target Harddisk: ");
3281 }
c07739f4
SI
3282
3283 if ($raid) {
679c813c 3284 $spinbutton_hdsize = $is_zfs ? $spinbutton_hdsize_zfs : $spinbutton_hdsize_btrfs;
c07739f4
SI
3285 } else {
3286 $spinbutton_hdsize = $spinbutton_hdsize_nonraid;
3287 }
3288
c7779156
FG
3289 my (undef, $pref_width) = $dialog->get_preferred_width();
3290 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 3291 $pref_height = 750 if $pref_height > 750;
c7779156 3292 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
3293 };
3294
c7779156 3295 &$switch_view();
f7b853d1
DM
3296
3297 $fstypecb->signal_connect (changed => sub {
3298 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 3299 &$switch_view();
f7b853d1
DM
3300 });
3301
95844cc6
TL
3302 my $sep2 = Gtk3::HSeparator->new();
3303 $sep2->set_visible(1);
3304 $contarea->pack_end($sep2, 1, 1, 10);
3305
c6ed3b24 3306 $dialog->show();
aed81ff0
DM
3307
3308 $dialog->run();
3309
3310 my $get_float = sub {
3311 my ($entry) = @_;
3312
3313 my $text = $entry->get_text();
3314 return undef if !defined($text);
3315
3316 $text =~ s/^\s+//;
3317 $text =~ s/\s+$//;
3318
3319 return undef if $text !~ m/^\d+(\.\d+)?$/;
3320
3321 return $text;
3322 };
3323
3324 my $tmp;
3325
3326 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
3327 $config_options->{hdsize} = $tmp;
3328 } else {
3329 delete $config_options->{hdsize};
3330 }
3331
3332 if (defined($tmp = &$get_float($entry_swapsize))) {
3333 $config_options->{swapsize} = $tmp;
3334 } else {
3335 delete $config_options->{swapsize};
3336 }
3337
3338 if (defined($tmp = &$get_float($entry_maxroot))) {
3339 $config_options->{maxroot} = $tmp;
3340 } else {
3341 delete $config_options->{maxroot};
3342 }
3343
3344 if (defined($tmp = &$get_float($entry_minfree))) {
3345 $config_options->{minfree} = $tmp;
3346 } else {
3347 delete $config_options->{minfree};
3348 }
3349
b6e875ca 3350 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
3351 $config_options->{maxvz} = $tmp;
3352 } else {
3353 delete $config_options->{maxvz};
3354 }
3355
3356 $dialog->destroy();
3357}
3358
121ebc59 3359my $get_raid_devlist = sub {
c6ed3b24
DM
3360
3361 my $dev_name_hash = {};
3362
3363 my $devlist = [];
5f8e86d5 3364 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24 3365 if (my $hd = $config_options->{"disksel$i"}) {
17fd908e 3366 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
1464c7c9 3367 die "device '$devname' is used more than once\n"
c6ed3b24
DM
3368 if $dev_name_hash->{$devname};
3369 $dev_name_hash->{$devname} = $hd;
3370 push @$devlist, $hd;
3371 }
3372 }
3373
121ebc59
DM
3374 return $devlist;
3375};
3376
14aacec8
FG
3377sub zfs_mirror_size_check {
3378 my ($expected, $actual) = @_;
3379
3380 die "mirrored disks must have same size\n"
3381 if abs($expected - $actual) > $expected / 10;
3382}
3383
5ea943cf
SI
3384sub legacy_bios_4k_check {
3385 my ($lbs) = @_;
3386 die "Booting from 4kn drive in legacy BIOS mode is not supported.\n"
3387 if (($boot_type ne 'efi') && ($lbs == 4096));
3388}
3389
121ebc59 3390sub get_zfs_raid_setup {
121ebc59
DM
3391 my $filesys = $config_options->{filesys};
3392
3393 my $devlist = &$get_raid_devlist();
3394
224bb7b0 3395 my $diskcount = scalar(@$devlist);
0cfa502c 3396 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24
DM
3397
3398 my $cmd= '';
3399 if ($filesys eq 'zfs (RAID0)') {
c6ed3b24 3400 foreach my $hd (@$devlist) {
5ea943cf 3401 legacy_bios_4k_check(@$hd[4]);
c6ed3b24
DM
3402 $cmd .= " @$hd[1]";
3403 }
3404 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 3405 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 3406 $cmd .= ' mirror ';
269c66a6 3407 my $hd = @$devlist[0];
14aacec8 3408 my $expected_size = @$hd[2]; # all disks need approximately same size
eaeccd9f 3409 foreach my $hd (@$devlist) {
14aacec8 3410 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3411 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3412 $cmd .= " @$hd[1]";
c6ed3b24
DM
3413 }
3414 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 3415 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 3416 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 3417
224bb7b0 3418 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
3419 my $hd1 = @$devlist[$i];
3420 my $hd2 = @$devlist[$i+1];
14aacec8 3421 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
5ea943cf
SI
3422 legacy_bios_4k_check(@$hd1[4]);
3423 legacy_bios_4k_check(@$hd2[4]);
c6ed3b24
DM
3424 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
3425 }
3426
3427 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3428 my $level = $1;
3429 my $mindisks = 2 + $level;
0cfa502c 3430 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 3431 my $hd = @$devlist[0];
14aacec8 3432 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 3433 $cmd .= " raidz$level";
eaeccd9f 3434 foreach my $hd (@$devlist) {
14aacec8 3435 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3436 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3437 $cmd .= " @$hd[1]";
c6ed3b24
DM
3438 }
3439 } else {
3440 die "unknown zfs mode '$filesys'\n";
3441 }
3442
82695821 3443 return ($devlist, $cmd);
c6ed3b24
DM
3444}
3445
121ebc59
DM
3446sub get_btrfs_raid_setup {
3447
3448 my $filesys = $config_options->{filesys};
3449
3450 my $devlist = &$get_raid_devlist();
3451
3452 my $diskcount = scalar(@$devlist);
0cfa502c 3453 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
3454
3455 my $mode;
3456
3457 if ($diskcount == 1) {
3458 $mode = 'single';
3459 } else {
3460 if ($filesys eq 'btrfs (RAID0)') {
3461 $mode = 'raid0';
3462 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 3463 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
3464 $mode = 'raid1';
3465 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 3466 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
3467 $mode = 'raid10';
3468 } else {
9d69f3d3 3469 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
3470 }
3471 }
3472
3473 return ($devlist, $mode);
3474}
3475
218a4b6b 3476my $last_hd_selected = 0;
89a12446
DM
3477sub create_hdsel_view {
3478
451b1da5 3479 $prev_btn->set_sensitive(1); # enable previous button at this point
201a5120 3480
71590b6a 3481 cleanup_view();
89a12446 3482
71590b6a
OB
3483 my $vbox = Gtk3::VBox->new(0, 0);
3484 $inbox->pack_start($vbox, 1, 0, 0);
3485 my $hbox = Gtk3::HBox->new(0, 0);
3486 $vbox->pack_start($hbox, 0, 0, 10);
968fa90b 3487
17fd908e 3488 my ($disk, $devname, $size, $model, $logical_bsize) = @{@$hds[0]};
9227a70f 3489 $target_hd = $devname if !defined($target_hd);
89a12446 3490
71590b6a
OB
3491 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3492 $hbox->pack_start($target_hd_label, 0, 0, 0);
89a12446 3493
bcbfab6b 3494 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 3495
1aa5bd02 3496 foreach my $hd (@$hds) {
17fd908e 3497 ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c2ca8ba8 3498 $target_hd_combo->append_text(get_device_desc($devname, $size, $model));
1aa5bd02 3499 }
89a12446 3500
90af1603
OB
3501 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3502 if ($raid) {
3503 $target_hd_label->set_text("Target: $config_options->{filesys} ");
3504 $target_hd_combo->set_visible(0);
3505 $target_hd_combo->set_no_show_all(1);
3506 }
218a4b6b 3507 $target_hd_combo->set_active($last_hd_selected);
71590b6a 3508 $target_hd_combo->signal_connect(changed => sub {
1aa5bd02
DM
3509 $a = shift->get_active;
3510 my ($disk, $devname) = @{@$hds[$a]};
3b959bef 3511 $last_hd_selected = $a;
1aa5bd02 3512 $target_hd = $devname;
1aa5bd02 3513 });
1464c7c9 3514
71590b6a 3515 $hbox->pack_start($target_hd_combo, 0, 0, 10);
aed81ff0 3516
71590b6a 3517 my $options = Gtk3::Button->new('_Options');
aed81ff0
DM
3518 $options->signal_connect (clicked => \&create_hdoption_view);
3519 $hbox->pack_start ($options, 0, 0, 0);
3520
89a12446
DM
3521
3522 $inbox->show_all;
3523
201a5120 3524 display_html();
c6ed3b24 3525
71590b6a 3526 set_next(undef, sub {
c6ed3b24
DM
3527
3528 if ($config_options->{filesys} =~ m/zfs/) {
a7d40341 3529 my ($devlist) = eval { get_zfs_raid_setup() };
c6ed3b24 3530 if (my $err = $@) {
303dfb2c
TL
3531 display_message("Warning: $err\nPlease fix ZFS setup first.");
3532 return;
c6ed3b24 3533 }
303dfb2c 3534 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
121ebc59 3535 } elsif ($config_options->{filesys} =~ m/btrfs/) {
a7d40341 3536 my ($devlist) = eval { get_btrfs_raid_setup() };
121ebc59 3537 if (my $err = $@) {
303dfb2c
TL
3538 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3539 return;
121ebc59 3540 }
303dfb2c 3541 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
c6ed3b24 3542 } else {
5ea943cf
SI
3543 eval { legacy_bios_4k_check(logical_blocksize($target_hd)) };
3544 if (my $err = $@) {
3545 display_message("Warning: $err\n");
3546 return;
3547 }
a7d40341 3548 $config_options->{target_hds} = [ $target_hd ];
c6ed3b24 3549 }
303dfb2c
TL
3550
3551 $step_number++;
3552 create_country_view();
c6ed3b24 3553 });
89a12446
DM
3554}
3555
3556sub create_extract_view {
3557
71590b6a 3558 cleanup_view();
89a12446 3559
550958aa
DM
3560 display_info();
3561
201a5120 3562 $next->set_sensitive(0);
ac3ee85b
TL
3563 $prev_btn->set_sensitive(0);
3564 $prev_btn->hide();
89a12446 3565
71590b6a 3566 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 3567 $inbox->pack_start ($vbox, 1, 0, 0);
71590b6a 3568 my $hbox = Gtk3::HBox->new(0, 0);
53986d77 3569 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3570
71590b6a 3571 my $vbox2 = Gtk3::VBox->new(0, 0);
89a12446
DM
3572 $hbox->pack_start ($vbox2, 0, 0, 0);
3573
71590b6a 3574 $progress_status = Gtk3::Label->new('');
89a12446 3575 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3576
7becc472 3577 $progress = Gtk3::ProgressBar->new;
45feca6f 3578 $progress->set_show_text(1);
7becc472 3579 $progress->set_size_request (600, -1);
89a12446 3580
71590b6a 3581 $vbox2->pack_start($progress, 0, 0, 0);
89a12446 3582
201a5120 3583 $inbox->show_all();
89a12446
DM
3584
3585 my $tdir = $opt_testmode ? "target" : "/target";
3586 mkdir $tdir;
97980bf2 3587 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446 3588
71590b6a 3589 eval { extract_data($base, $tdir); };
89a12446
DM
3590 my $err = $@;
3591
201a5120 3592 $next->set_sensitive(1);
89a12446 3593
71590b6a 3594 set_next("_Reboot", sub { exit (0); } );
89a12446 3595
296cf41f 3596 if ($err) {
201a5120
OB
3597 display_html("fail.htm");
3598 display_error($err);
296cf41f 3599 } else {
201a5120
OB
3600 cleanup_view();
3601 display_html("success.htm");
dfc02f3c
TL
3602
3603 if ($config_options->{autoreboot}) {
3604 Glib::Timeout->add(1000, sub {
3605 if ($autoreboot_seconds > 0) {
3606 $autoreboot_seconds--;
3607 display_html("success.htm");
3608 } else {
3609 exit(0);
3610 }
3611 });
3612 }
296cf41f 3613 }
89a12446
DM
3614}
3615
89a12446
DM
3616sub create_intro_view {
3617
451b1da5 3618 $prev_btn->set_sensitive(0);
201a5120
OB
3619
3620 cleanup_view();
89a12446 3621
ca951e77 3622 if (int($total_memory) < 1024) {
3befbf97 3623 display_error("Less than 1 GiB of usable memory detected, installation will probably fail.\n\n".
c2f72dd6 3624 "See 'System Requirements' in the $setup->{fullname} documentation.");
2b85ee1b
OB
3625 }
3626
bdeca872
DM
3627 if ($setup->{product} eq 'pve') {
3628 eval {
3629 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3630 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
2780ea4f 3631 display_error("No support for KVM virtualization detected.\n\n" .
bdeca872
DM
3632 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3633 }
3634 };
3635 }
7fff0d85 3636
201a5120 3637 display_html();
89a12446 3638
201a5120 3639 $step_number++;
71590b6a 3640 set_next("I a_gree", \&create_hdsel_view);
89a12446
DM
3641}
3642
71590b6a 3643$ipconf = get_ip_config();
89a12446 3644
9d1f1ee3 3645$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3646
3647# read country, kmap and timezone infos
71590b6a 3648$cmap = read_cmap();
89a12446 3649
9d1f1ee3
FG
3650if (!defined($cmap->{country}->{$country})) {
3651 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3652 $country = undef;
3653}
3654
89a12446
DM
3655create_main_window ();
3656
ff2ce71c
FG
3657my $initial_error = 0;
3658
89a12446
DM
3659if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3660 print "no hardisks found\n";
ff2ce71c 3661 $initial_error = 1;
201a5120 3662 display_html("nohds.htm");
71590b6a 3663 set_next("Reboot", sub { exit(0); } );
89a12446 3664} else {
89a12446
DM
3665 foreach my $hd (@$hds) {
3666 my ($disk, $devname) = @$hd;
3667 next if $devname =~ m|^/dev/md\d+$|;
3668 print "found Disk$disk N:$devname\n";
3669 }
89a12446
DM
3670}
3671
72836708
FG
3672if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3673 print "no network interfaces found\n";
3674 $initial_error = 1;
201a5120 3675 display_html("nonics.htm");
71590b6a 3676 set_next("Reboot", sub { exit(0); } );
72836708
FG
3677}
3678
ff2ce71c
FG
3679create_intro_view () if !$initial_error;
3680
7becc472 3681Gtk3->main;
89a12446 3682
0e631479
SI
3683# reap left over zombie processes
3684while ((my $child = waitpid(-1, POSIX::WNOHANG)) > 0) {
3685 print "reaped child $child\n";
3686}
3687
89a12446 3688exit 0;