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