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