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