]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
depreacate grub_platform variable, replaced by $boot_type
[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");
1097 syscmd("chroot $targetdir /etc/kernel/postinst.d/zz-pve-efiboot") == 0 ||
1098 die "unable to generate systemd-boot config\n";
1099
1100 syscmd("umount $targetdir/$espmp") == 0 ||
1101 die "unable to umount ESP $targetdir/$espmp\n";
1102
1103}
121ebc59 1104
c6ed3b24 1105sub extract_data {
fafc616c 1106 my ($basefile, $targetdir) = @_;
89a12446 1107
c6ed3b24 1108 die "target '$targetdir' does not exist\n" if ! -d $targetdir;
89a12446 1109
121ebc59
DM
1110 my $starttime = [Time::HiRes::gettimeofday];
1111
c6ed3b24 1112 my $bootdevinfo = [];
84761f93 1113
c6ed3b24
DM
1114 my $swapfile;
1115 my $rootdev;
e2c51d7c 1116 my $datadev;
84761f93 1117
121ebc59
DM
1118 my $use_zfs = 0;
1119 my $use_btrfs = 0;
89092156 1120
c6ed3b24 1121 my $filesys = $config_options->{filesys};
89092156 1122
c6ed3b24
DM
1123 if ($filesys =~ m/zfs/) {
1124 $target_hd = undef; # do not use this config
1125 $use_zfs = 1;
5772392c 1126 $targetdir = "/$zfspoolname/ROOT/$zfsrootvolname";
121ebc59
DM
1127 } elsif ($filesys =~ m/btrfs/) {
1128 $target_hd = undef; # do not use this config
1129 $use_btrfs = 1;
c6ed3b24 1130 }
1464c7c9 1131
c6ed3b24
DM
1132 if ($use_zfs) {
1133 my $i;
1134 for ($i = 5; $i > 0; $i--) {
1135 syscmd("modprobe zfs");
1136 last if -c "/dev/zfs";
1137 sleep(1);
1138 }
89092156 1139
c6ed3b24
DM
1140 die "unable to load zfs kernel module\n" if !$i;
1141 }
89092156 1142
c6ed3b24 1143 eval {
89a12446 1144
89a12446 1145
c6ed3b24 1146 my $maxper = 0.25;
89a12446 1147
71590b6a 1148 update_progress(0, 0, $maxper, "create partitions");
c6ed3b24 1149
857c43a9
FG
1150 syscmd("vgchange -an") if !$opt_testmode; # deactivate all detected VGs
1151
c6ed3b24 1152 if ($opt_testmode) {
89a12446 1153
6b900321
DM
1154 $rootdev = abs_path($opt_testmode);
1155 syscmd("umount $rootdev");
121ebc59 1156
6b900321 1157 if ($use_btrfs) {
121ebc59 1158
1464c7c9 1159 die "unsupported btrfs mode (for testing environment)\n"
121ebc59
DM
1160 if $filesys ne 'btrfs (RAID0)';
1161
1162 btrfs_create([$rootdev], 'single');
5c06ced5 1163
121ebc59 1164 } elsif ($use_zfs) {
5c06ced5 1165
121ebc59 1166 die "unsupported zfs mode (for testing environment)\n"
c6ed3b24
DM
1167 if $filesys ne 'zfs (RAID0)';
1168
71590b6a 1169 syscmd("zpool destroy $zfstestpool");
5c06ced5 1170
5fd81672 1171 zfs_create_rpool($rootdev);
1464c7c9 1172
121ebc59
DM
1173 } else {
1174
6b900321 1175 # nothing to do
121ebc59
DM
1176 }
1177
1178 } elsif ($use_btrfs) {
1179
1180 my ($devlist, $btrfs_mode) = get_btrfs_raid_setup();
1181 my $btrfs_partitions = [];
1182 my $disksize;
1183 foreach my $hd (@$devlist) {
1184 my $devname = @$hd[1];
857c43a9 1185 &$clean_disk($devname);
121ebc59
DM
1186 my ($size, $osdev, $efidev) =
1187 partition_bootable_disk($devname, undef, '8300');
1188 $rootdev = $osdev if !defined($rootdev); # simply point to first disk
1189 my $by_id = find_stable_path("/dev/disk/by-id", $devname);
1190 push @$bootdevinfo, { esp => $efidev, devname => $devname,
1191 osdev => $osdev, by_id => $by_id };
1192 push @$btrfs_partitions, $osdev;
1193 $disksize = $size;
5c06ced5 1194 }
c6ed3b24 1195
121ebc59
DM
1196 &$udevadm_trigger_block();
1197
1198 btrfs_create($btrfs_partitions, $btrfs_mode);
1199
c6ed3b24
DM
1200 } elsif ($use_zfs) {
1201
c6ed3b24
DM
1202 my ($devlist, $bootdevlist, $vdev) = get_zfs_raid_setup();
1203
1204 my $disksize;
857c43a9
FG
1205 foreach my $hd (@$devlist) {
1206 &$clean_disk(@$hd[1]);
1207 }
c6ed3b24
DM
1208 foreach my $hd (@$bootdevlist) {
1209 my $devname = @$hd[1];
118d4f40 1210
e38884af 1211 my ($size, $osdev, $efidev) =
d6e919d7 1212 partition_bootable_disk($devname, $config_options->{hdsize}, 'BF01');
14aacec8 1213 zfs_mirror_size_check($disksize, $size) if $disksize;
e38884af
SI
1214 push @$bootdevinfo, { esp => $efidev, devname => $devname,
1215 osdev => $osdev};
c6ed3b24 1216 $disksize = $size;
c6ed3b24
DM
1217 }
1218
121ebc59 1219 &$udevadm_trigger_block();
c6ed3b24 1220
35c6f89c
DM
1221 foreach my $di (@$bootdevinfo) {
1222 my $devname = $di->{devname};
1223 $di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
1464c7c9 1224
35c6f89c
DM
1225 # Note: using /dev/disk/by-id/ does not work for unknown reason, we get
1226 # cannot create 'rpool': no such pool or dataset
1227 #my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
c6ed3b24 1228
35c6f89c
DM
1229 my $osdev = $di->{osdev};
1230 $vdev =~ s/ $devname/ $osdev/;
1231 }
1232
5fd81672 1233 zfs_create_rpool($vdev);
1464c7c9 1234
c6ed3b24
DM
1235 } else {
1236
1237 die "target '$target_hd' is not a valid block device\n" if ! -b $target_hd;
1238
857c43a9
FG
1239 &$clean_disk($target_hd);
1240
1464c7c9
DM
1241 my ($os_size, $osdev, $efidev);
1242 ($os_size, $osdev, $efidev) =
d6e919d7 1243 partition_bootable_disk($target_hd, $config_options->{hdsize}, '8E00');
c6ed3b24 1244
121ebc59 1245 &$udevadm_trigger_block();
c6ed3b24 1246
35c6f89c 1247 my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
1464c7c9 1248 push @$bootdevinfo, { esp => $efidev, devname => $target_hd,
35c6f89c 1249 osdev => $osdev, by_id => $by_id };
c6ed3b24 1250
35c6f89c 1251 my $swap_size = compute_swapsize($os_size);
e2c51d7c 1252 ($rootdev, $swapfile, $datadev) =
35c6f89c 1253 create_lvm_volumes($osdev, $os_size, $swap_size);
c6ed3b24 1254
35c6f89c 1255 # trigger udev to create /dev/disk/by-uuid
121ebc59 1256 &$udevadm_trigger_block(1);
89a12446
DM
1257 }
1258
481671c3
DM
1259 if ($use_zfs) {
1260 # to be fast during installation
71590b6a 1261 syscmd("zfs set sync=disabled $zfspoolname") == 0 ||
481671c3
DM
1262 die "unable to set zfs properties\n";
1263 }
1264
71590b6a 1265 update_progress(0.03, 0, $maxper, "create swap space");
89a12446 1266 if ($swapfile) {
71590b6a 1267 syscmd("mkswap -f $swapfile") == 0 ||
89a12446
DM
1268 die "unable to create swap space\n";
1269 }
1270
71590b6a 1271 update_progress(0.05, 0, $maxper, "creating filesystems");
89a12446 1272
c6ed3b24 1273 foreach my $di (@$bootdevinfo) {
f810f5d0 1274 next if !$di->{esp};
71590b6a 1275 syscmd("mkfs.vfat -F32 $di->{esp}") == 0 ||
c6ed3b24
DM
1276 die "unable to initialize EFI ESP on device $di->{esp}\n";
1277 }
1278
121ebc59
DM
1279 if ($use_zfs) {
1280 # do nothing
1281 } elsif ($use_btrfs) {
1282 # do nothing
1283 } else {
71590b6a 1284 create_filesystem($rootdev, 'root', $filesys, 0.05, $maxper, 0, 1);
89a12446
DM
1285 }
1286
71590b6a 1287 update_progress(1, 0.05, $maxper, "mounting target $rootdev");
89a12446 1288
121ebc59
DM
1289 if ($use_zfs) {
1290 # do nothing
121ebc59 1291 } else {
6e56032e
FG
1292 my $mount_opts = 'noatime';
1293 $mount_opts .= ',nobarrier'
1294 if $use_btrfs || $filesys =~ /^ext\d$/;
1295
1296 syscmd("mount -n $rootdev -o $mount_opts $targetdir") == 0 ||
35c6f89c
DM
1297 die "unable to mount $rootdev\n";
1298 }
89a12446 1299
35c6f89c
DM
1300 mkdir "$targetdir/boot";
1301 mkdir "$targetdir/boot/efi";
89a12446 1302
5fd81672
DM
1303 mkdir "$targetdir/var";
1304 mkdir "$targetdir/var/lib";
121ebc59 1305
f7d18efd
DM
1306 if ($setup->{product} eq 'pve') {
1307 mkdir "$targetdir/var/lib/vz";
1308 mkdir "$targetdir/var/lib/pve";
1309
1310 if ($use_btrfs) {
1311 syscmd("btrfs subvolume create $targetdir/var/lib/pve/local-btrfs") == 0 ||
1312 die "unable to create btrfs subvolume\n";
1313 }
121ebc59 1314 }
89a12446 1315
8d7ddbde
TL
1316 mkdir "$targetdir/mnt";
1317 mkdir "$targetdir/mnt/hostrun";
1318 syscmd("mount --bind /run $targetdir/mnt/hostrun") == 0 ||
1319 die "unable to bindmount run on $targetdir/mnt/hostrun\n";
1320
71590b6a 1321 update_progress(1, 0.05, $maxper, "extracting base system");
89a12446 1322
fafc616c
DM
1323 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
1324 $ino || die "unable to open file '$basefile' - $!\n";
968fa90b 1325
c437cef5
DM
1326 my $files = file_read_firstline("${proxmox_cddir}/proxmox/$setup->{product}-base.cnt") ||
1327 die "unable to read base file count\n";
89a12446
DM
1328
1329 my $per = 0;
1330 my $count = 0;
1331
71590b6a 1332 run_command("unsquashfs -f -dest $targetdir -i $basefile", sub {
89a12446 1333 my $line = shift;
fafc616c 1334 return if $line !~ m/^$targetdir/;
89a12446
DM
1335 $count++;
1336 my $nper = int (($count *100)/$files);
1337 if ($nper != $per) {
1338 $per = $nper;
0f3d1edd 1339 my $frac = $per > 100 ? 1 : $per/100;
71590b6a 1340 update_progress($frac, $maxper, 0.5);
89a12446
DM
1341 }
1342 });
1343
71590b6a 1344 syscmd("mount -n -t tmpfs tmpfs $targetdir/tmp") == 0 ||
89a12446 1345 die "unable to mount tmpfs on $targetdir/tmp\n";
71590b6a 1346 syscmd("mount -n -t proc proc $targetdir/proc") == 0 ||
89a12446 1347 die "unable to mount proc on $targetdir/proc\n";
71590b6a 1348 syscmd("mount -n -t sysfs sysfs $targetdir/sys") == 0 ||
89a12446 1349 die "unable to mount sysfs on $targetdir/sys\n";
8d7ddbde
TL
1350 syscmd("chroot $targetdir mount --bind /mnt/hostrun /run") == 0 ||
1351 die "unable to re-bindmount hostrun on /run in chroot\n";
89a12446 1352
71590b6a 1353 update_progress(1, $maxper, 0.5, "configuring base system");
89a12446
DM
1354
1355 # configure hosts
1356
968fa90b 1357 my $hosts =
89a12446 1358 "127.0.0.1 localhost.localdomain localhost\n" .
57cd2e0f 1359 "$ipaddress $hostname.$domain $hostname\n\n" .
89a12446
DM
1360 "# The following lines are desirable for IPv6 capable hosts\n\n" .
1361 "::1 ip6-localhost ip6-loopback\n" .
1362 "fe00::0 ip6-localnet\n" .
1363 "ff00::0 ip6-mcastprefix\n" .
1364 "ff02::1 ip6-allnodes\n" .
1365 "ff02::2 ip6-allrouters\n" .
1366 "ff02::3 ip6-allhosts\n";
1367
71590b6a 1368 write_config($hosts, "$targetdir/etc/hosts");
89a12446 1369
71590b6a 1370 write_config("$hostname\n", "$targetdir/etc/hostname");
89a12446 1371
71590b6a 1372 syscmd("/bin/hostname $hostname") if !$opt_testmode;
89a12446
DM
1373
1374 # configure interfaces
1375
b6200603
DM
1376 my $ifaces = "auto lo\niface lo inet loopback\n\n";
1377
1378 my $ntype = $ipversion == 4 ? 'inet' : 'inet6';
1379
4a0331ab
DM
1380 my $ethdev = $ipconf->{ifaces}->{$ipconf->{selected}}->{name};
1381
1382 if ($setup->{bridged_network}) {
1383 $ifaces .= "iface $ethdev $ntype manual\n";
1384
1385 $ifaces .=
1386 "\nauto vmbr0\niface vmbr0 $ntype static\n" .
1387 "\taddress $ipaddress\n" .
1388 "\tnetmask $netmask\n" .
1389 "\tgateway $gateway\n" .
1390 "\tbridge_ports $ethdev\n" .
1391 "\tbridge_stp off\n" .
1392 "\tbridge_fd 0\n";
1393 } else {
1394 $ifaces .= "auto $ethdev\n" .
1395 "iface $ethdev $ntype static\n" .
1396 "\taddress $ipaddress\n" .
1397 "\tnetmask $netmask\n" .
1398 "\tgateway $gateway\n";
1399 }
89a12446 1400
fe44bd92
FG
1401 foreach my $iface (sort keys %{$ipconf->{ifaces}}) {
1402 my $name = $ipconf->{ifaces}->{$iface}->{name};
4a0331ab 1403 next if $name eq $ethdev;
fe44bd92
FG
1404
1405 $ifaces .= "\niface $name $ntype manual\n";
1406 }
1407
71590b6a 1408 write_config($ifaces, "$targetdir/etc/network/interfaces");
89a12446
DM
1409
1410 # configure dns
1411
39a0f44b 1412 my $resolvconf = "search $domain\nnameserver $dnsserver\n";
71590b6a 1413 write_config($resolvconf, "$targetdir/etc/resolv.conf");
89a12446 1414
5c06ced5
DM
1415 # configure fstab
1416
1417 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass>\n";
1418
121ebc59
DM
1419 if ($use_zfs) {
1420 # do nothing
1421 } elsif ($use_btrfs) {
1422 my $fsuuid;
1423 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev";
1424 run_command($cmd, sub {
1425 my $line = shift;
1426
1427 if ($line =~ m/^UUID=([A-Fa-f0-9\-]+)$/) {
1428 $fsuuid = $1;
1429 }
1430 });
1431
1432 die "unable to detect FS UUID" if !defined($fsuuid);
1433
1434 $fstab .= "UUID=$fsuuid / btrfs defaults 0 1\n";
1435 } else {
80090926
DM
1436 my $root_mountopt = $fssetup->{$filesys}->{root_mountopt} || 'defaults';
1437 $fstab .= "$rootdev / $filesys ${root_mountopt} 0 1\n";
7bc4f6bd 1438 }
a84ea010
DM
1439
1440 # mount /boot/efi
1441 # Note: this is required by current grub, but really dangerous, because
1442 # vfat does not have journaling, so it triggers manual fsck after each crash
1443 # so we only mount /boot/efi if really required (efi systems).
e691666e 1444 if ($boot_type eq 'efi') {
a84ea010 1445 if (scalar(@$bootdevinfo)) {
f810f5d0 1446 my $di = @$bootdevinfo[0]; # simply use first disk
e38884af 1447 if ($di->{esp} && ! $use_zfs) {
f810f5d0
DM
1448 my $efi_boot_uuid = $di->{esp};
1449 if (my $uuid = find_dev_by_uuid ($di->{esp})) {
1450 $efi_boot_uuid = "UUID=$uuid";
1451 }
1464c7c9 1452
f810f5d0
DM
1453 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1\n";
1454 }
a84ea010 1455 }
84761f93
DM
1456 }
1457
a84ea010 1458
89a12446
DM
1459 $fstab .= "$swapfile none swap sw 0 0\n" if $swapfile;
1460
1461 $fstab .= "proc /proc proc defaults 0 0\n";
1462
71590b6a
OB
1463 write_config($fstab, "$targetdir/etc/fstab");
1464 write_config("", "$targetdir/etc/mtab");
968fa90b 1465
71590b6a 1466 syscmd("cp ${proxmox_libdir}/policy-disable-rc.d " .
968fa90b 1467 "$targetdir/usr/sbin/policy-rc.d") == 0 ||
89a12446 1468 die "unable to copy policy-rc.d\n";
71590b6a 1469 syscmd("cp ${proxmox_libdir}/fake-start-stop-daemon " .
968fa90b 1470 "$targetdir/sbin/") == 0 ||
89a12446
DM
1471 die "unable to copy start-stop-daemon\n";
1472
71590b6a
OB
1473 diversion_add($targetdir, "/sbin/start-stop-daemon", "/sbin/fake-start-stop-daemon");
1474 diversion_add($targetdir, "/usr/sbin/update-grub", "/bin/true");
1475 diversion_add($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
89a12446 1476
71590b6a 1477 syscmd("touch $targetdir/proxmox_install_mode");
89a12446 1478
e35d5efb 1479 my $grub_install_devices_txt = '';
3573c046 1480 foreach my $di (@$bootdevinfo) {
e35d5efb 1481 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt;
ff863262 1482 $grub_install_devices_txt .= $di->{by_id} || $di->{devname};
3573c046
DM
1483 }
1484
b1293fcb
FG
1485 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1486 my $xkmap = $cmap->{kmap}->{$keymap}->{x11} // 'us';
1464c7c9 1487
89a12446
DM
1488 debconfig_set ($targetdir, <<_EOD);
1489locales locales/default_environment_locale select en_US.UTF-8
1490locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1491samba-common samba-common/dhcp boolean false
1492samba-common samba-common/workgroup string WORKGROUP
e953719f 1493postfix postfix/main_mailer_type select No configuration
b1293fcb 1494keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
814f5c39 1495d-i debian-installer/locale select en_US.UTF-8
3573c046 1496grub-pc grub-pc/install_devices select $grub_install_devices_txt
89a12446
DM
1497_EOD
1498
89a12446 1499 my $pkg_count = 0;
97980bf2 1500 while (<${proxmox_pkgdir}/*.deb>) { $pkg_count++ };
89a12446 1501
121ebc59
DM
1502 # btrfs/dpkg is extremely slow without --force-unsafe-io
1503 my $dpkg_opts = $use_btrfs ? "--force-unsafe-io" : "";
1504
89a12446 1505 $count = 0;
97980bf2 1506 while (<${proxmox_pkgdir}/*.deb>) {
89a12446
DM
1507 chomp;
1508 my $path = $_;
97980bf2 1509 my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/;
71590b6a 1510 update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb");
89a12446 1511 print "extracting: $deb\n";
71590b6a 1512 syscmd("cp $path $targetdir/tmp/$deb") == 0 ||
89a12446 1513 die "installation of package $deb failed\n";
71590b6a 1514 syscmd("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/$deb") == 0 ||
968fa90b 1515 die "installation of package $deb failed\n";
71590b6a 1516 update_progress((++$count)/$pkg_count, 0.5, 0.75);
89a12446
DM
1517 }
1518
3b11dce4
FG
1519 # needed for postfix postinst in case no other NIC is active
1520 syscmd("chroot $targetdir ifup lo");
1521
121ebc59 1522 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a";
89a12446 1523 $count = 0;
71590b6a 1524 run_command($cmd, sub {
89a12446
DM
1525 my $line = shift;
1526 if ($line =~ m/Setting up\s+(\S+)/) {
71590b6a
OB
1527 update_progress((++$count)/$pkg_count, 0.75, 0.95,
1528 "configuring $1");
89a12446
DM
1529 }
1530 });
968fa90b 1531
89a12446
DM
1532 unlink "$targetdir/etc/mailname";
1533 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
71590b6a 1534 write_config($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
89a12446
DM
1535
1536 # make sure we have all postfix directories
71590b6a 1537 syscmd("chroot $targetdir /usr/sbin/postfix check");
89a12446 1538 # cleanup mail queue
71590b6a 1539 syscmd("chroot $targetdir /usr/sbin/postsuper -d ALL");
89a12446 1540
6b5dc3d0 1541 # enable NTP (timedatectl set-ntp true does not work without DBUS)
71590b6a 1542 syscmd("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
6b5dc3d0 1543
89a12446
DM
1544 unlink "$targetdir/proxmox_install_mode";
1545
968fa90b 1546 # set timezone
89a12446
DM
1547 unlink ("$targetdir/etc/localtime");
1548 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
71590b6a 1549 write_config("$timezone\n", "$targetdir/etc/timezone");
89a12446 1550
89a12446
DM
1551 # set apt mirror
1552 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1553 my $fn = "$targetdir/etc/apt/sources.list";
71590b6a 1554 syscmd("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
89a12446
DM
1555 }
1556
19edf8b7
DM
1557 # create extended_states for apt (avoid cron job warning if that
1558 # file does not exist)
71590b6a 1559 write_config('', "$targetdir/var/lib/apt/extended_states");
19edf8b7 1560
c2657b8b 1561 # allow ssh root login
abcadb95 1562 syscmd(['sed', '-i', 's/^#\?PermitRootLogin.*/PermitRootLogin yes/', "$targetdir/etc/ssh/sshd_config"]);
861a26d4
DM
1563
1564 if ($setup->{product} eq 'pmg') {
1565 # install initial clamav DB
1566 my $srcdir = "${proxmox_cddir}/proxmox/clamav";
05eb99e2 1567 foreach my $fn ("main.cvd", "bytecode.cvd", "daily.cvd", "safebrowsing.cvd") {
71590b6a 1568 syscmd("cp \"$srcdir/$fn\" \"$targetdir/var/lib/clamav\"") == 0 ||
861a26d4
DM
1569 die "installation of clamav db file '$fn' failed\n";
1570 }
1571 syscmd("chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav") == 0 ||
1572 die "unable to set owner for clamav database files\n";
1573 }
1574
58a09baa
DM
1575 if ($setup->{product} eq 'pve') {
1576 # save installer settings
1577 my $ucc = uc ($country);
1578 debconfig_set($targetdir, "pve-manager pve-manager/country string $ucc\n");
1579 }
89a12446 1580
71590b6a 1581 update_progress(0.8, 0.95, 1, "make system bootable");
89a12446 1582
5c06ced5 1583 if ($use_zfs) {
71590b6a 1584 syscmd("sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT\\/$zfsrootvolname boot=zfs\"/' $targetdir/etc/default/grub") == 0 ||
5c06ced5 1585 die "unable to update /etc/default/grub\n";
e38884af
SI
1586 if ($boot_type eq 'efi') {
1587 write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs", "$targetdir/etc/kernel/cmdline");
1588 }
1464c7c9 1589
5c06ced5 1590 }
23c337f5 1591
71590b6a
OB
1592 diversion_remove($targetdir, "/usr/sbin/update-grub");
1593 diversion_remove($targetdir, "/usr/sbin/update-initramfs");
89a12446 1594
56207f2a
DM
1595 my $kapi;
1596 foreach my $fn (<$targetdir/lib/modules/*>) {
1597 if ($fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$!) {
1598 die "found multiple kernels\n" if defined($kapi);
1599 $kapi = $1;
1600 }
1601 }
1602 die "unable to detect kernel version\n" if !defined($kapi);
1603
c6ed3b24 1604 if (!$opt_testmode) {
89a12446
DM
1605
1606 unlink ("$targetdir/etc/mtab");
1607 symlink ("/proc/mounts", "$targetdir/etc/mtab");
71590b6a 1608 syscmd("mount -n --bind /dev $targetdir/dev");
89a12446 1609
71590b6a 1610 syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
89a12446
DM
1611 die "unable to install initramfs\n";
1612
c6ed3b24
DM
1613 foreach my $di (@$bootdevinfo) {
1614 my $dev = $di->{devname};
71590b6a 1615 syscmd("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
f810f5d0
DM
1616 die "unable to install the i386-pc boot loader on '$dev'\n";
1617
e38884af
SI
1618 if ($di->{esp} && $use_zfs) {
1619 prepare_systemd_boot_esp($di->{esp}, $targetdir);
1620 } elsif ($di->{esp}) {
71590b6a 1621 syscmd("mount -n $di->{esp} -t vfat $targetdir/boot/efi") == 0 ||
f810f5d0 1622 die "unable to mount $di->{esp}\n";
71590b6a 1623 my $rc = syscmd("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
5e0d6dce
FG
1624 if ($rc != 0) {
1625 if (-d '/sys/firmware/efi') {
1626 die "unable to install the EFI boot loader on '$dev'\n";
1627 } else {
1628 warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
1629 }
1630 }
fd3ec787
DM
1631 # also install fallback boot file (OVMF does not boot without)
1632 mkdir("$targetdir/boot/efi/EFI/BOOT");
1633 syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
1634 die "unable to copy efi boot loader\n";
1635
71590b6a 1636 syscmd("umount $targetdir/boot/efi") == 0 ||
f810f5d0 1637 die "unable to umount $targetdir/boot/efi\n";
1e61f3d8 1638 }
c6ed3b24 1639 }
89a12446 1640
71590b6a 1641 syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
c6ed3b24 1642 die "unable to update boot loader config\n";
89a12446 1643
71590b6a 1644 syscmd("umount $targetdir/dev");
89a12446
DM
1645 }
1646
968fa90b 1647 # cleanup
89a12446 1648
89a12446
DM
1649 unlink "$targetdir/usr/sbin/policy-rc.d";
1650
71590b6a 1651 diversion_remove($targetdir, "/sbin/start-stop-daemon");
89a12446
DM
1652
1653 # set root password
968fa90b 1654 my $octets = encode("utf-8", $password);
71590b6a
OB
1655 run_command("chroot $targetdir /usr/sbin/chpasswd", undef,
1656 "root:$octets\n");
7053f98b 1657
038552a1 1658 if ($setup->{product} eq 'pmg') {
038552a1 1659 # save admin email
71590b6a
OB
1660 write_config("section: admin\n\temail ${mailto}\n",
1661 "$targetdir/etc/pmg/pmg.conf");
038552a1
DM
1662
1663 } elsif ($setup->{product} eq 'pve') {
7053f98b 1664
8acc47b5 1665 # create pmxcfs DB
7053f98b 1666
8acc47b5
DM
1667 my $tmpdir = "$targetdir/tmp/pve";
1668 mkdir $tmpdir;
7053f98b 1669
8acc47b5
DM
1670 # write vnc keymap to datacenter.cfg
1671 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
71590b6a
OB
1672 write_config("keyboard: $vnckmap\n",
1673 "$tmpdir/datacenter.cfg");
968fa90b 1674
8acc47b5 1675 # save admin email
71590b6a
OB
1676 write_config("user:root\@pam:1:0:::${mailto}::\n",
1677 "$tmpdir/user.cfg");
5fd81672 1678
8acc47b5 1679 # write storage.cfg
d8c697d4 1680 my $storage_cfg_fn = "$tmpdir/storage.cfg";
8acc47b5 1681 if ($use_zfs) {
71590b6a 1682 write_config($storage_cfg_zfs, $storage_cfg_fn);
8acc47b5 1683 } elsif ($use_btrfs) {
71590b6a 1684 write_config($storage_cfg_btrfs, $storage_cfg_fn);
e2c51d7c 1685 } elsif ($datadev) {
71590b6a 1686 write_config($storage_cfg_lvmthin, $storage_cfg_fn);
e2c51d7c 1687 } else {
71590b6a 1688 write_config($storage_cfg_local, $storage_cfg_fn);
8acc47b5 1689 }
7053f98b 1690
8acc47b5
DM
1691 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1692
71590b6a 1693 syscmd("rm -rf $tmpdir");
8acc47b5 1694 }
89a12446
DM
1695 };
1696
1697 my $err = $@;
1698
71590b6a 1699 update_progress(1, 0, 1, "");
89a12446
DM
1700
1701 print $err if $err;
1702
1703 if ($opt_testmode) {
121ebc59
DM
1704 my $elapsed = Time::HiRes::tv_interval($starttime);
1705 print "Elapsed extract time: $elapsed\n";
1706
71590b6a 1707 syscmd("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
89a12446
DM
1708 }
1709
8d7ddbde
TL
1710 syscmd("umount $targetdir/run");
1711 syscmd("umount $targetdir/mnt/hostrun");
71590b6a
OB
1712 syscmd("umount $targetdir/tmp");
1713 syscmd("umount $targetdir/proc");
1714 syscmd("umount $targetdir/sys");
6fbd1fb1
DM
1715
1716 if ($use_zfs) {
71590b6a 1717 syscmd("zfs umount -a") == 0 ||
6fbd1fb1
DM
1718 die "unable to unmount zfs\n";
1719 } else {
71590b6a 1720 syscmd("umount -d $targetdir");
6fbd1fb1 1721 }
89a12446 1722
5c06ced5 1723 if (!$err && $use_zfs) {
71590b6a 1724 syscmd("zfs set sync=standard $zfspoolname") == 0 ||
481671c3
DM
1725 die "unable to set zfs properties\n";
1726
71590b6a 1727 syscmd("zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname") == 0 ||
5c06ced5 1728 die "zfs set mountpoint failed\n";
1464c7c9 1729
71590b6a 1730 syscmd("zpool set bootfs=$zfspoolname/ROOT/$zfsrootvolname $zfspoolname") == 0 ||
5c06ced5 1731 die "zfs set bootfs failed\n";
71590b6a 1732 syscmd("zpool export $zfspoolname");
5c06ced5
DM
1733 }
1734
89a12446
DM
1735 die $err if $err;
1736}
1737
550958aa
DM
1738my $last_display_change = 0;
1739
1740my $display_info_counter = 0;
1741
1742my $display_info_items = [
1743 "extract1-license.htm",
1744 "extract2-rulesystem.htm",
1745 "extract3-spam.htm",
1746 "extract4-virus.htm",
1747 ];
1748
1749sub display_info {
1750
1751 my $min_display_time = 15;
1752
1753 my $ctime = time();
1754
1755 return if ($ctime - $last_display_change) < $min_display_time;
1756
1757 my $page = $display_info_items->[$display_info_counter % scalar(@$display_info_items)];
1758
1759 $display_info_counter++;
1760
1761 display_html($page);
1762}
1763
89a12446
DM
1764sub display_html {
1765 my ($filename) = @_;
1766
201a5120
OB
1767 $filename = $steps[$step_number]->{html} if !$filename;
1768
a04ac176 1769 my $path = "${proxmox_libdir}/html/$filename";
c437cef5 1770
8a50920c
DM
1771 my $url = "file://$path";
1772
1773 my $data = file_get_contents($path);
1774
1775 if ($filename eq 'license.htm') {
93f25df9
TL
1776 my $license = eval { decode('utf8', file_get_contents("${proxmox_cddir}/EULA")) };
1777 if (my $err = $@) {
1778 die $err if !$opt_testmode;
1779 $license = "TESTMODE: Ignore non existent EULA...\n";
1780 }
3c866639 1781 my $title = "END USER LICENSE AGREEMENT (EULA)";
f91c161b 1782 $data =~ s/__LICENSE__/$license/;
8a50920c
DM
1783 $data =~ s/__LICENSE_TITLE__/$title/;
1784 }
1785
ed0e6aea 1786 $htmlview->load_html($data, $url);
550958aa
DM
1787
1788 $last_display_change = time();
7becc472
DM
1789}
1790
201a5120
OB
1791sub prev_function {
1792
1793 my ($text, $fctn) = @_;
1794
1795 $fctn = $step_number if !$fctn;
1796 $text = "_Previous" if !$text;
451b1da5 1797 $prev_btn->set_label ($text);
201a5120
OB
1798
1799 $step_number--;
1800 $steps[$step_number]->{function}();
1801
71590b6a 1802 $prev_btn->grab_focus();
201a5120
OB
1803}
1804
89a12446
DM
1805sub set_next {
1806 my ($text, $fctn) = @_;
1807
1808 $next_fctn = $fctn;
201a5120
OB
1809 my $step = $steps[$step_number];
1810 $text //= $steps[$step_number]->{next_button} // '_Next';
71590b6a 1811 $next->set_label($text);
968fa90b 1812
71590b6a 1813 $next->grab_focus();
89a12446 1814}
89a12446
DM
1815
1816sub create_main_window {
1817
71590b6a
OB
1818 $window = Gtk3::Window->new();
1819 $window->set_default_size(1024, 768);
84761f93 1820 $window->set_has_resize_grip(0);
71590b6a 1821 $window->set_decorated(0) if !$opt_testmode;
89a12446 1822
71590b6a 1823 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 1824
782b4acd
DM
1825 my $logofn = "$setup->{product}-banner.png";
1826 my $image = Gtk3::Image->new_from_file("${proxmox_libdir}/$logofn");
71590b6a 1827 $vbox->pack_start($image, 0, 0, 0);
89a12446 1828
71590b6a
OB
1829 my $hbox = Gtk3::HBox->new(0, 0);
1830 $vbox->pack_start($hbox, 1, 1, 0);
89a12446 1831
7becc472
DM
1832 # my $f1 = Gtk3::Frame->new ('test');
1833 # $f1->set_shadow_type ('none');
1834 # $hbox->pack_start ($f1, 1, 1, 0);
89a12446 1835
71590b6a
OB
1836 my $sep1 = Gtk3::HSeparator->new();
1837 $vbox->pack_start($sep1, 0, 0, 0);
89a12446 1838
71590b6a
OB
1839 $cmdbox = Gtk3::HBox->new();
1840 $vbox->pack_start($cmdbox, 0, 0, 10);
89a12446 1841
71590b6a
OB
1842 $next = Gtk3::Button->new('_Next');
1843 $next->signal_connect(clicked => sub { $last_display_change = 0; &$next_fctn (); });
1844 $cmdbox->pack_end($next, 0, 0, 10);
201a5120
OB
1845
1846
71590b6a
OB
1847 $prev_btn = Gtk3::Button->new('_Previous');
1848 $prev_btn->signal_connect(clicked => sub { $last_display_change = 0; &prev_function (); });
1849 $cmdbox->pack_end($prev_btn, 0, 0, 10);
201a5120
OB
1850
1851
71590b6a
OB
1852 my $abort = Gtk3::Button->new('_Abort');
1853 $abort->set_can_focus(0);
1854 $cmdbox->pack_start($abort, 0, 0, 10);
1855 $abort->signal_connect(clicked => sub { exit (-1); });
89a12446 1856
71590b6a
OB
1857 my $vbox2 = Gtk3::VBox->new(0, 0);
1858 $hbox->add($vbox2);
89a12446 1859
ed0e6aea 1860 $htmlview = Gtk3::WebKit2::WebView->new();
7becc472
DM
1861 my $scrolls = Gtk3::ScrolledWindow->new();
1862 $scrolls->add($htmlview);
1464c7c9 1863
71590b6a
OB
1864 my $hbox2 = Gtk3::HBox->new(0, 0);
1865 $hbox2->pack_start($scrolls, 1, 1, 0);
89a12446 1866
71590b6a 1867 $vbox2->pack_start($hbox2, 1, 1, 0);
89a12446 1868
71590b6a
OB
1869 my $vbox3 = Gtk3::VBox->new(0, 0);
1870 $vbox2->pack_start($vbox3, 0, 0, 0);
89a12446 1871
7becc472 1872 my $sep2 = Gtk3::HSeparator->new;
71590b6a 1873 $vbox3->pack_start($sep2, 0, 0, 0);
89a12446 1874
71590b6a
OB
1875 $inbox = Gtk3::HBox->new(0, 0);
1876 $vbox3->pack_start($inbox, 0, 0, 0);
89a12446 1877
71590b6a 1878 $window->add($vbox);
89a12446
DM
1879
1880 $window->show_all;
71590b6a 1881 $window->realize();
89a12446
DM
1882}
1883
1464c7c9 1884sub cleanup_view {
d2120e51
DM
1885 $inbox->foreach(sub {
1886 my $child = shift;
1464c7c9 1887 $inbox->remove ($child);
d2120e51 1888 });
89a12446
DM
1889}
1890
aed81ff0
DM
1891# fixme: newer GTK3 has special properties to handle numbers with Entry
1892# only allow floating point numbers with Gtk3::Entry
e73c5fcf 1893
aed81ff0
DM
1894sub check_float {
1895 my ($entry, $event) = @_;
1896
e73c5fcf
FG
1897 return check_number($entry, $event, 1);
1898}
1899
1900sub check_int {
1901 my ($entry, $event) = @_;
1902
1903 return check_number($entry, $event, 0);
1904}
1905
1906sub check_number {
1907 my ($entry, $event, $float) = @_;
aed81ff0
DM
1908
1909 my $val = $event->get_keyval;
1910
e73c5fcf 1911 if (($float && $val == ord '.') ||
aed81ff0
DM
1912 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
1913 $val == Gtk3::Gdk::KEY_Shift_L ||
1914 $val == Gtk3::Gdk::KEY_Tab ||
1915 $val == Gtk3::Gdk::KEY_Left ||
1916 $val == Gtk3::Gdk::KEY_Right ||
1917 $val == Gtk3::Gdk::KEY_BackSpace ||
1918 $val == Gtk3::Gdk::KEY_Delete ||
1919 ($val >= ord '0' && $val <= ord '9') ||
1920 ($val >= Gtk3::Gdk::KEY_KP_0 &&
1921 $val <= Gtk3::Gdk::KEY_KP_9)) {
1922 return undef;
1923 }
1924
1925 return 1;
1926}
1927
d2120e51 1928sub create_text_input {
89a12446
DM
1929 my ($default, $text) = @_;
1930
71590b6a 1931 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 1932
71590b6a
OB
1933 my $label = Gtk3::Label->new($text);
1934 $label->set_size_request(150, -1);
1935 $label->set_alignment(1, 0.5);
1936 $hbox->pack_start($label, 0, 0, 10);
1937 my $e1 = Gtk3::Entry->new();
1938 $e1->set_width_chars(30);
1939 $hbox->pack_start($e1, 0, 0, 0);
1940 $e1->set_text($default);
89a12446
DM
1941
1942 return ($hbox, $e1);
1943}
1944
89a12446
DM
1945sub get_ip_config {
1946
fe44bd92
FG
1947 my $ifaces = {};
1948 my $default;
89a12446 1949
fe44bd92
FG
1950 my $links = `ip -o l`;
1951 foreach my $l (split /\n/,$links) {
1952 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
1953 next if !$name || $name eq 'lo';
89a12446 1954
fe44bd92
FG
1955 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
1956 $driver =~ s!^.*/!!;
1957
1958 $ifaces->{"$index"} = {
1959 name => $name,
1960 driver => $driver,
1961 flags => $flags,
1962 state => $state,
1963 mac => $mac,
1964 };
1965
1966 my $addresses = `ip -o a s $name`;
1967 foreach my $a (split /\n/,$addresses) {
1968 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
1969 next if !$ip;
32b6fbcf 1970 next if $a =~ /scope\s+link/; # ignore link local
fe44bd92
FG
1971
1972 my $mask = $prefix;
1973
1974 if ($family eq 'inet') {
1975 next if !$ip =~ /$IPV4RE/;
1976 next if $prefix < 8 || $prefix > 32;
1977 $mask = @$ipv4_reverse_mask[$prefix];
1978 } else {
1979 next if !$ip =~ /$IPV6RE/;
1980 }
1981
1982 $default = $index if !$default;
1983
1984 $ifaces->{"$index"}->{"$family"} = {
1985 mask => $mask,
1986 addr => $ip,
1987 };
1988 }
1989 }
1990
1991
1992 my $route = `ip route`;
1993 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
89a12446
DM
1994
1995 my $resolvconf = `cat /etc/resolv.conf`;
1996 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
713790a4 1997 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
89a12446
DM
1998
1999 return {
fe44bd92
FG
2000 default => $default,
2001 ifaces => $ifaces,
89a12446
DM
2002 gateway => $gateway,
2003 dnsserver => $dnsserver,
713790a4 2004 domain => $domain,
89a12446
DM
2005 }
2006}
2007
2008sub display_message {
2009 my ($msg) = @_;
2010
71590b6a
OB
2011 my $dialog = Gtk3::MessageDialog->new($window, 'modal',
2012 'info', 'ok', $msg);
89a12446
DM
2013 $dialog->run();
2014 $dialog->destroy();
2015}
2016
2017sub display_error {
2018 my ($msg) = @_;
2019
71590b6a
OB
2020 my $dialog = Gtk3::MessageDialog->new($window, 'modal',
2021 'error', 'ok', $msg);
89a12446
DM
2022 $dialog->run();
2023 $dialog->destroy();
2024}
2025
fe44bd92
FG
2026my $ipconf_first_view = 1;
2027
89a12446
DM
2028sub create_ipconf_view {
2029
201a5120
OB
2030 cleanup_view();
2031 display_html();
89a12446 2032
71590b6a
OB
2033 my $vbox = Gtk3::VBox->new(0, 0);
2034 $inbox->pack_start($vbox, 1, 0, 0);
2035 my $hbox = Gtk3::HBox->new(0, 0);
2036 $vbox->pack_start($hbox, 0, 0, 10);
2037 my $vbox2 = Gtk3::VBox->new(0, 0);
2038 $hbox->add($vbox2);
89a12446 2039
ebc4f76f 2040 my $ipaddr_text = $config->{ipaddress} // "192.168.100.2";
fe44bd92
FG
2041 my $ipbox;
2042 ($ipbox, $ipconf_entry_addr) =
71590b6a 2043 create_text_input($ipaddr_text, 'IP Address:');
fe44bd92 2044
ebc4f76f 2045 my $netmask_text = $config->{netmask} // "255.255.255.0";
fe44bd92
FG
2046 my $maskbox;
2047 ($maskbox, $ipconf_entry_mask) =
71590b6a 2048 create_text_input($netmask_text, 'Netmask:');
fe44bd92
FG
2049
2050 my $device_cb = Gtk3::ComboBoxText->new();
2051 $device_cb->set_active(0);
2052 $device_cb->set_visible(1);
2053
2054 my $get_device_desc = sub {
2055 my $iface = shift;
2056 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
2057 };
2058
2059 my $device_active_map = {};
ebc4f76f 2060 my $device_active_reverse_map = {};
5b6ba737
FG
2061
2062 my $device_change_handler = sub {
2063 my $current = shift;
d6524c52
TL
2064
2065 my $new = $device_active_map->{$current->get_active()};
cd2d2a27 2066 return if defined($ipconf->{selected}) && $new eq $ipconf->{selected};
d6524c52
TL
2067
2068 $ipconf->{selected} = $new;
5b6ba737 2069 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
ebc4f76f 2070 $config->{mngmt_nic} = $iface->{name};
5b6ba737
FG
2071 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2072 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
2073 $ipconf_entry_mask->set_text($iface->{inet}->{mask} || $iface->{inet6}->{mask})
2074 if $iface->{inet}->{mask} || $iface->{inet6}->{mask};
2075 };
2076
fe44bd92
FG
2077 my $i = 0;
2078 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2079 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
ebc4f76f
TL
2080 $device_active_map->{$i} = $index;
2081 $device_active_reverse_map->{$ipconf->{ifaces}->{$index}->{name}} = $i;
fe44bd92
FG
2082 if ($ipconf_first_view && $index == $ipconf->{default}) {
2083 $device_cb->set_active($i);
5b6ba737 2084 &$device_change_handler($device_cb);
fe44bd92
FG
2085 $ipconf_first_view = 0;
2086 }
71590b6a 2087 $device_cb->signal_connect('changed' => $device_change_handler);
fe44bd92
FG
2088 $i++;
2089 }
2090
ebc4f76f
TL
2091 if (my $nic = $config->{mngmt_nic}) {
2092 $device_cb->set_active($device_active_reverse_map->{$nic} // 0);
2093 } else {
2094 $device_cb->set_active(0);
2095 }
5b6ba737 2096
71590b6a
OB
2097 my $devicebox = Gtk3::HBox->new(0, 0);
2098 my $label = Gtk3::Label->new("Management Interface:");
2099 $label->set_size_request(150, -1);
2100 $label->set_alignment(1, 0.5);
2101 $devicebox->pack_start($label, 0, 0, 10);
2102 $devicebox->pack_start($device_cb, 0, 0, 0);
fe44bd92 2103
71590b6a 2104 $vbox2->pack_start($devicebox, 0, 0, 2);
968fa90b 2105
ebc4f76f 2106 my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
1464c7c9 2107
968fa90b 2108 my ($hostbox, $hostentry) =
71590b6a
OB
2109 create_text_input($hn, 'Hostname (FQDN):');
2110 $vbox2->pack_start($hostbox, 0, 0, 2);
89a12446 2111
71590b6a 2112 $vbox2->pack_start($ipbox, 0, 0, 2);
89a12446 2113
71590b6a 2114 $vbox2->pack_start($maskbox, 0, 0, 2);
89a12446 2115
ebc4f76f 2116 $gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
89a12446
DM
2117
2118 my $gwbox;
d2120e51 2119 ($gwbox, $ipconf_entry_gw) =
71590b6a 2120 create_text_input($gateway, 'Gateway:');
89a12446 2121
71590b6a 2122 $vbox2->pack_start($gwbox, 0, 0, 2);
89a12446 2123
ebc4f76f 2124 $dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
89a12446
DM
2125
2126 my $dnsbox;
d2120e51 2127 ($dnsbox, $ipconf_entry_dns) =
71590b6a 2128 create_text_input($dnsserver, 'DNS Server:');
89a12446 2129
71590b6a 2130 $vbox2->pack_start($dnsbox, 0, 0, 0);
89a12446
DM
2131
2132 $inbox->show_all;
71590b6a 2133 set_next(undef, sub {
d2120e51
DM
2134
2135 # verify hostname
1464c7c9 2136
89a12446 2137 my $text = $hostentry->get_text();
968fa90b 2138
89a12446
DM
2139 $text =~ s/^\s+//;
2140 $text =~ s/\s+$//;
2141
ebc4f76f
TL
2142 $config->{fqdn} = $text;
2143
ac3757a9 2144 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2145
24973868
WB
2146 # Debian does not support purely numeric hostnames
2147 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2148 display_message("Purely numeric hostnames are not allowed.");
2149 $hostentry->grab_focus();
2150 return;
2151 }
2152
a39bc1f2 2153 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2154 $text =~ m/^([^\.]+)\.(\S+)$/) {
2155 $hostname = $1;
2156 $domain = $2;
d2120e51 2157 } else {
71590b6a 2158 display_message("Hostname does not look like a fully qualified domain name.");
d2120e51 2159 $hostentry->grab_focus();
89a12446
DM
2160 return;
2161 }
d2120e51
DM
2162
2163 # verify ip address
2164
2165 $text = $ipconf_entry_addr->get_text();
2166 $text =~ s/^\s+//;
2167 $text =~ s/\s+$//;
2168 if ($text =~ m!^($IPV4RE)$!) {
2169 $ipaddress = $text;
b6200603
DM
2170 $ipversion = 4;
2171 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2172 $ipaddress = $text;
b6200603 2173 $ipversion = 6;
d2120e51 2174 } else {
71590b6a 2175 display_message("IP address is not valid.");
d2120e51
DM
2176 $ipconf_entry_addr->grab_focus();
2177 return;
2178 }
ebc4f76f 2179 $config->{ipaddress} = $ipaddress;
d2120e51
DM
2180
2181 $text = $ipconf_entry_mask->get_text();
2182 $text =~ s/^\s+//;
2183 $text =~ s/\s+$//;
b6200603
DM
2184 if (($ipversion == 6) && ($text =~ m/^(\d+)$/) && ($1 >= 8) && ($1 <= 126)) {
2185 $netmask = $text;
2186 } elsif (($ipversion == 4) && defined($ipv4_mask_hash->{$text})) {
d2120e51
DM
2187 $netmask = $text;
2188 } else {
71590b6a 2189 display_message("Netmask is not valid.");
d2120e51
DM
2190 $ipconf_entry_mask->grab_focus();
2191 return;
2192 }
ebc4f76f 2193 $config->{netmask} = $netmask;
d2120e51
DM
2194
2195 $text = $ipconf_entry_gw->get_text();
2196 $text =~ s/^\s+//;
2197 $text =~ s/\s+$//;
b6200603
DM
2198 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2199 $gateway = $text;
2200 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2201 $gateway = $text;
2202 } else {
71590b6a 2203 display_message("Gateway is not valid.");
d2120e51
DM
2204 $ipconf_entry_gw->grab_focus();
2205 return;
2206 }
ebc4f76f 2207 $config->{gateway} = $gateway;
1464c7c9 2208
d2120e51
DM
2209 $text = $ipconf_entry_dns->get_text();
2210 $text =~ s/^\s+//;
2211 $text =~ s/\s+$//;
b6200603
DM
2212 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2213 $dnsserver = $text;
2214 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2215 $dnsserver = $text;
2216 } else {
71590b6a 2217 display_message("DNS server is not valid.");
d2120e51
DM
2218 $ipconf_entry_dns->grab_focus();
2219 return;
2220 }
ebc4f76f 2221 $config->{dnsserver} = $dnsserver;
1464c7c9 2222
d2120e51 2223 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2224
201a5120 2225 $step_number++;
2e33c3f0 2226 create_ack_view();
89a12446
DM
2227 });
2228
2229 $hostentry->grab_focus();
2230}
2231
2e33c3f0
OB
2232sub create_ack_view {
2233
2234 cleanup_view();
2235
2236 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
2237 my $ack_html = "${proxmox_libdir}/html/$steps[$step_number]->{html}";
2238 my $html_data = file_get_contents($ack_template);
2239
2240 my %config_values = (
a7d40341 2241 __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
0470018e 2242 __target_fs__ => $config_options->{filesys},
0ddd2227 2243 __country__ => $cmap->{country}->{$country}->{name},
2e33c3f0
OB
2244 __timezone__ => $timezone,
2245 __keymap__ => $keymap,
2246 __mailto__ => $mailto,
2247 __interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
2248 __hostname__ => $hostname,
2249 __ip__ => $ipaddress,
2250 __netmask__ => $netmask,
2251 __gateway__ => $gateway,
2252 __dnsserver__ => $dnsserver,
2253 );
2254
2255 while ( my ($k, $v) = each %config_values) {
2256 $html_data =~ s/$k/$v/g;
2257 }
2258
2259 write_config($html_data, $ack_html);
2260
2261 display_html();
2262
2263 set_next(undef, sub {
2264 $step_number++;
2265 create_extract_view();
2266 });
2267}
2268
89a12446
DM
2269sub get_device_desc {
2270 my ($devname, $size, $model) = @_;
2271
d2120e51 2272 if ($size && ($size > 0)) {
1bd457bb 2273 $size = int($size/2048); # size in MB, from 512B "sectors"
89a12446 2274
d2120e51 2275 my $text = "$devname (";
89a12446
DM
2276 if ($size >= 1024) {
2277 $size = int($size/1024); # size in GB
d2120e51 2278 $text .= "${size}GB";
89a12446 2279 } else {
d2120e51 2280 $text .= "${size}MB";
89a12446
DM
2281 }
2282
d2120e51
DM
2283 $text .= ", $model" if $model;
2284 $text .= ")";
2285
89a12446
DM
2286 } else {
2287 return $devname;
2288 }
2289}
2290
2291sub update_layout {
2292 my ($cb, $kmap) = @_;
2293
2294 my $ind;
2295 my $def;
2296 my $i = 0;
2297 my $kmaphash = $cmap->{kmaphash};
2298 foreach my $layout (sort keys %$kmaphash) {
2299 $def = $i if $kmaphash->{$layout} eq 'en-us';
2300 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2301 $i++;
2302 }
2303
71590b6a 2304 $cb->set_active($ind || $def || 0);
89a12446
DM
2305}
2306
2307my $lastzonecb;
2308sub update_zonelist {
2309 my ($box, $cc) = @_;
2310
2311 my $cczones = $cmap->{cczones};
2312 my $zones = $cmap->{zones};
2313
2314 my $sel;
2315 if ($lastzonecb) {
2316 $sel = $lastzonecb->get_active_text();
2317 $box->remove ($lastzonecb);
2318 } else {
2319 $sel = $timezone; # used once to select default
2320 }
2321
bcbfab6b 2322 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
71590b6a 2323 $cb->set_size_request(200, -1);
89a12446 2324
71590b6a 2325 $cb->signal_connect('changed' => sub {
89a12446
DM
2326 $timezone = $cb->get_active_text();
2327 });
2328
2329 my @za;
2330 if ($cc && defined ($cczones->{$cc})) {
2331 @za = keys %{$cczones->{$cc}};
2332 } else {
2333 @za = keys %$zones;
2334 }
2335 my $ind;
2336 my $i = 0;
2337 foreach my $zone (sort @za) {
2338 $ind = $i if $sel && $zone eq $sel;
71590b6a 2339 $cb->append_text($zone);
89a12446
DM
2340 $i++;
2341 }
2342
71590b6a 2343 $cb->set_active($ind || 0);
89a12446
DM
2344
2345 $cb->show;
71590b6a 2346 $box->pack_start($cb, 0, 0, 0);
89a12446
DM
2347}
2348
2349sub create_password_view {
2350
71590b6a 2351 cleanup_view();
89a12446 2352
71590b6a
OB
2353 my $vbox2 = Gtk3::VBox->new(0, 0);
2354 $inbox->pack_start($vbox2, 1, 0, 0);
2355 my $vbox = Gtk3::VBox->new(0, 0);
2356 $vbox2->pack_start($vbox, 0, 0, 10);
2357
2358 my $hbox1 = Gtk3::HBox->new(0, 0);
2359 my $label = Gtk3::Label->new("Password");
2360 $label->set_size_request(150, -1);
2361 $label->set_alignment(1, 0.5);
2362 $hbox1->pack_start($label, 0, 0, 10);
2363 my $pwe1 = Gtk3::Entry->new();
2364 $pwe1->set_visibility(0);
201a5120 2365 $pwe1->set_text($password) if $password;
71590b6a
OB
2366 $pwe1->set_size_request(200, -1);
2367 $hbox1->pack_start($pwe1, 0, 0, 0);
2368
2369 my $hbox2 = Gtk3::HBox->new(0, 0);
2370 $label = Gtk3::Label->new("Confirm");
2371 $label->set_size_request(150, -1);
2372 $label->set_alignment(1, 0.5);
2373 $hbox2->pack_start($label, 0, 0, 10);
2374 my $pwe2 = Gtk3::Entry->new();
2375 $pwe2->set_visibility(0);
201a5120 2376 $pwe2->set_text($password) if $password;
71590b6a
OB
2377 $pwe2->set_size_request(200, -1);
2378 $hbox2->pack_start($pwe2, 0, 0, 0);
2379
2380 my $hbox3 = Gtk3::HBox->new(0, 0);
2381 $label = Gtk3::Label->new("E-Mail");
2382 $label->set_size_request(150, -1);
2383 $label->set_alignment(1, 0.5);
2384 $hbox3->pack_start($label, 0, 0, 10);
2385 my $eme = Gtk3::Entry->new();
2386 $eme->set_size_request(200, -1);
201a5120 2387 $eme->set_text($mailto);
71590b6a 2388 $hbox3->pack_start($eme, 0, 0, 0);
89a12446
DM
2389
2390
71590b6a
OB
2391 $vbox->pack_start($hbox1, 0, 0, 5);
2392 $vbox->pack_start($hbox2, 0, 0, 5);
2393 $vbox->pack_start($hbox3, 0, 0, 15);
89a12446
DM
2394
2395 $inbox->show_all;
2396
201a5120 2397 display_html();
89a12446
DM
2398
2399 set_next (undef, sub {
2400
2401 my $t1 = $pwe1->get_text;
2402 my $t2 = $pwe2->get_text;
2403
2404 if (length ($t1) < 5) {
71590b6a 2405 display_message("Password is too short.");
89a12446
DM
2406 $pwe1->grab_focus();
2407 return;
2408 }
2409
2410 if ($t1 ne $t2) {
71590b6a 2411 display_message("Password does not match.");
89a12446
DM
2412 $pwe1->grab_focus();
2413 return;
2414 }
2415
2416 my $t3 = $eme->get_text;
2417 if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
71590b6a 2418 display_message("E-Mail does not look like a valid address" .
89a12446
DM
2419 " (user\@domain.tld)");
2420 $eme->grab_focus();
2421 return;
a39bc1f2 2422 }
89a12446 2423
a39bc1f2 2424 if ($t3 eq 'mail@example.invalid') {
71590b6a 2425 display_message("Please enter a valid E-Mail address");
a39bc1f2
FG
2426 $eme->grab_focus();
2427 return;
89a12446
DM
2428 }
2429
2430 $password = $t1;
2431 $mailto = $t3;
2432
201a5120 2433 $step_number++;
89a12446
DM
2434 create_ipconf_view();
2435 });
2436
2437 $pwe1->grab_focus();
2438
2439}
2440
2441sub create_country_view {
2442
71590b6a 2443 cleanup_view();
89a12446
DM
2444
2445 my $countryhash = $cmap->{countryhash};
2446 my $ctr = $cmap->{country};
2447
71590b6a
OB
2448 my $vbox2 = Gtk3::VBox->new(0, 0);
2449 $inbox->pack_start($vbox2, 1, 0, 0);
2450 my $vbox = Gtk3::VBox->new(0, 0);
2451 $vbox2->pack_start($vbox, 0, 0, 10);
89a12446 2452
71590b6a
OB
2453 my $w = Gtk3::Entry->new();
2454 $w->set_size_request(200, -1);
89a12446 2455
71590b6a
OB
2456 my $c = Gtk3::EntryCompletion->new();
2457 $c->set_text_column(0);
89a12446 2458 $c->set_minimum_key_length(0);
71590b6a
OB
2459 $c->set_popup_set_width(1);
2460 $c->set_inline_completion(1);
2461
2462 my $hbox2 = Gtk3::HBox->new(0, 0);
2463 my $label = Gtk3::Label->new("Time zone");
2464 $label->set_size_request(150, -1);
2465 $label->set_alignment(1, 0.5);
2466 $hbox2->pack_start($label, 0, 0, 10);
89a12446
DM
2467 update_zonelist ($hbox2);
2468
71590b6a
OB
2469 my $hbox3 = Gtk3::HBox->new(0, 0);
2470 $label = Gtk3::Label->new("Keyboard Layout");
2471 $label->set_size_request(150, -1);
2472 $label->set_alignment(1, 0.5);
2473 $hbox3->pack_start($label, 0, 0, 10);
89a12446 2474
bcbfab6b 2475 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2476 $kmapcb->set_size_request (200, -1);
2477 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2478 $kmapcb->append_text ($layout);
2479 }
2480
71590b6a 2481 update_layout($kmapcb);
89a12446
DM
2482 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2483
2484 $kmapcb->signal_connect ('changed' => sub {
2485 my $sel = $kmapcb->get_active_text();
2486 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2487 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2488 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
2489 syscmd ("setxkbmap $xkmap $xvar") if !$opt_testmode;
2490 $keymap = $kmap;
2491 }
2492 });
2493
2494 $w->signal_connect ('changed' => sub {
2495 my ($entry, $event) = @_;
2496 my $text = $entry->get_text;
2497
2498 if (my $cc = $countryhash->{lc($text)}) {
71590b6a 2499 update_zonelist($hbox2, $cc);
89a12446 2500 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
71590b6a 2501 update_layout($kmapcb, $kmap);
89a12446
DM
2502 }
2503 });
2504
2505 $w->signal_connect (key_press_event => sub {
2506 my ($entry, $event) = @_;
2507 my $text = $entry->get_text;
2508
7becc472
DM
2509 my $val = $event->get_keyval;
2510
2511 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2512 my $cc = $countryhash->{lc($text)};
1464c7c9 2513
89a12446
DM
2514 my $found = 0;
2515 my $compl;
7becc472 2516
4443aa27
DM
2517 if ($cc) {
2518 $found = 1;
2519 $compl = $ctr->{$cc}->{name};
2520 } else {
2521 foreach my $cc (keys %$ctr) {
2522 my $ct = $ctr->{$cc}->{name};
2523 if ($ct =~ m/^\Q$text\E.*$/i) {
2524 $found++;
2525 $compl = $ct;
2526 }
2527 last if $found > 1;
89a12446 2528 }
89a12446 2529 }
4443aa27 2530
89a12446 2531 if ($found == 1) {
7becc472 2532 $entry->set_text($compl);
3df718ea 2533 $c->complete();
89a12446
DM
2534 return undef;
2535 } else {
7becc472
DM
2536 #Gtk3::Gdk::beep();
2537 print chr(7); # beep ?
89a12446
DM
2538 }
2539
3df718ea
DM
2540 $c->complete();
2541
7becc472
DM
2542 my $buf = $w->get_buffer();
2543 $buf->insert_text(-1, '', -1); # popup selection
2544
89a12446
DM
2545 return 1;
2546 }
2547
2548 return undef;
2549 });
1464c7c9 2550
7becc472 2551 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2552 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2553 my $iter = $ls->append();
2554 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2555 }
2556 $c->set_model ($ls);
2557
968fa90b 2558 $w->set_completion ($c);
89a12446 2559
71590b6a 2560 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 2561
71590b6a
OB
2562 $label = Gtk3::Label->new("Country");
2563 $label->set_alignment(1, 0.5);
2564 $label->set_size_request(150, -1);
2565 $hbox->pack_start($label, 0, 0, 10);
2566 $hbox->pack_start($w, 0, 0, 0);
89a12446 2567
71590b6a
OB
2568 $vbox->pack_start($hbox, 0, 0, 5);
2569 $vbox->pack_start($hbox2, 0, 0, 5);
2570 $vbox->pack_start($hbox3, 0, 0, 5);
89a12446 2571
9d1f1ee3 2572 if ($country && $ctr->{$country}) {
89a12446
DM
2573 $w->set_text ($ctr->{$country}->{name});
2574 }
2575
2576 $inbox->show_all;
2577
201a5120 2578 display_html();
89a12446
DM
2579 set_next (undef, sub {
2580
2581 my $text = $w->get_text;
2582
2583 if (my $cc = $countryhash->{lc($text)}) {
2584 $country = $cc;
201a5120 2585 $step_number++;
89a12446
DM
2586 create_password_view();
2587 return;
2588 } else {
71590b6a 2589 display_message("Please select a country first.");
89a12446
DM
2590 $w->grab_focus();
2591 }
2592 });
2593
2594 $w->grab_focus();
2595}
2596
c6ed3b24
DM
2597my $target_hd_combo;
2598my $target_hd_label;
2599
bd3a2e26 2600my $hdoption_first_setup = 1;
c6ed3b24 2601
c7779156
FG
2602my $create_basic_grid = sub {
2603 my $grid = Gtk3::Grid->new();
2604 $grid->set_visible(1);
2605 $grid->set_column_spacing(10);
2606 $grid->set_row_spacing(10);
2607 $grid->set_hexpand(1);
2608
2609 $grid->set_margin_start(5);
2610 $grid->set_margin_end(5);
2611 $grid->set_margin_top(5);
2612 $grid->set_margin_bottom(5);
2613
2614 return $grid;
2615};
2616
2617my $create_label_widget_grid = sub {
2618 my ($labeled_widgets) = @_;
2619
2620 my $grid = &$create_basic_grid();
2621 my $row = 0;
2622
2623 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2624 my $widget = @$labeled_widgets[$i+1];
2625 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2626 $label->set_visible(1);
2627 $label->set_alignment (1, 0.5);
2628 $grid->attach($label, 0, $row, 1, 1);
2629 $widget->set_visible(1);
2630 $grid->attach($widget, 1, $row, 1, 1);
2631 $row++;
2632 }
2633
2634 return $grid;
2635};
2636
2637my $create_raid_disk_grid = sub {
2638 my $disk_labeled_widgets = [];
2639 for (my $i = 0; $i < @$hds; $i++) {
2640 my $disk_selector = Gtk3::ComboBoxText->new();
2641 $disk_selector->append_text("-- do not use --");
2642 $disk_selector->set_active(0);
2643 $disk_selector->set_visible(1);
2644 foreach my $hd (@$hds) {
2645 my ($disk, $devname, $size, $model) = @$hd;
2646 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2647 $disk_selector->{pve_disk_id} = $i;
2648 $disk_selector->signal_connect (changed => sub {
2649 my $w = shift;
2650 my $diskid = $w->{pve_disk_id};
2651 my $a = $w->get_active - 1;
2652 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2653 });
2654 }
2655
bd3a2e26 2656 if ($hdoption_first_setup) {
c7779156
FG
2657 $disk_selector->set_active ($i+1) if $hds->[$i];
2658 } else {
2659 my $hdind = 0;
2660 if (my $cur_hd = $config_options->{"disksel$i"}) {
2661 foreach my $hd (@$hds) {
2662 if (@$hd[1] eq @$cur_hd[1]) {
2663 $disk_selector->set_active($hdind+1);
2664 last;
2665 }
2666 $hdind++;
2667 }
2668 }
2669 }
2670
2671 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2672 }
2673
2674 my $scrolled_window = Gtk3::ScrolledWindow->new();
2675 $scrolled_window->set_hexpand(1);
650a9aab 2676 $scrolled_window->set_propagate_natural_height(1) if @$hds > 4;
c7779156
FG
2677 $scrolled_window->add(&$create_label_widget_grid($disk_labeled_widgets));
2678 $scrolled_window->set_policy('never', 'automatic');
2679
2680 return $scrolled_window;
2681# &$create_label_widget_grid($disk_labeled_widgets)
2682};
2683
d7fe65ff
TL
2684# shared between different ui parts (e.g., ZFS and "normal" single disk FS)
2685my $hdsize_size_adj;
2686my $hdsize_entry_buffer;
2687
2688my $get_hdsize_spinbtn = sub {
2689 my $hdsize = shift;
754abb44 2690
93c8fdb0
SI
2691 $hdsize_entry_buffer //= Gtk3::EntryBuffer->new(undef, 1);
2692
2693 if (defined($hdsize)) {
d7fe65ff 2694 $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
93c8fdb0
SI
2695 } else {
2696 die "called get_hdsize_spinbtn with \$hdsize_size_adj not defined but did not pass hdsize!\n"
2697 if !defined($hdsize_size_adj);
d7fe65ff
TL
2698 }
2699
2700 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2701 $spinbutton_hdsize->set_buffer($hdsize_entry_buffer);
6039e2f1 2702 $spinbutton_hdsize->set_adjustment($hdsize_size_adj);
d7fe65ff
TL
2703 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2704 return $spinbutton_hdsize;
2705};
2706
c7779156
FG
2707my $create_raid_advanced_grid = sub {
2708 my $labeled_widgets = [];
6c99667a
FG
2709 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2710 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2711 $spinbutton_ashift->signal_connect ("value-changed" => sub {
2712 my $w = shift;
2713 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
2714 });
2715 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 2716 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 2717 push @$labeled_widgets, "ashift";
6c99667a 2718 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
2719
2720 my $combo_compress = Gtk3::ComboBoxText->new();
2721 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
2722 # note: gzip / lze not allowed for bootfs vdevs
2723 my $comp_opts = ["on","off","lzjb","lz4"];
2724 foreach my $opt (@$comp_opts) {
2725 $combo_compress->append($opt, $opt);
2726 }
2727 $config_options->{compress} = "on" if !defined($config_options->{compress});
2728 $combo_compress->set_active_id($config_options->{compress});
2729 $combo_compress->signal_connect (changed => sub {
2730 my $w = shift;
2731 $config_options->{compress} = $w->get_active_text();
2732 });
2733 push @$labeled_widgets, "compress";
2734 push @$labeled_widgets, $combo_compress;
2735
2736 my $combo_checksum = Gtk3::ComboBoxText->new();
2737 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2738 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2739 foreach my $opt (@$csum_opts) {
2740 $combo_checksum->append($opt, $opt);
2741 }
2742 $config_options->{checksum} = "on" if !($config_options->{checksum});
2743 $combo_checksum->set_active_id($config_options->{checksum});
2744 $combo_checksum->signal_connect (changed => sub {
2745 my $w = shift;
2746 $config_options->{checksum} = $w->get_active_text();
2747 });
2748 push @$labeled_widgets, "checksum";
2749 push @$labeled_widgets, $combo_checksum;
2750
2751 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2752 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2753 $spinbutton_copies->signal_connect ("value-changed" => sub {
2754 my $w = shift;
2755 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
2756 });
2757 $config_options->{copies} = 1 if !defined($config_options->{copies});
2758 $spinbutton_copies->set_value($config_options->{copies});
2759 push @$labeled_widgets, "copies", $spinbutton_copies;
2760
d7fe65ff 2761 push @$labeled_widgets, "hdsize", $get_hdsize_spinbtn->();
c7779156
FG
2762 return &$create_label_widget_grid($labeled_widgets);;
2763};
2764
aed81ff0
DM
2765sub create_hdoption_view {
2766
2767 my $dialog = Gtk3::Dialog->new();
2768
2769 $dialog->set_title("Harddisk options");
2770
2771 $dialog->add_button("_OK", 1);
2772
2773 my $contarea = $dialog->get_content_area();
2774
2775 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2776 $contarea->pack_start($hbox2, 1, 1, 10);
2777
2778 my $grid = Gtk3::Grid->new();
2779 $grid->set_column_spacing(10);
2780 $grid->set_row_spacing(10);
1464c7c9 2781
aed81ff0 2782 $hbox2->pack_start($grid, 1, 0, 10);
c6ed3b24
DM
2783
2784 my $row = 0;
2785
aed81ff0
DM
2786 # Filesystem type
2787
71590b6a 2788 my $label0 = Gtk3::Label->new("Filesystem");
aed81ff0 2789 $label0->set_alignment (1, 0.5);
c6ed3b24 2790 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 2791
bcbfab6b 2792 my $fstypecb = Gtk3::ComboBoxText->new();
aed81ff0 2793
121ebc59
DM
2794 my $fstype = ['ext3', 'ext4', 'xfs',
2795 'zfs (RAID0)', 'zfs (RAID1)',
2796 'zfs (RAID10)', 'zfs (RAIDZ-1)',
6f52fc3d
DM
2797 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2798
2799 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 2800 if $setup->{enable_btrfs};
aed81ff0 2801
c6ed3b24
DM
2802 my $tcount = 0;
2803 foreach my $tmp (@$fstype) {
2804 $fstypecb->append_text($tmp);
2805 $fstypecb->set_active ($tcount)
2806 if $config_options->{filesys} eq $tmp;
2807 $tcount++;
2808 }
2809
2810 $grid->attach($fstypecb, 1, $row, 1, 1);
2811
2812 $hbox2->show_all();
2813
2814 $row++;
2815
c7779156
FG
2816 my $sep = Gtk3::HSeparator->new();
2817 $sep->set_visible(1);
2818 $grid->attach($sep, 0, $row, 2, 1);
2819 $row++;
aed81ff0 2820
f0a0d90b
TL
2821 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.");
2822 $hw_raid_note->set_line_wrap(1);
2823 $hw_raid_note->set_max_width_chars(30);
f0a0d90b
TL
2824 $hw_raid_note->set_visible(0);
2825 $grid->attach($hw_raid_note, 0, $row++, 2, 1);
2826
c7779156 2827 my $hdsize_labeled_widgets = [];
aed81ff0 2828
c7779156 2829 # size compute
c6ed3b24 2830 my $hdsize = 0;
aed81ff0
DM
2831 if ( -b $target_hd) {
2832 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
c6ed3b24 2833 } elsif ($target_hd) {
aed81ff0
DM
2834 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
2835 }
2836
d7fe65ff 2837 my $spinbutton_hdsize = $get_hdsize_spinbtn->($hdsize);
c7779156 2838 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
aed81ff0
DM
2839
2840 my $entry_swapsize = Gtk3::Entry->new();
2841 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
2842 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 2843 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 2844 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
2845
2846 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
2847 if ($setup->{product} eq 'pve') {
2848 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
2849 $entry_maxroot->signal_connect (key_press_event => \&check_float);
2850 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
2851 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
2852 }
aed81ff0
DM
2853
2854 my $entry_minfree = Gtk3::Entry->new();
034f75e4 2855 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0 2856 $entry_minfree->signal_connect (key_press_event => \&check_float);
e093944c 2857 $entry_minfree->set_text($config_options->{minfree}) if defined($config_options->{minfree});
c7779156 2858 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 2859
b6e875ca
DM
2860 my $entry_maxvz;
2861 if ($setup->{product} eq 'pve') {
2862 $entry_maxvz = Gtk3::Entry->new();
2863 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
2864 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 2865 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
2866 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
2867 }
c7779156
FG
2868
2869 my $options_stack = Gtk3::Stack->new();
2870 $options_stack->set_visible(1);
2871 $options_stack->set_hexpand(1);
2872 $options_stack->set_vexpand(1);
2873 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
2874 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
2875 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
2876 $options_stack->set_visible_child_name("raiddisk");
2877 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2878 $options_stack_switcher->set_halign('center');
2879 $options_stack_switcher->set_stack($options_stack);
2880 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
2881 $row++;
2882 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 2883 $row++;
aed81ff0 2884
bd3a2e26 2885 $hdoption_first_setup = 0;
c7779156
FG
2886
2887 my $switch_view = sub {
2888 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
2889 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 2890
c7779156
FG
2891 $target_hd_combo->set_visible(!$raid);
2892 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
2893 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
f0a0d90b 2894 $hw_raid_note->set_visible($raid);
c7779156
FG
2895 $options_stack_switcher->set_visible($enable_zfs_opts);
2896 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
2897 if ($raid) {
c6ed3b24 2898 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 2899 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 2900 } else {
c6ed3b24
DM
2901 $target_hd_label->set_text("Target Harddisk: ");
2902 }
c7779156
FG
2903 my (undef, $pref_width) = $dialog->get_preferred_width();
2904 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 2905 $pref_height = 750 if $pref_height > 750;
c7779156 2906 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
2907 };
2908
c7779156 2909 &$switch_view();
f7b853d1
DM
2910
2911 $fstypecb->signal_connect (changed => sub {
2912 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 2913 &$switch_view();
f7b853d1
DM
2914 });
2915
95844cc6
TL
2916 my $sep2 = Gtk3::HSeparator->new();
2917 $sep2->set_visible(1);
2918 $contarea->pack_end($sep2, 1, 1, 10);
2919
c6ed3b24 2920 $dialog->show();
aed81ff0
DM
2921
2922 $dialog->run();
2923
2924 my $get_float = sub {
2925 my ($entry) = @_;
2926
2927 my $text = $entry->get_text();
2928 return undef if !defined($text);
2929
2930 $text =~ s/^\s+//;
2931 $text =~ s/\s+$//;
2932
2933 return undef if $text !~ m/^\d+(\.\d+)?$/;
2934
2935 return $text;
2936 };
2937
2938 my $tmp;
2939
2940 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
2941 $config_options->{hdsize} = $tmp;
2942 } else {
2943 delete $config_options->{hdsize};
2944 }
2945
2946 if (defined($tmp = &$get_float($entry_swapsize))) {
2947 $config_options->{swapsize} = $tmp;
2948 } else {
2949 delete $config_options->{swapsize};
2950 }
2951
2952 if (defined($tmp = &$get_float($entry_maxroot))) {
2953 $config_options->{maxroot} = $tmp;
2954 } else {
2955 delete $config_options->{maxroot};
2956 }
2957
2958 if (defined($tmp = &$get_float($entry_minfree))) {
2959 $config_options->{minfree} = $tmp;
2960 } else {
2961 delete $config_options->{minfree};
2962 }
2963
b6e875ca 2964 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
2965 $config_options->{maxvz} = $tmp;
2966 } else {
2967 delete $config_options->{maxvz};
2968 }
2969
2970 $dialog->destroy();
2971}
2972
121ebc59 2973my $get_raid_devlist = sub {
c6ed3b24
DM
2974
2975 my $dev_name_hash = {};
2976
2977 my $devlist = [];
5f8e86d5 2978 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24
DM
2979 if (my $hd = $config_options->{"disksel$i"}) {
2980 my ($disk, $devname, $size, $model) = @$hd;
1464c7c9 2981 die "device '$devname' is used more than once\n"
c6ed3b24
DM
2982 if $dev_name_hash->{$devname};
2983 $dev_name_hash->{$devname} = $hd;
2984 push @$devlist, $hd;
2985 }
2986 }
2987
121ebc59
DM
2988 return $devlist;
2989};
2990
14aacec8
FG
2991sub zfs_mirror_size_check {
2992 my ($expected, $actual) = @_;
2993
2994 die "mirrored disks must have same size\n"
2995 if abs($expected - $actual) > $expected / 10;
2996}
2997
121ebc59
DM
2998sub get_zfs_raid_setup {
2999
3000 my $filesys = $config_options->{filesys};
3001
3002 my $devlist = &$get_raid_devlist();
3003
224bb7b0 3004 my $diskcount = scalar(@$devlist);
0cfa502c 3005 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24 3006
121ebc59
DM
3007 my $bootdevlist = [];
3008
c6ed3b24
DM
3009 my $cmd= '';
3010 if ($filesys eq 'zfs (RAID0)') {
3011 push @$bootdevlist, @$devlist[0];
3012 foreach my $hd (@$devlist) {
3013 $cmd .= " @$hd[1]";
3014 }
3015 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 3016 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 3017 $cmd .= ' mirror ';
269c66a6 3018 my $hd = @$devlist[0];
14aacec8 3019 my $expected_size = @$hd[2]; # all disks need approximately same size
269c66a6 3020 foreach $hd (@$devlist) {
14aacec8 3021 zfs_mirror_size_check($expected_size, @$hd[2]);
c6ed3b24
DM
3022 $cmd .= " @$hd[1]";
3023 push @$bootdevlist, $hd;
3024 }
3025 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 3026 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 3027 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 3028
c6ed3b24
DM
3029 push @$bootdevlist, @$devlist[0], @$devlist[1];
3030
224bb7b0 3031 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
3032 my $hd1 = @$devlist[$i];
3033 my $hd2 = @$devlist[$i+1];
14aacec8 3034 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
c6ed3b24
DM
3035 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
3036 }
3037
3038 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3039 my $level = $1;
3040 my $mindisks = 2 + $level;
0cfa502c 3041 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 3042 my $hd = @$devlist[0];
14aacec8 3043 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 3044 $cmd .= " raidz$level";
269c66a6 3045 foreach $hd (@$devlist) {
14aacec8 3046 zfs_mirror_size_check($expected_size, @$hd[2]);
c6ed3b24
DM
3047 $cmd .= " @$hd[1]";
3048 push @$bootdevlist, $hd;
3049 }
3050 } else {
3051 die "unknown zfs mode '$filesys'\n";
3052 }
3053
3054 return ($devlist, $bootdevlist, $cmd);
3055}
3056
121ebc59
DM
3057sub get_btrfs_raid_setup {
3058
3059 my $filesys = $config_options->{filesys};
3060
3061 my $devlist = &$get_raid_devlist();
3062
3063 my $diskcount = scalar(@$devlist);
0cfa502c 3064 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
3065
3066 my $mode;
3067
3068 if ($diskcount == 1) {
3069 $mode = 'single';
3070 } else {
3071 if ($filesys eq 'btrfs (RAID0)') {
3072 $mode = 'raid0';
3073 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 3074 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
3075 $mode = 'raid1';
3076 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 3077 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
3078 $mode = 'raid10';
3079 } else {
9d69f3d3 3080 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
3081 }
3082 }
3083
3084 return ($devlist, $mode);
3085}
3086
218a4b6b 3087my $last_hd_selected = 0;
89a12446
DM
3088sub create_hdsel_view {
3089
451b1da5 3090 $prev_btn->set_sensitive(1); # enable previous button at this point
201a5120 3091
71590b6a 3092 cleanup_view();
89a12446 3093
71590b6a
OB
3094 my $vbox = Gtk3::VBox->new(0, 0);
3095 $inbox->pack_start($vbox, 1, 0, 0);
3096 my $hbox = Gtk3::HBox->new(0, 0);
3097 $vbox->pack_start($hbox, 0, 0, 10);
968fa90b 3098
89a12446 3099 my ($disk, $devname, $size, $model) = @{@$hds[0]};
9227a70f 3100 $target_hd = $devname if !defined($target_hd);
89a12446 3101
71590b6a
OB
3102 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3103 $hbox->pack_start($target_hd_label, 0, 0, 0);
89a12446 3104
bcbfab6b 3105 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 3106
1aa5bd02
DM
3107 foreach my $hd (@$hds) {
3108 ($disk, $devname, $size, $model) = @$hd;
71590b6a 3109 $target_hd_combo->append_text (get_device_desc($devname, $size, $model));
1aa5bd02 3110 }
89a12446 3111
90af1603
OB
3112 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3113 if ($raid) {
3114 $target_hd_label->set_text("Target: $config_options->{filesys} ");
3115 $target_hd_combo->set_visible(0);
3116 $target_hd_combo->set_no_show_all(1);
3117 }
218a4b6b 3118 $target_hd_combo->set_active($last_hd_selected);
71590b6a 3119 $target_hd_combo->signal_connect(changed => sub {
1aa5bd02
DM
3120 $a = shift->get_active;
3121 my ($disk, $devname) = @{@$hds[$a]};
3b959bef 3122 $last_hd_selected = $a;
1aa5bd02 3123 $target_hd = $devname;
1aa5bd02 3124 });
1464c7c9 3125
71590b6a 3126 $hbox->pack_start($target_hd_combo, 0, 0, 10);
aed81ff0 3127
71590b6a 3128 my $options = Gtk3::Button->new('_Options');
aed81ff0
DM
3129 $options->signal_connect (clicked => \&create_hdoption_view);
3130 $hbox->pack_start ($options, 0, 0, 0);
3131
89a12446
DM
3132
3133 $inbox->show_all;
3134
201a5120 3135 display_html();
c6ed3b24 3136
71590b6a 3137 set_next(undef, sub {
c6ed3b24
DM
3138
3139 if ($config_options->{filesys} =~ m/zfs/) {
a7d40341 3140 my ($devlist) = eval { get_zfs_raid_setup() };
c6ed3b24 3141 if (my $err = $@) {
303dfb2c
TL
3142 display_message("Warning: $err\nPlease fix ZFS setup first.");
3143 return;
c6ed3b24 3144 }
303dfb2c 3145 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
121ebc59 3146 } elsif ($config_options->{filesys} =~ m/btrfs/) {
a7d40341 3147 my ($devlist) = eval { get_btrfs_raid_setup() };
121ebc59 3148 if (my $err = $@) {
303dfb2c
TL
3149 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3150 return;
121ebc59 3151 }
303dfb2c 3152 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
c6ed3b24 3153 } else {
a7d40341 3154 $config_options->{target_hds} = [ $target_hd ];
c6ed3b24 3155 }
303dfb2c
TL
3156
3157 $step_number++;
3158 create_country_view();
c6ed3b24 3159 });
89a12446
DM
3160}
3161
3162sub create_extract_view {
3163
71590b6a 3164 cleanup_view();
89a12446 3165
550958aa
DM
3166 display_info();
3167
201a5120 3168 $next->set_sensitive(0);
ac3ee85b
TL
3169 $prev_btn->set_sensitive(0);
3170 $prev_btn->hide();
89a12446 3171
71590b6a 3172 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 3173 $inbox->pack_start ($vbox, 1, 0, 0);
71590b6a 3174 my $hbox = Gtk3::HBox->new(0, 0);
53986d77 3175 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3176
71590b6a 3177 my $vbox2 = Gtk3::VBox->new(0, 0);
89a12446
DM
3178 $hbox->pack_start ($vbox2, 0, 0, 0);
3179
71590b6a 3180 $progress_status = Gtk3::Label->new('');
89a12446 3181 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3182
7becc472 3183 $progress = Gtk3::ProgressBar->new;
45feca6f 3184 $progress->set_show_text(1);
7becc472 3185 $progress->set_size_request (600, -1);
89a12446 3186
71590b6a 3187 $vbox2->pack_start($progress, 0, 0, 0);
89a12446 3188
201a5120 3189 $inbox->show_all();
89a12446
DM
3190
3191 my $tdir = $opt_testmode ? "target" : "/target";
3192 mkdir $tdir;
97980bf2 3193 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446 3194
71590b6a 3195 eval { extract_data($base, $tdir); };
89a12446
DM
3196 my $err = $@;
3197
201a5120 3198 $next->set_sensitive(1);
89a12446 3199
71590b6a 3200 set_next("_Reboot", sub { exit (0); } );
89a12446 3201
296cf41f 3202 if ($err) {
201a5120
OB
3203 display_html("fail.htm");
3204 display_error($err);
296cf41f 3205 } else {
201a5120
OB
3206 cleanup_view();
3207 display_html("success.htm");
296cf41f 3208 }
89a12446
DM
3209}
3210
89a12446
DM
3211sub create_intro_view {
3212
451b1da5 3213 $prev_btn->set_sensitive(0);
201a5120
OB
3214
3215 cleanup_view();
89a12446 3216
bdeca872
DM
3217 if ($setup->{product} eq 'pve') {
3218 eval {
3219 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3220 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
3221 display_error("No support for KVM virtualisation detected.\n\n" .
3222 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3223 }
3224 };
3225 }
7fff0d85 3226
201a5120 3227 display_html();
89a12446 3228
201a5120 3229 $step_number++;
71590b6a 3230 set_next("I a_gree", \&create_hdsel_view);
89a12446
DM
3231}
3232
71590b6a 3233$ipconf = get_ip_config();
89a12446 3234
9d1f1ee3 3235$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3236
3237# read country, kmap and timezone infos
71590b6a 3238$cmap = read_cmap();
89a12446 3239
9d1f1ee3
FG
3240if (!defined($cmap->{country}->{$country})) {
3241 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3242 $country = undef;
3243}
3244
89a12446
DM
3245create_main_window ();
3246
ff2ce71c
FG
3247my $initial_error = 0;
3248
89a12446
DM
3249if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3250 print "no hardisks found\n";
ff2ce71c 3251 $initial_error = 1;
201a5120 3252 display_html("nohds.htm");
71590b6a 3253 set_next("Reboot", sub { exit(0); } );
89a12446 3254} else {
89a12446
DM
3255 foreach my $hd (@$hds) {
3256 my ($disk, $devname) = @$hd;
3257 next if $devname =~ m|^/dev/md\d+$|;
3258 print "found Disk$disk N:$devname\n";
3259 }
89a12446
DM
3260}
3261
72836708
FG
3262if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3263 print "no network interfaces found\n";
3264 $initial_error = 1;
201a5120 3265 display_html("nonics.htm");
71590b6a 3266 set_next("Reboot", sub { exit(0); } );
72836708
FG
3267}
3268
ff2ce71c
FG
3269create_intro_view () if !$initial_error;
3270
7becc472 3271Gtk3->main;
89a12446
DM
3272
3273exit 0;