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