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