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