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