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