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