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