]> git.proxmox.com Git - pve-installer.git/blame - proxinstall
d/control: pass ${perl:Depends} to Depends field
[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 694 } elsif ($dev =~ m|^/dev/xvd[a-z]$|) {
91749786 695 # Citrix Hypervisor blockdev
a8692b68 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;
d6524c52
TL
2029
2030 my $new = $device_active_map->{$current->get_active()};
2031 return if $new eq $ipconf->{selected};
2032
2033 $ipconf->{selected} = $new;
5b6ba737 2034 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
ebc4f76f 2035 $config->{mngmt_nic} = $iface->{name};
5b6ba737
FG
2036 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
2037 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
2038 $ipconf_entry_mask->set_text($iface->{inet}->{mask} || $iface->{inet6}->{mask})
2039 if $iface->{inet}->{mask} || $iface->{inet6}->{mask};
2040 };
2041
fe44bd92
FG
2042 my $i = 0;
2043 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
2044 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
ebc4f76f
TL
2045 $device_active_map->{$i} = $index;
2046 $device_active_reverse_map->{$ipconf->{ifaces}->{$index}->{name}} = $i;
fe44bd92
FG
2047 if ($ipconf_first_view && $index == $ipconf->{default}) {
2048 $device_cb->set_active($i);
5b6ba737 2049 &$device_change_handler($device_cb);
fe44bd92
FG
2050 $ipconf_first_view = 0;
2051 }
71590b6a 2052 $device_cb->signal_connect('changed' => $device_change_handler);
fe44bd92
FG
2053 $i++;
2054 }
2055
ebc4f76f
TL
2056 if (my $nic = $config->{mngmt_nic}) {
2057 $device_cb->set_active($device_active_reverse_map->{$nic} // 0);
2058 } else {
2059 $device_cb->set_active(0);
2060 }
5b6ba737 2061
71590b6a
OB
2062 my $devicebox = Gtk3::HBox->new(0, 0);
2063 my $label = Gtk3::Label->new("Management Interface:");
2064 $label->set_size_request(150, -1);
2065 $label->set_alignment(1, 0.5);
2066 $devicebox->pack_start($label, 0, 0, 10);
2067 $devicebox->pack_start($device_cb, 0, 0, 0);
fe44bd92 2068
71590b6a 2069 $vbox2->pack_start($devicebox, 0, 0, 2);
968fa90b 2070
ebc4f76f 2071 my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
1464c7c9 2072
968fa90b 2073 my ($hostbox, $hostentry) =
71590b6a
OB
2074 create_text_input($hn, 'Hostname (FQDN):');
2075 $vbox2->pack_start($hostbox, 0, 0, 2);
89a12446 2076
71590b6a 2077 $vbox2->pack_start($ipbox, 0, 0, 2);
89a12446 2078
71590b6a 2079 $vbox2->pack_start($maskbox, 0, 0, 2);
89a12446 2080
ebc4f76f 2081 $gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
89a12446
DM
2082
2083 my $gwbox;
d2120e51 2084 ($gwbox, $ipconf_entry_gw) =
71590b6a 2085 create_text_input($gateway, 'Gateway:');
89a12446 2086
71590b6a 2087 $vbox2->pack_start($gwbox, 0, 0, 2);
89a12446 2088
ebc4f76f 2089 $dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
89a12446
DM
2090
2091 my $dnsbox;
d2120e51 2092 ($dnsbox, $ipconf_entry_dns) =
71590b6a 2093 create_text_input($dnsserver, 'DNS Server:');
89a12446 2094
71590b6a 2095 $vbox2->pack_start($dnsbox, 0, 0, 0);
89a12446
DM
2096
2097 $inbox->show_all;
71590b6a 2098 set_next(undef, sub {
d2120e51
DM
2099
2100 # verify hostname
1464c7c9 2101
89a12446 2102 my $text = $hostentry->get_text();
968fa90b 2103
89a12446
DM
2104 $text =~ s/^\s+//;
2105 $text =~ s/\s+$//;
2106
ebc4f76f
TL
2107 $config->{fqdn} = $text;
2108
ac3757a9 2109 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
968fa90b 2110
24973868
WB
2111 # Debian does not support purely numeric hostnames
2112 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
2113 display_message("Purely numeric hostnames are not allowed.");
2114 $hostentry->grab_focus();
2115 return;
2116 }
2117
a39bc1f2 2118 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
89a12446
DM
2119 $text =~ m/^([^\.]+)\.(\S+)$/) {
2120 $hostname = $1;
2121 $domain = $2;
d2120e51 2122 } else {
71590b6a 2123 display_message("Hostname does not look like a fully qualified domain name.");
d2120e51 2124 $hostentry->grab_focus();
89a12446
DM
2125 return;
2126 }
d2120e51
DM
2127
2128 # verify ip address
2129
2130 $text = $ipconf_entry_addr->get_text();
2131 $text =~ s/^\s+//;
2132 $text =~ s/\s+$//;
2133 if ($text =~ m!^($IPV4RE)$!) {
2134 $ipaddress = $text;
b6200603
DM
2135 $ipversion = 4;
2136 } elsif ($text =~ m!^($IPV6RE)$!) {
1464c7c9 2137 $ipaddress = $text;
b6200603 2138 $ipversion = 6;
d2120e51 2139 } else {
71590b6a 2140 display_message("IP address is not valid.");
d2120e51
DM
2141 $ipconf_entry_addr->grab_focus();
2142 return;
2143 }
ebc4f76f 2144 $config->{ipaddress} = $ipaddress;
d2120e51
DM
2145
2146 $text = $ipconf_entry_mask->get_text();
2147 $text =~ s/^\s+//;
2148 $text =~ s/\s+$//;
b6200603
DM
2149 if (($ipversion == 6) && ($text =~ m/^(\d+)$/) && ($1 >= 8) && ($1 <= 126)) {
2150 $netmask = $text;
2151 } elsif (($ipversion == 4) && defined($ipv4_mask_hash->{$text})) {
d2120e51
DM
2152 $netmask = $text;
2153 } else {
71590b6a 2154 display_message("Netmask is not valid.");
d2120e51
DM
2155 $ipconf_entry_mask->grab_focus();
2156 return;
2157 }
ebc4f76f 2158 $config->{netmask} = $netmask;
d2120e51
DM
2159
2160 $text = $ipconf_entry_gw->get_text();
2161 $text =~ s/^\s+//;
2162 $text =~ s/\s+$//;
b6200603
DM
2163 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2164 $gateway = $text;
2165 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2166 $gateway = $text;
2167 } else {
71590b6a 2168 display_message("Gateway is not valid.");
d2120e51
DM
2169 $ipconf_entry_gw->grab_focus();
2170 return;
2171 }
ebc4f76f 2172 $config->{gateway} = $gateway;
1464c7c9 2173
d2120e51
DM
2174 $text = $ipconf_entry_dns->get_text();
2175 $text =~ s/^\s+//;
2176 $text =~ s/\s+$//;
b6200603
DM
2177 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2178 $dnsserver = $text;
2179 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
d2120e51
DM
2180 $dnsserver = $text;
2181 } else {
71590b6a 2182 display_message("DNS server is not valid.");
d2120e51
DM
2183 $ipconf_entry_dns->grab_focus();
2184 return;
2185 }
ebc4f76f 2186 $config->{dnsserver} = $dnsserver;
1464c7c9 2187
d2120e51 2188 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
1464c7c9 2189
201a5120 2190 $step_number++;
2e33c3f0 2191 create_ack_view();
89a12446
DM
2192 });
2193
2194 $hostentry->grab_focus();
2195}
2196
2e33c3f0
OB
2197sub create_ack_view {
2198
2199 cleanup_view();
2200
2201 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
2202 my $ack_html = "${proxmox_libdir}/html/$steps[$step_number]->{html}";
2203 my $html_data = file_get_contents($ack_template);
2204
2205 my %config_values = (
a7d40341 2206 __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
0470018e 2207 __target_fs__ => $config_options->{filesys},
0ddd2227 2208 __country__ => $cmap->{country}->{$country}->{name},
2e33c3f0
OB
2209 __timezone__ => $timezone,
2210 __keymap__ => $keymap,
2211 __mailto__ => $mailto,
2212 __interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
2213 __hostname__ => $hostname,
2214 __ip__ => $ipaddress,
2215 __netmask__ => $netmask,
2216 __gateway__ => $gateway,
2217 __dnsserver__ => $dnsserver,
2218 );
2219
2220 while ( my ($k, $v) = each %config_values) {
2221 $html_data =~ s/$k/$v/g;
2222 }
2223
2224 write_config($html_data, $ack_html);
2225
2226 display_html();
2227
2228 set_next(undef, sub {
2229 $step_number++;
2230 create_extract_view();
2231 });
2232}
2233
89a12446
DM
2234sub get_device_desc {
2235 my ($devname, $size, $model) = @_;
2236
d2120e51 2237 if ($size && ($size > 0)) {
1bd457bb 2238 $size = int($size/2048); # size in MB, from 512B "sectors"
89a12446 2239
d2120e51 2240 my $text = "$devname (";
89a12446
DM
2241 if ($size >= 1024) {
2242 $size = int($size/1024); # size in GB
d2120e51 2243 $text .= "${size}GB";
89a12446 2244 } else {
d2120e51 2245 $text .= "${size}MB";
89a12446
DM
2246 }
2247
d2120e51
DM
2248 $text .= ", $model" if $model;
2249 $text .= ")";
2250
89a12446
DM
2251 } else {
2252 return $devname;
2253 }
2254}
2255
2256sub update_layout {
2257 my ($cb, $kmap) = @_;
2258
2259 my $ind;
2260 my $def;
2261 my $i = 0;
2262 my $kmaphash = $cmap->{kmaphash};
2263 foreach my $layout (sort keys %$kmaphash) {
2264 $def = $i if $kmaphash->{$layout} eq 'en-us';
2265 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2266 $i++;
2267 }
2268
71590b6a 2269 $cb->set_active($ind || $def || 0);
89a12446
DM
2270}
2271
2272my $lastzonecb;
2273sub update_zonelist {
2274 my ($box, $cc) = @_;
2275
2276 my $cczones = $cmap->{cczones};
2277 my $zones = $cmap->{zones};
2278
2279 my $sel;
2280 if ($lastzonecb) {
2281 $sel = $lastzonecb->get_active_text();
2282 $box->remove ($lastzonecb);
2283 } else {
2284 $sel = $timezone; # used once to select default
2285 }
2286
bcbfab6b 2287 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
71590b6a 2288 $cb->set_size_request(200, -1);
89a12446 2289
71590b6a 2290 $cb->signal_connect('changed' => sub {
89a12446
DM
2291 $timezone = $cb->get_active_text();
2292 });
2293
2294 my @za;
2295 if ($cc && defined ($cczones->{$cc})) {
2296 @za = keys %{$cczones->{$cc}};
2297 } else {
2298 @za = keys %$zones;
2299 }
2300 my $ind;
2301 my $i = 0;
2302 foreach my $zone (sort @za) {
2303 $ind = $i if $sel && $zone eq $sel;
71590b6a 2304 $cb->append_text($zone);
89a12446
DM
2305 $i++;
2306 }
2307
71590b6a 2308 $cb->set_active($ind || 0);
89a12446
DM
2309
2310 $cb->show;
71590b6a 2311 $box->pack_start($cb, 0, 0, 0);
89a12446
DM
2312}
2313
2314sub create_password_view {
2315
71590b6a 2316 cleanup_view();
89a12446 2317
71590b6a
OB
2318 my $vbox2 = Gtk3::VBox->new(0, 0);
2319 $inbox->pack_start($vbox2, 1, 0, 0);
2320 my $vbox = Gtk3::VBox->new(0, 0);
2321 $vbox2->pack_start($vbox, 0, 0, 10);
2322
2323 my $hbox1 = Gtk3::HBox->new(0, 0);
2324 my $label = Gtk3::Label->new("Password");
2325 $label->set_size_request(150, -1);
2326 $label->set_alignment(1, 0.5);
2327 $hbox1->pack_start($label, 0, 0, 10);
2328 my $pwe1 = Gtk3::Entry->new();
2329 $pwe1->set_visibility(0);
201a5120 2330 $pwe1->set_text($password) if $password;
71590b6a
OB
2331 $pwe1->set_size_request(200, -1);
2332 $hbox1->pack_start($pwe1, 0, 0, 0);
2333
2334 my $hbox2 = Gtk3::HBox->new(0, 0);
2335 $label = Gtk3::Label->new("Confirm");
2336 $label->set_size_request(150, -1);
2337 $label->set_alignment(1, 0.5);
2338 $hbox2->pack_start($label, 0, 0, 10);
2339 my $pwe2 = Gtk3::Entry->new();
2340 $pwe2->set_visibility(0);
201a5120 2341 $pwe2->set_text($password) if $password;
71590b6a
OB
2342 $pwe2->set_size_request(200, -1);
2343 $hbox2->pack_start($pwe2, 0, 0, 0);
2344
2345 my $hbox3 = Gtk3::HBox->new(0, 0);
2346 $label = Gtk3::Label->new("E-Mail");
2347 $label->set_size_request(150, -1);
2348 $label->set_alignment(1, 0.5);
2349 $hbox3->pack_start($label, 0, 0, 10);
2350 my $eme = Gtk3::Entry->new();
2351 $eme->set_size_request(200, -1);
201a5120 2352 $eme->set_text($mailto);
71590b6a 2353 $hbox3->pack_start($eme, 0, 0, 0);
89a12446
DM
2354
2355
71590b6a
OB
2356 $vbox->pack_start($hbox1, 0, 0, 5);
2357 $vbox->pack_start($hbox2, 0, 0, 5);
2358 $vbox->pack_start($hbox3, 0, 0, 15);
89a12446
DM
2359
2360 $inbox->show_all;
2361
201a5120 2362 display_html();
89a12446
DM
2363
2364 set_next (undef, sub {
2365
2366 my $t1 = $pwe1->get_text;
2367 my $t2 = $pwe2->get_text;
2368
2369 if (length ($t1) < 5) {
71590b6a 2370 display_message("Password is too short.");
89a12446
DM
2371 $pwe1->grab_focus();
2372 return;
2373 }
2374
2375 if ($t1 ne $t2) {
71590b6a 2376 display_message("Password does not match.");
89a12446
DM
2377 $pwe1->grab_focus();
2378 return;
2379 }
2380
2381 my $t3 = $eme->get_text;
2382 if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
71590b6a 2383 display_message("E-Mail does not look like a valid address" .
89a12446
DM
2384 " (user\@domain.tld)");
2385 $eme->grab_focus();
2386 return;
a39bc1f2 2387 }
89a12446 2388
a39bc1f2 2389 if ($t3 eq 'mail@example.invalid') {
71590b6a 2390 display_message("Please enter a valid E-Mail address");
a39bc1f2
FG
2391 $eme->grab_focus();
2392 return;
89a12446
DM
2393 }
2394
2395 $password = $t1;
2396 $mailto = $t3;
2397
201a5120 2398 $step_number++;
89a12446
DM
2399 create_ipconf_view();
2400 });
2401
2402 $pwe1->grab_focus();
2403
2404}
2405
2406sub create_country_view {
2407
71590b6a 2408 cleanup_view();
89a12446
DM
2409
2410 my $countryhash = $cmap->{countryhash};
2411 my $ctr = $cmap->{country};
2412
71590b6a
OB
2413 my $vbox2 = Gtk3::VBox->new(0, 0);
2414 $inbox->pack_start($vbox2, 1, 0, 0);
2415 my $vbox = Gtk3::VBox->new(0, 0);
2416 $vbox2->pack_start($vbox, 0, 0, 10);
89a12446 2417
71590b6a
OB
2418 my $w = Gtk3::Entry->new();
2419 $w->set_size_request(200, -1);
89a12446 2420
71590b6a
OB
2421 my $c = Gtk3::EntryCompletion->new();
2422 $c->set_text_column(0);
89a12446 2423 $c->set_minimum_key_length(0);
71590b6a
OB
2424 $c->set_popup_set_width(1);
2425 $c->set_inline_completion(1);
2426
2427 my $hbox2 = Gtk3::HBox->new(0, 0);
2428 my $label = Gtk3::Label->new("Time zone");
2429 $label->set_size_request(150, -1);
2430 $label->set_alignment(1, 0.5);
2431 $hbox2->pack_start($label, 0, 0, 10);
89a12446
DM
2432 update_zonelist ($hbox2);
2433
71590b6a
OB
2434 my $hbox3 = Gtk3::HBox->new(0, 0);
2435 $label = Gtk3::Label->new("Keyboard Layout");
2436 $label->set_size_request(150, -1);
2437 $label->set_alignment(1, 0.5);
2438 $hbox3->pack_start($label, 0, 0, 10);
89a12446 2439
bcbfab6b 2440 my $kmapcb = Gtk3::ComboBoxText->new();
89a12446
DM
2441 $kmapcb->set_size_request (200, -1);
2442 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2443 $kmapcb->append_text ($layout);
2444 }
2445
71590b6a 2446 update_layout($kmapcb);
89a12446
DM
2447 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2448
2449 $kmapcb->signal_connect ('changed' => sub {
2450 my $sel = $kmapcb->get_active_text();
2451 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2452 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2453 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
2454 syscmd ("setxkbmap $xkmap $xvar") if !$opt_testmode;
2455 $keymap = $kmap;
2456 }
2457 });
2458
2459 $w->signal_connect ('changed' => sub {
2460 my ($entry, $event) = @_;
2461 my $text = $entry->get_text;
2462
2463 if (my $cc = $countryhash->{lc($text)}) {
71590b6a 2464 update_zonelist($hbox2, $cc);
89a12446 2465 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
71590b6a 2466 update_layout($kmapcb, $kmap);
89a12446
DM
2467 }
2468 });
2469
2470 $w->signal_connect (key_press_event => sub {
2471 my ($entry, $event) = @_;
2472 my $text = $entry->get_text;
2473
7becc472
DM
2474 my $val = $event->get_keyval;
2475
2476 if ($val == Gtk3::Gdk::KEY_Tab) {
89a12446 2477 my $cc = $countryhash->{lc($text)};
1464c7c9 2478
89a12446
DM
2479 my $found = 0;
2480 my $compl;
7becc472 2481
4443aa27
DM
2482 if ($cc) {
2483 $found = 1;
2484 $compl = $ctr->{$cc}->{name};
2485 } else {
2486 foreach my $cc (keys %$ctr) {
2487 my $ct = $ctr->{$cc}->{name};
2488 if ($ct =~ m/^\Q$text\E.*$/i) {
2489 $found++;
2490 $compl = $ct;
2491 }
2492 last if $found > 1;
89a12446 2493 }
89a12446 2494 }
4443aa27 2495
89a12446 2496 if ($found == 1) {
7becc472 2497 $entry->set_text($compl);
3df718ea 2498 $c->complete();
89a12446
DM
2499 return undef;
2500 } else {
7becc472
DM
2501 #Gtk3::Gdk::beep();
2502 print chr(7); # beep ?
89a12446
DM
2503 }
2504
3df718ea
DM
2505 $c->complete();
2506
7becc472
DM
2507 my $buf = $w->get_buffer();
2508 $buf->insert_text(-1, '', -1); # popup selection
2509
89a12446
DM
2510 return 1;
2511 }
2512
2513 return undef;
2514 });
1464c7c9 2515
7becc472 2516 my $ls = Gtk3::ListStore->new('Glib::String');
89a12446
DM
2517 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2518 my $iter = $ls->append();
2519 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2520 }
2521 $c->set_model ($ls);
2522
968fa90b 2523 $w->set_completion ($c);
89a12446 2524
71590b6a 2525 my $hbox = Gtk3::HBox->new(0, 0);
89a12446 2526
71590b6a
OB
2527 $label = Gtk3::Label->new("Country");
2528 $label->set_alignment(1, 0.5);
2529 $label->set_size_request(150, -1);
2530 $hbox->pack_start($label, 0, 0, 10);
2531 $hbox->pack_start($w, 0, 0, 0);
89a12446 2532
71590b6a
OB
2533 $vbox->pack_start($hbox, 0, 0, 5);
2534 $vbox->pack_start($hbox2, 0, 0, 5);
2535 $vbox->pack_start($hbox3, 0, 0, 5);
89a12446 2536
9d1f1ee3 2537 if ($country && $ctr->{$country}) {
89a12446
DM
2538 $w->set_text ($ctr->{$country}->{name});
2539 }
2540
2541 $inbox->show_all;
2542
201a5120 2543 display_html();
89a12446
DM
2544 set_next (undef, sub {
2545
2546 my $text = $w->get_text;
2547
2548 if (my $cc = $countryhash->{lc($text)}) {
2549 $country = $cc;
201a5120 2550 $step_number++;
89a12446
DM
2551 create_password_view();
2552 return;
2553 } else {
71590b6a 2554 display_message("Please select a country first.");
89a12446
DM
2555 $w->grab_focus();
2556 }
2557 });
2558
2559 $w->grab_focus();
2560}
2561
c6ed3b24
DM
2562my $target_hd_combo;
2563my $target_hd_label;
2564
bd3a2e26 2565my $hdoption_first_setup = 1;
c6ed3b24 2566
c7779156
FG
2567my $create_basic_grid = sub {
2568 my $grid = Gtk3::Grid->new();
2569 $grid->set_visible(1);
2570 $grid->set_column_spacing(10);
2571 $grid->set_row_spacing(10);
2572 $grid->set_hexpand(1);
2573
2574 $grid->set_margin_start(5);
2575 $grid->set_margin_end(5);
2576 $grid->set_margin_top(5);
2577 $grid->set_margin_bottom(5);
2578
2579 return $grid;
2580};
2581
2582my $create_label_widget_grid = sub {
2583 my ($labeled_widgets) = @_;
2584
2585 my $grid = &$create_basic_grid();
2586 my $row = 0;
2587
2588 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2589 my $widget = @$labeled_widgets[$i+1];
2590 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2591 $label->set_visible(1);
2592 $label->set_alignment (1, 0.5);
2593 $grid->attach($label, 0, $row, 1, 1);
2594 $widget->set_visible(1);
2595 $grid->attach($widget, 1, $row, 1, 1);
2596 $row++;
2597 }
2598
2599 return $grid;
2600};
2601
2602my $create_raid_disk_grid = sub {
2603 my $disk_labeled_widgets = [];
2604 for (my $i = 0; $i < @$hds; $i++) {
2605 my $disk_selector = Gtk3::ComboBoxText->new();
2606 $disk_selector->append_text("-- do not use --");
2607 $disk_selector->set_active(0);
2608 $disk_selector->set_visible(1);
2609 foreach my $hd (@$hds) {
2610 my ($disk, $devname, $size, $model) = @$hd;
2611 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2612 $disk_selector->{pve_disk_id} = $i;
2613 $disk_selector->signal_connect (changed => sub {
2614 my $w = shift;
2615 my $diskid = $w->{pve_disk_id};
2616 my $a = $w->get_active - 1;
2617 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2618 });
2619 }
2620
bd3a2e26 2621 if ($hdoption_first_setup) {
c7779156
FG
2622 $disk_selector->set_active ($i+1) if $hds->[$i];
2623 } else {
2624 my $hdind = 0;
2625 if (my $cur_hd = $config_options->{"disksel$i"}) {
2626 foreach my $hd (@$hds) {
2627 if (@$hd[1] eq @$cur_hd[1]) {
2628 $disk_selector->set_active($hdind+1);
2629 last;
2630 }
2631 $hdind++;
2632 }
2633 }
2634 }
2635
2636 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2637 }
2638
2639 my $scrolled_window = Gtk3::ScrolledWindow->new();
2640 $scrolled_window->set_hexpand(1);
650a9aab 2641 $scrolled_window->set_propagate_natural_height(1) if @$hds > 4;
c7779156
FG
2642 $scrolled_window->add(&$create_label_widget_grid($disk_labeled_widgets));
2643 $scrolled_window->set_policy('never', 'automatic');
2644
2645 return $scrolled_window;
2646# &$create_label_widget_grid($disk_labeled_widgets)
2647};
2648
d7fe65ff
TL
2649# shared between different ui parts (e.g., ZFS and "normal" single disk FS)
2650my $hdsize_size_adj;
2651my $hdsize_entry_buffer;
2652
2653my $get_hdsize_spinbtn = sub {
2654 my $hdsize = shift;
754abb44 2655
93c8fdb0
SI
2656 $hdsize_entry_buffer //= Gtk3::EntryBuffer->new(undef, 1);
2657
2658 if (defined($hdsize)) {
d7fe65ff 2659 $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
93c8fdb0
SI
2660 } else {
2661 die "called get_hdsize_spinbtn with \$hdsize_size_adj not defined but did not pass hdsize!\n"
2662 if !defined($hdsize_size_adj);
d7fe65ff
TL
2663 }
2664
2665 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2666 $spinbutton_hdsize->set_buffer($hdsize_entry_buffer);
6039e2f1 2667 $spinbutton_hdsize->set_adjustment($hdsize_size_adj);
d7fe65ff
TL
2668 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2669 return $spinbutton_hdsize;
2670};
2671
c7779156
FG
2672my $create_raid_advanced_grid = sub {
2673 my $labeled_widgets = [];
6c99667a
FG
2674 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2675 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2676 $spinbutton_ashift->signal_connect ("value-changed" => sub {
2677 my $w = shift;
2678 $config_options->{ashift} = $w->get_value_as_int();
c7779156
FG
2679 });
2680 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
6c99667a 2681 $spinbutton_ashift->set_value($config_options->{ashift});
c7779156 2682 push @$labeled_widgets, "ashift";
6c99667a 2683 push @$labeled_widgets, $spinbutton_ashift;
c7779156
FG
2684
2685 my $combo_compress = Gtk3::ComboBoxText->new();
2686 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
2687 # note: gzip / lze not allowed for bootfs vdevs
2688 my $comp_opts = ["on","off","lzjb","lz4"];
2689 foreach my $opt (@$comp_opts) {
2690 $combo_compress->append($opt, $opt);
2691 }
2692 $config_options->{compress} = "on" if !defined($config_options->{compress});
2693 $combo_compress->set_active_id($config_options->{compress});
2694 $combo_compress->signal_connect (changed => sub {
2695 my $w = shift;
2696 $config_options->{compress} = $w->get_active_text();
2697 });
2698 push @$labeled_widgets, "compress";
2699 push @$labeled_widgets, $combo_compress;
2700
2701 my $combo_checksum = Gtk3::ComboBoxText->new();
2702 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2703 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2704 foreach my $opt (@$csum_opts) {
2705 $combo_checksum->append($opt, $opt);
2706 }
2707 $config_options->{checksum} = "on" if !($config_options->{checksum});
2708 $combo_checksum->set_active_id($config_options->{checksum});
2709 $combo_checksum->signal_connect (changed => sub {
2710 my $w = shift;
2711 $config_options->{checksum} = $w->get_active_text();
2712 });
2713 push @$labeled_widgets, "checksum";
2714 push @$labeled_widgets, $combo_checksum;
2715
2716 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2717 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2718 $spinbutton_copies->signal_connect ("value-changed" => sub {
2719 my $w = shift;
2720 $config_options->{copies} = $w->get_value_as_int();
c7779156
FG
2721 });
2722 $config_options->{copies} = 1 if !defined($config_options->{copies});
2723 $spinbutton_copies->set_value($config_options->{copies});
2724 push @$labeled_widgets, "copies", $spinbutton_copies;
2725
d7fe65ff 2726 push @$labeled_widgets, "hdsize", $get_hdsize_spinbtn->();
c7779156
FG
2727 return &$create_label_widget_grid($labeled_widgets);;
2728};
2729
aed81ff0
DM
2730sub create_hdoption_view {
2731
2732 my $dialog = Gtk3::Dialog->new();
2733
2734 $dialog->set_title("Harddisk options");
2735
2736 $dialog->add_button("_OK", 1);
2737
2738 my $contarea = $dialog->get_content_area();
2739
2740 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2741 $contarea->pack_start($hbox2, 1, 1, 10);
2742
2743 my $grid = Gtk3::Grid->new();
2744 $grid->set_column_spacing(10);
2745 $grid->set_row_spacing(10);
1464c7c9 2746
aed81ff0 2747 $hbox2->pack_start($grid, 1, 0, 10);
c6ed3b24
DM
2748
2749 my $row = 0;
2750
aed81ff0
DM
2751 # Filesystem type
2752
71590b6a 2753 my $label0 = Gtk3::Label->new("Filesystem");
aed81ff0 2754 $label0->set_alignment (1, 0.5);
c6ed3b24 2755 $grid->attach($label0, 0, $row, 1, 1);
1464c7c9 2756
bcbfab6b 2757 my $fstypecb = Gtk3::ComboBoxText->new();
aed81ff0 2758
121ebc59
DM
2759 my $fstype = ['ext3', 'ext4', 'xfs',
2760 'zfs (RAID0)', 'zfs (RAID1)',
2761 'zfs (RAID10)', 'zfs (RAIDZ-1)',
6f52fc3d
DM
2762 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2763
2764 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
c20d6ab0 2765 if $setup->{enable_btrfs};
aed81ff0 2766
c6ed3b24
DM
2767 my $tcount = 0;
2768 foreach my $tmp (@$fstype) {
2769 $fstypecb->append_text($tmp);
2770 $fstypecb->set_active ($tcount)
2771 if $config_options->{filesys} eq $tmp;
2772 $tcount++;
2773 }
2774
2775 $grid->attach($fstypecb, 1, $row, 1, 1);
2776
2777 $hbox2->show_all();
2778
2779 $row++;
2780
c7779156
FG
2781 my $sep = Gtk3::HSeparator->new();
2782 $sep->set_visible(1);
2783 $grid->attach($sep, 0, $row, 2, 1);
2784 $row++;
aed81ff0 2785
c7779156 2786 my $hdsize_labeled_widgets = [];
aed81ff0 2787
c7779156 2788 # size compute
c6ed3b24 2789 my $hdsize = 0;
aed81ff0
DM
2790 if ( -b $target_hd) {
2791 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
c6ed3b24 2792 } elsif ($target_hd) {
aed81ff0
DM
2793 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
2794 }
2795
d7fe65ff 2796 my $spinbutton_hdsize = $get_hdsize_spinbtn->($hdsize);
c7779156 2797 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
aed81ff0
DM
2798
2799 my $entry_swapsize = Gtk3::Entry->new();
2800 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
2801 $entry_swapsize->signal_connect (key_press_event => \&check_float);
9bb301fb 2802 $entry_swapsize->set_text($config_options->{swapsize}) if defined($config_options->{swapsize});
c7779156 2803 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
aed81ff0
DM
2804
2805 my $entry_maxroot = Gtk3::Entry->new();
0adc7ca0
DM
2806 if ($setup->{product} eq 'pve') {
2807 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
2808 $entry_maxroot->signal_connect (key_press_event => \&check_float);
2809 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
2810 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
2811 }
aed81ff0
DM
2812
2813 my $entry_minfree = Gtk3::Entry->new();
034f75e4 2814 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
aed81ff0 2815 $entry_minfree->signal_connect (key_press_event => \&check_float);
e093944c 2816 $entry_minfree->set_text($config_options->{minfree}) if defined($config_options->{minfree});
c7779156 2817 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
aed81ff0 2818
b6e875ca
DM
2819 my $entry_maxvz;
2820 if ($setup->{product} eq 'pve') {
2821 $entry_maxvz = Gtk3::Entry->new();
2822 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
2823 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2ba9752e 2824 $entry_maxvz->set_text($config_options->{maxvz}) if defined($config_options->{maxvz});
b6e875ca
DM
2825 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
2826 }
c7779156
FG
2827
2828 my $options_stack = Gtk3::Stack->new();
2829 $options_stack->set_visible(1);
2830 $options_stack->set_hexpand(1);
2831 $options_stack->set_vexpand(1);
2832 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
2833 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
2834 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
2835 $options_stack->set_visible_child_name("raiddisk");
2836 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2837 $options_stack_switcher->set_halign('center');
2838 $options_stack_switcher->set_stack($options_stack);
2839 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
2840 $row++;
2841 $grid->attach($options_stack, 0, $row, 2, 1);
c6ed3b24 2842 $row++;
aed81ff0 2843
bd3a2e26 2844 $hdoption_first_setup = 0;
c7779156
FG
2845
2846 my $switch_view = sub {
2847 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
2848 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
c6ed3b24 2849
c7779156
FG
2850 $target_hd_combo->set_visible(!$raid);
2851 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
2852 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
2853 $options_stack_switcher->set_visible($enable_zfs_opts);
2854 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
2855 if ($raid) {
c6ed3b24 2856 $target_hd_label->set_text("Target: $config_options->{filesys} ");
c7779156 2857 $options_stack->set_visible_child_name("raiddisk");
c6ed3b24 2858 } else {
c6ed3b24
DM
2859 $target_hd_label->set_text("Target Harddisk: ");
2860 }
c7779156
FG
2861 my (undef, $pref_width) = $dialog->get_preferred_width();
2862 my (undef, $pref_height) = $dialog->get_preferred_height();
650a9aab 2863 $pref_height = 750 if $pref_height > 750;
c7779156 2864 $dialog->resize($pref_width, $pref_height);
f7b853d1
DM
2865 };
2866
c7779156 2867 &$switch_view();
f7b853d1
DM
2868
2869 $fstypecb->signal_connect (changed => sub {
2870 $config_options->{filesys} = $fstypecb->get_active_text();
c7779156 2871 &$switch_view();
f7b853d1
DM
2872 });
2873
c6ed3b24 2874 $dialog->show();
aed81ff0
DM
2875
2876 $dialog->run();
2877
2878 my $get_float = sub {
2879 my ($entry) = @_;
2880
2881 my $text = $entry->get_text();
2882 return undef if !defined($text);
2883
2884 $text =~ s/^\s+//;
2885 $text =~ s/\s+$//;
2886
2887 return undef if $text !~ m/^\d+(\.\d+)?$/;
2888
2889 return $text;
2890 };
2891
2892 my $tmp;
2893
2894 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
2895 $config_options->{hdsize} = $tmp;
2896 } else {
2897 delete $config_options->{hdsize};
2898 }
2899
2900 if (defined($tmp = &$get_float($entry_swapsize))) {
2901 $config_options->{swapsize} = $tmp;
2902 } else {
2903 delete $config_options->{swapsize};
2904 }
2905
2906 if (defined($tmp = &$get_float($entry_maxroot))) {
2907 $config_options->{maxroot} = $tmp;
2908 } else {
2909 delete $config_options->{maxroot};
2910 }
2911
2912 if (defined($tmp = &$get_float($entry_minfree))) {
2913 $config_options->{minfree} = $tmp;
2914 } else {
2915 delete $config_options->{minfree};
2916 }
2917
b6e875ca 2918 if ($entry_maxvz && defined($tmp = &$get_float($entry_maxvz))) {
aed81ff0
DM
2919 $config_options->{maxvz} = $tmp;
2920 } else {
2921 delete $config_options->{maxvz};
2922 }
2923
2924 $dialog->destroy();
2925}
2926
121ebc59 2927my $get_raid_devlist = sub {
c6ed3b24
DM
2928
2929 my $dev_name_hash = {};
2930
2931 my $devlist = [];
5f8e86d5 2932 for (my $i = 0; $i < @$hds; $i++) {
c6ed3b24
DM
2933 if (my $hd = $config_options->{"disksel$i"}) {
2934 my ($disk, $devname, $size, $model) = @$hd;
1464c7c9 2935 die "device '$devname' is used more than once\n"
c6ed3b24
DM
2936 if $dev_name_hash->{$devname};
2937 $dev_name_hash->{$devname} = $hd;
2938 push @$devlist, $hd;
2939 }
2940 }
2941
121ebc59
DM
2942 return $devlist;
2943};
2944
14aacec8
FG
2945sub zfs_mirror_size_check {
2946 my ($expected, $actual) = @_;
2947
2948 die "mirrored disks must have same size\n"
2949 if abs($expected - $actual) > $expected / 10;
2950}
2951
121ebc59
DM
2952sub get_zfs_raid_setup {
2953
2954 my $filesys = $config_options->{filesys};
2955
2956 my $devlist = &$get_raid_devlist();
2957
224bb7b0 2958 my $diskcount = scalar(@$devlist);
0cfa502c 2959 die "$filesys needs at least one device\n" if $diskcount < 1;
c6ed3b24 2960
121ebc59
DM
2961 my $bootdevlist = [];
2962
c6ed3b24
DM
2963 my $cmd= '';
2964 if ($filesys eq 'zfs (RAID0)') {
2965 push @$bootdevlist, @$devlist[0];
2966 foreach my $hd (@$devlist) {
2967 $cmd .= " @$hd[1]";
2968 }
2969 } elsif ($filesys eq 'zfs (RAID1)') {
0cfa502c 2970 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
c6ed3b24 2971 $cmd .= ' mirror ';
269c66a6 2972 my $hd = @$devlist[0];
14aacec8 2973 my $expected_size = @$hd[2]; # all disks need approximately same size
269c66a6 2974 foreach $hd (@$devlist) {
14aacec8 2975 zfs_mirror_size_check($expected_size, @$hd[2]);
c6ed3b24
DM
2976 $cmd .= " @$hd[1]";
2977 push @$bootdevlist, $hd;
2978 }
2979 } elsif ($filesys eq 'zfs (RAID10)') {
0cfa502c 2980 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
b8f4f0f9 2981 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
1464c7c9 2982
c6ed3b24
DM
2983 push @$bootdevlist, @$devlist[0], @$devlist[1];
2984
224bb7b0 2985 for (my $i = 0; $i < $diskcount; $i+=2) {
c6ed3b24
DM
2986 my $hd1 = @$devlist[$i];
2987 my $hd2 = @$devlist[$i+1];
14aacec8 2988 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
c6ed3b24
DM
2989 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
2990 }
2991
2992 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
2993 my $level = $1;
2994 my $mindisks = 2 + $level;
0cfa502c 2995 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
269c66a6 2996 my $hd = @$devlist[0];
14aacec8 2997 my $expected_size = @$hd[2]; # all disks need approximately same size
097ecf8f 2998 $cmd .= " raidz$level";
269c66a6 2999 foreach $hd (@$devlist) {
14aacec8 3000 zfs_mirror_size_check($expected_size, @$hd[2]);
c6ed3b24
DM
3001 $cmd .= " @$hd[1]";
3002 push @$bootdevlist, $hd;
3003 }
3004 } else {
3005 die "unknown zfs mode '$filesys'\n";
3006 }
3007
3008 return ($devlist, $bootdevlist, $cmd);
3009}
3010
121ebc59
DM
3011sub get_btrfs_raid_setup {
3012
3013 my $filesys = $config_options->{filesys};
3014
3015 my $devlist = &$get_raid_devlist();
3016
3017 my $diskcount = scalar(@$devlist);
0cfa502c 3018 die "$filesys needs at least one device\n" if $diskcount < 1;
121ebc59
DM
3019
3020 my $mode;
3021
3022 if ($diskcount == 1) {
3023 $mode = 'single';
3024 } else {
3025 if ($filesys eq 'btrfs (RAID0)') {
3026 $mode = 'raid0';
3027 } elsif ($filesys eq 'btrfs (RAID1)') {
0cfa502c 3028 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
121ebc59
DM
3029 $mode = 'raid1';
3030 } elsif ($filesys eq 'btrfs (RAID10)') {
0cfa502c 3031 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
121ebc59
DM
3032 $mode = 'raid10';
3033 } else {
9d69f3d3 3034 die "unknown btrfs mode '$filesys'\n";
121ebc59
DM
3035 }
3036 }
3037
3038 return ($devlist, $mode);
3039}
3040
218a4b6b 3041my $last_hd_selected = 0;
89a12446
DM
3042sub create_hdsel_view {
3043
451b1da5 3044 $prev_btn->set_sensitive(1); # enable previous button at this point
201a5120 3045
71590b6a 3046 cleanup_view();
89a12446 3047
71590b6a
OB
3048 my $vbox = Gtk3::VBox->new(0, 0);
3049 $inbox->pack_start($vbox, 1, 0, 0);
3050 my $hbox = Gtk3::HBox->new(0, 0);
3051 $vbox->pack_start($hbox, 0, 0, 10);
968fa90b 3052
89a12446 3053 my ($disk, $devname, $size, $model) = @{@$hds[0]};
9227a70f 3054 $target_hd = $devname if !defined($target_hd);
89a12446 3055
71590b6a
OB
3056 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3057 $hbox->pack_start($target_hd_label, 0, 0, 0);
89a12446 3058
bcbfab6b 3059 $target_hd_combo = Gtk3::ComboBoxText->new();
89a12446 3060
1aa5bd02
DM
3061 foreach my $hd (@$hds) {
3062 ($disk, $devname, $size, $model) = @$hd;
71590b6a 3063 $target_hd_combo->append_text (get_device_desc($devname, $size, $model));
1aa5bd02 3064 }
89a12446 3065
90af1603
OB
3066 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
3067 if ($raid) {
3068 $target_hd_label->set_text("Target: $config_options->{filesys} ");
3069 $target_hd_combo->set_visible(0);
3070 $target_hd_combo->set_no_show_all(1);
3071 }
218a4b6b 3072 $target_hd_combo->set_active($last_hd_selected);
71590b6a 3073 $target_hd_combo->signal_connect(changed => sub {
1aa5bd02
DM
3074 $a = shift->get_active;
3075 my ($disk, $devname) = @{@$hds[$a]};
3b959bef 3076 $last_hd_selected = $a;
1aa5bd02 3077 $target_hd = $devname;
1aa5bd02 3078 });
1464c7c9 3079
71590b6a 3080 $hbox->pack_start($target_hd_combo, 0, 0, 10);
aed81ff0 3081
71590b6a 3082 my $options = Gtk3::Button->new('_Options');
aed81ff0
DM
3083 $options->signal_connect (clicked => \&create_hdoption_view);
3084 $hbox->pack_start ($options, 0, 0, 0);
3085
89a12446
DM
3086
3087 $inbox->show_all;
3088
201a5120 3089 display_html();
c6ed3b24 3090
71590b6a 3091 set_next(undef, sub {
c6ed3b24
DM
3092
3093 if ($config_options->{filesys} =~ m/zfs/) {
a7d40341 3094 my ($devlist) = eval { get_zfs_raid_setup() };
c6ed3b24 3095 if (my $err = $@) {
303dfb2c
TL
3096 display_message("Warning: $err\nPlease fix ZFS setup first.");
3097 return;
c6ed3b24 3098 }
303dfb2c 3099 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
121ebc59 3100 } elsif ($config_options->{filesys} =~ m/btrfs/) {
a7d40341 3101 my ($devlist) = eval { get_btrfs_raid_setup() };
121ebc59 3102 if (my $err = $@) {
303dfb2c
TL
3103 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3104 return;
121ebc59 3105 }
303dfb2c 3106 $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
c6ed3b24 3107 } else {
a7d40341 3108 $config_options->{target_hds} = [ $target_hd ];
c6ed3b24 3109 }
303dfb2c
TL
3110
3111 $step_number++;
3112 create_country_view();
c6ed3b24 3113 });
89a12446
DM
3114}
3115
3116sub create_extract_view {
3117
71590b6a 3118 cleanup_view();
89a12446 3119
550958aa
DM
3120 display_info();
3121
201a5120 3122 $next->set_sensitive(0);
ac3ee85b
TL
3123 $prev_btn->set_sensitive(0);
3124 $prev_btn->hide();
89a12446 3125
71590b6a 3126 my $vbox = Gtk3::VBox->new(0, 0);
89a12446 3127 $inbox->pack_start ($vbox, 1, 0, 0);
71590b6a 3128 my $hbox = Gtk3::HBox->new(0, 0);
53986d77 3129 $vbox->pack_start ($hbox, 0, 0, 10);
89a12446 3130
71590b6a 3131 my $vbox2 = Gtk3::VBox->new(0, 0);
89a12446
DM
3132 $hbox->pack_start ($vbox2, 0, 0, 0);
3133
71590b6a 3134 $progress_status = Gtk3::Label->new('');
89a12446 3135 $vbox2->pack_start ($progress_status, 1, 1, 0);
968fa90b 3136
7becc472 3137 $progress = Gtk3::ProgressBar->new;
45feca6f 3138 $progress->set_show_text(1);
7becc472 3139 $progress->set_size_request (600, -1);
89a12446 3140
71590b6a 3141 $vbox2->pack_start($progress, 0, 0, 0);
89a12446 3142
201a5120 3143 $inbox->show_all();
89a12446
DM
3144
3145 my $tdir = $opt_testmode ? "target" : "/target";
3146 mkdir $tdir;
97980bf2 3147 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
89a12446 3148
71590b6a 3149 eval { extract_data($base, $tdir); };
89a12446
DM
3150 my $err = $@;
3151
201a5120 3152 $next->set_sensitive(1);
89a12446 3153
71590b6a 3154 set_next("_Reboot", sub { exit (0); } );
89a12446 3155
296cf41f 3156 if ($err) {
201a5120
OB
3157 display_html("fail.htm");
3158 display_error($err);
296cf41f 3159 } else {
201a5120
OB
3160 cleanup_view();
3161 display_html("success.htm");
296cf41f 3162 }
89a12446
DM
3163}
3164
89a12446
DM
3165sub create_intro_view {
3166
451b1da5 3167 $prev_btn->set_sensitive(0);
201a5120
OB
3168
3169 cleanup_view();
89a12446 3170
bdeca872
DM
3171 if ($setup->{product} eq 'pve') {
3172 eval {
3173 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3174 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
3175 display_error("No support for KVM virtualisation detected.\n\n" .
3176 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3177 }
3178 };
3179 }
7fff0d85 3180
201a5120 3181 display_html();
89a12446 3182
201a5120 3183 $step_number++;
71590b6a 3184 set_next("I a_gree", \&create_hdsel_view);
89a12446
DM
3185}
3186
71590b6a 3187$ipconf = get_ip_config();
89a12446 3188
9d1f1ee3 3189$country = detect_country() if $ipconf->{default} || $opt_testmode;
89a12446
DM
3190
3191# read country, kmap and timezone infos
71590b6a 3192$cmap = read_cmap();
89a12446 3193
9d1f1ee3
FG
3194if (!defined($cmap->{country}->{$country})) {
3195 print $logfd "ignoring detected country '$country', invalid or unknown\n";
3196 $country = undef;
3197}
3198
89a12446
DM
3199create_main_window ();
3200
ff2ce71c
FG
3201my $initial_error = 0;
3202
89a12446
DM
3203if (!defined ($hds) || (scalar (@$hds) <= 0)) {
3204 print "no hardisks found\n";
ff2ce71c 3205 $initial_error = 1;
201a5120 3206 display_html("nohds.htm");
71590b6a 3207 set_next("Reboot", sub { exit(0); } );
89a12446 3208} else {
89a12446
DM
3209 foreach my $hd (@$hds) {
3210 my ($disk, $devname) = @$hd;
3211 next if $devname =~ m|^/dev/md\d+$|;
3212 print "found Disk$disk N:$devname\n";
3213 }
89a12446
DM
3214}
3215
72836708
FG
3216if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
3217 print "no network interfaces found\n";
3218 $initial_error = 1;
201a5120 3219 display_html("nonics.htm");
71590b6a 3220 set_next("Reboot", sub { exit(0); } );
72836708
FG
3221}
3222
ff2ce71c
FG
3223create_intro_view () if !$initial_error;
3224
7becc472 3225Gtk3->main;
89a12446
DM
3226
3227exit 0;