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