]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
zfs: fix wrong command reference in error message
[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));
f0b70762
TL
968
969 my ($hard_limit, $soft_limit) = (2, 8);
970
971 die "root disk '$target_dev' too small (${hdgb} GB < $hard_limit GB)\n" if $hdgb < $hard_limit;
972 if ($hdgb < $soft_limit) {
973 my $response = display_prompt(
974 "Root disk space ${hdgb} GB is below recommended minimum space of $soft_limit GB,"
975 ." installation might not be successful! Continue?"
976 );
977 die "root disk '$target_dev' too small (${hdgb} GB < $soft_limit GB), and warning not accepted.\n"
978 if $response ne 'ok';
979 }
980
c6ed3b24 981
1250a6d5 982 syscmd("sgdisk -Z ${target_dev}");
34e44994 983
43b5216c 984 # 1 - BIOS boot partition (Grub Stage2): first free 1M
118d4f40 985 # 2 - EFI ESP: next free 512M
43b5216c 986 # 3 - OS/Data partition: rest, up to $maxhdsize in MB
c6ed3b24
DM
987
988 my $grubbootdev = get_partition_dev($target_dev, 1);
989 my $efibootdev = get_partition_dev($target_dev, 2);
a2876e48 990 my $osdev = get_partition_dev ($target_dev, 3);
aed81ff0 991
43b5216c 992 my $pcmd = ['sgdisk'];
89a12446 993
118d4f40
SI
994 my $pnum = 2;
995 push @$pcmd, "-n${pnum}:1M:+512M", "-t$pnum:EF00";
35be9ba7 996
f810f5d0 997 $pnum = 3;
118d4f40 998 push @$pcmd, "-n${pnum}:513M:${restricted_hdsize_mb}", "-t$pnum:$ptype";
35be9ba7 999
f810f5d0 1000 push @$pcmd, $target_dev;
b282cfe8 1001
118d4f40 1002 my $os_size = $hdsize - 513*1024; # 512M efi + 1M bios_boot + 1M alignment
89a12446 1003
f810f5d0
DM
1004 syscmd($pcmd) == 0 ||
1005 die "unable to partition harddisk '${target_dev}'\n";
89a12446 1006
5ea943cf 1007 my $blocksize = logical_blocksize($target_dev);
118d4f40 1008
5ea943cf
SI
1009 if ($blocksize != 4096) {
1010 $pnum = 1;
1011 $pcmd = ['sgdisk', '-a1', "-n$pnum:34:2047", "-t$pnum:EF02" , $target_dev];
1012
1013 syscmd($pcmd) == 0 ||
1014 die "unable to create bios_boot partition '${target_dev}'\n";
1015 }
118d4f40 1016
dc4ad419
FG
1017 &$udevadm_trigger_block();
1018
1019 foreach my $part ($efibootdev, $osdev) {
1020 syscmd("dd if=/dev/zero of=$part bs=1M count=256") if -b $part;
1021 }
1022
f810f5d0
DM
1023 return ($os_size, $osdev, $efibootdev);
1024}
5c06ced5 1025
4566af04
TL
1026sub get_pv_list_from_vgname {
1027 my ($vgname) = @_;
1028
1029 my $res;
1030
1031 my $parser = sub {
1032 my $line = shift;
1033 $line =~ s/^\s+//;
1034 $line =~ s/\s+$//;
1035 return if !$line;
1036 my ($pv, $vg_uuid) = split(/\s+/, $line);
1037
1038 if (!defined($res->{$vg_uuid}->{pvs})) {
1039 $res->{$vg_uuid}->{pvs} = "$pv";
1040 } else {
1041 $res->{$vg_uuid}->{pvs} .= ", $pv";
1042 }
1043 };
1044 run_command("pvs --noheadings -o pv_name,vg_uuid -S vg_name='$vgname'", $parser, undef, 1);
1045
1046 return $res;
1047}
1048
1049sub ask_existing_vg_rename_or_abort {
1050 my ($vgname) = @_;
1051
1052 # this normally only happens if one put a disk with a PVE installation in
1053 # this server and that disk is not the installation target.
1054 my $duplicate_vgs = get_pv_list_from_vgname($vgname);
1055 return if !$duplicate_vgs;
1056
1057 my $message = "Detected existing '$vgname' Volume Group(s)! Do you want to:\n";
1058
1059 for my $vg_uuid (keys %$duplicate_vgs) {
1060 my $vg = $duplicate_vgs->{$vg_uuid};
1061
1062 # no high randomnes properties, but this is only for the cases where
1063 # we either have multiple "$vgname" vgs from multiple old PVE disks, or
1064 # we have a disk with both a "$vgname" and "$vgname-old"...
1065 my $short_uid = sprintf "%08X", rand(0xffffffff);
1066 $vg->{new_vgname} = "$vgname-OLD-$short_uid";
1067
1068 $message .= "rename VG backed by PV '$vg->{pvs}' to '$vg->{new_vgname}'\n";
1069 }
1070 $message .= "or cancel the installation?";
1071
ebd6070d 1072 my $response = display_prompt($message);
4566af04
TL
1073
1074 if ($response eq 'ok') {
1075 for my $vg_uuid (keys %$duplicate_vgs) {
1076 my $vg = $duplicate_vgs->{$vg_uuid};
1077 my $new_vgname = $vg->{new_vgname};
1078
1079 syscmd("vgrename $vg_uuid $new_vgname") == 0 ||
1080 die "could not rename VG from '$vg->{pvs}' ($vg_uuid) to '$new_vgname'!\n";
1081 }
1082 } else {
1083 set_next("_Reboot", sub { exit (0); } );
1084 display_html("fail.htm");
1085 die "Cancled installation by user, due to already existing volume group '$vgname'\n";
1086 }
1087}
1088
c6ed3b24
DM
1089sub create_lvm_volumes {
1090 my ($lvmdev, $os_size, $swap_size) = @_;
7bc4f6bd 1091
f7d18efd
DM
1092 my $vgname = $setup->{product};
1093
4566af04
TL
1094 ask_existing_vg_rename_or_abort($vgname);
1095
f7d18efd
DM
1096 my $rootdev = "/dev/$vgname/root";
1097 my $datadev = "/dev/$vgname/data";
9bb301fb 1098 my $swapfile;
84761f93 1099
2df572ae 1100 # we use --metadatasize 250k, which results in "pe_start = 512"
c6ed3b24 1101 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
71590b6a 1102 syscmd("/sbin/pvcreate --metadatasize 250k -y -ff $lvmdev") == 0 ||
eb4b1e56 1103 die "unable to initialize physical volume $lvmdev\n";
71590b6a 1104 syscmd("/sbin/vgcreate $vgname $lvmdev") == 0 ||
f7d18efd 1105 die "unable to create volume group '$vgname'\n";
89a12446 1106
cb776c9a 1107 my $hdgb = int($os_size / (1024 * 1024));
d78e1947
TL
1108
1109 # always leave some space at the end to avoid roudning issues with LVM's physical extent (PE)
1110 # size of 4 MB.
1111 my $space = $hdgb <= 32 ? 4 * 1024 : (($hdgb > 128 ? 16 : $hdgb / 8) * 1024 * 1024);
89a12446 1112
b6e875ca 1113 my $rootsize;
76e9fa40 1114 my $datasize = 0;
89a12446 1115
b6e875ca 1116 if ($setup->{product} eq 'pve') {
89a12446 1117
1a5fa7b0 1118 my $maxroot_mb;
b6e875ca 1119 if ($config_options->{maxroot}) {
1a5fa7b0 1120 $maxroot_mb = $config_options->{maxroot} * 1024;
b6e875ca 1121 } else {
1a5fa7b0 1122 $maxroot_mb = 96 * 1024;
b6e875ca 1123 }
7bc4f6bd 1124
cb776c9a 1125 my $rest = $os_size - $swap_size;
1a5fa7b0
TL
1126 my $rest_mb = int($rest / 1024);
1127
1128 my $rootsize_mb;
1129 if ($rest_mb < 12 * 1024) {
1130 # no point in wasting space, try to get us actually installed and align down to 4 MB
1131 $rootsize_mb = ($rest_mb - 0.1) & ~3;
1132 } elsif ($rest_mb < 48 * 1024) {
1133 my $masked = int($rest_mb / 2) & ~3; # align down to 4 MB
1134 $rootsize_mb = $masked;
b6e875ca 1135 } else {
1a5fa7b0 1136 $rootsize_mb = $rest_mb / 4 + 12 * 1024;
cb776c9a
TL
1137 }
1138
1a5fa7b0
TL
1139 $rootsize_mb = $maxroot_mb if $rootsize_mb > $maxroot_mb;
1140 $rootsize = int($rootsize_mb * 1024);
cb776c9a
TL
1141
1142 $rest -= $rootsize; # in KB
1143
1144 my $minfree = $space;
1145 if (defined(my $cfg_minfree = $config_options->{minfree})) {
1146 $minfree = $cfg_minfree * 1024 * 1024 >= $rest ? $space : $cfg_minfree * 1024 * 1024;
b6e875ca
DM
1147 }
1148
1a5fa7b0 1149 $rest = int($rest - $minfree) & ~0xFFF; # align down to 4 MB boundaries
b6e875ca 1150
cb776c9a
TL
1151 if (defined(my $maxvz = $config_options->{maxvz})) {
1152 $rest = $maxvz * 1024 * 1024 <= $rest ? $maxvz * 1024 * 1024 : $rest;
b6e875ca 1153 }
7bc4f6bd 1154
b6e875ca
DM
1155 $datasize = $rest;
1156
1157 } else {
e093944c 1158 my $minfree = defined($config_options->{minfree}) ? $config_options->{minfree}*1024*1024 : $space;
cacd302a 1159 $rootsize = int($os_size - $minfree - $swap_size); # in KB
5a5e1742 1160 $rootsize &= ~0xFFF; # align down to 4 MB boundaries
c6ed3b24 1161 }
7bc4f6bd 1162
9bb301fb 1163 if ($swap_size) {
defb6756 1164 syscmd("/sbin/lvcreate -Wy --yes -L${swap_size}K -nswap $vgname") == 0 ||
9bb301fb
FG
1165 die "unable to create swap volume\n";
1166
1167 $swapfile = "/dev/$vgname/swap";
1168 }
89a12446 1169
defb6756 1170 syscmd("/sbin/lvcreate -Wy --yes -L${rootsize}K -nroot $vgname") == 0 ||
eb4b1e56 1171 die "unable to create root volume\n";
89a12446 1172
76e9fa40 1173 if ($datasize > 4 * 1024 * 1024) {
d1969047
FG
1174 my $metadatasize = $datasize/100; # default 1% of data
1175 $metadatasize = 1024*1024 if $metadatasize < 1024*1024; # but at least 1G
1176 $metadatasize = 16*1024*1024 if $metadatasize > 16*1024*1024; # but at most 16G
1177
1178 # otherwise the metadata is taken out of $minfree
76e9fa40 1179 $datasize -= 2 * $metadatasize;
d1969047
FG
1180
1181 # 1 4MB PE to allow for rounding
76e9fa40 1182 $datasize -= 4 * 1024;
d1969047 1183
defb6756 1184 syscmd("/sbin/lvcreate -Wy --yes -L${datasize}K -ndata $vgname") == 0 ||
b6e875ca 1185 die "unable to create data volume\n";
89a12446 1186
71590b6a 1187 syscmd("/sbin/lvconvert --yes --type thin-pool --poolmetadatasize ${metadatasize}K $vgname/data") == 0 ||
b6e875ca
DM
1188 die "unable to create data thin-pool\n";
1189 } else {
cb776c9a
TL
1190 if ($setup->{product} eq 'pve' && !defined($config_options->{maxvz})) {
1191 display_message("Skipping auto-creation of LVM thinpool for guest data due to low space.");
1192 }
b6e875ca
DM
1193 $datadev = undef;
1194 }
5fd81672 1195
71590b6a 1196 syscmd("/sbin/vgchange -a y $vgname") == 0 ||
eb4b1e56 1197 die "unable to activate volume group\n";
7bc4f6bd 1198
b6e875ca 1199 return ($rootdev, $swapfile, $datadev);
c6ed3b24 1200}
7bc4f6bd 1201
c6ed3b24
DM
1202sub compute_swapsize {
1203 my ($hdsize) = @_;
89a12446 1204
c6ed3b24 1205 my $hdgb = int($hdsize/(1024*1024));
5c06ced5 1206
9797bf7e 1207 my $swapsize_kb;
9bb301fb 1208 if (defined($config_options->{swapsize})) {
9797bf7e 1209 $swapsize_kb = $config_options->{swapsize} * 1024 * 1024;
c6ed3b24 1210 } else {
9b97f6f8
TL
1211 my $ss = int($total_memory);
1212 $ss = 4096 if $ss < 4096 && $hdgb >= 64;
1213 $ss = 2048 if $ss < 2048 && $hdgb >= 32;
1214 $ss = 1024 if $ss >= 2048 && $hdgb <= 16;
1215 $ss = 512 if $ss < 512;
1216 $ss = int($hdgb * 128) if $ss > $hdgb * 128;
1217 $ss = 8192 if $ss > 8192;
bd27b085 1218 $swapsize_kb = int($ss * 1024) & ~0xFFF; # align to 4 MB to avoid all to odd SWAP size
c6ed3b24 1219 }
d0d8ce3f 1220
9797bf7e 1221 return $swapsize_kb;
c6ed3b24 1222}
5c06ced5 1223
341a93b7
TL
1224my sub chroot_chown {
1225 my ($root, $path, %param) = @_;
1226
1227 my $recursive = $param{recursive} ? ' -R' : '';
1228 my $user = $param{user};
1229 die "can not chown without user parameter\n" if !defined($user);
1230 my $group = $param{group} // $user;
1231
1232 syscmd("chroot $root /bin/chown $user:$group $recursive $path") == 0 ||
1233 die "chroot: unable to change owner for '$path'\n";
1234}
1235
1236my sub chroot_chmod {
1237 my ($root, $path, %param) = @_;
1238
1239 my $recursive = $param{recursive} ? ' -R' : '';
1240 my $mode = $param{mode};
1241 die "can not chmod without mode parameter\n" if !defined($mode);
1242
1243 syscmd("chroot $root /bin/chmod $mode $recursive $path") == 0 ||
1244 die "chroot: unable to change permission mode for '$path'\n";
1245}
1246
d58ef02c 1247sub prepare_proxmox_boot_esp {
e38884af
SI
1248 my ($espdev, $targetdir) = @_;
1249
d58ef02c
SI
1250 syscmd("chroot $targetdir proxmox-boot-tool init $espdev") == 0 ||
1251 die "unable to init ESP and install proxmox-boot loader on '$espdev'\n";
e38884af 1252}
121ebc59 1253
597db5de
TL
1254sub prepare_grub_efi_boot_esp {
1255 my ($dev, $espdev, $targetdir) = @_;
1256
1257 syscmd("mount -n $espdev -t vfat $targetdir/boot/efi") == 0 ||
1258 die "unable to mount $espdev\n";
1259
1f8429eb
FG
1260 eval {
1261 my $rc = syscmd("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
1262 if ($rc != 0) {
1263 if ($boot_type eq 'efi') {
1264 die "unable to install the EFI boot loader on '$dev'\n";
1265 } else {
1266 warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
1267 }
597db5de 1268 }
1f8429eb
FG
1269 # also install fallback boot file (OVMF does not boot without)
1270 mkdir("$targetdir/boot/efi/EFI/BOOT");
1271 syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
1272 die "unable to copy efi boot loader\n";
1273 };
1274 my $err = $@;
1275
1276 eval {
1277 syscmd("umount $targetdir/boot/efi") == 0 ||
1278 die "unable to umount $targetdir/boot/efi\n";
1279 };
1280 warn $@ if $@;
597db5de 1281
1f8429eb 1282 die "failed to prepare EFI boot using Grub on '$espdev': $err" if $err;
597db5de
TL
1283}
1284
c6ed3b24 1285sub extract_data {
fafc616c 1286 my ($basefile, $targetdir) = @_;
89a12446 1287
c6ed3b24 1288 die "target '$targetdir' does not exist\n" if ! -d $targetdir;
89a12446 1289
121ebc59
DM
1290 my $starttime = [Time::HiRes::gettimeofday];
1291
c6ed3b24 1292 my $bootdevinfo = [];
84761f93 1293
c6ed3b24
DM
1294 my $swapfile;
1295 my $rootdev;
e2c51d7c 1296 my $datadev;
84761f93 1297
121ebc59
DM
1298 my $use_zfs = 0;
1299 my $use_btrfs = 0;
89092156 1300
c6ed3b24 1301 my $filesys = $config_options->{filesys};
89092156 1302
c6ed3b24
DM
1303 if ($filesys =~ m/zfs/) {
1304 $target_hd = undef; # do not use this config
1305 $use_zfs = 1;
5772392c 1306 $targetdir = "/$zfspoolname/ROOT/$zfsrootvolname";
121ebc59
DM
1307 } elsif ($filesys =~ m/btrfs/) {
1308 $target_hd = undef; # do not use this config
1309 $use_btrfs = 1;
c6ed3b24 1310 }
1464c7c9 1311
c6ed3b24
DM
1312 if ($use_zfs) {
1313 my $i;
1314 for ($i = 5; $i > 0; $i--) {
1315 syscmd("modprobe zfs");
1316 last if -c "/dev/zfs";
1317 sleep(1);
1318 }
89092156 1319
c6ed3b24
DM
1320 die "unable to load zfs kernel module\n" if !$i;
1321 }
89092156 1322
1f8429eb
FG
1323 my $bootloader_err;
1324
c6ed3b24 1325 eval {
c6ed3b24 1326 my $maxper = 0.25;
89a12446 1327
408dc55f 1328 update_progress(0, 0, $maxper, "cleanup root-disks");
c6ed3b24 1329
857c43a9
FG
1330 syscmd("vgchange -an") if !$opt_testmode; # deactivate all detected VGs
1331
c6ed3b24 1332 if ($opt_testmode) {
89a12446 1333
6b900321
DM
1334 $rootdev = abs_path($opt_testmode);
1335 syscmd("umount $rootdev");
121ebc59 1336
6b900321 1337 if ($use_btrfs) {
121ebc59 1338
1464c7c9 1339 die "unsupported btrfs mode (for testing environment)\n"
121ebc59
DM
1340 if $filesys ne 'btrfs (RAID0)';
1341
1342 btrfs_create([$rootdev], 'single');
5c06ced5 1343
121ebc59 1344 } elsif ($use_zfs) {
5c06ced5 1345
121ebc59 1346 die "unsupported zfs mode (for testing environment)\n"
c6ed3b24
DM
1347 if $filesys ne 'zfs (RAID0)';
1348
71590b6a 1349 syscmd("zpool destroy $zfstestpool");
5c06ced5 1350
5fd81672 1351 zfs_create_rpool($rootdev);
1464c7c9 1352
121ebc59
DM
1353 } else {
1354
6b900321 1355 # nothing to do
121ebc59
DM
1356 }
1357
1358 } elsif ($use_btrfs) {
1359
1360 my ($devlist, $btrfs_mode) = get_btrfs_raid_setup();
408dc55f
TL
1361
1362 foreach my $hd (@$devlist) {
1363 $clean_disk->(@$hd[1]);
1364 }
1365
1366 update_progress(0, 0.02, $maxper, "create partitions");
1367
121ebc59
DM
1368 my $btrfs_partitions = [];
1369 my $disksize;
1370 foreach my $hd (@$devlist) {
1371 my $devname = @$hd[1];
5ea943cf
SI
1372 my $logical_bsize = @$hd[4];
1373
121ebc59 1374 my ($size, $osdev, $efidev) =
679c813c 1375 partition_bootable_disk($devname, $config_options->{hdsize}, '8300');
121ebc59
DM
1376 $rootdev = $osdev if !defined($rootdev); # simply point to first disk
1377 my $by_id = find_stable_path("/dev/disk/by-id", $devname);
5ff5a8d0
SI
1378 push @$bootdevinfo, {
1379 esp => $efidev,
1380 devname => $devname,
1381 osdev => $osdev,
1382 by_id => $by_id,
5ea943cf 1383 logical_bsize => $logical_bsize,
5ff5a8d0 1384 };
121ebc59
DM
1385 push @$btrfs_partitions, $osdev;
1386 $disksize = $size;
5c06ced5 1387 }
c6ed3b24 1388
89560d15 1389 $udevadm_trigger_block->();
121ebc59 1390
408dc55f
TL
1391 update_progress(0, 0.03, $maxper, "create btrfs");
1392
121ebc59
DM
1393 btrfs_create($btrfs_partitions, $btrfs_mode);
1394
c6ed3b24
DM
1395 } elsif ($use_zfs) {
1396
82695821 1397 my ($devlist, $vdev) = get_zfs_raid_setup();
c6ed3b24 1398
857c43a9 1399 foreach my $hd (@$devlist) {
89560d15 1400 $clean_disk->(@$hd[1]);
857c43a9 1401 }
4fb6ac60 1402
408dc55f
TL
1403 update_progress(0, 0.02, $maxper, "create partitions");
1404
82695821 1405 # install esp/boot part on all, we can only win!
4fb6ac60 1406 my $disksize;
82695821 1407 for my $hd (@$devlist) {
c6ed3b24 1408 my $devname = @$hd[1];
5ea943cf 1409 my $logical_bsize = @$hd[4];
118d4f40 1410
e38884af 1411 my ($size, $osdev, $efidev) =
d6e919d7 1412 partition_bootable_disk($devname, $config_options->{hdsize}, 'BF01');
4fb6ac60 1413
4fb6ac60
TL
1414 push @$bootdevinfo, {
1415 esp => $efidev,
1416 devname => $devname,
5ea943cf
SI
1417 osdev => $osdev,
1418 logical_bsize => $logical_bsize,
4fb6ac60 1419 };
c6ed3b24 1420 $disksize = $size;
c6ed3b24
DM
1421 }
1422
89560d15 1423 $udevadm_trigger_block->();
c6ed3b24 1424
35c6f89c
DM
1425 foreach my $di (@$bootdevinfo) {
1426 my $devname = $di->{devname};
1427 $di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
1464c7c9 1428
e1fdd3d0 1429 my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
c6ed3b24 1430
35c6f89c
DM
1431 $vdev =~ s/ $devname/ $osdev/;
1432 }
1433
e1b49086
SI
1434 foreach my $hd (@$devlist) {
1435 my $devname = @$hd[1];
1436 my $by_id = find_stable_path ("/dev/disk/by-id", $devname);
1437
f0830a59 1438 $vdev =~ s/ $devname/ $by_id/ if $by_id;
e1b49086
SI
1439 }
1440
408dc55f
TL
1441 update_progress(0, 0.03, $maxper, "create rpool");
1442
5fd81672 1443 zfs_create_rpool($vdev);
1464c7c9 1444
c6ed3b24
DM
1445 } else {
1446
1447 die "target '$target_hd' is not a valid block device\n" if ! -b $target_hd;
1448
408dc55f
TL
1449 $clean_disk->($target_hd);
1450
1451 update_progress(0, 0.02, $maxper, "create partitions");
857c43a9 1452
5ea943cf
SI
1453 my $logical_bsize = logical_blocksize($target_hd);
1454
1464c7c9
DM
1455 my ($os_size, $osdev, $efidev);
1456 ($os_size, $osdev, $efidev) =
d6e919d7 1457 partition_bootable_disk($target_hd, $config_options->{hdsize}, '8E00');
c6ed3b24 1458
121ebc59 1459 &$udevadm_trigger_block();
c6ed3b24 1460
35c6f89c 1461 my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
5ff5a8d0
SI
1462 push @$bootdevinfo, {
1463 esp => $efidev,
1464 devname => $target_hd,
1465 osdev => $osdev,
1466 by_id => $by_id,
5ea943cf 1467 logical_bsize => $logical_bsize,
5ff5a8d0 1468 };
c6ed3b24 1469
408dc55f
TL
1470 update_progress(0, 0.03, $maxper, "create LVs");
1471
35c6f89c 1472 my $swap_size = compute_swapsize($os_size);
e2c51d7c 1473 ($rootdev, $swapfile, $datadev) =
35c6f89c 1474 create_lvm_volumes($osdev, $os_size, $swap_size);
c6ed3b24 1475
35c6f89c 1476 # trigger udev to create /dev/disk/by-uuid
121ebc59 1477 &$udevadm_trigger_block(1);
89a12446
DM
1478 }
1479
481671c3
DM
1480 if ($use_zfs) {
1481 # to be fast during installation
71590b6a 1482 syscmd("zfs set sync=disabled $zfspoolname") == 0 ||
481671c3
DM
1483 die "unable to set zfs properties\n";
1484 }
1485
408dc55f 1486 update_progress(0.04, 0, $maxper, "create swap space");
89a12446 1487 if ($swapfile) {
71590b6a 1488 syscmd("mkswap -f $swapfile") == 0 ||
89a12446
DM
1489 die "unable to create swap space\n";
1490 }
1491
408dc55f 1492 update_progress(0.045, 0, $maxper, "creating root filesystems");
89a12446 1493
c6ed3b24 1494 foreach my $di (@$bootdevinfo) {
f810f5d0 1495 next if !$di->{esp};
57a03069 1496 # FIXME remove '-s1' once https://github.com/dosfstools/dosfstools/issues/111 is fixed
5ea943cf
SI
1497 my $vfat_extra_opts = ($di->{logical_bsize} == 4096) ? '-s1' : '';
1498 syscmd("mkfs.vfat $vfat_extra_opts -F32 $di->{esp}") == 0 ||
c6ed3b24
DM
1499 die "unable to initialize EFI ESP on device $di->{esp}\n";
1500 }
1501
121ebc59
DM
1502 if ($use_zfs) {
1503 # do nothing
1504 } elsif ($use_btrfs) {
1505 # do nothing
1506 } else {
71590b6a 1507 create_filesystem($rootdev, 'root', $filesys, 0.05, $maxper, 0, 1);
89a12446
DM
1508 }
1509
71590b6a 1510 update_progress(1, 0.05, $maxper, "mounting target $rootdev");
89a12446 1511
121ebc59
DM
1512 if ($use_zfs) {
1513 # do nothing
121ebc59 1514 } else {
6e56032e
FG
1515 my $mount_opts = 'noatime';
1516 $mount_opts .= ',nobarrier'
1517 if $use_btrfs || $filesys =~ /^ext\d$/;
1518
1519 syscmd("mount -n $rootdev -o $mount_opts $targetdir") == 0 ||
35c6f89c
DM
1520 die "unable to mount $rootdev\n";
1521 }
89a12446 1522
35c6f89c
DM
1523 mkdir "$targetdir/boot";
1524 mkdir "$targetdir/boot/efi";
89a12446 1525
5fd81672
DM
1526 mkdir "$targetdir/var";
1527 mkdir "$targetdir/var/lib";
121ebc59 1528
f7d18efd
DM
1529 if ($setup->{product} eq 'pve') {
1530 mkdir "$targetdir/var/lib/vz";
1531 mkdir "$targetdir/var/lib/pve";
1532
1533 if ($use_btrfs) {
1534 syscmd("btrfs subvolume create $targetdir/var/lib/pve/local-btrfs") == 0 ||
1535 die "unable to create btrfs subvolume\n";
1536 }
121ebc59 1537 }
89a12446 1538
8d7ddbde
TL
1539 mkdir "$targetdir/mnt";
1540 mkdir "$targetdir/mnt/hostrun";
1541 syscmd("mount --bind /run $targetdir/mnt/hostrun") == 0 ||
1542 die "unable to bindmount run on $targetdir/mnt/hostrun\n";
1543
71590b6a 1544 update_progress(1, 0.05, $maxper, "extracting base system");
89a12446 1545
fafc616c
DM
1546 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
1547 $ino || die "unable to open file '$basefile' - $!\n";
968fa90b 1548
c437cef5
DM
1549 my $files = file_read_firstline("${proxmox_cddir}/proxmox/$setup->{product}-base.cnt") ||
1550 die "unable to read base file count\n";
89a12446
DM
1551
1552 my $per = 0;
1553 my $count = 0;
1554
71590b6a 1555 run_command("unsquashfs -f -dest $targetdir -i $basefile", sub {
89a12446 1556 my $line = shift;
fafc616c 1557 return if $line !~ m/^$targetdir/;
89a12446
DM
1558 $count++;
1559 my $nper = int (($count *100)/$files);
1560 if ($nper != $per) {
1561 $per = $nper;
0f3d1edd 1562 my $frac = $per > 100 ? 1 : $per/100;
71590b6a 1563 update_progress($frac, $maxper, 0.5);
89a12446
DM
1564 }
1565 });
1566
000f289d 1567 syscmd("mount -n -t tmpfs tmpfs $targetdir/tmp") == 0 || die "unable to mount tmpfs on $targetdir/tmp\n";
f9b3baff
TL
1568
1569 mkdir "$targetdir/tmp/pkg";
1570 syscmd("mount -n --bind '$proxmox_pkgdir' '$targetdir/tmp/pkg'") == 0
1571 || die "unable to bind-mount packages on $targetdir/tmp/pkg\n";
000f289d
TL
1572 syscmd("mount -n -t proc proc $targetdir/proc") == 0 || die "unable to mount proc on $targetdir/proc\n";
1573 syscmd("mount -n -t sysfs sysfs $targetdir/sys") == 0 || die "unable to mount sysfs on $targetdir/sys\n";
f238dd03 1574 if ($boot_type eq 'efi') {
dea730ea 1575 syscmd("mount -n -t efivarfs efivarfs $targetdir/sys/firmware/efi/efivars") == 0 ||
f238dd03
TL
1576 die "unable to mount efivarfs on $targetdir/sys/firmware/efi/efivars: $!\n";
1577 }
8d7ddbde
TL
1578 syscmd("chroot $targetdir mount --bind /mnt/hostrun /run") == 0 ||
1579 die "unable to re-bindmount hostrun on /run in chroot\n";
89a12446 1580
71590b6a 1581 update_progress(1, $maxper, 0.5, "configuring base system");
89a12446
DM
1582
1583 # configure hosts
1584
968fa90b 1585 my $hosts =
89a12446 1586 "127.0.0.1 localhost.localdomain localhost\n" .
57cd2e0f 1587 "$ipaddress $hostname.$domain $hostname\n\n" .
89a12446
DM
1588 "# The following lines are desirable for IPv6 capable hosts\n\n" .
1589 "::1 ip6-localhost ip6-loopback\n" .
1590 "fe00::0 ip6-localnet\n" .
1591 "ff00::0 ip6-mcastprefix\n" .
1592 "ff02::1 ip6-allnodes\n" .
1593 "ff02::2 ip6-allrouters\n" .
1594 "ff02::3 ip6-allhosts\n";
1595
71590b6a 1596 write_config($hosts, "$targetdir/etc/hosts");
89a12446 1597
71590b6a 1598 write_config("$hostname\n", "$targetdir/etc/hostname");
89a12446 1599
71590b6a 1600 syscmd("/bin/hostname $hostname") if !$opt_testmode;
89a12446
DM
1601
1602 # configure interfaces
1603
b6200603
DM
1604 my $ifaces = "auto lo\niface lo inet loopback\n\n";
1605
1606 my $ntype = $ipversion == 4 ? 'inet' : 'inet6';
1607
f5439194 1608 my $ethdev = $ipconf->{ifaces}->{$ipconf->{selected}}->{name};
4a0331ab
DM
1609
1610 if ($setup->{bridged_network}) {
f5439194 1611 $ifaces .= "iface $ethdev $ntype manual\n";
4a0331ab
DM
1612
1613 $ifaces .=
1614 "\nauto vmbr0\niface vmbr0 $ntype static\n" .
b1838e1e 1615 "\taddress $cidr\n" .
4a0331ab 1616 "\tgateway $gateway\n" .
f5439194 1617 "\tbridge-ports $ethdev\n" .
2b8fdf3d
FG
1618 "\tbridge-stp off\n" .
1619 "\tbridge-fd 0\n";
4a0331ab 1620 } else {
f5439194
TL
1621 $ifaces .= "auto $ethdev\n" .
1622 "iface $ethdev $ntype static\n" .
b1838e1e 1623 "\taddress $cidr\n" .
4a0331ab
DM
1624 "\tgateway $gateway\n";
1625 }
89a12446 1626
fe44bd92
FG
1627 foreach my $iface (sort keys %{$ipconf->{ifaces}}) {
1628 my $name = $ipconf->{ifaces}->{$iface}->{name};
f5439194 1629 next if $name eq $ethdev;
fe44bd92
FG
1630
1631 $ifaces .= "\niface $name $ntype manual\n";
1632 }
1633
71590b6a 1634 write_config($ifaces, "$targetdir/etc/network/interfaces");
89a12446
DM
1635
1636 # configure dns
1637
39a0f44b 1638 my $resolvconf = "search $domain\nnameserver $dnsserver\n";
71590b6a 1639 write_config($resolvconf, "$targetdir/etc/resolv.conf");
89a12446 1640
5c06ced5
DM
1641 # configure fstab
1642
1643 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass>\n";
1644
121ebc59
DM
1645 if ($use_zfs) {
1646 # do nothing
1647 } elsif ($use_btrfs) {
1648 my $fsuuid;
1649 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev";
1650 run_command($cmd, sub {
1651 my $line = shift;
1652
1653 if ($line =~ m/^UUID=([A-Fa-f0-9\-]+)$/) {
1654 $fsuuid = $1;
1655 }
1656 });
1657
1658 die "unable to detect FS UUID" if !defined($fsuuid);
1659
1660 $fstab .= "UUID=$fsuuid / btrfs defaults 0 1\n";
1661 } else {
80090926
DM
1662 my $root_mountopt = $fssetup->{$filesys}->{root_mountopt} || 'defaults';
1663 $fstab .= "$rootdev / $filesys ${root_mountopt} 0 1\n";
7bc4f6bd 1664 }
a84ea010
DM
1665
1666 # mount /boot/efi
1667 # Note: this is required by current grub, but really dangerous, because
1668 # vfat does not have journaling, so it triggers manual fsck after each crash
1669 # so we only mount /boot/efi if really required (efi systems).
4fb6ac60 1670 if ($boot_type eq 'efi' && !$use_zfs) {
a84ea010 1671 if (scalar(@$bootdevinfo)) {
f810f5d0 1672 my $di = @$bootdevinfo[0]; # simply use first disk
4fb6ac60
TL
1673
1674 if ($di->{esp}) {
f810f5d0
DM
1675 my $efi_boot_uuid = $di->{esp};
1676 if (my $uuid = find_dev_by_uuid ($di->{esp})) {
1677 $efi_boot_uuid = "UUID=$uuid";
1678 }
1464c7c9 1679
f810f5d0
DM
1680 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1\n";
1681 }
a84ea010 1682 }
84761f93
DM
1683 }
1684
c1cfbb1c 1685
89a12446
DM
1686 $fstab .= "$swapfile none swap sw 0 0\n" if $swapfile;
1687
1688 $fstab .= "proc /proc proc defaults 0 0\n";
1689
71590b6a
OB
1690 write_config($fstab, "$targetdir/etc/fstab");
1691 write_config("", "$targetdir/etc/mtab");
968fa90b 1692
c1cfbb1c
SI
1693 syscmd("cp ${proxmox_libdir}/policy-disable-rc.d " .
1694 "$targetdir/usr/sbin/policy-rc.d") == 0 ||
1695 die "unable to copy policy-rc.d\n";
1696 syscmd("cp ${proxmox_libdir}/fake-start-stop-daemon " .
1697 "$targetdir/sbin/") == 0 ||
89a12446
DM
1698 die "unable to copy start-stop-daemon\n";
1699
71590b6a
OB
1700 diversion_add($targetdir, "/sbin/start-stop-daemon", "/sbin/fake-start-stop-daemon");
1701 diversion_add($targetdir, "/usr/sbin/update-grub", "/bin/true");
1702 diversion_add($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
89a12446 1703
72d2dcd0
SI
1704 my $machine_id = run_command("systemd-id128 new");
1705 die "unable to create a new machine-id\n" if ! $machine_id;
1706 write_config($machine_id, "$targetdir/etc/machine-id");
1707
a346a962
SI
1708 syscmd("cp /etc/hostid $targetdir/etc/") == 0 ||
1709 die "unable to copy hostid\n";
1710
71590b6a 1711 syscmd("touch $targetdir/proxmox_install_mode");
89a12446 1712
e35d5efb 1713 my $grub_install_devices_txt = '';
3573c046 1714 foreach my $di (@$bootdevinfo) {
e35d5efb 1715 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt;
ff863262 1716 $grub_install_devices_txt .= $di->{by_id} || $di->{devname};
3573c046
DM
1717 }
1718
b1293fcb
FG
1719 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1720 my $xkmap = $cmap->{kmap}->{$keymap}->{x11} // 'us';
1464c7c9 1721
89a12446
DM
1722 debconfig_set ($targetdir, <<_EOD);
1723locales locales/default_environment_locale select en_US.UTF-8
1724locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1725samba-common samba-common/dhcp boolean false
1726samba-common samba-common/workgroup string WORKGROUP
e953719f 1727postfix postfix/main_mailer_type select No configuration
b1293fcb 1728keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
814f5c39 1729d-i debian-installer/locale select en_US.UTF-8
3573c046 1730grub-pc grub-pc/install_devices select $grub_install_devices_txt
89a12446
DM
1731_EOD
1732
89a12446 1733 my $pkg_count = 0;
97980bf2 1734 while (<${proxmox_pkgdir}/*.deb>) { $pkg_count++ };
89a12446 1735
121ebc59
DM
1736 # btrfs/dpkg is extremely slow without --force-unsafe-io
1737 my $dpkg_opts = $use_btrfs ? "--force-unsafe-io" : "";
1738
89a12446 1739 $count = 0;
97980bf2 1740 while (<${proxmox_pkgdir}/*.deb>) {
89a12446
DM
1741 chomp;
1742 my $path = $_;
97980bf2 1743 my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/;
71590b6a 1744 update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb");
89a12446 1745 print "extracting: $deb\n";
f9b3baff
TL
1746 syscmd("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/pkg/$deb") == 0
1747 || die "installation of package $deb failed\n";
71590b6a 1748 update_progress((++$count)/$pkg_count, 0.5, 0.75);
89a12446
DM
1749 }
1750
3b11dce4
FG
1751 # needed for postfix postinst in case no other NIC is active
1752 syscmd("chroot $targetdir ifup lo");
1753
121ebc59 1754 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a";
89a12446 1755 $count = 0;
71590b6a 1756 run_command($cmd, sub {
89a12446
DM
1757 my $line = shift;
1758 if ($line =~ m/Setting up\s+(\S+)/) {
84d08198 1759 update_progress((++$count)/$pkg_count, 0.75, 0.95, "configuring $1");
89a12446
DM
1760 }
1761 });
968fa90b 1762
89a12446
DM
1763 unlink "$targetdir/etc/mailname";
1764 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
71590b6a 1765 write_config($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
89a12446
DM
1766
1767 # make sure we have all postfix directories
71590b6a 1768 syscmd("chroot $targetdir /usr/sbin/postfix check");
89a12446 1769 # cleanup mail queue
71590b6a 1770 syscmd("chroot $targetdir /usr/sbin/postsuper -d ALL");
29da2d42
SI
1771 # create /etc/aliases.db (/etc/aliases is shipped in the base squashfs)
1772 syscmd("chroot $targetdir /usr/bin/newaliases");
89a12446
DM
1773
1774 unlink "$targetdir/proxmox_install_mode";
1775
968fa90b 1776 # set timezone
89a12446
DM
1777 unlink ("$targetdir/etc/localtime");
1778 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
71590b6a 1779 write_config("$timezone\n", "$targetdir/etc/timezone");
89a12446 1780
89a12446
DM
1781 # set apt mirror
1782 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1783 my $fn = "$targetdir/etc/apt/sources.list";
71590b6a 1784 syscmd("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
89a12446
DM
1785 }
1786
19edf8b7
DM
1787 # create extended_states for apt (avoid cron job warning if that
1788 # file does not exist)
71590b6a 1789 write_config('', "$targetdir/var/lib/apt/extended_states");
19edf8b7 1790
c2657b8b 1791 # allow ssh root login
abcadb95 1792 syscmd(['sed', '-i', 's/^#\?PermitRootLogin.*/PermitRootLogin yes/', "$targetdir/etc/ssh/sshd_config"]);
861a26d4
DM
1793
1794 if ($setup->{product} eq 'pmg') {
1795 # install initial clamav DB
1796 my $srcdir = "${proxmox_cddir}/proxmox/clamav";
05eb99e2 1797 foreach my $fn ("main.cvd", "bytecode.cvd", "daily.cvd", "safebrowsing.cvd") {
71590b6a 1798 syscmd("cp \"$srcdir/$fn\" \"$targetdir/var/lib/clamav\"") == 0 ||
861a26d4
DM
1799 die "installation of clamav db file '$fn' failed\n";
1800 }
1801 syscmd("chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav") == 0 ||
1802 die "unable to set owner for clamav database files\n";
1803 }
1804
58a09baa
DM
1805 if ($setup->{product} eq 'pve') {
1806 # save installer settings
1807 my $ucc = uc ($country);
1808 debconfig_set($targetdir, "pve-manager pve-manager/country string $ucc\n");
1809 }
89a12446 1810
71590b6a 1811 update_progress(0.8, 0.95, 1, "make system bootable");
89a12446 1812
5c06ced5 1813 if ($use_zfs) {
b13dac4b
FG
1814 # add ZFS options while preserving existing kernel cmdline
1815 my $zfs_snippet = "GRUB_CMDLINE_LINUX=\"\$GRUB_CMDLINE_LINUX root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs\"";
1816 write_config($zfs_snippet, "$targetdir/etc/default/grub.d/zfs.cfg");
4fb6ac60 1817
c9b4369c 1818 write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs\n", "$targetdir/etc/kernel/cmdline");
1464c7c9 1819
5c06ced5 1820 }
23c337f5 1821
71590b6a
OB
1822 diversion_remove($targetdir, "/usr/sbin/update-grub");
1823 diversion_remove($targetdir, "/usr/sbin/update-initramfs");
89a12446 1824
56207f2a
DM
1825 my $kapi;
1826 foreach my $fn (<$targetdir/lib/modules/*>) {
1827 if ($fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$!) {
1828 die "found multiple kernels\n" if defined($kapi);
1829 $kapi = $1;
1830 }
1831 }
1832 die "unable to detect kernel version\n" if !defined($kapi);
1833
c6ed3b24 1834 if (!$opt_testmode) {
89a12446
DM
1835
1836 unlink ("$targetdir/etc/mtab");
1837 symlink ("/proc/mounts", "$targetdir/etc/mtab");
71590b6a 1838 syscmd("mount -n --bind /dev $targetdir/dev");
89a12446 1839
1f8429eb
FG
1840 my $bootloader_err_list = [];
1841 eval {
1842 syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1843 die "unable to install initramfs\n";
1844
5ea943cf
SI
1845 my $native_4k_disk_bootable = 0;
1846 foreach my $di (@$bootdevinfo) {
1847 $native_4k_disk_bootable |= ($di->{logical_bsize} == 4096);
1848 }
1849
1f8429eb
FG
1850 foreach my $di (@$bootdevinfo) {
1851 my $dev = $di->{devname};
d58ef02c
SI
1852 if ($use_zfs) {
1853 prepare_proxmox_boot_esp($di->{esp}, $targetdir);
1854 } else {
1855 if (!$native_4k_disk_bootable) {
1856 eval {
1857 syscmd("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1858 die "unable to install the i386-pc boot loader on '$dev'\n";
1859 };
1860 push @$bootloader_err_list, $@ if $@;
1861 }
1f8429eb
FG
1862
1863 eval {
1864 if (my $esp = $di->{esp}) {
1f8429eb
FG
1865 prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
1866 }
1867 }
1868 };
1869 push @$bootloader_err_list, $@ if $@;
1e61f3d8 1870 }
89a12446 1871
1f8429eb
FG
1872 syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
1873 die "unable to update boot loader config\n";
1f8429eb
FG
1874 };
1875 push @$bootloader_err_list, $@ if $@;
1876
1877 if (scalar(@$bootloader_err_list) > 0) {
1878 $bootloader_err = "bootloader setup errors:\n";
1879 map { $bootloader_err .= "- $_" } @$bootloader_err_list;
1880 warn $bootloader_err;
f2afc0fc 1881 }
03c686b7 1882
71590b6a 1883 syscmd("umount $targetdir/dev");
89a12446
DM
1884 }
1885
968fa90b 1886 # cleanup
89a12446 1887
89a12446
DM
1888 unlink "$targetdir/usr/sbin/policy-rc.d";
1889
71590b6a 1890 diversion_remove($targetdir, "/sbin/start-stop-daemon");
89a12446
DM
1891
1892 # set root password
968fa90b 1893 my $octets = encode("utf-8", $password);
71590b6a
OB
1894 run_command("chroot $targetdir /usr/sbin/chpasswd", undef,
1895 "root:$octets\n");
7053f98b 1896
038552a1 1897 if ($setup->{product} eq 'pmg') {
038552a1 1898 # save admin email
71590b6a
OB
1899 write_config("section: admin\n\temail ${mailto}\n",
1900 "$targetdir/etc/pmg/pmg.conf");
038552a1
DM
1901
1902 } elsif ($setup->{product} eq 'pve') {
7053f98b 1903
8acc47b5 1904 # create pmxcfs DB
7053f98b 1905
8acc47b5
DM
1906 my $tmpdir = "$targetdir/tmp/pve";
1907 mkdir $tmpdir;
7053f98b 1908
8acc47b5
DM
1909 # write vnc keymap to datacenter.cfg
1910 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
71590b6a
OB
1911 write_config("keyboard: $vnckmap\n",
1912 "$tmpdir/datacenter.cfg");
968fa90b 1913
8acc47b5 1914 # save admin email
e32b8d2d 1915 write_config("user:root\@pam:1:0:::${mailto}::\n", "$tmpdir/user.cfg");
5fd81672 1916
8acc47b5 1917 # write storage.cfg
d8c697d4 1918 my $storage_cfg_fn = "$tmpdir/storage.cfg";
8acc47b5 1919 if ($use_zfs) {
71590b6a 1920 write_config($storage_cfg_zfs, $storage_cfg_fn);
8acc47b5 1921 } elsif ($use_btrfs) {
71590b6a 1922 write_config($storage_cfg_btrfs, $storage_cfg_fn);
e2c51d7c 1923 } elsif ($datadev) {
71590b6a 1924 write_config($storage_cfg_lvmthin, $storage_cfg_fn);
e2c51d7c 1925 } else {
71590b6a 1926 write_config($storage_cfg_local, $storage_cfg_fn);
8acc47b5 1927 }
7053f98b 1928
8acc47b5
DM
1929 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1930
71590b6a 1931 syscmd("rm -rf $tmpdir");
ca74501d
TL
1932 } elsif ($setup->{product} eq 'pbs') {
1933 my $base_cfg_path = "/etc/proxmox-backup";
e32b8d2d 1934 mkdir "$targetdir/$base_cfg_path";
341a93b7
TL
1935
1936 chroot_chown($targetdir, $base_cfg_path, user => 'backup', recursive => 1);
1937 chroot_chmod($targetdir, $base_cfg_path, mode => '0700');
e32b8d2d
TL
1938
1939 my $user_cfg_fn = "$base_cfg_path/user.cfg";
1940 write_config("user: root\@pam\n\temail ${mailto}\n", "$targetdir/$user_cfg_fn");
1941 chroot_chown($targetdir, $user_cfg_fn, user => 'root', group => 'backup');
1942 chroot_chmod($targetdir, $user_cfg_fn, mode => '0640');
8acc47b5 1943 }
89a12446
DM
1944 };
1945
1946 my $err = $@;
1947
71590b6a 1948 update_progress(1, 0, 1, "");
89a12446
DM
1949
1950 print $err if $err;
1951
1952 if ($opt_testmode) {
121ebc59
DM
1953 my $elapsed = Time::HiRes::tv_interval($starttime);
1954 print "Elapsed extract time: $elapsed\n";
1955
71590b6a 1956 syscmd("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
89a12446
DM
1957 }
1958
8d7ddbde
TL
1959 syscmd("umount $targetdir/run");
1960 syscmd("umount $targetdir/mnt/hostrun");
f9b3baff 1961 syscmd("umount $targetdir/tmp/pkg");
71590b6a
OB
1962 syscmd("umount $targetdir/tmp");
1963 syscmd("umount $targetdir/proc");
f238dd03 1964 syscmd("umount $targetdir/sys/firmware/efi/efivars");
71590b6a 1965 syscmd("umount $targetdir/sys");
435522c9 1966 rmdir("$targetdir/mnt/hostrun");
6fbd1fb1
DM
1967
1968 if ($use_zfs) {
71590b6a 1969 syscmd("zfs umount -a") == 0 ||
6fbd1fb1
DM
1970 die "unable to unmount zfs\n";
1971 } else {
71590b6a 1972 syscmd("umount -d $targetdir");
6fbd1fb1 1973 }
89a12446 1974
5c06ced5 1975 if (!$err && $use_zfs) {
71590b6a 1976 syscmd("zfs set sync=standard $zfspoolname") == 0 ||
481671c3
DM
1977 die "unable to set zfs properties\n";
1978
71590b6a 1979 syscmd("zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname") == 0 ||
5c06ced5 1980 die "zfs set mountpoint failed\n";
1464c7c9 1981
9fda294e
TL
1982 syscmd("zpool set bootfs=$zfspoolname/ROOT/$zfsrootvolname $zfspoolname") == 0 ||
1983 die "zpool set bootfs failed\n";
71590b6a 1984 syscmd("zpool export $zfspoolname");
5c06ced5
DM
1985 }
1986
1f8429eb
FG
1987 if ($bootloader_err) {
1988 $err = $err ? "$err\n$bootloader_err" : $bootloader_err;
1989 }
1990
89a12446
DM
1991 die $err if $err;
1992}
1993
550958aa
DM
1994my $last_display_change = 0;
1995
1996my $display_info_counter = 0;
1997
1998my $display_info_items = [
1999 "extract1-license.htm",
2000 "extract2-rulesystem.htm",
2001 "extract3-spam.htm",
2002 "extract4-virus.htm",
2003 ];
2004
2005sub display_info {
2006
2007 my $min_display_time = 15;
2008
2009 my $ctime = time();
2010
2011 return if ($ctime - $last_display_change) < $min_display_time;
2012
2013 my $page = $display_info_items->[$display_info_counter % scalar(@$display_info_items)];
2014
2015 $display_info_counter++;
2016
2017 display_html($page);
2018}
2019
89a12446
DM
2020sub display_html {
2021 my ($filename) = @_;
2022
201a5120
OB
2023 $filename = $steps[$step_number]->{html} if !$filename;
2024
c2f72dd6
TL
2025 my $htmldir = "${proxmox_libdir}/html";
2026 my $path;
2027 if (-f "$htmldir/$setup->{product}/$filename") {
2028 $path = "$htmldir/$setup->{product}/$filename";
c2f72dd6 2029 } else {
029fde30 2030 $path = "$htmldir/$filename";
c2f72dd6 2031 }
8a50920c
DM
2032
2033 my $data = file_get_contents($path);
2034
2035 if ($filename eq 'license.htm') {
93f25df9
TL
2036 my $license = eval { decode('utf8', file_get_contents("${proxmox_cddir}/EULA")) };
2037 if (my $err = $@) {
2038 die $err if !$opt_testmode;
2039 $license = "TESTMODE: Ignore non existent EULA...\n";
2040 }
3c866639 2041 my $title = "END USER LICENSE AGREEMENT (EULA)";
f91c161b 2042 $data =~ s/__LICENSE__/$license/;
8a50920c 2043 $data =~ s/__LICENSE_TITLE__/$title/;
3bcac16b
TL
2044 } elsif ($filename eq 'success.htm') {
2045 my $addr = $ipversion == 6 ? "[${ipaddress}]" : "$ipaddress";
cfb92364 2046 $data =~ s/__IPADDR__/$addr/g;
c2f72dd6 2047 $data =~ s/__PORT__/$setup->{port}/g;
dfc02f3c
TL
2048
2049 my $autoreboot_msg = $config_options->{autoreboot}
2050 ? "Automatic reboot scheduled in $autoreboot_seconds seconds."
2051 : '';
2052 $data =~ s/__AUTOREBOOT_MSG__/$autoreboot_msg/;
8a50920c 2053 }
c2f72dd6 2054 $data =~ s/__FULL_PRODUCT_NAME__/$setup->{fullname}/g;
8a50920c 2055
029fde30
TL
2056 # always set base-path to common path, all resources are accesible from there.
2057 $htmlview->load_html($data, "file://$htmldir/");
550958aa
DM
2058
2059 $last_display_change = time();
7becc472
DM
2060}
2061
201a5120
OB
2062sub prev_function {
2063
2064 my ($text, $fctn) = @_;
2065
2066 $fctn = $step_number if !$fctn;
2067 $text = "_Previous" if !$text;
451b1da5 2068 $prev_btn->set_label ($text);
201a5120
OB
2069
2070 $step_number--;
2071 $steps[$step_number]->{function}();
2072
71590b6a 2073 $prev_btn->grab_focus();
201a5120
OB
2074}
2075
89a12446
DM
2076sub set_next {
2077 my ($text, $fctn) = @_;
2078
2079 $next_fctn = $fctn;
201a5120
OB
2080 my $step = $steps[$step_number];
2081 $text //= $steps[$step_number]->{next_button} // '_Next';
71590b6a 2082 $next->set_label($text);
968fa90b 2083
71590b6a 2084 $next->grab_focus();
89a12446 2085}
89a12446
DM
2086
2087sub create_main_window {
2088
71590b6a
OB
2089 $window = Gtk3::Window->new();
2090 $window->set_default_size(1024, 768);
84761f93 2091 $window->set_has_resize_grip(0);
d6b47e68 2092 $window->fullscreen() if !$opt_testmode;
71590b6a 2093 $window->set_decorated(0) if !$opt_testmode;
89a12446 2094
71590b6a 2095 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 2096
782b4acd
DM
2097 my $logofn = "$setup->{product}-banner.png";
2098 my $image = Gtk3::Image->new_from_file("${proxmox_libdir}/$logofn");
71590b6a 2099 $vbox->pack_start($image, 0, 0, 0);
89a12446 2100
71590b6a
OB
2101 my $hbox = Gtk3::HBox->new(0, 0);
2102 $vbox->pack_start($hbox, 1, 1, 0);
89a12446 2103
7becc472
DM
2104 # my $f1 = Gtk3::Frame->new ('test');
2105 # $f1->set_shadow_type ('none');
2106 # $hbox->pack_start ($f1, 1, 1, 0);
89a12446 2107
71590b6a
OB
2108 my $sep1 = Gtk3::HSeparator->new();
2109 $vbox->pack_start($sep1, 0, 0, 0);
89a12446 2110
71590b6a
OB
2111 $cmdbox = Gtk3::HBox->new();
2112 $vbox->pack_start($cmdbox, 0, 0, 10);
89a12446 2113
71590b6a
OB
2114 $next = Gtk3::Button->new('_Next');
2115 $next->signal_connect(clicked => sub { $last_display_change = 0; &$next_fctn (); });
2116 $cmdbox->pack_end($next, 0, 0, 10);
201a5120
OB
2117
2118
71590b6a
OB
2119 $prev_btn = Gtk3::Button->new('_Previous');
2120 $prev_btn->signal_connect(clicked => sub { $last_display_change = 0; &prev_function (); });
2121 $cmdbox->pack_end($prev_btn, 0, 0, 10);
201a5120
OB
2122
2123
71590b6a
OB
2124 my $abort = Gtk3::Button->new('_Abort');
2125 $abort->set_can_focus(0);
2126 $cmdbox->pack_start($abort, 0, 0, 10);
2127 $abort->signal_connect(clicked => sub { exit (-1); });
89a12446 2128
71590b6a
OB
2129 my $vbox2 = Gtk3::VBox->new(0, 0);
2130 $hbox->add($vbox2);
89a12446 2131
ed0e6aea 2132 $htmlview = Gtk3::WebKit2::WebView->new();
7becc472
DM
2133 my $scrolls = Gtk3::ScrolledWindow->new();
2134 $scrolls->add($htmlview);
1464c7c9 2135
71590b6a
OB
2136 my $hbox2 = Gtk3::HBox->new(0, 0);
2137 $hbox2->pack_start($scrolls, 1, 1, 0);
89a12446 2138
71590b6a 2139 $vbox2->pack_start($hbox2, 1, 1, 0);
89a12446 2140
71590b6a
OB
2141 my $vbox3 = Gtk3::VBox->new(0, 0);
2142 $vbox2->pack_start($vbox3, 0, 0, 0);
89a12446 2143
7becc472 2144 my $sep2 = Gtk3::HSeparator->new;
71590b6a 2145 $vbox3->pack_start($sep2, 0, 0, 0);
89a12446 2146
71590b6a
OB
2147 $inbox = Gtk3::HBox->new(0, 0);
2148 $vbox3->pack_start($inbox, 0, 0, 0);
89a12446 2149
71590b6a 2150 $window->add($vbox);
89a12446
DM
2151
2152 $window->show_all;
71590b6a 2153 $window->realize();
89a12446
DM
2154}
2155
1464c7c9 2156sub cleanup_view {
d2120e51
DM
2157 $inbox->foreach(sub {
2158 my $child = shift;
1464c7c9 2159 $inbox->remove ($child);
d2120e51 2160 });
89a12446
DM
2161}
2162
aed81ff0
DM
2163# fixme: newer GTK3 has special properties to handle numbers with Entry
2164# only allow floating point numbers with Gtk3::Entry
e73c5fcf 2165
aed81ff0
DM
2166sub check_float {
2167 my ($entry, $event) = @_;
2168
e73c5fcf
FG
2169 return check_number($entry, $event, 1);
2170}
2171
2172sub check_int {
2173 my ($entry, $event) = @_;
2174
2175 return check_number($entry, $event, 0);
2176}
2177
2178sub check_number {
2179 my ($entry, $event, $float) = @_;
aed81ff0
DM
2180
2181 my $val = $event->get_keyval;
2182
e73c5fcf 2183 if (($float && $val == ord '.') ||
aed81ff0
DM
2184 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
2185 $val == Gtk3::Gdk::KEY_Shift_L ||
2186 $val == Gtk3::Gdk::KEY_Tab ||
2187 $val == Gtk3::Gdk::KEY_Left ||
2188 $val == Gtk3::Gdk::KEY_Right ||
2189 $val == Gtk3::Gdk::KEY_BackSpace ||
2190 $val == Gtk3::Gdk::KEY_Delete ||
2191 ($val >= ord '0' && $val <= ord '9') ||
2192 ($val >= Gtk3::Gdk::KEY_KP_0 &&
2193 $val <= Gtk3::Gdk::KEY_KP_9)) {
2194 return undef;
2195 }
2196
2197 return 1;
2198}
2199
d2120e51 2200sub create_text_input {
89a12446
DM
2201 my ($default, $text) = @_;
2202
cc120d79 2203 my $hbox = Gtk3::Box->new('horizontal', 0);
89a12446 2204
71590b6a
OB
2205 my $label = Gtk3::Label->new($text);
2206 $label->set_size_request(150, -1);
2207 $label->set_alignment(1, 0.5);
2208 $hbox->pack_start($label, 0, 0, 10);
2209 my $e1 = Gtk3::Entry->new();
cc120d79 2210 $e1->set_width_chars(35);
71590b6a
OB
2211 $hbox->pack_start($e1, 0, 0, 0);
2212 $e1->set_text($default);
89a12446
DM
2213
2214 return ($hbox, $e1);
2215}
cc120d79
TL
2216sub create_cidr_inputs {
2217 my ($default_ip, $default_mask) = @_;
2218
2219 my $hbox = Gtk3::Box->new('horizontal', 0);
2220
2221 my $label = Gtk3::Label->new('IP Address (CIDR)');
2222 $label->set_size_request(150, -1);
2223 $label->set_alignment(1, 0.5);
2224 $hbox->pack_start($label, 0, 0, 10);
2225
2226 my $ip_el = Gtk3::Entry->new();
2227 $ip_el->set_width_chars(28);
2228 $hbox->pack_start($ip_el, 0, 0, 0);
2229 $ip_el->set_text($default_ip);
2230
2231 $label = Gtk3::Label->new('/');
2232 $label->set_size_request(10, -1);
2233 $label->set_alignment(0.5, 0.5);
2234 $hbox->pack_start($label, 0, 0, 2);
2235
2236 my $cidr_el = Gtk3::Entry->new();
2237 $cidr_el->set_width_chars(3);
2238 $hbox->pack_start($cidr_el, 0, 0, 0);
2239 $cidr_el->set_text($default_mask);
2240
2241 return ($hbox, $ip_el, $cidr_el);
2242}
89a12446 2243
89a12446
DM
2244sub get_ip_config {
2245
fe44bd92
FG
2246 my $ifaces = {};
2247 my $default;
89a12446 2248
fe44bd92
FG
2249 my $links = `ip -o l`;
2250 foreach my $l (split /\n/,$links) {
2251 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
2252 next if !$name || $name eq 'lo';
89a12446 2253
fe44bd92
FG
2254 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
2255 $driver =~ s!^.*/!!;
2256
2257 $ifaces->{"$index"} = {
2258 name => $name,
2259 driver => $driver,
2260 flags => $flags,
2261 state => $state,
2262 mac => $mac,
2263 };
2264
2265 my $addresses = `ip -o a s $name`;
2266 foreach my $a (split /\n/,$addresses) {
2267 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
2268 next if !$ip;
32b6fbcf 2269 next if $a =~ /scope\s+link/; # ignore link local
fe44bd92
FG
2270
2271 my $mask = $prefix;
2272
2273 if ($family eq 'inet') {
2274 next if !$ip =~ /$IPV4RE/;
2275 next if $prefix < 8 || $prefix > 32;
2276 $mask = @$ipv4_reverse_mask[$prefix];
2277 } else {
2278 next if !$ip =~ /$IPV6RE/;
2279 }
2280
2281 $default = $index if !$default;
2282
2283 $ifaces->{"$index"}->{"$family"} = {
cc120d79 2284 prefix => $prefix,
fe44bd92
FG
2285 mask => $mask,
2286 addr => $ip,
2287 };
2288 }
2289 }
2290
2291
2292 my $route = `ip route`;
2293 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
89a12446
DM
2294
2295 my $resolvconf = `cat /etc/resolv.conf`;
2296 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
713790a4 2297 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
89a12446
DM
2298
2299 return {
fe44bd92
FG
2300 default => $default,
2301 ifaces => $ifaces,
89a12446
DM
2302 gateway => $gateway,
2303 dnsserver => $dnsserver,
713790a4 2304 domain => $domain,
89a12446
DM
2305 }
2306}
2307
2308sub display_message {
2309 my ($msg) = @_;
2310
21b2e8ea 2311 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'info', 'ok', $msg);
89a12446
DM
2312 $dialog->run();
2313 $dialog->destroy();
2314}
2315
2316sub display_error {
2317 my ($msg) = @_;
2318
21b2e8ea 2319 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'error', 'ok', $msg);
89a12446
DM
2320 $dialog->run();
2321 $dialog->destroy();
2322}
2323
ebd6070d
TL
2324sub display_prompt {
2325 my ($query) = @_;
2326
2327 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'question', 'ok-cancel', $query);
2328 my $response = $dialog->run();
2329 $dialog->destroy();
2330
2331 return $response;
2332}
2333
fe44bd92
FG
2334my $ipconf_first_view = 1;
2335
89a12446
DM
2336sub create_ipconf_view {
2337
201a5120
OB
2338 cleanup_view();
2339 display_html();
89a12446 2340
cc120d79
TL
2341 my $vcontainer = Gtk3::Box->new('vertical', 0);
2342 $inbox->pack_start($vcontainer, 1, 0, 0);
2343 my $hcontainer = Gtk3::Box->new('horizontal', 0);
2344 $vcontainer->pack_start($hcontainer, 0, 0, 10);
2345 my $vbox = Gtk3::Box->new('vertical', 0);
2346 $hcontainer->add($vbox);
89a12446 2347
ebc4f76f 2348 my $ipaddr_text = $config->{ipaddress} // "192.168.100.2";
cc120d79
TL
2349 my $netmask_text = $config->{netmask} // "24";
2350 my $cidr_box;
2351 ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) =
2352 create_cidr_inputs($ipaddr_text, $netmask_text);
fe44bd92
FG
2353
2354 my $device_cb = Gtk3::ComboBoxText->new();
2355 $device_cb->set_active(0);
2356 $device_cb->set_visible(1);
2357
2358 my $get_device_desc = sub {
2359 my $iface = shift;
2360 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
2361 };
2362
2363 my $device_active_map = {};
ebc4f76f 2364 my $device_active_reverse_map = {};
5b6ba737
FG
2365
2366 my $device_change_handler = sub {
2367 my $current = shift;
d6524c52
TL
2368
2369 my $new = $device_active_map->{$current->get_active()};
cd2d2a27 2370 return if defined($ipconf->{selected}) && $new eq $ipconf->{selected};
d6524c52
TL
2371
2372 $ipconf->{selected} = $new;
5b6ba737 2373 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
ebc4f76f 2374 $config->{mngmt_nic} = $iface->{name};
5b6ba737
FG
2375 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2376 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
cc120d79
TL
2377 $ipconf_entry_mask->set_text($iface->{inet}->{prefix} || $iface->{inet6}->{prefix})
2378 if $iface->{inet}->{prefix} || $iface->{inet6}->{prefix};
5b6ba737
FG
2379 };
2380
fe44bd92
FG
2381 my $i = 0;
2382 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2383 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
ebc4f76f
TL
2384 $device_active_map->{$i} = $index;
2385 $device_active_reverse_map->{$ipconf->{ifaces}->{$index}->{name}} = $i;
fe44bd92
FG
2386 if ($ipconf_first_view && $index == $ipconf->{default}) {
2387 $device_cb->set_active($i);
5b6ba737 2388 &$device_change_handler($device_cb);
fe44bd92
FG
2389 $ipconf_first_view = 0;
2390 }
71590b6a 2391 $device_cb->signal_connect('changed' => $device_change_handler);
fe44bd92
FG
2392 $i++;
2393 }
2394
ebc4f76f
TL
2395 if (my $nic = $config->{mngmt_nic}) {
2396 $device_cb->set_active($device_active_reverse_map->{$nic} // 0);
2397 } else {
2398 $device_cb->set_active(0);
2399 }
5b6ba737 2400
71590b6a
OB
2401 my $devicebox = Gtk3::HBox->new(0, 0);
2402 my $label = Gtk3::Label->new("Management Interface:");
2403 $label->set_size_request(150, -1);
2404 $label->set_alignment(1, 0.5);
2405 $devicebox->pack_start($label, 0, 0, 10);
2406 $devicebox->pack_start($device_cb, 0, 0, 0);
fe44bd92 2407
cc120d79 2408 $vbox->pack_start($devicebox, 0, 0, 2);
968fa90b 2409
ebc4f76f 2410 my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
1464c7c9 2411
cc120d79
TL
2412 my ($hostbox, $hostentry) = create_text_input($hn, 'Hostname (FQDN):');
2413 $vbox->pack_start($hostbox, 0, 0, 2);
89a12446 2414
cc120d79 2415 $vbox->pack_start($cidr_box, 0, 0, 2);
89a12446 2416
ebc4f76f 2417 $gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
89a12446
DM
2418
2419 my $gwbox;
d2120e51 2420 ($gwbox, $ipconf_entry_gw) =
71590b6a 2421 create_text_input($gateway, 'Gateway:');
89a12446 2422
cc120d79 2423 $vbox->pack_start($gwbox, 0, 0, 2);
89a12446 2424
ebc4f76f 2425 $dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
89a12446
DM
2426
2427 my $dnsbox;
d2120e51 2428 ($dnsbox, $ipconf_entry_dns) =
71590b6a 2429 create_text_input($dnsserver, 'DNS Server:');
89a12446 2430
cc120d79 2431 $vbox->pack_start($dnsbox, 0, 0, 0);
89a12446
DM
2432
2433 $inbox->show_all;
71590b6a 2434 set_next(undef, sub {
d2120e51
DM
2435
2436 # verify hostname
1464c7c9 2437
89a12446 2438 my $text = $hostentry->get_text();
968fa90b 2439
89a12446
DM
2440 $text =~ s/^\s+//;
2441 $text =~ s/\s+$//;
2442
ebc4f76f
TL
2443 $config->{fqdn} = $text;
2444
ac3757a9 2445 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2446
24973868
WB
2447 # Debian does not support purely numeric hostnames
2448 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2449 display_message("Purely numeric hostnames are not allowed.");
2450 $hostentry->grab_focus();
2451 return;
2452 }
2453
a39bc1f2 2454 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2455 $text =~ m/^([^\.]+)\.(\S+)$/) {
2456 $hostname = $1;
2457 $domain = $2;
d2120e51 2458 } else {
71590b6a 2459 display_message("Hostname does not look like a fully qualified domain name.");
d2120e51 2460 $hostentry->grab_focus();
89a12446
DM
2461 return;
2462 }
d2120e51
DM
2463
2464 # verify ip address
2465
2466 $text = $ipconf_entry_addr->get_text();
2467 $text =~ s/^\s+//;
2468 $text =~ s/\s+$//;
2469 if ($text =~ m!^($IPV4RE)$!) {
2470 $ipaddress = $text;
b6200603
DM
2471 $ipversion = 4;
2472 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2473 $ipaddress = $text;
b6200603 2474 $ipversion = 6;
d2120e51 2475 } else {
71590b6a 2476 display_message("IP address is not valid.");
d2120e51
DM
2477 $ipconf_entry_addr->grab_focus();
2478 return;
2479 }
ebc4f76f 2480 $config->{ipaddress} = $ipaddress;
d2120e51
DM
2481
2482 $text = $ipconf_entry_mask->get_text();
2483 $text =~ s/^\s+//;
2484 $text =~ s/\s+$//;
cc120d79 2485 if ($ipversion == 6 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 126) {
b6200603 2486 $netmask = $text;
cc120d79 2487 } elsif ($ipversion == 4 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 32) {
d2120e51 2488 $netmask = $text;
cc120d79
TL
2489 } elsif ($ipversion == 4 && defined($ipv4_mask_hash->{$text})) {
2490 # costs nothing to handle 255.x.y.z style masks, so continue to allow it
2491 $netmask = $ipv4_mask_hash->{$text};
d2120e51 2492 } else {
71590b6a 2493 display_message("Netmask is not valid.");
d2120e51
DM
2494 $ipconf_entry_mask->grab_focus();
2495 return;
2496 }
cc120d79 2497 $cidr = "$ipaddress/$netmask";
ebc4f76f 2498 $config->{netmask} = $netmask;
d2120e51
DM
2499
2500 $text = $ipconf_entry_gw->get_text();
2501 $text =~ s/^\s+//;
2502 $text =~ s/\s+$//;
b6200603
DM
2503 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2504 $gateway = $text;
2505 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2506 $gateway = $text;
2507 } else {
71590b6a 2508 display_message("Gateway is not valid.");
d2120e51
DM
2509 $ipconf_entry_gw->grab_focus();
2510 return;
2511 }
ebc4f76f 2512 $config->{gateway} = $gateway;
1464c7c9 2513
d2120e51
DM
2514 $text = $ipconf_entry_dns->get_text();
2515 $text =~ s/^\s+//;
2516 $text =~ s/\s+$//;
b6200603
DM
2517 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2518 $dnsserver = $text;
2519 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2520 $dnsserver = $text;
2521 } else {
71590b6a 2522 display_message("DNS server is not valid.");
d2120e51
DM
2523 $ipconf_entry_dns->grab_focus();
2524 return;
2525 }
ebc4f76f 2526 $config->{dnsserver} = $dnsserver;
1464c7c9 2527
d2120e51 2528 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2529
201a5120 2530 $step_number++;
2e33c3f0 2531 create_ack_view();
89a12446
DM
2532 });
2533
2534 $hostentry->grab_focus();
2535}
2536
2e33c3f0
OB
2537sub create_ack_view {
2538
2539 cleanup_view();
2540
dfc02f3c
TL
2541 my $vbox = Gtk3::VBox->new(0, 0);
2542 $inbox->pack_start($vbox, 1, 0, 0);
dfc02f3c
TL
2543
2544 my $reboot_checkbox = Gtk3::CheckButton->new('Automatically reboot after successful installation');
2545 $reboot_checkbox->set_active(1);
2546 $reboot_checkbox->signal_connect ("toggled" => sub {
2547 my $cb = shift;
2548 $config_options->{autoreboot} = $cb->get_active();
2549 });
2550 $vbox->pack_start($reboot_checkbox, 0, 0, 2);
2551
029fde30 2552 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
c2f72dd6 2553 my $ack_html = "${proxmox_libdir}/html/$setup->{product}/$steps[$step_number]->{html}";
2e33c3f0
OB
2554 my $html_data = file_get_contents($ack_template);
2555
2556 my %config_values = (
a7d40341 2557 __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
0470018e 2558 __target_fs__ => $config_options->{filesys},
0ddd2227 2559 __country__ => $cmap->{country}->{$country}->{name},
2e33c3f0
OB
2560 __timezone__ => $timezone,
2561 __keymap__ => $keymap,
2562 __mailto__ => $mailto,
2563 __interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
2564 __hostname__ => $hostname,
2565 __ip__ => $ipaddress,
b1838e1e 2566 __cidr__ => $cidr,
2e33c3f0
OB
2567 __netmask__ => $netmask,
2568 __gateway__ => $gateway,
2569 __dnsserver__ => $dnsserver,
2570 );
2571
029fde30 2572 while (my ($k, $v) = each %config_values) {
2e33c3f0
OB
2573 $html_data =~ s/$k/$v/g;
2574 }
2575
2576 write_config($html_data, $ack_html);
2577
2578 display_html();
2579
dfc02f3c
TL
2580 $inbox->show_all;
2581
2e33c3f0
OB
2582 set_next(undef, sub {
2583 $step_number++;
2584 create_extract_view();
2585 });
2586}
2587
89a12446
DM
2588sub get_device_desc {
2589 my ($devname, $size, $model) = @_;
2590
d2120e51 2591 if ($size && ($size > 0)) {
b04864ec 2592 $size = int($size/2048); # size in MiB, from 512B "sectors"
89a12446 2593
d2120e51 2594 my $text = "$devname (";
89a12446 2595 if ($size >= 1024) {
b04864ec 2596 $size = $size/1024; # size in GiB
ceabb291 2597 if ($size >= 1024) {
b04864ec
SI
2598 $size = $size/1024; # size in TiB
2599 $text .= sprintf("%.2f", $size) . "TiB";
ceabb291 2600 } else {
b04864ec 2601 $text .= sprintf("%.2f", $size) . "GiB";
ceabb291 2602 }
89a12446 2603 } else {
ceabb291 2604 $text .= "${size}MiB";
89a12446
DM
2605 }
2606
d2120e51
DM
2607 $text .= ", $model" if $model;
2608 $text .= ")";
b04864ec 2609 return $text;
d2120e51 2610
89a12446
DM
2611 } else {
2612 return $devname;
2613 }
2614}
2615
d92ada4e
SI
2616my $last_layout;
2617my $country_layout;
89a12446
DM
2618sub update_layout {
2619 my ($cb, $kmap) = @_;
2620
2621 my $ind;
2622 my $def;
2623 my $i = 0;
2624 my $kmaphash = $cmap->{kmaphash};
2625 foreach my $layout (sort keys %$kmaphash) {
2626 $def = $i if $kmaphash->{$layout} eq 'en-us';
2627 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2628 $i++;
2629 }
2630
d92ada4e
SI
2631 my $val = $ind || $def || 0;
2632
2633 if (!defined($kmap)) {
2634 $last_layout //= $val;
2635 } elsif (!defined($country_layout) || $country_layout != $val) {
2636 $last_layout = $country_layout = $val;
2637 }
2638 $cb->set_active($last_layout);
89a12446
DM
2639}
2640
2641my $lastzonecb;
2642sub update_zonelist {
2643 my ($box, $cc) = @_;
2644
2645 my $cczones = $cmap->{cczones};
2646 my $zones = $cmap->{zones};
2647
2648 my $sel;
2649 if ($lastzonecb) {
2650 $sel = $lastzonecb->get_active_text();
2651 $box->remove ($lastzonecb);
2652 } else {
2653 $sel = $timezone; # used once to select default
2654 }
2655
bcbfab6b 2656 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
71590b6a 2657 $cb->set_size_request(200, -1);
89a12446 2658
71590b6a 2659 $cb->signal_connect('changed' => sub {
89a12446
DM
2660 $timezone = $cb->get_active_text();
2661 });
2662
2663 my @za;
2664 if ($cc && defined ($cczones->{$cc})) {
2665 @za = keys %{$cczones->{$cc}};
2666 } else {
2667 @za = keys %$zones;
2668 }
2669 my $ind;
2670 my $i = 0;
2671 foreach my $zone (sort @za) {
2672 $ind = $i if $sel && $zone eq $sel;
71590b6a 2673 $cb->append_text($zone);
89a12446
DM
2674 $i++;
2675 }
2676
71590b6a 2677 $cb->set_active($ind || 0);
89a12446
DM
2678
2679 $cb->show;
71590b6a 2680 $box->pack_start($cb, 0, 0, 0);
89a12446
DM
2681}
2682
2683sub create_password_view {
2684
71590b6a 2685 cleanup_view();
89a12446 2686
71590b6a
OB
2687 my $vbox2 = Gtk3::VBox->new(0, 0);
2688 $inbox->pack_start($vbox2, 1, 0, 0);
2689 my $vbox = Gtk3::VBox->new(0, 0);
2690 $vbox2->pack_start($vbox, 0, 0, 10);
2691
2692 my $hbox1 = Gtk3::HBox->new(0, 0);
2693 my $label = Gtk3::Label->new("Password");
2694 $label->set_size_request(150, -1);
2695 $label->set_alignment(1, 0.5);
2696 $hbox1->pack_start($label, 0, 0, 10);
2697 my $pwe1 = Gtk3::Entry->new();
2698 $pwe1->set_visibility(0);
201a5120 2699 $pwe1->set_text($password) if $password;
71590b6a
OB
2700 $pwe1->set_size_request(200, -1);
2701 $hbox1->pack_start($pwe1, 0, 0, 0);
2702
2703 my $hbox2 = Gtk3::HBox->new(0, 0);
2704 $label = Gtk3::Label->new("Confirm");
2705 $label->set_size_request(150, -1);
2706 $label->set_alignment(1, 0.5);
2707 $hbox2->pack_start($label, 0, 0, 10);
2708 my $pwe2 = Gtk3::Entry->new();
2709 $pwe2->set_visibility(0);
201a5120 2710 $pwe2->set_text($password) if $password;
71590b6a
OB
2711 $pwe2->set_size_request(200, -1);
2712 $hbox2->pack_start($pwe2, 0, 0, 0);
2713
2714 my $hbox3 = Gtk3::HBox->new(0, 0);
b11c55ff 2715 $label = Gtk3::Label->new("Email");
71590b6a
OB
2716 $label->set_size_request(150, -1);
2717 $label->set_alignment(1, 0.5);
2718 $hbox3->pack_start($label, 0, 0, 10);
2719 my $eme = Gtk3::Entry->new();
2720 $eme->set_size_request(200, -1);
201a5120 2721 $eme->set_text($mailto);
71590b6a 2722 $hbox3->pack_start($eme, 0, 0, 0);
89a12446
DM
2723
2724
71590b6a
OB
2725 $vbox->pack_start($hbox1, 0, 0, 5);
2726 $vbox->pack_start($hbox2, 0, 0, 5);
2727 $vbox->pack_start($hbox3, 0, 0, 15);
89a12446
DM
2728
2729 $inbox->show_all;
2730
201a5120 2731 display_html();
89a12446
DM
2732
2733 set_next (undef, sub {
2734
2735 my $t1 = $pwe1->get_text;
2736 my $t2 = $pwe2->get_text;
2737
2738 if (length ($t1) < 5) {
71590b6a 2739 display_message("Password is too short.");
89a12446
DM
2740 $pwe1->grab_focus();
2741 return;
2742 }
2743
2744 if ($t1 ne $t2) {
71590b6a 2745 display_message("Password does not match.");
89a12446
DM
2746 $pwe1->grab_focus();
2747 return;
2748 }
2749
2750 my $t3 = $eme->get_text;
c82fffd8 2751 if ($t3 !~ m/^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
b11c55ff 2752 display_message("Email does not look like a valid address" .
89a12446
DM
2753 " (user\@domain.tld)");
2754 $eme->grab_focus();
2755 return;
a39bc1f2 2756 }
89a12446 2757
a39bc1f2 2758 if ($t3 eq 'mail@example.invalid') {
b11c55ff 2759 display_message("Please enter a valid Email address");
a39bc1f2
FG
2760 $eme->grab_focus();
2761 return;
89a12446
DM
2762 }
2763
2764 $password = $t1;
2765 $mailto = $t3;
2766
201a5120 2767 $step_number++;
89a12446
DM
2768 create_ipconf_view();
2769 });
2770
2771 $pwe1->grab_focus();
2772
2773}
2774
d92ada4e 2775my $installer_kmap;
89a12446
DM
2776sub create_country_view {
2777
71590b6a 2778 cleanup_view();
89a12446
DM
2779
2780 my $countryhash = $cmap->{countryhash};
2781 my $ctr = $cmap->{country};
2782
71590b6a
OB
2783 my $vbox2 = Gtk3::VBox->new(0, 0);
2784 $inbox->pack_start($vbox2, 1, 0, 0);
2785 my $vbox = Gtk3::VBox->new(0, 0);
2786 $vbox2->pack_start($vbox, 0, 0, 10);
89a12446 2787
71590b6a
OB
2788 my $w = Gtk3::Entry->new();
2789 $w->set_size_request(200, -1);
89a12446 2790
71590b6a
OB
2791 my $c = Gtk3::EntryCompletion->new();
2792 $c->set_text_column(0);
89a12446 2793 $c->set_minimum_key_length(0);
71590b6a
OB
2794 $c->set_popup_set_width(1);
2795 $c->set_inline_completion(1);
2796
2797 my $hbox2 = Gtk3::HBox->new(0, 0);
2798 my $label = Gtk3::Label->new("Time zone");
2799 $label->set_size_request(150, -1);
2800 $label->set_alignment(1, 0.5);
2801 $hbox2->pack_start($label, 0, 0, 10);
89a12446
DM
2802 update_zonelist ($hbox2);
2803
71590b6a
OB
2804 my $hbox3 = Gtk3::HBox->new(0, 0);
2805 $label = Gtk3::Label->new("Keyboard Layout");
2806 $label->set_size_request(150, -1);
2807 $label->set_alignment(1, 0.5);
2808 $hbox3->pack_start($label, 0, 0, 10);
89a12446 2809
bcbfab6b 2810 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2811 $kmapcb->set_size_request (200, -1);
2812 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2813 $kmapcb->append_text ($layout);
2814 }
2815
71590b6a 2816 update_layout($kmapcb);
89a12446
DM
2817 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2818
2819 $kmapcb->signal_connect ('changed' => sub {
2820 my $sel = $kmapcb->get_active_text();
d92ada4e 2821 $last_layout = $kmapcb->get_active();
89a12446
DM
2822 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2823 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2824 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
89a12446 2825 $keymap = $kmap;
07ec6825 2826
d92ada4e
SI
2827 return if (defined($installer_kmap) && $installer_kmap eq $kmap);
2828
2829 $installer_kmap = $keymap;
2830
07ec6825
SI
2831 if (! $opt_testmode) {
2832 syscmd ("setxkbmap $xkmap $xvar");
2663e171
SI
2833
2834 my $kbd_config = qq{
2835 XKBLAYOUT="$xkmap"
2836 XKBVARIANT="$xvar"
2837 BACKSPACE="guess"
2838 };
2839 $kbd_config =~ s/^\s+//gm;
2840
2841 run_in_background( sub {
2842 write_config($kbd_config, '/etc/default/keyboard');
2843 system("setupcon");
2844 });
07ec6825 2845 }
89a12446
DM
2846 }
2847 });
2848
2849 $w->signal_connect ('changed' => sub {
2850 my ($entry, $event) = @_;
2851 my $text = $entry->get_text;
2852
2853 if (my $cc = $countryhash->{lc($text)}) {
71590b6a 2854 update_zonelist($hbox2, $cc);
89a12446 2855 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
71590b6a 2856 update_layout($kmapcb, $kmap);
89a12446
DM
2857 }
2858 });
2859
2860 $w->signal_connect (key_press_event => sub {
2861 my ($entry, $event) = @_;
2862 my $text = $entry->get_text;
2863
7becc472
DM
2864 my $val = $event->get_keyval;
2865
2866 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2867 my $cc = $countryhash->{lc($text)};
1464c7c9 2868
89a12446
DM
2869 my $found = 0;
2870 my $compl;
7becc472 2871
4443aa27
DM
2872 if ($cc) {
2873 $found = 1;
2874 $compl = $ctr->{$cc}->{name};
2875 } else {
2876 foreach my $cc (keys %$ctr) {
2877 my $ct = $ctr->{$cc}->{name};
2878 if ($ct =~ m/^\Q$text\E.*$/i) {
2879 $found++;
2880 $compl = $ct;
2881 }
2882 last if $found > 1;
89a12446 2883 }
89a12446 2884 }
4443aa27 2885
89a12446 2886 if ($found == 1) {
7becc472 2887 $entry->set_text($compl);
3df718ea 2888 $c->complete();
89a12446
DM
2889 return undef;
2890 } else {
7becc472
DM
2891 #Gtk3::Gdk::beep();
2892 print chr(7); # beep ?
89a12446
DM
2893 }
2894
3df718ea
DM
2895 $c->complete();
2896
7becc472
DM
2897 my $buf = $w->get_buffer();
2898 $buf->insert_text(-1, '', -1); # popup selection
2899
89a12446
DM
2900 return 1;
2901 }
2902
2903 return undef;
2904 });
1464c7c9 2905
7becc472 2906 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2907 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2908 my $iter = $ls->append();
2909 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2910 }
2911 $c->set_model ($ls);
2912
968fa90b 2913 $w->set_completion ($c);
89a12446 2914
71590b6a 2915 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 2916
71590b6a
OB
2917 $label = Gtk3::Label->new("Country");
2918 $label->set_alignment(1, 0.5);
2919 $label->set_size_request(150, -1);
2920 $hbox->pack_start($label, 0, 0, 10);
2921 $hbox->pack_start($w, 0, 0, 0);
89a12446 2922
71590b6a
OB
2923 $vbox->pack_start($hbox, 0, 0, 5);
2924 $vbox->pack_start($hbox2, 0, 0, 5);
2925 $vbox->pack_start($hbox3, 0, 0, 5);
89a12446 2926
9d1f1ee3 2927 if ($country && $ctr->{$country}) {
89a12446
DM
2928 $w->set_text ($ctr->{$country}->{name});
2929 }
2930
2931 $inbox->show_all;
2932
201a5120 2933 display_html();
89a12446
DM
2934 set_next (undef, sub {
2935
2936 my $text = $w->get_text;
2937
2938 if (my $cc = $countryhash->{lc($text)}) {
2939 $country = $cc;
201a5120 2940 $step_number++;
89a12446
DM
2941 create_password_view();
2942 return;
2943 } else {
71590b6a 2944 display_message("Please select a country first.");
89a12446
DM
2945 $w->grab_focus();
2946 }
2947 });
2948
2949 $w->grab_focus();
2950}
2951
c6ed3b24
DM
2952my $target_hd_combo;
2953my $target_hd_label;
2954
bd3a2e26 2955my $hdoption_first_setup = 1;
c6ed3b24 2956
c7779156
FG
2957my $create_basic_grid = sub {
2958 my $grid = Gtk3::Grid->new();
2959 $grid->set_visible(1);
2960 $grid->set_column_spacing(10);
2961 $grid->set_row_spacing(10);
2962 $grid->set_hexpand(1);
2963
6d8b8564
TL
2964 $grid->set_margin_start(10);
2965 $grid->set_margin_end(20);
c7779156
FG
2966 $grid->set_margin_top(5);
2967 $grid->set_margin_bottom(5);
2968
2969 return $grid;
2970};
2971
2972my $create_label_widget_grid = sub {
2973 my ($labeled_widgets) = @_;
2974
2975 my $grid = &$create_basic_grid();
2976 my $row = 0;
2977
2978 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2979 my $widget = @$labeled_widgets[$i+1];
2980 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2981 $label->set_visible(1);
2982 $label->set_alignment (1, 0.5);
2983 $grid->attach($label, 0, $row, 1, 1);
2984 $widget->set_visible(1);
2985 $grid->attach($widget, 1, $row, 1, 1);
2986 $row++;
2987 }
2988
2989 return $grid;
2990};
2991
329a65a5 2992# only relevant for raid with its multipl diskX to diskY mappings.
ebf1e983 2993my $get_selected_hdsize = sub {
c07739f4 2994 my $hdsize = shift;
329a65a5
TL
2995 return $hdsize if defined($hdsize);
2996
2997 # compute the smallest disk size of the actually selected disks
2998 my $hd_count = scalar(@$hds);
2999 for (my $i = 0; $i < $hd_count; $i++) {
3000 my $cur_hd = $config_options->{"disksel$i"} // next;
3001 my $disksize = int(@$cur_hd[2] / (2 * 1024 * 1024.0)); # size in GB
3002 $hdsize //= $disksize;
3003 $hdsize = $disksize if $disksize < $hdsize;
c07739f4 3004 }
3bcf46e2
TL
3005
3006 if (my $cfg_hdsize = $config_options->{hdsize}) {
3007 # had the dialog open previously and set an even lower size than the disk selection allows
3008 $hdsize = $cfg_hdsize if $cfg_hdsize < $hdsize;
3009 }
ebf1e983
TL
3010 return $hdsize // 0; # fall back to zero, e.g., if none is selected hdsize cannot be any size
3011};
3012
3013my sub update_hdsize_adjustment {
3014 my ($adjustment, $hdsize) = @_;
3015
3016 $hdsize = $get_selected_hdsize->($hdsize);
3017 # expect that lower = 0 and step increments = 1 still are valid
3018 $adjustment->set_upper($hdsize + 1);
3019 $adjustment->set_value($hdsize);
3020}
c07739f4 3021
ebf1e983
TL
3022my sub create_hdsize_adjustment {
3023 my ($hdsize) = @_;
3024 $hdsize = $get_selected_hdsize->($hdsize);
3025 # params are: initial value, lower, upper, step increment, page increment, page size
c07739f4 3026 return Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
ebf1e983 3027}
c07739f4 3028
e2b003a6 3029my sub get_hdsize_spin_button {
c07739f4
SI
3030 my $hdsize = shift;
3031
3032 my $hdsize_entry_buffer = Gtk3::EntryBuffer->new(undef, 1);
ebf1e983 3033 my $hdsize_size_adj = create_hdsize_adjustment($hdsize);
c07739f4
SI
3034
3035 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
3036 $spinbutton_hdsize->set_buffer($hdsize_entry_buffer);
3037 $spinbutton_hdsize->set_adjustment($hdsize_size_adj);
3038 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
3039 return $spinbutton_hdsize;
3040};
3041
c7779156 3042my $create_raid_disk_grid = sub {
679c813c 3043 my ($hdsize_buttons) = @_;
c2ca8ba8 3044
04fa0758 3045 my $hd_count = scalar(@$hds);
c7779156 3046 my $disk_labeled_widgets = [];
04fa0758 3047 for (my $i = 0; $i < $hd_count; $i++) {
c7779156
FG
3048 my $disk_selector = Gtk3::ComboBoxText->new();
3049 $disk_selector->append_text("-- do not use --");
3050 $disk_selector->set_active(0);
3051 $disk_selector->set_visible(1);
c2ca8ba8 3052
c7779156 3053 foreach my $hd (@$hds) {
17fd908e 3054 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c7779156 3055 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
c7779156
FG
3056 }
3057
8d1ca71a
TL
3058 $disk_selector->{pve_disk_id} = $i;
3059 $disk_selector->signal_connect(changed => sub {
3060 my $w = shift;
3061 my $diskid = $w->{pve_disk_id};
3062 my $a = $w->get_active - 1;
3063 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
8d1ca71a 3064 for my $btn (@$hdsize_buttons) {
ebf1e983 3065 update_hdsize_adjustment($btn->get_adjustment());
8d1ca71a
TL
3066 }
3067 });
3068
bd3a2e26 3069 if ($hdoption_first_setup) {
c7779156
FG
3070 $disk_selector->set_active ($i+1) if $hds->[$i];
3071 } else {
3072 my $hdind = 0;
3073 if (my $cur_hd = $config_options->{"disksel$i"}) {
3074 foreach my $hd (@$hds) {
3075 if (@$hd[1] eq @$cur_hd[1]) {
3076 $disk_selector->set_active($hdind+1);
3077 last;
3078 }
3079 $hdind++;
3080 }
3081 }
3082 }
3083
3084 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
3085 }
3086
3235f39b
TL
3087 my $clear_all_button = Gtk3::Button->new('_Deselect All');
3088 if ($hd_count > 3) {
3089 $clear_all_button->signal_connect('clicked', sub {
3090 my $is_widget = 0;
3091 for my $disk_selector (@$disk_labeled_widgets) {
3092 $disk_selector->set_active(0) if $is_widget;
3093 $is_widget ^= 1;
3094 }
3095 });
3096 $clear_all_button->set_visible(1);
3097 }
3098
c7779156
FG
3099 my $scrolled_window = Gtk3::ScrolledWindow->new();
3100 $scrolled_window->set_hexpand(1);
04fa0758 3101 $scrolled_window->set_propagate_natural_height(1) if $hd_count > 4;
3235f39b
TL
3102
3103 my $diskgrid = $create_label_widget_grid->($disk_labeled_widgets);
3104
3105 $scrolled_window->add($diskgrid);
c7779156 3106 $scrolled_window->set_policy('never', 'automatic');
3235f39b 3107 $scrolled_window->set_visible(1);
0bc39c50 3108 $scrolled_window->set_min_content_height(190);
3235f39b
TL
3109
3110 my $vbox = Gtk3::Box->new('vertical', 0);
3111 $vbox->pack_start($scrolled_window, 1, 1, 10);
3112
3113 my $hbox = Gtk3::Box->new('horizontal', 0);
3114 $hbox->pack_end($clear_all_button, 0, 0, 20);
3115 $hbox->set_visible(1);
3116 $vbox->pack_end($hbox, 0, 0, 0);
c7779156 3117
3235f39b 3118 return $vbox;
c7779156
FG
3119};
3120
3121my $create_raid_advanced_grid = sub {
c07739f4 3122 my ($hdsize_btn) = @_;
c7779156 3123 my $labeled_widgets = [];
2cdba397 3124 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9, 13, 1);
6c99667a
FG
3125 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
3126 $spinbutton_ashift->signal_connect ("value-changed" => sub {
3127 my $w = shift;
3128 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
3129 });
3130 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 3131 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 3132 push @$labeled_widgets, "ashift";
6c99667a 3133 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
3134
3135 my $combo_compress = Gtk3::ComboBoxText->new();
3136 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
59cea7a7 3137 my $comp_opts = ["on","off","lzjb","lz4", "zle", "gzip", "zstd"];
c7779156
FG
3138 foreach my $opt (@$comp_opts) {
3139 $combo_compress->append($opt, $opt);
3140 }
3141 $config_options->{compress} = "on" if !defined($config_options->{compress});
3142 $combo_compress->set_active_id($config_options->{compress});
3143 $combo_compress->signal_connect (changed => sub {
3144 my $w = shift;
3145 $config_options->{compress} = $w->get_active_text();
3146 });
3147 push @$labeled_widgets, "compress";
3148 push @$labeled_widgets, $combo_compress;
3149
3150 my $combo_checksum = Gtk3::ComboBoxText->new();
3151 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
3152 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
3153 foreach my $opt (@$csum_opts) {
3154 $combo_checksum->append($opt, $opt);
3155 }
3156 $config_options->{checksum} = "on" if !($config_options->{checksum});
3157 $combo_checksum->set_active_id($config_options->{checksum});
3158 $combo_checksum->signal_connect (changed => sub {
3159 my $w = shift;
3160 $config_options->{checksum} = $w->get_active_text();
3161 });
3162 push @$labeled_widgets, "checksum";
3163 push @$labeled_widgets, $combo_checksum;
3164
3165 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
3166 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
3167 $spinbutton_copies->signal_connect ("value-changed" => sub {
3168 my $w = shift;
3169 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
3170 });
3171 $config_options->{copies} = 1 if !defined($config_options->{copies});
3172 $spinbutton_copies->set_value($config_options->{copies});
3173 push @$labeled_widgets, "copies", $spinbutton_copies;
3174
c07739f4 3175 push @$labeled_widgets, "hdsize", $hdsize_btn;
2cdba397 3176 return $create_label_widget_grid->($labeled_widgets);;
c7779156
FG
3177};
3178
679c813c
SI
3179my $create_btrfs_raid_advanced_grid = sub {
3180 my ($hdsize_btn) = @_;
3181 my $labeled_widgets = [];
3182 push @$labeled_widgets, "hdsize", $hdsize_btn;
3183 return $create_label_widget_grid->($labeled_widgets);;
3184};
3185
aed81ff0 3186sub create_hdoption_view {
aed81ff0
DM
3187 my $dialog = Gtk3::Dialog->new();
3188
3189 $dialog->set_title("Harddisk options");
3190
3191 $dialog->add_button("_OK", 1);
3192
3193 my $contarea = $dialog->get_content_area();
3194
3195 my $hbox2 = Gtk3::Box->new('horizontal', 0);
6d8b8564 3196 $contarea->pack_start($hbox2, 1, 1, 5);
aed81ff0
DM
3197
3198 my $grid = Gtk3::Grid->new();
3199 $grid->set_column_spacing(10);
3200 $grid->set_row_spacing(10);
1464c7c9 3201
6d8b8564 3202 $hbox2->pack_start($grid, 1, 0, 5);
c6ed3b24
DM
3203
3204 my $row = 0;
3205
aed81ff0 3206 # Filesystem type
71590b6a 3207 my $label0 = Gtk3::Label->new("Filesystem");
aed81ff0 3208 $label0->set_alignment (1, 0.5);
c6ed3b24 3209 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 3210
bcbfab6b 3211 my $fstypecb = Gtk3::ComboBoxText->new();
2cdba397
TL
3212 my $fstype = [
3213 'ext4',
3214 'xfs',
3215 'zfs (RAID0)',
3216 'zfs (RAID1)',
3217 'zfs (RAID10)',
3218 'zfs (RAIDZ-1)',
3219 'zfs (RAIDZ-2)',
3220 'zfs (RAIDZ-3)',
3221 ];
6f52fc3d 3222 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 3223 if $setup->{enable_btrfs};
aed81ff0 3224
c6ed3b24
DM
3225 my $tcount = 0;
3226 foreach my $tmp (@$fstype) {
3227 $fstypecb->append_text($tmp);
2cdba397 3228 $fstypecb->set_active ($tcount) if $config_options->{filesys} eq $tmp;
c6ed3b24
DM
3229 $tcount++;
3230 }
3231
3232 $grid->attach($fstypecb, 1, $row, 1, 1);
3233
3234 $hbox2->show_all();
3235
3236 $row++;
3237
c7779156
FG
3238 my $sep = Gtk3::HSeparator->new();
3239 $sep->set_visible(1);
3240 $grid->attach($sep, 0, $row, 2, 1);
3241 $row++;
aed81ff0 3242
af35966c 3243 my $hw_raid_note = Gtk3::Label->new(""); # text will be set below, before making it visible
f0a0d90b
TL
3244 $hw_raid_note->set_line_wrap(1);
3245 $hw_raid_note->set_max_width_chars(30);
f0a0d90b
TL
3246 $hw_raid_note->set_visible(0);
3247 $grid->attach($hw_raid_note, 0, $row++, 2, 1);
3248
c7779156 3249 my $hdsize_labeled_widgets = [];
aed81ff0 3250
c7779156 3251 # size compute
c6ed3b24 3252 my $hdsize = 0;
aed81ff0 3253 if ( -b $target_hd) {
c2ca8ba8 3254 $hdsize = int(hd_size ($target_hd) / (1024 * 1024.0)); # size in GB
c6ed3b24 3255 } elsif ($target_hd) {
c2ca8ba8 3256 $hdsize = int((-s $target_hd) / (1024 * 1024 * 1024.0));
aed81ff0
DM
3257 }
3258
e2b003a6 3259 my $spinbutton_hdsize_nonraid = get_hdsize_spin_button($hdsize);
c07739f4
SI
3260 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize_nonraid;
3261 my $spinbutton_hdsize = $spinbutton_hdsize_nonraid;
aed81ff0
DM
3262
3263 my $entry_swapsize = Gtk3::Entry->new();
3264 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
3265 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 3266 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 3267 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
3268
3269 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
3270 if ($setup->{product} eq 'pve') {
3271 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
3272 $entry_maxroot->signal_connect (key_press_event => \&check_float);
3273 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
3274 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
3275 }
aed81ff0
DM
3276
3277 my $entry_minfree = Gtk3::Entry->new();
034f75e4 3278 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0 3279 $entry_minfree->signal_connect (key_press_event => \&check_float);
e093944c 3280 $entry_minfree->set_text($config_options->{minfree}) if defined($config_options->{minfree});
c7779156 3281 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 3282
b6e875ca
DM
3283 my $entry_maxvz;
3284 if ($setup->{product} eq 'pve') {
3285 $entry_maxvz = Gtk3::Entry->new();
3286 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
3287 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 3288 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
3289 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
3290 }
c7779156 3291
e2b003a6
TL
3292 my $spinbutton_hdsize_zfs = get_hdsize_spin_button($hdsize);
3293 my $spinbutton_hdsize_btrfs = get_hdsize_spin_button($hdsize);
679c813c 3294 my $hdsize_buttons = [ $spinbutton_hdsize_zfs, $spinbutton_hdsize_btrfs ];
c7779156
FG
3295 my $options_stack = Gtk3::Stack->new();
3296 $options_stack->set_visible(1);
3297 $options_stack->set_hexpand(1);
3298 $options_stack->set_vexpand(1);
679c813c 3299 $options_stack->add_titled(&$create_raid_disk_grid($hdsize_buttons), "raiddisk", "Disk Setup");
c7779156 3300 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
c07739f4 3301 $options_stack->add_titled(&$create_raid_advanced_grid($spinbutton_hdsize_zfs), "raidzfsadvanced", "Advanced Options");
679c813c 3302 $options_stack->add_titled(&$create_btrfs_raid_advanced_grid($spinbutton_hdsize_btrfs), "raidbtrfsadvanced", "Advanced Options");
c7779156
FG
3303 $options_stack->set_visible_child_name("raiddisk");
3304 my $options_stack_switcher = Gtk3::StackSwitcher->new();
3305 $options_stack_switcher->set_halign('center');
3306 $options_stack_switcher->set_stack($options_stack);
3307 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
3308 $row++;
3309 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 3310 $row++;
aed81ff0 3311
bd3a2e26 3312 $hdoption_first_setup = 0;
c7779156
FG
3313
3314 my $switch_view = sub {
3315 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
af35966c 3316 my $is_zfs = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 3317
c7779156
FG
3318 $target_hd_combo->set_visible(!$raid);
3319 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
3320 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
af35966c
TL
3321
3322 if ($raid) {
3323 my $msg = "<b>Note</b>: " . ($is_zfs
3324 ? "ZFS is not compatible with hardware RAID controllers, for details see the documentation."
78164ad6 3325 : "BTRFS integration in $setup->{fullname} is a technology preview!"
af35966c
TL
3326 );
3327 $hw_raid_note->set_markup($msg);
3328 }
f0a0d90b 3329 $hw_raid_note->set_visible($raid);
679c813c 3330 $options_stack_switcher->set_visible($raid);
af35966c 3331 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($is_zfs);
679c813c 3332 $options_stack->get_child_by_name("raidbtrfsadvanced")->set_visible(!$is_zfs);
c7779156 3333 if ($raid) {
c6ed3b24 3334 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 3335 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 3336 } else {
c6ed3b24
DM
3337 $target_hd_label->set_text("Target Harddisk: ");
3338 }
c07739f4
SI
3339
3340 if ($raid) {
679c813c 3341 $spinbutton_hdsize = $is_zfs ? $spinbutton_hdsize_zfs : $spinbutton_hdsize_btrfs;
c07739f4
SI
3342 } else {
3343 $spinbutton_hdsize = $spinbutton_hdsize_nonraid;
3344 }
3345
c7779156
FG
3346 my (undef, $pref_width) = $dialog->get_preferred_width();
3347 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 3348 $pref_height = 750 if $pref_height > 750;
c7779156 3349 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
3350 };
3351
c7779156 3352 &$switch_view();
f7b853d1
DM
3353
3354 $fstypecb->signal_connect (changed => sub {
3355 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 3356 &$switch_view();
f7b853d1
DM
3357 });
3358
95844cc6
TL
3359 my $sep2 = Gtk3::HSeparator->new();
3360 $sep2->set_visible(1);
3361 $contarea->pack_end($sep2, 1, 1, 10);
3362
c6ed3b24 3363 $dialog->show();
aed81ff0
DM
3364
3365 $dialog->run();
3366
3367 my $get_float = sub {
3368 my ($entry) = @_;
3369
3370 my $text = $entry->get_text();
3371 return undef if !defined($text);
3372
3373 $text =~ s/^\s+//;
3374 $text =~ s/\s+$//;
3375
3376 return undef if $text !~ m/^\d+(\.\d+)?$/;
3377
3378 return $text;
3379 };
3380
3381 my $tmp;
3382
3383 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
3384 $config_options->{hdsize} = $tmp;
3385 } else {
3386 delete $config_options->{hdsize};
3387 }
3388
3389 if (defined($tmp = &$get_float($entry_swapsize))) {
3390 $config_options->{swapsize} = $tmp;
3391 } else {
3392 delete $config_options->{swapsize};
3393 }
3394
3395 if (defined($tmp = &$get_float($entry_maxroot))) {
3396 $config_options->{maxroot} = $tmp;
3397 } else {
3398 delete $config_options->{maxroot};
3399 }
3400
3401 if (defined($tmp = &$get_float($entry_minfree))) {
3402 $config_options->{minfree} = $tmp;
3403 } else {
3404 delete $config_options->{minfree};
3405 }
3406
b6e875ca 3407 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
3408 $config_options->{maxvz} = $tmp;
3409 } else {
3410 delete $config_options->{maxvz};
3411 }
3412
3413 $dialog->destroy();
3414}
3415
121ebc59 3416my $get_raid_devlist = sub {
c6ed3b24
DM
3417
3418 my $dev_name_hash = {};
3419
3420 my $devlist = [];
5f8e86d5 3421 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24 3422 if (my $hd = $config_options->{"disksel$i"}) {
17fd908e 3423 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
1464c7c9 3424 die "device '$devname' is used more than once\n"
c6ed3b24
DM
3425 if $dev_name_hash->{$devname};
3426 $dev_name_hash->{$devname} = $hd;
3427 push @$devlist, $hd;
3428 }
3429 }
3430
121ebc59
DM
3431 return $devlist;
3432};
3433
14aacec8
FG
3434sub zfs_mirror_size_check {
3435 my ($expected, $actual) = @_;
3436
3437 die "mirrored disks must have same size\n"
3438 if abs($expected - $actual) > $expected / 10;
3439}
3440
5ea943cf
SI
3441sub legacy_bios_4k_check {
3442 my ($lbs) = @_;
3443 die "Booting from 4kn drive in legacy BIOS mode is not supported.\n"
3444 if (($boot_type ne 'efi') && ($lbs == 4096));
3445}
3446
121ebc59 3447sub get_zfs_raid_setup {
121ebc59
DM
3448 my $filesys = $config_options->{filesys};
3449
3450 my $devlist = &$get_raid_devlist();
3451
224bb7b0 3452 my $diskcount = scalar(@$devlist);
0cfa502c 3453 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24
DM
3454
3455 my $cmd= '';
3456 if ($filesys eq 'zfs (RAID0)') {
c6ed3b24 3457 foreach my $hd (@$devlist) {
5ea943cf 3458 legacy_bios_4k_check(@$hd[4]);
c6ed3b24
DM
3459 $cmd .= " @$hd[1]";
3460 }
3461 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 3462 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 3463 $cmd .= ' mirror ';
269c66a6 3464 my $hd = @$devlist[0];
14aacec8 3465 my $expected_size = @$hd[2]; # all disks need approximately same size
eaeccd9f 3466 foreach my $hd (@$devlist) {
14aacec8 3467 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3468 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3469 $cmd .= " @$hd[1]";
c6ed3b24
DM
3470 }
3471 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 3472 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 3473 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 3474
224bb7b0 3475 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
3476 my $hd1 = @$devlist[$i];
3477 my $hd2 = @$devlist[$i+1];
14aacec8 3478 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
5ea943cf
SI
3479 legacy_bios_4k_check(@$hd1[4]);
3480 legacy_bios_4k_check(@$hd2[4]);
c6ed3b24
DM
3481 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
3482 }
3483
3484 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3485 my $level = $1;
3486 my $mindisks = 2 + $level;
0cfa502c 3487 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 3488 my $hd = @$devlist[0];
14aacec8 3489 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 3490 $cmd .= " raidz$level";
eaeccd9f 3491 foreach my $hd (@$devlist) {
14aacec8 3492 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3493 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3494 $cmd .= " @$hd[1]";
c6ed3b24
DM
3495 }
3496 } else {
3497 die "unknown zfs mode '$filesys'\n";
3498 }
3499
82695821 3500 return ($devlist, $cmd);
c6ed3b24
DM
3501}
3502
121ebc59
DM
3503sub get_btrfs_raid_setup {
3504
3505 my $filesys = $config_options->{filesys};
3506
3507 my $devlist = &$get_raid_devlist();
3508
3509 my $diskcount = scalar(@$devlist);
0cfa502c 3510 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
3511
3512 my $mode;
3513
3514 if ($diskcount == 1) {
3515 $mode = 'single';
3516 } else {
3517 if ($filesys eq 'btrfs (RAID0)') {
3518 $mode = 'raid0';
3519 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 3520 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
3521 $mode = 'raid1';
3522 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 3523 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
3524 $mode = 'raid10';
3525 } else {
9d69f3d3 3526 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
3527 }
3528 }
3529
3530 return ($devlist, $mode);
3531}
3532
218a4b6b 3533my $last_hd_selected = 0;
89a12446
DM
3534sub create_hdsel_view {
3535
451b1da5 3536 $prev_btn->set_sensitive(1); # enable previous button at this point
201a5120 3537
71590b6a 3538 cleanup_view();
89a12446 3539
71590b6a
OB
3540 my $vbox = Gtk3::VBox->new(0, 0);
3541 $inbox->pack_start($vbox, 1, 0, 0);
3542 my $hbox = Gtk3::HBox->new(0, 0);
3543 $vbox->pack_start($hbox, 0, 0, 10);
968fa90b 3544
17fd908e 3545 my ($disk, $devname, $size, $model, $logical_bsize) = @{@$hds[0]};
9227a70f 3546 $target_hd = $devname if !defined($target_hd);
89a12446 3547
71590b6a
OB
3548 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3549 $hbox->pack_start($target_hd_label, 0, 0, 0);
89a12446 3550
bcbfab6b 3551 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 3552
1aa5bd02 3553 foreach my $hd (@$hds) {
17fd908e 3554 ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c2ca8ba8 3555 $target_hd_combo->append_text(get_device_desc($devname, $size, $model));
1aa5bd02 3556 }
89a12446 3557
90af1603
OB
3558 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3559 if ($raid) {
3560 $target_hd_label->set_text("Target: $config_options->{filesys} ");
3561 $target_hd_combo->set_visible(0);
3562 $target_hd_combo->set_no_show_all(1);
3563 }
218a4b6b 3564 $target_hd_combo->set_active($last_hd_selected);
71590b6a 3565 $target_hd_combo->signal_connect(changed => sub {
1aa5bd02
DM
3566 $a = shift->get_active;
3567 my ($disk, $devname) = @{@$hds[$a]};
3b959bef 3568 $last_hd_selected = $a;
1aa5bd02 3569 $target_hd = $devname;
1aa5bd02 3570 });
1464c7c9 3571
71590b6a 3572 $hbox->pack_start($target_hd_combo, 0, 0, 10);
aed81ff0 3573
71590b6a 3574 my $options = Gtk3::Button->new('_Options');
aed81ff0
DM
3575 $options->signal_connect (clicked => \&create_hdoption_view);
3576 $hbox->pack_start ($options, 0, 0, 0);
3577
89a12446
DM
3578
3579 $inbox->show_all;
3580
201a5120 3581 display_html();
c6ed3b24 3582
71590b6a 3583 set_next(undef, sub {
c6ed3b24
DM
3584
3585 if ($config_options->{filesys} =~ m/zfs/) {
a7d40341 3586 my ($devlist) = eval { get_zfs_raid_setup() };
c6ed3b24 3587 if (my $err = $@) {
303dfb2c
TL
3588 display_message("Warning: $err\nPlease fix ZFS setup first.");
3589 return;
c6ed3b24 3590 }
303dfb2c 3591 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
121ebc59 3592 } elsif ($config_options->{filesys} =~ m/btrfs/) {
a7d40341 3593 my ($devlist) = eval { get_btrfs_raid_setup() };
121ebc59 3594 if (my $err = $@) {
303dfb2c
TL
3595 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3596 return;
121ebc59 3597 }
303dfb2c 3598 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
c6ed3b24 3599 } else {
5ea943cf
SI
3600 eval { legacy_bios_4k_check(logical_blocksize($target_hd)) };
3601 if (my $err = $@) {
3602 display_message("Warning: $err\n");
3603 return;
3604 }
a7d40341 3605 $config_options->{target_hds} = [ $target_hd ];
c6ed3b24 3606 }
303dfb2c
TL
3607
3608 $step_number++;
3609 create_country_view();
c6ed3b24 3610 });
89a12446
DM
3611}
3612
3613sub create_extract_view {
3614
71590b6a 3615 cleanup_view();
89a12446 3616
550958aa
DM
3617 display_info();
3618
201a5120 3619 $next->set_sensitive(0);
ac3ee85b
TL
3620 $prev_btn->set_sensitive(0);
3621 $prev_btn->hide();
89a12446 3622
71590b6a 3623 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 3624 $inbox->pack_start ($vbox, 1, 0, 0);
71590b6a 3625 my $hbox = Gtk3::HBox->new(0, 0);
53986d77 3626 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3627
71590b6a 3628 my $vbox2 = Gtk3::VBox->new(0, 0);
89a12446
DM
3629 $hbox->pack_start ($vbox2, 0, 0, 0);
3630
71590b6a 3631 $progress_status = Gtk3::Label->new('');
89a12446 3632 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3633
7becc472 3634 $progress = Gtk3::ProgressBar->new;
45feca6f 3635 $progress->set_show_text(1);
7becc472 3636 $progress->set_size_request (600, -1);
89a12446 3637
71590b6a 3638 $vbox2->pack_start($progress, 0, 0, 0);
89a12446 3639
201a5120 3640 $inbox->show_all();
89a12446
DM
3641
3642 my $tdir = $opt_testmode ? "target" : "/target";
3643 mkdir $tdir;
97980bf2 3644 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446 3645
71590b6a 3646 eval { extract_data($base, $tdir); };
89a12446
DM
3647 my $err = $@;
3648
201a5120 3649 $next->set_sensitive(1);
89a12446 3650
71590b6a 3651 set_next("_Reboot", sub { exit (0); } );
89a12446 3652
296cf41f 3653 if ($err) {
201a5120
OB
3654 display_html("fail.htm");
3655 display_error($err);
296cf41f 3656 } else {
201a5120
OB
3657 cleanup_view();
3658 display_html("success.htm");
dfc02f3c
TL
3659
3660 if ($config_options->{autoreboot}) {
3661 Glib::Timeout->add(1000, sub {
3662 if ($autoreboot_seconds > 0) {
3663 $autoreboot_seconds--;
3664 display_html("success.htm");
3665 } else {
3666 exit(0);
3667 }
3668 });
3669 }
296cf41f 3670 }
89a12446
DM
3671}
3672
89a12446
DM
3673sub create_intro_view {
3674
451b1da5 3675 $prev_btn->set_sensitive(0);
201a5120
OB
3676
3677 cleanup_view();
89a12446 3678
ca951e77 3679 if (int($total_memory) < 1024) {
3befbf97 3680 display_error("Less than 1 GiB of usable memory detected, installation will probably fail.\n\n".
c2f72dd6 3681 "See 'System Requirements' in the $setup->{fullname} documentation.");
2b85ee1b
OB
3682 }
3683
bdeca872
DM
3684 if ($setup->{product} eq 'pve') {
3685 eval {
3686 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3687 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
2780ea4f 3688 display_error("No support for KVM virtualization detected.\n\n" .
bdeca872
DM
3689 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3690 }
3691 };
3692 }
7fff0d85 3693
201a5120 3694 display_html();
89a12446 3695
201a5120 3696 $step_number++;
71590b6a 3697 set_next("I a_gree", \&create_hdsel_view);
89a12446
DM
3698}
3699
71590b6a 3700$ipconf = get_ip_config();
89a12446 3701
9d1f1ee3 3702$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3703
3704# read country, kmap and timezone infos
71590b6a 3705$cmap = read_cmap();
89a12446 3706
9d1f1ee3
FG
3707if (!defined($cmap->{country}->{$country})) {
3708 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3709 $country = undef;
3710}
3711
89a12446
DM
3712create_main_window ();
3713
ff2ce71c
FG
3714my $initial_error = 0;
3715
89a12446
DM
3716if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3717 print "no hardisks found\n";
ff2ce71c 3718 $initial_error = 1;
201a5120 3719 display_html("nohds.htm");
71590b6a 3720 set_next("Reboot", sub { exit(0); } );
89a12446 3721} else {
89a12446
DM
3722 foreach my $hd (@$hds) {
3723 my ($disk, $devname) = @$hd;
3724 next if $devname =~ m|^/dev/md\d+$|;
3725 print "found Disk$disk N:$devname\n";
3726 }
89a12446
DM
3727}
3728
72836708
FG
3729if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3730 print "no network interfaces found\n";
3731 $initial_error = 1;
201a5120 3732 display_html("nonics.htm");
71590b6a 3733 set_next("Reboot", sub { exit(0); } );
72836708
FG
3734}
3735
ff2ce71c
FG
3736create_intro_view () if !$initial_error;
3737
7becc472 3738Gtk3->main;
89a12446 3739
0e631479
SI
3740# reap left over zombie processes
3741while ((my $child = waitpid(-1, POSIX::WNOHANG)) > 0) {
3742 print "reaped child $child\n";
3743}
3744
89a12446 3745exit 0;