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