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