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