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