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