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