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