]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
bump version to 7.1-2
[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
DM
1742
1743 unlink "$targetdir/proxmox_install_mode";
1744
968fa90b 1745 # set timezone
89a12446
DM
1746 unlink ("$targetdir/etc/localtime");
1747 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
71590b6a 1748 write_config("$timezone\n", "$targetdir/etc/timezone");
89a12446 1749
89a12446
DM
1750 # set apt mirror
1751 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1752 my $fn = "$targetdir/etc/apt/sources.list";
71590b6a 1753 syscmd("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
89a12446
DM
1754 }
1755
19edf8b7
DM
1756 # create extended_states for apt (avoid cron job warning if that
1757 # file does not exist)
71590b6a 1758 write_config('', "$targetdir/var/lib/apt/extended_states");
19edf8b7 1759
c2657b8b 1760 # allow ssh root login
abcadb95 1761 syscmd(['sed', '-i', 's/^#\?PermitRootLogin.*/PermitRootLogin yes/', "$targetdir/etc/ssh/sshd_config"]);
861a26d4
DM
1762
1763 if ($setup->{product} eq 'pmg') {
1764 # install initial clamav DB
1765 my $srcdir = "${proxmox_cddir}/proxmox/clamav";
05eb99e2 1766 foreach my $fn ("main.cvd", "bytecode.cvd", "daily.cvd", "safebrowsing.cvd") {
71590b6a 1767 syscmd("cp \"$srcdir/$fn\" \"$targetdir/var/lib/clamav\"") == 0 ||
861a26d4
DM
1768 die "installation of clamav db file '$fn' failed\n";
1769 }
1770 syscmd("chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav") == 0 ||
1771 die "unable to set owner for clamav database files\n";
1772 }
1773
58a09baa
DM
1774 if ($setup->{product} eq 'pve') {
1775 # save installer settings
1776 my $ucc = uc ($country);
1777 debconfig_set($targetdir, "pve-manager pve-manager/country string $ucc\n");
1778 }
89a12446 1779
71590b6a 1780 update_progress(0.8, 0.95, 1, "make system bootable");
89a12446 1781
5c06ced5 1782 if ($use_zfs) {
b13dac4b
FG
1783 # add ZFS options while preserving existing kernel cmdline
1784 my $zfs_snippet = "GRUB_CMDLINE_LINUX=\"\$GRUB_CMDLINE_LINUX root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs\"";
1785 write_config($zfs_snippet, "$targetdir/etc/default/grub.d/zfs.cfg");
4fb6ac60 1786
c9b4369c 1787 write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs\n", "$targetdir/etc/kernel/cmdline");
1464c7c9 1788
5c06ced5 1789 }
23c337f5 1790
71590b6a
OB
1791 diversion_remove($targetdir, "/usr/sbin/update-grub");
1792 diversion_remove($targetdir, "/usr/sbin/update-initramfs");
89a12446 1793
56207f2a
DM
1794 my $kapi;
1795 foreach my $fn (<$targetdir/lib/modules/*>) {
1796 if ($fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$!) {
1797 die "found multiple kernels\n" if defined($kapi);
1798 $kapi = $1;
1799 }
1800 }
1801 die "unable to detect kernel version\n" if !defined($kapi);
1802
c6ed3b24 1803 if (!$opt_testmode) {
89a12446
DM
1804
1805 unlink ("$targetdir/etc/mtab");
1806 symlink ("/proc/mounts", "$targetdir/etc/mtab");
71590b6a 1807 syscmd("mount -n --bind /dev $targetdir/dev");
89a12446 1808
1f8429eb
FG
1809 my $bootloader_err_list = [];
1810 eval {
1811 syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1812 die "unable to install initramfs\n";
1813
5ea943cf
SI
1814 my $native_4k_disk_bootable = 0;
1815 foreach my $di (@$bootdevinfo) {
1816 $native_4k_disk_bootable |= ($di->{logical_bsize} == 4096);
1817 }
1818
1f8429eb
FG
1819 foreach my $di (@$bootdevinfo) {
1820 my $dev = $di->{devname};
d58ef02c
SI
1821 if ($use_zfs) {
1822 prepare_proxmox_boot_esp($di->{esp}, $targetdir);
1823 } else {
1824 if (!$native_4k_disk_bootable) {
1825 eval {
1826 syscmd("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1827 die "unable to install the i386-pc boot loader on '$dev'\n";
1828 };
1829 push @$bootloader_err_list, $@ if $@;
1830 }
1f8429eb
FG
1831
1832 eval {
1833 if (my $esp = $di->{esp}) {
1f8429eb
FG
1834 prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
1835 }
1836 }
1837 };
1838 push @$bootloader_err_list, $@ if $@;
1e61f3d8 1839 }
89a12446 1840
1f8429eb
FG
1841 syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
1842 die "unable to update boot loader config\n";
1f8429eb
FG
1843 };
1844 push @$bootloader_err_list, $@ if $@;
1845
1846 if (scalar(@$bootloader_err_list) > 0) {
1847 $bootloader_err = "bootloader setup errors:\n";
1848 map { $bootloader_err .= "- $_" } @$bootloader_err_list;
1849 warn $bootloader_err;
f2afc0fc 1850 }
03c686b7 1851
71590b6a 1852 syscmd("umount $targetdir/dev");
89a12446
DM
1853 }
1854
968fa90b 1855 # cleanup
89a12446 1856
89a12446
DM
1857 unlink "$targetdir/usr/sbin/policy-rc.d";
1858
71590b6a 1859 diversion_remove($targetdir, "/sbin/start-stop-daemon");
89a12446
DM
1860
1861 # set root password
968fa90b 1862 my $octets = encode("utf-8", $password);
71590b6a
OB
1863 run_command("chroot $targetdir /usr/sbin/chpasswd", undef,
1864 "root:$octets\n");
7053f98b 1865
038552a1 1866 if ($setup->{product} eq 'pmg') {
038552a1 1867 # save admin email
71590b6a
OB
1868 write_config("section: admin\n\temail ${mailto}\n",
1869 "$targetdir/etc/pmg/pmg.conf");
038552a1
DM
1870
1871 } elsif ($setup->{product} eq 'pve') {
7053f98b 1872
8acc47b5 1873 # create pmxcfs DB
7053f98b 1874
8acc47b5
DM
1875 my $tmpdir = "$targetdir/tmp/pve";
1876 mkdir $tmpdir;
7053f98b 1877
8acc47b5
DM
1878 # write vnc keymap to datacenter.cfg
1879 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
71590b6a
OB
1880 write_config("keyboard: $vnckmap\n",
1881 "$tmpdir/datacenter.cfg");
968fa90b 1882
8acc47b5 1883 # save admin email
e32b8d2d 1884 write_config("user:root\@pam:1:0:::${mailto}::\n", "$tmpdir/user.cfg");
5fd81672 1885
8acc47b5 1886 # write storage.cfg
d8c697d4 1887 my $storage_cfg_fn = "$tmpdir/storage.cfg";
8acc47b5 1888 if ($use_zfs) {
71590b6a 1889 write_config($storage_cfg_zfs, $storage_cfg_fn);
8acc47b5 1890 } elsif ($use_btrfs) {
71590b6a 1891 write_config($storage_cfg_btrfs, $storage_cfg_fn);
e2c51d7c 1892 } elsif ($datadev) {
71590b6a 1893 write_config($storage_cfg_lvmthin, $storage_cfg_fn);
e2c51d7c 1894 } else {
71590b6a 1895 write_config($storage_cfg_local, $storage_cfg_fn);
8acc47b5 1896 }
7053f98b 1897
8acc47b5
DM
1898 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1899
71590b6a 1900 syscmd("rm -rf $tmpdir");
ca74501d
TL
1901 } elsif ($setup->{product} eq 'pbs') {
1902 my $base_cfg_path = "/etc/proxmox-backup";
e32b8d2d 1903 mkdir "$targetdir/$base_cfg_path";
341a93b7
TL
1904
1905 chroot_chown($targetdir, $base_cfg_path, user => 'backup', recursive => 1);
1906 chroot_chmod($targetdir, $base_cfg_path, mode => '0700');
e32b8d2d
TL
1907
1908 my $user_cfg_fn = "$base_cfg_path/user.cfg";
1909 write_config("user: root\@pam\n\temail ${mailto}\n", "$targetdir/$user_cfg_fn");
1910 chroot_chown($targetdir, $user_cfg_fn, user => 'root', group => 'backup');
1911 chroot_chmod($targetdir, $user_cfg_fn, mode => '0640');
8acc47b5 1912 }
89a12446
DM
1913 };
1914
1915 my $err = $@;
1916
71590b6a 1917 update_progress(1, 0, 1, "");
89a12446
DM
1918
1919 print $err if $err;
1920
1921 if ($opt_testmode) {
121ebc59
DM
1922 my $elapsed = Time::HiRes::tv_interval($starttime);
1923 print "Elapsed extract time: $elapsed\n";
1924
71590b6a 1925 syscmd("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
89a12446
DM
1926 }
1927
8d7ddbde
TL
1928 syscmd("umount $targetdir/run");
1929 syscmd("umount $targetdir/mnt/hostrun");
f9b3baff 1930 syscmd("umount $targetdir/tmp/pkg");
71590b6a
OB
1931 syscmd("umount $targetdir/tmp");
1932 syscmd("umount $targetdir/proc");
f238dd03 1933 syscmd("umount $targetdir/sys/firmware/efi/efivars");
71590b6a 1934 syscmd("umount $targetdir/sys");
435522c9 1935 rmdir("$targetdir/mnt/hostrun");
6fbd1fb1
DM
1936
1937 if ($use_zfs) {
71590b6a 1938 syscmd("zfs umount -a") == 0 ||
6fbd1fb1
DM
1939 die "unable to unmount zfs\n";
1940 } else {
71590b6a 1941 syscmd("umount -d $targetdir");
6fbd1fb1 1942 }
89a12446 1943
5c06ced5 1944 if (!$err && $use_zfs) {
71590b6a 1945 syscmd("zfs set sync=standard $zfspoolname") == 0 ||
481671c3
DM
1946 die "unable to set zfs properties\n";
1947
71590b6a 1948 syscmd("zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname") == 0 ||
5c06ced5 1949 die "zfs set mountpoint failed\n";
1464c7c9 1950
71590b6a 1951 syscmd("zpool set bootfs=$zfspoolname/ROOT/$zfsrootvolname $zfspoolname") == 0 ||
5c06ced5 1952 die "zfs set bootfs failed\n";
71590b6a 1953 syscmd("zpool export $zfspoolname");
5c06ced5
DM
1954 }
1955
1f8429eb
FG
1956 if ($bootloader_err) {
1957 $err = $err ? "$err\n$bootloader_err" : $bootloader_err;
1958 }
1959
89a12446
DM
1960 die $err if $err;
1961}
1962
550958aa
DM
1963my $last_display_change = 0;
1964
1965my $display_info_counter = 0;
1966
1967my $display_info_items = [
1968 "extract1-license.htm",
1969 "extract2-rulesystem.htm",
1970 "extract3-spam.htm",
1971 "extract4-virus.htm",
1972 ];
1973
1974sub display_info {
1975
1976 my $min_display_time = 15;
1977
1978 my $ctime = time();
1979
1980 return if ($ctime - $last_display_change) < $min_display_time;
1981
1982 my $page = $display_info_items->[$display_info_counter % scalar(@$display_info_items)];
1983
1984 $display_info_counter++;
1985
1986 display_html($page);
1987}
1988
89a12446
DM
1989sub display_html {
1990 my ($filename) = @_;
1991
201a5120
OB
1992 $filename = $steps[$step_number]->{html} if !$filename;
1993
c2f72dd6
TL
1994 my $htmldir = "${proxmox_libdir}/html";
1995 my $path;
1996 if (-f "$htmldir/$setup->{product}/$filename") {
1997 $path = "$htmldir/$setup->{product}/$filename";
c2f72dd6 1998 } else {
029fde30 1999 $path = "$htmldir/$filename";
c2f72dd6 2000 }
8a50920c
DM
2001
2002 my $data = file_get_contents($path);
2003
2004 if ($filename eq 'license.htm') {
93f25df9
TL
2005 my $license = eval { decode('utf8', file_get_contents("${proxmox_cddir}/EULA")) };
2006 if (my $err = $@) {
2007 die $err if !$opt_testmode;
2008 $license = "TESTMODE: Ignore non existent EULA...\n";
2009 }
3c866639 2010 my $title = "END USER LICENSE AGREEMENT (EULA)";
f91c161b 2011 $data =~ s/__LICENSE__/$license/;
8a50920c 2012 $data =~ s/__LICENSE_TITLE__/$title/;
3bcac16b
TL
2013 } elsif ($filename eq 'success.htm') {
2014 my $addr = $ipversion == 6 ? "[${ipaddress}]" : "$ipaddress";
cfb92364 2015 $data =~ s/__IPADDR__/$addr/g;
c2f72dd6 2016 $data =~ s/__PORT__/$setup->{port}/g;
dfc02f3c
TL
2017
2018 my $autoreboot_msg = $config_options->{autoreboot}
2019 ? "Automatic reboot scheduled in $autoreboot_seconds seconds."
2020 : '';
2021 $data =~ s/__AUTOREBOOT_MSG__/$autoreboot_msg/;
8a50920c 2022 }
c2f72dd6 2023 $data =~ s/__FULL_PRODUCT_NAME__/$setup->{fullname}/g;
8a50920c 2024
029fde30
TL
2025 # always set base-path to common path, all resources are accesible from there.
2026 $htmlview->load_html($data, "file://$htmldir/");
550958aa
DM
2027
2028 $last_display_change = time();
7becc472
DM
2029}
2030
201a5120
OB
2031sub prev_function {
2032
2033 my ($text, $fctn) = @_;
2034
2035 $fctn = $step_number if !$fctn;
2036 $text = "_Previous" if !$text;
451b1da5 2037 $prev_btn->set_label ($text);
201a5120
OB
2038
2039 $step_number--;
2040 $steps[$step_number]->{function}();
2041
71590b6a 2042 $prev_btn->grab_focus();
201a5120
OB
2043}
2044
89a12446
DM
2045sub set_next {
2046 my ($text, $fctn) = @_;
2047
2048 $next_fctn = $fctn;
201a5120
OB
2049 my $step = $steps[$step_number];
2050 $text //= $steps[$step_number]->{next_button} // '_Next';
71590b6a 2051 $next->set_label($text);
968fa90b 2052
71590b6a 2053 $next->grab_focus();
89a12446 2054}
89a12446
DM
2055
2056sub create_main_window {
2057
71590b6a
OB
2058 $window = Gtk3::Window->new();
2059 $window->set_default_size(1024, 768);
84761f93 2060 $window->set_has_resize_grip(0);
d6b47e68 2061 $window->fullscreen() if !$opt_testmode;
71590b6a 2062 $window->set_decorated(0) if !$opt_testmode;
89a12446 2063
71590b6a 2064 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 2065
782b4acd
DM
2066 my $logofn = "$setup->{product}-banner.png";
2067 my $image = Gtk3::Image->new_from_file("${proxmox_libdir}/$logofn");
71590b6a 2068 $vbox->pack_start($image, 0, 0, 0);
89a12446 2069
71590b6a
OB
2070 my $hbox = Gtk3::HBox->new(0, 0);
2071 $vbox->pack_start($hbox, 1, 1, 0);
89a12446 2072
7becc472
DM
2073 # my $f1 = Gtk3::Frame->new ('test');
2074 # $f1->set_shadow_type ('none');
2075 # $hbox->pack_start ($f1, 1, 1, 0);
89a12446 2076
71590b6a
OB
2077 my $sep1 = Gtk3::HSeparator->new();
2078 $vbox->pack_start($sep1, 0, 0, 0);
89a12446 2079
71590b6a
OB
2080 $cmdbox = Gtk3::HBox->new();
2081 $vbox->pack_start($cmdbox, 0, 0, 10);
89a12446 2082
71590b6a
OB
2083 $next = Gtk3::Button->new('_Next');
2084 $next->signal_connect(clicked => sub { $last_display_change = 0; &$next_fctn (); });
2085 $cmdbox->pack_end($next, 0, 0, 10);
201a5120
OB
2086
2087
71590b6a
OB
2088 $prev_btn = Gtk3::Button->new('_Previous');
2089 $prev_btn->signal_connect(clicked => sub { $last_display_change = 0; &prev_function (); });
2090 $cmdbox->pack_end($prev_btn, 0, 0, 10);
201a5120
OB
2091
2092
71590b6a
OB
2093 my $abort = Gtk3::Button->new('_Abort');
2094 $abort->set_can_focus(0);
2095 $cmdbox->pack_start($abort, 0, 0, 10);
2096 $abort->signal_connect(clicked => sub { exit (-1); });
89a12446 2097
71590b6a
OB
2098 my $vbox2 = Gtk3::VBox->new(0, 0);
2099 $hbox->add($vbox2);
89a12446 2100
ed0e6aea 2101 $htmlview = Gtk3::WebKit2::WebView->new();
7becc472
DM
2102 my $scrolls = Gtk3::ScrolledWindow->new();
2103 $scrolls->add($htmlview);
1464c7c9 2104
71590b6a
OB
2105 my $hbox2 = Gtk3::HBox->new(0, 0);
2106 $hbox2->pack_start($scrolls, 1, 1, 0);
89a12446 2107
71590b6a 2108 $vbox2->pack_start($hbox2, 1, 1, 0);
89a12446 2109
71590b6a
OB
2110 my $vbox3 = Gtk3::VBox->new(0, 0);
2111 $vbox2->pack_start($vbox3, 0, 0, 0);
89a12446 2112
7becc472 2113 my $sep2 = Gtk3::HSeparator->new;
71590b6a 2114 $vbox3->pack_start($sep2, 0, 0, 0);
89a12446 2115
71590b6a
OB
2116 $inbox = Gtk3::HBox->new(0, 0);
2117 $vbox3->pack_start($inbox, 0, 0, 0);
89a12446 2118
71590b6a 2119 $window->add($vbox);
89a12446
DM
2120
2121 $window->show_all;
71590b6a 2122 $window->realize();
89a12446
DM
2123}
2124
1464c7c9 2125sub cleanup_view {
d2120e51
DM
2126 $inbox->foreach(sub {
2127 my $child = shift;
1464c7c9 2128 $inbox->remove ($child);
d2120e51 2129 });
89a12446
DM
2130}
2131
aed81ff0
DM
2132# fixme: newer GTK3 has special properties to handle numbers with Entry
2133# only allow floating point numbers with Gtk3::Entry
e73c5fcf 2134
aed81ff0
DM
2135sub check_float {
2136 my ($entry, $event) = @_;
2137
e73c5fcf
FG
2138 return check_number($entry, $event, 1);
2139}
2140
2141sub check_int {
2142 my ($entry, $event) = @_;
2143
2144 return check_number($entry, $event, 0);
2145}
2146
2147sub check_number {
2148 my ($entry, $event, $float) = @_;
aed81ff0
DM
2149
2150 my $val = $event->get_keyval;
2151
e73c5fcf 2152 if (($float && $val == ord '.') ||
aed81ff0
DM
2153 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
2154 $val == Gtk3::Gdk::KEY_Shift_L ||
2155 $val == Gtk3::Gdk::KEY_Tab ||
2156 $val == Gtk3::Gdk::KEY_Left ||
2157 $val == Gtk3::Gdk::KEY_Right ||
2158 $val == Gtk3::Gdk::KEY_BackSpace ||
2159 $val == Gtk3::Gdk::KEY_Delete ||
2160 ($val >= ord '0' && $val <= ord '9') ||
2161 ($val >= Gtk3::Gdk::KEY_KP_0 &&
2162 $val <= Gtk3::Gdk::KEY_KP_9)) {
2163 return undef;
2164 }
2165
2166 return 1;
2167}
2168
d2120e51 2169sub create_text_input {
89a12446
DM
2170 my ($default, $text) = @_;
2171
cc120d79 2172 my $hbox = Gtk3::Box->new('horizontal', 0);
89a12446 2173
71590b6a
OB
2174 my $label = Gtk3::Label->new($text);
2175 $label->set_size_request(150, -1);
2176 $label->set_alignment(1, 0.5);
2177 $hbox->pack_start($label, 0, 0, 10);
2178 my $e1 = Gtk3::Entry->new();
cc120d79 2179 $e1->set_width_chars(35);
71590b6a
OB
2180 $hbox->pack_start($e1, 0, 0, 0);
2181 $e1->set_text($default);
89a12446
DM
2182
2183 return ($hbox, $e1);
2184}
cc120d79
TL
2185sub create_cidr_inputs {
2186 my ($default_ip, $default_mask) = @_;
2187
2188 my $hbox = Gtk3::Box->new('horizontal', 0);
2189
2190 my $label = Gtk3::Label->new('IP Address (CIDR)');
2191 $label->set_size_request(150, -1);
2192 $label->set_alignment(1, 0.5);
2193 $hbox->pack_start($label, 0, 0, 10);
2194
2195 my $ip_el = Gtk3::Entry->new();
2196 $ip_el->set_width_chars(28);
2197 $hbox->pack_start($ip_el, 0, 0, 0);
2198 $ip_el->set_text($default_ip);
2199
2200 $label = Gtk3::Label->new('/');
2201 $label->set_size_request(10, -1);
2202 $label->set_alignment(0.5, 0.5);
2203 $hbox->pack_start($label, 0, 0, 2);
2204
2205 my $cidr_el = Gtk3::Entry->new();
2206 $cidr_el->set_width_chars(3);
2207 $hbox->pack_start($cidr_el, 0, 0, 0);
2208 $cidr_el->set_text($default_mask);
2209
2210 return ($hbox, $ip_el, $cidr_el);
2211}
89a12446 2212
89a12446
DM
2213sub get_ip_config {
2214
fe44bd92
FG
2215 my $ifaces = {};
2216 my $default;
89a12446 2217
fe44bd92
FG
2218 my $links = `ip -o l`;
2219 foreach my $l (split /\n/,$links) {
2220 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
2221 next if !$name || $name eq 'lo';
89a12446 2222
fe44bd92
FG
2223 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
2224 $driver =~ s!^.*/!!;
2225
2226 $ifaces->{"$index"} = {
2227 name => $name,
2228 driver => $driver,
2229 flags => $flags,
2230 state => $state,
2231 mac => $mac,
2232 };
2233
2234 my $addresses = `ip -o a s $name`;
2235 foreach my $a (split /\n/,$addresses) {
2236 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
2237 next if !$ip;
32b6fbcf 2238 next if $a =~ /scope\s+link/; # ignore link local
fe44bd92
FG
2239
2240 my $mask = $prefix;
2241
2242 if ($family eq 'inet') {
2243 next if !$ip =~ /$IPV4RE/;
2244 next if $prefix < 8 || $prefix > 32;
2245 $mask = @$ipv4_reverse_mask[$prefix];
2246 } else {
2247 next if !$ip =~ /$IPV6RE/;
2248 }
2249
2250 $default = $index if !$default;
2251
2252 $ifaces->{"$index"}->{"$family"} = {
cc120d79 2253 prefix => $prefix,
fe44bd92
FG
2254 mask => $mask,
2255 addr => $ip,
2256 };
2257 }
2258 }
2259
2260
2261 my $route = `ip route`;
2262 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
89a12446
DM
2263
2264 my $resolvconf = `cat /etc/resolv.conf`;
2265 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
713790a4 2266 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
89a12446
DM
2267
2268 return {
fe44bd92
FG
2269 default => $default,
2270 ifaces => $ifaces,
89a12446
DM
2271 gateway => $gateway,
2272 dnsserver => $dnsserver,
713790a4 2273 domain => $domain,
89a12446
DM
2274 }
2275}
2276
2277sub display_message {
2278 my ($msg) = @_;
2279
21b2e8ea 2280 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'info', 'ok', $msg);
89a12446
DM
2281 $dialog->run();
2282 $dialog->destroy();
2283}
2284
2285sub display_error {
2286 my ($msg) = @_;
2287
21b2e8ea 2288 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'error', 'ok', $msg);
89a12446
DM
2289 $dialog->run();
2290 $dialog->destroy();
2291}
2292
fe44bd92
FG
2293my $ipconf_first_view = 1;
2294
89a12446
DM
2295sub create_ipconf_view {
2296
201a5120
OB
2297 cleanup_view();
2298 display_html();
89a12446 2299
cc120d79
TL
2300 my $vcontainer = Gtk3::Box->new('vertical', 0);
2301 $inbox->pack_start($vcontainer, 1, 0, 0);
2302 my $hcontainer = Gtk3::Box->new('horizontal', 0);
2303 $vcontainer->pack_start($hcontainer, 0, 0, 10);
2304 my $vbox = Gtk3::Box->new('vertical', 0);
2305 $hcontainer->add($vbox);
89a12446 2306
ebc4f76f 2307 my $ipaddr_text = $config->{ipaddress} // "192.168.100.2";
cc120d79
TL
2308 my $netmask_text = $config->{netmask} // "24";
2309 my $cidr_box;
2310 ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) =
2311 create_cidr_inputs($ipaddr_text, $netmask_text);
fe44bd92
FG
2312
2313 my $device_cb = Gtk3::ComboBoxText->new();
2314 $device_cb->set_active(0);
2315 $device_cb->set_visible(1);
2316
2317 my $get_device_desc = sub {
2318 my $iface = shift;
2319 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
2320 };
2321
2322 my $device_active_map = {};
ebc4f76f 2323 my $device_active_reverse_map = {};
5b6ba737
FG
2324
2325 my $device_change_handler = sub {
2326 my $current = shift;
d6524c52
TL
2327
2328 my $new = $device_active_map->{$current->get_active()};
cd2d2a27 2329 return if defined($ipconf->{selected}) && $new eq $ipconf->{selected};
d6524c52
TL
2330
2331 $ipconf->{selected} = $new;
5b6ba737 2332 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
ebc4f76f 2333 $config->{mngmt_nic} = $iface->{name};
5b6ba737
FG
2334 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2335 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
cc120d79
TL
2336 $ipconf_entry_mask->set_text($iface->{inet}->{prefix} || $iface->{inet6}->{prefix})
2337 if $iface->{inet}->{prefix} || $iface->{inet6}->{prefix};
5b6ba737
FG
2338 };
2339
fe44bd92
FG
2340 my $i = 0;
2341 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2342 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
ebc4f76f
TL
2343 $device_active_map->{$i} = $index;
2344 $device_active_reverse_map->{$ipconf->{ifaces}->{$index}->{name}} = $i;
fe44bd92
FG
2345 if ($ipconf_first_view && $index == $ipconf->{default}) {
2346 $device_cb->set_active($i);
5b6ba737 2347 &$device_change_handler($device_cb);
fe44bd92
FG
2348 $ipconf_first_view = 0;
2349 }
71590b6a 2350 $device_cb->signal_connect('changed' => $device_change_handler);
fe44bd92
FG
2351 $i++;
2352 }
2353
ebc4f76f
TL
2354 if (my $nic = $config->{mngmt_nic}) {
2355 $device_cb->set_active($device_active_reverse_map->{$nic} // 0);
2356 } else {
2357 $device_cb->set_active(0);
2358 }
5b6ba737 2359
71590b6a
OB
2360 my $devicebox = Gtk3::HBox->new(0, 0);
2361 my $label = Gtk3::Label->new("Management Interface:");
2362 $label->set_size_request(150, -1);
2363 $label->set_alignment(1, 0.5);
2364 $devicebox->pack_start($label, 0, 0, 10);
2365 $devicebox->pack_start($device_cb, 0, 0, 0);
fe44bd92 2366
cc120d79 2367 $vbox->pack_start($devicebox, 0, 0, 2);
968fa90b 2368
ebc4f76f 2369 my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
1464c7c9 2370
cc120d79
TL
2371 my ($hostbox, $hostentry) = create_text_input($hn, 'Hostname (FQDN):');
2372 $vbox->pack_start($hostbox, 0, 0, 2);
89a12446 2373
cc120d79 2374 $vbox->pack_start($cidr_box, 0, 0, 2);
89a12446 2375
ebc4f76f 2376 $gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
89a12446
DM
2377
2378 my $gwbox;
d2120e51 2379 ($gwbox, $ipconf_entry_gw) =
71590b6a 2380 create_text_input($gateway, 'Gateway:');
89a12446 2381
cc120d79 2382 $vbox->pack_start($gwbox, 0, 0, 2);
89a12446 2383
ebc4f76f 2384 $dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
89a12446
DM
2385
2386 my $dnsbox;
d2120e51 2387 ($dnsbox, $ipconf_entry_dns) =
71590b6a 2388 create_text_input($dnsserver, 'DNS Server:');
89a12446 2389
cc120d79 2390 $vbox->pack_start($dnsbox, 0, 0, 0);
89a12446
DM
2391
2392 $inbox->show_all;
71590b6a 2393 set_next(undef, sub {
d2120e51
DM
2394
2395 # verify hostname
1464c7c9 2396
89a12446 2397 my $text = $hostentry->get_text();
968fa90b 2398
89a12446
DM
2399 $text =~ s/^\s+//;
2400 $text =~ s/\s+$//;
2401
ebc4f76f
TL
2402 $config->{fqdn} = $text;
2403
ac3757a9 2404 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2405
24973868
WB
2406 # Debian does not support purely numeric hostnames
2407 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2408 display_message("Purely numeric hostnames are not allowed.");
2409 $hostentry->grab_focus();
2410 return;
2411 }
2412
a39bc1f2 2413 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2414 $text =~ m/^([^\.]+)\.(\S+)$/) {
2415 $hostname = $1;
2416 $domain = $2;
d2120e51 2417 } else {
71590b6a 2418 display_message("Hostname does not look like a fully qualified domain name.");
d2120e51 2419 $hostentry->grab_focus();
89a12446
DM
2420 return;
2421 }
d2120e51
DM
2422
2423 # verify ip address
2424
2425 $text = $ipconf_entry_addr->get_text();
2426 $text =~ s/^\s+//;
2427 $text =~ s/\s+$//;
2428 if ($text =~ m!^($IPV4RE)$!) {
2429 $ipaddress = $text;
b6200603
DM
2430 $ipversion = 4;
2431 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2432 $ipaddress = $text;
b6200603 2433 $ipversion = 6;
d2120e51 2434 } else {
71590b6a 2435 display_message("IP address is not valid.");
d2120e51
DM
2436 $ipconf_entry_addr->grab_focus();
2437 return;
2438 }
ebc4f76f 2439 $config->{ipaddress} = $ipaddress;
d2120e51
DM
2440
2441 $text = $ipconf_entry_mask->get_text();
2442 $text =~ s/^\s+//;
2443 $text =~ s/\s+$//;
cc120d79 2444 if ($ipversion == 6 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 126) {
b6200603 2445 $netmask = $text;
cc120d79 2446 } elsif ($ipversion == 4 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 32) {
d2120e51 2447 $netmask = $text;
cc120d79
TL
2448 } elsif ($ipversion == 4 && defined($ipv4_mask_hash->{$text})) {
2449 # costs nothing to handle 255.x.y.z style masks, so continue to allow it
2450 $netmask = $ipv4_mask_hash->{$text};
d2120e51 2451 } else {
71590b6a 2452 display_message("Netmask is not valid.");
d2120e51
DM
2453 $ipconf_entry_mask->grab_focus();
2454 return;
2455 }
cc120d79 2456 $cidr = "$ipaddress/$netmask";
ebc4f76f 2457 $config->{netmask} = $netmask;
d2120e51
DM
2458
2459 $text = $ipconf_entry_gw->get_text();
2460 $text =~ s/^\s+//;
2461 $text =~ s/\s+$//;
b6200603
DM
2462 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2463 $gateway = $text;
2464 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2465 $gateway = $text;
2466 } else {
71590b6a 2467 display_message("Gateway is not valid.");
d2120e51
DM
2468 $ipconf_entry_gw->grab_focus();
2469 return;
2470 }
ebc4f76f 2471 $config->{gateway} = $gateway;
1464c7c9 2472
d2120e51
DM
2473 $text = $ipconf_entry_dns->get_text();
2474 $text =~ s/^\s+//;
2475 $text =~ s/\s+$//;
b6200603
DM
2476 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2477 $dnsserver = $text;
2478 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2479 $dnsserver = $text;
2480 } else {
71590b6a 2481 display_message("DNS server is not valid.");
d2120e51
DM
2482 $ipconf_entry_dns->grab_focus();
2483 return;
2484 }
ebc4f76f 2485 $config->{dnsserver} = $dnsserver;
1464c7c9 2486
d2120e51 2487 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2488
201a5120 2489 $step_number++;
2e33c3f0 2490 create_ack_view();
89a12446
DM
2491 });
2492
2493 $hostentry->grab_focus();
2494}
2495
2e33c3f0
OB
2496sub create_ack_view {
2497
2498 cleanup_view();
2499
dfc02f3c
TL
2500 my $vbox = Gtk3::VBox->new(0, 0);
2501 $inbox->pack_start($vbox, 1, 0, 0);
dfc02f3c
TL
2502
2503 my $reboot_checkbox = Gtk3::CheckButton->new('Automatically reboot after successful installation');
2504 $reboot_checkbox->set_active(1);
2505 $reboot_checkbox->signal_connect ("toggled" => sub {
2506 my $cb = shift;
2507 $config_options->{autoreboot} = $cb->get_active();
2508 });
2509 $vbox->pack_start($reboot_checkbox, 0, 0, 2);
2510
029fde30 2511 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
c2f72dd6 2512 my $ack_html = "${proxmox_libdir}/html/$setup->{product}/$steps[$step_number]->{html}";
2e33c3f0
OB
2513 my $html_data = file_get_contents($ack_template);
2514
2515 my %config_values = (
a7d40341 2516 __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
0470018e 2517 __target_fs__ => $config_options->{filesys},
0ddd2227 2518 __country__ => $cmap->{country}->{$country}->{name},
2e33c3f0
OB
2519 __timezone__ => $timezone,
2520 __keymap__ => $keymap,
2521 __mailto__ => $mailto,
2522 __interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
2523 __hostname__ => $hostname,
2524 __ip__ => $ipaddress,
b1838e1e 2525 __cidr__ => $cidr,
2e33c3f0
OB
2526 __netmask__ => $netmask,
2527 __gateway__ => $gateway,
2528 __dnsserver__ => $dnsserver,
2529 );
2530
029fde30 2531 while (my ($k, $v) = each %config_values) {
2e33c3f0
OB
2532 $html_data =~ s/$k/$v/g;
2533 }
2534
2535 write_config($html_data, $ack_html);
2536
2537 display_html();
2538
dfc02f3c
TL
2539 $inbox->show_all;
2540
2e33c3f0
OB
2541 set_next(undef, sub {
2542 $step_number++;
2543 create_extract_view();
2544 });
2545}
2546
89a12446
DM
2547sub get_device_desc {
2548 my ($devname, $size, $model) = @_;
2549
d2120e51 2550 if ($size && ($size > 0)) {
b04864ec 2551 $size = int($size/2048); # size in MiB, from 512B "sectors"
89a12446 2552
d2120e51 2553 my $text = "$devname (";
89a12446 2554 if ($size >= 1024) {
b04864ec 2555 $size = $size/1024; # size in GiB
ceabb291 2556 if ($size >= 1024) {
b04864ec
SI
2557 $size = $size/1024; # size in TiB
2558 $text .= sprintf("%.2f", $size) . "TiB";
ceabb291 2559 } else {
b04864ec 2560 $text .= sprintf("%.2f", $size) . "GiB";
ceabb291 2561 }
89a12446 2562 } else {
ceabb291 2563 $text .= "${size}MiB";
89a12446
DM
2564 }
2565
d2120e51
DM
2566 $text .= ", $model" if $model;
2567 $text .= ")";
b04864ec 2568 return $text;
d2120e51 2569
89a12446
DM
2570 } else {
2571 return $devname;
2572 }
2573}
2574
d92ada4e
SI
2575my $last_layout;
2576my $country_layout;
89a12446
DM
2577sub update_layout {
2578 my ($cb, $kmap) = @_;
2579
2580 my $ind;
2581 my $def;
2582 my $i = 0;
2583 my $kmaphash = $cmap->{kmaphash};
2584 foreach my $layout (sort keys %$kmaphash) {
2585 $def = $i if $kmaphash->{$layout} eq 'en-us';
2586 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2587 $i++;
2588 }
2589
d92ada4e
SI
2590 my $val = $ind || $def || 0;
2591
2592 if (!defined($kmap)) {
2593 $last_layout //= $val;
2594 } elsif (!defined($country_layout) || $country_layout != $val) {
2595 $last_layout = $country_layout = $val;
2596 }
2597 $cb->set_active($last_layout);
89a12446
DM
2598}
2599
2600my $lastzonecb;
2601sub update_zonelist {
2602 my ($box, $cc) = @_;
2603
2604 my $cczones = $cmap->{cczones};
2605 my $zones = $cmap->{zones};
2606
2607 my $sel;
2608 if ($lastzonecb) {
2609 $sel = $lastzonecb->get_active_text();
2610 $box->remove ($lastzonecb);
2611 } else {
2612 $sel = $timezone; # used once to select default
2613 }
2614
bcbfab6b 2615 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
71590b6a 2616 $cb->set_size_request(200, -1);
89a12446 2617
71590b6a 2618 $cb->signal_connect('changed' => sub {
89a12446
DM
2619 $timezone = $cb->get_active_text();
2620 });
2621
2622 my @za;
2623 if ($cc && defined ($cczones->{$cc})) {
2624 @za = keys %{$cczones->{$cc}};
2625 } else {
2626 @za = keys %$zones;
2627 }
2628 my $ind;
2629 my $i = 0;
2630 foreach my $zone (sort @za) {
2631 $ind = $i if $sel && $zone eq $sel;
71590b6a 2632 $cb->append_text($zone);
89a12446
DM
2633 $i++;
2634 }
2635
71590b6a 2636 $cb->set_active($ind || 0);
89a12446
DM
2637
2638 $cb->show;
71590b6a 2639 $box->pack_start($cb, 0, 0, 0);
89a12446
DM
2640}
2641
2642sub create_password_view {
2643
71590b6a 2644 cleanup_view();
89a12446 2645
71590b6a
OB
2646 my $vbox2 = Gtk3::VBox->new(0, 0);
2647 $inbox->pack_start($vbox2, 1, 0, 0);
2648 my $vbox = Gtk3::VBox->new(0, 0);
2649 $vbox2->pack_start($vbox, 0, 0, 10);
2650
2651 my $hbox1 = Gtk3::HBox->new(0, 0);
2652 my $label = Gtk3::Label->new("Password");
2653 $label->set_size_request(150, -1);
2654 $label->set_alignment(1, 0.5);
2655 $hbox1->pack_start($label, 0, 0, 10);
2656 my $pwe1 = Gtk3::Entry->new();
2657 $pwe1->set_visibility(0);
201a5120 2658 $pwe1->set_text($password) if $password;
71590b6a
OB
2659 $pwe1->set_size_request(200, -1);
2660 $hbox1->pack_start($pwe1, 0, 0, 0);
2661
2662 my $hbox2 = Gtk3::HBox->new(0, 0);
2663 $label = Gtk3::Label->new("Confirm");
2664 $label->set_size_request(150, -1);
2665 $label->set_alignment(1, 0.5);
2666 $hbox2->pack_start($label, 0, 0, 10);
2667 my $pwe2 = Gtk3::Entry->new();
2668 $pwe2->set_visibility(0);
201a5120 2669 $pwe2->set_text($password) if $password;
71590b6a
OB
2670 $pwe2->set_size_request(200, -1);
2671 $hbox2->pack_start($pwe2, 0, 0, 0);
2672
2673 my $hbox3 = Gtk3::HBox->new(0, 0);
b11c55ff 2674 $label = Gtk3::Label->new("Email");
71590b6a
OB
2675 $label->set_size_request(150, -1);
2676 $label->set_alignment(1, 0.5);
2677 $hbox3->pack_start($label, 0, 0, 10);
2678 my $eme = Gtk3::Entry->new();
2679 $eme->set_size_request(200, -1);
201a5120 2680 $eme->set_text($mailto);
71590b6a 2681 $hbox3->pack_start($eme, 0, 0, 0);
89a12446
DM
2682
2683
71590b6a
OB
2684 $vbox->pack_start($hbox1, 0, 0, 5);
2685 $vbox->pack_start($hbox2, 0, 0, 5);
2686 $vbox->pack_start($hbox3, 0, 0, 15);
89a12446
DM
2687
2688 $inbox->show_all;
2689
201a5120 2690 display_html();
89a12446
DM
2691
2692 set_next (undef, sub {
2693
2694 my $t1 = $pwe1->get_text;
2695 my $t2 = $pwe2->get_text;
2696
2697 if (length ($t1) < 5) {
71590b6a 2698 display_message("Password is too short.");
89a12446
DM
2699 $pwe1->grab_focus();
2700 return;
2701 }
2702
2703 if ($t1 ne $t2) {
71590b6a 2704 display_message("Password does not match.");
89a12446
DM
2705 $pwe1->grab_focus();
2706 return;
2707 }
2708
2709 my $t3 = $eme->get_text;
c82fffd8 2710 if ($t3 !~ m/^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
b11c55ff 2711 display_message("Email does not look like a valid address" .
89a12446
DM
2712 " (user\@domain.tld)");
2713 $eme->grab_focus();
2714 return;
a39bc1f2 2715 }
89a12446 2716
a39bc1f2 2717 if ($t3 eq 'mail@example.invalid') {
b11c55ff 2718 display_message("Please enter a valid Email address");
a39bc1f2
FG
2719 $eme->grab_focus();
2720 return;
89a12446
DM
2721 }
2722
2723 $password = $t1;
2724 $mailto = $t3;
2725
201a5120 2726 $step_number++;
89a12446
DM
2727 create_ipconf_view();
2728 });
2729
2730 $pwe1->grab_focus();
2731
2732}
2733
d92ada4e 2734my $installer_kmap;
89a12446
DM
2735sub create_country_view {
2736
71590b6a 2737 cleanup_view();
89a12446
DM
2738
2739 my $countryhash = $cmap->{countryhash};
2740 my $ctr = $cmap->{country};
2741
71590b6a
OB
2742 my $vbox2 = Gtk3::VBox->new(0, 0);
2743 $inbox->pack_start($vbox2, 1, 0, 0);
2744 my $vbox = Gtk3::VBox->new(0, 0);
2745 $vbox2->pack_start($vbox, 0, 0, 10);
89a12446 2746
71590b6a
OB
2747 my $w = Gtk3::Entry->new();
2748 $w->set_size_request(200, -1);
89a12446 2749
71590b6a
OB
2750 my $c = Gtk3::EntryCompletion->new();
2751 $c->set_text_column(0);
89a12446 2752 $c->set_minimum_key_length(0);
71590b6a
OB
2753 $c->set_popup_set_width(1);
2754 $c->set_inline_completion(1);
2755
2756 my $hbox2 = Gtk3::HBox->new(0, 0);
2757 my $label = Gtk3::Label->new("Time zone");
2758 $label->set_size_request(150, -1);
2759 $label->set_alignment(1, 0.5);
2760 $hbox2->pack_start($label, 0, 0, 10);
89a12446
DM
2761 update_zonelist ($hbox2);
2762
71590b6a
OB
2763 my $hbox3 = Gtk3::HBox->new(0, 0);
2764 $label = Gtk3::Label->new("Keyboard Layout");
2765 $label->set_size_request(150, -1);
2766 $label->set_alignment(1, 0.5);
2767 $hbox3->pack_start($label, 0, 0, 10);
89a12446 2768
bcbfab6b 2769 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2770 $kmapcb->set_size_request (200, -1);
2771 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2772 $kmapcb->append_text ($layout);
2773 }
2774
71590b6a 2775 update_layout($kmapcb);
89a12446
DM
2776 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2777
2778 $kmapcb->signal_connect ('changed' => sub {
2779 my $sel = $kmapcb->get_active_text();
d92ada4e 2780 $last_layout = $kmapcb->get_active();
89a12446
DM
2781 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2782 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2783 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
89a12446 2784 $keymap = $kmap;
07ec6825 2785
d92ada4e
SI
2786 return if (defined($installer_kmap) && $installer_kmap eq $kmap);
2787
2788 $installer_kmap = $keymap;
2789
07ec6825
SI
2790 if (! $opt_testmode) {
2791 syscmd ("setxkbmap $xkmap $xvar");
2663e171
SI
2792
2793 my $kbd_config = qq{
2794 XKBLAYOUT="$xkmap"
2795 XKBVARIANT="$xvar"
2796 BACKSPACE="guess"
2797 };
2798 $kbd_config =~ s/^\s+//gm;
2799
2800 run_in_background( sub {
2801 write_config($kbd_config, '/etc/default/keyboard');
2802 system("setupcon");
2803 });
07ec6825 2804 }
89a12446
DM
2805 }
2806 });
2807
2808 $w->signal_connect ('changed' => sub {
2809 my ($entry, $event) = @_;
2810 my $text = $entry->get_text;
2811
2812 if (my $cc = $countryhash->{lc($text)}) {
71590b6a 2813 update_zonelist($hbox2, $cc);
89a12446 2814 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
71590b6a 2815 update_layout($kmapcb, $kmap);
89a12446
DM
2816 }
2817 });
2818
2819 $w->signal_connect (key_press_event => sub {
2820 my ($entry, $event) = @_;
2821 my $text = $entry->get_text;
2822
7becc472
DM
2823 my $val = $event->get_keyval;
2824
2825 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2826 my $cc = $countryhash->{lc($text)};
1464c7c9 2827
89a12446
DM
2828 my $found = 0;
2829 my $compl;
7becc472 2830
4443aa27
DM
2831 if ($cc) {
2832 $found = 1;
2833 $compl = $ctr->{$cc}->{name};
2834 } else {
2835 foreach my $cc (keys %$ctr) {
2836 my $ct = $ctr->{$cc}->{name};
2837 if ($ct =~ m/^\Q$text\E.*$/i) {
2838 $found++;
2839 $compl = $ct;
2840 }
2841 last if $found > 1;
89a12446 2842 }
89a12446 2843 }
4443aa27 2844
89a12446 2845 if ($found == 1) {
7becc472 2846 $entry->set_text($compl);
3df718ea 2847 $c->complete();
89a12446
DM
2848 return undef;
2849 } else {
7becc472
DM
2850 #Gtk3::Gdk::beep();
2851 print chr(7); # beep ?
89a12446
DM
2852 }
2853
3df718ea
DM
2854 $c->complete();
2855
7becc472
DM
2856 my $buf = $w->get_buffer();
2857 $buf->insert_text(-1, '', -1); # popup selection
2858
89a12446
DM
2859 return 1;
2860 }
2861
2862 return undef;
2863 });
1464c7c9 2864
7becc472 2865 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2866 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2867 my $iter = $ls->append();
2868 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2869 }
2870 $c->set_model ($ls);
2871
968fa90b 2872 $w->set_completion ($c);
89a12446 2873
71590b6a 2874 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 2875
71590b6a
OB
2876 $label = Gtk3::Label->new("Country");
2877 $label->set_alignment(1, 0.5);
2878 $label->set_size_request(150, -1);
2879 $hbox->pack_start($label, 0, 0, 10);
2880 $hbox->pack_start($w, 0, 0, 0);
89a12446 2881
71590b6a
OB
2882 $vbox->pack_start($hbox, 0, 0, 5);
2883 $vbox->pack_start($hbox2, 0, 0, 5);
2884 $vbox->pack_start($hbox3, 0, 0, 5);
89a12446 2885
9d1f1ee3 2886 if ($country && $ctr->{$country}) {
89a12446
DM
2887 $w->set_text ($ctr->{$country}->{name});
2888 }
2889
2890 $inbox->show_all;
2891
201a5120 2892 display_html();
89a12446
DM
2893 set_next (undef, sub {
2894
2895 my $text = $w->get_text;
2896
2897 if (my $cc = $countryhash->{lc($text)}) {
2898 $country = $cc;
201a5120 2899 $step_number++;
89a12446
DM
2900 create_password_view();
2901 return;
2902 } else {
71590b6a 2903 display_message("Please select a country first.");
89a12446
DM
2904 $w->grab_focus();
2905 }
2906 });
2907
2908 $w->grab_focus();
2909}
2910
c6ed3b24
DM
2911my $target_hd_combo;
2912my $target_hd_label;
2913
bd3a2e26 2914my $hdoption_first_setup = 1;
c6ed3b24 2915
c7779156
FG
2916my $create_basic_grid = sub {
2917 my $grid = Gtk3::Grid->new();
2918 $grid->set_visible(1);
2919 $grid->set_column_spacing(10);
2920 $grid->set_row_spacing(10);
2921 $grid->set_hexpand(1);
2922
6d8b8564
TL
2923 $grid->set_margin_start(10);
2924 $grid->set_margin_end(20);
c7779156
FG
2925 $grid->set_margin_top(5);
2926 $grid->set_margin_bottom(5);
2927
2928 return $grid;
2929};
2930
2931my $create_label_widget_grid = sub {
2932 my ($labeled_widgets) = @_;
2933
2934 my $grid = &$create_basic_grid();
2935 my $row = 0;
2936
2937 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2938 my $widget = @$labeled_widgets[$i+1];
2939 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2940 $label->set_visible(1);
2941 $label->set_alignment (1, 0.5);
2942 $grid->attach($label, 0, $row, 1, 1);
2943 $widget->set_visible(1);
2944 $grid->attach($widget, 1, $row, 1, 1);
2945 $row++;
2946 }
2947
2948 return $grid;
2949};
2950
329a65a5 2951# only relevant for raid with its multipl diskX to diskY mappings.
ebf1e983 2952my $get_selected_hdsize = sub {
c07739f4 2953 my $hdsize = shift;
329a65a5
TL
2954 return $hdsize if defined($hdsize);
2955
2956 # compute the smallest disk size of the actually selected disks
2957 my $hd_count = scalar(@$hds);
2958 for (my $i = 0; $i < $hd_count; $i++) {
2959 my $cur_hd = $config_options->{"disksel$i"} // next;
2960 my $disksize = int(@$cur_hd[2] / (2 * 1024 * 1024.0)); # size in GB
2961 $hdsize //= $disksize;
2962 $hdsize = $disksize if $disksize < $hdsize;
c07739f4 2963 }
3bcf46e2
TL
2964
2965 if (my $cfg_hdsize = $config_options->{hdsize}) {
2966 # had the dialog open previously and set an even lower size than the disk selection allows
2967 $hdsize = $cfg_hdsize if $cfg_hdsize < $hdsize;
2968 }
ebf1e983
TL
2969 return $hdsize // 0; # fall back to zero, e.g., if none is selected hdsize cannot be any size
2970};
2971
2972my sub update_hdsize_adjustment {
2973 my ($adjustment, $hdsize) = @_;
2974
2975 $hdsize = $get_selected_hdsize->($hdsize);
2976 # expect that lower = 0 and step increments = 1 still are valid
2977 $adjustment->set_upper($hdsize + 1);
2978 $adjustment->set_value($hdsize);
2979}
c07739f4 2980
ebf1e983
TL
2981my sub create_hdsize_adjustment {
2982 my ($hdsize) = @_;
2983 $hdsize = $get_selected_hdsize->($hdsize);
2984 # params are: initial value, lower, upper, step increment, page increment, page size
c07739f4 2985 return Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
ebf1e983 2986}
c07739f4 2987
e2b003a6 2988my sub get_hdsize_spin_button {
c07739f4
SI
2989 my $hdsize = shift;
2990
2991 my $hdsize_entry_buffer = Gtk3::EntryBuffer->new(undef, 1);
ebf1e983 2992 my $hdsize_size_adj = create_hdsize_adjustment($hdsize);
c07739f4
SI
2993
2994 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2995 $spinbutton_hdsize->set_buffer($hdsize_entry_buffer);
2996 $spinbutton_hdsize->set_adjustment($hdsize_size_adj);
2997 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2998 return $spinbutton_hdsize;
2999};
3000
c7779156 3001my $create_raid_disk_grid = sub {
679c813c 3002 my ($hdsize_buttons) = @_;
c2ca8ba8 3003
04fa0758 3004 my $hd_count = scalar(@$hds);
c7779156 3005 my $disk_labeled_widgets = [];
04fa0758 3006 for (my $i = 0; $i < $hd_count; $i++) {
c7779156
FG
3007 my $disk_selector = Gtk3::ComboBoxText->new();
3008 $disk_selector->append_text("-- do not use --");
3009 $disk_selector->set_active(0);
3010 $disk_selector->set_visible(1);
c2ca8ba8 3011
c7779156 3012 foreach my $hd (@$hds) {
17fd908e 3013 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c7779156 3014 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
c7779156
FG
3015 }
3016
8d1ca71a
TL
3017 $disk_selector->{pve_disk_id} = $i;
3018 $disk_selector->signal_connect(changed => sub {
3019 my $w = shift;
3020 my $diskid = $w->{pve_disk_id};
3021 my $a = $w->get_active - 1;
3022 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
8d1ca71a 3023 for my $btn (@$hdsize_buttons) {
ebf1e983 3024 update_hdsize_adjustment($btn->get_adjustment());
8d1ca71a
TL
3025 }
3026 });
3027
bd3a2e26 3028 if ($hdoption_first_setup) {
c7779156
FG
3029 $disk_selector->set_active ($i+1) if $hds->[$i];
3030 } else {
3031 my $hdind = 0;
3032 if (my $cur_hd = $config_options->{"disksel$i"}) {
3033 foreach my $hd (@$hds) {
3034 if (@$hd[1] eq @$cur_hd[1]) {
3035 $disk_selector->set_active($hdind+1);
3036 last;
3037 }
3038 $hdind++;
3039 }
3040 }
3041 }
3042
3043 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
3044 }
3045
3235f39b
TL
3046 my $clear_all_button = Gtk3::Button->new('_Deselect All');
3047 if ($hd_count > 3) {
3048 $clear_all_button->signal_connect('clicked', sub {
3049 my $is_widget = 0;
3050 for my $disk_selector (@$disk_labeled_widgets) {
3051 $disk_selector->set_active(0) if $is_widget;
3052 $is_widget ^= 1;
3053 }
3054 });
3055 $clear_all_button->set_visible(1);
3056 }
3057
c7779156
FG
3058 my $scrolled_window = Gtk3::ScrolledWindow->new();
3059 $scrolled_window->set_hexpand(1);
04fa0758 3060 $scrolled_window->set_propagate_natural_height(1) if $hd_count > 4;
3235f39b
TL
3061
3062 my $diskgrid = $create_label_widget_grid->($disk_labeled_widgets);
3063
3064 $scrolled_window->add($diskgrid);
c7779156 3065 $scrolled_window->set_policy('never', 'automatic');
3235f39b 3066 $scrolled_window->set_visible(1);
0bc39c50 3067 $scrolled_window->set_min_content_height(190);
3235f39b
TL
3068
3069 my $vbox = Gtk3::Box->new('vertical', 0);
3070 $vbox->pack_start($scrolled_window, 1, 1, 10);
3071
3072 my $hbox = Gtk3::Box->new('horizontal', 0);
3073 $hbox->pack_end($clear_all_button, 0, 0, 20);
3074 $hbox->set_visible(1);
3075 $vbox->pack_end($hbox, 0, 0, 0);
c7779156 3076
3235f39b 3077 return $vbox;
c7779156
FG
3078};
3079
3080my $create_raid_advanced_grid = sub {
c07739f4 3081 my ($hdsize_btn) = @_;
c7779156 3082 my $labeled_widgets = [];
2cdba397 3083 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9, 13, 1);
6c99667a
FG
3084 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
3085 $spinbutton_ashift->signal_connect ("value-changed" => sub {
3086 my $w = shift;
3087 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
3088 });
3089 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 3090 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 3091 push @$labeled_widgets, "ashift";
6c99667a 3092 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
3093
3094 my $combo_compress = Gtk3::ComboBoxText->new();
3095 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
38aa09be 3096 my $comp_opts = ["on","off","lzjb","lz4", "lze", "gzip", "zstd"];
c7779156
FG
3097 foreach my $opt (@$comp_opts) {
3098 $combo_compress->append($opt, $opt);
3099 }
3100 $config_options->{compress} = "on" if !defined($config_options->{compress});
3101 $combo_compress->set_active_id($config_options->{compress});
3102 $combo_compress->signal_connect (changed => sub {
3103 my $w = shift;
3104 $config_options->{compress} = $w->get_active_text();
3105 });
3106 push @$labeled_widgets, "compress";
3107 push @$labeled_widgets, $combo_compress;
3108
3109 my $combo_checksum = Gtk3::ComboBoxText->new();
3110 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
3111 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
3112 foreach my $opt (@$csum_opts) {
3113 $combo_checksum->append($opt, $opt);
3114 }
3115 $config_options->{checksum} = "on" if !($config_options->{checksum});
3116 $combo_checksum->set_active_id($config_options->{checksum});
3117 $combo_checksum->signal_connect (changed => sub {
3118 my $w = shift;
3119 $config_options->{checksum} = $w->get_active_text();
3120 });
3121 push @$labeled_widgets, "checksum";
3122 push @$labeled_widgets, $combo_checksum;
3123
3124 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
3125 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
3126 $spinbutton_copies->signal_connect ("value-changed" => sub {
3127 my $w = shift;
3128 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
3129 });
3130 $config_options->{copies} = 1 if !defined($config_options->{copies});
3131 $spinbutton_copies->set_value($config_options->{copies});
3132 push @$labeled_widgets, "copies", $spinbutton_copies;
3133
c07739f4 3134 push @$labeled_widgets, "hdsize", $hdsize_btn;
2cdba397 3135 return $create_label_widget_grid->($labeled_widgets);;
c7779156
FG
3136};
3137
679c813c
SI
3138my $create_btrfs_raid_advanced_grid = sub {
3139 my ($hdsize_btn) = @_;
3140 my $labeled_widgets = [];
3141 push @$labeled_widgets, "hdsize", $hdsize_btn;
3142 return $create_label_widget_grid->($labeled_widgets);;
3143};
3144
aed81ff0 3145sub create_hdoption_view {
aed81ff0
DM
3146 my $dialog = Gtk3::Dialog->new();
3147
3148 $dialog->set_title("Harddisk options");
3149
3150 $dialog->add_button("_OK", 1);
3151
3152 my $contarea = $dialog->get_content_area();
3153
3154 my $hbox2 = Gtk3::Box->new('horizontal', 0);
6d8b8564 3155 $contarea->pack_start($hbox2, 1, 1, 5);
aed81ff0
DM
3156
3157 my $grid = Gtk3::Grid->new();
3158 $grid->set_column_spacing(10);
3159 $grid->set_row_spacing(10);
1464c7c9 3160
6d8b8564 3161 $hbox2->pack_start($grid, 1, 0, 5);
c6ed3b24
DM
3162
3163 my $row = 0;
3164
aed81ff0 3165 # Filesystem type
71590b6a 3166 my $label0 = Gtk3::Label->new("Filesystem");
aed81ff0 3167 $label0->set_alignment (1, 0.5);
c6ed3b24 3168 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 3169
bcbfab6b 3170 my $fstypecb = Gtk3::ComboBoxText->new();
2cdba397
TL
3171 my $fstype = [
3172 'ext4',
3173 'xfs',
3174 'zfs (RAID0)',
3175 'zfs (RAID1)',
3176 'zfs (RAID10)',
3177 'zfs (RAIDZ-1)',
3178 'zfs (RAIDZ-2)',
3179 'zfs (RAIDZ-3)',
3180 ];
6f52fc3d 3181 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 3182 if $setup->{enable_btrfs};
aed81ff0 3183
c6ed3b24
DM
3184 my $tcount = 0;
3185 foreach my $tmp (@$fstype) {
3186 $fstypecb->append_text($tmp);
2cdba397 3187 $fstypecb->set_active ($tcount) if $config_options->{filesys} eq $tmp;
c6ed3b24
DM
3188 $tcount++;
3189 }
3190
3191 $grid->attach($fstypecb, 1, $row, 1, 1);
3192
3193 $hbox2->show_all();
3194
3195 $row++;
3196
c7779156
FG
3197 my $sep = Gtk3::HSeparator->new();
3198 $sep->set_visible(1);
3199 $grid->attach($sep, 0, $row, 2, 1);
3200 $row++;
aed81ff0 3201
af35966c 3202 my $hw_raid_note = Gtk3::Label->new(""); # text will be set below, before making it visible
f0a0d90b
TL
3203 $hw_raid_note->set_line_wrap(1);
3204 $hw_raid_note->set_max_width_chars(30);
f0a0d90b
TL
3205 $hw_raid_note->set_visible(0);
3206 $grid->attach($hw_raid_note, 0, $row++, 2, 1);
3207
c7779156 3208 my $hdsize_labeled_widgets = [];
aed81ff0 3209
c7779156 3210 # size compute
c6ed3b24 3211 my $hdsize = 0;
aed81ff0 3212 if ( -b $target_hd) {
c2ca8ba8 3213 $hdsize = int(hd_size ($target_hd) / (1024 * 1024.0)); # size in GB
c6ed3b24 3214 } elsif ($target_hd) {
c2ca8ba8 3215 $hdsize = int((-s $target_hd) / (1024 * 1024 * 1024.0));
aed81ff0
DM
3216 }
3217
e2b003a6 3218 my $spinbutton_hdsize_nonraid = get_hdsize_spin_button($hdsize);
c07739f4
SI
3219 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize_nonraid;
3220 my $spinbutton_hdsize = $spinbutton_hdsize_nonraid;
aed81ff0
DM
3221
3222 my $entry_swapsize = Gtk3::Entry->new();
3223 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
3224 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 3225 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 3226 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
3227
3228 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
3229 if ($setup->{product} eq 'pve') {
3230 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
3231 $entry_maxroot->signal_connect (key_press_event => \&check_float);
3232 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
3233 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
3234 }
aed81ff0
DM
3235
3236 my $entry_minfree = Gtk3::Entry->new();
034f75e4 3237 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0 3238 $entry_minfree->signal_connect (key_press_event => \&check_float);
e093944c 3239 $entry_minfree->set_text($config_options->{minfree}) if defined($config_options->{minfree});
c7779156 3240 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 3241
b6e875ca
DM
3242 my $entry_maxvz;
3243 if ($setup->{product} eq 'pve') {
3244 $entry_maxvz = Gtk3::Entry->new();
3245 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
3246 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 3247 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
3248 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
3249 }
c7779156 3250
e2b003a6
TL
3251 my $spinbutton_hdsize_zfs = get_hdsize_spin_button($hdsize);
3252 my $spinbutton_hdsize_btrfs = get_hdsize_spin_button($hdsize);
679c813c 3253 my $hdsize_buttons = [ $spinbutton_hdsize_zfs, $spinbutton_hdsize_btrfs ];
c7779156
FG
3254 my $options_stack = Gtk3::Stack->new();
3255 $options_stack->set_visible(1);
3256 $options_stack->set_hexpand(1);
3257 $options_stack->set_vexpand(1);
679c813c 3258 $options_stack->add_titled(&$create_raid_disk_grid($hdsize_buttons), "raiddisk", "Disk Setup");
c7779156 3259 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
c07739f4 3260 $options_stack->add_titled(&$create_raid_advanced_grid($spinbutton_hdsize_zfs), "raidzfsadvanced", "Advanced Options");
679c813c 3261 $options_stack->add_titled(&$create_btrfs_raid_advanced_grid($spinbutton_hdsize_btrfs), "raidbtrfsadvanced", "Advanced Options");
c7779156
FG
3262 $options_stack->set_visible_child_name("raiddisk");
3263 my $options_stack_switcher = Gtk3::StackSwitcher->new();
3264 $options_stack_switcher->set_halign('center');
3265 $options_stack_switcher->set_stack($options_stack);
3266 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
3267 $row++;
3268 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 3269 $row++;
aed81ff0 3270
bd3a2e26 3271 $hdoption_first_setup = 0;
c7779156
FG
3272
3273 my $switch_view = sub {
3274 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
af35966c 3275 my $is_zfs = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 3276
c7779156
FG
3277 $target_hd_combo->set_visible(!$raid);
3278 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
3279 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
af35966c
TL
3280
3281 if ($raid) {
3282 my $msg = "<b>Note</b>: " . ($is_zfs
3283 ? "ZFS is not compatible with hardware RAID controllers, for details see the documentation."
78164ad6 3284 : "BTRFS integration in $setup->{fullname} is a technology preview!"
af35966c
TL
3285 );
3286 $hw_raid_note->set_markup($msg);
3287 }
f0a0d90b 3288 $hw_raid_note->set_visible($raid);
679c813c 3289 $options_stack_switcher->set_visible($raid);
af35966c 3290 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($is_zfs);
679c813c 3291 $options_stack->get_child_by_name("raidbtrfsadvanced")->set_visible(!$is_zfs);
c7779156 3292 if ($raid) {
c6ed3b24 3293 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 3294 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 3295 } else {
c6ed3b24
DM
3296 $target_hd_label->set_text("Target Harddisk: ");
3297 }
c07739f4
SI
3298
3299 if ($raid) {
679c813c 3300 $spinbutton_hdsize = $is_zfs ? $spinbutton_hdsize_zfs : $spinbutton_hdsize_btrfs;
c07739f4
SI
3301 } else {
3302 $spinbutton_hdsize = $spinbutton_hdsize_nonraid;
3303 }
3304
c7779156
FG
3305 my (undef, $pref_width) = $dialog->get_preferred_width();
3306 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 3307 $pref_height = 750 if $pref_height > 750;
c7779156 3308 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
3309 };
3310
c7779156 3311 &$switch_view();
f7b853d1
DM
3312
3313 $fstypecb->signal_connect (changed => sub {
3314 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 3315 &$switch_view();
f7b853d1
DM
3316 });
3317
95844cc6
TL
3318 my $sep2 = Gtk3::HSeparator->new();
3319 $sep2->set_visible(1);
3320 $contarea->pack_end($sep2, 1, 1, 10);
3321
c6ed3b24 3322 $dialog->show();
aed81ff0
DM
3323
3324 $dialog->run();
3325
3326 my $get_float = sub {
3327 my ($entry) = @_;
3328
3329 my $text = $entry->get_text();
3330 return undef if !defined($text);
3331
3332 $text =~ s/^\s+//;
3333 $text =~ s/\s+$//;
3334
3335 return undef if $text !~ m/^\d+(\.\d+)?$/;
3336
3337 return $text;
3338 };
3339
3340 my $tmp;
3341
3342 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
3343 $config_options->{hdsize} = $tmp;
3344 } else {
3345 delete $config_options->{hdsize};
3346 }
3347
3348 if (defined($tmp = &$get_float($entry_swapsize))) {
3349 $config_options->{swapsize} = $tmp;
3350 } else {
3351 delete $config_options->{swapsize};
3352 }
3353
3354 if (defined($tmp = &$get_float($entry_maxroot))) {
3355 $config_options->{maxroot} = $tmp;
3356 } else {
3357 delete $config_options->{maxroot};
3358 }
3359
3360 if (defined($tmp = &$get_float($entry_minfree))) {
3361 $config_options->{minfree} = $tmp;
3362 } else {
3363 delete $config_options->{minfree};
3364 }
3365
b6e875ca 3366 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
3367 $config_options->{maxvz} = $tmp;
3368 } else {
3369 delete $config_options->{maxvz};
3370 }
3371
3372 $dialog->destroy();
3373}
3374
121ebc59 3375my $get_raid_devlist = sub {
c6ed3b24
DM
3376
3377 my $dev_name_hash = {};
3378
3379 my $devlist = [];
5f8e86d5 3380 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24 3381 if (my $hd = $config_options->{"disksel$i"}) {
17fd908e 3382 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
1464c7c9 3383 die "device '$devname' is used more than once\n"
c6ed3b24
DM
3384 if $dev_name_hash->{$devname};
3385 $dev_name_hash->{$devname} = $hd;
3386 push @$devlist, $hd;
3387 }
3388 }
3389
121ebc59
DM
3390 return $devlist;
3391};
3392
14aacec8
FG
3393sub zfs_mirror_size_check {
3394 my ($expected, $actual) = @_;
3395
3396 die "mirrored disks must have same size\n"
3397 if abs($expected - $actual) > $expected / 10;
3398}
3399
5ea943cf
SI
3400sub legacy_bios_4k_check {
3401 my ($lbs) = @_;
3402 die "Booting from 4kn drive in legacy BIOS mode is not supported.\n"
3403 if (($boot_type ne 'efi') && ($lbs == 4096));
3404}
3405
121ebc59 3406sub get_zfs_raid_setup {
121ebc59
DM
3407 my $filesys = $config_options->{filesys};
3408
3409 my $devlist = &$get_raid_devlist();
3410
224bb7b0 3411 my $diskcount = scalar(@$devlist);
0cfa502c 3412 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24
DM
3413
3414 my $cmd= '';
3415 if ($filesys eq 'zfs (RAID0)') {
c6ed3b24 3416 foreach my $hd (@$devlist) {
5ea943cf 3417 legacy_bios_4k_check(@$hd[4]);
c6ed3b24
DM
3418 $cmd .= " @$hd[1]";
3419 }
3420 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 3421 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 3422 $cmd .= ' mirror ';
269c66a6 3423 my $hd = @$devlist[0];
14aacec8 3424 my $expected_size = @$hd[2]; # all disks need approximately same size
eaeccd9f 3425 foreach my $hd (@$devlist) {
14aacec8 3426 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3427 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3428 $cmd .= " @$hd[1]";
c6ed3b24
DM
3429 }
3430 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 3431 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 3432 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 3433
224bb7b0 3434 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
3435 my $hd1 = @$devlist[$i];
3436 my $hd2 = @$devlist[$i+1];
14aacec8 3437 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
5ea943cf
SI
3438 legacy_bios_4k_check(@$hd1[4]);
3439 legacy_bios_4k_check(@$hd2[4]);
c6ed3b24
DM
3440 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
3441 }
3442
3443 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3444 my $level = $1;
3445 my $mindisks = 2 + $level;
0cfa502c 3446 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 3447 my $hd = @$devlist[0];
14aacec8 3448 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 3449 $cmd .= " raidz$level";
eaeccd9f 3450 foreach my $hd (@$devlist) {
14aacec8 3451 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3452 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3453 $cmd .= " @$hd[1]";
c6ed3b24
DM
3454 }
3455 } else {
3456 die "unknown zfs mode '$filesys'\n";
3457 }
3458
82695821 3459 return ($devlist, $cmd);
c6ed3b24
DM
3460}
3461
121ebc59
DM
3462sub get_btrfs_raid_setup {
3463
3464 my $filesys = $config_options->{filesys};
3465
3466 my $devlist = &$get_raid_devlist();
3467
3468 my $diskcount = scalar(@$devlist);
0cfa502c 3469 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
3470
3471 my $mode;
3472
3473 if ($diskcount == 1) {
3474 $mode = 'single';
3475 } else {
3476 if ($filesys eq 'btrfs (RAID0)') {
3477 $mode = 'raid0';
3478 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 3479 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
3480 $mode = 'raid1';
3481 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 3482 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
3483 $mode = 'raid10';
3484 } else {
9d69f3d3 3485 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
3486 }
3487 }
3488
3489 return ($devlist, $mode);
3490}
3491
218a4b6b 3492my $last_hd_selected = 0;
89a12446
DM
3493sub create_hdsel_view {
3494
451b1da5 3495 $prev_btn->set_sensitive(1); # enable previous button at this point
201a5120 3496
71590b6a 3497 cleanup_view();
89a12446 3498
71590b6a
OB
3499 my $vbox = Gtk3::VBox->new(0, 0);
3500 $inbox->pack_start($vbox, 1, 0, 0);
3501 my $hbox = Gtk3::HBox->new(0, 0);
3502 $vbox->pack_start($hbox, 0, 0, 10);
968fa90b 3503
17fd908e 3504 my ($disk, $devname, $size, $model, $logical_bsize) = @{@$hds[0]};
9227a70f 3505 $target_hd = $devname if !defined($target_hd);
89a12446 3506
71590b6a
OB
3507 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3508 $hbox->pack_start($target_hd_label, 0, 0, 0);
89a12446 3509
bcbfab6b 3510 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 3511
1aa5bd02 3512 foreach my $hd (@$hds) {
17fd908e 3513 ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c2ca8ba8 3514 $target_hd_combo->append_text(get_device_desc($devname, $size, $model));
1aa5bd02 3515 }
89a12446 3516
90af1603
OB
3517 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3518 if ($raid) {
3519 $target_hd_label->set_text("Target: $config_options->{filesys} ");
3520 $target_hd_combo->set_visible(0);
3521 $target_hd_combo->set_no_show_all(1);
3522 }
218a4b6b 3523 $target_hd_combo->set_active($last_hd_selected);
71590b6a 3524 $target_hd_combo->signal_connect(changed => sub {
1aa5bd02
DM
3525 $a = shift->get_active;
3526 my ($disk, $devname) = @{@$hds[$a]};
3b959bef 3527 $last_hd_selected = $a;
1aa5bd02 3528 $target_hd = $devname;
1aa5bd02 3529 });
1464c7c9 3530
71590b6a 3531 $hbox->pack_start($target_hd_combo, 0, 0, 10);
aed81ff0 3532
71590b6a 3533 my $options = Gtk3::Button->new('_Options');
aed81ff0
DM
3534 $options->signal_connect (clicked => \&create_hdoption_view);
3535 $hbox->pack_start ($options, 0, 0, 0);
3536
89a12446
DM
3537
3538 $inbox->show_all;
3539
201a5120 3540 display_html();
c6ed3b24 3541
71590b6a 3542 set_next(undef, sub {
c6ed3b24
DM
3543
3544 if ($config_options->{filesys} =~ m/zfs/) {
a7d40341 3545 my ($devlist) = eval { get_zfs_raid_setup() };
c6ed3b24 3546 if (my $err = $@) {
303dfb2c
TL
3547 display_message("Warning: $err\nPlease fix ZFS setup first.");
3548 return;
c6ed3b24 3549 }
303dfb2c 3550 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
121ebc59 3551 } elsif ($config_options->{filesys} =~ m/btrfs/) {
a7d40341 3552 my ($devlist) = eval { get_btrfs_raid_setup() };
121ebc59 3553 if (my $err = $@) {
303dfb2c
TL
3554 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3555 return;
121ebc59 3556 }
303dfb2c 3557 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
c6ed3b24 3558 } else {
5ea943cf
SI
3559 eval { legacy_bios_4k_check(logical_blocksize($target_hd)) };
3560 if (my $err = $@) {
3561 display_message("Warning: $err\n");
3562 return;
3563 }
a7d40341 3564 $config_options->{target_hds} = [ $target_hd ];
c6ed3b24 3565 }
303dfb2c
TL
3566
3567 $step_number++;
3568 create_country_view();
c6ed3b24 3569 });
89a12446
DM
3570}
3571
3572sub create_extract_view {
3573
71590b6a 3574 cleanup_view();
89a12446 3575
550958aa
DM
3576 display_info();
3577
201a5120 3578 $next->set_sensitive(0);
ac3ee85b
TL
3579 $prev_btn->set_sensitive(0);
3580 $prev_btn->hide();
89a12446 3581
71590b6a 3582 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 3583 $inbox->pack_start ($vbox, 1, 0, 0);
71590b6a 3584 my $hbox = Gtk3::HBox->new(0, 0);
53986d77 3585 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3586
71590b6a 3587 my $vbox2 = Gtk3::VBox->new(0, 0);
89a12446
DM
3588 $hbox->pack_start ($vbox2, 0, 0, 0);
3589
71590b6a 3590 $progress_status = Gtk3::Label->new('');
89a12446 3591 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3592
7becc472 3593 $progress = Gtk3::ProgressBar->new;
45feca6f 3594 $progress->set_show_text(1);
7becc472 3595 $progress->set_size_request (600, -1);
89a12446 3596
71590b6a 3597 $vbox2->pack_start($progress, 0, 0, 0);
89a12446 3598
201a5120 3599 $inbox->show_all();
89a12446
DM
3600
3601 my $tdir = $opt_testmode ? "target" : "/target";
3602 mkdir $tdir;
97980bf2 3603 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446 3604
71590b6a 3605 eval { extract_data($base, $tdir); };
89a12446
DM
3606 my $err = $@;
3607
201a5120 3608 $next->set_sensitive(1);
89a12446 3609
71590b6a 3610 set_next("_Reboot", sub { exit (0); } );
89a12446 3611
296cf41f 3612 if ($err) {
201a5120
OB
3613 display_html("fail.htm");
3614 display_error($err);
296cf41f 3615 } else {
201a5120
OB
3616 cleanup_view();
3617 display_html("success.htm");
dfc02f3c
TL
3618
3619 if ($config_options->{autoreboot}) {
3620 Glib::Timeout->add(1000, sub {
3621 if ($autoreboot_seconds > 0) {
3622 $autoreboot_seconds--;
3623 display_html("success.htm");
3624 } else {
3625 exit(0);
3626 }
3627 });
3628 }
296cf41f 3629 }
89a12446
DM
3630}
3631
89a12446
DM
3632sub create_intro_view {
3633
451b1da5 3634 $prev_btn->set_sensitive(0);
201a5120
OB
3635
3636 cleanup_view();
89a12446 3637
ca951e77 3638 if (int($total_memory) < 1024) {
3befbf97 3639 display_error("Less than 1 GiB of usable memory detected, installation will probably fail.\n\n".
c2f72dd6 3640 "See 'System Requirements' in the $setup->{fullname} documentation.");
2b85ee1b
OB
3641 }
3642
bdeca872
DM
3643 if ($setup->{product} eq 'pve') {
3644 eval {
3645 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3646 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
2780ea4f 3647 display_error("No support for KVM virtualization detected.\n\n" .
bdeca872
DM
3648 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3649 }
3650 };
3651 }
7fff0d85 3652
201a5120 3653 display_html();
89a12446 3654
201a5120 3655 $step_number++;
71590b6a 3656 set_next("I a_gree", \&create_hdsel_view);
89a12446
DM
3657}
3658
71590b6a 3659$ipconf = get_ip_config();
89a12446 3660
9d1f1ee3 3661$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3662
3663# read country, kmap and timezone infos
71590b6a 3664$cmap = read_cmap();
89a12446 3665
9d1f1ee3
FG
3666if (!defined($cmap->{country}->{$country})) {
3667 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3668 $country = undef;
3669}
3670
89a12446
DM
3671create_main_window ();
3672
ff2ce71c
FG
3673my $initial_error = 0;
3674
89a12446
DM
3675if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3676 print "no hardisks found\n";
ff2ce71c 3677 $initial_error = 1;
201a5120 3678 display_html("nohds.htm");
71590b6a 3679 set_next("Reboot", sub { exit(0); } );
89a12446 3680} else {
89a12446
DM
3681 foreach my $hd (@$hds) {
3682 my ($disk, $devname) = @$hd;
3683 next if $devname =~ m|^/dev/md\d+$|;
3684 print "found Disk$disk N:$devname\n";
3685 }
89a12446
DM
3686}
3687
72836708
FG
3688if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3689 print "no network interfaces found\n";
3690 $initial_error = 1;
201a5120 3691 display_html("nonics.htm");
71590b6a 3692 set_next("Reboot", sub { exit(0); } );
72836708
FG
3693}
3694
ff2ce71c
FG
3695create_intro_view () if !$initial_error;
3696
7becc472 3697Gtk3->main;
89a12446 3698
0e631479
SI
3699# reap left over zombie processes
3700while ((my $child = waitpid(-1, POSIX::WNOHANG)) > 0) {
3701 print "reaped child $child\n";
3702}
3703
89a12446 3704exit 0;