]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
buildsys: better clean
[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";
1985 } elsif (-f "$htmldir/common/$filename") {
1986 $path = "$htmldir/common/$filename";
1987 } else {
1988 # FIXME: die now already?
1989 }
8a50920c
DM
1990
1991 my $data = file_get_contents($path);
1992
1993 if ($filename eq 'license.htm') {
93f25df9
TL
1994 my $license = eval { decode('utf8', file_get_contents("${proxmox_cddir}/EULA")) };
1995 if (my $err = $@) {
1996 die $err if !$opt_testmode;
1997 $license = "TESTMODE: Ignore non existent EULA...\n";
1998 }
3c866639 1999 my $title = "END USER LICENSE AGREEMENT (EULA)";
f91c161b 2000 $data =~ s/__LICENSE__/$license/;
8a50920c 2001 $data =~ s/__LICENSE_TITLE__/$title/;
3bcac16b
TL
2002 } elsif ($filename eq 'success.htm') {
2003 my $addr = $ipversion == 6 ? "[${ipaddress}]" : "$ipaddress";
cfb92364 2004 $data =~ s/__IPADDR__/$addr/g;
c2f72dd6 2005 $data =~ s/__PORT__/$setup->{port}/g;
dfc02f3c
TL
2006
2007 my $autoreboot_msg = $config_options->{autoreboot}
2008 ? "Automatic reboot scheduled in $autoreboot_seconds seconds."
2009 : '';
2010 $data =~ s/__AUTOREBOOT_MSG__/$autoreboot_msg/;
8a50920c 2011 }
c2f72dd6 2012 $data =~ s/__FULL_PRODUCT_NAME__/$setup->{fullname}/g;
8a50920c 2013
c2f72dd6
TL
2014 # HACK: always set base-path to common path, all resources are there.
2015 # NOTE: we could also use an overlayfs with lower=common upper=$product & work=/run/$tmp
2016 $htmlview->load_html($data, "file://$htmldir/common/");
550958aa
DM
2017
2018 $last_display_change = time();
7becc472
DM
2019}
2020
201a5120
OB
2021sub prev_function {
2022
2023 my ($text, $fctn) = @_;
2024
2025 $fctn = $step_number if !$fctn;
2026 $text = "_Previous" if !$text;
451b1da5 2027 $prev_btn->set_label ($text);
201a5120
OB
2028
2029 $step_number--;
2030 $steps[$step_number]->{function}();
2031
71590b6a 2032 $prev_btn->grab_focus();
201a5120
OB
2033}
2034
89a12446
DM
2035sub set_next {
2036 my ($text, $fctn) = @_;
2037
2038 $next_fctn = $fctn;
201a5120
OB
2039 my $step = $steps[$step_number];
2040 $text //= $steps[$step_number]->{next_button} // '_Next';
71590b6a 2041 $next->set_label($text);
968fa90b 2042
71590b6a 2043 $next->grab_focus();
89a12446 2044}
89a12446
DM
2045
2046sub create_main_window {
2047
71590b6a
OB
2048 $window = Gtk3::Window->new();
2049 $window->set_default_size(1024, 768);
84761f93 2050 $window->set_has_resize_grip(0);
d6b47e68 2051 $window->fullscreen() if !$opt_testmode;
71590b6a 2052 $window->set_decorated(0) if !$opt_testmode;
89a12446 2053
71590b6a 2054 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 2055
782b4acd
DM
2056 my $logofn = "$setup->{product}-banner.png";
2057 my $image = Gtk3::Image->new_from_file("${proxmox_libdir}/$logofn");
71590b6a 2058 $vbox->pack_start($image, 0, 0, 0);
89a12446 2059
71590b6a
OB
2060 my $hbox = Gtk3::HBox->new(0, 0);
2061 $vbox->pack_start($hbox, 1, 1, 0);
89a12446 2062
7becc472
DM
2063 # my $f1 = Gtk3::Frame->new ('test');
2064 # $f1->set_shadow_type ('none');
2065 # $hbox->pack_start ($f1, 1, 1, 0);
89a12446 2066
71590b6a
OB
2067 my $sep1 = Gtk3::HSeparator->new();
2068 $vbox->pack_start($sep1, 0, 0, 0);
89a12446 2069
71590b6a
OB
2070 $cmdbox = Gtk3::HBox->new();
2071 $vbox->pack_start($cmdbox, 0, 0, 10);
89a12446 2072
71590b6a
OB
2073 $next = Gtk3::Button->new('_Next');
2074 $next->signal_connect(clicked => sub { $last_display_change = 0; &$next_fctn (); });
2075 $cmdbox->pack_end($next, 0, 0, 10);
201a5120
OB
2076
2077
71590b6a
OB
2078 $prev_btn = Gtk3::Button->new('_Previous');
2079 $prev_btn->signal_connect(clicked => sub { $last_display_change = 0; &prev_function (); });
2080 $cmdbox->pack_end($prev_btn, 0, 0, 10);
201a5120
OB
2081
2082
71590b6a
OB
2083 my $abort = Gtk3::Button->new('_Abort');
2084 $abort->set_can_focus(0);
2085 $cmdbox->pack_start($abort, 0, 0, 10);
2086 $abort->signal_connect(clicked => sub { exit (-1); });
89a12446 2087
71590b6a
OB
2088 my $vbox2 = Gtk3::VBox->new(0, 0);
2089 $hbox->add($vbox2);
89a12446 2090
ed0e6aea 2091 $htmlview = Gtk3::WebKit2::WebView->new();
7becc472
DM
2092 my $scrolls = Gtk3::ScrolledWindow->new();
2093 $scrolls->add($htmlview);
1464c7c9 2094
71590b6a
OB
2095 my $hbox2 = Gtk3::HBox->new(0, 0);
2096 $hbox2->pack_start($scrolls, 1, 1, 0);
89a12446 2097
71590b6a 2098 $vbox2->pack_start($hbox2, 1, 1, 0);
89a12446 2099
71590b6a
OB
2100 my $vbox3 = Gtk3::VBox->new(0, 0);
2101 $vbox2->pack_start($vbox3, 0, 0, 0);
89a12446 2102
7becc472 2103 my $sep2 = Gtk3::HSeparator->new;
71590b6a 2104 $vbox3->pack_start($sep2, 0, 0, 0);
89a12446 2105
71590b6a
OB
2106 $inbox = Gtk3::HBox->new(0, 0);
2107 $vbox3->pack_start($inbox, 0, 0, 0);
89a12446 2108
71590b6a 2109 $window->add($vbox);
89a12446
DM
2110
2111 $window->show_all;
71590b6a 2112 $window->realize();
89a12446
DM
2113}
2114
1464c7c9 2115sub cleanup_view {
d2120e51
DM
2116 $inbox->foreach(sub {
2117 my $child = shift;
1464c7c9 2118 $inbox->remove ($child);
d2120e51 2119 });
89a12446
DM
2120}
2121
aed81ff0
DM
2122# fixme: newer GTK3 has special properties to handle numbers with Entry
2123# only allow floating point numbers with Gtk3::Entry
e73c5fcf 2124
aed81ff0
DM
2125sub check_float {
2126 my ($entry, $event) = @_;
2127
e73c5fcf
FG
2128 return check_number($entry, $event, 1);
2129}
2130
2131sub check_int {
2132 my ($entry, $event) = @_;
2133
2134 return check_number($entry, $event, 0);
2135}
2136
2137sub check_number {
2138 my ($entry, $event, $float) = @_;
aed81ff0
DM
2139
2140 my $val = $event->get_keyval;
2141
e73c5fcf 2142 if (($float && $val == ord '.') ||
aed81ff0
DM
2143 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
2144 $val == Gtk3::Gdk::KEY_Shift_L ||
2145 $val == Gtk3::Gdk::KEY_Tab ||
2146 $val == Gtk3::Gdk::KEY_Left ||
2147 $val == Gtk3::Gdk::KEY_Right ||
2148 $val == Gtk3::Gdk::KEY_BackSpace ||
2149 $val == Gtk3::Gdk::KEY_Delete ||
2150 ($val >= ord '0' && $val <= ord '9') ||
2151 ($val >= Gtk3::Gdk::KEY_KP_0 &&
2152 $val <= Gtk3::Gdk::KEY_KP_9)) {
2153 return undef;
2154 }
2155
2156 return 1;
2157}
2158
d2120e51 2159sub create_text_input {
89a12446
DM
2160 my ($default, $text) = @_;
2161
cc120d79 2162 my $hbox = Gtk3::Box->new('horizontal', 0);
89a12446 2163
71590b6a
OB
2164 my $label = Gtk3::Label->new($text);
2165 $label->set_size_request(150, -1);
2166 $label->set_alignment(1, 0.5);
2167 $hbox->pack_start($label, 0, 0, 10);
2168 my $e1 = Gtk3::Entry->new();
cc120d79 2169 $e1->set_width_chars(35);
71590b6a
OB
2170 $hbox->pack_start($e1, 0, 0, 0);
2171 $e1->set_text($default);
89a12446
DM
2172
2173 return ($hbox, $e1);
2174}
cc120d79
TL
2175sub create_cidr_inputs {
2176 my ($default_ip, $default_mask) = @_;
2177
2178 my $hbox = Gtk3::Box->new('horizontal', 0);
2179
2180 my $label = Gtk3::Label->new('IP Address (CIDR)');
2181 $label->set_size_request(150, -1);
2182 $label->set_alignment(1, 0.5);
2183 $hbox->pack_start($label, 0, 0, 10);
2184
2185 my $ip_el = Gtk3::Entry->new();
2186 $ip_el->set_width_chars(28);
2187 $hbox->pack_start($ip_el, 0, 0, 0);
2188 $ip_el->set_text($default_ip);
2189
2190 $label = Gtk3::Label->new('/');
2191 $label->set_size_request(10, -1);
2192 $label->set_alignment(0.5, 0.5);
2193 $hbox->pack_start($label, 0, 0, 2);
2194
2195 my $cidr_el = Gtk3::Entry->new();
2196 $cidr_el->set_width_chars(3);
2197 $hbox->pack_start($cidr_el, 0, 0, 0);
2198 $cidr_el->set_text($default_mask);
2199
2200 return ($hbox, $ip_el, $cidr_el);
2201}
89a12446 2202
89a12446
DM
2203sub get_ip_config {
2204
fe44bd92
FG
2205 my $ifaces = {};
2206 my $default;
89a12446 2207
fe44bd92
FG
2208 my $links = `ip -o l`;
2209 foreach my $l (split /\n/,$links) {
2210 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
2211 next if !$name || $name eq 'lo';
89a12446 2212
fe44bd92
FG
2213 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
2214 $driver =~ s!^.*/!!;
2215
2216 $ifaces->{"$index"} = {
2217 name => $name,
2218 driver => $driver,
2219 flags => $flags,
2220 state => $state,
2221 mac => $mac,
2222 };
2223
2224 my $addresses = `ip -o a s $name`;
2225 foreach my $a (split /\n/,$addresses) {
2226 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
2227 next if !$ip;
32b6fbcf 2228 next if $a =~ /scope\s+link/; # ignore link local
fe44bd92
FG
2229
2230 my $mask = $prefix;
2231
2232 if ($family eq 'inet') {
2233 next if !$ip =~ /$IPV4RE/;
2234 next if $prefix < 8 || $prefix > 32;
2235 $mask = @$ipv4_reverse_mask[$prefix];
2236 } else {
2237 next if !$ip =~ /$IPV6RE/;
2238 }
2239
2240 $default = $index if !$default;
2241
2242 $ifaces->{"$index"}->{"$family"} = {
cc120d79 2243 prefix => $prefix,
fe44bd92
FG
2244 mask => $mask,
2245 addr => $ip,
2246 };
2247 }
2248 }
2249
2250
2251 my $route = `ip route`;
2252 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
89a12446
DM
2253
2254 my $resolvconf = `cat /etc/resolv.conf`;
2255 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
713790a4 2256 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
89a12446
DM
2257
2258 return {
fe44bd92
FG
2259 default => $default,
2260 ifaces => $ifaces,
89a12446
DM
2261 gateway => $gateway,
2262 dnsserver => $dnsserver,
713790a4 2263 domain => $domain,
89a12446
DM
2264 }
2265}
2266
2267sub display_message {
2268 my ($msg) = @_;
2269
71590b6a
OB
2270 my $dialog = Gtk3::MessageDialog->new($window, 'modal',
2271 'info', 'ok', $msg);
89a12446
DM
2272 $dialog->run();
2273 $dialog->destroy();
2274}
2275
2276sub display_error {
2277 my ($msg) = @_;
2278
71590b6a
OB
2279 my $dialog = Gtk3::MessageDialog->new($window, 'modal',
2280 'error', 'ok', $msg);
89a12446
DM
2281 $dialog->run();
2282 $dialog->destroy();
2283}
2284
fe44bd92
FG
2285my $ipconf_first_view = 1;
2286
89a12446
DM
2287sub create_ipconf_view {
2288
201a5120
OB
2289 cleanup_view();
2290 display_html();
89a12446 2291
cc120d79
TL
2292 my $vcontainer = Gtk3::Box->new('vertical', 0);
2293 $inbox->pack_start($vcontainer, 1, 0, 0);
2294 my $hcontainer = Gtk3::Box->new('horizontal', 0);
2295 $vcontainer->pack_start($hcontainer, 0, 0, 10);
2296 my $vbox = Gtk3::Box->new('vertical', 0);
2297 $hcontainer->add($vbox);
89a12446 2298
ebc4f76f 2299 my $ipaddr_text = $config->{ipaddress} // "192.168.100.2";
cc120d79
TL
2300 my $netmask_text = $config->{netmask} // "24";
2301 my $cidr_box;
2302 ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) =
2303 create_cidr_inputs($ipaddr_text, $netmask_text);
fe44bd92
FG
2304
2305 my $device_cb = Gtk3::ComboBoxText->new();
2306 $device_cb->set_active(0);
2307 $device_cb->set_visible(1);
2308
2309 my $get_device_desc = sub {
2310 my $iface = shift;
2311 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
2312 };
2313
2314 my $device_active_map = {};
ebc4f76f 2315 my $device_active_reverse_map = {};
5b6ba737
FG
2316
2317 my $device_change_handler = sub {
2318 my $current = shift;
d6524c52
TL
2319
2320 my $new = $device_active_map->{$current->get_active()};
cd2d2a27 2321 return if defined($ipconf->{selected}) && $new eq $ipconf->{selected};
d6524c52
TL
2322
2323 $ipconf->{selected} = $new;
5b6ba737 2324 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
ebc4f76f 2325 $config->{mngmt_nic} = $iface->{name};
5b6ba737
FG
2326 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2327 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
cc120d79
TL
2328 $ipconf_entry_mask->set_text($iface->{inet}->{prefix} || $iface->{inet6}->{prefix})
2329 if $iface->{inet}->{prefix} || $iface->{inet6}->{prefix};
5b6ba737
FG
2330 };
2331
fe44bd92
FG
2332 my $i = 0;
2333 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2334 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
ebc4f76f
TL
2335 $device_active_map->{$i} = $index;
2336 $device_active_reverse_map->{$ipconf->{ifaces}->{$index}->{name}} = $i;
fe44bd92
FG
2337 if ($ipconf_first_view && $index == $ipconf->{default}) {
2338 $device_cb->set_active($i);
5b6ba737 2339 &$device_change_handler($device_cb);
fe44bd92
FG
2340 $ipconf_first_view = 0;
2341 }
71590b6a 2342 $device_cb->signal_connect('changed' => $device_change_handler);
fe44bd92
FG
2343 $i++;
2344 }
2345
ebc4f76f
TL
2346 if (my $nic = $config->{mngmt_nic}) {
2347 $device_cb->set_active($device_active_reverse_map->{$nic} // 0);
2348 } else {
2349 $device_cb->set_active(0);
2350 }
5b6ba737 2351
71590b6a
OB
2352 my $devicebox = Gtk3::HBox->new(0, 0);
2353 my $label = Gtk3::Label->new("Management Interface:");
2354 $label->set_size_request(150, -1);
2355 $label->set_alignment(1, 0.5);
2356 $devicebox->pack_start($label, 0, 0, 10);
2357 $devicebox->pack_start($device_cb, 0, 0, 0);
fe44bd92 2358
cc120d79 2359 $vbox->pack_start($devicebox, 0, 0, 2);
968fa90b 2360
ebc4f76f 2361 my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
1464c7c9 2362
cc120d79
TL
2363 my ($hostbox, $hostentry) = create_text_input($hn, 'Hostname (FQDN):');
2364 $vbox->pack_start($hostbox, 0, 0, 2);
89a12446 2365
cc120d79 2366 $vbox->pack_start($cidr_box, 0, 0, 2);
89a12446 2367
ebc4f76f 2368 $gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
89a12446
DM
2369
2370 my $gwbox;
d2120e51 2371 ($gwbox, $ipconf_entry_gw) =
71590b6a 2372 create_text_input($gateway, 'Gateway:');
89a12446 2373
cc120d79 2374 $vbox->pack_start($gwbox, 0, 0, 2);
89a12446 2375
ebc4f76f 2376 $dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
89a12446
DM
2377
2378 my $dnsbox;
d2120e51 2379 ($dnsbox, $ipconf_entry_dns) =
71590b6a 2380 create_text_input($dnsserver, 'DNS Server:');
89a12446 2381
cc120d79 2382 $vbox->pack_start($dnsbox, 0, 0, 0);
89a12446
DM
2383
2384 $inbox->show_all;
71590b6a 2385 set_next(undef, sub {
d2120e51
DM
2386
2387 # verify hostname
1464c7c9 2388
89a12446 2389 my $text = $hostentry->get_text();
968fa90b 2390
89a12446
DM
2391 $text =~ s/^\s+//;
2392 $text =~ s/\s+$//;
2393
ebc4f76f
TL
2394 $config->{fqdn} = $text;
2395
ac3757a9 2396 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2397
24973868
WB
2398 # Debian does not support purely numeric hostnames
2399 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2400 display_message("Purely numeric hostnames are not allowed.");
2401 $hostentry->grab_focus();
2402 return;
2403 }
2404
a39bc1f2 2405 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2406 $text =~ m/^([^\.]+)\.(\S+)$/) {
2407 $hostname = $1;
2408 $domain = $2;
d2120e51 2409 } else {
71590b6a 2410 display_message("Hostname does not look like a fully qualified domain name.");
d2120e51 2411 $hostentry->grab_focus();
89a12446
DM
2412 return;
2413 }
d2120e51
DM
2414
2415 # verify ip address
2416
2417 $text = $ipconf_entry_addr->get_text();
2418 $text =~ s/^\s+//;
2419 $text =~ s/\s+$//;
2420 if ($text =~ m!^($IPV4RE)$!) {
2421 $ipaddress = $text;
b6200603
DM
2422 $ipversion = 4;
2423 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2424 $ipaddress = $text;
b6200603 2425 $ipversion = 6;
d2120e51 2426 } else {
71590b6a 2427 display_message("IP address is not valid.");
d2120e51
DM
2428 $ipconf_entry_addr->grab_focus();
2429 return;
2430 }
ebc4f76f 2431 $config->{ipaddress} = $ipaddress;
d2120e51
DM
2432
2433 $text = $ipconf_entry_mask->get_text();
2434 $text =~ s/^\s+//;
2435 $text =~ s/\s+$//;
cc120d79 2436 if ($ipversion == 6 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 126) {
b6200603 2437 $netmask = $text;
cc120d79 2438 } elsif ($ipversion == 4 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 32) {
d2120e51 2439 $netmask = $text;
cc120d79
TL
2440 } elsif ($ipversion == 4 && defined($ipv4_mask_hash->{$text})) {
2441 # costs nothing to handle 255.x.y.z style masks, so continue to allow it
2442 $netmask = $ipv4_mask_hash->{$text};
d2120e51 2443 } else {
71590b6a 2444 display_message("Netmask is not valid.");
d2120e51
DM
2445 $ipconf_entry_mask->grab_focus();
2446 return;
2447 }
cc120d79 2448 $cidr = "$ipaddress/$netmask";
ebc4f76f 2449 $config->{netmask} = $netmask;
d2120e51
DM
2450
2451 $text = $ipconf_entry_gw->get_text();
2452 $text =~ s/^\s+//;
2453 $text =~ s/\s+$//;
b6200603
DM
2454 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2455 $gateway = $text;
2456 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2457 $gateway = $text;
2458 } else {
71590b6a 2459 display_message("Gateway is not valid.");
d2120e51
DM
2460 $ipconf_entry_gw->grab_focus();
2461 return;
2462 }
ebc4f76f 2463 $config->{gateway} = $gateway;
1464c7c9 2464
d2120e51
DM
2465 $text = $ipconf_entry_dns->get_text();
2466 $text =~ s/^\s+//;
2467 $text =~ s/\s+$//;
b6200603
DM
2468 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2469 $dnsserver = $text;
2470 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2471 $dnsserver = $text;
2472 } else {
71590b6a 2473 display_message("DNS server is not valid.");
d2120e51
DM
2474 $ipconf_entry_dns->grab_focus();
2475 return;
2476 }
ebc4f76f 2477 $config->{dnsserver} = $dnsserver;
1464c7c9 2478
d2120e51 2479 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2480
201a5120 2481 $step_number++;
2e33c3f0 2482 create_ack_view();
89a12446
DM
2483 });
2484
2485 $hostentry->grab_focus();
2486}
2487
2e33c3f0
OB
2488sub create_ack_view {
2489
2490 cleanup_view();
2491
dfc02f3c
TL
2492 my $vbox = Gtk3::VBox->new(0, 0);
2493 $inbox->pack_start($vbox, 1, 0, 0);
dfc02f3c
TL
2494
2495 my $reboot_checkbox = Gtk3::CheckButton->new('Automatically reboot after successful installation');
2496 $reboot_checkbox->set_active(1);
2497 $reboot_checkbox->signal_connect ("toggled" => sub {
2498 my $cb = shift;
2499 $config_options->{autoreboot} = $cb->get_active();
2500 });
2501 $vbox->pack_start($reboot_checkbox, 0, 0, 2);
2502
c2f72dd6
TL
2503 my $ack_template = "${proxmox_libdir}/html/common/ack_template.htm";
2504 my $ack_html = "${proxmox_libdir}/html/$setup->{product}/$steps[$step_number]->{html}";
2e33c3f0
OB
2505 my $html_data = file_get_contents($ack_template);
2506
2507 my %config_values = (
a7d40341 2508 __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
0470018e 2509 __target_fs__ => $config_options->{filesys},
0ddd2227 2510 __country__ => $cmap->{country}->{$country}->{name},
2e33c3f0
OB
2511 __timezone__ => $timezone,
2512 __keymap__ => $keymap,
2513 __mailto__ => $mailto,
2514 __interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
2515 __hostname__ => $hostname,
2516 __ip__ => $ipaddress,
b1838e1e 2517 __cidr__ => $cidr,
2e33c3f0
OB
2518 __netmask__ => $netmask,
2519 __gateway__ => $gateway,
2520 __dnsserver__ => $dnsserver,
2521 );
2522
2523 while ( my ($k, $v) = each %config_values) {
2524 $html_data =~ s/$k/$v/g;
2525 }
2526
2527 write_config($html_data, $ack_html);
2528
2529 display_html();
2530
dfc02f3c
TL
2531 $inbox->show_all;
2532
2e33c3f0
OB
2533 set_next(undef, sub {
2534 $step_number++;
2535 create_extract_view();
2536 });
2537}
2538
89a12446
DM
2539sub get_device_desc {
2540 my ($devname, $size, $model) = @_;
2541
d2120e51 2542 if ($size && ($size > 0)) {
1bd457bb 2543 $size = int($size/2048); # size in MB, from 512B "sectors"
89a12446 2544
d2120e51 2545 my $text = "$devname (";
89a12446
DM
2546 if ($size >= 1024) {
2547 $size = int($size/1024); # size in GB
ceabb291
TL
2548 if ($size >= 1024) {
2549 $size = int($size/1024); # size in GB
2550 $text .= "${size}TiB";
2551 } else {
2552 $text .= "${size}GiB";
2553 }
89a12446 2554 } else {
ceabb291 2555 $text .= "${size}MiB";
89a12446
DM
2556 }
2557
d2120e51
DM
2558 $text .= ", $model" if $model;
2559 $text .= ")";
2560
89a12446
DM
2561 } else {
2562 return $devname;
2563 }
2564}
2565
d92ada4e
SI
2566my $last_layout;
2567my $country_layout;
89a12446
DM
2568sub update_layout {
2569 my ($cb, $kmap) = @_;
2570
2571 my $ind;
2572 my $def;
2573 my $i = 0;
2574 my $kmaphash = $cmap->{kmaphash};
2575 foreach my $layout (sort keys %$kmaphash) {
2576 $def = $i if $kmaphash->{$layout} eq 'en-us';
2577 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2578 $i++;
2579 }
2580
d92ada4e
SI
2581 my $val = $ind || $def || 0;
2582
2583 if (!defined($kmap)) {
2584 $last_layout //= $val;
2585 } elsif (!defined($country_layout) || $country_layout != $val) {
2586 $last_layout = $country_layout = $val;
2587 }
2588 $cb->set_active($last_layout);
89a12446
DM
2589}
2590
2591my $lastzonecb;
2592sub update_zonelist {
2593 my ($box, $cc) = @_;
2594
2595 my $cczones = $cmap->{cczones};
2596 my $zones = $cmap->{zones};
2597
2598 my $sel;
2599 if ($lastzonecb) {
2600 $sel = $lastzonecb->get_active_text();
2601 $box->remove ($lastzonecb);
2602 } else {
2603 $sel = $timezone; # used once to select default
2604 }
2605
bcbfab6b 2606 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
71590b6a 2607 $cb->set_size_request(200, -1);
89a12446 2608
71590b6a 2609 $cb->signal_connect('changed' => sub {
89a12446
DM
2610 $timezone = $cb->get_active_text();
2611 });
2612
2613 my @za;
2614 if ($cc && defined ($cczones->{$cc})) {
2615 @za = keys %{$cczones->{$cc}};
2616 } else {
2617 @za = keys %$zones;
2618 }
2619 my $ind;
2620 my $i = 0;
2621 foreach my $zone (sort @za) {
2622 $ind = $i if $sel && $zone eq $sel;
71590b6a 2623 $cb->append_text($zone);
89a12446
DM
2624 $i++;
2625 }
2626
71590b6a 2627 $cb->set_active($ind || 0);
89a12446
DM
2628
2629 $cb->show;
71590b6a 2630 $box->pack_start($cb, 0, 0, 0);
89a12446
DM
2631}
2632
2633sub create_password_view {
2634
71590b6a 2635 cleanup_view();
89a12446 2636
71590b6a
OB
2637 my $vbox2 = Gtk3::VBox->new(0, 0);
2638 $inbox->pack_start($vbox2, 1, 0, 0);
2639 my $vbox = Gtk3::VBox->new(0, 0);
2640 $vbox2->pack_start($vbox, 0, 0, 10);
2641
2642 my $hbox1 = Gtk3::HBox->new(0, 0);
2643 my $label = Gtk3::Label->new("Password");
2644 $label->set_size_request(150, -1);
2645 $label->set_alignment(1, 0.5);
2646 $hbox1->pack_start($label, 0, 0, 10);
2647 my $pwe1 = Gtk3::Entry->new();
2648 $pwe1->set_visibility(0);
201a5120 2649 $pwe1->set_text($password) if $password;
71590b6a
OB
2650 $pwe1->set_size_request(200, -1);
2651 $hbox1->pack_start($pwe1, 0, 0, 0);
2652
2653 my $hbox2 = Gtk3::HBox->new(0, 0);
2654 $label = Gtk3::Label->new("Confirm");
2655 $label->set_size_request(150, -1);
2656 $label->set_alignment(1, 0.5);
2657 $hbox2->pack_start($label, 0, 0, 10);
2658 my $pwe2 = Gtk3::Entry->new();
2659 $pwe2->set_visibility(0);
201a5120 2660 $pwe2->set_text($password) if $password;
71590b6a
OB
2661 $pwe2->set_size_request(200, -1);
2662 $hbox2->pack_start($pwe2, 0, 0, 0);
2663
2664 my $hbox3 = Gtk3::HBox->new(0, 0);
b11c55ff 2665 $label = Gtk3::Label->new("Email");
71590b6a
OB
2666 $label->set_size_request(150, -1);
2667 $label->set_alignment(1, 0.5);
2668 $hbox3->pack_start($label, 0, 0, 10);
2669 my $eme = Gtk3::Entry->new();
2670 $eme->set_size_request(200, -1);
201a5120 2671 $eme->set_text($mailto);
71590b6a 2672 $hbox3->pack_start($eme, 0, 0, 0);
89a12446
DM
2673
2674
71590b6a
OB
2675 $vbox->pack_start($hbox1, 0, 0, 5);
2676 $vbox->pack_start($hbox2, 0, 0, 5);
2677 $vbox->pack_start($hbox3, 0, 0, 15);
89a12446
DM
2678
2679 $inbox->show_all;
2680
201a5120 2681 display_html();
89a12446
DM
2682
2683 set_next (undef, sub {
2684
2685 my $t1 = $pwe1->get_text;
2686 my $t2 = $pwe2->get_text;
2687
2688 if (length ($t1) < 5) {
71590b6a 2689 display_message("Password is too short.");
89a12446
DM
2690 $pwe1->grab_focus();
2691 return;
2692 }
2693
2694 if ($t1 ne $t2) {
71590b6a 2695 display_message("Password does not match.");
89a12446
DM
2696 $pwe1->grab_focus();
2697 return;
2698 }
2699
2700 my $t3 = $eme->get_text;
c82fffd8 2701 if ($t3 !~ m/^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
b11c55ff 2702 display_message("Email does not look like a valid address" .
89a12446
DM
2703 " (user\@domain.tld)");
2704 $eme->grab_focus();
2705 return;
a39bc1f2 2706 }
89a12446 2707
a39bc1f2 2708 if ($t3 eq 'mail@example.invalid') {
b11c55ff 2709 display_message("Please enter a valid Email address");
a39bc1f2
FG
2710 $eme->grab_focus();
2711 return;
89a12446
DM
2712 }
2713
2714 $password = $t1;
2715 $mailto = $t3;
2716
201a5120 2717 $step_number++;
89a12446
DM
2718 create_ipconf_view();
2719 });
2720
2721 $pwe1->grab_focus();
2722
2723}
2724
d92ada4e 2725my $installer_kmap;
89a12446
DM
2726sub create_country_view {
2727
71590b6a 2728 cleanup_view();
89a12446
DM
2729
2730 my $countryhash = $cmap->{countryhash};
2731 my $ctr = $cmap->{country};
2732
71590b6a
OB
2733 my $vbox2 = Gtk3::VBox->new(0, 0);
2734 $inbox->pack_start($vbox2, 1, 0, 0);
2735 my $vbox = Gtk3::VBox->new(0, 0);
2736 $vbox2->pack_start($vbox, 0, 0, 10);
89a12446 2737
71590b6a
OB
2738 my $w = Gtk3::Entry->new();
2739 $w->set_size_request(200, -1);
89a12446 2740
71590b6a
OB
2741 my $c = Gtk3::EntryCompletion->new();
2742 $c->set_text_column(0);
89a12446 2743 $c->set_minimum_key_length(0);
71590b6a
OB
2744 $c->set_popup_set_width(1);
2745 $c->set_inline_completion(1);
2746
2747 my $hbox2 = Gtk3::HBox->new(0, 0);
2748 my $label = Gtk3::Label->new("Time zone");
2749 $label->set_size_request(150, -1);
2750 $label->set_alignment(1, 0.5);
2751 $hbox2->pack_start($label, 0, 0, 10);
89a12446
DM
2752 update_zonelist ($hbox2);
2753
71590b6a
OB
2754 my $hbox3 = Gtk3::HBox->new(0, 0);
2755 $label = Gtk3::Label->new("Keyboard Layout");
2756 $label->set_size_request(150, -1);
2757 $label->set_alignment(1, 0.5);
2758 $hbox3->pack_start($label, 0, 0, 10);
89a12446 2759
bcbfab6b 2760 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2761 $kmapcb->set_size_request (200, -1);
2762 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2763 $kmapcb->append_text ($layout);
2764 }
2765
71590b6a 2766 update_layout($kmapcb);
89a12446
DM
2767 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2768
2769 $kmapcb->signal_connect ('changed' => sub {
2770 my $sel = $kmapcb->get_active_text();
d92ada4e 2771 $last_layout = $kmapcb->get_active();
89a12446
DM
2772 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2773 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2774 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
89a12446 2775 $keymap = $kmap;
07ec6825 2776
d92ada4e
SI
2777 return if (defined($installer_kmap) && $installer_kmap eq $kmap);
2778
2779 $installer_kmap = $keymap;
2780
07ec6825
SI
2781 if (! $opt_testmode) {
2782 syscmd ("setxkbmap $xkmap $xvar");
2663e171
SI
2783
2784 my $kbd_config = qq{
2785 XKBLAYOUT="$xkmap"
2786 XKBVARIANT="$xvar"
2787 BACKSPACE="guess"
2788 };
2789 $kbd_config =~ s/^\s+//gm;
2790
2791 run_in_background( sub {
2792 write_config($kbd_config, '/etc/default/keyboard');
2793 system("setupcon");
2794 });
07ec6825 2795 }
89a12446
DM
2796 }
2797 });
2798
2799 $w->signal_connect ('changed' => sub {
2800 my ($entry, $event) = @_;
2801 my $text = $entry->get_text;
2802
2803 if (my $cc = $countryhash->{lc($text)}) {
71590b6a 2804 update_zonelist($hbox2, $cc);
89a12446 2805 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
71590b6a 2806 update_layout($kmapcb, $kmap);
89a12446
DM
2807 }
2808 });
2809
2810 $w->signal_connect (key_press_event => sub {
2811 my ($entry, $event) = @_;
2812 my $text = $entry->get_text;
2813
7becc472
DM
2814 my $val = $event->get_keyval;
2815
2816 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2817 my $cc = $countryhash->{lc($text)};
1464c7c9 2818
89a12446
DM
2819 my $found = 0;
2820 my $compl;
7becc472 2821
4443aa27
DM
2822 if ($cc) {
2823 $found = 1;
2824 $compl = $ctr->{$cc}->{name};
2825 } else {
2826 foreach my $cc (keys %$ctr) {
2827 my $ct = $ctr->{$cc}->{name};
2828 if ($ct =~ m/^\Q$text\E.*$/i) {
2829 $found++;
2830 $compl = $ct;
2831 }
2832 last if $found > 1;
89a12446 2833 }
89a12446 2834 }
4443aa27 2835
89a12446 2836 if ($found == 1) {
7becc472 2837 $entry->set_text($compl);
3df718ea 2838 $c->complete();
89a12446
DM
2839 return undef;
2840 } else {
7becc472
DM
2841 #Gtk3::Gdk::beep();
2842 print chr(7); # beep ?
89a12446
DM
2843 }
2844
3df718ea
DM
2845 $c->complete();
2846
7becc472
DM
2847 my $buf = $w->get_buffer();
2848 $buf->insert_text(-1, '', -1); # popup selection
2849
89a12446
DM
2850 return 1;
2851 }
2852
2853 return undef;
2854 });
1464c7c9 2855
7becc472 2856 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2857 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2858 my $iter = $ls->append();
2859 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2860 }
2861 $c->set_model ($ls);
2862
968fa90b 2863 $w->set_completion ($c);
89a12446 2864
71590b6a 2865 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 2866
71590b6a
OB
2867 $label = Gtk3::Label->new("Country");
2868 $label->set_alignment(1, 0.5);
2869 $label->set_size_request(150, -1);
2870 $hbox->pack_start($label, 0, 0, 10);
2871 $hbox->pack_start($w, 0, 0, 0);
89a12446 2872
71590b6a
OB
2873 $vbox->pack_start($hbox, 0, 0, 5);
2874 $vbox->pack_start($hbox2, 0, 0, 5);
2875 $vbox->pack_start($hbox3, 0, 0, 5);
89a12446 2876
9d1f1ee3 2877 if ($country && $ctr->{$country}) {
89a12446
DM
2878 $w->set_text ($ctr->{$country}->{name});
2879 }
2880
2881 $inbox->show_all;
2882
201a5120 2883 display_html();
89a12446
DM
2884 set_next (undef, sub {
2885
2886 my $text = $w->get_text;
2887
2888 if (my $cc = $countryhash->{lc($text)}) {
2889 $country = $cc;
201a5120 2890 $step_number++;
89a12446
DM
2891 create_password_view();
2892 return;
2893 } else {
71590b6a 2894 display_message("Please select a country first.");
89a12446
DM
2895 $w->grab_focus();
2896 }
2897 });
2898
2899 $w->grab_focus();
2900}
2901
c6ed3b24
DM
2902my $target_hd_combo;
2903my $target_hd_label;
2904
bd3a2e26 2905my $hdoption_first_setup = 1;
c6ed3b24 2906
c7779156
FG
2907my $create_basic_grid = sub {
2908 my $grid = Gtk3::Grid->new();
2909 $grid->set_visible(1);
2910 $grid->set_column_spacing(10);
2911 $grid->set_row_spacing(10);
2912 $grid->set_hexpand(1);
2913
6d8b8564
TL
2914 $grid->set_margin_start(10);
2915 $grid->set_margin_end(20);
c7779156
FG
2916 $grid->set_margin_top(5);
2917 $grid->set_margin_bottom(5);
2918
2919 return $grid;
2920};
2921
2922my $create_label_widget_grid = sub {
2923 my ($labeled_widgets) = @_;
2924
2925 my $grid = &$create_basic_grid();
2926 my $row = 0;
2927
2928 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2929 my $widget = @$labeled_widgets[$i+1];
2930 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2931 $label->set_visible(1);
2932 $label->set_alignment (1, 0.5);
2933 $grid->attach($label, 0, $row, 1, 1);
2934 $widget->set_visible(1);
2935 $grid->attach($widget, 1, $row, 1, 1);
2936 $row++;
2937 }
2938
2939 return $grid;
2940};
2941
2942my $create_raid_disk_grid = sub {
04fa0758 2943 my $hd_count = scalar(@$hds);
c7779156 2944 my $disk_labeled_widgets = [];
04fa0758 2945 for (my $i = 0; $i < $hd_count; $i++) {
c7779156
FG
2946 my $disk_selector = Gtk3::ComboBoxText->new();
2947 $disk_selector->append_text("-- do not use --");
2948 $disk_selector->set_active(0);
2949 $disk_selector->set_visible(1);
2950 foreach my $hd (@$hds) {
17fd908e 2951 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
c7779156
FG
2952 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2953 $disk_selector->{pve_disk_id} = $i;
2954 $disk_selector->signal_connect (changed => sub {
2955 my $w = shift;
2956 my $diskid = $w->{pve_disk_id};
2957 my $a = $w->get_active - 1;
2958 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2959 });
2960 }
2961
bd3a2e26 2962 if ($hdoption_first_setup) {
c7779156
FG
2963 $disk_selector->set_active ($i+1) if $hds->[$i];
2964 } else {
2965 my $hdind = 0;
2966 if (my $cur_hd = $config_options->{"disksel$i"}) {
2967 foreach my $hd (@$hds) {
2968 if (@$hd[1] eq @$cur_hd[1]) {
2969 $disk_selector->set_active($hdind+1);
2970 last;
2971 }
2972 $hdind++;
2973 }
2974 }
2975 }
2976
2977 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2978 }
2979
3235f39b
TL
2980 my $clear_all_button = Gtk3::Button->new('_Deselect All');
2981 if ($hd_count > 3) {
2982 $clear_all_button->signal_connect('clicked', sub {
2983 my $is_widget = 0;
2984 for my $disk_selector (@$disk_labeled_widgets) {
2985 $disk_selector->set_active(0) if $is_widget;
2986 $is_widget ^= 1;
2987 }
2988 });
2989 $clear_all_button->set_visible(1);
2990 }
2991
c7779156
FG
2992 my $scrolled_window = Gtk3::ScrolledWindow->new();
2993 $scrolled_window->set_hexpand(1);
04fa0758 2994 $scrolled_window->set_propagate_natural_height(1) if $hd_count > 4;
3235f39b
TL
2995
2996 my $diskgrid = $create_label_widget_grid->($disk_labeled_widgets);
2997
2998 $scrolled_window->add($diskgrid);
c7779156 2999 $scrolled_window->set_policy('never', 'automatic');
3235f39b 3000 $scrolled_window->set_visible(1);
0bc39c50 3001 $scrolled_window->set_min_content_height(190);
3235f39b
TL
3002
3003 my $vbox = Gtk3::Box->new('vertical', 0);
3004 $vbox->pack_start($scrolled_window, 1, 1, 10);
3005
3006 my $hbox = Gtk3::Box->new('horizontal', 0);
3007 $hbox->pack_end($clear_all_button, 0, 0, 20);
3008 $hbox->set_visible(1);
3009 $vbox->pack_end($hbox, 0, 0, 0);
c7779156 3010
3235f39b 3011 return $vbox;
c7779156
FG
3012};
3013
d7fe65ff
TL
3014# shared between different ui parts (e.g., ZFS and "normal" single disk FS)
3015my $hdsize_size_adj;
3016my $hdsize_entry_buffer;
3017
3018my $get_hdsize_spinbtn = sub {
3019 my $hdsize = shift;
754abb44 3020
93c8fdb0
SI
3021 $hdsize_entry_buffer //= Gtk3::EntryBuffer->new(undef, 1);
3022
3023 if (defined($hdsize)) {
d7fe65ff 3024 $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
93c8fdb0
SI
3025 } else {
3026 die "called get_hdsize_spinbtn with \$hdsize_size_adj not defined but did not pass hdsize!\n"
3027 if !defined($hdsize_size_adj);
d7fe65ff
TL
3028 }
3029
3030 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
3031 $spinbutton_hdsize->set_buffer($hdsize_entry_buffer);
6039e2f1 3032 $spinbutton_hdsize->set_adjustment($hdsize_size_adj);
d7fe65ff
TL
3033 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
3034 return $spinbutton_hdsize;
3035};
3036
c7779156
FG
3037my $create_raid_advanced_grid = sub {
3038 my $labeled_widgets = [];
2cdba397 3039 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9, 13, 1);
6c99667a
FG
3040 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
3041 $spinbutton_ashift->signal_connect ("value-changed" => sub {
3042 my $w = shift;
3043 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
3044 });
3045 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 3046 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 3047 push @$labeled_widgets, "ashift";
6c99667a 3048 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
3049
3050 my $combo_compress = Gtk3::ComboBoxText->new();
3051 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
3052 # note: gzip / lze not allowed for bootfs vdevs
3053 my $comp_opts = ["on","off","lzjb","lz4"];
3054 foreach my $opt (@$comp_opts) {
3055 $combo_compress->append($opt, $opt);
3056 }
3057 $config_options->{compress} = "on" if !defined($config_options->{compress});
3058 $combo_compress->set_active_id($config_options->{compress});
3059 $combo_compress->signal_connect (changed => sub {
3060 my $w = shift;
3061 $config_options->{compress} = $w->get_active_text();
3062 });
3063 push @$labeled_widgets, "compress";
3064 push @$labeled_widgets, $combo_compress;
3065
3066 my $combo_checksum = Gtk3::ComboBoxText->new();
3067 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
3068 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
3069 foreach my $opt (@$csum_opts) {
3070 $combo_checksum->append($opt, $opt);
3071 }
3072 $config_options->{checksum} = "on" if !($config_options->{checksum});
3073 $combo_checksum->set_active_id($config_options->{checksum});
3074 $combo_checksum->signal_connect (changed => sub {
3075 my $w = shift;
3076 $config_options->{checksum} = $w->get_active_text();
3077 });
3078 push @$labeled_widgets, "checksum";
3079 push @$labeled_widgets, $combo_checksum;
3080
3081 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
3082 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
3083 $spinbutton_copies->signal_connect ("value-changed" => sub {
3084 my $w = shift;
3085 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
3086 });
3087 $config_options->{copies} = 1 if !defined($config_options->{copies});
3088 $spinbutton_copies->set_value($config_options->{copies});
3089 push @$labeled_widgets, "copies", $spinbutton_copies;
3090
d7fe65ff 3091 push @$labeled_widgets, "hdsize", $get_hdsize_spinbtn->();
2cdba397 3092 return $create_label_widget_grid->($labeled_widgets);;
c7779156
FG
3093};
3094
aed81ff0
DM
3095sub create_hdoption_view {
3096
3097 my $dialog = Gtk3::Dialog->new();
3098
3099 $dialog->set_title("Harddisk options");
3100
3101 $dialog->add_button("_OK", 1);
3102
3103 my $contarea = $dialog->get_content_area();
3104
3105 my $hbox2 = Gtk3::Box->new('horizontal', 0);
6d8b8564 3106 $contarea->pack_start($hbox2, 1, 1, 5);
aed81ff0
DM
3107
3108 my $grid = Gtk3::Grid->new();
3109 $grid->set_column_spacing(10);
3110 $grid->set_row_spacing(10);
1464c7c9 3111
6d8b8564 3112 $hbox2->pack_start($grid, 1, 0, 5);
c6ed3b24
DM
3113
3114 my $row = 0;
3115
aed81ff0 3116 # Filesystem type
71590b6a 3117 my $label0 = Gtk3::Label->new("Filesystem");
aed81ff0 3118 $label0->set_alignment (1, 0.5);
c6ed3b24 3119 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 3120
bcbfab6b 3121 my $fstypecb = Gtk3::ComboBoxText->new();
2cdba397
TL
3122 my $fstype = [
3123 'ext4',
3124 'xfs',
3125 'zfs (RAID0)',
3126 'zfs (RAID1)',
3127 'zfs (RAID10)',
3128 'zfs (RAIDZ-1)',
3129 'zfs (RAIDZ-2)',
3130 'zfs (RAIDZ-3)',
3131 ];
6f52fc3d 3132 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 3133 if $setup->{enable_btrfs};
aed81ff0 3134
c6ed3b24
DM
3135 my $tcount = 0;
3136 foreach my $tmp (@$fstype) {
3137 $fstypecb->append_text($tmp);
2cdba397 3138 $fstypecb->set_active ($tcount) if $config_options->{filesys} eq $tmp;
c6ed3b24
DM
3139 $tcount++;
3140 }
3141
3142 $grid->attach($fstypecb, 1, $row, 1, 1);
3143
3144 $hbox2->show_all();
3145
3146 $row++;
3147
c7779156
FG
3148 my $sep = Gtk3::HSeparator->new();
3149 $sep->set_visible(1);
3150 $grid->attach($sep, 0, $row, 2, 1);
3151 $row++;
aed81ff0 3152
8f38d1a1
TL
3153 my $hw_raid_note = Gtk3::Label->new(
3154 "Note: ZFS is not compatible with hardware RAID controllers, for details see the documentation."
3155 );
f0a0d90b
TL
3156 $hw_raid_note->set_line_wrap(1);
3157 $hw_raid_note->set_max_width_chars(30);
f0a0d90b
TL
3158 $hw_raid_note->set_visible(0);
3159 $grid->attach($hw_raid_note, 0, $row++, 2, 1);
3160
c7779156 3161 my $hdsize_labeled_widgets = [];
aed81ff0 3162
c7779156 3163 # size compute
c6ed3b24 3164 my $hdsize = 0;
aed81ff0
DM
3165 if ( -b $target_hd) {
3166 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
c6ed3b24 3167 } elsif ($target_hd) {
aed81ff0
DM
3168 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
3169 }
3170
d7fe65ff 3171 my $spinbutton_hdsize = $get_hdsize_spinbtn->($hdsize);
c7779156 3172 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
aed81ff0
DM
3173
3174 my $entry_swapsize = Gtk3::Entry->new();
3175 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
3176 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 3177 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 3178 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
3179
3180 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
3181 if ($setup->{product} eq 'pve') {
3182 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
3183 $entry_maxroot->signal_connect (key_press_event => \&check_float);
3184 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
3185 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
3186 }
aed81ff0
DM
3187
3188 my $entry_minfree = Gtk3::Entry->new();
034f75e4 3189 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0 3190 $entry_minfree->signal_connect (key_press_event => \&check_float);
e093944c 3191 $entry_minfree->set_text($config_options->{minfree}) if defined($config_options->{minfree});
c7779156 3192 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 3193
b6e875ca
DM
3194 my $entry_maxvz;
3195 if ($setup->{product} eq 'pve') {
3196 $entry_maxvz = Gtk3::Entry->new();
3197 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
3198 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 3199 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
3200 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
3201 }
c7779156
FG
3202
3203 my $options_stack = Gtk3::Stack->new();
3204 $options_stack->set_visible(1);
3205 $options_stack->set_hexpand(1);
3206 $options_stack->set_vexpand(1);
3207 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
3208 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
3209 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
3210 $options_stack->set_visible_child_name("raiddisk");
3211 my $options_stack_switcher = Gtk3::StackSwitcher->new();
3212 $options_stack_switcher->set_halign('center');
3213 $options_stack_switcher->set_stack($options_stack);
3214 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
3215 $row++;
3216 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 3217 $row++;
aed81ff0 3218
bd3a2e26 3219 $hdoption_first_setup = 0;
c7779156
FG
3220
3221 my $switch_view = sub {
3222 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3223 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 3224
c7779156
FG
3225 $target_hd_combo->set_visible(!$raid);
3226 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
3227 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
f0a0d90b 3228 $hw_raid_note->set_visible($raid);
c7779156
FG
3229 $options_stack_switcher->set_visible($enable_zfs_opts);
3230 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
3231 if ($raid) {
c6ed3b24 3232 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 3233 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 3234 } else {
c6ed3b24
DM
3235 $target_hd_label->set_text("Target Harddisk: ");
3236 }
c7779156
FG
3237 my (undef, $pref_width) = $dialog->get_preferred_width();
3238 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 3239 $pref_height = 750 if $pref_height > 750;
c7779156 3240 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
3241 };
3242
c7779156 3243 &$switch_view();
f7b853d1
DM
3244
3245 $fstypecb->signal_connect (changed => sub {
3246 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 3247 &$switch_view();
f7b853d1
DM
3248 });
3249
95844cc6
TL
3250 my $sep2 = Gtk3::HSeparator->new();
3251 $sep2->set_visible(1);
3252 $contarea->pack_end($sep2, 1, 1, 10);
3253
c6ed3b24 3254 $dialog->show();
aed81ff0
DM
3255
3256 $dialog->run();
3257
3258 my $get_float = sub {
3259 my ($entry) = @_;
3260
3261 my $text = $entry->get_text();
3262 return undef if !defined($text);
3263
3264 $text =~ s/^\s+//;
3265 $text =~ s/\s+$//;
3266
3267 return undef if $text !~ m/^\d+(\.\d+)?$/;
3268
3269 return $text;
3270 };
3271
3272 my $tmp;
3273
3274 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
3275 $config_options->{hdsize} = $tmp;
3276 } else {
3277 delete $config_options->{hdsize};
3278 }
3279
3280 if (defined($tmp = &$get_float($entry_swapsize))) {
3281 $config_options->{swapsize} = $tmp;
3282 } else {
3283 delete $config_options->{swapsize};
3284 }
3285
3286 if (defined($tmp = &$get_float($entry_maxroot))) {
3287 $config_options->{maxroot} = $tmp;
3288 } else {
3289 delete $config_options->{maxroot};
3290 }
3291
3292 if (defined($tmp = &$get_float($entry_minfree))) {
3293 $config_options->{minfree} = $tmp;
3294 } else {
3295 delete $config_options->{minfree};
3296 }
3297
b6e875ca 3298 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
3299 $config_options->{maxvz} = $tmp;
3300 } else {
3301 delete $config_options->{maxvz};
3302 }
3303
3304 $dialog->destroy();
3305}
3306
121ebc59 3307my $get_raid_devlist = sub {
c6ed3b24
DM
3308
3309 my $dev_name_hash = {};
3310
3311 my $devlist = [];
5f8e86d5 3312 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24 3313 if (my $hd = $config_options->{"disksel$i"}) {
17fd908e 3314 my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
1464c7c9 3315 die "device '$devname' is used more than once\n"
c6ed3b24
DM
3316 if $dev_name_hash->{$devname};
3317 $dev_name_hash->{$devname} = $hd;
3318 push @$devlist, $hd;
3319 }
3320 }
3321
121ebc59
DM
3322 return $devlist;
3323};
3324
14aacec8
FG
3325sub zfs_mirror_size_check {
3326 my ($expected, $actual) = @_;
3327
3328 die "mirrored disks must have same size\n"
3329 if abs($expected - $actual) > $expected / 10;
3330}
3331
5ea943cf
SI
3332sub legacy_bios_4k_check {
3333 my ($lbs) = @_;
3334 die "Booting from 4kn drive in legacy BIOS mode is not supported.\n"
3335 if (($boot_type ne 'efi') && ($lbs == 4096));
3336}
3337
121ebc59 3338sub get_zfs_raid_setup {
121ebc59
DM
3339 my $filesys = $config_options->{filesys};
3340
3341 my $devlist = &$get_raid_devlist();
3342
224bb7b0 3343 my $diskcount = scalar(@$devlist);
0cfa502c 3344 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24
DM
3345
3346 my $cmd= '';
3347 if ($filesys eq 'zfs (RAID0)') {
c6ed3b24 3348 foreach my $hd (@$devlist) {
5ea943cf 3349 legacy_bios_4k_check(@$hd[4]);
c6ed3b24
DM
3350 $cmd .= " @$hd[1]";
3351 }
3352 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 3353 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 3354 $cmd .= ' mirror ';
269c66a6 3355 my $hd = @$devlist[0];
14aacec8 3356 my $expected_size = @$hd[2]; # all disks need approximately same size
eaeccd9f 3357 foreach my $hd (@$devlist) {
14aacec8 3358 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3359 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3360 $cmd .= " @$hd[1]";
c6ed3b24
DM
3361 }
3362 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 3363 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 3364 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 3365
224bb7b0 3366 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
3367 my $hd1 = @$devlist[$i];
3368 my $hd2 = @$devlist[$i+1];
14aacec8 3369 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
5ea943cf
SI
3370 legacy_bios_4k_check(@$hd1[4]);
3371 legacy_bios_4k_check(@$hd2[4]);
c6ed3b24
DM
3372 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
3373 }
3374
3375 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3376 my $level = $1;
3377 my $mindisks = 2 + $level;
0cfa502c 3378 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 3379 my $hd = @$devlist[0];
14aacec8 3380 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 3381 $cmd .= " raidz$level";
eaeccd9f 3382 foreach my $hd (@$devlist) {
14aacec8 3383 zfs_mirror_size_check($expected_size, @$hd[2]);
5ea943cf 3384 legacy_bios_4k_check(@$hd[4]);
c6ed3b24 3385 $cmd .= " @$hd[1]";
c6ed3b24
DM
3386 }
3387 } else {
3388 die "unknown zfs mode '$filesys'\n";
3389 }
3390
82695821 3391 return ($devlist, $cmd);
c6ed3b24
DM
3392}
3393
121ebc59
DM
3394sub get_btrfs_raid_setup {
3395
3396 my $filesys = $config_options->{filesys};
3397
3398 my $devlist = &$get_raid_devlist();
3399
3400 my $diskcount = scalar(@$devlist);
0cfa502c 3401 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
3402
3403 my $mode;
3404
3405 if ($diskcount == 1) {
3406 $mode = 'single';
3407 } else {
3408 if ($filesys eq 'btrfs (RAID0)') {
3409 $mode = 'raid0';
3410 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 3411 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
3412 $mode = 'raid1';
3413 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 3414 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
3415 $mode = 'raid10';
3416 } else {
9d69f3d3 3417 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
3418 }
3419 }
3420
3421 return ($devlist, $mode);
3422}
3423
218a4b6b 3424my $last_hd_selected = 0;
89a12446
DM
3425sub create_hdsel_view {
3426
451b1da5 3427 $prev_btn->set_sensitive(1); # enable previous button at this point
201a5120 3428
71590b6a 3429 cleanup_view();
89a12446 3430
71590b6a
OB
3431 my $vbox = Gtk3::VBox->new(0, 0);
3432 $inbox->pack_start($vbox, 1, 0, 0);
3433 my $hbox = Gtk3::HBox->new(0, 0);
3434 $vbox->pack_start($hbox, 0, 0, 10);
968fa90b 3435
17fd908e 3436 my ($disk, $devname, $size, $model, $logical_bsize) = @{@$hds[0]};
9227a70f 3437 $target_hd = $devname if !defined($target_hd);
89a12446 3438
71590b6a
OB
3439 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3440 $hbox->pack_start($target_hd_label, 0, 0, 0);
89a12446 3441
bcbfab6b 3442 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 3443
1aa5bd02 3444 foreach my $hd (@$hds) {
17fd908e 3445 ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
71590b6a 3446 $target_hd_combo->append_text (get_device_desc($devname, $size, $model));
1aa5bd02 3447 }
89a12446 3448
90af1603
OB
3449 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3450 if ($raid) {
3451 $target_hd_label->set_text("Target: $config_options->{filesys} ");
3452 $target_hd_combo->set_visible(0);
3453 $target_hd_combo->set_no_show_all(1);
3454 }
218a4b6b 3455 $target_hd_combo->set_active($last_hd_selected);
71590b6a 3456 $target_hd_combo->signal_connect(changed => sub {
1aa5bd02
DM
3457 $a = shift->get_active;
3458 my ($disk, $devname) = @{@$hds[$a]};
3b959bef 3459 $last_hd_selected = $a;
1aa5bd02 3460 $target_hd = $devname;
1aa5bd02 3461 });
1464c7c9 3462
71590b6a 3463 $hbox->pack_start($target_hd_combo, 0, 0, 10);
aed81ff0 3464
71590b6a 3465 my $options = Gtk3::Button->new('_Options');
aed81ff0
DM
3466 $options->signal_connect (clicked => \&create_hdoption_view);
3467 $hbox->pack_start ($options, 0, 0, 0);
3468
89a12446
DM
3469
3470 $inbox->show_all;
3471
201a5120 3472 display_html();
c6ed3b24 3473
71590b6a 3474 set_next(undef, sub {
c6ed3b24
DM
3475
3476 if ($config_options->{filesys} =~ m/zfs/) {
a7d40341 3477 my ($devlist) = eval { get_zfs_raid_setup() };
c6ed3b24 3478 if (my $err = $@) {
303dfb2c
TL
3479 display_message("Warning: $err\nPlease fix ZFS setup first.");
3480 return;
c6ed3b24 3481 }
303dfb2c 3482 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
121ebc59 3483 } elsif ($config_options->{filesys} =~ m/btrfs/) {
a7d40341 3484 my ($devlist) = eval { get_btrfs_raid_setup() };
121ebc59 3485 if (my $err = $@) {
303dfb2c
TL
3486 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3487 return;
121ebc59 3488 }
303dfb2c 3489 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
c6ed3b24 3490 } else {
5ea943cf
SI
3491 eval { legacy_bios_4k_check(logical_blocksize($target_hd)) };
3492 if (my $err = $@) {
3493 display_message("Warning: $err\n");
3494 return;
3495 }
a7d40341 3496 $config_options->{target_hds} = [ $target_hd ];
c6ed3b24 3497 }
303dfb2c
TL
3498
3499 $step_number++;
3500 create_country_view();
c6ed3b24 3501 });
89a12446
DM
3502}
3503
3504sub create_extract_view {
3505
71590b6a 3506 cleanup_view();
89a12446 3507
550958aa
DM
3508 display_info();
3509
201a5120 3510 $next->set_sensitive(0);
ac3ee85b
TL
3511 $prev_btn->set_sensitive(0);
3512 $prev_btn->hide();
89a12446 3513
71590b6a 3514 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 3515 $inbox->pack_start ($vbox, 1, 0, 0);
71590b6a 3516 my $hbox = Gtk3::HBox->new(0, 0);
53986d77 3517 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3518
71590b6a 3519 my $vbox2 = Gtk3::VBox->new(0, 0);
89a12446
DM
3520 $hbox->pack_start ($vbox2, 0, 0, 0);
3521
71590b6a 3522 $progress_status = Gtk3::Label->new('');
89a12446 3523 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3524
7becc472 3525 $progress = Gtk3::ProgressBar->new;
45feca6f 3526 $progress->set_show_text(1);
7becc472 3527 $progress->set_size_request (600, -1);
89a12446 3528
71590b6a 3529 $vbox2->pack_start($progress, 0, 0, 0);
89a12446 3530
201a5120 3531 $inbox->show_all();
89a12446
DM
3532
3533 my $tdir = $opt_testmode ? "target" : "/target";
3534 mkdir $tdir;
97980bf2 3535 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446 3536
71590b6a 3537 eval { extract_data($base, $tdir); };
89a12446
DM
3538 my $err = $@;
3539
201a5120 3540 $next->set_sensitive(1);
89a12446 3541
71590b6a 3542 set_next("_Reboot", sub { exit (0); } );
89a12446 3543
296cf41f 3544 if ($err) {
201a5120
OB
3545 display_html("fail.htm");
3546 display_error($err);
296cf41f 3547 } else {
201a5120
OB
3548 cleanup_view();
3549 display_html("success.htm");
dfc02f3c
TL
3550
3551 if ($config_options->{autoreboot}) {
3552 Glib::Timeout->add(1000, sub {
3553 if ($autoreboot_seconds > 0) {
3554 $autoreboot_seconds--;
3555 display_html("success.htm");
3556 } else {
3557 exit(0);
3558 }
3559 });
3560 }
296cf41f 3561 }
89a12446
DM
3562}
3563
89a12446
DM
3564sub create_intro_view {
3565
451b1da5 3566 $prev_btn->set_sensitive(0);
201a5120
OB
3567
3568 cleanup_view();
89a12446 3569
ca951e77 3570 if (int($total_memory) < 1024) {
3befbf97 3571 display_error("Less than 1 GiB of usable memory detected, installation will probably fail.\n\n".
c2f72dd6 3572 "See 'System Requirements' in the $setup->{fullname} documentation.");
2b85ee1b
OB
3573 }
3574
bdeca872
DM
3575 if ($setup->{product} eq 'pve') {
3576 eval {
3577 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3578 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
2780ea4f 3579 display_error("No support for KVM virtualization detected.\n\n" .
bdeca872
DM
3580 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3581 }
3582 };
3583 }
7fff0d85 3584
201a5120 3585 display_html();
89a12446 3586
201a5120 3587 $step_number++;
71590b6a 3588 set_next("I a_gree", \&create_hdsel_view);
89a12446
DM
3589}
3590
71590b6a 3591$ipconf = get_ip_config();
89a12446 3592
9d1f1ee3 3593$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3594
3595# read country, kmap and timezone infos
71590b6a 3596$cmap = read_cmap();
89a12446 3597
9d1f1ee3
FG
3598if (!defined($cmap->{country}->{$country})) {
3599 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3600 $country = undef;
3601}
3602
89a12446
DM
3603create_main_window ();
3604
ff2ce71c
FG
3605my $initial_error = 0;
3606
89a12446
DM
3607if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3608 print "no hardisks found\n";
ff2ce71c 3609 $initial_error = 1;
201a5120 3610 display_html("nohds.htm");
71590b6a 3611 set_next("Reboot", sub { exit(0); } );
89a12446 3612} else {
89a12446
DM
3613 foreach my $hd (@$hds) {
3614 my ($disk, $devname) = @$hd;
3615 next if $devname =~ m|^/dev/md\d+$|;
3616 print "found Disk$disk N:$devname\n";
3617 }
89a12446
DM
3618}
3619
72836708
FG
3620if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3621 print "no network interfaces found\n";
3622 $initial_error = 1;
201a5120 3623 display_html("nonics.htm");
71590b6a 3624 set_next("Reboot", sub { exit(0); } );
72836708
FG
3625}
3626
ff2ce71c
FG
3627create_intro_view () if !$initial_error;
3628
7becc472 3629Gtk3->main;
89a12446 3630
0e631479
SI
3631# reap left over zombie processes
3632while ((my $child = waitpid(-1, POSIX::WNOHANG)) > 0) {
3633 print "reaped child $child\n";
3634}
3635
89a12446 3636exit 0;