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