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