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