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