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