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