]> git.proxmox.com Git - pve-installer.git/blob - proxinstall
c548ac64e1d655edd72898a8771eef143f070bc0
[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 Local only
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 debconfig_set ($targetdir, <<_EOD);
1472 postfix postfix/main_mailer_type select No configuration
1473 _EOD
1474
1475 unlink "$targetdir/etc/mailname";
1476 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/;
1477 write_config ($postfix_main_cf, "$targetdir/etc/postfix/main.cf");
1478
1479 # make sure we have all postfix directories
1480 syscmd ("chroot $targetdir /usr/sbin/postfix check");
1481 # cleanup mail queue
1482 syscmd ("chroot $targetdir /usr/sbin/postsuper -d ALL");
1483
1484 # enable NTP (timedatectl set-ntp true does not work without DBUS)
1485 syscmd ("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
1486
1487 unlink "$targetdir/proxmox_install_mode";
1488
1489 # set timezone
1490 unlink ("$targetdir/etc/localtime");
1491 symlink ("/usr/share/zoneinfo/$timezone", "$targetdir/etc/localtime");
1492 write_config ("$timezone\n", "$targetdir/etc/timezone");
1493
1494 # set apt mirror
1495 if (my $mirror = $cmap->{country}->{$country}->{mirror}) {
1496 my $fn = "$targetdir/etc/apt/sources.list";
1497 syscmd ("sed -i 's/ftp\\.debian\\.org/$mirror/' '$fn'");
1498 }
1499
1500 # create extended_states for apt (avoid cron job warning if that
1501 # file does not exist)
1502 write_config ('', "$targetdir/var/lib/apt/extended_states");
1503
1504 # allow ssh root login
1505 syscmd ("sed -i 's/^PermitRootLogin.*/PermitRootLogin yes/' '$targetdir/etc/ssh/sshd_config'");
1506
1507 # save installer settings
1508 my $ucc = uc ($country);
1509 debconfig_set ($targetdir, <<_EOD);
1510 pve-manager pve-manager/country string $ucc
1511 _EOD
1512
1513 update_progress (0.8, 0.95, 1, "make system bootable");
1514
1515 if ($use_zfs) {
1516 syscmd ("sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT\\/pve-1 boot=zfs\"/' $targetdir/etc/default/grub") == 0 ||
1517 die "unable to update /etc/default/grub\n";
1518
1519 }
1520
1521 diversion_remove ($targetdir, "/usr/sbin/update-grub");
1522 diversion_remove ($targetdir, "/usr/sbin/update-initramfs");
1523
1524 if (!$opt_testmode) {
1525
1526 unlink ("$targetdir/etc/mtab");
1527 symlink ("/proc/mounts", "$targetdir/etc/mtab");
1528 syscmd ("mount -n --bind /dev $targetdir/dev");
1529
1530 syscmd ("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
1531 die "unable to install initramfs\n";
1532
1533 foreach my $di (@$bootdevinfo) {
1534 my $dev = $di->{devname};
1535 syscmd ("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
1536 die "unable to install the i386-pc boot loader on '$dev'\n";
1537
1538 if ($di->{esp}) {
1539 syscmd ("mount -n $di->{esp} -t vfat $targetdir/boot/efi") == 0 ||
1540 die "unable to mount $di->{esp}\n";
1541 my $rc = syscmd ("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
1542 if ($rc != 0) {
1543 if (-d '/sys/firmware/efi') {
1544 die "unable to install the EFI boot loader on '$dev'\n";
1545 } else {
1546 warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
1547 }
1548 }
1549 # also install fallback boot file (OVMF does not boot without)
1550 mkdir("$targetdir/boot/efi/EFI/BOOT");
1551 syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
1552 die "unable to copy efi boot loader\n";
1553
1554 syscmd ("umount $targetdir/boot/efi") == 0 ||
1555 die "unable to umount $targetdir/boot/efi\n";
1556 }
1557 }
1558
1559 syscmd ("chroot $targetdir /usr/sbin/update-grub") == 0 ||
1560 die "unable to update boot loader config\n";
1561
1562 syscmd ("umount $targetdir/dev");
1563 }
1564
1565 # cleanup
1566
1567 # hack: remove dead.letter from sshd installation
1568 syscmd ("rm -rf $targetdir/dead.letter");
1569
1570 unlink "$targetdir/usr/sbin/policy-rc.d";
1571
1572 diversion_remove ($targetdir, "/sbin/start-stop-daemon");
1573
1574 # set root password
1575 my $octets = encode("utf-8", $password);
1576 run_command ("chroot $targetdir /usr/sbin/chpasswd", undef,
1577 "root:$octets\n");
1578
1579 # create pmxcfs DB
1580
1581 my $tmpdir = "$targetdir/tmp/pve";
1582 mkdir $tmpdir;
1583
1584 # write vnc keymap to datacenter.cfg
1585 my $vnckmap = $cmap->{kmap}->{$keymap}->{kvm} || 'en-us';
1586 write_config ("keyboard: $vnckmap\n",
1587 "$tmpdir/datacenter.cfg");
1588
1589 # save admin email
1590 write_config ("user:root\@pam:1:0:::${mailto}::\n",
1591 "$tmpdir/user.cfg");
1592
1593 # write storage.cfg
1594 my $strorage_cfg_fn = "$tmpdir/storage.cfg";
1595 if ($use_zfs) {
1596 write_config ($storage_cfg_zfs, $strorage_cfg_fn);
1597 } elsif ($use_btrfs) {
1598 write_config ($storage_cfg_btrfs, $strorage_cfg_fn);
1599 } else {
1600 write_config ($storage_cfg_lvmthin, $strorage_cfg_fn);
1601 }
1602
1603 run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
1604
1605 syscmd ("rm -rf $tmpdir");
1606 };
1607
1608 my $err = $@;
1609
1610 update_progress (1, 0, 1, "");
1611
1612 print $err if $err;
1613
1614 if ($opt_testmode) {
1615 my $elapsed = Time::HiRes::tv_interval($starttime);
1616 print "Elapsed extract time: $elapsed\n";
1617
1618 syscmd ("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> pve-final.pkglist");
1619 }
1620
1621 syscmd ("umount $targetdir/var/lib/vz");
1622 syscmd ("umount $targetdir/tmp");
1623 syscmd ("umount $targetdir/proc");
1624 syscmd ("umount $targetdir/sys");
1625
1626 if ($use_zfs) {
1627 syscmd ("zfs umount -a") == 0 ||
1628 die "unable to unmount zfs\n";
1629 } else {
1630 syscmd ("umount -d $targetdir");
1631 }
1632
1633 if (!$err && $use_zfs) {
1634 syscmd ("zfs set sync=standard $zfspoolname") == 0 ||
1635 die "unable to set zfs properties\n";
1636
1637 syscmd ("zfs set mountpoint=/ $zfspoolname/ROOT/pve-1") == 0 ||
1638 die "zfs set mountpoint failed\n";
1639
1640 syscmd ("zpool set bootfs=$zfspoolname/ROOT/pve-1 $zfspoolname") == 0 ||
1641 die "zfs set bootfs failed\n";
1642 syscmd ("zpool export $zfspoolname");
1643 }
1644
1645 die $err if $err;
1646 }
1647
1648 sub display_html {
1649 my ($filename) = @_;
1650
1651 my $path = "${proxmox_libdir}/html/$filename";
1652
1653 my $url = "file://$path";
1654
1655 my $data = file_get_contents($path);
1656
1657 if ($filename eq 'license.htm') {
1658 my $license = decode('utf8', file_get_contents("${proxmox_cddir}/EULA"));
1659 my $title = "END USER LICENSE AGREEMENT (EULA)";
1660 $data =~ s/__LICENSE__/$license/;
1661 $data =~ s/__LICENSE_TITLE__/$title/;
1662 }
1663
1664 $htmlview->load_html_string($data, $url);
1665 }
1666
1667 sub set_next {
1668 my ($text, $fctn) = @_;
1669
1670 $next_fctn = $fctn;
1671 $text = "_Next" if !$text;
1672 $next->set_label ($text);
1673
1674 $next->grab_focus ();
1675 }
1676
1677 sub create_main_window {
1678
1679 $window = Gtk3::Window->new ();
1680 $window->set_default_size (1024, 768);
1681 $window->set_has_resize_grip(0);
1682 $window->set_decorated (0) if !$opt_testmode;
1683
1684 my $vbox = Gtk3::VBox->new (0, 0);
1685
1686 my $image = Gtk3::Image->new_from_file ("${proxmox_libdir}/proxlogo.png");
1687 $vbox->pack_start ($image, 0, 0, 0);
1688
1689 my $hbox = Gtk3::HBox->new (0, 0);
1690 $vbox->pack_start ($hbox, 1, 1, 0);
1691
1692 # my $f1 = Gtk3::Frame->new ('test');
1693 # $f1->set_shadow_type ('none');
1694 # $hbox->pack_start ($f1, 1, 1, 0);
1695
1696 my $sep1 = Gtk3::HSeparator->new;
1697 $vbox->pack_start ($sep1, 0, 0, 0);
1698
1699 $cmdbox = Gtk3::HBox->new ();
1700 $vbox->pack_start ($cmdbox, 0, 0, 10);
1701
1702 $next = Gtk3::Button->new ('_Next');
1703 $next->signal_connect (clicked => sub { &$next_fctn (); });
1704 $cmdbox->pack_end ($next, 0, 0, 10);
1705 my $abort = Gtk3::Button->new ('_Abort');
1706 $abort->set_can_focus (0);
1707 $cmdbox->pack_start ($abort, 0, 0, 10);
1708 $abort->signal_connect (clicked => sub { exit (-1); });
1709
1710 my $vbox2 = Gtk3::VBox->new (0, 0);
1711 $hbox->add ($vbox2);
1712
1713 $htmlview = Gtk3::WebKit::WebView->new();
1714 my $scrolls = Gtk3::ScrolledWindow->new();
1715 $scrolls->add($htmlview);
1716
1717 my $hbox2 = Gtk3::HBox->new (0, 0);
1718 $hbox2->pack_start ($scrolls, 1, 1, 0);
1719
1720 $vbox2->pack_start ($hbox2, 1, 1, 0);
1721
1722 my $vbox3 = Gtk3::VBox->new (0, 0);
1723 $vbox2->pack_start ($vbox3, 0, 0, 0);
1724
1725 my $sep2 = Gtk3::HSeparator->new;
1726 $vbox3->pack_start ($sep2, 0, 0, 0);
1727
1728 $inbox = Gtk3::HBox->new (0, 0);
1729 $vbox3->pack_start ($inbox, 0, 0, 0);
1730
1731 $window->add ($vbox);
1732
1733 $window->show_all;
1734 $window->realize ();
1735 }
1736
1737 sub cleanup_view {
1738 $inbox->foreach(sub {
1739 my $child = shift;
1740 $inbox->remove ($child);
1741 });
1742 }
1743
1744 # fixme: newer GTK3 has special properties to handle numbers with Entry
1745 # only allow floating point numbers with Gtk3::Entry
1746
1747 sub check_float {
1748 my ($entry, $event) = @_;
1749
1750 return check_number($entry, $event, 1);
1751 }
1752
1753 sub check_int {
1754 my ($entry, $event) = @_;
1755
1756 return check_number($entry, $event, 0);
1757 }
1758
1759 sub check_number {
1760 my ($entry, $event, $float) = @_;
1761
1762 my $val = $event->get_keyval;
1763
1764 if (($float && $val == ord '.') ||
1765 $val == Gtk3::Gdk::KEY_ISO_Left_Tab ||
1766 $val == Gtk3::Gdk::KEY_Shift_L ||
1767 $val == Gtk3::Gdk::KEY_Tab ||
1768 $val == Gtk3::Gdk::KEY_Left ||
1769 $val == Gtk3::Gdk::KEY_Right ||
1770 $val == Gtk3::Gdk::KEY_BackSpace ||
1771 $val == Gtk3::Gdk::KEY_Delete ||
1772 ($val >= ord '0' && $val <= ord '9') ||
1773 ($val >= Gtk3::Gdk::KEY_KP_0 &&
1774 $val <= Gtk3::Gdk::KEY_KP_9)) {
1775 return undef;
1776 }
1777
1778 return 1;
1779 }
1780
1781 sub create_text_input {
1782 my ($default, $text) = @_;
1783
1784 my $hbox = Gtk3::HBox->new (0, 0);
1785
1786 my $label = Gtk3::Label->new ($text);
1787 $label->set_size_request (150, -1);
1788 $label->set_alignment (1, 0.5);
1789 $hbox->pack_start ($label, 0, 0, 10);
1790 my $e1 = Gtk3::Entry->new ();
1791 $e1->set_width_chars (30);
1792 $hbox->pack_start ($e1, 0, 0, 0);
1793 $e1->set_text ($default);
1794
1795 return ($hbox, $e1);
1796 }
1797
1798 sub get_ip_config {
1799
1800 my $ifaces = {};
1801 my $default;
1802
1803 my $links = `ip -o l`;
1804 foreach my $l (split /\n/,$links) {
1805 my ($index, $name, $flags, $state, $mac) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ether\s+(\S+)\s+/;
1806 next if !$name || $name eq 'lo';
1807
1808 my $driver = readlink "/sys/class/net/$name/device/driver" || 'unknown';
1809 $driver =~ s!^.*/!!;
1810
1811 $ifaces->{"$index"} = {
1812 name => $name,
1813 driver => $driver,
1814 flags => $flags,
1815 state => $state,
1816 mac => $mac,
1817 };
1818
1819 my $addresses = `ip -o a s $name`;
1820 foreach my $a (split /\n/,$addresses) {
1821 my ($family, $ip, $prefix) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
1822 next if !$ip;
1823 next if $a =~ /scope\s+link/; # ignore link local
1824
1825 my $mask = $prefix;
1826
1827 if ($family eq 'inet') {
1828 next if !$ip =~ /$IPV4RE/;
1829 next if $prefix < 8 || $prefix > 32;
1830 $mask = @$ipv4_reverse_mask[$prefix];
1831 } else {
1832 next if !$ip =~ /$IPV6RE/;
1833 }
1834
1835 $default = $index if !$default;
1836
1837 $ifaces->{"$index"}->{"$family"} = {
1838 mask => $mask,
1839 addr => $ip,
1840 };
1841 }
1842 }
1843
1844
1845 my $route = `ip route`;
1846 my ($gateway) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
1847
1848 my $resolvconf = `cat /etc/resolv.conf`;
1849 my ($dnsserver) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
1850 my ($domain) = $resolvconf =~ m/^domain\s+(\S+)$/m;
1851
1852 return {
1853 default => $default,
1854 ifaces => $ifaces,
1855 gateway => $gateway,
1856 dnsserver => $dnsserver,
1857 domain => $domain,
1858 }
1859 }
1860
1861 sub display_message {
1862 my ($msg) = @_;
1863
1864 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
1865 'info', 'ok', $msg);
1866 $dialog->run();
1867 $dialog->destroy();
1868 }
1869
1870 sub display_error {
1871 my ($msg) = @_;
1872
1873 my $dialog = Gtk3::MessageDialog->new ($window, 'modal',
1874 'error', 'ok', $msg);
1875 $dialog->run();
1876 $dialog->destroy();
1877 }
1878
1879 my $ipconf_first_view = 1;
1880
1881 sub create_ipconf_view {
1882
1883 cleanup_view ();
1884 display_html ("ipconf.htm");
1885
1886 my $vbox = Gtk3::VBox->new (0, 0);
1887 $inbox->pack_start ($vbox, 1, 0, 0);
1888 my $hbox = Gtk3::HBox->new (0, 0);
1889 $vbox->pack_start ($hbox, 0, 0, 10);
1890 my $vbox2 = Gtk3::VBox->new (0, 0);
1891 $hbox->add ($vbox2);
1892
1893 my $ipbox;
1894 ($ipbox, $ipconf_entry_addr) =
1895 create_text_input ("192.168.100.2", 'IP Address:');
1896
1897 my $maskbox;
1898 ($maskbox, $ipconf_entry_mask) =
1899 create_text_input ("255.255.255.0", 'Netmask:');
1900
1901 my $device_cb = Gtk3::ComboBoxText->new();
1902 $device_cb->set_active(0);
1903 $device_cb->set_visible(1);
1904
1905 my $get_device_desc = sub {
1906 my $iface = shift;
1907 return "$iface->{name} - $iface->{mac} ($iface->{driver})";
1908 };
1909
1910 my $device_active_map = {};
1911
1912 my $device_change_handler = sub {
1913 my $current = shift;
1914 $ipconf->{selected} = $device_active_map->{$current->get_active()};
1915 my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
1916 $ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
1917 if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
1918 $ipconf_entry_mask->set_text($iface->{inet}->{mask} || $iface->{inet6}->{mask})
1919 if $iface->{inet}->{mask} || $iface->{inet6}->{mask};
1920 };
1921
1922 my $i = 0;
1923 foreach my $index (sort keys %{$ipconf->{ifaces}}) {
1924 $device_cb->append_text(&$get_device_desc($ipconf->{ifaces}->{$index}));
1925 $device_active_map->{$i} = $index;
1926 if ($ipconf_first_view && $index == $ipconf->{default}) {
1927 $device_cb->set_active($i);
1928 &$device_change_handler($device_cb);
1929 $ipconf_first_view = 0;
1930 }
1931 $device_cb->signal_connect ('changed' => $device_change_handler);
1932 $i++;
1933 }
1934
1935 $device_cb->set_active(0)
1936 if !($ipconf->{selected});
1937
1938 my $devicebox = Gtk3::HBox->new (0, 0);
1939 my $label = Gtk3::Label->new ("Management Interface:");
1940 $label->set_size_request (150, -1);
1941 $label->set_alignment (1, 0.5);
1942 $devicebox->pack_start ($label, 0, 0, 10);
1943 $devicebox->pack_start ($device_cb, 0, 0, 0);
1944
1945 $vbox2->pack_start ($devicebox, 0, 0, 2);
1946
1947 my $hn = $ipconf->{domain} ? "pve.$ipconf->{domain}" : 'pve.example.invalid';
1948
1949 my ($hostbox, $hostentry) =
1950 create_text_input ($hn, 'Hostname (FQDN):');
1951 $vbox2->pack_start ($hostbox, 0, 0, 2);
1952
1953 $vbox2->pack_start ($ipbox, 0, 0, 2);
1954
1955 $vbox2->pack_start ($maskbox, 0, 0, 2);
1956
1957 $gateway = $ipconf->{gateway} || '192.168.100.1';
1958
1959 my $gwbox;
1960 ($gwbox, $ipconf_entry_gw) =
1961 create_text_input ($gateway, 'Gateway:');
1962
1963 $vbox2->pack_start ($gwbox, 0, 0, 2);
1964
1965 $dnsserver = $ipconf->{dnsserver} || $gateway;
1966
1967 my $dnsbox;
1968 ($dnsbox, $ipconf_entry_dns) =
1969 create_text_input ($dnsserver, 'DNS Server:');
1970
1971 $vbox2->pack_start ($dnsbox, 0, 0, 0);
1972
1973 $inbox->show_all;
1974 set_next (undef, sub {
1975
1976 # verify hostname
1977
1978 my $text = $hostentry->get_text();
1979
1980 $text =~ s/^\s+//;
1981 $text =~ s/\s+$//;
1982
1983 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
1984
1985 # Debian does not support purely numeric hostnames
1986 if ($text && $text =~ /^[0-9]+(?:\.|$)/) {
1987 display_message("Purely numeric hostnames are not allowed.");
1988 $hostentry->grab_focus();
1989 return;
1990 }
1991
1992 if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
1993 $text =~ m/^([^\.]+)\.(\S+)$/) {
1994 $hostname = $1;
1995 $domain = $2;
1996 } else {
1997 display_message ("Hostname does not look like a fully qualified domain name.");
1998 $hostentry->grab_focus();
1999 return;
2000 }
2001
2002 # verify ip address
2003
2004 $text = $ipconf_entry_addr->get_text();
2005 $text =~ s/^\s+//;
2006 $text =~ s/\s+$//;
2007 if ($text =~ m!^($IPV4RE)$!) {
2008 $ipaddress = $text;
2009 $ipversion = 4;
2010 } elsif ($text =~ m!^($IPV6RE)$!) {
2011 $ipaddress = $text;
2012 $ipversion = 6;
2013 } else {
2014 display_message ("IP address is not valid.");
2015 $ipconf_entry_addr->grab_focus();
2016 return;
2017 }
2018
2019 $text = $ipconf_entry_mask->get_text();
2020 $text =~ s/^\s+//;
2021 $text =~ s/\s+$//;
2022 if (($ipversion == 6) && ($text =~ m/^(\d+)$/) && ($1 >= 8) && ($1 <= 126)) {
2023 $netmask = $text;
2024 } elsif (($ipversion == 4) && defined($ipv4_mask_hash->{$text})) {
2025 $netmask = $text;
2026 } else {
2027 display_message ("Netmask is not valid.");
2028 $ipconf_entry_mask->grab_focus();
2029 return;
2030 }
2031
2032 $text = $ipconf_entry_gw->get_text();
2033 $text =~ s/^\s+//;
2034 $text =~ s/\s+$//;
2035 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2036 $gateway = $text;
2037 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
2038 $gateway = $text;
2039 } else {
2040 display_message ("Gateway is not valid.");
2041 $ipconf_entry_gw->grab_focus();
2042 return;
2043 }
2044
2045 $text = $ipconf_entry_dns->get_text();
2046 $text =~ s/^\s+//;
2047 $text =~ s/\s+$//;
2048 if (($ipversion == 4) && ($text =~ m!^($IPV4RE)$!)) {
2049 $dnsserver = $text;
2050 } elsif (($ipversion == 6) && ($text =~ m!^($IPV6RE)$!)) {
2051 $dnsserver = $text;
2052 } else {
2053 display_message ("DNS server is not valid.");
2054 $ipconf_entry_dns->grab_focus();
2055 return;
2056 }
2057
2058 #print "TEST $ipaddress $netmask $gateway $dnsserver\n";
2059
2060 create_extract_view ();
2061 });
2062
2063 $hostentry->grab_focus();
2064 }
2065
2066 sub get_device_desc {
2067 my ($devname, $size, $model) = @_;
2068
2069 if ($size && ($size > 0)) {
2070 $size = int($size/2048); # size in MB, from 512B "sectors"
2071
2072 my $text = "$devname (";
2073 if ($size >= 1024) {
2074 $size = int($size/1024); # size in GB
2075 $text .= "${size}GB";
2076 } else {
2077 $text .= "${size}MB";
2078 }
2079
2080 $text .= ", $model" if $model;
2081 $text .= ")";
2082
2083 } else {
2084 return $devname;
2085 }
2086 }
2087
2088 sub update_layout {
2089 my ($cb, $kmap) = @_;
2090
2091 my $ind;
2092 my $def;
2093 my $i = 0;
2094 my $kmaphash = $cmap->{kmaphash};
2095 foreach my $layout (sort keys %$kmaphash) {
2096 $def = $i if $kmaphash->{$layout} eq 'en-us';
2097 $ind = $i if $kmap && $kmaphash->{$layout} eq $kmap;
2098 $i++;
2099 }
2100
2101 $cb->set_active ($ind || $def || 0);
2102 }
2103
2104 my $lastzonecb;
2105 sub update_zonelist {
2106 my ($box, $cc) = @_;
2107
2108 my $cczones = $cmap->{cczones};
2109 my $zones = $cmap->{zones};
2110
2111 my $sel;
2112 if ($lastzonecb) {
2113 $sel = $lastzonecb->get_active_text();
2114 $box->remove ($lastzonecb);
2115 } else {
2116 $sel = $timezone; # used once to select default
2117 }
2118
2119 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
2120 $cb->set_size_request (200, -1);
2121
2122 $cb->signal_connect ('changed' => sub {
2123 $timezone = $cb->get_active_text();
2124 });
2125
2126 my @za;
2127 if ($cc && defined ($cczones->{$cc})) {
2128 @za = keys %{$cczones->{$cc}};
2129 } else {
2130 @za = keys %$zones;
2131 }
2132 my $ind;
2133 my $i = 0;
2134 foreach my $zone (sort @za) {
2135 $ind = $i if $sel && $zone eq $sel;
2136 $cb->append_text ($zone);
2137 $i++;
2138 }
2139
2140 $cb->set_active ($ind || 0);
2141
2142 $cb->show;
2143 $box->pack_start ($cb, 0, 0, 0);
2144 }
2145
2146 sub create_password_view {
2147
2148 cleanup_view ();
2149
2150 my $vbox2 = Gtk3::VBox->new (0, 0);
2151 $inbox->pack_start ($vbox2, 1, 0, 0);
2152 my $vbox = Gtk3::VBox->new (0, 0);
2153 $vbox2->pack_start ($vbox, 0, 0, 10);
2154
2155 my $hbox1 = Gtk3::HBox->new (0, 0);
2156 my $label = Gtk3::Label->new ("Password");
2157 $label->set_size_request (150, -1);
2158 $label->set_alignment (1, 0.5);
2159 $hbox1->pack_start ($label, 0, 0, 10);
2160 my $pwe1 = Gtk3::Entry->new ();
2161 $pwe1->set_visibility (0);
2162 $pwe1->set_size_request (200, -1);
2163 $hbox1->pack_start ($pwe1, 0, 0, 0);
2164
2165 my $hbox2 = Gtk3::HBox->new (0, 0);
2166 $label = Gtk3::Label->new ("Confirm");
2167 $label->set_size_request (150, -1);
2168 $label->set_alignment (1, 0.5);
2169 $hbox2->pack_start ($label, 0, 0, 10);
2170 my $pwe2 = Gtk3::Entry->new ();
2171 $pwe2->set_visibility (0);
2172 $pwe2->set_size_request (200, -1);
2173 $hbox2->pack_start ($pwe2, 0, 0, 0);
2174
2175 my $hbox3 = Gtk3::HBox->new (0, 0);
2176 $label = Gtk3::Label->new ("E-Mail");
2177 $label->set_size_request (150, -1);
2178 $label->set_alignment (1, 0.5);
2179 $hbox3->pack_start ($label, 0, 0, 10);
2180 my $eme = Gtk3::Entry->new ();
2181 $eme->set_size_request (200, -1);
2182 $eme->set_text('mail@example.invalid');
2183 $hbox3->pack_start ($eme, 0, 0, 0);
2184
2185
2186 $vbox->pack_start ($hbox1, 0, 0, 5);
2187 $vbox->pack_start ($hbox2, 0, 0, 5);
2188 $vbox->pack_start ($hbox3, 0, 0, 15);
2189
2190 $inbox->show_all;
2191
2192 display_html ("passwd.htm");
2193
2194 set_next (undef, sub {
2195
2196 my $t1 = $pwe1->get_text;
2197 my $t2 = $pwe2->get_text;
2198
2199 if (length ($t1) < 5) {
2200 display_message ("Password is too short.");
2201 $pwe1->grab_focus();
2202 return;
2203 }
2204
2205 if ($t1 ne $t2) {
2206 display_message ("Password does not match.");
2207 $pwe1->grab_focus();
2208 return;
2209 }
2210
2211 my $t3 = $eme->get_text;
2212 if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
2213 display_message ("E-Mail does not look like a valid address" .
2214 " (user\@domain.tld)");
2215 $eme->grab_focus();
2216 return;
2217 }
2218
2219 if ($t3 eq 'mail@example.invalid') {
2220 display_message ("Please enter a valid E-Mail address");
2221 $eme->grab_focus();
2222 return;
2223 }
2224
2225 $password = $t1;
2226 $mailto = $t3;
2227
2228 create_ipconf_view();
2229 });
2230
2231 $pwe1->grab_focus();
2232
2233 }
2234
2235 sub create_country_view {
2236
2237 cleanup_view ();
2238
2239 my $countryhash = $cmap->{countryhash};
2240 my $ctr = $cmap->{country};
2241
2242 my $vbox2 = Gtk3::VBox->new (0, 0);
2243 $inbox->pack_start ($vbox2, 1, 0, 0);
2244 my $vbox = Gtk3::VBox->new (0, 0);
2245 $vbox2->pack_start ($vbox, 0, 0, 10);
2246
2247 my $w = Gtk3::Entry->new ();
2248 $w->set_size_request (200, -1);
2249
2250 my $c = Gtk3::EntryCompletion->new ();
2251 $c->set_text_column (0);
2252 $c->set_minimum_key_length(0);
2253 $c->set_popup_set_width (1);
2254 $c->set_inline_completion (1);
2255
2256 my $hbox2 = Gtk3::HBox->new (0, 0);
2257 my $label = Gtk3::Label->new ("Time zone");
2258 $label->set_size_request (150, -1);
2259 $label->set_alignment (1, 0.5);
2260 $hbox2->pack_start ($label, 0, 0, 10);
2261 update_zonelist ($hbox2);
2262
2263 my $hbox3 = Gtk3::HBox->new (0, 0);
2264 $label = Gtk3::Label->new ("Keyboard Layout");
2265 $label->set_size_request (150, -1);
2266 $label->set_alignment (1, 0.5);
2267 $hbox3->pack_start ($label, 0, 0, 10);
2268
2269 my $kmapcb = Gtk3::ComboBoxText->new();
2270 $kmapcb->set_size_request (200, -1);
2271 foreach my $layout (sort keys %{$cmap->{kmaphash}}) {
2272 $kmapcb->append_text ($layout);
2273 }
2274
2275 update_layout ($kmapcb);
2276 $hbox3->pack_start ($kmapcb, 0, 0, 0);
2277
2278 $kmapcb->signal_connect ('changed' => sub {
2279 my $sel = $kmapcb->get_active_text();
2280 if (my $kmap = $cmap->{kmaphash}->{$sel}) {
2281 my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
2282 my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
2283 syscmd ("setxkbmap $xkmap $xvar") if !$opt_testmode;
2284 $keymap = $kmap;
2285 }
2286 });
2287
2288 $w->signal_connect ('changed' => sub {
2289 my ($entry, $event) = @_;
2290 my $text = $entry->get_text;
2291
2292 if (my $cc = $countryhash->{lc($text)}) {
2293 update_zonelist ($hbox2, $cc);
2294 my $kmap = $ctr->{$cc}->{kmap} || 'en-us';
2295 update_layout ($kmapcb, $kmap);
2296 }
2297 });
2298
2299 $w->signal_connect (key_press_event => sub {
2300 my ($entry, $event) = @_;
2301 my $text = $entry->get_text;
2302
2303 my $val = $event->get_keyval;
2304
2305 if ($val == Gtk3::Gdk::KEY_Tab) {
2306 my $cc = $countryhash->{lc($text)};
2307
2308 my $found = 0;
2309 my $compl;
2310
2311 if ($cc) {
2312 $found = 1;
2313 $compl = $ctr->{$cc}->{name};
2314 } else {
2315 foreach my $cc (keys %$ctr) {
2316 my $ct = $ctr->{$cc}->{name};
2317 if ($ct =~ m/^\Q$text\E.*$/i) {
2318 $found++;
2319 $compl = $ct;
2320 }
2321 last if $found > 1;
2322 }
2323 }
2324
2325 if ($found == 1) {
2326 $entry->set_text($compl);
2327 $c->complete();
2328 return undef;
2329 } else {
2330 #Gtk3::Gdk::beep();
2331 print chr(7); # beep ?
2332 }
2333
2334 $c->complete();
2335
2336 my $buf = $w->get_buffer();
2337 $buf->insert_text(-1, '', -1); # popup selection
2338
2339 return 1;
2340 }
2341
2342 return undef;
2343 });
2344
2345 my $ls = Gtk3::ListStore->new('Glib::String');
2346 foreach my $cc (sort {$ctr->{$a}->{name} cmp $ctr->{$b}->{name} } keys %$ctr) {
2347 my $iter = $ls->append();
2348 $ls->set ($iter, 0, $ctr->{$cc}->{name});
2349 }
2350 $c->set_model ($ls);
2351
2352 $w->set_completion ($c);
2353
2354 my $hbox = Gtk3::HBox->new (0, 0);
2355
2356 $label = Gtk3::Label->new ("Country");
2357 $label->set_alignment (1, 0.5);
2358 $label->set_size_request (150, -1);
2359 $hbox->pack_start ($label, 0, 0, 10);
2360 $hbox->pack_start ($w, 0, 0, 0);
2361
2362 $vbox->pack_start ($hbox, 0, 0, 5);
2363 $vbox->pack_start ($hbox2, 0, 0, 5);
2364 $vbox->pack_start ($hbox3, 0, 0, 5);
2365
2366 if ($country && $ctr->{$country}) {
2367 $w->set_text ($ctr->{$country}->{name});
2368 }
2369
2370 $inbox->show_all;
2371
2372 display_html ("country.htm");
2373 set_next (undef, sub {
2374
2375 my $text = $w->get_text;
2376
2377 if (my $cc = $countryhash->{lc($text)}) {
2378 $country = $cc;
2379 create_password_view();
2380 return;
2381 } else {
2382 display_message ("Please select a country first.");
2383 $w->grab_focus();
2384 }
2385 });
2386
2387 $w->grab_focus();
2388 }
2389
2390 my $target_hd_combo;
2391 my $target_hd_label;
2392
2393 my $hdopion_first_setup = 1;
2394
2395 my $create_basic_grid = sub {
2396 my $grid = Gtk3::Grid->new();
2397 $grid->set_visible(1);
2398 $grid->set_column_spacing(10);
2399 $grid->set_row_spacing(10);
2400 $grid->set_hexpand(1);
2401
2402 $grid->set_margin_start(5);
2403 $grid->set_margin_end(5);
2404 $grid->set_margin_top(5);
2405 $grid->set_margin_bottom(5);
2406
2407 return $grid;
2408 };
2409
2410 my $create_label_widget_grid = sub {
2411 my ($labeled_widgets) = @_;
2412
2413 my $grid = &$create_basic_grid();
2414 my $row = 0;
2415
2416 for (my $i = 0; $i < @$labeled_widgets; $i += 2) {
2417 my $widget = @$labeled_widgets[$i+1];
2418 my $label = Gtk3::Label->new(@$labeled_widgets[$i]);
2419 $label->set_visible(1);
2420 $label->set_alignment (1, 0.5);
2421 $grid->attach($label, 0, $row, 1, 1);
2422 $widget->set_visible(1);
2423 $grid->attach($widget, 1, $row, 1, 1);
2424 $row++;
2425 }
2426
2427 return $grid;
2428 };
2429
2430 my $create_raid_disk_grid = sub {
2431 my $disk_labeled_widgets = [];
2432 for (my $i = 0; $i < @$hds; $i++) {
2433 my $disk_selector = Gtk3::ComboBoxText->new();
2434 $disk_selector->append_text("-- do not use --");
2435 $disk_selector->set_active(0);
2436 $disk_selector->set_visible(1);
2437 foreach my $hd (@$hds) {
2438 my ($disk, $devname, $size, $model) = @$hd;
2439 $disk_selector->append_text(get_device_desc ($devname, $size, $model));
2440 $disk_selector->{pve_disk_id} = $i;
2441 $disk_selector->signal_connect (changed => sub {
2442 my $w = shift;
2443 my $diskid = $w->{pve_disk_id};
2444 my $a = $w->get_active - 1;
2445 $config_options->{"disksel${diskid}"} = ($a >= 0) ? $hds->[$a] : undef;
2446 });
2447 }
2448
2449 if ($hdopion_first_setup) {
2450 $disk_selector->set_active ($i+1) if $hds->[$i];
2451 } else {
2452 my $hdind = 0;
2453 if (my $cur_hd = $config_options->{"disksel$i"}) {
2454 foreach my $hd (@$hds) {
2455 if (@$hd[1] eq @$cur_hd[1]) {
2456 $disk_selector->set_active($hdind+1);
2457 last;
2458 }
2459 $hdind++;
2460 }
2461 }
2462 }
2463
2464 push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
2465 }
2466
2467 my $scrolled_window = Gtk3::ScrolledWindow->new();
2468 $scrolled_window->set_hexpand(1);
2469 $scrolled_window->set_propagate_natural_height(1) if @$hds > 4;
2470 $scrolled_window->add(&$create_label_widget_grid($disk_labeled_widgets));
2471 $scrolled_window->set_policy('never', 'automatic');
2472
2473 return $scrolled_window;
2474 # &$create_label_widget_grid($disk_labeled_widgets)
2475 };
2476
2477 my $create_raid_advanced_grid = sub {
2478 my $labeled_widgets = [];
2479 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2480 $spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2481 $spinbutton_ashift->signal_connect ("value-changed" => sub {
2482 my $w = shift;
2483 $config_options->{ashift} = $w->get_value_as_int();
2484 });
2485 $config_options->{ashift} = 12 if ! defined($config_options->{ashift});
2486 $spinbutton_ashift->set_value($config_options->{ashift});
2487 push @$labeled_widgets, "ashift";
2488 push @$labeled_widgets, $spinbutton_ashift;
2489
2490 my $combo_compress = Gtk3::ComboBoxText->new();
2491 $combo_compress->set_tooltip_text("zfs compression algorithm for rpool dataset");
2492 # note: gzip / lze not allowed for bootfs vdevs
2493 my $comp_opts = ["on","off","lzjb","lz4"];
2494 foreach my $opt (@$comp_opts) {
2495 $combo_compress->append($opt, $opt);
2496 }
2497 $config_options->{compress} = "on" if !defined($config_options->{compress});
2498 $combo_compress->set_active_id($config_options->{compress});
2499 $combo_compress->signal_connect (changed => sub {
2500 my $w = shift;
2501 $config_options->{compress} = $w->get_active_text();
2502 });
2503 push @$labeled_widgets, "compress";
2504 push @$labeled_widgets, $combo_compress;
2505
2506 my $combo_checksum = Gtk3::ComboBoxText->new();
2507 $combo_checksum->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2508 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2509 foreach my $opt (@$csum_opts) {
2510 $combo_checksum->append($opt, $opt);
2511 }
2512 $config_options->{checksum} = "on" if !($config_options->{checksum});
2513 $combo_checksum->set_active_id($config_options->{checksum});
2514 $combo_checksum->signal_connect (changed => sub {
2515 my $w = shift;
2516 $config_options->{checksum} = $w->get_active_text();
2517 });
2518 push @$labeled_widgets, "checksum";
2519 push @$labeled_widgets, $combo_checksum;
2520
2521 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2522 $spinbutton_copies->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2523 $spinbutton_copies->signal_connect ("value-changed" => sub {
2524 my $w = shift;
2525 $config_options->{copies} = $w->get_value_as_int();
2526 });
2527 $config_options->{copies} = 1 if !defined($config_options->{copies});
2528 $spinbutton_copies->set_value($config_options->{copies});
2529 push @$labeled_widgets, "copies", $spinbutton_copies;
2530
2531 return &$create_label_widget_grid($labeled_widgets);;
2532 };
2533
2534 sub create_hdoption_view {
2535
2536 my $dialog = Gtk3::Dialog->new();
2537
2538 $dialog->set_title("Harddisk options");
2539
2540 $dialog->add_button("_OK", 1);
2541
2542 my $contarea = $dialog->get_content_area();
2543
2544 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2545 $contarea->pack_start($hbox2, 1, 1, 10);
2546
2547 my $grid = Gtk3::Grid->new();
2548 $grid->set_column_spacing(10);
2549 $grid->set_row_spacing(10);
2550
2551 $hbox2->pack_start($grid, 1, 0, 10);
2552
2553 my $row = 0;
2554
2555 # Filesystem type
2556
2557 my $label0 = Gtk3::Label->new ("Filesystem");
2558 $label0->set_alignment (1, 0.5);
2559 $grid->attach($label0, 0, $row, 1, 1);
2560
2561 my $fstypecb = Gtk3::ComboBoxText->new();
2562
2563 my $fstype = ['ext3', 'ext4', 'xfs',
2564 'zfs (RAID0)', 'zfs (RAID1)',
2565 'zfs (RAID10)', 'zfs (RAIDZ-1)',
2566 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2567
2568 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
2569 if $enable_btrfs;
2570
2571 my $tcount = 0;
2572 foreach my $tmp (@$fstype) {
2573 $fstypecb->append_text($tmp);
2574 $fstypecb->set_active ($tcount)
2575 if $config_options->{filesys} eq $tmp;
2576 $tcount++;
2577 }
2578
2579 $grid->attach($fstypecb, 1, $row, 1, 1);
2580
2581 $hbox2->show_all();
2582
2583 $row++;
2584
2585 my $sep = Gtk3::HSeparator->new();
2586 $sep->set_visible(1);
2587 $grid->attach($sep, 0, $row, 2, 1);
2588 $row++;
2589
2590 my $hdsize_labeled_widgets = [];
2591
2592 # size compute
2593 my $hdsize = 0;
2594 if ( -b $target_hd) {
2595 $hdsize = int(hd_size ($target_hd) / (1024*1024.0)); # size in GB
2596 } elsif ($target_hd) {
2597 $hdsize = int((-s $target_hd) / (1024*1024*1024.0));
2598 }
2599
2600 my $hdsize_size_adj = Gtk3::Adjustment->new($config_options->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
2601 my $spinbutton_hdsize = Gtk3::SpinButton->new($hdsize_size_adj, 1, 1);
2602 $spinbutton_hdsize->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2603 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize;
2604
2605 my $entry_swapsize = Gtk3::Entry->new();
2606 $entry_swapsize->set_tooltip_text("maximum SWAP size (GB)");
2607 $entry_swapsize->signal_connect (key_press_event => \&check_float);
2608 $entry_swapsize->set_text($config_options->{swapsize}) if $config_options->{swapsize};
2609 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize;
2610
2611 my $entry_maxroot = Gtk3::Entry->new();
2612 $entry_maxroot->set_tooltip_text("maximum size (GB) for LVM root volume");
2613 $entry_maxroot->signal_connect (key_press_event => \&check_float);
2614 $entry_maxroot->set_text($config_options->{maxroot}) if $config_options->{maxroot};
2615 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot;
2616
2617 my $entry_minfree = Gtk3::Entry->new();
2618 $entry_minfree->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
2619 $entry_minfree->signal_connect (key_press_event => \&check_float);
2620 $entry_minfree->set_text($config_options->{minfree}) if $config_options->{minfree};
2621 push @$hdsize_labeled_widgets, "minfree", $entry_minfree;
2622
2623 my $entry_maxvz = Gtk3::Entry->new();
2624 $entry_maxvz->set_tooltip_text("maximum size (GB) for LVM data volume");
2625 $entry_maxvz->signal_connect (key_press_event => \&check_float);
2626 $entry_maxvz->set_text($config_options->{maxvz}) if $config_options->{maxvz};
2627 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz;
2628
2629 my $options_stack = Gtk3::Stack->new();
2630 $options_stack->set_visible(1);
2631 $options_stack->set_hexpand(1);
2632 $options_stack->set_vexpand(1);
2633 $options_stack->add_titled(&$create_raid_disk_grid(), "raiddisk", "Disk Setup");
2634 $options_stack->add_titled(&$create_label_widget_grid($hdsize_labeled_widgets), "hdsize", "Size Options");
2635 $options_stack->add_titled(&$create_raid_advanced_grid("zfs"), "raidzfsadvanced", "Advanced Options");
2636 $options_stack->set_visible_child_name("raiddisk");
2637 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2638 $options_stack_switcher->set_halign('center');
2639 $options_stack_switcher->set_stack($options_stack);
2640 $grid->attach($options_stack_switcher, 0, $row, 2, 1);
2641 $row++;
2642 $grid->attach($options_stack, 0, $row, 2, 1);
2643 $row++;
2644
2645 $hdopion_first_setup = 0;
2646
2647 my $switch_view = sub {
2648 my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
2649 my $enable_zfs_opts = $config_options->{filesys} =~ m/zfs/;
2650
2651 $target_hd_combo->set_visible(!$raid);
2652 $options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
2653 $options_stack->get_child_by_name("raiddisk")->set_visible($raid);
2654 $options_stack_switcher->set_visible($enable_zfs_opts);
2655 $options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
2656 if ($raid) {
2657 $target_hd_label->set_text("Target: $config_options->{filesys} ");
2658 $options_stack->set_visible_child_name("raiddisk");
2659 } else {
2660 $target_hd_label->set_text("Target Harddisk: ");
2661 }
2662 my (undef, $pref_width) = $dialog->get_preferred_width();
2663 my (undef, $pref_height) = $dialog->get_preferred_height();
2664 $pref_height = 750 if $pref_height > 750;
2665 $dialog->resize($pref_width, $pref_height);
2666 };
2667
2668 &$switch_view();
2669
2670 $fstypecb->signal_connect (changed => sub {
2671 $config_options->{filesys} = $fstypecb->get_active_text();
2672 &$switch_view();
2673 });
2674
2675 $dialog->show();
2676
2677 $dialog->run();
2678
2679 my $get_float = sub {
2680 my ($entry) = @_;
2681
2682 my $text = $entry->get_text();
2683 return undef if !defined($text);
2684
2685 $text =~ s/^\s+//;
2686 $text =~ s/\s+$//;
2687
2688 return undef if $text !~ m/^\d+(\.\d+)?$/;
2689
2690 return $text;
2691 };
2692
2693 my $tmp;
2694
2695 if (($tmp = &$get_float($spinbutton_hdsize)) && ($tmp != $hdsize)) {
2696 $config_options->{hdsize} = $tmp;
2697 } else {
2698 delete $config_options->{hdsize};
2699 }
2700
2701 if (defined($tmp = &$get_float($entry_swapsize))) {
2702 $config_options->{swapsize} = $tmp;
2703 } else {
2704 delete $config_options->{swapsize};
2705 }
2706
2707 if (defined($tmp = &$get_float($entry_maxroot))) {
2708 $config_options->{maxroot} = $tmp;
2709 } else {
2710 delete $config_options->{maxroot};
2711 }
2712
2713 if (defined($tmp = &$get_float($entry_minfree))) {
2714 $config_options->{minfree} = $tmp;
2715 } else {
2716 delete $config_options->{minfree};
2717 }
2718
2719 if (defined($tmp = &$get_float($entry_maxvz))) {
2720 $config_options->{maxvz} = $tmp;
2721 } else {
2722 delete $config_options->{maxvz};
2723 }
2724
2725 $dialog->destroy();
2726 }
2727
2728 my $get_raid_devlist = sub {
2729
2730 my $dev_name_hash = {};
2731
2732 my $devlist = [];
2733 for (my $i = 0; $i < @$hds; $i++) {
2734 if (my $hd = $config_options->{"disksel$i"}) {
2735 my ($disk, $devname, $size, $model) = @$hd;
2736 die "device '$devname' is used more than once\n"
2737 if $dev_name_hash->{$devname};
2738 $dev_name_hash->{$devname} = $hd;
2739 push @$devlist, $hd;
2740 }
2741 }
2742
2743 return $devlist;
2744 };
2745
2746 sub zfs_mirror_size_check {
2747 my ($expected, $actual) = @_;
2748
2749 die "mirrored disks must have same size\n"
2750 if abs($expected - $actual) > $expected / 10;
2751 }
2752
2753 sub get_zfs_raid_setup {
2754
2755 my $filesys = $config_options->{filesys};
2756
2757 my $devlist = &$get_raid_devlist();
2758
2759 my $diskcount = scalar(@$devlist);
2760 die "$filesys needs at least one device\n" if $diskcount < 1;
2761
2762 my $bootdevlist = [];
2763
2764 my $cmd= '';
2765 if ($filesys eq 'zfs (RAID0)') {
2766 push @$bootdevlist, @$devlist[0];
2767 foreach my $hd (@$devlist) {
2768 $cmd .= " @$hd[1]";
2769 }
2770 } elsif ($filesys eq 'zfs (RAID1)') {
2771 die "zfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
2772 $cmd .= ' mirror ';
2773 my $hd = @$devlist[0];
2774 my $expected_size = @$hd[2]; # all disks need approximately same size
2775 foreach $hd (@$devlist) {
2776 zfs_mirror_size_check($expected_size, @$hd[2]);
2777 $cmd .= " @$hd[1]";
2778 push @$bootdevlist, $hd;
2779 }
2780 } elsif ($filesys eq 'zfs (RAID10)') {
2781 die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
2782 die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
2783
2784 push @$bootdevlist, @$devlist[0], @$devlist[1];
2785
2786 for (my $i = 0; $i < $diskcount; $i+=2) {
2787 my $hd1 = @$devlist[$i];
2788 my $hd2 = @$devlist[$i+1];
2789 zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
2790 $cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
2791 }
2792
2793 } elsif ($filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
2794 my $level = $1;
2795 my $mindisks = 2 + $level;
2796 die "zfs (RAIDZ-$level) needs at least $mindisks devices\n" if scalar(@$devlist) < $mindisks;
2797 my $hd = @$devlist[0];
2798 my $expected_size = @$hd[2]; # all disks need approximately same size
2799 $cmd .= " raidz$level";
2800 foreach $hd (@$devlist) {
2801 zfs_mirror_size_check($expected_size, @$hd[2]);
2802 $cmd .= " @$hd[1]";
2803 push @$bootdevlist, $hd;
2804 }
2805 } else {
2806 die "unknown zfs mode '$filesys'\n";
2807 }
2808
2809 return ($devlist, $bootdevlist, $cmd);
2810 }
2811
2812 sub get_btrfs_raid_setup {
2813
2814 my $filesys = $config_options->{filesys};
2815
2816 my $devlist = &$get_raid_devlist();
2817
2818 my $diskcount = scalar(@$devlist);
2819 die "$filesys needs at least one device\n" if $diskcount < 1;
2820
2821 my $mode;
2822
2823 if ($diskcount == 1) {
2824 $mode = 'single';
2825 } else {
2826 if ($filesys eq 'btrfs (RAID0)') {
2827 $mode = 'raid0';
2828 } elsif ($filesys eq 'btrfs (RAID1)') {
2829 die "btrfs (RAID1) needs at least 2 device\n" if $diskcount < 2;
2830 $mode = 'raid1';
2831 } elsif ($filesys eq 'btrfs (RAID10)') {
2832 die "btrfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
2833 $mode = 'raid10';
2834 } else {
2835 die "unknown btrfs mode '$filesys'\n";
2836 }
2837 }
2838
2839 return ($devlist, $mode);
2840 }
2841
2842 sub create_hdsel_view {
2843
2844 cleanup_view ();
2845
2846 my $vbox = Gtk3::VBox->new (0, 0);
2847 $inbox->pack_start ($vbox, 1, 0, 0);
2848 my $hbox = Gtk3::HBox->new (0, 0);
2849 $vbox->pack_start ($hbox, 0, 0, 10);
2850
2851 my ($disk, $devname, $size, $model) = @{@$hds[0]};
2852 $target_hd = $devname;
2853
2854 $target_hd_label = Gtk3::Label->new ("Target Harddisk: ");
2855 $hbox->pack_start ($target_hd_label, 0, 0, 0);
2856
2857 $target_hd_combo = Gtk3::ComboBoxText->new();
2858
2859 foreach my $hd (@$hds) {
2860 ($disk, $devname, $size, $model) = @$hd;
2861 $target_hd_combo->append_text (get_device_desc ($devname, $size, $model));
2862 }
2863
2864 $target_hd_combo->set_active (0);
2865 $target_hd_combo->signal_connect (changed => sub {
2866 $a = shift->get_active;
2867 my ($disk, $devname) = @{@$hds[$a]};
2868 $target_hd = $devname;
2869 });
2870
2871 $hbox->pack_start ($target_hd_combo, 0, 0, 10);
2872
2873 my $options = Gtk3::Button->new ('_Options');
2874 $options->signal_connect (clicked => \&create_hdoption_view);
2875 $hbox->pack_start ($options, 0, 0, 0);
2876
2877
2878 $inbox->show_all;
2879
2880 display_html ("page1.htm");
2881
2882 set_next (undef, sub {
2883
2884 if ($config_options->{filesys} =~ m/zfs/) {
2885 eval { get_zfs_raid_setup(); };
2886 if (my $err = $@) {
2887 display_message ("Warning: $err\n" .
2888 "Please fix ZFS setup first.");
2889 } else {
2890 create_country_view();
2891 }
2892 } elsif ($config_options->{filesys} =~ m/btrfs/) {
2893 eval { get_btrfs_raid_setup(); };
2894 if (my $err = $@) {
2895 display_message ("Warning: $err\n" .
2896 "Please fix BTRFS setup first.");
2897 } else {
2898 create_country_view();
2899 }
2900 } else {
2901 create_country_view();
2902 }
2903 });
2904 }
2905
2906 sub create_extract_view {
2907
2908 cleanup_view ();
2909
2910 display_html ("extract1-license.htm");
2911 $next->set_sensitive (0);
2912
2913 my $vbox = Gtk3::VBox->new (0, 0);
2914 $inbox->pack_start ($vbox, 1, 0, 0);
2915 my $hbox = Gtk3::HBox->new (0, 0);
2916 $vbox->pack_start ($hbox, 0, 0, 10);
2917
2918 my $vbox2 = Gtk3::VBox->new (0, 0);
2919 $hbox->pack_start ($vbox2, 0, 0, 0);
2920
2921 $progress_status = Gtk3::Label->new ('');
2922 $vbox2->pack_start ($progress_status, 1, 1, 0);
2923
2924 $progress = Gtk3::ProgressBar->new;
2925 $progress->set_show_text(1);
2926 $progress->set_size_request (600, -1);
2927
2928 $vbox2->pack_start ($progress, 0, 0, 0);
2929
2930 $inbox->show_all;
2931
2932 my $tdir = $opt_testmode ? "target" : "/target";
2933 mkdir $tdir;
2934 my $base = "${proxmox_cddir}/$setup->{product}-base.squashfs";
2935
2936 eval { extract_data ($base, $tdir); };
2937 my $err = $@;
2938
2939 $next->set_sensitive (1);
2940
2941 set_next ("_Reboot", sub { exit (0); } );
2942
2943 if ($err) {
2944 display_html ("fail.htm");
2945 display_error ($err);
2946 } else {
2947 cleanup_view ();
2948 display_html ("success.htm");
2949 }
2950 }
2951
2952 sub create_intro_view {
2953
2954 cleanup_view ();
2955
2956 eval {
2957 my $cpuinfo = file_get_contents('/proc/cpuinfo');
2958 if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
2959 display_error("No support for KVM virtualisation detected.\n\n" .
2960 "Check BIOS settings for Intel VT / AMD-V / SVM.")
2961 }
2962 };
2963
2964 display_html ("license.htm");
2965
2966 set_next ("I a_gree", \&create_hdsel_view);
2967 }
2968
2969 $ipconf = get_ip_config ();
2970
2971 $country = detect_country() if $ipconf->{default} || $opt_testmode;
2972
2973 # read country, kmap and timezone infos
2974 $cmap = read_cmap ();
2975
2976 if (!defined($cmap->{country}->{$country})) {
2977 print $logfd "ignoring detected country '$country', invalid or unknown\n";
2978 $country = undef;
2979 }
2980
2981 create_main_window ();
2982
2983 my $initial_error = 0;
2984
2985 if (!defined ($hds) || (scalar (@$hds) <= 0)) {
2986 print "no hardisks found\n";
2987 $initial_error = 1;
2988 display_html ("nohds.htm");
2989 set_next ("Reboot", sub { exit (0); } );
2990 } else {
2991 foreach my $hd (@$hds) {
2992 my ($disk, $devname) = @$hd;
2993 next if $devname =~ m|^/dev/md\d+$|;
2994 print "found Disk$disk N:$devname\n";
2995 }
2996 }
2997
2998 if (!$initial_error && (scalar keys %{ $ipconf->{ifaces} } == 0)) {
2999 print "no network interfaces found\n";
3000 $initial_error = 1;
3001 display_html ("nonics.htm");
3002 set_next ("Reboot", sub { exit (0); } );
3003 }
3004
3005 create_intro_view () if !$initial_error;
3006
3007 Gtk3->main;
3008
3009 exit 0;