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