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