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