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