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