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