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