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