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