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