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