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