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