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