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