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