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