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