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