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