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