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