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