]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
zfs: assume that all devices are boot devices
[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
28my $setup = ProxmoxInstallerSetup::setup();
29
89a12446
DM
30my $opt_testmode;
31
7becc472
DM
32if (!$ENV{G_SLICE} || $ENV{G_SLICE} ne "always-malloc") {
33 die "do not use slice allocator (run with 'G_SLICE=always-malloc ./proxinstall ...')\n";
34}
35
71590b6a 36if (!GetOptions('testmode=s' => \$opt_testmode)) {
89a12446
DM
37 die "usage error\n";
38 exit (-1);
39}
40
6b900321
DM
41my $zfstestpool = "test_rpool";
42my $zfspoolname = $opt_testmode ? $zfstestpool : 'rpool';
5772392c 43my $zfsrootvolname = "$setup->{product}-1";
5fd81672 44
b10074d1
TL
45my $product_cfg = {
46 pve => {
47 fullname => 'Proxmox VE',
48 port => '8006',
49 },
50 pmg => {
51 fullname => 'Proxmox Mail Gateway',
52 port => '8006',
53 },
54 pbs => {
55 fullname => 'Proxmox Backup Server',
56 port => '8007',
57 },
18a9811e
TL
58};
59
5fd81672
DM
60my $storage_cfg_zfs = <<__EOD__;
61dir: local
62 path /var/lib/vz
63 content iso,vztmpl,backup
64
239398be 65zfspool: local-zfs
5fd81672
DM
66 pool $zfspoolname/data
67 sparse
68 content images,rootdir
69__EOD__
70
121ebc59
DM
71my $storage_cfg_btrfs = <<__EOD__;
72dir: local
73 path /var/lib/vz
74 content iso,vztmpl,backup
75 disabled
76
77btrfs: local-btrfs
78 path /var/lib/pve/local-btrfs
79 content iso,vztmpl,backup,images,rootdir
80__EOD__
81
5fd81672
DM
82my $storage_cfg_lvmthin = <<__EOD__;
83dir: local
84 path /var/lib/vz
85 content iso,vztmpl,backup
86
239398be 87lvmthin: local-lvm
5fd81672
DM
88 thinpool data
89 vgname pve
90 content rootdir,images
91__EOD__
92
e2c51d7c
FG
93my $storage_cfg_local = <<__EOD__;
94dir: local
95 path /var/lib/vz
96 content iso,vztmpl,backup,rootdir,images
97__EOD__
5fd81672 98
d2120e51
DM
99sub file_read_firstline {
100 my ($filename) = @_;
101
102 my $fh = IO::File->new ($filename, "r");
103 return undef if !$fh;
104 my $res = <$fh>;
105 chomp $res if $res;
106 $fh->close;
107 return $res;
108}
109
71590b6a 110my $logfd = IO::File->new(">/tmp/install.log");
89a12446 111
c7429f77
DM
112my $proxmox_libdir = $opt_testmode ?
113 Cwd::cwd() . "/testdir/var/lib/pve-installer" : "/var/lib/pve-installer";
97980bf2
DM
114my $proxmox_cddir = $opt_testmode ? "../pve-cd-builder/tmp/data-gz/" : "/cdrom";
115my $proxmox_pkgdir = "${proxmox_cddir}/proxmox/packages/";
89a12446 116
e38884af 117my $boot_type = -d '/sys/firmware/efi' ? 'efi' : 'bios';
89a12446 118
32300628 119my $IPV4OCTET = "(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9])?[0-9])";
d2120e51 120my $IPV4RE = "(?:(?:$IPV4OCTET\\.){3}$IPV4OCTET)";
b6200603
DM
121my $IPV6H16 = "(?:[0-9a-fA-F]{1,4})";
122my $IPV6LS32 = "(?:(?:$IPV4RE|$IPV6H16:$IPV6H16))";
123
124my $IPV6RE = "(?:" .
125 "(?:(?:" . "(?:$IPV6H16:){6})$IPV6LS32)|" .
126 "(?:(?:" . "::(?:$IPV6H16:){5})$IPV6LS32)|" .
127 "(?:(?:(?:" . "$IPV6H16)?::(?:$IPV6H16:){4})$IPV6LS32)|" .
128 "(?:(?:(?:(?:$IPV6H16:){0,1}$IPV6H16)?::(?:$IPV6H16:){3})$IPV6LS32)|" .
129 "(?:(?:(?:(?:$IPV6H16:){0,2}$IPV6H16)?::(?:$IPV6H16:){2})$IPV6LS32)|" .
130 "(?:(?:(?:(?:$IPV6H16:){0,3}$IPV6H16)?::(?:$IPV6H16:){1})$IPV6LS32)|" .
131 "(?:(?:(?:(?:$IPV6H16:){0,4}$IPV6H16)?::" . ")$IPV6LS32)|" .
132 "(?:(?:(?:(?:$IPV6H16:){0,5}$IPV6H16)?::" . ")$IPV6H16)|" .
133 "(?:(?:(?:(?:$IPV6H16:){0,6}$IPV6H16)?::" . ")))";
134
135my $IPRE = "(?:$IPV4RE|$IPV6RE)";
136
137
d2120e51
DM
138my $ipv4_mask_hash = {
139 '128.0.0.0' => 1,
140 '192.0.0.0' => 2,
141 '224.0.0.0' => 3,
142 '240.0.0.0' => 4,
143 '248.0.0.0' => 5,
144 '252.0.0.0' => 6,
145 '254.0.0.0' => 7,
146 '255.0.0.0' => 8,
147 '255.128.0.0' => 9,
148 '255.192.0.0' => 10,
149 '255.224.0.0' => 11,
150 '255.240.0.0' => 12,
151 '255.248.0.0' => 13,
152 '255.252.0.0' => 14,
153 '255.254.0.0' => 15,
154 '255.255.0.0' => 16,
155 '255.255.128.0' => 17,
156 '255.255.192.0' => 18,
157 '255.255.224.0' => 19,
158 '255.255.240.0' => 20,
159 '255.255.248.0' => 21,
160 '255.255.252.0' => 22,
161 '255.255.254.0' => 23,
162 '255.255.255.0' => 24,
163 '255.255.255.128' => 25,
164 '255.255.255.192' => 26,
165 '255.255.255.224' => 27,
166 '255.255.255.240' => 28,
167 '255.255.255.248' => 29,
35f05681
FG
168 '255.255.255.252' => 30,
169 '255.255.255.254' => 31,
170 '255.255.255.255' => 32
d2120e51
DM
171};
172
fe44bd92
FG
173my $ipv4_reverse_mask = [
174 '0.0.0.0',
175 '128.0.0.0',
176 '192.0.0.0',
177 '224.0.0.0',
178 '240.0.0.0',
179 '248.0.0.0',
180 '252.0.0.0',
181 '254.0.0.0',
182 '255.0.0.0',
183 '255.128.0.0',
184 '255.192.0.0',
185 '255.224.0.0',
186 '255.240.0.0',
187 '255.248.0.0',
188 '255.252.0.0',
189 '255.254.0.0',
190 '255.255.0.0',
191 '255.255.128.0',
192 '255.255.192.0',
193 '255.255.224.0',
194 '255.255.240.0',
195 '255.255.248.0',
196 '255.255.252.0',
197 '255.255.254.0',
198 '255.255.255.0',
199 '255.255.255.128',
200 '255.255.255.192',
201 '255.255.255.224',
202 '255.255.255.240',
203 '255.255.255.248',
204 '255.255.255.252',
205 '255.255.255.254',
206 '255.255.255.255',
207];
208
201a5120
OB
209my $step_number = 0; # Init number for global function list
210
211my @steps = (
212 {
213 step => 'intro',
214 html => 'license.htm',
215 next_button => 'I a_gree',
216 function => \&create_intro_view,
217 },
218 {
219 step => 'intro',
220 html => 'page1.htm',
221 function => \&create_hdsel_view,
222 },
223 {
224 step => 'country',
225 html => 'country.htm',
226 function => \&create_country_view,
227 },
228 {
229 step => 'password',
230 html => 'passwd.htm',
231 function => \&create_password_view,
232 },
233 {
234 step => 'ipconf',
201a5120
OB
235 html => 'ipconf.htm',
236 function => \&create_ipconf_view,
237 },
2e33c3f0
OB
238 {
239 step => 'ack',
240 html => 'ack.htm',
241 next_button => '_Install',
242 function => \&create_ack_view,
243 },
201a5120
OB
244 {
245 step => 'extract',
246 next_button => '_Reboot',
247 function => \&create_extract_view,
248 },
249);
250
251# GUI global variables
7becc472 252my ($window, $cmdbox, $inbox, $htmlview);
451b1da5 253my $prev_btn;
c6ed3b24 254my ($next, $next_fctn, $target_hd);
89a12446 255my ($progress, $progress_status);
201a5120 256
b1838e1e 257my ($ipversion, $ipaddress, $cidr, $ipconf_entry_addr);
d2120e51
DM
258my ($netmask, $ipconf_entry_mask);
259my ($gateway, $ipconf_entry_gw);
260my ($dnsserver, $ipconf_entry_dns);
89a12446
DM
261my $hostname = 'proxmox';
262my $domain = 'domain.tld';
d2120e51 263my $cmdline = file_read_firstline("/proc/cmdline");
89a12446
DM
264my $ipconf;
265my $country;
266my $timezone = 'Europe/Vienna';
89a12446 267my $keymap = 'en-us';
201a5120
OB
268my $password;
269my $mailto = 'mail@example.invalid';
89a12446 270my $cmap;
dfc02f3c 271my $autoreboot_seconds = 5;
89a12446 272
c4ea5da3
TL
273my $config = {
274 # TODO: add all the user-provided options for previous button
275 country => $country,
276 timezone => $timezone,
277 keymap => $keymap,
278
279 password => $password,
280 mailto => $mailto,
281
282 mngmt_nic => undef,
283 hostname => $hostname,
284 fqdn => undef,
285 ipaddress => undef,
286 netmask => undef,
287 gateway => undef,
201a5120
OB
288};
289
aed81ff0
DM
290# parse command line args
291
dfc02f3c
TL
292my $config_options = {
293 autoreboot => 1,
294};
aed81ff0 295
0abf0d36 296if ($cmdline =~ m/\s(ext4|xfs)(\s.*)?$/) {
5c06ced5
DM
297 $config_options->{filesys} = $1;
298} else {
aeb3d07f 299 $config_options->{filesys} = 'ext4';
5c06ced5 300}
aed81ff0
DM
301
302if ($cmdline =~ m/hdsize=(\d+(\.\d+)?)[\s\n]/i) {
303 $config_options->{hdsize} = $1;
304}
305
306if ($cmdline =~ m/swapsize=(\d+(\.\d+)?)[\s\n]/i) {
307 $config_options->{swapsize} = $1;
308}
309
310if ($cmdline =~ m/maxroot=(\d+(\.\d+)?)[\s\n]/i) {
311 $config_options->{maxroot} = $1;
312}
313
314if ($cmdline =~ m/minfree=(\d+(\.\d+)?)[\s\n]/i) {
315 $config_options->{minfree} = $1;
316}
b6e875ca
DM
317
318if ($setup->{product} eq 'pve') {
319 if ($cmdline =~ m/maxvz=(\d+(\.\d+)?)[\s\n]/i) {
320 $config_options->{maxvz} = $1;
321 }
aed81ff0 322}
89a12446
DM
323
324my $postfix_main_cf = <<_EOD;
325# See /usr/share/postfix/main.cf.dist for a commented, more complete version
326
327myhostname=__FQDN__
328
329smtpd_banner = \$myhostname ESMTP \$mail_name (Debian/GNU)
330biff = no
331
332# appending .domain is the MUA's job.
333append_dot_mydomain = no
334
335# Uncomment the next line to generate "delayed mail" warnings
336#delay_warning_time = 4h
337
338alias_maps = hash:/etc/aliases
339alias_database = hash:/etc/aliases
340mydestination = \$myhostname, localhost.\$mydomain, localhost
968fa90b 341relayhost =
89a12446
DM
342mynetworks = 127.0.0.0/8
343inet_interfaces = loopback-only
344recipient_delimiter = +
345
b0f2ae38
SI
346compatibility_level = 2
347
89a12446
DM
348_EOD
349
84761f93
DM
350sub shellquote {
351 my $str = shift;
352
353 return String::ShellQuote::shell_quote($str);
354}
355
356sub cmd2string {
357 my ($cmd) = @_;
358
359 die "no arguments" if !$cmd;
360
361 return $cmd if !ref($cmd);
362
363 my @qa = ();
364 foreach my $arg (@$cmd) { push @qa, shellquote($arg); }
365
366 return join (' ', @qa);
367}
368
968fa90b 369sub syscmd {
89a12446
DM
370 my ($cmd) = @_;
371
71590b6a 372 return run_command($cmd, undef, undef, 1);
89a12446 373}
968fa90b 374
89a12446
DM
375sub run_command {
376 my ($cmd, $func, $input, $noout) = @_;
377
84761f93
DM
378 my $cmdstr;
379 if (!ref($cmd)) {
380 $cmdstr = $cmd;
381 if ($cmd =~ m/|/) {
382 # see 'man bash' for option pipefail
383 $cmd = [ '/bin/bash', '-c', "set -o pipefail && $cmd" ];
384 } else {
385 $cmd = [ $cmd ];
386 }
387 } else {
388 $cmdstr = cmd2string($cmd);
389 }
390
89a12446 391 my $cmdtxt;
84761f93
DM
392 if ($input && ($cmdstr !~ m/chpasswd/)) {
393 $cmdtxt = "# $cmdstr <<EOD\n$input";
89a12446
DM
394 chomp $cmdtxt;
395 $cmdtxt .= "\nEOD\n";
396 } else {
84761f93 397 $cmdtxt = "# $cmdstr\n";
89a12446 398 }
4a5dbe69
DM
399
400 if ($opt_testmode) {
401 print $cmdtxt;
402 STDOUT->flush();
403 }
404
89a12446
DM
405 print $logfd $cmdtxt;
406
407 my $reader = IO::File->new();
408 my $writer = IO::File->new();
409 my $error = IO::File->new();
410
411 my $orig_pid = $$;
412
413 my $pid;
414 eval {
71590b6a 415 $pid = open3($writer, $reader, $error, @$cmd) || die $!;
89a12446
DM
416 };
417
418 my $err = $@;
419
420 # catch exec errors
421 if ($orig_pid != $$) {
968fa90b
DM
422 POSIX::_exit (1);
423 kill ('KILL', $$);
89a12446
DM
424 }
425
426 die $err if $err;
427
428 print $writer $input if defined $input;
429 close $writer;
430
eaeccd9f 431 my $select = IO::Select->new();
71590b6a
OB
432 $select->add($reader);
433 $select->add($error);
89a12446
DM
434
435 my ($ostream, $logout) = ('', '', '');
436
437 while ($select->count) {
438 my @handles = $select->can_read (0.2);
439
d2120e51 440 Gtk3::main_iteration() while Gtk3::events_pending();
89a12446
DM
441
442 next if !scalar (@handles); # timeout
443
444 foreach my $h (@handles) {
445 my $buf = '';
446 my $count = sysread ($h, $buf, 4096);
447 if (!defined ($count)) {
448 my $err = $!;
449 kill (9, $pid);
450 waitpid ($pid, 0);
451 die "command '$cmd' failed: $err";
452 }
71590b6a 453 $select->remove($h) if !$count;
89a12446
DM
454 if ($h eq $reader) {
455 $ostream .= $buf if !($noout || $func);
456 $logout .= $buf;
457 while ($logout =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) {
458 my $line = $1;
459 &$func($line) if $func;
460 }
461
462 } elsif ($h eq $error) {
463 $ostream .= $buf if !($noout || $func);
464 }
465 print $buf;
466 STDOUT->flush();
467 print $logfd $buf;
468 }
469 }
470
471 &$func($logout) if $func;
472
473 my $rv = waitpid ($pid, 0);
474
475 return $? if $noout; # behave like standard system();
476
556b1466
DM
477 if ($? == -1) {
478 die "command '$cmdstr' failed to execute\n";
479 } elsif (my $sig = ($? & 127)) {
480 die "command '$cmdstr' failed - got signal $sig\n";
481 } elsif (my $exitcode = ($? >> 8)) {
482 die "command '$cmdstr' failed with exit code $exitcode";
89a12446
DM
483 }
484
485 return $ostream;
486}
487
0e631479
SI
488# forks and runs the provided coderef in the child
489# do not use syscmd or run_command as both confuse the GTK mainloop if
490# run from a child process
491sub run_in_background {
492 my ($cmd) = @_;
493
494 my $pid = fork() // die "fork failed: $!\n";
495 if (!$pid) {
496 eval { $cmd->(); };
497 if (my $err = $@) {
498 warn "run_in_background error: $err\n";
499 POSIX::_exit(1);
500 }
501 POSIX::_exit(0);
502 }
503}
504
89a12446
DM
505sub detect_country {
506
507 print "trying to detect country...\n";
62c05878
DM
508 my $cpid = open2(\*TMP, undef, "traceroute -N 1 -q 1 -n 8.8.8.8");
509 return undef if !$cpid;
968fa90b 510
89a12446
DM
511 my $country;
512
513 my $previous_alarm = alarm (10);
514 eval {
515 local $SIG{ALRM} = sub { die "timed out!\n" };
516 my $line;
517 while (defined ($line = <TMP>)) {
518 print $logfd "DC TRACEROUTE: $line";
519 if ($line =~ m/\s*\d+\s+(\d+\.\d+\.\d+\.\d+)\s/) {
520 my $geoip = `geoiplookup $1`;
521 print $logfd "DC GEOIP: $geoip";
522 if ($geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) {
523 $country = lc ($1);
62c05878 524 print $logfd "DC FOUND: $country\n";
89a12446
DM
525 last;
526 }
527 }
528 }
529 };
530
531 my $err = $@;
532
533 alarm ($previous_alarm);
534
535 close (TMP);
536
537 if ($err) {
538 print "unable to detect country - $err\n";
539 } elsif ($country) {
540 print "detected country: " . uc($country) . "\n";
541 } else {
542 print "unable to detect country\n";
543 }
544
545 return $country;
546}
547
548sub get_memtotal {
549
eaeccd9f 550 open (my $MEMINFO, '<', '/proc/meminfo');
89a12446
DM
551
552 my $res = 512; # default to 512 if something goes wrong
eaeccd9f 553 while (my $line = <$MEMINFO>) {
89a12446
DM
554 if ($line =~ m/^MemTotal:\s+(\d+)\s*kB/i) {
555 $res = int ($1 / 1024);
968fa90b 556 }
89a12446
DM
557 }
558
eaeccd9f 559 close($MEMINFO);
89a12446
DM
560
561 return $res;
562}
563
564my $total_memory = get_memtotal();
565
566sub link_points_to {
567 my ($src, $dest) = @_;
568
569 my ($dev1,$ino1) = stat ($src);
570 my ($dev2,$ino2) = stat ($dest);
571
572 return 0 if !($dev1 && $dev2 && $ino1 && $ino2);
573
574 return $ino1 == $ino2 && $dev1 == $dev2;
575}
576
577sub find_stable_path {
578 my ($stabledir, $bdev) = @_;
579
a5af22f5 580 foreach my $path (<$stabledir/*>) {
71590b6a 581 if (link_points_to($path, $bdev)) {
a5af22f5 582 return wantarray ? ($path, basename($path)) : $path;
89a12446 583 }
89a12446 584 }
89a12446
DM
585}
586
587sub find_dev_by_uuid {
588 my $bdev = shift;
589
71590b6a 590 my ($full_path, $name) = find_stable_path("/dev/disk/by-uuid", $bdev);
89a12446
DM
591
592 return $name;
593}
594
595sub hd_list {
596
597 my $res = ();
598
599 if ($opt_testmode) {
76960140
TL
600 my @disks = split /,/, $opt_testmode;
601
602 for my $disk (@disks) {
17fd908e 603 push @$res, [-1, $disk, int((-s $disk)/512), "TESTDISK", 512];
76960140 604 }
121ebc59 605 return $res;
89a12446
DM
606 }
607
608 my $count = 0;
609
610 foreach my $bd (</sys/block/*>) {
611 next if $bd =~ m|^/sys/block/ram\d+$|;
612 next if $bd =~ m|^/sys/block/loop\d+$|;
613 next if $bd =~ m|^/sys/block/md\d+$|;
614 next if $bd =~ m|^/sys/block/dm-.*$|;
615 next if $bd =~ m|^/sys/block/fd\d+$|;
616 next if $bd =~ m|^/sys/block/sr\d+$|;
617
d2120e51 618 my $dev = file_read_firstline("$bd/dev");
89a12446 619 chomp $dev;
968fa90b 620
89a12446
DM
621 next if !$dev;
622
623 my $info = `udevadm info --path $bd --query all`;
624 next if !$info;
625
626 next if $info !~ m/^E: DEVTYPE=disk$/m;
627
628 next if $info =~ m/^E: ID_CDROM/m;
2f4aa276 629 next if $info =~ m/^E: ID_FS_TYPE=iso9660/m;
89a12446
DM
630
631 my ($name) = $info =~ m/^N: (\S+)$/m;
632
968fa90b 633 if ($name) {
89a12446
DM
634 my $real_name = "/dev/$name";
635
d2120e51 636 my $size = file_read_firstline("$bd/size");
89a12446
DM
637 chomp $size;
638 $size = undef if !($size && $size =~ m/^\d+$/);
639
d2120e51 640 my $model = file_read_firstline("$bd/device/model") || '';
89a12446
DM
641 $model =~ s/^\s+//;
642 $model =~ s/\s+$//;
643 if (length ($model) > 30) {
644 $model = substr ($model, 0, 30);
645 }
17fd908e
SI
646
647 my $logical_bsize = file_read_firstline("$bd/queue/logical_block_size") // '';
648 chomp $logical_bsize;
649 $logical_bsize = undef if !($logical_bsize && $logical_bsize =~ m/^\d+$/);
650
651 push @$res, [$count++, $real_name, $size, $model, $logical_bsize] if $size;
89a12446
DM
652 } else {
653 print STDERR "ERROR: unable to map device $dev ($bd)\n";
654 }
655 }
656
657 return $res;
658}
659
660sub read_cmap {
8c094410 661 my $countryfn = "${proxmox_libdir}/country.dat";
eaeccd9f 662 open (my $TMP, "<:encoding(utf8)", "$countryfn") || die "unable to open '$countryfn' - $!\n";
89a12446
DM
663 my $line;
664 my $country = {};
665 my $countryhash = {};
666 my $kmap = {};
667 my $kmaphash = {};
eaeccd9f 668 while (defined ($line = <$TMP>)) {
89a12446
DM
669 if ($line =~ m|^map:([^\s:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]*):$|) {
670 $kmap->{$1} = {
671 name => $2,
672 kvm => $3,
673 console => $4,
674 x11 => $5,
675 x11var => $6,
676 };
677 $kmaphash->{$2} = $1;
678 } elsif ($line =~ m|^([a-z]{2}):([^:]+):([^:]*):([^:]*):$|) {
679 $country->{$1} = {
680 name => $2,
681 kmap => $3,
682 mirror => $4,
683 };
684 $countryhash->{lc($2)} = $1;
685 } else {
686 warn "unable to parse 'country.dat' line: $line";
687 }
688 }
eaeccd9f
TL
689 close ($TMP);
690 $TMP = undef;
89a12446
DM
691
692 my $zones = {};
693 my $cczones = {};
694 my $zonefn = "/usr/share/zoneinfo/zone.tab";
eaeccd9f
TL
695 open ($TMP, '<', "$zonefn") || die "unable to open '$zonefn' - $!\n";
696 while (defined ($line = <$TMP>)) {
89a12446
DM
697 next if $line =~ m/^\#/;
698 next if $line =~ m/^\s*$/;
699 if ($line =~ m|^([A-Z][A-Z])\s+\S+\s+(([^/]+)/\S+)\s|) {
700 my $cc = lc($1);
701 $cczones->{$cc}->{$2} = 1;
702 $country->{$cc}->{zone} = $2 if !defined ($country->{$cc}->{zone});
703 $zones->{$2} = 1;
704
705 }
706 }
eaeccd9f 707 close ($TMP);
89a12446
DM
708
709 return {
710 zones => $zones,
711 cczones => $cczones,
712 country => $country,
713 countryhash => $countryhash,
714 kmap => $kmap,
715 kmaphash => $kmaphash,
716 }
717}
718
719# search for Harddisks
71590b6a 720my $hds = hd_list();
89a12446
DM
721
722sub hd_size {
723 my ($dev) = @_;
724
725 foreach my $hd (@$hds) {
17fd908e 726 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
69f2883c 727 # size is always (also for 4kn disks) in 512B "sectors"! convert to KB
89a12446
DM
728 return int($size/2) if $devname eq $dev;
729 }
730
eb4b1e56 731 die "no such device '$dev'\n";
89a12446
DM
732}
733
17fd908e
SI
734sub logical_blocksize {
735 my ($dev) = @_;
736
737 foreach my $hd (@$hds) {
738 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
739 return $logical_bsize if $devname eq $dev;
740 }
741
742 die "no such device '$dev'\n";
743}
744
89a12446 745sub get_partition_dev {
c6ed3b24
DM
746 my ($dev, $partnum) = @_;
747
c9e6f67b
WL
748 if ($dev =~ m|^/dev/sd([a-h]?[a-z]\|i[a-v])$|) {
749 return "${dev}$partnum";
a8692b68 750 } elsif ($dev =~ m|^/dev/xvd[a-z]$|) {
91749786 751 # Citrix Hypervisor blockdev
a8692b68 752 return "${dev}$partnum";
c9e6f67b 753 } elsif ($dev =~ m|^/dev/[hxev]d[a-z]$|) {
c6ed3b24
DM
754 return "${dev}$partnum";
755 } elsif ($dev =~ m|^/dev/[^/]+/c\d+d\d+$|) {
756 return "${dev}p$partnum";
757 } elsif ($dev =~ m|^/dev/[^/]+/d\d+$|) {
758 return "${dev}p$partnum";
759 } elsif ($dev =~ m|^/dev/[^/]+/hd[a-z]$|) {
760 return "${dev}$partnum";
ed32cc83
WL
761 } elsif ($dev =~ m|^/dev/nvme\d+n\d+$|) {
762 return "${dev}p$partnum";
89a12446 763 } else {
c6ed3b24 764 die "unable to get device for partition $partnum on device $dev\n";
89a12446
DM
765 }
766
767}
768
8a50920c
DM
769sub file_get_contents {
770 my ($filename, $max) = @_;
771
772 my $fh = IO::File->new($filename, "r") ||
773 die "can't open '$filename' - $!\n";
774
775 local $/; # slurp mode
776
777 my $content = <$fh>;
778
779 close $fh;
780
781 return $content;
782}
783
89a12446
DM
784sub write_config {
785 my ($text, $filename) = @_;
786
71590b6a 787 my $fd = IO::File->new(">$filename") ||
eb4b1e56 788 die "unable to open file '$filename' - $!\n";
89a12446
DM
789 print $fd $text;
790 $fd->close();
791}
792
793sub update_progress {
794 my ($frac, $start, $end, $text) = @_;
795
796 my $part = $end - $start;
797 my $res = $start + $frac*$part;
798
799 $progress->set_fraction ($res);
800 $progress->set_text (sprintf ("%d%%", int ($res*100)));
801 $progress_status->set_text ($text) if defined ($text);
802
550958aa
DM
803 display_info() if $res < 0.9;
804
d2120e51 805 Gtk3::main_iteration() while Gtk3::events_pending();
89a12446
DM
806}
807
80090926 808my $fssetup = {
80090926
DM
809 ext4 => {
810 mkfs => 'mkfs.ext4 -F',
1f8d0104
DM
811 mkfs_root_opt => '',
812 mkfs_data_opt => '-m 0',
80090926
DM
813 root_mountopt => 'errors=remount-ro',
814 },
815 xfs => {
816 mkfs => 'mkfs.xfs -f',
1f8d0104
DM
817 mkfs_root_opt => '',
818 mkfs_data_opt => '',
80090926
DM
819 root_mountopt => '',
820 },
821};
822
89a12446 823sub create_filesystem {
1f8d0104 824 my ($dev, $name, $type, $start, $end, $fs, $fe) = @_;
89a12446
DM
825
826 my $range = $end - $start;
827 my $rs = $start + $range*$fs;
828 my $re = $start + $range*$fe;
829 my $max = 0;
830
80090926 831 my $fsdata = $fssetup->{$type} || die "internal error - unknown file system '$type'";
1f8d0104 832 my $opts = $name eq 'root' ? $fsdata->{mkfs_root_opt} : $fsdata->{mkfs_data_opt};
1464c7c9 833
71590b6a 834 update_progress(0, $rs, $re, "creating $name filesystem");
89a12446 835
71590b6a 836 run_command("$fsdata->{mkfs} $opts $dev", sub {
89a12446
DM
837 my $line = shift;
838
839 if ($line =~ m/Writing inode tables:\s+(\d+)\/(\d+)/) {
840 $max = $2;
841 } elsif ($max && $line =~ m/(\d+)\/$max/) {
71590b6a 842 update_progress(($1/$max)*0.9, $rs, $re);
89a12446 843 } elsif ($line =~ m/Creating journal.*done/) {
71590b6a 844 update_progress(0.95, $rs, $re);
89a12446 845 } elsif ($line =~ m/Writing superblocks and filesystem.*done/) {
71590b6a 846 update_progress(1, $rs, $re);
968fa90b 847 }
89a12446
DM
848 });
849}
850
851sub debconfig_set {
852 my ($targetdir, $dcdata) = @_;
853
854 my $cfgfile = "/tmp/debconf.txt";
71590b6a
OB
855 write_config($dcdata, "$targetdir/$cfgfile");
856 syscmd("chroot $targetdir debconf-set-selections $cfgfile");
968fa90b 857 unlink "$targetdir/$cfgfile";
89a12446
DM
858}
859
860sub diversion_add {
861 my ($targetdir, $cmd, $new_cmd) = @_;
862
71590b6a
OB
863 syscmd("chroot $targetdir dpkg-divert --package proxmox " .
864 "--add --rename $cmd") == 0 ||
865 die "unable to exec dpkg-divert\n";
89a12446 866
71590b6a 867 syscmd("ln -sf ${new_cmd} $targetdir/$cmd") == 0 ||
968fa90b 868 die "unable to link diversion to ${new_cmd}\n";
89a12446
DM
869}
870
871sub diversion_remove {
872 my ($targetdir, $cmd) = @_;
873
71590b6a 874 syscmd("mv $targetdir/${cmd}.distrib $targetdir/${cmd};") == 0 ||
89a12446 875 die "unable to remove $cmd diversion\n";
968fa90b 876
71590b6a 877 syscmd("chroot $targetdir dpkg-divert --remove $cmd") == 0 ||
89a12446
DM
878 die "unable to remove $cmd diversion\n";
879}
880
121ebc59
DM
881sub btrfs_create {
882 my ($partitions, $mode) = @_;
883
884 die "unknown btrfs mode '$mode'"
885 if !($mode eq 'single' || $mode eq 'raid0' ||
886 $mode eq 'raid1' || $mode eq 'raid10');
887
888 my $cmd = ['mkfs.btrfs', '-f'];
889
890 push @$cmd, '-d', $mode, '-m', $mode;
891
892 push @$cmd, @$partitions;
893
894 syscmd($cmd);
895}
896
5c06ced5 897sub zfs_create_rpool {
5fd81672 898 my ($vdev) = @_;
486c490d 899
c7779156
FG
900 my $cmd = "zpool create -f -o cachefile=none";
901
902 $cmd .= " -o ashift=$config_options->{ashift}"
903 if defined($config_options->{ashift});
904
71590b6a 905 syscmd("$cmd $zfspoolname $vdev") == 0 ||
5c06ced5
DM
906 die "unable to create zfs root pool\n";
907
71590b6a
OB
908 syscmd("zfs create $zfspoolname/ROOT") == 0 ||
909 die "unable to create zfs $zfspoolname/ROOT volume\n";
3fcd8420
DM
910
911 if ($setup->{product} eq 'pve') {
71590b6a 912 syscmd("zfs create $zfspoolname/data") == 0 ||
3fcd8420
DM
913 die "unable to create zfs $zfspoolname/data volume\n";
914 }
5fd81672 915
71590b6a 916 syscmd("zfs create $zfspoolname/ROOT/$zfsrootvolname") == 0 ||
5772392c 917 die "unable to create zfs $zfspoolname/ROOT/$zfsrootvolname volume\n";
5c06ced5 918
2df572ae 919 # disable atime during install
71590b6a 920 syscmd("zfs set atime=off $zfspoolname") == 0 ||
5c06ced5 921 die "unable to set zfs properties\n";
c7779156
FG
922
923 my $value = $config_options->{compress};
71590b6a 924 syscmd("zfs set compression=$value $zfspoolname")
c7779156
FG
925 if defined($value) && $value ne 'off';
926
927 $value = $config_options->{checksum};
71590b6a 928 syscmd("zfs set checksum=$value $zfspoolname")
c7779156
FG
929 if defined($value) && $value ne 'on';
930
931 $value = $config_options->{copies};
71590b6a 932 syscmd("zfs set copies=$value $zfspoolname")
c7779156 933 if defined($value) && $value != 1;
5c06ced5
DM
934}
935
dc4ad419
FG
936my $udevadm_trigger_block = sub {
937 my ($nowait) = @_;
938
939 sleep(1) if !$nowait; # give kernel time to reread part table
940
941 # trigger udev to create /dev/disk/by-uuid
71590b6a
OB
942 syscmd("udevadm trigger --subsystem-match block");
943 syscmd("udevadm settle --timeout 10");
dc4ad419
FG
944};
945
857c43a9
FG
946my $clean_disk = sub {
947 my ($disk) = @_;
948
949 my $partitions = `lsblk --output kname --noheadings --path --list $disk`;
950 foreach my $part (split "\n", $partitions) {
951 next if $part eq $disk;
952 next if $part !~ /^\Q$disk\E/;
953 eval { syscmd("pvremove -ff -y $part"); };
063ae64a 954 eval { syscmd("zpool labelclear -f $part"); };
857c43a9
FG
955 eval { syscmd("dd if=/dev/zero of=$part bs=1M count=16"); };
956 }
957};
958
c6ed3b24 959sub partition_bootable_disk {
d6e919d7 960 my ($target_dev, $maxhdsizegb, $ptype) = @_;
89a12446 961
c6ed3b24 962 die "too dangerous" if $opt_testmode;
89a12446 963
121ebc59 964 die "unknown partition type '$ptype'"
118d4f40 965 if !($ptype eq '8E00' || $ptype eq '8300' || $ptype eq 'BF01');
121ebc59 966
1bd457bb 967 my $hdsize = hd_size($target_dev); # size in KB (1024 bytes)
c6ed3b24 968
9b4dc6e8 969 my $restricted_hdsize_mb = 0; # 0 ==> end of partition
d6e919d7
SI
970 if ($maxhdsizegb) {
971 my $maxhdsize = $maxhdsizegb * 1024 * 1024;
972 if ($maxhdsize < $hdsize) {
973 $hdsize = $maxhdsize;
974 $restricted_hdsize_mb = int($hdsize/1024) . 'M';
975 }
c6ed3b24
DM
976 }
977
978 my $hdgb = int($hdsize/(1024*1024));
3f6dfdf3 979 die "hardisk '$target_dev' too small (${hdgb}GB)\n" if $hdgb < 8;
c6ed3b24 980
34e44994
SI
981 syscmd("sgdisk -Z ${target_dev}");
982
43b5216c 983 # 1 - BIOS boot partition (Grub Stage2): first free 1M
118d4f40 984 # 2 - EFI ESP: next free 512M
43b5216c 985 # 3 - OS/Data partition: rest, up to $maxhdsize in MB
c6ed3b24
DM
986
987 my $grubbootdev = get_partition_dev($target_dev, 1);
988 my $efibootdev = get_partition_dev($target_dev, 2);
a2876e48 989 my $osdev = get_partition_dev ($target_dev, 3);
aed81ff0 990
43b5216c 991 my $pcmd = ['sgdisk'];
89a12446 992
118d4f40
SI
993 my $pnum = 2;
994 push @$pcmd, "-n${pnum}:1M:+512M", "-t$pnum:EF00";
35be9ba7 995
f810f5d0 996 $pnum = 3;
118d4f40 997 push @$pcmd, "-n${pnum}:513M:${restricted_hdsize_mb}", "-t$pnum:$ptype";
35be9ba7 998
f810f5d0 999 push @$pcmd, $target_dev;
b282cfe8 1000
118d4f40 1001 my $os_size = $hdsize - 513*1024; # 512M efi + 1M bios_boot + 1M alignment
89a12446 1002
f810f5d0
DM
1003 syscmd($pcmd) == 0 ||
1004 die "unable to partition harddisk '${target_dev}'\n";
89a12446 1005
5ea943cf 1006 my $blocksize = logical_blocksize($target_dev);
118d4f40 1007
5ea943cf
SI
1008 if ($blocksize != 4096) {
1009 $pnum = 1;
1010 $pcmd = ['sgdisk', '-a1', "-n$pnum:34:2047", "-t$pnum:EF02" , $target_dev];
1011
1012 syscmd($pcmd) == 0 ||
1013 die "unable to create bios_boot partition '${target_dev}'\n";
1014 }
118d4f40 1015
dc4ad419
FG
1016 &$udevadm_trigger_block();
1017
1018 foreach my $part ($efibootdev, $osdev) {
1019 syscmd("dd if=/dev/zero of=$part bs=1M count=256") if -b $part;
1020 }
1021
f810f5d0
DM
1022 return ($os_size, $osdev, $efibootdev);
1023}
5c06ced5 1024
4566af04
TL
1025sub get_pv_list_from_vgname {
1026 my ($vgname) = @_;
1027
1028 my $res;
1029
1030 my $parser = sub {
1031 my $line = shift;
1032 $line =~ s/^\s+//;
1033 $line =~ s/\s+$//;
1034 return if !$line;
1035 my ($pv, $vg_uuid) = split(/\s+/, $line);
1036
1037 if (!defined($res->{$vg_uuid}->{pvs})) {
1038 $res->{$vg_uuid}->{pvs} = "$pv";
1039 } else {
1040 $res->{$vg_uuid}->{pvs} .= ", $pv";
1041 }
1042 };
1043 run_command("pvs --noheadings -o pv_name,vg_uuid -S vg_name='$vgname'", $parser, undef, 1);
1044
1045 return $res;
1046}
1047
1048sub ask_existing_vg_rename_or_abort {
1049 my ($vgname) = @_;
1050
1051 # this normally only happens if one put a disk with a PVE installation in
1052 # this server and that disk is not the installation target.
1053 my $duplicate_vgs = get_pv_list_from_vgname($vgname);
1054 return if !$duplicate_vgs;
1055
1056 my $message = "Detected existing '$vgname' Volume Group(s)! Do you want to:\n";
1057
1058 for my $vg_uuid (keys %$duplicate_vgs) {
1059 my $vg = $duplicate_vgs->{$vg_uuid};
1060
1061 # no high randomnes properties, but this is only for the cases where
1062 # we either have multiple "$vgname" vgs from multiple old PVE disks, or
1063 # we have a disk with both a "$vgname" and "$vgname-old"...
1064 my $short_uid = sprintf "%08X", rand(0xffffffff);
1065 $vg->{new_vgname} = "$vgname-OLD-$short_uid";
1066
1067 $message .= "rename VG backed by PV '$vg->{pvs}' to '$vg->{new_vgname}'\n";
1068 }
1069 $message .= "or cancel the installation?";
1070
1071 my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'question', 'ok-cancel', $message);
1072 my $response = $dialog->run();
1073 $dialog->destroy();
1074
1075 if ($response eq 'ok') {
1076 for my $vg_uuid (keys %$duplicate_vgs) {
1077 my $vg = $duplicate_vgs->{$vg_uuid};
1078 my $new_vgname = $vg->{new_vgname};
1079
1080 syscmd("vgrename $vg_uuid $new_vgname") == 0 ||
1081 die "could not rename VG from '$vg->{pvs}' ($vg_uuid) to '$new_vgname'!\n";
1082 }
1083 } else {
1084 set_next("_Reboot", sub { exit (0); } );
1085 display_html("fail.htm");
1086 die "Cancled installation by user, due to already existing volume group '$vgname'\n";
1087 }
1088}
1089
c6ed3b24
DM
1090sub create_lvm_volumes {
1091 my ($lvmdev, $os_size, $swap_size) = @_;
7bc4f6bd 1092
f7d18efd
DM
1093 my $vgname = $setup->{product};
1094
4566af04
TL
1095 ask_existing_vg_rename_or_abort($vgname);
1096
f7d18efd
DM
1097 my $rootdev = "/dev/$vgname/root";
1098 my $datadev = "/dev/$vgname/data";
9bb301fb 1099 my $swapfile;
84761f93 1100
2df572ae 1101 # we use --metadatasize 250k, which results in "pe_start = 512"
c6ed3b24 1102 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
71590b6a 1103 syscmd("/sbin/pvcreate --metadatasize 250k -y -ff $lvmdev") == 0 ||
eb4b1e56 1104 die "unable to initialize physical volume $lvmdev\n";
71590b6a 1105 syscmd("/sbin/vgcreate $vgname $lvmdev") == 0 ||
f7d18efd 1106 die "unable to create volume group '$vgname'\n";
89a12446 1107
c6ed3b24
DM
1108 my $hdgb = int($os_size/(1024*1024));
1109 my $space = (($hdgb > 128) ? 16 : ($hdgb/8))*1024*1024;
89a12446 1110
b6e875ca
DM
1111 my $rootsize;
1112 my $datasize;
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
b6e875ca
DM
1123 $rootsize = (($hdgb > ($maxroot*4)) ? $maxroot : $hdgb/4)*1024*1024;
1124
1125 my $rest = $os_size - $swap_size - $rootsize; # in KB
7bc4f6bd 1126
b6e875ca 1127 my $minfree;
e093944c 1128 if (defined($config_options->{minfree})) {
1464c7c9 1129 $minfree = (($config_options->{minfree}*1024*1024) >= $rest ) ? $space :
b6e875ca
DM
1130 $config_options->{minfree}*1024*1024 ;
1131 } else {
1132 $minfree = $space;
1133 }
1134
1135 $rest = $rest - $minfree;
1136
2ba9752e 1137 if (defined($config_options->{maxvz})) {
b6e875ca
DM
1138 $rest = (($config_options->{maxvz}*1024*1024) <= $rest) ?
1139 $config_options->{maxvz}*1024*1024 : $rest;
1140 }
7bc4f6bd 1141
b6e875ca
DM
1142 $datasize = $rest;
1143
1144 } else {
e093944c 1145 my $minfree = defined($config_options->{minfree}) ? $config_options->{minfree}*1024*1024 : $space;
b6e875ca 1146 $rootsize = $os_size - $minfree - $swap_size; # in KB
c6ed3b24 1147 }
7bc4f6bd 1148
9bb301fb 1149 if ($swap_size) {
71590b6a 1150 syscmd("/sbin/lvcreate -L${swap_size}K -nswap $vgname") == 0 ||
9bb301fb
FG
1151 die "unable to create swap volume\n";
1152
1153 $swapfile = "/dev/$vgname/swap";
1154 }
89a12446 1155
71590b6a 1156 syscmd("/sbin/lvcreate -L${rootsize}K -nroot $vgname") == 0 ||
eb4b1e56 1157 die "unable to create root volume\n";
89a12446 1158
d1969047
FG
1159 if ($datasize > 4*1024*1024) {
1160 my $metadatasize = $datasize/100; # default 1% of data
1161 $metadatasize = 1024*1024 if $metadatasize < 1024*1024; # but at least 1G
1162 $metadatasize = 16*1024*1024 if $metadatasize > 16*1024*1024; # but at most 16G
1163
1164 # otherwise the metadata is taken out of $minfree
1165 $datasize -= 2*$metadatasize;
1166
1167 # 1 4MB PE to allow for rounding
1168 $datasize -= 4*1024;
1169
71590b6a 1170 syscmd("/sbin/lvcreate -L${datasize}K -ndata $vgname") == 0 ||
b6e875ca 1171 die "unable to create data volume\n";
89a12446 1172
71590b6a 1173 syscmd("/sbin/lvconvert --yes --type thin-pool --poolmetadatasize ${metadatasize}K $vgname/data") == 0 ||
b6e875ca
DM
1174 die "unable to create data thin-pool\n";
1175 } else {
1176 $datadev = undef;
1177 }
5fd81672 1178
71590b6a 1179 syscmd("/sbin/vgchange -a y $vgname") == 0 ||
eb4b1e56 1180 die "unable to activate volume group\n";
7bc4f6bd 1181
b6e875ca 1182 return ($rootdev, $swapfile, $datadev);
c6ed3b24 1183}
7bc4f6bd 1184
c6ed3b24
DM
1185sub compute_swapsize {
1186 my ($hdsize) = @_;
89a12446 1187
c6ed3b24 1188 my $hdgb = int($hdsize/(1024*1024));
5c06ced5 1189
c6ed3b24 1190 my $swapsize;
9bb301fb 1191 if (defined($config_options->{swapsize})) {
c6ed3b24
DM
1192 $swapsize = $config_options->{swapsize}*1024*1024;
1193 } else {
1194 my $ss = int ($total_memory / 1024);
1195 $ss = 4 if $ss < 4;
1196 $ss = ($hdgb/8) if $ss > ($hdgb/8);
cbdfeb36 1197 $ss = 8 if $ss > 8;
c6ed3b24
DM
1198 $swapsize = $ss*1024*1024;
1199 }
d0d8ce3f
DM
1200
1201 return $swapsize;
c6ed3b24 1202}
5c06ced5 1203
341a93b7
TL
1204my sub chroot_chown {
1205 my ($root, $path, %param) = @_;
1206
1207 my $recursive = $param{recursive} ? ' -R' : '';
1208 my $user = $param{user};
1209 die "can not chown without user parameter\n" if !defined($user);
1210 my $group = $param{group} // $user;
1211
1212 syscmd("chroot $root /bin/chown $user:$group $recursive $path") == 0 ||
1213 die "chroot: unable to change owner for '$path'\n";
1214}
1215
1216my sub chroot_chmod {
1217 my ($root, $path, %param) = @_;
1218
1219 my $recursive = $param{recursive} ? ' -R' : '';
1220 my $mode = $param{mode};
1221 die "can not chmod without mode parameter\n" if !defined($mode);
1222
1223 syscmd("chroot $root /bin/chmod $mode $recursive $path") == 0 ||
1224 die "chroot: unable to change permission mode for '$path'\n";
1225}
1226
e38884af
SI
1227sub prepare_systemd_boot_esp {
1228 my ($espdev, $targetdir) = @_;
1229
795e7ee5
TL
1230 syscmd("chroot $targetdir pve-efiboot-tool init $espdev") == 0 ||
1231 die "unable to init ESP and install systemd-boot loader on '$espdev'\n";
e38884af 1232}
121ebc59 1233
597db5de
TL
1234sub prepare_grub_efi_boot_esp {
1235 my ($dev, $espdev, $targetdir) = @_;
1236
1237 syscmd("mount -n $espdev -t vfat $targetdir/boot/efi") == 0 ||
1238 die "unable to mount $espdev\n";
1239
1f8429eb
FG
1240 eval {
1241 my $rc = syscmd("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
1242 if ($rc != 0) {
1243 if ($boot_type eq 'efi') {
1244 die "unable to install the EFI boot loader on '$dev'\n";
1245 } else {
1246 warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
1247 }
597db5de 1248 }
1f8429eb
FG
1249 # also install fallback boot file (OVMF does not boot without)
1250 mkdir("$targetdir/boot/efi/EFI/BOOT");
1251 syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
1252 die "unable to copy efi boot loader\n";
1253 };
1254 my $err = $@;
1255
1256 eval {
1257 syscmd("umount $targetdir/boot/efi") == 0 ||
1258 die "unable to umount $targetdir/boot/efi\n";
1259 };
1260 warn $@ if $@;
597db5de 1261
1f8429eb 1262 die "failed to prepare EFI boot using Grub on '$espdev': $err" if $err;
597db5de
TL
1263}
1264
c6ed3b24 1265sub extract_data {
fafc616c 1266 my ($basefile, $targetdir) = @_;
89a12446 1267
c6ed3b24 1268 die "target '$targetdir' does not exist\n" if ! -d $targetdir;
89a12446 1269
121ebc59
DM
1270 my $starttime = [Time::HiRes::gettimeofday];
1271
c6ed3b24 1272 my $bootdevinfo = [];
84761f93 1273
c6ed3b24
DM
1274 my $swapfile;
1275 my $rootdev;
e2c51d7c 1276 my $datadev;
84761f93 1277
121ebc59
DM
1278 my $use_zfs = 0;
1279 my $use_btrfs = 0;
89092156 1280
c6ed3b24 1281 my $filesys = $config_options->{filesys};
89092156 1282
c6ed3b24
DM
1283 if ($filesys =~ m/zfs/) {
1284 $target_hd = undef; # do not use this config
1285 $use_zfs = 1;
5772392c 1286 $targetdir = "/$zfspoolname/ROOT/$zfsrootvolname";
121ebc59
DM
1287 } elsif ($filesys =~ m/btrfs/) {
1288 $target_hd = undef; # do not use this config
1289 $use_btrfs = 1;
c6ed3b24 1290 }
1464c7c9 1291
c6ed3b24
DM
1292 if ($use_zfs) {
1293 my $i;
1294 for ($i = 5; $i > 0; $i--) {
1295 syscmd("modprobe zfs");
1296 last if -c "/dev/zfs";
1297 sleep(1);
1298 }
89092156 1299
c6ed3b24
DM
1300 die "unable to load zfs kernel module\n" if !$i;
1301 }
89092156 1302
1f8429eb
FG
1303 my $bootloader_err;
1304
c6ed3b24 1305 eval {
89a12446 1306
89a12446 1307
c6ed3b24 1308 my $maxper = 0.25;
89a12446 1309
71590b6a 1310 update_progress(0, 0, $maxper, "create partitions");
c6ed3b24 1311
857c43a9
FG
1312 syscmd("vgchange -an") if !$opt_testmode; # deactivate all detected VGs
1313
c6ed3b24 1314 if ($opt_testmode) {
89a12446 1315
6b900321
DM
1316 $rootdev = abs_path($opt_testmode);
1317 syscmd("umount $rootdev");
121ebc59 1318
6b900321 1319 if ($use_btrfs) {
121ebc59 1320
1464c7c9 1321 die "unsupported btrfs mode (for testing environment)\n"
121ebc59
DM
1322 if $filesys ne 'btrfs (RAID0)';
1323
1324 btrfs_create([$rootdev], 'single');
5c06ced5 1325
121ebc59 1326 } elsif ($use_zfs) {
5c06ced5 1327
121ebc59 1328 die "unsupported zfs mode (for testing environment)\n"
c6ed3b24
DM
1329 if $filesys ne 'zfs (RAID0)';
1330
71590b6a 1331 syscmd("zpool destroy $zfstestpool");
5c06ced5 1332
5fd81672 1333 zfs_create_rpool($rootdev);
1464c7c9 1334
121ebc59
DM
1335 } else {
1336
6b900321 1337 # nothing to do
121ebc59
DM
1338 }
1339
1340 } elsif ($use_btrfs) {
1341
1342 my ($devlist, $btrfs_mode) = get_btrfs_raid_setup();
1343 my $btrfs_partitions = [];
1344 my $disksize;
1345 foreach my $hd (@$devlist) {
1346 my $devname = @$hd[1];
5ea943cf
SI
1347 my $logical_bsize = @$hd[4];
1348
857c43a9 1349 &$clean_disk($devname);
121ebc59
DM
1350 my ($size, $osdev, $efidev) =
1351 partition_bootable_disk($devname, undef, '8300');
1352 $rootdev = $osdev if !defined($rootdev); # simply point to first disk
1353 my $by_id = find_stable_path("/dev/disk/by-id", $devname);
5ff5a8d0
SI
1354 push @$bootdevinfo, {
1355 esp => $efidev,
1356 devname => $devname,
1357 osdev => $osdev,
1358 by_id => $by_id,
5ea943cf 1359 logical_bsize => $logical_bsize,
5ff5a8d0 1360 };
121ebc59
DM
1361 push @$btrfs_partitions, $osdev;
1362 $disksize = $size;
5c06ced5 1363 }
c6ed3b24 1364
121ebc59
DM
1365 &$udevadm_trigger_block();
1366
1367 btrfs_create($btrfs_partitions, $btrfs_mode);
1368
c6ed3b24
DM
1369 } elsif ($use_zfs) {
1370
82695821 1371 my ($devlist, $vdev) = get_zfs_raid_setup();
c6ed3b24 1372
857c43a9
FG
1373 foreach my $hd (@$devlist) {
1374 &$clean_disk(@$hd[1]);
1375 }
4fb6ac60 1376
82695821 1377 # install esp/boot part on all, we can only win!
4fb6ac60 1378 my $disksize;
82695821 1379 for my $hd (@$devlist) {
c6ed3b24 1380 my $devname = @$hd[1];
5ea943cf 1381 my $logical_bsize = @$hd[4];
118d4f40 1382
e38884af 1383 my ($size, $osdev, $efidev) =
d6e919d7 1384 partition_bootable_disk($devname, $config_options->{hdsize}, 'BF01');
4fb6ac60 1385
14aacec8 1386 zfs_mirror_size_check($disksize, $size) if $disksize;
4fb6ac60
TL
1387
1388 push @$bootdevinfo, {
1389 esp => $efidev,
1390 devname => $devname,
5ea943cf
SI
1391 osdev => $osdev,
1392 logical_bsize => $logical_bsize,
4fb6ac60 1393 };
c6ed3b24 1394 $disksize = $size;
c6ed3b24
DM
1395 }
1396
121ebc59 1397 &$udevadm_trigger_block();
c6ed3b24 1398
35c6f89c
DM
1399 foreach my $di (@$bootdevinfo) {
1400 my $devname = $di->{devname};
1401 $di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
1464c7c9 1402
e1fdd3d0 1403 my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
c6ed3b24 1404
35c6f89c
DM
1405 $vdev =~ s/ $devname/ $osdev/;
1406 }
1407
e1b49086
SI
1408 foreach my $hd (@$devlist) {
1409 my $devname = @$hd[1];
1410 my $by_id = find_stable_path ("/dev/disk/by-id", $devname);
1411
f0830a59 1412 $vdev =~ s/ $devname/ $by_id/ if $by_id;
e1b49086
SI
1413 }
1414
5fd81672 1415 zfs_create_rpool($vdev);
1464c7c9 1416
c6ed3b24
DM
1417 } else {
1418
1419 die "target '$target_hd' is not a valid block device\n" if ! -b $target_hd;
1420
857c43a9
FG
1421 &$clean_disk($target_hd);
1422
5ea943cf
SI
1423 my $logical_bsize = logical_blocksize($target_hd);
1424
1464c7c9
DM
1425 my ($os_size, $osdev, $efidev);
1426 ($os_size, $osdev, $efidev) =
d6e919d7 1427 partition_bootable_disk($target_hd, $config_options->{hdsize}, '8E00');
c6ed3b24 1428
121ebc59 1429 &$udevadm_trigger_block();
c6ed3b24 1430
35c6f89c 1431 my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
5ff5a8d0
SI
1432 push @$bootdevinfo, {
1433 esp => $efidev,
1434 devname => $target_hd,
1435 osdev => $osdev,
1436 by_id => $by_id,
5ea943cf 1437 logical_bsize => $logical_bsize,
5ff5a8d0 1438 };
c6ed3b24 1439
35c6f89c 1440 my $swap_size = compute_swapsize($os_size);
e2c51d7c 1441 ($rootdev, $swapfile, $datadev) =
35c6f89c 1442 create_lvm_volumes($osdev, $os_size, $swap_size);
c6ed3b24 1443
35c6f89c 1444 # trigger udev to create /dev/disk/by-uuid
121ebc59 1445 &$udevadm_trigger_block(1);
89a12446
DM
1446 }
1447
481671c3
DM
1448 if ($use_zfs) {
1449 # to be fast during installation
71590b6a 1450 syscmd("zfs set sync=disabled $zfspoolname") == 0 ||
481671c3
DM
1451 die "unable to set zfs properties\n";
1452 }
1453
71590b6a 1454 update_progress(0.03, 0, $maxper, "create swap space");
89a12446 1455 if ($swapfile) {
71590b6a 1456 syscmd("mkswap -f $swapfile") == 0 ||
89a12446
DM
1457 die "unable to create swap space\n";
1458 }
1459
71590b6a 1460 update_progress(0.05, 0, $maxper, "creating filesystems");
89a12446 1461
c6ed3b24 1462 foreach my $di (@$bootdevinfo) {
f810f5d0 1463 next if !$di->{esp};
57a03069 1464 # FIXME remove '-s1' once https://github.com/dosfstools/dosfstools/issues/111 is fixed
5ea943cf
SI
1465 my $vfat_extra_opts = ($di->{logical_bsize} == 4096) ? '-s1' : '';
1466 syscmd("mkfs.vfat $vfat_extra_opts -F32 $di->{esp}") == 0 ||
c6ed3b24
DM
1467 die "unable to initialize EFI ESP on device $di->{esp}\n";
1468 }
1469
121ebc59
DM
1470 if ($use_zfs) {
1471 # do nothing
1472 } elsif ($use_btrfs) {
1473 # do nothing
1474 } else {
71590b6a 1475 create_filesystem($rootdev, 'root', $filesys, 0.05, $maxper, 0, 1);
89a12446
DM
1476 }
1477
71590b6a 1478 update_progress(1, 0.05, $maxper, "mounting target $rootdev");
89a12446 1479
121ebc59
DM
1480 if ($use_zfs) {
1481 # do nothing
121ebc59 1482 } else {
6e56032e
FG
1483 my $mount_opts = 'noatime';
1484 $mount_opts .= ',nobarrier'
1485 if $use_btrfs || $filesys =~ /^ext\d$/;
1486
1487 syscmd("mount -n $rootdev -o $mount_opts $targetdir") == 0 ||
35c6f89c
DM
1488 die "unable to mount $rootdev\n";
1489 }
89a12446 1490
35c6f89c
DM
1491 mkdir "$targetdir/boot";
1492 mkdir "$targetdir/boot/efi";
89a12446 1493
5fd81672
DM
1494 mkdir "$targetdir/var";
1495 mkdir "$targetdir/var/lib";
121ebc59 1496
f7d18efd
DM
1497 if ($setup->{product} eq 'pve') {
1498 mkdir "$targetdir/var/lib/vz";
1499 mkdir "$targetdir/var/lib/pve";
1500
1501 if ($use_btrfs) {
1502 syscmd("btrfs subvolume create $targetdir/var/lib/pve/local-btrfs") == 0 ||
1503 die "unable to create btrfs subvolume\n";
1504 }
121ebc59 1505 }
89a12446 1506
8d7ddbde
TL
1507 mkdir "$targetdir/mnt";
1508 mkdir "$targetdir/mnt/hostrun";
1509 syscmd("mount --bind /run $targetdir/mnt/hostrun") == 0 ||
1510 die "unable to bindmount run on $targetdir/mnt/hostrun\n";
1511
71590b6a 1512 update_progress(1, 0.05, $maxper, "extracting base system");
89a12446 1513
fafc616c
DM
1514 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
1515 $ino || die "unable to open file '$basefile' - $!\n";
968fa90b 1516
c437cef5
DM
1517 my $files = file_read_firstline("${proxmox_cddir}/proxmox/$setup->{product}-base.cnt") ||
1518 die "unable to read base file count\n";
89a12446
DM
1519
1520 my $per = 0;
1521 my $count = 0;
1522
71590b6a 1523 run_command("unsquashfs -f -dest $targetdir -i $basefile", sub {
89a12446 1524 my $line = shift;
fafc616c 1525 return if $line !~ m/^$targetdir/;
89a12446
DM
1526 $count++;
1527 my $nper = int (($count *100)/$files);
1528 if ($nper != $per) {
1529 $per = $nper;
0f3d1edd 1530 my $frac = $per > 100 ? 1 : $per/100;
71590b6a 1531 update_progress($frac, $maxper, 0.5);
89a12446
DM
1532 }
1533 });
1534
71590b6a 1535 syscmd("mount -n -t tmpfs tmpfs $targetdir/tmp") == 0 ||
89a12446 1536 die "unable to mount tmpfs on $targetdir/tmp\n";
71590b6a 1537 syscmd("mount -n -t proc proc $targetdir/proc") == 0 ||
89a12446 1538 die "unable to mount proc on $targetdir/proc\n";
71590b6a 1539 syscmd("mount -n -t sysfs sysfs $targetdir/sys") == 0 ||
89a12446 1540 die "unable to mount sysfs on $targetdir/sys\n";
f238dd03 1541 if ($boot_type eq 'efi') {
dea730ea 1542 syscmd("mount -n -t efivarfs efivarfs $targetdir/sys/firmware/efi/efivars") == 0 ||
f238dd03
TL
1543 die "unable to mount efivarfs on $targetdir/sys/firmware/efi/efivars: $!\n";
1544 }
8d7ddbde
TL
1545 syscmd("chroot $targetdir mount --bind /mnt/hostrun /run") == 0 ||
1546 die "unable to re-bindmount hostrun on /run in chroot\n";
89a12446 1547
71590b6a 1548 update_progress(1, $maxper, 0.5, "configuring base system");
89a12446
DM
1549
1550 # configure hosts
1551
968fa90b 1552 my $hosts =
89a12446 1553 "127.0.0.1 localhost.localdomain localhost\n" .
57cd2e0f 1554 "$ipaddress $hostname.$domain $hostname\n\n" .
89a12446
DM
1555 "# The following lines are desirable for IPv6 capable hosts\n\n" .
1556 "::1 ip6-localhost ip6-loopback\n" .
1557 "fe00::0 ip6-localnet\n" .
1558 "ff00::0 ip6-mcastprefix\n" .
1559 "ff02::1 ip6-allnodes\n" .
1560 "ff02::2 ip6-allrouters\n" .
1561 "ff02::3 ip6-allhosts\n";
1562
71590b6a 1563 write_config($hosts, "$targetdir/etc/hosts");
89a12446 1564
71590b6a 1565 write_config("$hostname\n", "$targetdir/etc/hostname");
89a12446 1566
71590b6a 1567 syscmd("/bin/hostname $hostname") if !$opt_testmode;
89a12446
DM
1568
1569 # configure interfaces
1570
b6200603
DM
1571 my $ifaces = "auto lo\niface lo inet loopback\n\n";
1572
1573 my $ntype = $ipversion == 4 ? 'inet' : 'inet6';
1574
4a0331ab
DM
1575 my $ethdev = $ipconf->{ifaces}->{$ipconf->{selected}}->{name};
1576
1577 if ($setup->{bridged_network}) {
1578 $ifaces .= "iface $ethdev $ntype manual\n";
1579
1580 $ifaces .=
1581 "\nauto vmbr0\niface vmbr0 $ntype static\n" .
b1838e1e 1582 "\taddress $cidr\n" .
4a0331ab
DM
1583 "\tgateway $gateway\n" .
1584 "\tbridge_ports $ethdev\n" .
1585 "\tbridge_stp off\n" .
1586 "\tbridge_fd 0\n";
1587 } else {
1588 $ifaces .= "auto $ethdev\n" .
1589 "iface $ethdev $ntype static\n" .
b1838e1e 1590 "\taddress $cidr\n" .
4a0331ab
DM
1591 "\tgateway $gateway\n";
1592 }
89a12446 1593
fe44bd92
FG
1594 foreach my $iface (sort keys %{$ipconf->{ifaces}}) {
1595 my $name = $ipconf->{ifaces}->{$iface}->{name};
4a0331ab 1596 next if $name eq $ethdev;
fe44bd92
FG
1597
1598 $ifaces .= "\niface $name $ntype manual\n";
1599 }
1600
71590b6a 1601 write_config($ifaces, "$targetdir/etc/network/interfaces");
89a12446
DM
1602
1603 # configure dns
1604
39a0f44b 1605 my $resolvconf = "search $domain\nnameserver $dnsserver\n";
71590b6a 1606 write_config($resolvconf, "$targetdir/etc/resolv.conf");
89a12446 1607
5c06ced5
DM
1608 # configure fstab
1609
1610 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass>\n";
1611
121ebc59
DM
1612 if ($use_zfs) {
1613 # do nothing
1614 } elsif ($use_btrfs) {
1615 my $fsuuid;
1616 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev";
1617 run_command($cmd, sub {
1618 my $line = shift;
1619
1620 if ($line =~ m/^UUID=([A-Fa-f0-9\-]+)$/) {
1621 $fsuuid = $1;
1622 }
1623 });
1624
1625 die "unable to detect FS UUID" if !defined($fsuuid);
1626
1627 $fstab .= "UUID=$fsuuid / btrfs defaults 0 1\n";
1628 } else {
80090926
DM
1629 my $root_mountopt = $fssetup->{$filesys}->{root_mountopt} || 'defaults';
1630 $fstab .= "$rootdev / $filesys ${root_mountopt} 0 1\n";
7bc4f6bd 1631 }
a84ea010
DM
1632
1633 # mount /boot/efi
1634 # Note: this is required by current grub, but really dangerous, because
1635 # vfat does not have journaling, so it triggers manual fsck after each crash
1636 # so we only mount /boot/efi if really required (efi systems).
4fb6ac60 1637 if ($boot_type eq 'efi' && !$use_zfs) {
a84ea010 1638 if (scalar(@$bootdevinfo)) {
f810f5d0 1639 my $di = @$bootdevinfo[0]; # simply use first disk
4fb6ac60
TL
1640
1641 if ($di->{esp}) {
f810f5d0
DM
1642 my $efi_boot_uuid = $di->{esp};
1643 if (my $uuid = find_dev_by_uuid ($di->{esp})) {
1644 $efi_boot_uuid = "UUID=$uuid";
1645 }
1464c7c9 1646
f810f5d0
DM
1647 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1\n";
1648 }
a84ea010 1649 }
84761f93
DM
1650 }
1651
c1cfbb1c 1652
89a12446
DM
1653 $fstab .= "$swapfile none swap sw 0 0\n" if $swapfile;
1654
1655 $fstab .= "proc /proc proc defaults 0 0\n";
1656
71590b6a
OB
1657 write_config($fstab, "$targetdir/etc/fstab");
1658 write_config("", "$targetdir/etc/mtab");
968fa90b 1659
c1cfbb1c
SI
1660 syscmd("cp ${proxmox_libdir}/policy-disable-rc.d " .
1661 "$targetdir/usr/sbin/policy-rc.d") == 0 ||
1662 die "unable to copy policy-rc.d\n";
1663 syscmd("cp ${proxmox_libdir}/fake-start-stop-daemon " .
1664 "$targetdir/sbin/") == 0 ||
89a12446
DM
1665 die "unable to copy start-stop-daemon\n";
1666
71590b6a
OB
1667 diversion_add($targetdir, "/sbin/start-stop-daemon", "/sbin/fake-start-stop-daemon");
1668 diversion_add($targetdir, "/usr/sbin/update-grub", "/bin/true");
1669 diversion_add($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
89a12446 1670
72d2dcd0
SI
1671 my $machine_id = run_command("systemd-id128 new");
1672 die "unable to create a new machine-id\n" if ! $machine_id;
1673 write_config($machine_id, "$targetdir/etc/machine-id");
1674
a346a962
SI
1675 syscmd("cp /etc/hostid $targetdir/etc/") == 0 ||
1676 die "unable to copy hostid\n";
1677
71590b6a 1678 syscmd("touch $targetdir/proxmox_install_mode");
89a12446 1679
e35d5efb 1680 my $grub_install_devices_txt = '';
3573c046 1681 foreach my $di (@$bootdevinfo) {
e35d5efb 1682 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt;
ff863262 1683 $grub_install_devices_txt .= $di->{by_id} || $di->{devname};
3573c046
DM
1684 }
1685
b1293fcb
FG
1686 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1687 my $xkmap = $cmap->{kmap}->{$keymap}->{x11} // 'us';
1464c7c9 1688
89a12446
DM
1689 debconfig_set ($targetdir, <<_EOD);
1690locales locales/default_environment_locale select en_US.UTF-8
1691locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1692samba-common samba-common/dhcp boolean false
1693samba-common samba-common/workgroup string WORKGROUP
e953719f 1694postfix postfix/main_mailer_type select No configuration
b1293fcb 1695keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
814f5c39 1696d-i debian-installer/locale select en_US.UTF-8
3573c046 1697grub-pc grub-pc/install_devices select $grub_install_devices_txt
89a12446
DM
1698_EOD
1699
89a12446 1700 my $pkg_count = 0;
97980bf2 1701 while (<${proxmox_pkgdir}/*.deb>) { $pkg_count++ };
89a12446 1702
121ebc59
DM
1703 # btrfs/dpkg is extremely slow without --force-unsafe-io
1704 my $dpkg_opts = $use_btrfs ? "--force-unsafe-io" : "";
1705
89a12446 1706 $count = 0;
97980bf2 1707 while (<${proxmox_pkgdir}/*.deb>) {
89a12446
DM
1708 chomp;
1709 my $path = $_;
97980bf2 1710 my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/;
71590b6a 1711 update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb");
89a12446 1712 print "extracting: $deb\n";
71590b6a 1713 syscmd("cp $path $targetdir/tmp/$deb") == 0 ||
89a12446 1714 die "installation of package $deb failed\n";
71590b6a 1715 syscmd("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/$deb") == 0 ||
968fa90b 1716 die "installation of package $deb failed\n";
71590b6a 1717 update_progress((++$count)/$pkg_count, 0.5, 0.75);
89a12446
DM
1718 }
1719
3b11dce4
FG
1720 # needed for postfix postinst in case no other NIC is active
1721 syscmd("chroot $targetdir ifup lo");
1722
121ebc59 1723 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a";
89a12446 1724 $count = 0;
71590b6a 1725 run_command($cmd, sub {
89a12446
DM
1726 my $line = shift;
1727 if ($line =~ m/Setting up\s+(\S+)/) {
71590b6a
OB
1728 update_progress((++$count)/$pkg_count, 0.75, 0.95,
1729 "configuring $1");
89a12446
DM
1730 }
1731 });
968fa90b 1732
89a12446
DM
1733 unlink "$targetdir/etc/mailname";
1734 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
71590b6a 1735 write_config($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
89a12446
DM
1736
1737 # make sure we have all postfix directories
71590b6a 1738 syscmd("chroot $targetdir /usr/sbin/postfix check");
89a12446 1739 # cleanup mail queue
71590b6a 1740 syscmd("chroot $targetdir /usr/sbin/postsuper -d ALL");
29da2d42
SI
1741 # create /etc/aliases.db (/etc/aliases is shipped in the base squashfs)
1742 syscmd("chroot $targetdir /usr/bin/newaliases");
89a12446 1743
6b5dc3d0 1744 # enable NTP (timedatectl set-ntp true does not work without DBUS)
71590b6a 1745 syscmd("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
6b5dc3d0 1746
89a12446
DM
1747 unlink "$targetdir/proxmox_install_mode";
1748
968fa90b 1749 # set timezone
89a12446
DM
1750 unlink ("$targetdir/etc/localtime");
1751 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
71590b6a 1752 write_config("$timezone\n", "$targetdir/etc/timezone");
89a12446 1753
89a12446
DM
1754 # set apt mirror
1755 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1756 my $fn = "$targetdir/etc/apt/sources.list";
71590b6a 1757 syscmd("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
89a12446
DM
1758 }
1759
19edf8b7
DM
1760 # create extended_states for apt (avoid cron job warning if that
1761 # file does not exist)
71590b6a 1762 write_config('', "$targetdir/var/lib/apt/extended_states");
19edf8b7 1763
c2657b8b 1764 # allow ssh root login
abcadb95 1765 syscmd(['sed', '-i', 's/^#\?PermitRootLogin.*/PermitRootLogin yes/', "$targetdir/etc/ssh/sshd_config"]);
861a26d4
DM
1766
1767 if ($setup->{product} eq 'pmg') {
1768 # install initial clamav DB
1769 my $srcdir = "${proxmox_cddir}/proxmox/clamav";
05eb99e2 1770 foreach my $fn ("main.cvd", "bytecode.cvd", "daily.cvd", "safebrowsing.cvd") {
71590b6a 1771 syscmd("cp \"$srcdir/$fn\" \"$targetdir/var/lib/clamav\"") == 0 ||
861a26d4
DM
1772 die "installation of clamav db file '$fn' failed\n";
1773 }
1774 syscmd("chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav") == 0 ||
1775 die "unable to set owner for clamav database files\n";
1776 }
1777
58a09baa
DM
1778 if ($setup->{product} eq 'pve') {
1779 # save installer settings
1780 my $ucc = uc ($country);
1781 debconfig_set($targetdir, "pve-manager pve-manager/country string $ucc\n");
1782 }
89a12446 1783
71590b6a 1784 update_progress(0.8, 0.95, 1, "make system bootable");
89a12446 1785
5c06ced5 1786 if ($use_zfs) {
71590b6a 1787 syscmd("sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT\\/$zfsrootvolname boot=zfs\"/' $targetdir/etc/default/grub") == 0 ||
5c06ced5 1788 die "unable to update /etc/default/grub\n";
4fb6ac60 1789
d8530b74 1790 write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs", "$targetdir/etc/kernel/cmdline");
1464c7c9 1791
5c06ced5 1792 }
23c337f5 1793
71590b6a
OB
1794 diversion_remove($targetdir, "/usr/sbin/update-grub");
1795 diversion_remove($targetdir, "/usr/sbin/update-initramfs");
89a12446 1796
56207f2a
DM
1797 my $kapi;
1798 foreach my $fn (<$targetdir/lib/modules/*>) {
1799 if ($fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$!) {
1800 die "found multiple kernels\n" if defined($kapi);
1801 $kapi = $1;
1802 }
1803 }
1804 die "unable to detect kernel version\n" if !defined($kapi);
1805
c6ed3b24 1806 if (!$opt_testmode) {
89a12446
DM
1807
1808 unlink ("$targetdir/etc/mtab");
1809 symlink ("/proc/mounts", "$targetdir/etc/mtab");
71590b6a 1810 syscmd("mount -n --bind /dev $targetdir/dev");
89a12446 1811
1f8429eb
FG
1812 my $bootloader_err_list = [];
1813 eval {
1814 syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1815 die "unable to install initramfs\n";
1816
5ea943cf
SI
1817 my $native_4k_disk_bootable = 0;
1818 foreach my $di (@$bootdevinfo) {
1819 $native_4k_disk_bootable |= ($di->{logical_bsize} == 4096);
1820 }
1821
1f8429eb
FG
1822 foreach my $di (@$bootdevinfo) {
1823 my $dev = $di->{devname};
5ea943cf
SI
1824 if (!$native_4k_disk_bootable) {
1825 eval {
1826 syscmd("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1827 die "unable to install the i386-pc boot loader on '$dev'\n";
1828 };
1829 push @$bootloader_err_list, $@ if $@;
1830 }
1f8429eb
FG
1831
1832 eval {
1833 if (my $esp = $di->{esp}) {
1834 if ($use_zfs) {
1835 prepare_systemd_boot_esp($esp, $targetdir);
1836 } else {
1837 prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
1838 }
1839 }
1840 };
1841 push @$bootloader_err_list, $@ if $@;
1e61f3d8 1842 }
89a12446 1843
1f8429eb
FG
1844 syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
1845 die "unable to update boot loader config\n";
1f8429eb
FG
1846 };
1847 push @$bootloader_err_list, $@ if $@;
1848
1849 if (scalar(@$bootloader_err_list) > 0) {
1850 $bootloader_err = "bootloader setup errors:\n";
1851 map { $bootloader_err .= "- $_" } @$bootloader_err_list;
1852 warn $bootloader_err;
f2afc0fc 1853 }
03c686b7 1854
71590b6a 1855 syscmd("umount $targetdir/dev");
89a12446
DM
1856 }
1857
968fa90b 1858 # cleanup
89a12446 1859
89a12446
DM
1860 unlink "$targetdir/usr/sbin/policy-rc.d";
1861
71590b6a 1862 diversion_remove($targetdir, "/sbin/start-stop-daemon");
89a12446
DM
1863
1864 # set root password
968fa90b 1865 my $octets = encode("utf-8", $password);
71590b6a
OB
1866 run_command("chroot $targetdir /usr/sbin/chpasswd", undef,
1867 "root:$octets\n");
7053f98b 1868
038552a1 1869 if ($setup->{product} eq 'pmg') {
038552a1 1870 # save admin email
71590b6a
OB
1871 write_config("section: admin\n\temail ${mailto}\n",
1872 "$targetdir/etc/pmg/pmg.conf");
038552a1
DM
1873
1874 } elsif ($setup->{product} eq 'pve') {
7053f98b 1875
8acc47b5 1876 # create pmxcfs DB
7053f98b 1877
8acc47b5
DM
1878 my $tmpdir = "$targetdir/tmp/pve";
1879 mkdir $tmpdir;
7053f98b 1880
8acc47b5
DM
1881 # write vnc keymap to datacenter.cfg
1882 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
71590b6a
OB
1883 write_config("keyboard: $vnckmap\n",
1884 "$tmpdir/datacenter.cfg");
968fa90b 1885
8acc47b5 1886 # save admin email
e32b8d2d 1887 write_config("user:root\@pam:1:0:::${mailto}::\n", "$tmpdir/user.cfg");
5fd81672 1888
8acc47b5 1889 # write storage.cfg
d8c697d4 1890 my $storage_cfg_fn = "$tmpdir/storage.cfg";
8acc47b5 1891 if ($use_zfs) {
71590b6a 1892 write_config($storage_cfg_zfs, $storage_cfg_fn);
8acc47b5 1893 } elsif ($use_btrfs) {
71590b6a 1894 write_config($storage_cfg_btrfs, $storage_cfg_fn);
e2c51d7c 1895 } elsif ($datadev) {
71590b6a 1896 write_config($storage_cfg_lvmthin, $storage_cfg_fn);
e2c51d7c 1897 } else {
71590b6a 1898 write_config($storage_cfg_local, $storage_cfg_fn);
8acc47b5 1899 }
7053f98b 1900
8acc47b5
DM
1901 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1902
71590b6a 1903 syscmd("rm -rf $tmpdir");
ca74501d
TL
1904 } elsif ($setup->{product} eq 'pbs') {
1905 my $base_cfg_path = "/etc/proxmox-backup";
e32b8d2d 1906 mkdir "$targetdir/$base_cfg_path";
341a93b7
TL
1907
1908 chroot_chown($targetdir, $base_cfg_path, user => 'backup', recursive => 1);
1909 chroot_chmod($targetdir, $base_cfg_path, mode => '0700');
e32b8d2d
TL
1910
1911 my $user_cfg_fn = "$base_cfg_path/user.cfg";
1912 write_config("user: root\@pam\n\temail ${mailto}\n", "$targetdir/$user_cfg_fn");
1913 chroot_chown($targetdir, $user_cfg_fn, user => 'root', group => 'backup');
1914 chroot_chmod($targetdir, $user_cfg_fn, mode => '0640');
8acc47b5 1915 }
89a12446
DM
1916 };
1917
1918 my $err = $@;
1919
71590b6a 1920 update_progress(1, 0, 1, "");
89a12446
DM
1921
1922 print $err if $err;
1923
1924 if ($opt_testmode) {
121ebc59
DM
1925 my $elapsed = Time::HiRes::tv_interval($starttime);
1926 print "Elapsed extract time: $elapsed\n";
1927
71590b6a 1928 syscmd("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
89a12446
DM
1929 }
1930
8d7ddbde
TL
1931 syscmd("umount $targetdir/run");
1932 syscmd("umount $targetdir/mnt/hostrun");
71590b6a
OB
1933 syscmd("umount $targetdir/tmp");
1934 syscmd("umount $targetdir/proc");
f238dd03 1935 syscmd("umount $targetdir/sys/firmware/efi/efivars");
71590b6a 1936 syscmd("umount $targetdir/sys");
6fbd1fb1
DM
1937
1938 if ($use_zfs) {
71590b6a 1939 syscmd("zfs umount -a") == 0 ||
6fbd1fb1
DM
1940 die "unable to unmount zfs\n";
1941 } else {
71590b6a 1942 syscmd("umount -d $targetdir");
6fbd1fb1 1943 }
89a12446 1944
5c06ced5 1945 if (!$err && $use_zfs) {
71590b6a 1946 syscmd("zfs set sync=standard $zfspoolname") == 0 ||
481671c3
DM
1947 die "unable to set zfs properties\n";
1948
71590b6a 1949 syscmd("zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname") == 0 ||
5c06ced5 1950 die "zfs set mountpoint failed\n";
1464c7c9 1951
71590b6a 1952 syscmd("zpool set bootfs=$zfspoolname/ROOT/$zfsrootvolname $zfspoolname") == 0 ||
5c06ced5 1953 die "zfs set bootfs failed\n";
71590b6a 1954 syscmd("zpool export $zfspoolname");
5c06ced5
DM
1955 }
1956
1f8429eb
FG
1957 if ($bootloader_err) {
1958 $err = $err ? "$err\n$bootloader_err" : $bootloader_err;
1959 }
1960
89a12446
DM
1961 die $err if $err;
1962}
1963
550958aa
DM
1964my $last_display_change = 0;
1965
1966my $display_info_counter = 0;
1967
1968my $display_info_items = [
1969 "extract1-license.htm",
1970 "extract2-rulesystem.htm",
1971 "extract3-spam.htm",
1972 "extract4-virus.htm",
1973 ];
1974
1975sub display_info {
1976
1977 my $min_display_time = 15;
1978
1979 my $ctime = time();
1980
1981 return if ($ctime - $last_display_change) < $min_display_time;
1982
1983 my $page = $display_info_items->[$display_info_counter % scalar(@$display_info_items)];
1984
1985 $display_info_counter++;
1986
1987 display_html($page);
1988}
1989
89a12446
DM
1990sub display_html {
1991 my ($filename) = @_;
1992
201a5120
OB
1993 $filename = $steps[$step_number]->{html} if !$filename;
1994
a04ac176 1995 my $path = "${proxmox_libdir}/html/$filename";
c437cef5 1996
8a50920c
DM
1997 my $url = "file://$path";
1998
1999 my $data = file_get_contents($path);
2000
b10074d1
TL
2001 my $product = $product_cfg->{$setup->{product}};
2002
8a50920c 2003 if ($filename eq 'license.htm') {
93f25df9
TL
2004 my $license = eval { decode('utf8', file_get_contents("${proxmox_cddir}/EULA")) };
2005 if (my $err = $@) {
2006 die $err if !$opt_testmode;
2007 $license = "TESTMODE: Ignore non existent EULA...\n";
2008 }
3c866639 2009 my $title = "END USER LICENSE AGREEMENT (EULA)";
f91c161b 2010 $data =~ s/__LICENSE__/$license/;
8a50920c 2011 $data =~ s/__LICENSE_TITLE__/$title/;
3bcac16b
TL
2012 } elsif ($filename eq 'success.htm') {
2013 my $addr = $ipversion == 6 ? "[${ipaddress}]" : "$ipaddress";
cfb92364
TL
2014 $data =~ s/__IPADDR__/$addr/g;
2015 $data =~ s/__PORT__/$product->{port}/g;
dfc02f3c
TL
2016
2017 my $autoreboot_msg = $config_options->{autoreboot}
2018 ? "Automatic reboot scheduled in $autoreboot_seconds seconds."
2019 : '';
2020 $data =~ s/__AUTOREBOOT_MSG__/$autoreboot_msg/;
8a50920c 2021 }
cfb92364 2022 $data =~ s/__FULL_PRODUCT_NAME__/$product->{fullname}/g;
8a50920c 2023
ed0e6aea 2024 $htmlview->load_html($data, $url);
550958aa
DM
2025
2026 $last_display_change = time();
7becc472
DM
2027}
2028
201a5120
OB
2029sub prev_function {
2030
2031 my ($text, $fctn) = @_;
2032
2033 $fctn = $step_number if !$fctn;
2034 $text = "_Previous" if !$text;
451b1da5 2035 $prev_btn->set_label ($text);
201a5120
OB
2036
2037 $step_number--;
2038 $steps[$step_number]->{function}();
2039
71590b6a 2040 $prev_btn->grab_focus();
201a5120
OB
2041}
2042
89a12446
DM
2043sub set_next {
2044 my ($text, $fctn) = @_;
2045
2046 $next_fctn = $fctn;
201a5120
OB
2047 my $step = $steps[$step_number];
2048 $text //= $steps[$step_number]->{next_button} // '_Next';
71590b6a 2049 $next->set_label($text);
968fa90b 2050
71590b6a 2051 $next->grab_focus();
89a12446 2052}
89a12446
DM
2053
2054sub create_main_window {
2055
71590b6a
OB
2056 $window = Gtk3::Window->new();
2057 $window->set_default_size(1024, 768);
84761f93 2058 $window->set_has_resize_grip(0);
d6b47e68 2059 $window->fullscreen() if !$opt_testmode;
71590b6a 2060 $window->set_decorated(0) if !$opt_testmode;
89a12446 2061
71590b6a 2062 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 2063
782b4acd
DM
2064 my $logofn = "$setup->{product}-banner.png";
2065 my $image = Gtk3::Image->new_from_file("${proxmox_libdir}/$logofn");
71590b6a 2066 $vbox->pack_start($image, 0, 0, 0);
89a12446 2067
71590b6a
OB
2068 my $hbox = Gtk3::HBox->new(0, 0);
2069 $vbox->pack_start($hbox, 1, 1, 0);
89a12446 2070
7becc472
DM
2071 # my $f1 = Gtk3::Frame->new ('test');
2072 # $f1->set_shadow_type ('none');
2073 # $hbox->pack_start ($f1, 1, 1, 0);
89a12446 2074
71590b6a
OB
2075 my $sep1 = Gtk3::HSeparator->new();
2076 $vbox->pack_start($sep1, 0, 0, 0);
89a12446 2077
71590b6a
OB
2078 $cmdbox = Gtk3::HBox->new();
2079 $vbox->pack_start($cmdbox, 0, 0, 10);
89a12446 2080
71590b6a
OB
2081 $next = Gtk3::Button->new('_Next');
2082 $next->signal_connect(clicked => sub { $last_display_change = 0; &$next_fctn (); });
2083 $cmdbox->pack_end($next, 0, 0, 10);
201a5120
OB
2084
2085
71590b6a
OB
2086 $prev_btn = Gtk3::Button->new('_Previous');
2087 $prev_btn->signal_connect(clicked => sub { $last_display_change = 0; &prev_function (); });
2088 $cmdbox->pack_end($prev_btn, 0, 0, 10);
201a5120
OB
2089
2090
71590b6a
OB
2091 my $abort = Gtk3::Button->new('_Abort');
2092 $abort->set_can_focus(0);
2093 $cmdbox->pack_start($abort, 0, 0, 10);
2094 $abort->signal_connect(clicked => sub { exit (-1); });
89a12446 2095
71590b6a
OB
2096 my $vbox2 = Gtk3::VBox->new(0, 0);
2097 $hbox->add($vbox2);
89a12446 2098
ed0e6aea 2099 $htmlview = Gtk3::WebKit2::WebView->new();
7becc472
DM
2100 my $scrolls = Gtk3::ScrolledWindow->new();
2101 $scrolls->add($htmlview);
1464c7c9 2102
71590b6a
OB
2103 my $hbox2 = Gtk3::HBox->new(0, 0);
2104 $hbox2->pack_start($scrolls, 1, 1, 0);
89a12446 2105
71590b6a 2106 $vbox2->pack_start($hbox2, 1, 1, 0);
89a12446 2107
71590b6a
OB
2108 my $vbox3 = Gtk3::VBox->new(0, 0);
2109 $vbox2->pack_start($vbox3, 0, 0, 0);
89a12446 2110
7becc472 2111 my $sep2 = Gtk3::HSeparator->new;
71590b6a 2112 $vbox3->pack_start($sep2, 0, 0, 0);
89a12446 2113
71590b6a
OB
2114 $inbox = Gtk3::HBox->new(0, 0);
2115 $vbox3->pack_start($inbox, 0, 0, 0);
89a12446 2116
71590b6a 2117 $window->add($vbox);
89a12446
DM
2118
2119 $window->show_all;
71590b6a 2120 $window->realize();
89a12446
DM
2121}
2122
1464c7c9 2123sub cleanup_view {
d2120e51
DM
2124 $inbox->foreach(sub {
2125 my $child = shift;
1464c7c9 2126 $inbox->remove ($child);
d2120e51 2127 });
89a12446
DM
2128}
2129
aed81ff0
DM
2130# fixme: newer GTK3 has special properties to handle numbers with Entry
2131# only allow floating point numbers with Gtk3::Entry
e73c5fcf 2132
aed81ff0
DM
2133sub check_float {
2134 my ($entry, $event) = @_;
2135
e73c5fcf
FG
2136 return check_number($entry, $event, 1);
2137}
2138
2139sub check_int {
2140 my ($entry, $event) = @_;
2141
2142 return check_number($entry, $event, 0);
2143}
2144
2145sub check_number {
2146 my ($entry, $event, $float) = @_;
aed81ff0
DM
2147
2148 my $val = $event->get_keyval;
2149
e73c5fcf 2150 if (($float && $val == ord '.') ||
aed81ff0
DM
2151 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
2152 $val == Gtk3::Gdk::KEY_Shift_L ||
2153 $val == Gtk3::Gdk::KEY_Tab ||
2154 $val == Gtk3::Gdk::KEY_Left ||
2155 $val == Gtk3::Gdk::KEY_Right ||
2156 $val == Gtk3::Gdk::KEY_BackSpace ||
2157 $val == Gtk3::Gdk::KEY_Delete ||
2158 ($val >= ord '0' && $val <= ord '9') ||
2159 ($val >= Gtk3::Gdk::KEY_KP_0 &&
2160 $val <= Gtk3::Gdk::KEY_KP_9)) {
2161 return undef;
2162 }
2163
2164 return 1;
2165}
2166
d2120e51 2167sub create_text_input {
89a12446
DM
2168 my ($default, $text) = @_;
2169
cc120d79 2170 my $hbox = Gtk3::Box->new('horizontal', 0);
89a12446 2171
71590b6a
OB
2172 my $label = Gtk3::Label->new($text);
2173 $label->set_size_request(150, -1);
2174 $label->set_alignment(1, 0.5);
2175 $hbox->pack_start($label, 0, 0, 10);
2176 my $e1 = Gtk3::Entry->new();
cc120d79 2177 $e1->set_width_chars(35);
71590b6a
OB
2178 $hbox->pack_start($e1, 0, 0, 0);
2179 $e1->set_text($default);
89a12446
DM
2180
2181 return ($hbox, $e1);
2182}
cc120d79
TL
2183sub create_cidr_inputs {
2184 my ($default_ip, $default_mask) = @_;
2185
2186 my $hbox = Gtk3::Box->new('horizontal', 0);
2187
2188 my $label = Gtk3::Label->new('IP Address (CIDR)');
2189 $label->set_size_request(150, -1);
2190 $label->set_alignment(1, 0.5);
2191 $hbox->pack_start($label, 0, 0, 10);
2192
2193 my $ip_el = Gtk3::Entry->new();
2194 $ip_el->set_width_chars(28);
2195 $hbox->pack_start($ip_el, 0, 0, 0);
2196 $ip_el->set_text($default_ip);
2197
2198 $label = Gtk3::Label->new('/');
2199 $label->set_size_request(10, -1);
2200 $label->set_alignment(0.5, 0.5);
2201 $hbox->pack_start($label, 0, 0, 2);
2202
2203 my $cidr_el = Gtk3::Entry->new();
2204 $cidr_el->set_width_chars(3);
2205 $hbox->pack_start($cidr_el, 0, 0, 0);
2206 $cidr_el->set_text($default_mask);
2207
2208 return ($hbox, $ip_el, $cidr_el);
2209}
89a12446 2210
89a12446
DM
2211sub get_ip_config {
2212
fe44bd92
FG
2213 my $ifaces = {};
2214 my $default;
89a12446 2215
fe44bd92
FG
2216 my $links = `ip -o l`;
2217 foreach my $l (split /\n/,$links) {
2218 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
2219 next if !$name || $name eq 'lo';
89a12446 2220
fe44bd92
FG
2221 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
2222 $driver =~ s!^.*/!!;
2223
2224 $ifaces->{"$index"} = {
2225 name => $name,
2226 driver => $driver,
2227 flags => $flags,
2228 state => $state,
2229 mac => $mac,
2230 };
2231
2232 my $addresses = `ip -o a s $name`;
2233 foreach my $a (split /\n/,$addresses) {
2234 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
2235 next if !$ip;
32b6fbcf 2236 next if $a =~ /scope\s+link/; # ignore link local
fe44bd92
FG
2237
2238 my $mask = $prefix;
2239
2240 if ($family eq 'inet') {
2241 next if !$ip =~ /$IPV4RE/;
2242 next if $prefix < 8 || $prefix > 32;
2243 $mask = @$ipv4_reverse_mask[$prefix];
2244 } else {
2245 next if !$ip =~ /$IPV6RE/;
2246 }
2247
2248 $default = $index if !$default;
2249
2250 $ifaces->{"$index"}->{"$family"} = {
cc120d79 2251 prefix => $prefix,
fe44bd92
FG
2252 mask => $mask,
2253 addr => $ip,
2254 };
2255 }
2256 }
2257
2258
2259 my $route = `ip route`;
2260 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
89a12446
DM
2261
2262 my $resolvconf = `cat /etc/resolv.conf`;
2263 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
713790a4 2264 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
89a12446
DM
2265
2266 return {
fe44bd92
FG
2267 default => $default,
2268 ifaces => $ifaces,
89a12446
DM
2269 gateway => $gateway,
2270 dnsserver => $dnsserver,
713790a4 2271 domain => $domain,
89a12446
DM
2272 }
2273}
2274
2275sub display_message {
2276 my ($msg) = @_;
2277
71590b6a
OB
2278 my $dialog = Gtk3::MessageDialog->new($window, 'modal',
2279 'info', 'ok', $msg);
89a12446
DM
2280 $dialog->run();
2281 $dialog->destroy();
2282}
2283
2284sub display_error {
2285 my ($msg) = @_;
2286
71590b6a
OB
2287 my $dialog = Gtk3::MessageDialog->new($window, 'modal',
2288 'error', 'ok', $msg);
89a12446
DM
2289 $dialog->run();
2290 $dialog->destroy();
2291}
2292
fe44bd92
FG
2293my $ipconf_first_view = 1;
2294
89a12446
DM
2295sub create_ipconf_view {
2296
201a5120
OB
2297 cleanup_view();
2298 display_html();
89a12446 2299
cc120d79
TL
2300 my $vcontainer = Gtk3::Box->new('vertical', 0);
2301 $inbox->pack_start($vcontainer, 1, 0, 0);
2302 my $hcontainer = Gtk3::Box->new('horizontal', 0);
2303 $vcontainer->pack_start($hcontainer, 0, 0, 10);
2304 my $vbox = Gtk3::Box->new('vertical', 0);
2305 $hcontainer->add($vbox);
89a12446 2306
ebc4f76f 2307 my $ipaddr_text = $config->{ipaddress} // "192.168.100.2";
cc120d79
TL
2308 my $netmask_text = $config->{netmask} // "24";
2309 my $cidr_box;
2310 ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) =
2311 create_cidr_inputs($ipaddr_text, $netmask_text);
fe44bd92
FG
2312
2313 my $device_cb = Gtk3::ComboBoxText->new();
2314 $device_cb->set_active(0);
2315 $device_cb->set_visible(1);
2316
2317 my $get_device_desc = sub {
2318 my $iface = shift;
2319 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
2320 };
2321
2322 my $device_active_map = {};
ebc4f76f 2323 my $device_active_reverse_map = {};
5b6ba737
FG
2324
2325 my $device_change_handler = sub {
2326 my $current = shift;
d6524c52
TL
2327
2328 my $new = $device_active_map->{$current->get_active()};
cd2d2a27 2329 return if defined($ipconf->{selected}) && $new eq $ipconf->{selected};
d6524c52
TL
2330
2331 $ipconf->{selected} = $new;
5b6ba737 2332 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
ebc4f76f 2333 $config->{mngmt_nic} = $iface->{name};
5b6ba737
FG
2334 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2335 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
cc120d79
TL
2336 $ipconf_entry_mask->set_text($iface->{inet}->{prefix} || $iface->{inet6}->{prefix})
2337 if $iface->{inet}->{prefix} || $iface->{inet6}->{prefix};
5b6ba737
FG
2338 };
2339
fe44bd92
FG
2340 my $i = 0;
2341 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2342 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
ebc4f76f
TL
2343 $device_active_map->{$i} = $index;
2344 $device_active_reverse_map->{$ipconf->{ifaces}->{$index}->{name}} = $i;
fe44bd92
FG
2345 if ($ipconf_first_view && $index == $ipconf->{default}) {
2346 $device_cb->set_active($i);
5b6ba737 2347 &$device_change_handler($device_cb);
fe44bd92
FG
2348 $ipconf_first_view = 0;
2349 }
71590b6a 2350 $device_cb->signal_connect('changed' => $device_change_handler);
fe44bd92
FG
2351 $i++;
2352 }
2353
ebc4f76f
TL
2354 if (my $nic = $config->{mngmt_nic}) {
2355 $device_cb->set_active($device_active_reverse_map->{$nic} // 0);
2356 } else {
2357 $device_cb->set_active(0);
2358 }
5b6ba737 2359
71590b6a
OB
2360 my $devicebox = Gtk3::HBox->new(0, 0);
2361 my $label = Gtk3::Label->new("Management Interface:");
2362 $label->set_size_request(150, -1);
2363 $label->set_alignment(1, 0.5);
2364 $devicebox->pack_start($label, 0, 0, 10);
2365 $devicebox->pack_start($device_cb, 0, 0, 0);
fe44bd92 2366
cc120d79 2367 $vbox->pack_start($devicebox, 0, 0, 2);
968fa90b 2368
ebc4f76f 2369 my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
1464c7c9 2370
cc120d79
TL
2371 my ($hostbox, $hostentry) = create_text_input($hn, 'Hostname (FQDN):');
2372 $vbox->pack_start($hostbox, 0, 0, 2);
89a12446 2373
cc120d79 2374 $vbox->pack_start($cidr_box, 0, 0, 2);
89a12446 2375
ebc4f76f 2376 $gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
89a12446
DM
2377
2378 my $gwbox;
d2120e51 2379 ($gwbox, $ipconf_entry_gw) =
71590b6a 2380 create_text_input($gateway, 'Gateway:');
89a12446 2381
cc120d79 2382 $vbox->pack_start($gwbox, 0, 0, 2);
89a12446 2383
ebc4f76f 2384 $dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
89a12446
DM
2385
2386 my $dnsbox;
d2120e51 2387 ($dnsbox, $ipconf_entry_dns) =
71590b6a 2388 create_text_input($dnsserver, 'DNS Server:');
89a12446 2389
cc120d79 2390 $vbox->pack_start($dnsbox, 0, 0, 0);
89a12446
DM
2391
2392 $inbox->show_all;
71590b6a 2393 set_next(undef, sub {
d2120e51
DM
2394
2395 # verify hostname
1464c7c9 2396
89a12446 2397 my $text = $hostentry->get_text();
968fa90b 2398
89a12446
DM
2399 $text =~ s/^\s+//;
2400 $text =~ s/\s+$//;
2401
ebc4f76f
TL
2402 $config->{fqdn} = $text;
2403
ac3757a9 2404 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2405
24973868
WB
2406 # Debian does not support purely numeric hostnames
2407 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2408 display_message("Purely numeric hostnames are not allowed.");
2409 $hostentry->grab_focus();
2410 return;
2411 }
2412
a39bc1f2 2413 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2414 $text =~ m/^([^\.]+)\.(\S+)$/) {
2415 $hostname = $1;
2416 $domain = $2;
d2120e51 2417 } else {
71590b6a 2418 display_message("Hostname does not look like a fully qualified domain name.");
d2120e51 2419 $hostentry->grab_focus();
89a12446
DM
2420 return;
2421 }
d2120e51
DM
2422
2423 # verify ip address
2424
2425 $text = $ipconf_entry_addr->get_text();
2426 $text =~ s/^\s+//;
2427 $text =~ s/\s+$//;
2428 if ($text =~ m!^($IPV4RE)$!) {
2429 $ipaddress = $text;
b6200603
DM
2430 $ipversion = 4;
2431 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2432 $ipaddress = $text;
b6200603 2433 $ipversion = 6;
d2120e51 2434 } else {
71590b6a 2435 display_message("IP address is not valid.");
d2120e51
DM
2436 $ipconf_entry_addr->grab_focus();
2437 return;
2438 }
ebc4f76f 2439 $config->{ipaddress} = $ipaddress;
d2120e51
DM
2440
2441 $text = $ipconf_entry_mask->get_text();
2442 $text =~ s/^\s+//;
2443 $text =~ s/\s+$//;
cc120d79 2444 if ($ipversion == 6 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 126) {
b6200603 2445 $netmask = $text;
cc120d79 2446 } elsif ($ipversion == 4 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 32) {
d2120e51 2447 $netmask = $text;
cc120d79
TL
2448 } elsif ($ipversion == 4 && defined($ipv4_mask_hash->{$text})) {
2449 # costs nothing to handle 255.x.y.z style masks, so continue to allow it
2450 $netmask = $ipv4_mask_hash->{$text};
d2120e51 2451 } else {
71590b6a 2452 display_message("Netmask is not valid.");
d2120e51
DM
2453 $ipconf_entry_mask->grab_focus();
2454 return;
2455 }
cc120d79 2456 $cidr = "$ipaddress/$netmask";
ebc4f76f 2457 $config->{netmask} = $netmask;
d2120e51
DM
2458
2459 $text = $ipconf_entry_gw->get_text();
2460 $text =~ s/^\s+//;
2461 $text =~ s/\s+$//;
b6200603
DM
2462 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2463 $gateway = $text;
2464 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2465 $gateway = $text;
2466 } else {
71590b6a 2467 display_message("Gateway is not valid.");
d2120e51
DM
2468 $ipconf_entry_gw->grab_focus();
2469 return;
2470 }
ebc4f76f 2471 $config->{gateway} = $gateway;
1464c7c9 2472
d2120e51
DM
2473 $text = $ipconf_entry_dns->get_text();
2474 $text =~ s/^\s+//;
2475 $text =~ s/\s+$//;
b6200603
DM
2476 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2477 $dnsserver = $text;
2478 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2479 $dnsserver = $text;
2480 } else {
71590b6a 2481 display_message("DNS server is not valid.");
d2120e51
DM
2482 $ipconf_entry_dns->grab_focus();
2483 return;
2484 }
ebc4f76f 2485 $config->{dnsserver} = $dnsserver;
1464c7c9 2486
d2120e51 2487 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2488
201a5120 2489 $step_number++;
2e33c3f0 2490 create_ack_view();
89a12446
DM
2491 });
2492
2493 $hostentry->grab_focus();
2494}
2495
2e33c3f0
OB
2496sub create_ack_view {
2497
2498 cleanup_view();
2499
dfc02f3c
TL
2500 my $vbox = Gtk3::VBox->new(0, 0);
2501 $inbox->pack_start($vbox, 1, 0, 0);
2502 #my $hbox = Gtk3::HBox->new(0, 0);
2503 #$vbox->pack_start($hbox, 0, 0, 10);
2504
2505 my $reboot_checkbox = Gtk3::CheckButton->new('Automatically reboot after successful installation');
2506 $reboot_checkbox->set_active(1);
2507 $reboot_checkbox->signal_connect ("toggled" => sub {
2508 my $cb = shift;
2509 $config_options->{autoreboot} = $cb->get_active();
2510 });
2511 $vbox->pack_start($reboot_checkbox, 0, 0, 2);
2512
2e33c3f0
OB
2513 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
2514 my $ack_html = "${proxmox_libdir}/html/$steps[$step_number]->{html}";
2515 my $html_data = file_get_contents($ack_template);
2516
2517 my %config_values = (
a7d40341 2518 __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
0470018e 2519 __target_fs__ => $config_options->{filesys},
0ddd2227 2520 __country__ => $cmap->{country}->{$country}->{name},
2e33c3f0
OB
2521 __timezone__ => $timezone,
2522 __keymap__ => $keymap,
2523 __mailto__ => $mailto,
2524 __interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
2525 __hostname__ => $hostname,
2526 __ip__ => $ipaddress,
b1838e1e 2527 __cidr__ => $cidr,
2e33c3f0
OB
2528 __netmask__ => $netmask,
2529 __gateway__ => $gateway,
2530 __dnsserver__ => $dnsserver,
2531 );
2532
2533 while ( my ($k, $v) = each %config_values) {
2534 $html_data =~ s/$k/$v/g;
2535 }
2536
2537 write_config($html_data, $ack_html);
2538
2539 display_html();
2540
dfc02f3c
TL
2541 $inbox->show_all;
2542
2e33c3f0
OB
2543 set_next(undef, sub {
2544 $step_number++;
2545 create_extract_view();
2546 });
2547}
2548
89a12446
DM
2549sub get_device_desc {
2550 my ($devname, $size, $model) = @_;
2551
d2120e51 2552 if ($size && ($size > 0)) {
1bd457bb 2553 $size = int($size/2048); # size in MB, from 512B "sectors"
89a12446 2554
d2120e51 2555 my $text = "$devname (";
89a12446
DM
2556 if ($size >= 1024) {
2557 $size = int($size/1024); # size in GB
d2120e51 2558 $text .= "${size}GB";
89a12446 2559 } else {
d2120e51 2560 $text .= "${size}MB";
89a12446
DM
2561 }
2562
d2120e51
DM
2563 $text .= ", $model" if $model;
2564 $text .= ")";
2565
89a12446
DM
2566 } else {
2567 return $devname;
2568 }
2569}
2570
d92ada4e
SI
2571my $last_layout;
2572my $country_layout;
89a12446
DM
2573sub update_layout {
2574 my ($cb, $kmap) = @_;
2575
2576 my $ind;
2577 my $def;
2578 my $i = 0;
2579 my $kmaphash = $cmap->{kmaphash};
2580 foreach my $layout (sort keys %$kmaphash) {
2581 $def = $i if $kmaphash->{$layout} eq 'en-us';
2582 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2583 $i++;
2584 }
2585
d92ada4e
SI
2586 my $val = $ind || $def || 0;
2587
2588 if (!defined($kmap)) {
2589 $last_layout //= $val;
2590 } elsif (!defined($country_layout) || $country_layout != $val) {
2591 $last_layout = $country_layout = $val;
2592 }
2593 $cb->set_active($last_layout);
89a12446
DM
2594}
2595
2596my $lastzonecb;
2597sub update_zonelist {
2598 my ($box, $cc) = @_;
2599
2600 my $cczones = $cmap->{cczones};
2601 my $zones = $cmap->{zones};
2602
2603 my $sel;
2604 if ($lastzonecb) {
2605 $sel = $lastzonecb->get_active_text();
2606 $box->remove ($lastzonecb);
2607 } else {
2608 $sel = $timezone; # used once to select default
2609 }
2610
bcbfab6b 2611 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
71590b6a 2612 $cb->set_size_request(200, -1);
89a12446 2613
71590b6a 2614 $cb->signal_connect('changed' => sub {
89a12446
DM
2615 $timezone = $cb->get_active_text();
2616 });
2617
2618 my @za;
2619 if ($cc && defined ($cczones->{$cc})) {
2620 @za = keys %{$cczones->{$cc}};
2621 } else {
2622 @za = keys %$zones;
2623 }
2624 my $ind;
2625 my $i = 0;
2626 foreach my $zone (sort @za) {
2627 $ind = $i if $sel && $zone eq $sel;
71590b6a 2628 $cb->append_text($zone);
89a12446
DM
2629 $i++;
2630 }
2631
71590b6a 2632 $cb->set_active($ind || 0);
89a12446
DM
2633
2634 $cb->show;
71590b6a 2635 $box->pack_start($cb, 0, 0, 0);
89a12446
DM
2636}
2637
2638sub create_password_view {
2639
71590b6a 2640 cleanup_view();
89a12446 2641
71590b6a
OB
2642 my $vbox2 = Gtk3::VBox->new(0, 0);
2643 $inbox->pack_start($vbox2, 1, 0, 0);
2644 my $vbox = Gtk3::VBox->new(0, 0);
2645 $vbox2->pack_start($vbox, 0, 0, 10);
2646
2647 my $hbox1 = Gtk3::HBox->new(0, 0);
2648 my $label = Gtk3::Label->new("Password");
2649 $label->set_size_request(150, -1);
2650 $label->set_alignment(1, 0.5);
2651 $hbox1->pack_start($label, 0, 0, 10);
2652 my $pwe1 = Gtk3::Entry->new();
2653 $pwe1->set_visibility(0);
201a5120 2654 $pwe1->set_text($password) if $password;
71590b6a
OB
2655 $pwe1->set_size_request(200, -1);
2656 $hbox1->pack_start($pwe1, 0, 0, 0);
2657
2658 my $hbox2 = Gtk3::HBox->new(0, 0);
2659 $label = Gtk3::Label->new("Confirm");
2660 $label->set_size_request(150, -1);
2661 $label->set_alignment(1, 0.5);
2662 $hbox2->pack_start($label, 0, 0, 10);
2663 my $pwe2 = Gtk3::Entry->new();
2664 $pwe2->set_visibility(0);
201a5120 2665 $pwe2->set_text($password) if $password;
71590b6a
OB
2666 $pwe2->set_size_request(200, -1);
2667 $hbox2->pack_start($pwe2, 0, 0, 0);
2668
2669 my $hbox3 = Gtk3::HBox->new(0, 0);
b11c55ff 2670 $label = Gtk3::Label->new("Email");
71590b6a
OB
2671 $label->set_size_request(150, -1);
2672 $label->set_alignment(1, 0.5);
2673 $hbox3->pack_start($label, 0, 0, 10);
2674 my $eme = Gtk3::Entry->new();
2675 $eme->set_size_request(200, -1);
201a5120 2676 $eme->set_text($mailto);
71590b6a 2677 $hbox3->pack_start($eme, 0, 0, 0);
89a12446
DM
2678
2679
71590b6a
OB
2680 $vbox->pack_start($hbox1, 0, 0, 5);
2681 $vbox->pack_start($hbox2, 0, 0, 5);
2682 $vbox->pack_start($hbox3, 0, 0, 15);
89a12446
DM
2683
2684 $inbox->show_all;
2685
201a5120 2686 display_html();
89a12446
DM
2687
2688 set_next (undef, sub {
2689
2690 my $t1 = $pwe1->get_text;
2691 my $t2 = $pwe2->get_text;
2692
2693 if (length ($t1) < 5) {
71590b6a 2694 display_message("Password is too short.");
89a12446
DM
2695 $pwe1->grab_focus();
2696 return;
2697 }
2698
2699 if ($t1 ne $t2) {
71590b6a 2700 display_message("Password does not match.");
89a12446
DM
2701 $pwe1->grab_focus();
2702 return;
2703 }
2704
2705 my $t3 = $eme->get_text;
c82fffd8 2706 if ($t3 !~ m/^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
b11c55ff 2707 display_message("Email does not look like a valid address" .
89a12446
DM
2708 " (user\@domain.tld)");
2709 $eme->grab_focus();
2710 return;
a39bc1f2 2711 }
89a12446 2712
a39bc1f2 2713 if ($t3 eq 'mail@example.invalid') {
b11c55ff 2714 display_message("Please enter a valid Email address");
a39bc1f2
FG
2715 $eme->grab_focus();
2716 return;
89a12446
DM
2717 }
2718
2719 $password = $t1;
2720 $mailto = $t3;
2721
201a5120 2722 $step_number++;
89a12446
DM
2723 create_ipconf_view();
2724 });
2725
2726 $pwe1->grab_focus();
2727
2728}
2729
d92ada4e 2730my $installer_kmap;
89a12446
DM
2731sub create_country_view {
2732
71590b6a 2733 cleanup_view();
89a12446
DM
2734
2735 my $countryhash = $cmap->{countryhash};
2736 my $ctr = $cmap->{country};
2737
71590b6a
OB
2738 my $vbox2 = Gtk3::VBox->new(0, 0);
2739 $inbox->pack_start($vbox2, 1, 0, 0);
2740 my $vbox = Gtk3::VBox->new(0, 0);
2741 $vbox2->pack_start($vbox, 0, 0, 10);
89a12446 2742
71590b6a
OB
2743 my $w = Gtk3::Entry->new();
2744 $w->set_size_request(200, -1);
89a12446 2745
71590b6a
OB
2746 my $c = Gtk3::EntryCompletion->new();
2747 $c->set_text_column(0);
89a12446 2748 $c->set_minimum_key_length(0);
71590b6a
OB
2749 $c->set_popup_set_width(1);
2750 $c->set_inline_completion(1);
2751
2752 my $hbox2 = Gtk3::HBox->new(0, 0);
2753 my $label = Gtk3::Label->new("Time zone");
2754 $label->set_size_request(150, -1);
2755 $label->set_alignment(1, 0.5);
2756 $hbox2->pack_start($label, 0, 0, 10);
89a12446
DM
2757 update_zonelist ($hbox2);
2758
71590b6a
OB
2759 my $hbox3 = Gtk3::HBox->new(0, 0);
2760 $label = Gtk3::Label->new("Keyboard Layout");
2761 $label->set_size_request(150, -1);
2762 $label->set_alignment(1, 0.5);
2763 $hbox3->pack_start($label, 0, 0, 10);
89a12446 2764
bcbfab6b 2765 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2766 $kmapcb->set_size_request (200, -1);
2767 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2768 $kmapcb->append_text ($layout);
2769 }
2770
71590b6a 2771 update_layout($kmapcb);
89a12446
DM
2772 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2773
2774 $kmapcb->signal_connect ('changed' => sub {
2775 my $sel = $kmapcb->get_active_text();
d92ada4e 2776 $last_layout = $kmapcb->get_active();
89a12446
DM
2777 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2778 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2779 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
89a12446 2780 $keymap = $kmap;
07ec6825 2781
d92ada4e
SI
2782 return if (defined($installer_kmap) && $installer_kmap eq $kmap);
2783
2784 $installer_kmap = $keymap;
2785
07ec6825
SI
2786 if (! $opt_testmode) {
2787 syscmd ("setxkbmap $xkmap $xvar");
2663e171
SI
2788
2789 my $kbd_config = qq{
2790 XKBLAYOUT="$xkmap"
2791 XKBVARIANT="$xvar"
2792 BACKSPACE="guess"
2793 };
2794 $kbd_config =~ s/^\s+//gm;
2795
2796 run_in_background( sub {
2797 write_config($kbd_config, '/etc/default/keyboard');
2798 system("setupcon");
2799 });
07ec6825 2800 }
89a12446
DM
2801 }
2802 });
2803
2804 $w->signal_connect ('changed' => sub {
2805 my ($entry, $event) = @_;
2806 my $text = $entry->get_text;
2807
2808 if (my $cc = $countryhash->{lc($text)}) {
71590b6a 2809 update_zonelist($hbox2, $cc);
89a12446 2810 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
71590b6a 2811 update_layout($kmapcb, $kmap);
89a12446
DM
2812 }
2813 });
2814
2815 $w->signal_connect (key_press_event => sub {
2816 my ($entry, $event) = @_;
2817 my $text = $entry->get_text;
2818
7becc472
DM
2819 my $val = $event->get_keyval;
2820
2821 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2822 my $cc = $countryhash->{lc($text)};
1464c7c9 2823
89a12446
DM
2824 my $found = 0;
2825 my $compl;
7becc472 2826
4443aa27
DM
2827 if ($cc) {
2828 $found = 1;
2829 $compl = $ctr->{$cc}->{name};
2830 } else {
2831 foreach my $cc (keys %$ctr) {
2832 my $ct = $ctr->{$cc}->{name};
2833 if ($ct =~ m/^\Q$text\E.*$/i) {
2834 $found++;
2835 $compl = $ct;
2836 }
2837 last if $found > 1;
89a12446 2838 }
89a12446 2839 }
4443aa27 2840
89a12446 2841 if ($found == 1) {
7becc472 2842 $entry->set_text($compl);
3df718ea 2843 $c->complete();
89a12446
DM
2844 return undef;
2845 } else {
7becc472
DM
2846 #Gtk3::Gdk::beep();
2847 print chr(7); # beep ?
89a12446
DM
2848 }
2849
3df718ea
DM
2850 $c->complete();
2851
7becc472
DM
2852 my $buf = $w->get_buffer();
2853 $buf->insert_text(-1, '', -1); # popup selection
2854
89a12446
DM
2855 return 1;
2856 }
2857
2858 return undef;
2859 });
1464c7c9 2860
7becc472 2861 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2862 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2863 my $iter = $ls->append();
2864 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2865 }
2866 $c->set_model ($ls);
2867
968fa90b 2868 $w->set_completion ($c);
89a12446 2869
71590b6a 2870 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 2871
71590b6a
OB
2872 $label = Gtk3::Label->new("Country");
2873 $label->set_alignment(1, 0.5);
2874 $label->set_size_request(150, -1);
2875 $hbox->pack_start($label, 0, 0, 10);
2876 $hbox->pack_start($w, 0, 0, 0);
89a12446 2877
71590b6a
OB
2878 $vbox->pack_start($hbox, 0, 0, 5);
2879 $vbox->pack_start($hbox2, 0, 0, 5);
2880 $vbox->pack_start($hbox3, 0, 0, 5);
89a12446 2881
9d1f1ee3 2882 if ($country && $ctr->{$country}) {
89a12446
DM
2883 $w->set_text ($ctr->{$country}->{name});
2884 }
2885
2886 $inbox->show_all;
2887
201a5120 2888 display_html();
89a12446
DM
2889 set_next (undef, sub {
2890
2891 my $text = $w->get_text;
2892
2893 if (my $cc = $countryhash->{lc($text)}) {
2894 $country = $cc;
201a5120 2895 $step_number++;
89a12446
DM
2896 create_password_view();
2897 return;
2898 } else {
71590b6a 2899 display_message("Please select a country first.");
89a12446
DM
2900 $w->grab_focus();
2901 }
2902 });
2903
2904 $w->grab_focus();
2905}
2906
c6ed3b24
DM
2907my $target_hd_combo;
2908my $target_hd_label;
2909
bd3a2e26 2910my $hdoption_first_setup = 1;
c6ed3b24 2911
c7779156
FG
2912my $create_basic_grid = sub {
2913 my $grid = Gtk3::Grid->new();
2914 $grid->set_visible(1);
2915 $grid->set_column_spacing(10);
2916 $grid->set_row_spacing(10);
2917 $grid->set_hexpand(1);
2918
6d8b8564
TL
2919 $grid->set_margin_start(10);
2920 $grid->set_margin_end(20);
c7779156
FG
2921 $grid->set_margin_top(5);
2922 $grid->set_margin_bottom(5);
2923
2924 return $grid;
2925};
2926
2927my $create_label_widget_grid = sub {
2928 my ($labeled_widgets) = @_;
2929
2930 my $grid = &$create_basic_grid();
2931 my $row = 0;
2932
2933 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2934 my $widget = @$labeled_widgets[$i+1];
2935 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2936 $label->set_visible(1);
2937 $label->set_alignment (1, 0.5);
2938 $grid->attach($label, 0, $row, 1, 1);
2939 $widget->set_visible(1);
2940 $grid->attach($widget, 1, $row, 1, 1);
2941 $row++;
2942 }
2943
2944 return $grid;
2945};
2946
2947my $create_raid_disk_grid = sub {
04fa0758 2948 my $hd_count = scalar(@$hds);
c7779156 2949 my $disk_labeled_widgets = [];
04fa0758 2950 for (my $i = 0; $i < $hd_count; $i++) {
c7779156
FG
2951 my $disk_selector = Gtk3::ComboBoxText->new();
2952 $disk_selector->append_text("-- do not use --");
2953 $disk_selector->set_active(0);
2954 $disk_selector->set_visible(1);
2955 foreach my $hd (@$hds) {
17fd908e 2956 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c7779156
FG
2957 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2958 $disk_selector->{pve_disk_id} = $i;
2959 $disk_selector->signal_connect (changed => sub {
2960 my $w = shift;
2961 my $diskid = $w->{pve_disk_id};
2962 my $a = $w->get_active - 1;
2963 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2964 });
2965 }
2966
bd3a2e26 2967 if ($hdoption_first_setup) {
c7779156
FG
2968 $disk_selector->set_active ($i+1) if $hds->[$i];
2969 } else {
2970 my $hdind = 0;
2971 if (my $cur_hd = $config_options->{"disksel$i"}) {
2972 foreach my $hd (@$hds) {
2973 if (@$hd[1] eq @$cur_hd[1]) {
2974 $disk_selector->set_active($hdind+1);
2975 last;
2976 }
2977 $hdind++;
2978 }
2979 }
2980 }
2981
2982 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2983 }
2984
3235f39b
TL
2985 my $clear_all_button = Gtk3::Button->new('_Deselect All');
2986 if ($hd_count > 3) {
2987 $clear_all_button->signal_connect('clicked', sub {
2988 my $is_widget = 0;
2989 for my $disk_selector (@$disk_labeled_widgets) {
2990 $disk_selector->set_active(0) if $is_widget;
2991 $is_widget ^= 1;
2992 }
2993 });
2994 $clear_all_button->set_visible(1);
2995 }
2996
c7779156
FG
2997 my $scrolled_window = Gtk3::ScrolledWindow->new();
2998 $scrolled_window->set_hexpand(1);
04fa0758 2999 $scrolled_window->set_propagate_natural_height(1) if $hd_count > 4;
3235f39b
TL
3000
3001 my $diskgrid = $create_label_widget_grid->($disk_labeled_widgets);
3002
3003 $scrolled_window->add($diskgrid);
c7779156 3004 $scrolled_window->set_policy('never', 'automatic');
3235f39b 3005 $scrolled_window->set_visible(1);
0bc39c50 3006 $scrolled_window->set_min_content_height(190);
3235f39b
TL
3007
3008 my $vbox = Gtk3::Box->new('vertical', 0);
3009 $vbox->pack_start($scrolled_window, 1, 1, 10);
3010
3011 my $hbox = Gtk3::Box->new('horizontal', 0);
3012 $hbox->pack_end($clear_all_button, 0, 0, 20);
3013 $hbox->set_visible(1);
3014 $vbox->pack_end($hbox, 0, 0, 0);
c7779156 3015
3235f39b 3016 return $vbox;
c7779156
FG
3017};
3018
d7fe65ff
TL
3019# shared between different ui parts (e.g., ZFS and "normal" single disk FS)
3020my $hdsize_size_adj;
3021my $hdsize_entry_buffer;
3022
3023my $get_hdsize_spinbtn = sub {
3024 my $hdsize = shift;
754abb44 3025
93c8fdb0
SI
3026 $hdsize_entry_buffer //= Gtk3::EntryBuffer->new(undef, 1);
3027
3028 if (defined($hdsize)) {
d7fe65ff 3029 $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
93c8fdb0
SI
3030 } else {
3031 die "called get_hdsize_spinbtn with \$hdsize_size_adj not defined but did not pass hdsize!\n"
3032 if !defined($hdsize_size_adj);
d7fe65ff
TL
3033 }
3034
3035 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
3036 $spinbutton_hdsize->set_buffer($hdsize_entry_buffer);
6039e2f1 3037 $spinbutton_hdsize->set_adjustment($hdsize_size_adj);
d7fe65ff
TL
3038 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
3039 return $spinbutton_hdsize;
3040};
3041
c7779156
FG
3042my $create_raid_advanced_grid = sub {
3043 my $labeled_widgets = [];
2cdba397 3044 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9, 13, 1);
6c99667a
FG
3045 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
3046 $spinbutton_ashift->signal_connect ("value-changed" => sub {
3047 my $w = shift;
3048 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
3049 });
3050 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 3051 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 3052 push @$labeled_widgets, "ashift";
6c99667a 3053 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
3054
3055 my $combo_compress = Gtk3::ComboBoxText->new();
3056 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
3057 # note: gzip / lze not allowed for bootfs vdevs
3058 my $comp_opts = ["on","off","lzjb","lz4"];
3059 foreach my $opt (@$comp_opts) {
3060 $combo_compress->append($opt, $opt);
3061 }
3062 $config_options->{compress} = "on" if !defined($config_options->{compress});
3063 $combo_compress->set_active_id($config_options->{compress});
3064 $combo_compress->signal_connect (changed => sub {
3065 my $w = shift;
3066 $config_options->{compress} = $w->get_active_text();
3067 });
3068 push @$labeled_widgets, "compress";
3069 push @$labeled_widgets, $combo_compress;
3070
3071 my $combo_checksum = Gtk3::ComboBoxText->new();
3072 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
3073 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
3074 foreach my $opt (@$csum_opts) {
3075 $combo_checksum->append($opt, $opt);
3076 }
3077 $config_options->{checksum} = "on" if !($config_options->{checksum});
3078 $combo_checksum->set_active_id($config_options->{checksum});
3079 $combo_checksum->signal_connect (changed => sub {
3080 my $w = shift;
3081 $config_options->{checksum} = $w->get_active_text();
3082 });
3083 push @$labeled_widgets, "checksum";
3084 push @$labeled_widgets, $combo_checksum;
3085
3086 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
3087 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
3088 $spinbutton_copies->signal_connect ("value-changed" => sub {
3089 my $w = shift;
3090 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
3091 });
3092 $config_options->{copies} = 1 if !defined($config_options->{copies});
3093 $spinbutton_copies->set_value($config_options->{copies});
3094 push @$labeled_widgets, "copies", $spinbutton_copies;
3095
d7fe65ff 3096 push @$labeled_widgets, "hdsize", $get_hdsize_spinbtn->();
2cdba397 3097 return $create_label_widget_grid->($labeled_widgets);;
c7779156
FG
3098};
3099
aed81ff0
DM
3100sub create_hdoption_view {
3101
3102 my $dialog = Gtk3::Dialog->new();
3103
3104 $dialog->set_title("Harddisk options");
3105
3106 $dialog->add_button("_OK", 1);
3107
3108 my $contarea = $dialog->get_content_area();
3109
3110 my $hbox2 = Gtk3::Box->new('horizontal', 0);
6d8b8564 3111 $contarea->pack_start($hbox2, 1, 1, 5);
aed81ff0
DM
3112
3113 my $grid = Gtk3::Grid->new();
3114 $grid->set_column_spacing(10);
3115 $grid->set_row_spacing(10);
1464c7c9 3116
6d8b8564 3117 $hbox2->pack_start($grid, 1, 0, 5);
c6ed3b24
DM
3118
3119 my $row = 0;
3120
aed81ff0 3121 # Filesystem type
71590b6a 3122 my $label0 = Gtk3::Label->new("Filesystem");
aed81ff0 3123 $label0->set_alignment (1, 0.5);
c6ed3b24 3124 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 3125
bcbfab6b 3126 my $fstypecb = Gtk3::ComboBoxText->new();
2cdba397
TL
3127 my $fstype = [
3128 'ext4',
3129 'xfs',
3130 'zfs (RAID0)',
3131 'zfs (RAID1)',
3132 'zfs (RAID10)',
3133 'zfs (RAIDZ-1)',
3134 'zfs (RAIDZ-2)',
3135 'zfs (RAIDZ-3)',
3136 ];
6f52fc3d 3137 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 3138 if $setup->{enable_btrfs};
aed81ff0 3139
c6ed3b24
DM
3140 my $tcount = 0;
3141 foreach my $tmp (@$fstype) {
3142 $fstypecb->append_text($tmp);
2cdba397 3143 $fstypecb->set_active ($tcount) if $config_options->{filesys} eq $tmp;
c6ed3b24
DM
3144 $tcount++;
3145 }
3146
3147 $grid->attach($fstypecb, 1, $row, 1, 1);
3148
3149 $hbox2->show_all();
3150
3151 $row++;
3152
c7779156
FG
3153 my $sep = Gtk3::HSeparator->new();
3154 $sep->set_visible(1);
3155 $grid->attach($sep, 0, $row, 2, 1);
3156 $row++;
aed81ff0 3157
8f38d1a1
TL
3158 my $hw_raid_note = Gtk3::Label->new(
3159 "Note: ZFS is not compatible with hardware RAID controllers, for details see the documentation."
3160 );
f0a0d90b
TL
3161 $hw_raid_note->set_line_wrap(1);
3162 $hw_raid_note->set_max_width_chars(30);
f0a0d90b
TL
3163 $hw_raid_note->set_visible(0);
3164 $grid->attach($hw_raid_note, 0, $row++, 2, 1);
3165
c7779156 3166 my $hdsize_labeled_widgets = [];
aed81ff0 3167
c7779156 3168 # size compute
c6ed3b24 3169 my $hdsize = 0;
aed81ff0
DM
3170 if ( -b $target_hd) {
3171 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
c6ed3b24 3172 } elsif ($target_hd) {
aed81ff0
DM
3173 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
3174 }
3175
d7fe65ff 3176 my $spinbutton_hdsize = $get_hdsize_spinbtn->($hdsize);
c7779156 3177 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
aed81ff0
DM
3178
3179 my $entry_swapsize = Gtk3::Entry->new();
3180 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
3181 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 3182 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 3183 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
3184
3185 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
3186 if ($setup->{product} eq 'pve') {
3187 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
3188 $entry_maxroot->signal_connect (key_press_event => \&check_float);
3189 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
3190 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
3191 }
aed81ff0
DM
3192
3193 my $entry_minfree = Gtk3::Entry->new();
034f75e4 3194 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0 3195 $entry_minfree->signal_connect (key_press_event => \&check_float);
e093944c 3196 $entry_minfree->set_text($config_options->{minfree}) if defined($config_options->{minfree});
c7779156 3197 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 3198
b6e875ca
DM
3199 my $entry_maxvz;
3200 if ($setup->{product} eq 'pve') {
3201 $entry_maxvz = Gtk3::Entry->new();
3202 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
3203 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 3204 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
3205 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
3206 }
c7779156
FG
3207
3208 my $options_stack = Gtk3::Stack->new();
3209 $options_stack->set_visible(1);
3210 $options_stack->set_hexpand(1);
3211 $options_stack->set_vexpand(1);
3212 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
3213 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
3214 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
3215 $options_stack->set_visible_child_name("raiddisk");
3216 my $options_stack_switcher = Gtk3::StackSwitcher->new();
3217 $options_stack_switcher->set_halign('center');
3218 $options_stack_switcher->set_stack($options_stack);
3219 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
3220 $row++;
3221 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 3222 $row++;
aed81ff0 3223
bd3a2e26 3224 $hdoption_first_setup = 0;
c7779156
FG
3225
3226 my $switch_view = sub {
3227 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3228 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 3229
c7779156
FG
3230 $target_hd_combo->set_visible(!$raid);
3231 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
3232 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
f0a0d90b 3233 $hw_raid_note->set_visible($raid);
c7779156
FG
3234 $options_stack_switcher->set_visible($enable_zfs_opts);
3235 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
3236 if ($raid) {
c6ed3b24 3237 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 3238 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 3239 } else {
c6ed3b24
DM
3240 $target_hd_label->set_text("Target Harddisk: ");
3241 }
c7779156
FG
3242 my (undef, $pref_width) = $dialog->get_preferred_width();
3243 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 3244 $pref_height = 750 if $pref_height > 750;
c7779156 3245 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
3246 };
3247
c7779156 3248 &$switch_view();
f7b853d1
DM
3249
3250 $fstypecb->signal_connect (changed => sub {
3251 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 3252 &$switch_view();
f7b853d1
DM
3253 });
3254
95844cc6
TL
3255 my $sep2 = Gtk3::HSeparator->new();
3256 $sep2->set_visible(1);
3257 $contarea->pack_end($sep2, 1, 1, 10);
3258
c6ed3b24 3259 $dialog->show();
aed81ff0
DM
3260
3261 $dialog->run();
3262
3263 my $get_float = sub {
3264 my ($entry) = @_;
3265
3266 my $text = $entry->get_text();
3267 return undef if !defined($text);
3268
3269 $text =~ s/^\s+//;
3270 $text =~ s/\s+$//;
3271
3272 return undef if $text !~ m/^\d+(\.\d+)?$/;
3273
3274 return $text;
3275 };
3276
3277 my $tmp;
3278
3279 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
3280 $config_options->{hdsize} = $tmp;
3281 } else {
3282 delete $config_options->{hdsize};
3283 }
3284
3285 if (defined($tmp = &$get_float($entry_swapsize))) {
3286 $config_options->{swapsize} = $tmp;
3287 } else {
3288 delete $config_options->{swapsize};
3289 }
3290
3291 if (defined($tmp = &$get_float($entry_maxroot))) {
3292 $config_options->{maxroot} = $tmp;
3293 } else {
3294 delete $config_options->{maxroot};
3295 }
3296
3297 if (defined($tmp = &$get_float($entry_minfree))) {
3298 $config_options->{minfree} = $tmp;
3299 } else {
3300 delete $config_options->{minfree};
3301 }
3302
b6e875ca 3303 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
3304 $config_options->{maxvz} = $tmp;
3305 } else {
3306 delete $config_options->{maxvz};
3307 }
3308
3309 $dialog->destroy();
3310}
3311
121ebc59 3312my $get_raid_devlist = sub {
c6ed3b24
DM
3313
3314 my $dev_name_hash = {};
3315
3316 my $devlist = [];
5f8e86d5 3317 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24 3318 if (my $hd = $config_options->{"disksel$i"}) {
17fd908e 3319 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
1464c7c9 3320 die "device '$devname' is used more than once\n"
c6ed3b24
DM
3321 if $dev_name_hash->{$devname};
3322 $dev_name_hash->{$devname} = $hd;
3323 push @$devlist, $hd;
3324 }
3325 }
3326
121ebc59
DM
3327 return $devlist;
3328};
3329
14aacec8
FG
3330sub zfs_mirror_size_check {
3331 my ($expected, $actual) = @_;
3332
3333 die "mirrored disks must have same size\n"
3334 if abs($expected - $actual) > $expected / 10;
3335}
3336
5ea943cf
SI
3337sub legacy_bios_4k_check {
3338 my ($lbs) = @_;
3339 die "Booting from 4kn drive in legacy BIOS mode is not supported.\n"
3340 if (($boot_type ne 'efi') && ($lbs == 4096));
3341}
3342
121ebc59
DM
3343sub get_zfs_raid_setup {
3344
3345 my $filesys = $config_options->{filesys};
3346
3347 my $devlist = &$get_raid_devlist();
3348
224bb7b0 3349 my $diskcount = scalar(@$devlist);
0cfa502c 3350 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24
DM
3351
3352 my $cmd= '';
3353 if ($filesys eq 'zfs (RAID0)') {
c6ed3b24 3354 foreach my $hd (@$devlist) {
5ea943cf 3355 legacy_bios_4k_check(@$hd[4]);
c6ed3b24
DM
3356 $cmd .= " @$hd[1]";
3357 }
3358 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 3359 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 3360 $cmd .= ' mirror ';
269c66a6 3361 my $hd = @$devlist[0];
14aacec8 3362 my $expected_size = @$hd[2]; # all disks need approximately same size
eaeccd9f 3363 foreach my $hd (@$devlist) {
14aacec8 3364 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3365 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3366 $cmd .= " @$hd[1]";
c6ed3b24
DM
3367 }
3368 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 3369 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 3370 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 3371
224bb7b0 3372 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
3373 my $hd1 = @$devlist[$i];
3374 my $hd2 = @$devlist[$i+1];
14aacec8 3375 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
5ea943cf
SI
3376 legacy_bios_4k_check(@$hd1[4]);
3377 legacy_bios_4k_check(@$hd2[4]);
c6ed3b24
DM
3378 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
3379 }
3380
3381 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3382 my $level = $1;
3383 my $mindisks = 2 + $level;
0cfa502c 3384 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 3385 my $hd = @$devlist[0];
14aacec8 3386 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 3387 $cmd .= " raidz$level";
eaeccd9f 3388 foreach my $hd (@$devlist) {
14aacec8 3389 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3390 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3391 $cmd .= " @$hd[1]";
c6ed3b24
DM
3392 }
3393 } else {
3394 die "unknown zfs mode '$filesys'\n";
3395 }
3396
82695821 3397 return ($devlist, $cmd);
c6ed3b24
DM
3398}
3399
121ebc59
DM
3400sub get_btrfs_raid_setup {
3401
3402 my $filesys = $config_options->{filesys};
3403
3404 my $devlist = &$get_raid_devlist();
3405
3406 my $diskcount = scalar(@$devlist);
0cfa502c 3407 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
3408
3409 my $mode;
3410
3411 if ($diskcount == 1) {
3412 $mode = 'single';
3413 } else {
3414 if ($filesys eq 'btrfs (RAID0)') {
3415 $mode = 'raid0';
3416 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 3417 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
3418 $mode = 'raid1';
3419 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 3420 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
3421 $mode = 'raid10';
3422 } else {
9d69f3d3 3423 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
3424 }
3425 }
3426
3427 return ($devlist, $mode);
3428}
3429
218a4b6b 3430my $last_hd_selected = 0;
89a12446
DM
3431sub create_hdsel_view {
3432
451b1da5 3433 $prev_btn->set_sensitive(1); # enable previous button at this point
201a5120 3434
71590b6a 3435 cleanup_view();
89a12446 3436
71590b6a
OB
3437 my $vbox = Gtk3::VBox->new(0, 0);
3438 $inbox->pack_start($vbox, 1, 0, 0);
3439 my $hbox = Gtk3::HBox->new(0, 0);
3440 $vbox->pack_start($hbox, 0, 0, 10);
968fa90b 3441
17fd908e 3442 my ($disk, $devname, $size, $model, $logical_bsize) = @{@$hds[0]};
9227a70f 3443 $target_hd = $devname if !defined($target_hd);
89a12446 3444
71590b6a
OB
3445 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3446 $hbox->pack_start($target_hd_label, 0, 0, 0);
89a12446 3447
bcbfab6b 3448 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 3449
1aa5bd02 3450 foreach my $hd (@$hds) {
17fd908e 3451 ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
71590b6a 3452 $target_hd_combo->append_text (get_device_desc($devname, $size, $model));
1aa5bd02 3453 }
89a12446 3454
90af1603
OB
3455 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3456 if ($raid) {
3457 $target_hd_label->set_text("Target: $config_options->{filesys} ");
3458 $target_hd_combo->set_visible(0);
3459 $target_hd_combo->set_no_show_all(1);
3460 }
218a4b6b 3461 $target_hd_combo->set_active($last_hd_selected);
71590b6a 3462 $target_hd_combo->signal_connect(changed => sub {
1aa5bd02
DM
3463 $a = shift->get_active;
3464 my ($disk, $devname) = @{@$hds[$a]};
3b959bef 3465 $last_hd_selected = $a;
1aa5bd02 3466 $target_hd = $devname;
1aa5bd02 3467 });
1464c7c9 3468
71590b6a 3469 $hbox->pack_start($target_hd_combo, 0, 0, 10);
aed81ff0 3470
71590b6a 3471 my $options = Gtk3::Button->new('_Options');
aed81ff0
DM
3472 $options->signal_connect (clicked => \&create_hdoption_view);
3473 $hbox->pack_start ($options, 0, 0, 0);
3474
89a12446
DM
3475
3476 $inbox->show_all;
3477
201a5120 3478 display_html();
c6ed3b24 3479
71590b6a 3480 set_next(undef, sub {
c6ed3b24
DM
3481
3482 if ($config_options->{filesys} =~ m/zfs/) {
a7d40341 3483 my ($devlist) = eval { get_zfs_raid_setup() };
c6ed3b24 3484 if (my $err = $@) {
303dfb2c
TL
3485 display_message("Warning: $err\nPlease fix ZFS setup first.");
3486 return;
c6ed3b24 3487 }
303dfb2c 3488 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
121ebc59 3489 } elsif ($config_options->{filesys} =~ m/btrfs/) {
a7d40341 3490 my ($devlist) = eval { get_btrfs_raid_setup() };
121ebc59 3491 if (my $err = $@) {
303dfb2c
TL
3492 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3493 return;
121ebc59 3494 }
303dfb2c 3495 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
c6ed3b24 3496 } else {
5ea943cf
SI
3497 eval { legacy_bios_4k_check(logical_blocksize($target_hd)) };
3498 if (my $err = $@) {
3499 display_message("Warning: $err\n");
3500 return;
3501 }
a7d40341 3502 $config_options->{target_hds} = [ $target_hd ];
c6ed3b24 3503 }
303dfb2c
TL
3504
3505 $step_number++;
3506 create_country_view();
c6ed3b24 3507 });
89a12446
DM
3508}
3509
3510sub create_extract_view {
3511
71590b6a 3512 cleanup_view();
89a12446 3513
550958aa
DM
3514 display_info();
3515
201a5120 3516 $next->set_sensitive(0);
ac3ee85b
TL
3517 $prev_btn->set_sensitive(0);
3518 $prev_btn->hide();
89a12446 3519
71590b6a 3520 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 3521 $inbox->pack_start ($vbox, 1, 0, 0);
71590b6a 3522 my $hbox = Gtk3::HBox->new(0, 0);
53986d77 3523 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3524
71590b6a 3525 my $vbox2 = Gtk3::VBox->new(0, 0);
89a12446
DM
3526 $hbox->pack_start ($vbox2, 0, 0, 0);
3527
71590b6a 3528 $progress_status = Gtk3::Label->new('');
89a12446 3529 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3530
7becc472 3531 $progress = Gtk3::ProgressBar->new;
45feca6f 3532 $progress->set_show_text(1);
7becc472 3533 $progress->set_size_request (600, -1);
89a12446 3534
71590b6a 3535 $vbox2->pack_start($progress, 0, 0, 0);
89a12446 3536
201a5120 3537 $inbox->show_all();
89a12446
DM
3538
3539 my $tdir = $opt_testmode ? "target" : "/target";
3540 mkdir $tdir;
97980bf2 3541 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446 3542
71590b6a 3543 eval { extract_data($base, $tdir); };
89a12446
DM
3544 my $err = $@;
3545
201a5120 3546 $next->set_sensitive(1);
89a12446 3547
71590b6a 3548 set_next("_Reboot", sub { exit (0); } );
89a12446 3549
296cf41f 3550 if ($err) {
201a5120
OB
3551 display_html("fail.htm");
3552 display_error($err);
296cf41f 3553 } else {
201a5120
OB
3554 cleanup_view();
3555 display_html("success.htm");
dfc02f3c
TL
3556
3557 if ($config_options->{autoreboot}) {
3558 Glib::Timeout->add(1000, sub {
3559 if ($autoreboot_seconds > 0) {
3560 $autoreboot_seconds--;
3561 display_html("success.htm");
3562 } else {
3563 exit(0);
3564 }
3565 });
3566 }
296cf41f 3567 }
89a12446
DM
3568}
3569
89a12446
DM
3570sub create_intro_view {
3571
451b1da5 3572 $prev_btn->set_sensitive(0);
201a5120
OB
3573
3574 cleanup_view();
89a12446 3575
ca951e77 3576 if (int($total_memory) < 1024) {
b10074d1 3577 my $product = $product_cfg->{$setup->{product}};
18a9811e 3578
3befbf97 3579 display_error("Less than 1 GiB of usable memory detected, installation will probably fail.\n\n".
b10074d1 3580 "See 'System Requirements' in the $product->{fullname} documentation.");
2b85ee1b
OB
3581 }
3582
bdeca872
DM
3583 if ($setup->{product} eq 'pve') {
3584 eval {
3585 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3586 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
2780ea4f 3587 display_error("No support for KVM virtualization detected.\n\n" .
bdeca872
DM
3588 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3589 }
3590 };
3591 }
7fff0d85 3592
201a5120 3593 display_html();
89a12446 3594
201a5120 3595 $step_number++;
71590b6a 3596 set_next("I a_gree", \&create_hdsel_view);
89a12446
DM
3597}
3598
71590b6a 3599$ipconf = get_ip_config();
89a12446 3600
9d1f1ee3 3601$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3602
3603# read country, kmap and timezone infos
71590b6a 3604$cmap = read_cmap();
89a12446 3605
9d1f1ee3
FG
3606if (!defined($cmap->{country}->{$country})) {
3607 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3608 $country = undef;
3609}
3610
89a12446
DM
3611create_main_window ();
3612
ff2ce71c
FG
3613my $initial_error = 0;
3614
89a12446
DM
3615if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3616 print "no hardisks found\n";
ff2ce71c 3617 $initial_error = 1;
201a5120 3618 display_html("nohds.htm");
71590b6a 3619 set_next("Reboot", sub { exit(0); } );
89a12446 3620} else {
89a12446
DM
3621 foreach my $hd (@$hds) {
3622 my ($disk, $devname) = @$hd;
3623 next if $devname =~ m|^/dev/md\d+$|;
3624 print "found Disk$disk N:$devname\n";
3625 }
89a12446
DM
3626}
3627
72836708
FG
3628if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3629 print "no network interfaces found\n";
3630 $initial_error = 1;
201a5120 3631 display_html("nonics.htm");
71590b6a 3632 set_next("Reboot", sub { exit(0); } );
72836708
FG
3633}
3634
ff2ce71c
FG
3635create_intro_view () if !$initial_error;
3636
7becc472 3637Gtk3->main;
89a12446 3638
0e631479
SI
3639# reap left over zombie processes
3640while ((my $child = waitpid(-1, POSIX::WNOHANG)) > 0) {
3641 print "reaped child $child\n";
3642}
3643
89a12446 3644exit 0;