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