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