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