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