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