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