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