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