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