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