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