]>
git.proxmox.com Git - pve-installer.git/blob - proxinstall
3 $ENV { DEBIAN_FRONTEND
} = 'noninteractive' ;
18 use String
:: ShellQuote
;
24 use ProxmoxInstallerSetup
;
26 my $setup = ProxmoxInstallerSetup
:: setup
();
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 " ;
34 if (! GetOptions
( 'testmode=s' => \
$opt_testmode )) {
39 my $zfstestpool = "test_rpool" ;
40 my $zfspoolname = $opt_testmode ?
$zfstestpool : 'rpool' ;
41 my $zfsrootvolname = " $setup ->{product}-1" ;
43 my $storage_cfg_zfs = <<__EOD__;
46 content iso,vztmpl,backup
49 pool $zfspoolname/data
51 content images,rootdir
54 my $storage_cfg_btrfs = <<__EOD__;
57 content iso,vztmpl,backup
61 path /var/lib/pve/local-btrfs
62 content iso,vztmpl,backup,images,rootdir
65 my $storage_cfg_lvmthin = <<__EOD__;
68 content iso,vztmpl,backup
73 content rootdir,images
76 my $storage_cfg_local = <<__EOD__;
79 content iso,vztmpl,backup,rootdir,images
82 sub file_read_firstline
{
85 my $fh = IO
:: File-
> new ( $filename, "r" );
93 my $logfd = IO
:: File-
> new ( ">/tmp/install.log" );
95 my $proxmox_libdir = $opt_testmode ?
96 Cwd
:: cwd
() . "/testdir/var/lib/pve-installer" : "/var/lib/pve-installer" ;
97 my $proxmox_cddir = $opt_testmode ?
"../pve-cd-builder/tmp/data-gz/" : "/cdrom" ;
98 my $proxmox_pkgdir = "${proxmox_cddir}/proxmox/packages/" ;
100 my $boot_type = - d
'/sys/firmware/efi' ?
'efi' : 'bios' ;
102 my $IPV4OCTET = "(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9])?[0-9])" ;
103 my $IPV4RE = "(?:(?: $IPV4OCTET\\ .){3} $IPV4OCTET )" ;
104 my $IPV6H16 = "(?:[0-9a-fA-F]{1,4})" ;
105 my $IPV6LS32 = "(?:(?: $IPV4RE | $IPV6H16 : $IPV6H16 ))" ;
108 "(?:(?:" . "(?: $IPV6H16 :){6}) $IPV6LS32 )|" .
109 "(?:(?:" . "::(?: $IPV6H16 :){5}) $IPV6LS32 )|" .
110 "(?:(?:(?:" . " $IPV6H16 )?::(?: $IPV6H16 :){4}) $IPV6LS32 )|" .
111 "(?:(?:(?:(?: $IPV6H16 :){0,1} $IPV6H16 )?::(?: $IPV6H16 :){3}) $IPV6LS32 )|" .
112 "(?:(?:(?:(?: $IPV6H16 :){0,2} $IPV6H16 )?::(?: $IPV6H16 :){2}) $IPV6LS32 )|" .
113 "(?:(?:(?:(?: $IPV6H16 :){0,3} $IPV6H16 )?::(?: $IPV6H16 :){1}) $IPV6LS32 )|" .
114 "(?:(?:(?:(?: $IPV6H16 :){0,4} $IPV6H16 )?::" . ") $IPV6LS32 )|" .
115 "(?:(?:(?:(?: $IPV6H16 :){0,5} $IPV6H16 )?::" . ") $IPV6H16 )|" .
116 "(?:(?:(?:(?: $IPV6H16 :){0,6} $IPV6H16 )?::" . ")))" ;
118 my $IPRE = "(?: $IPV4RE | $IPV6RE )" ;
121 my $ipv4_mask_hash = {
138 '255.255.128.0' => 17 ,
139 '255.255.192.0' => 18 ,
140 '255.255.224.0' => 19 ,
141 '255.255.240.0' => 20 ,
142 '255.255.248.0' => 21 ,
143 '255.255.252.0' => 22 ,
144 '255.255.254.0' => 23 ,
145 '255.255.255.0' => 24 ,
146 '255.255.255.128' => 25 ,
147 '255.255.255.192' => 26 ,
148 '255.255.255.224' => 27 ,
149 '255.255.255.240' => 28 ,
150 '255.255.255.248' => 29 ,
151 '255.255.255.252' => 30 ,
152 '255.255.255.254' => 31 ,
153 '255.255.255.255' => 32
156 my $ipv4_reverse_mask = [
192 my $step_number = 0 ; # Init number for global function list
197 html
=> 'license.htm' ,
198 next_button
=> 'I a_gree' ,
199 function
=> \
& create_intro_view
,
204 function
=> \
& create_hdsel_view
,
208 html
=> 'country.htm' ,
209 function
=> \
& create_country_view
,
213 html
=> 'passwd.htm' ,
214 function
=> \
& create_password_view
,
218 html
=> 'ipconf.htm' ,
219 function
=> \
& create_ipconf_view
,
224 next_button
=> '_Install' ,
225 function
=> \
& create_ack_view
,
229 next_button
=> '_Reboot' ,
230 function
=> \
& create_extract_view
,
234 # GUI global variables
235 my ( $window, $cmdbox, $inbox, $htmlview );
237 my ( $next, $next_fctn, $target_hd );
238 my ( $progress, $progress_status );
240 my ( $ipversion, $ipaddress, $ipconf_entry_addr );
241 my ( $netmask, $ipconf_entry_mask );
242 my ( $gateway, $ipconf_entry_gw );
243 my ( $dnsserver, $ipconf_entry_dns );
244 my $hostname = 'proxmox' ;
245 my $domain = 'domain.tld' ;
246 my $cmdline = file_read_firstline
( "/proc/cmdline" );
249 my $timezone = 'Europe/Vienna' ;
250 my $keymap = 'en-us' ;
252 my $mailto = 'mail @example .invalid' ;
256 # TODO: add all the user-provided options for previous button
258 timezone
=> $timezone,
261 password
=> $password,
265 hostname
=> $hostname,
272 # parse command line args
274 my $config_options = {};
276 if ( $cmdline =~ m/\s(ext3|ext4|xfs)(\s.*)?$/ ) {
277 $config_options ->{ filesys
} = $1 ;
279 $config_options ->{ filesys
} = 'ext4' ;
282 if ( $cmdline =~ m/hdsize=(\d+(\.\d+)?)[\s\n]/i ) {
283 $config_options ->{ hdsize
} = $1 ;
286 if ( $cmdline =~ m/swapsize=(\d+(\.\d+)?)[\s\n]/i ) {
287 $config_options ->{ swapsize
} = $1 ;
290 if ( $cmdline =~ m/maxroot=(\d+(\.\d+)?)[\s\n]/i ) {
291 $config_options ->{ maxroot
} = $1 ;
294 if ( $cmdline =~ m/minfree=(\d+(\.\d+)?)[\s\n]/i ) {
295 $config_options ->{ minfree
} = $1 ;
298 if ( $setup ->{ product
} eq 'pve' ) {
299 if ( $cmdline =~ m/maxvz=(\d+(\.\d+)?)[\s\n]/i ) {
300 $config_options ->{ maxvz
} = $1 ;
304 my $postfix_main_cf = <<_EOD;
305 # See /usr/share/postfix/main.cf.dist for a commented, more complete version
309 smtpd_banner = \ $myhostname ESMTP \ $mail_name (Debian/GNU)
312 # appending .domain is the MUA's job.
313 append_dot_mydomain = no
315 # Uncomment the next line to generate "delayed mail" warnings
316 #delay_warning_time = 4h
318 alias_maps = hash:/etc/aliases
319 alias_database = hash:/etc/aliases
320 mydestination = \ $myhostname, localhost.\ $mydomain, localhost
322 mynetworks = 127.0.0.0/8
323 inet_interfaces = loopback-only
324 recipient_delimiter = +
331 return String
:: ShellQuote
:: shell_quote
( $str );
337 die "no arguments" if ! $cmd ;
339 return $cmd if ! ref ( $cmd );
342 foreach my $arg ( @$cmd ) { push @qa, shellquote
( $arg ); }
344 return join ( ' ' , @qa );
350 return run_command
( $cmd, undef , undef , 1 );
354 my ( $cmd, $func, $input, $noout ) = @_ ;
360 # see 'man bash' for option pipefail
361 $cmd = [ '/bin/bash' , '-c' , "set -o pipefail && $cmd " ];
366 $cmdstr = cmd2string
( $cmd );
370 if ( $input && ( $cmdstr !~ m/chpasswd/ )) {
371 $cmdtxt = "# $cmdstr <<EOD \n $input " ;
373 $cmdtxt .= " \n EOD \n " ;
375 $cmdtxt = "# $cmdstr\n " ;
383 print $logfd $cmdtxt ;
385 my $reader = IO
:: File-
> new ();
386 my $writer = IO
:: File-
> new ();
387 my $error = IO
:: File-
> new ();
393 $pid = open3
( $writer, $reader, $error, @$cmd ) || die $! ;
399 if ( $orig_pid != $$ ) {
406 print $writer $input if defined $input ;
409 my $select = new IO
:: Select
;
410 $select -> add ( $reader );
411 $select -> add ( $error );
413 my ( $ostream, $logout ) = ( '' , '' , '' );
415 while ( $select -> count ) {
416 my @handles = $select -> can_read ( 0.2 );
418 Gtk3
:: main_iteration
() while Gtk3
:: events_pending
();
420 next if ! scalar ( @handles ); # timeout
422 foreach my $h ( @handles ) {
424 my $count = sysread ( $h, $buf, 4096 );
425 if (! defined ( $count )) {
429 die "command ' $cmd ' failed: $err " ;
431 $select -> remove ( $h ) if ! $count ;
433 $ostream .= $buf if !( $noout || $func );
435 while ( $logout =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s ) {
437 & $func ( $line ) if $func ;
440 } elsif ( $h eq $error ) {
441 $ostream .= $buf if !( $noout || $func );
449 & $func ( $logout ) if $func ;
451 my $rv = waitpid ( $pid, 0 );
453 return $? if $noout ; # behave like standard system();
456 die "command ' $cmdstr ' failed to execute \n " ;
457 } elsif ( my $sig = ( $? & 127 )) {
458 die "command ' $cmdstr ' failed - got signal $sig\n " ;
459 } elsif ( my $exitcode = ( $? >> 8 )) {
460 die "command ' $cmdstr ' failed with exit code $exitcode " ;
468 print "trying to detect country... \n " ;
469 my $cpid = open2
( \
* TMP
, undef , "traceroute -N 1 -q 1 -n 8.8.8.8" );
470 return undef if ! $cpid ;
474 my $previous_alarm = alarm ( 10 );
476 local $SIG { ALRM
} = sub { die "timed out! \n " };
478 while ( defined ( $line = < TMP
>)) {
479 print $logfd "DC TRACEROUTE: $line " ;
480 if ( $line =~ m/\s*\d+\s+(\d+\.\d+\.\d+\.\d+)\s/ ) {
481 my $geoip = `geoiplookup $1` ;
482 print $logfd "DC GEOIP: $geoip ";
483 if ( $geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) {
485 print $logfd "DC FOUND: $country\n ";
494 alarm ( $previous_alarm );
499 print "unable to detect country - $err\n ";
501 print "detected country: " . uc( $country ) . " \n ";
503 print "unable to detect country \n ";
511 open (MEMINFO, "/proc/meminfo");
513 my $res = 512; # default to 512 if something goes wrong
514 while (my $line = <MEMINFO>) {
515 if ( $line =~ m/^MemTotal:\s+(\d+)\s*kB/i) {
516 $res = int ( $1 / 1024);
525 my $total_memory = get_memtotal();
528 my ( $src, $dest ) = @_ ;
530 my ( $dev1,$ino1 ) = stat ( $src );
531 my ( $dev2,$ino2 ) = stat ( $dest );
533 return 0 if !( $dev1 && $dev2 && $ino1 && $ino2 );
535 return $ino1 == $ino2 && $dev1 == $dev2 ;
538 sub find_stable_path {
539 my ( $stabledir, $bdev ) = @_ ;
541 foreach my $path (< $stabledir/* >) {
542 if (link_points_to( $path, $bdev )) {
543 return wantarray ? ( $path, basename( $path )) : $path ;
548 sub find_dev_by_uuid {
551 my ( $full_path, $name ) = find_stable_path("/dev/disk/by-uuid", $bdev );
561 my @disks = split /,/, $opt_testmode ;
563 for my $disk ( @disks ) {
564 push @$res, [-1, $disk, int((-s $disk )/512), "TESTDISK"];
571 foreach my $bd (</sys/block/*>) {
572 next if $bd =~ m|^/sys/block/ram\d+$|;
573 next if $bd =~ m|^/sys/block/loop\d+$|;
574 next if $bd =~ m|^/sys/block/md\d+$|;
575 next if $bd =~ m|^/sys/block/dm-.*$|;
576 next if $bd =~ m|^/sys/block/fd\d+$|;
577 next if $bd =~ m|^/sys/block/sr\d+$|;
579 my $dev = file_read_firstline(" $bd/dev ");
584 my $info = ` udevadm info
-- path
$bd -- query all
`;
587 next if $info !~ m/^E: DEVTYPE=disk$/m;
589 next if $info =~ m/^E: ID_CDROM/m;
591 my ( $name ) = $info =~ m/^N: (\S+)$/m;
594 my $real_name = "/dev/ $name ";
596 my $size = file_read_firstline(" $bd/size ");
598 $size = undef if !( $size && $size =~ m/^\d+$/);
600 my $model = file_read_firstline(" $bd/device/model ") || '';
603 if (length ( $model ) > 30) {
604 $model = substr ( $model, 0, 30);
606 push @$res, [ $count++, $real_name, $size, $model ] if $size ;
608 print STDERR "ERROR: unable to map device $dev ( $bd ) \n ";
616 my $countryfn = "${proxmox_libdir}/country.dat";
617 open (TMP, "< $countryfn ") || die "unable to open ' $countryfn ' - $!\n ";
620 my $countryhash = {};
623 while (defined ( $line = <TMP>)) {
624 if ( $line =~ m|^map:([^\s:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]*):$|) {
632 $kmaphash ->{ $2 } = $1 ;
633 } elsif ( $line =~ m|^([a-z]{2}):([^:]+):([^:]*):([^:]*):$|) {
639 $countryhash ->{lc( $2 )} = $1 ;
641 warn "unable to parse 'country.dat' line: $line ";
648 my $zonefn = "/usr/share/zoneinfo/zone.tab";
649 open (TMP, "< $zonefn ") || die "unable to open ' $zonefn ' - $!\n ";
650 while (defined ( $line = <TMP>)) {
651 next if $line =~ m/^\#/;
652 next if $line =~ m/^\s*$/;
653 if ( $line =~ m|^([A-Z][A-Z])\s+\S+\s+(([^/]+)/\S+)\s|) {
655 $cczones ->{ $cc }->{ $2 } = 1;
656 $country ->{ $cc }->{zone} = $2 if !defined ( $country ->{ $cc }->{zone});
667 countryhash => $countryhash,
669 kmaphash => $kmaphash,
673 # search for Harddisks
679 foreach my $hd ( @$hds ) {
680 my ( $disk, $devname, $size, $model ) = @$hd ;
681 # size is always in 512B "sectors"! convert to KB
682 return int( $size/2 ) if $devname eq $dev ;
685 die "no such device ' $dev ' \n ";
688 sub get_partition_dev {
689 my ( $dev, $partnum ) = @_ ;
691 if ( $dev =~ m|^/dev/sd([a-h]?[a-z]\|i[a-v])$|) {
692 return "${dev} $partnum ";
693 } elsif ( $dev =~ m|^/dev/xvd[a-z]$|) {
694 # Citrix Hypervisor blockdev
695 return "${dev} $partnum ";
696 } elsif ( $dev =~ m|^/dev/[hxev]d[a-z]$|) {
697 return "${dev} $partnum ";
698 } elsif ( $dev =~ m|^/dev/[^/]+/c\d+d\d+$|) {
699 return "${dev}p $partnum ";
700 } elsif ( $dev =~ m|^/dev/[^/]+/d\d+$|) {
701 return "${dev}p $partnum ";
702 } elsif ( $dev =~ m|^/dev/[^/]+/hd[a-z]$|) {
703 return "${dev} $partnum ";
704 } elsif ( $dev =~ m|^/dev/nvme\d+n\d+$|) {
705 return "${dev}p $partnum ";
707 die "unable to get device for partition $partnum on device $dev\n ";
712 sub file_get_contents {
713 my ( $filename, $max ) = @_ ;
715 my $fh = IO::File->new( $filename, "r") ||
716 die "can't open ' $filename ' - $!\n ";
718 local $/ ; # slurp mode
728 my ( $text, $filename ) = @_ ;
730 my $fd = IO::File->new("> $filename ") ||
731 die "unable to open file ' $filename ' - $!\n ";
736 sub update_progress {
737 my ( $frac, $start, $end, $text ) = @_ ;
739 my $part = $end - $start ;
740 my $res = $start + $frac*$part ;
742 $progress ->set_fraction ( $res );
743 $progress ->set_text (sprintf (" %d%% ", int ( $res*100 )));
744 $progress_status ->set_text ( $text ) if defined ( $text );
746 display_info() if $res < 0.9;
748 Gtk3::main_iteration() while Gtk3::events_pending();
753 mkfs => 'mkfs.ext3 -F',
755 mkfs_data_opt => '-m 0',
756 root_mountopt => 'errors=remount-ro',
759 mkfs => 'mkfs.ext4 -F',
761 mkfs_data_opt => '-m 0',
762 root_mountopt => 'errors=remount-ro',
765 mkfs => 'mkfs.xfs -f',
772 sub create_filesystem {
773 my ( $dev, $name, $type, $start, $end, $fs, $fe ) = @_ ;
775 my $range = $end - $start ;
776 my $rs = $start + $range*$fs ;
777 my $re = $start + $range*$fe ;
780 my $fsdata = $fssetup ->{ $type } || die "internal error - unknown file system ' $type '";
781 my $opts = $name eq 'root' ? $fsdata ->{mkfs_root_opt} : $fsdata ->{mkfs_data_opt};
783 update_progress(0, $rs, $re, "creating $name filesystem");
785 run_command(" $fsdata ->{mkfs} $opts $dev ", sub {
788 if ( $line =~ m/Writing inode tables:\s+(\d+)\/(\d+)/) {
790 } elsif ( $max && $line =~ m/(\d+)\/ $max/ ) {
791 update_progress(( $1/$max )*0.9, $rs, $re );
792 } elsif ( $line =~ m/Creating journal.*done/) {
793 update_progress(0.95, $rs, $re );
794 } elsif ( $line =~ m/Writing superblocks and filesystem.*done/) {
795 update_progress(1, $rs, $re );
801 my ( $targetdir, $dcdata ) = @_ ;
803 my $cfgfile = "/tmp/debconf.txt";
804 write_config( $dcdata, " $targetdir/$cfgfile ");
805 syscmd("chroot $targetdir debconf-set-selections $cfgfile ");
806 unlink " $targetdir/$cfgfile ";
810 my ( $targetdir, $cmd, $new_cmd ) = @_ ;
812 syscmd("chroot $targetdir dpkg-divert --package proxmox " .
813 "--add --rename $cmd ") == 0 ||
814 die "unable to exec dpkg-divert \n ";
816 syscmd("ln -sf ${new_cmd} $targetdir/$cmd ") == 0 ||
817 die "unable to link diversion to ${new_cmd} \n ";
820 sub diversion_remove {
821 my ( $targetdir, $cmd ) = @_ ;
823 syscmd("mv $targetdir/$ {cmd}.distrib $targetdir/$ {cmd};") == 0 ||
824 die "unable to remove $cmd diversion \n ";
826 syscmd("chroot $targetdir dpkg-divert --remove $cmd ") == 0 ||
827 die "unable to remove $cmd diversion \n ";
831 my ( $partitions, $mode ) = @_ ;
833 die "unknown btrfs mode ' $mode '"
834 if !( $mode eq 'single' || $mode eq 'raid0' ||
835 $mode eq 'raid1' || $mode eq 'raid10');
837 my $cmd = ['mkfs.btrfs', '-f'];
839 push @$cmd, '-d', $mode, '-m', $mode ;
841 push @$cmd, @$partitions ;
846 sub zfs_create_rpool {
849 my $cmd = "zpool create -f -o cachefile=none";
851 $cmd .= " -o ashift= $config_options ->{ashift}"
852 if defined( $config_options ->{ashift});
854 syscmd(" $cmd $zfspoolname $vdev ") == 0 ||
855 die "unable to create zfs root pool \n ";
857 syscmd("zfs create $zfspoolname/ROOT ") == 0 ||
858 die "unable to create zfs $zfspoolname/ROOT volume \n ";
860 if ( $setup ->{product} eq 'pve') {
861 syscmd("zfs create $zfspoolname/data ") == 0 ||
862 die "unable to create zfs $zfspoolname/data volume \n ";
865 syscmd("zfs create $zfspoolname/ROOT/$zfsrootvolname ") == 0 ||
866 die "unable to create zfs $zfspoolname/ROOT/$zfsrootvolname volume \n ";
868 # disable atime during install
869 syscmd("zfs set atime=off $zfspoolname ") == 0 ||
870 die "unable to set zfs properties \n ";
872 my $value = $config_options ->{compress};
873 syscmd("zfs set compression= $value $zfspoolname ")
874 if defined( $value ) && $value ne 'off';
876 $value = $config_options ->{checksum};
877 syscmd("zfs set checksum= $value $zfspoolname ")
878 if defined( $value ) && $value ne 'on';
880 $value = $config_options ->{copies};
881 syscmd("zfs set copies= $value $zfspoolname ")
882 if defined( $value ) && $value != 1;
885 my $udevadm_trigger_block = sub {
888 sleep(1) if ! $nowait ; # give kernel time to reread part table
890 # trigger udev to create /dev/disk/by-uuid
891 syscmd("udevadm trigger --subsystem-match block");
892 syscmd("udevadm settle --timeout 10");
895 my $clean_disk = sub {
898 my $partitions = ` lsblk
-- output kname
-- noheadings
-- path
-- list
$disk` ;
899 foreach my $part ( split " \n " , $partitions ) {
900 next if $part eq $disk ;
901 next if $part !~ /^\Q$disk\E/ ;
902 eval { syscmd
( "pvremove -ff -y $part " ); };
903 eval { syscmd
( "dd if=/dev/zero of= $part bs=1M count=16" ); };
907 sub partition_bootable_disk
{
908 my ( $target_dev, $maxhdsizegb, $ptype ) = @_ ;
910 die "too dangerous" if $opt_testmode ;
912 die "unknown partition type ' $ptype '"
913 if !( $ptype eq '8E00' || $ptype eq '8300' || $ptype eq 'BF01' );
915 syscmd
( "sgdisk -Z ${target_dev}" );
916 my $hdsize = hd_size
( $target_dev ); # size in KB (1024 bytes)
918 my $restricted_hdsize_mb = 0 ; # 0 ==> end of partition
920 my $maxhdsize = $maxhdsizegb * 1024 * 1024 ;
921 if ( $maxhdsize < $hdsize ) {
922 $hdsize = $maxhdsize ;
923 $restricted_hdsize_mb = int ( $hdsize/1024 ) . 'M' ;
927 my $hdgb = int ( $hdsize/ ( 1024 * 1024 ));
928 die "hardisk ' $target_dev ' too small (${hdgb}GB) \n " if $hdgb < 8 ;
930 # 1 - BIOS boot partition (Grub Stage2): first free 1M
931 # 2 - EFI ESP: next free 512M
932 # 3 - OS/Data partition: rest, up to $maxhdsize in MB
934 my $grubbootdev = get_partition_dev
( $target_dev, 1 );
935 my $efibootdev = get_partition_dev
( $target_dev, 2 );
936 my $osdev = get_partition_dev
( $target_dev, 3 );
938 my $pcmd = [ 'sgdisk' ];
941 push @$pcmd, "-n${pnum}:1M:+512M" , "-t $pnum :EF00" ;
944 push @$pcmd, "-n${pnum}:513M:${restricted_hdsize_mb}" , "-t $pnum : $ptype " ;
946 push @$pcmd, $target_dev ;
948 my $os_size = $hdsize - 513 * 1024 ; # 512M efi + 1M bios_boot + 1M alignment
950 syscmd
( $pcmd ) == 0 ||
951 die "unable to partition harddisk '${target_dev}' \n " ;
954 $pcmd = [ 'sgdisk' , '-a1' , "-n $pnum :34:2047" , "-t $pnum :EF02" , $target_dev ];
956 syscmd
( $pcmd ) == 0 ||
957 die "unable to create bios_boot partition '${target_dev}' \n " ;
959 & $udevadm_trigger_block ();
961 foreach my $part ( $efibootdev, $osdev ) {
962 syscmd
( "dd if=/dev/zero of= $part bs=1M count=256" ) if - b
$part ;
965 return ( $os_size, $osdev, $efibootdev );
968 sub create_lvm_volumes
{
969 my ( $lvmdev, $os_size, $swap_size ) = @_ ;
971 my $vgname = $setup ->{ product
};
973 my $rootdev = "/dev/ $vgname/root " ;
974 my $datadev = "/dev/ $vgname/data " ;
977 # we use --metadatasize 250k, which results in "pe_start = 512"
978 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
979 syscmd
( "/sbin/pvcreate --metadatasize 250k -y -ff $lvmdev " ) == 0 ||
980 die "unable to initialize physical volume $lvmdev\n " ;
981 syscmd
( "/sbin/vgcreate $vgname $lvmdev " ) == 0 ||
982 die "unable to create volume group ' $vgname ' \n " ;
984 my $hdgb = int ( $os_size/ ( 1024 * 1024 ));
985 my $space = (( $hdgb > 128 ) ?
16 : ( $hdgb/8 ))* 1024 * 1024 ;
990 if ( $setup ->{ product
} eq 'pve' ) {
993 if ( $config_options ->{ maxroot
}) {
994 $maxroot = $config_options ->{ maxroot
};
999 $rootsize = (( $hdgb > ( $maxroot*4 )) ?
$maxroot : $hdgb/4 )* 1024 * 1024 ;
1001 my $rest = $os_size - $swap_size - $rootsize ; # in KB
1004 if ( defined ( $config_options ->{ minfree
})) {
1005 $minfree = (( $config_options ->{ minfree
}* 1024 * 1024 ) >= $rest ) ?
$space :
1006 $config_options ->{ minfree
}* 1024 * 1024 ;
1011 $rest = $rest - $minfree ;
1013 if ( defined ( $config_options ->{ maxvz
})) {
1014 $rest = (( $config_options ->{ maxvz
}* 1024 * 1024 ) <= $rest ) ?
1015 $config_options ->{ maxvz
}* 1024 * 1024 : $rest ;
1021 my $minfree = defined ( $config_options ->{ minfree
}) ?
$config_options ->{ minfree
}* 1024 * 1024 : $space ;
1022 $rootsize = $os_size - $minfree - $swap_size ; # in KB
1026 syscmd
( "/sbin/lvcreate -L${swap_size}K -nswap $vgname " ) == 0 ||
1027 die "unable to create swap volume \n " ;
1029 $swapfile = "/dev/ $vgname/swap " ;
1032 syscmd
( "/sbin/lvcreate -L${rootsize}K -nroot $vgname " ) == 0 ||
1033 die "unable to create root volume \n " ;
1035 if ( $datasize > 4 * 1024 * 1024 ) {
1036 my $metadatasize = $datasize/100 ; # default 1% of data
1037 $metadatasize = 1024 * 1024 if $metadatasize < 1024 * 1024 ; # but at least 1G
1038 $metadatasize = 16 * 1024 * 1024 if $metadatasize > 16 * 1024 * 1024 ; # but at most 16G
1040 # otherwise the metadata is taken out of $minfree
1041 $datasize -= 2 * $metadatasize ;
1043 # 1 4MB PE to allow for rounding
1044 $datasize -= 4 * 1024 ;
1046 syscmd
( "/sbin/lvcreate -L${datasize}K -ndata $vgname " ) == 0 ||
1047 die "unable to create data volume \n " ;
1049 syscmd
( "/sbin/lvconvert --yes --type thin-pool --poolmetadatasize ${metadatasize}K $vgname/data " ) == 0 ||
1050 die "unable to create data thin-pool \n " ;
1055 syscmd
( "/sbin/vgchange -a y $vgname " ) == 0 ||
1056 die "unable to activate volume group \n " ;
1058 return ( $rootdev, $swapfile, $datadev );
1061 sub compute_swapsize
{
1064 my $hdgb = int ( $hdsize/ ( 1024 * 1024 ));
1067 if ( defined ( $config_options ->{ swapsize
})) {
1068 $swapsize = $config_options ->{ swapsize
}* 1024 * 1024 ;
1070 my $ss = int ( $total_memory / 1024 );
1072 $ss = ( $hdgb/8 ) if $ss > ( $hdgb/8 );
1074 $swapsize = $ss*1024*1024 ;
1080 sub prepare_systemd_boot_esp
{
1081 my ( $espdev, $targetdir ) = @_ ;
1083 my $espuuid = find_dev_by_uuid
( $espdev );
1084 my $espmp = "var/tmp/ $espuuid " ;
1085 mkdir " $targetdir/$espmp " ;
1087 syscmd
( "mount -n $espdev -t vfat $targetdir/$espmp " ) == 0 ||
1088 die "unable to mount ESP $espdev\n " ;
1090 File
:: Path
:: make_path
( " $targetdir/$espmp/EFI/proxmox " ) ||
1091 die "unable to create directory $targetdir/$espmp/EFI/proxmox\n " ;
1093 syscmd
( "chroot $targetdir bootctl --no-variables --path / $espmp install" ) == 0 ||
1094 die "unable to install systemd-boot loader \n " ;
1095 write_config
( "timeout 3 \n default proxmox-* \n " ,
1096 " $targetdir/$espmp/loader/loader .conf" );
1097 syscmd
( "chroot $targetdir /etc/kernel/postinst.d/zz-pve-efiboot" ) == 0 ||
1098 die "unable to generate systemd-boot config \n " ;
1100 syscmd
( "umount $targetdir/$espmp " ) == 0 ||
1101 die "unable to umount ESP $targetdir/$espmp\n " ;
1106 my ( $basefile, $targetdir ) = @_ ;
1108 die "target ' $targetdir ' does not exist \n " if ! - d
$targetdir ;
1110 my $starttime = [ Time
:: HiRes
:: gettimeofday
];
1112 my $bootdevinfo = [];
1121 my $filesys = $config_options ->{ filesys
};
1123 if ( $filesys =~ m/zfs/ ) {
1124 $target_hd = undef ; # do not use this config
1126 $targetdir = "/ $zfspoolname/ROOT/$zfsrootvolname " ;
1127 } elsif ( $filesys =~ m/btrfs/ ) {
1128 $target_hd = undef ; # do not use this config
1134 for ( $i = 5 ; $i > 0 ; $i --) {
1135 syscmd
( "modprobe zfs" );
1136 last if - c
"/dev/zfs" ;
1140 die "unable to load zfs kernel module \n " if ! $i ;
1148 update_progress
( 0 , 0 , $maxper, "create partitions" );
1150 syscmd
( "vgchange -an" ) if ! $opt_testmode ; # deactivate all detected VGs
1152 if ( $opt_testmode ) {
1154 $rootdev = abs_path
( $opt_testmode );
1155 syscmd
( "umount $rootdev " );
1159 die "unsupported btrfs mode (for testing environment) \n "
1160 if $filesys ne 'btrfs (RAID0)' ;
1162 btrfs_create
([ $rootdev ], 'single' );
1164 } elsif ( $use_zfs ) {
1166 die "unsupported zfs mode (for testing environment) \n "
1167 if $filesys ne 'zfs (RAID0)' ;
1169 syscmd
( "zpool destroy $zfstestpool " );
1171 zfs_create_rpool
( $rootdev );
1178 } elsif ( $use_btrfs ) {
1180 my ( $devlist, $btrfs_mode ) = get_btrfs_raid_setup
();
1181 my $btrfs_partitions = [];
1183 foreach my $hd ( @$devlist ) {
1184 my $devname = @$hd [ 1 ];
1185 & $clean_disk ( $devname );
1186 my ( $size, $osdev, $efidev ) =
1187 partition_bootable_disk
( $devname, undef , '8300' );
1188 $rootdev = $osdev if ! defined ( $rootdev ); # simply point to first disk
1189 my $by_id = find_stable_path
( "/dev/disk/by-id" , $devname );
1190 push @$bootdevinfo, { esp
=> $efidev, devname
=> $devname,
1191 osdev
=> $osdev, by_id
=> $by_id };
1192 push @$btrfs_partitions, $osdev ;
1196 & $udevadm_trigger_block ();
1198 btrfs_create
( $btrfs_partitions, $btrfs_mode );
1200 } elsif ( $use_zfs ) {
1202 my ( $devlist, $bootdevlist, $vdev ) = get_zfs_raid_setup
();
1205 foreach my $hd ( @$devlist ) {
1206 & $clean_disk ( @$hd [ 1 ]);
1208 foreach my $hd ( @$bootdevlist ) {
1209 my $devname = @$hd [ 1 ];
1211 my ( $size, $osdev, $efidev ) =
1212 partition_bootable_disk
( $devname, $config_options ->{ hdsize
}, 'BF01' );
1213 zfs_mirror_size_check
( $disksize, $size ) if $disksize ;
1214 push @$bootdevinfo, { esp
=> $efidev, devname
=> $devname,
1219 & $udevadm_trigger_block ();
1221 foreach my $di ( @$bootdevinfo ) {
1222 my $devname = $di ->{ devname
};
1223 $di ->{ by_id
} = find_stable_path
( "/dev/disk/by-id" , $devname );
1225 # Note: using /dev/disk/by-id/ does not work for unknown reason, we get
1226 # cannot create 'rpool': no such pool or dataset
1227 #my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
1229 my $osdev = $di ->{ osdev
};
1230 $vdev =~ s/ $devname/ $osdev/ ;
1233 zfs_create_rpool
( $vdev );
1237 die "target ' $target_hd ' is not a valid block device \n " if ! - b
$target_hd ;
1239 & $clean_disk ( $target_hd );
1241 my ( $os_size, $osdev, $efidev );
1242 ( $os_size, $osdev, $efidev ) =
1243 partition_bootable_disk
( $target_hd, $config_options ->{ hdsize
}, '8E00' );
1245 & $udevadm_trigger_block ();
1247 my $by_id = find_stable_path
( "/dev/disk/by-id" , $target_hd );
1248 push @$bootdevinfo, { esp
=> $efidev, devname
=> $target_hd,
1249 osdev
=> $osdev, by_id
=> $by_id };
1251 my $swap_size = compute_swapsize
( $os_size );
1252 ( $rootdev, $swapfile, $datadev ) =
1253 create_lvm_volumes
( $osdev, $os_size, $swap_size );
1255 # trigger udev to create /dev/disk/by-uuid
1256 & $udevadm_trigger_block ( 1 );
1260 # to be fast during installation
1261 syscmd
( "zfs set sync=disabled $zfspoolname " ) == 0 ||
1262 die "unable to set zfs properties \n " ;
1265 update_progress
( 0.03 , 0 , $maxper, "create swap space" );
1267 syscmd
( "mkswap -f $swapfile " ) == 0 ||
1268 die "unable to create swap space \n " ;
1271 update_progress
( 0.05 , 0 , $maxper, "creating filesystems" );
1273 foreach my $di ( @$bootdevinfo ) {
1274 next if ! $di ->{ esp
};
1275 syscmd
( "mkfs.vfat -F32 $di ->{esp}" ) == 0 ||
1276 die "unable to initialize EFI ESP on device $di ->{esp} \n " ;
1281 } elsif ( $use_btrfs ) {
1284 create_filesystem
( $rootdev, 'root' , $filesys, 0.05 , $maxper, 0 , 1 );
1287 update_progress
( 1 , 0.05 , $maxper, "mounting target $rootdev " );
1292 my $mount_opts = 'noatime' ;
1293 $mount_opts .= ',nobarrier'
1294 if $use_btrfs || $filesys =~ /^ext\d$/ ;
1296 syscmd
( "mount -n $rootdev -o $mount_opts $targetdir " ) == 0 ||
1297 die "unable to mount $rootdev\n " ;
1300 mkdir " $targetdir/boot " ;
1301 mkdir " $targetdir/boot/efi " ;
1303 mkdir " $targetdir/var " ;
1304 mkdir " $targetdir/var/lib " ;
1306 if ( $setup ->{ product
} eq 'pve' ) {
1307 mkdir " $targetdir/var/lib/vz " ;
1308 mkdir " $targetdir/var/lib/pve " ;
1311 syscmd
( "btrfs subvolume create $targetdir/var/lib/pve/local -btrfs" ) == 0 ||
1312 die "unable to create btrfs subvolume \n " ;
1316 mkdir " $targetdir/mnt " ;
1317 mkdir " $targetdir/mnt/hostrun " ;
1318 syscmd
( "mount --bind /run $targetdir/mnt/hostrun " ) == 0 ||
1319 die "unable to bindmount run on $targetdir/mnt/hostrun\n " ;
1321 update_progress
( 1 , 0.05 , $maxper, "extracting base system" );
1323 my ( $dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size ) = stat ( $basefile );
1324 $ino || die "unable to open file ' $basefile ' - $!\n " ;
1326 my $files = file_read_firstline
( "${proxmox_cddir}/proxmox/ $setup ->{product}-base.cnt" ) ||
1327 die "unable to read base file count \n " ;
1332 run_command
( "unsquashfs -f -dest $targetdir -i $basefile " , sub {
1334 return if $line !~ m/^$targetdir/ ;
1336 my $nper = int (( $count * 100 )/ $files );
1337 if ( $nper != $per ) {
1339 my $frac = $per > 100 ?
1 : $per/100 ;
1340 update_progress
( $frac, $maxper, 0.5 );
1344 syscmd
( "mount -n -t tmpfs tmpfs $targetdir/tmp " ) == 0 ||
1345 die "unable to mount tmpfs on $targetdir/tmp\n " ;
1346 syscmd
( "mount -n -t proc proc $targetdir/proc " ) == 0 ||
1347 die "unable to mount proc on $targetdir/proc\n " ;
1348 syscmd
( "mount -n -t sysfs sysfs $targetdir/sys " ) == 0 ||
1349 die "unable to mount sysfs on $targetdir/sys\n " ;
1350 syscmd
( "chroot $targetdir mount --bind /mnt/hostrun /run" ) == 0 ||
1351 die "unable to re-bindmount hostrun on /run in chroot \n " ;
1353 update_progress
( 1 , $maxper, 0.5 , "configuring base system" );
1358 "127.0.0.1 localhost.localdomain localhost \n " .
1359 " $ipaddress $hostname . $domain $hostname\n\n " .
1360 "# The following lines are desirable for IPv6 capable hosts \n\n " .
1361 "::1 ip6-localhost ip6-loopback \n " .
1362 "fe00::0 ip6-localnet \n " .
1363 "ff00::0 ip6-mcastprefix \n " .
1364 "ff02::1 ip6-allnodes \n " .
1365 "ff02::2 ip6-allrouters \n " .
1366 "ff02::3 ip6-allhosts \n " ;
1368 write_config
( $hosts, " $targetdir/etc/hosts " );
1370 write_config
( " $hostname\n " , " $targetdir/etc/hostname " );
1372 syscmd
( "/bin/hostname $hostname " ) if ! $opt_testmode ;
1374 # configure interfaces
1376 my $ifaces = "auto lo \n iface lo inet loopback \n\n " ;
1378 my $ntype = $ipversion == 4 ?
'inet' : 'inet6' ;
1380 my $ethdev = $ipconf ->{ ifaces
}->{ $ipconf ->{ selected
}}->{ name
};
1382 if ( $setup ->{ bridged_network
}) {
1383 $ifaces .= "iface $ethdev $ntype manual \n " ;
1386 " \n auto vmbr0 \n iface vmbr0 $ntype static \n " .
1387 " \t address $ipaddress\n " .
1388 " \t netmask $netmask\n " .
1389 " \t gateway $gateway\n " .
1390 " \t bridge_ports $ethdev\n " .
1391 " \t bridge_stp off \n " .
1394 $ifaces .= "auto $ethdev\n " .
1395 "iface $ethdev $ntype static \n " .
1396 " \t address $ipaddress\n " .
1397 " \t netmask $netmask\n " .
1398 " \t gateway $gateway\n " ;
1401 foreach my $iface ( sort keys %{ $ipconf ->{ ifaces
}}) {
1402 my $name = $ipconf ->{ ifaces
}->{ $iface }->{ name
};
1403 next if $name eq $ethdev ;
1405 $ifaces .= " \n iface $name $ntype manual \n " ;
1408 write_config
( $ifaces, " $targetdir/etc/network/interfaces " );
1412 my $resolvconf = "search $domain\nnameserver $dnsserver\n " ;
1413 write_config
( $resolvconf, " $targetdir/etc/resolv .conf" );
1417 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass> \n " ;
1421 } elsif ( $use_btrfs ) {
1423 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev " ;
1424 run_command
( $cmd, sub {
1427 if ( $line =~ m/^UUID=([A-Fa-f0-9\-]+)$/ ) {
1432 die "unable to detect FS UUID" if ! defined ( $fsuuid );
1434 $fstab .= "UUID= $fsuuid / btrfs defaults 0 1 \n " ;
1436 my $root_mountopt = $fssetup ->{ $filesys }->{ root_mountopt
} || 'defaults' ;
1437 $fstab .= " $rootdev / $filesys ${root_mountopt} 0 1 \n " ;
1441 # Note: this is required by current grub, but really dangerous, because
1442 # vfat does not have journaling, so it triggers manual fsck after each crash
1443 # so we only mount /boot/efi if really required (efi systems).
1444 if ( $boot_type eq 'efi' ) {
1445 if ( scalar ( @$bootdevinfo )) {
1446 my $di = @$bootdevinfo [ 0 ]; # simply use first disk
1447 if ( $di ->{ esp
} && ! $use_zfs ) {
1448 my $efi_boot_uuid = $di ->{ esp
};
1449 if ( my $uuid = find_dev_by_uuid
( $di ->{ esp
})) {
1450 $efi_boot_uuid = "UUID= $uuid " ;
1453 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1 \n " ;
1459 $fstab .= " $swapfile none swap sw 0 0 \n " if $swapfile ;
1461 $fstab .= "proc /proc proc defaults 0 0 \n " ;
1463 write_config
( $fstab, " $targetdir/etc/fstab " );
1464 write_config
( "" , " $targetdir/etc/mtab " );
1466 syscmd
( "cp ${proxmox_libdir}/policy-disable-rc.d " .
1467 " $targetdir/usr/sbin/policy -rc.d" ) == 0 ||
1468 die "unable to copy policy-rc.d \n " ;
1469 syscmd
( "cp ${proxmox_libdir}/fake-start-stop-daemon " .
1470 " $targetdir/sbin/ " ) == 0 ||
1471 die "unable to copy start-stop-daemon \n " ;
1473 diversion_add
( $targetdir, "/sbin/start-stop-daemon" , "/sbin/fake-start-stop-daemon" );
1474 diversion_add
( $targetdir, "/usr/sbin/update-grub" , "/bin/true" );
1475 diversion_add
( $targetdir, "/usr/sbin/update-initramfs" , "/bin/true" );
1477 syscmd
( "touch $targetdir/proxmox_install_mode " );
1479 my $grub_install_devices_txt = '' ;
1480 foreach my $di ( @$bootdevinfo ) {
1481 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt ;
1482 $grub_install_devices_txt .= $di ->{ by_id
} || $di ->{ devname
};
1485 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1486 my $xkmap = $cmap ->{ kmap
}->{ $keymap }->{ x11
} // 'us' ;
1488 debconfig_set
( $targetdir, <<_EOD);
1489 locales locales/default_environment_locale select en_US.UTF-8
1490 locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1491 samba-common samba-common/dhcp boolean false
1492 samba-common samba-common/workgroup string WORKGROUP
1493 postfix postfix/main_mailer_type select No configuration
1494 keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
1495 d-i debian-installer/locale select en_US.UTF-8
1496 grub-pc grub-pc/install_devices select $grub_install_devices_txt
1500 while (<${ proxmox_pkgdir
}/*. deb
>) { $pkg_count++ };
1502 # btrfs/dpkg is extremely slow without --force-unsafe-io
1503 my $dpkg_opts = $use_btrfs ?
"--force-unsafe-io" : "" ;
1506 while (<${ proxmox_pkgdir
}/*. deb
>) {
1509 my ( $deb ) = $path =~ m/${proxmox_pkgdir}\/ (.* \
. deb
)/;
1510 update_progress
( $count/$pkg_count, 0.5 , 0.75 , "extracting $deb " );
1511 print "extracting: $deb\n " ;
1512 syscmd
( "cp $path $targetdir/tmp/$deb " ) == 0 ||
1513 die "installation of package $deb failed \n " ;
1514 syscmd
( "chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/ $deb " ) == 0 ||
1515 die "installation of package $deb failed \n " ;
1516 update_progress
((++ $count )/ $pkg_count, 0.5 , 0.75 );
1519 # needed for postfix postinst in case no other NIC is active
1520 syscmd
( "chroot $targetdir ifup lo" );
1522 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a" ;
1524 run_command
( $cmd, sub {
1526 if ( $line =~ m/Setting up\s+(\S+)/ ) {
1527 update_progress
((++ $count )/ $pkg_count, 0.75 , 0.95 ,
1532 unlink " $targetdir/etc/mailname " ;
1533 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/ ;
1534 write_config
( $postfix_main_cf, " $targetdir/etc/postfix/main .cf" );
1536 # make sure we have all postfix directories
1537 syscmd
( "chroot $targetdir /usr/sbin/postfix check" );
1538 # cleanup mail queue
1539 syscmd
( "chroot $targetdir /usr/sbin/postsuper -d ALL" );
1541 # enable NTP (timedatectl set-ntp true does not work without DBUS)
1542 syscmd
( "chroot $targetdir /bin/systemctl enable systemd-timesyncd.service" );
1544 unlink " $targetdir/proxmox_install_mode " ;
1547 unlink ( " $targetdir/etc/localtime " );
1548 symlink ( "/usr/share/zoneinfo/ $timezone " , " $targetdir/etc/localtime " );
1549 write_config
( " $timezone\n " , " $targetdir/etc/timezone " );
1552 if ( my $mirror = $cmap ->{ country
}->{ $country }->{ mirror
}) {
1553 my $fn = " $targetdir/etc/apt/sources .list" ;
1554 syscmd
( "sed -i 's/ftp\\.debian\\.org/$mirror/' ' $fn '" );
1557 # create extended_states for apt (avoid cron job warning if that
1558 # file does not exist)
1559 write_config
( '' , " $targetdir/var/lib/apt/extended_states " );
1561 # allow ssh root login
1562 syscmd
([ 'sed' , '-i' , 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' , " $targetdir/etc/ssh/sshd_config " ]);
1564 if ( $setup ->{ product
} eq 'pmg' ) {
1565 # install initial clamav DB
1566 my $srcdir = "${proxmox_cddir}/proxmox/clamav" ;
1567 foreach my $fn ( "main.cvd" , "bytecode.cvd" , "daily.cvd" , "safebrowsing.cvd" ) {
1568 syscmd
( "cp \" $srcdir/$fn\ " \" $targetdir/var/lib/clamav\ "" ) == 0 ||
1569 die "installation of clamav db file ' $fn ' failed \n " ;
1571 syscmd
( "chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav" ) == 0 ||
1572 die "unable to set owner for clamav database files \n " ;
1575 if ( $setup ->{ product
} eq 'pve' ) {
1576 # save installer settings
1577 my $ucc = uc ( $country );
1578 debconfig_set
( $targetdir, "pve-manager pve-manager/country string $ucc\n " );
1581 update_progress
( 0.8 , 0.95 , 1 , "make system bootable" );
1584 syscmd
( "sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT \\ / $zfsrootvolname boot=zfs \" /' $targetdir/etc/default/grub " ) == 0 ||
1585 die "unable to update /etc/default/grub \n " ;
1586 if ( $boot_type eq 'efi' ) {
1587 write_config
( "root=ZFS= $zfspoolname/ROOT/$zfsrootvolname boot=zfs" , " $targetdir/etc/kernel/cmdline " );
1592 diversion_remove
( $targetdir, "/usr/sbin/update-grub" );
1593 diversion_remove
( $targetdir, "/usr/sbin/update-initramfs" );
1596 foreach my $fn (< $targetdir/lib/modules/* >) {
1597 if ( $fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$! ) {
1598 die "found multiple kernels \n " if defined ( $kapi );
1602 die "unable to detect kernel version \n " if ! defined ( $kapi );
1604 if (! $opt_testmode ) {
1606 unlink ( " $targetdir/etc/mtab " );
1607 symlink ( "/proc/mounts" , " $targetdir/etc/mtab " );
1608 syscmd
( "mount -n --bind /dev $targetdir/dev " );
1610 syscmd
( "chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi " ) == 0 ||
1611 die "unable to install initramfs \n " ;
1613 foreach my $di ( @$bootdevinfo ) {
1614 my $dev = $di ->{ devname
};
1615 syscmd
( "chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev " ) == 0 ||
1616 die "unable to install the i386-pc boot loader on ' $dev ' \n " ;
1618 if ( $di ->{ esp
} && $use_zfs ) {
1619 prepare_systemd_boot_esp
( $di ->{ esp
}, $targetdir );
1620 } elsif ( $di ->{ esp
}) {
1621 syscmd
( "mount -n $di ->{esp} -t vfat $targetdir/boot/efi " ) == 0 ||
1622 die "unable to mount $di ->{esp} \n " ;
1623 my $rc = syscmd
( "chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev " );
1625 if (- d
'/sys/firmware/efi' ) {
1626 die "unable to install the EFI boot loader on ' $dev ' \n " ;
1628 warn "unable to install the EFI boot loader on ' $dev ', ignoring (not booted using UEFI) \n " ;
1631 # also install fallback boot file (OVMF does not boot without)
1632 mkdir ( " $targetdir/boot/efi/EFI/BOOT " );
1633 syscmd
( "cp $targetdir/boot/efi/EFI/proxmox/grubx64 .efi $targetdir/boot/efi/EFI/BOOT/BOOTx64 .EFI" ) == 0 ||
1634 die "unable to copy efi boot loader \n " ;
1636 syscmd
( "umount $targetdir/boot/efi " ) == 0 ||
1637 die "unable to umount $targetdir/boot/efi\n " ;
1641 syscmd
( "chroot $targetdir /usr/sbin/update-grub" ) == 0 ||
1642 die "unable to update boot loader config \n " ;
1644 syscmd
( "umount $targetdir/dev " );
1649 unlink " $targetdir/usr/sbin/policy -rc.d" ;
1651 diversion_remove
( $targetdir, "/sbin/start-stop-daemon" );
1654 my $octets = encode
( "utf-8" , $password );
1655 run_command
( "chroot $targetdir /usr/sbin/chpasswd" , undef ,
1658 if ( $setup ->{ product
} eq 'pmg' ) {
1660 write_config
( "section: admin \n\t email ${mailto} \n " ,
1661 " $targetdir/etc/pmg/pmg .conf" );
1663 } elsif ( $setup ->{ product
} eq 'pve' ) {
1667 my $tmpdir = " $targetdir/tmp/pve " ;
1670 # write vnc keymap to datacenter.cfg
1671 my $vnckmap = $cmap ->{ kmap
}->{ $keymap }->{ kvm
} || 'en-us' ;
1672 write_config
( "keyboard: $vnckmap\n " ,
1673 " $tmpdir/datacenter .cfg" );
1676 write_config
( "user:root\ @pam :1:0:::${mailto}:: \n " ,
1677 " $tmpdir/user .cfg" );
1680 my $storage_cfg_fn = " $tmpdir/storage .cfg" ;
1682 write_config
( $storage_cfg_zfs, $storage_cfg_fn );
1683 } elsif ( $use_btrfs ) {
1684 write_config
( $storage_cfg_btrfs, $storage_cfg_fn );
1685 } elsif ( $datadev ) {
1686 write_config
( $storage_cfg_lvmthin, $storage_cfg_fn );
1688 write_config
( $storage_cfg_local, $storage_cfg_fn );
1691 run_command
( "chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db" );
1693 syscmd
( "rm -rf $tmpdir " );
1699 update_progress
( 1 , 0 , 1 , "" );
1703 if ( $opt_testmode ) {
1704 my $elapsed = Time
:: HiRes
:: tv_interval
( $starttime );
1705 print "Elapsed extract time: $elapsed\n " ;
1707 syscmd
( "chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package} \n '> final.pkglist" );
1710 syscmd
( "umount $targetdir/run " );
1711 syscmd
( "umount $targetdir/mnt/hostrun " );
1712 syscmd
( "umount $targetdir/tmp " );
1713 syscmd
( "umount $targetdir/proc " );
1714 syscmd
( "umount $targetdir/sys " );
1717 syscmd
( "zfs umount -a" ) == 0 ||
1718 die "unable to unmount zfs \n " ;
1720 syscmd
( "umount -d $targetdir " );
1723 if (! $err && $use_zfs ) {
1724 syscmd
( "zfs set sync=standard $zfspoolname " ) == 0 ||
1725 die "unable to set zfs properties \n " ;
1727 syscmd
( "zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname " ) == 0 ||
1728 die "zfs set mountpoint failed \n " ;
1730 syscmd
( "zpool set bootfs= $zfspoolname/ROOT/$zfsrootvolname $zfspoolname " ) == 0 ||
1731 die "zfs set bootfs failed \n " ;
1732 syscmd
( "zpool export $zfspoolname " );
1738 my $last_display_change = 0 ;
1740 my $display_info_counter = 0 ;
1742 my $display_info_items = [
1743 "extract1-license.htm" ,
1744 "extract2-rulesystem.htm" ,
1745 "extract3-spam.htm" ,
1746 "extract4-virus.htm" ,
1751 my $min_display_time = 15 ;
1755 return if ( $ctime - $last_display_change ) < $min_display_time ;
1757 my $page = $display_info_items ->[ $display_info_counter % scalar ( @$display_info_items )];
1759 $display_info_counter++ ;
1761 display_html
( $page );
1765 my ( $filename ) = @_ ;
1767 $filename = $steps [ $step_number ]->{ html
} if ! $filename ;
1769 my $path = "${proxmox_libdir}/html/ $filename " ;
1771 my $url = "file:// $path " ;
1773 my $data = file_get_contents
( $path );
1775 if ( $filename eq 'license.htm' ) {
1776 my $license = eval { decode
( 'utf8' , file_get_contents
( "${proxmox_cddir}/EULA" )) };
1778 die $err if ! $opt_testmode ;
1779 $license = "TESTMODE: Ignore non existent EULA... \n " ;
1781 my $title = "END USER LICENSE AGREEMENT (EULA)" ;
1782 $data =~ s/__LICENSE__/$license/ ;
1783 $data =~ s/__LICENSE_TITLE__/$title/ ;
1786 $htmlview -> load_html ( $data, $url );
1788 $last_display_change = time ();
1793 my ( $text, $fctn ) = @_ ;
1795 $fctn = $step_number if ! $fctn ;
1796 $text = "_Previous" if ! $text ;
1797 $prev_btn -> set_label ( $text );
1800 $steps [ $step_number ]->{ function
}();
1802 $prev_btn -> grab_focus ();
1806 my ( $text, $fctn ) = @_ ;
1809 my $step = $steps [ $step_number ];
1810 $text // = $steps [ $step_number ]->{ next_button
} // '_Next' ;
1811 $next -> set_label ( $text );
1813 $next -> grab_focus ();
1816 sub create_main_window
{
1818 $window = Gtk3
:: Window-
> new ();
1819 $window -> set_default_size ( 1024 , 768 );
1820 $window -> set_has_resize_grip ( 0 );
1821 $window -> set_decorated ( 0 ) if ! $opt_testmode ;
1823 my $vbox = Gtk3
:: VBox-
> new ( 0 , 0 );
1825 my $logofn = " $setup ->{product}-banner.png" ;
1826 my $image = Gtk3
:: Image-
> new_from_file ( "${proxmox_libdir}/ $logofn " );
1827 $vbox -> pack_start ( $image, 0 , 0 , 0 );
1829 my $hbox = Gtk3
:: HBox-
> new ( 0 , 0 );
1830 $vbox -> pack_start ( $hbox, 1 , 1 , 0 );
1832 # my $f1 = Gtk3::Frame->new ('test');
1833 # $f1->set_shadow_type ('none');
1834 # $hbox->pack_start ($f1, 1, 1, 0);
1836 my $sep1 = Gtk3
:: HSeparator-
> new ();
1837 $vbox -> pack_start ( $sep1, 0 , 0 , 0 );
1839 $cmdbox = Gtk3
:: HBox-
> new ();
1840 $vbox -> pack_start ( $cmdbox, 0 , 0 , 10 );
1842 $next = Gtk3
:: Button-
> new ( '_Next' );
1843 $next -> signal_connect ( clicked
=> sub { $last_display_change = 0 ; & $next_fctn (); });
1844 $cmdbox -> pack_end ( $next, 0 , 0 , 10 );
1847 $prev_btn = Gtk3
:: Button-
> new ( '_Previous' );
1848 $prev_btn -> signal_connect ( clicked
=> sub { $last_display_change = 0 ; & prev_function
(); });
1849 $cmdbox -> pack_end ( $prev_btn, 0 , 0 , 10 );
1852 my $abort = Gtk3
:: Button-
> new ( '_Abort' );
1853 $abort -> set_can_focus ( 0 );
1854 $cmdbox -> pack_start ( $abort, 0 , 0 , 10 );
1855 $abort -> signal_connect ( clicked
=> sub { exit (- 1 ); });
1857 my $vbox2 = Gtk3
:: VBox-
> new ( 0 , 0 );
1860 $htmlview = Gtk3
:: WebKit2
:: WebView-
> new ();
1861 my $scrolls = Gtk3
:: ScrolledWindow-
> new ();
1862 $scrolls -> add ( $htmlview );
1864 my $hbox2 = Gtk3
:: HBox-
> new ( 0 , 0 );
1865 $hbox2 -> pack_start ( $scrolls, 1 , 1 , 0 );
1867 $vbox2 -> pack_start ( $hbox2, 1 , 1 , 0 );
1869 my $vbox3 = Gtk3
:: VBox-
> new ( 0 , 0 );
1870 $vbox2 -> pack_start ( $vbox3, 0 , 0 , 0 );
1872 my $sep2 = Gtk3
:: HSeparator-
> new ;
1873 $vbox3 -> pack_start ( $sep2, 0 , 0 , 0 );
1875 $inbox = Gtk3
:: HBox-
> new ( 0 , 0 );
1876 $vbox3 -> pack_start ( $inbox, 0 , 0 , 0 );
1878 $window -> add ( $vbox );
1885 $inbox -> foreach ( sub {
1887 $inbox -> remove ( $child );
1891 # fixme: newer GTK3 has special properties to handle numbers with Entry
1892 # only allow floating point numbers with Gtk3::Entry
1895 my ( $entry, $event ) = @_ ;
1897 return check_number
( $entry, $event, 1 );
1901 my ( $entry, $event ) = @_ ;
1903 return check_number
( $entry, $event, 0 );
1907 my ( $entry, $event, $float ) = @_ ;
1909 my $val = $event -> get_keyval ;
1911 if (( $float && $val == ord '.' ) ||
1912 $val == Gtk3
:: Gdk
:: KEY_ISO_Left_Tab
||
1913 $val == Gtk3
:: Gdk
:: KEY_Shift_L
||
1914 $val == Gtk3
:: Gdk
:: KEY_Tab
||
1915 $val == Gtk3
:: Gdk
:: KEY_Left
||
1916 $val == Gtk3
:: Gdk
:: KEY_Right
||
1917 $val == Gtk3
:: Gdk
:: KEY_BackSpace
||
1918 $val == Gtk3
:: Gdk
:: KEY_Delete
||
1919 ( $val >= ord '0' && $val <= ord '9' ) ||
1920 ( $val >= Gtk3
:: Gdk
:: KEY_KP_0
&&
1921 $val <= Gtk3
:: Gdk
:: KEY_KP_9
)) {
1928 sub create_text_input
{
1929 my ( $default, $text ) = @_ ;
1931 my $hbox = Gtk3
:: HBox-
> new ( 0 , 0 );
1933 my $label = Gtk3
:: Label-
> new ( $text );
1934 $label -> set_size_request ( 150 , - 1 );
1935 $label -> set_alignment ( 1 , 0.5 );
1936 $hbox -> pack_start ( $label, 0 , 0 , 10 );
1937 my $e1 = Gtk3
:: Entry-
> new ();
1938 $e1 -> set_width_chars ( 30 );
1939 $hbox -> pack_start ( $e1, 0 , 0 , 0 );
1940 $e1 -> set_text ( $default );
1942 return ( $hbox, $e1 );
1950 my $links = `ip -o l` ;
1951 foreach my $l ( split /\n/ , $links ) {
1952 my ( $index, $name, $flags, $state, $mac ) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ ether\s
+( \S
+) \s
+/;
1953 next if ! $name || $name eq 'lo' ;
1955 my $driver = readlink "/sys/class/net/ $name/device/driver " || 'unknown' ;
1956 $driver =~ s!^.*/!! ;
1958 $ifaces ->{ " $index " } = {
1966 my $addresses = `ip -o a s $name` ;
1967 foreach my $a (split / \n /, $addresses ) {
1968 my ( $family, $ip, $prefix ) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
1970 next if $a =~ /scope\s+link/; # ignore link local
1974 if ( $family eq 'inet') {
1975 next if ! $ip =~ / $IPV4RE/ ;
1976 next if $prefix < 8 || $prefix > 32;
1977 $mask = @$ipv4_reverse_mask [ $prefix ];
1979 next if ! $ip =~ / $IPV6RE/ ;
1982 $default = $index if ! $default ;
1984 $ifaces ->{" $index "}->{" $family "} = {
1992 my $route = ` ip route
`;
1993 my ( $gateway ) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
1995 my $resolvconf = ` cat
/etc/ resolv
. conf
`;
1996 my ( $dnsserver ) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
1997 my ( $domain ) = $resolvconf =~ m/^domain\s+(\S+)$/m;
2000 default => $default,
2002 gateway => $gateway,
2003 dnsserver => $dnsserver,
2008 sub display_message {
2011 my $dialog = Gtk3::MessageDialog->new( $window, 'modal',
2012 'info', 'ok', $msg );
2020 my $dialog = Gtk3::MessageDialog->new( $window, 'modal',
2021 'error', 'ok', $msg );
2026 my $ipconf_first_view = 1;
2028 sub create_ipconf_view {
2033 my $vbox = Gtk3::VBox->new(0, 0);
2034 $inbox ->pack_start( $vbox, 1, 0, 0);
2035 my $hbox = Gtk3::HBox->new(0, 0);
2036 $vbox ->pack_start( $hbox, 0, 0, 10);
2037 my $vbox2 = Gtk3::VBox->new(0, 0);
2040 my $ipaddr_text = $config ->{ipaddress} // "192.168.100.2";
2042 ( $ipbox, $ipconf_entry_addr ) =
2043 create_text_input( $ipaddr_text, 'IP Address:');
2045 my $netmask_text = $config ->{netmask} // "255.255.255.0";
2047 ( $maskbox, $ipconf_entry_mask ) =
2048 create_text_input( $netmask_text, 'Netmask:');
2050 my $device_cb = Gtk3::ComboBoxText->new();
2051 $device_cb ->set_active(0);
2052 $device_cb ->set_visible(1);
2054 my $get_device_desc = sub {
2056 return " $iface ->{name} - $iface ->{mac} ( $iface ->{driver})";
2059 my $device_active_map = {};
2060 my $device_active_reverse_map = {};
2062 my $device_change_handler = sub {
2063 my $current = shift;
2065 my $new = $device_active_map ->{ $current ->get_active()};
2066 return if defined( $ipconf ->{selected}) && $new eq $ipconf ->{selected};
2068 $ipconf ->{selected} = $new ;
2069 my $iface = $ipconf ->{ifaces}->{ $ipconf ->{selected}};
2070 $config ->{mngmt_nic} = $iface ->{name};
2071 $ipconf_entry_addr ->set_text( $iface ->{inet}->{addr} || $iface ->{inet6}->{addr})
2072 if $iface ->{inet}->{addr} || $iface ->{inet6}->{addr};
2073 $ipconf_entry_mask ->set_text( $iface ->{inet}->{mask} || $iface ->{inet6}->{mask})
2074 if $iface ->{inet}->{mask} || $iface ->{inet6}->{mask};
2078 foreach my $index (sort keys %{ $ipconf ->{ifaces}}) {
2079 $device_cb ->append_text(& $get_device_desc ( $ipconf ->{ifaces}->{ $index }));
2080 $device_active_map ->{ $i } = $index ;
2081 $device_active_reverse_map ->{ $ipconf ->{ifaces}->{ $index }->{name}} = $i ;
2082 if ( $ipconf_first_view && $index == $ipconf ->{default}) {
2083 $device_cb ->set_active( $i );
2084 & $device_change_handler ( $device_cb );
2085 $ipconf_first_view = 0;
2087 $device_cb ->signal_connect('changed' => $device_change_handler );
2091 if (my $nic = $config ->{mngmt_nic}) {
2092 $device_cb ->set_active( $device_active_reverse_map ->{ $nic } // 0);
2094 $device_cb ->set_active(0);
2097 my $devicebox = Gtk3::HBox->new(0, 0);
2098 my $label = Gtk3::Label->new("Management Interface:");
2099 $label ->set_size_request(150, -1);
2100 $label ->set_alignment(1, 0.5);
2101 $devicebox ->pack_start( $label, 0, 0, 10);
2102 $devicebox ->pack_start( $device_cb, 0, 0, 0);
2104 $vbox2 ->pack_start( $devicebox, 0, 0, 2);
2106 my $hn = $config ->{fqdn} // " $setup ->{product}." . ( $ipconf ->{domain} // "example.invalid");
2108 my ( $hostbox, $hostentry ) =
2109 create_text_input( $hn, 'Hostname (FQDN):');
2110 $vbox2 ->pack_start( $hostbox, 0, 0, 2);
2112 $vbox2 ->pack_start( $ipbox, 0, 0, 2);
2114 $vbox2 ->pack_start( $maskbox, 0, 0, 2);
2116 $gateway = $config ->{gateway} // $ipconf ->{gateway} || '192.168.100.1';
2119 ( $gwbox, $ipconf_entry_gw ) =
2120 create_text_input( $gateway, 'Gateway:');
2122 $vbox2 ->pack_start( $gwbox, 0, 0, 2);
2124 $dnsserver = $config ->{dnsserver} // $ipconf ->{dnsserver} || $gateway ;
2127 ( $dnsbox, $ipconf_entry_dns ) =
2128 create_text_input( $dnsserver, 'DNS Server:');
2130 $vbox2 ->pack_start( $dnsbox, 0, 0, 0);
2133 set_next(undef, sub {
2137 my $text = $hostentry ->get_text();
2142 $config ->{fqdn} = $text ;
2144 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
2146 # Debian does not support purely numeric hostnames
2147 if ( $text && $text =~ /^[0-9]+(?:\.|$)/) {
2148 display_message("Purely numeric hostnames are not allowed.");
2149 $hostentry ->grab_focus();
2153 if ( $text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
2154 $text =~ m/^([^\.]+)\.(\S+)$/) {
2158 display_message("Hostname does not look like a fully qualified domain name.");
2159 $hostentry ->grab_focus();
2165 $text = $ipconf_entry_addr ->get_text();
2168 if ( $text =~ m!^($IPV4RE)$!) {
2171 } elsif ( $text =~ m!^($IPV6RE)$!) {
2175 display_message("IP address is not valid.");
2176 $ipconf_entry_addr ->grab_focus();
2179 $config ->{ipaddress} = $ipaddress ;
2181 $text = $ipconf_entry_mask ->get_text();
2184 if (( $ipversion == 6) && ( $text =~ m/^(\d+)$/) && ( $1 >= 8) && ( $1 <= 126)) {
2186 } elsif (( $ipversion == 4) && defined( $ipv4_mask_hash ->{ $text })) {
2189 display_message("Netmask is not valid.");
2190 $ipconf_entry_mask ->grab_focus();
2193 $config ->{netmask} = $netmask ;
2195 $text = $ipconf_entry_gw ->get_text();
2198 if (( $ipversion == 4) && ( $text =~ m!^($IPV4RE)$!)) {
2200 } elsif (( $ipversion == 6) && ( $text =~ m!^($IPV6RE)$!)) {
2203 display_message("Gateway is not valid.");
2204 $ipconf_entry_gw ->grab_focus();
2207 $config ->{gateway} = $gateway ;
2209 $text = $ipconf_entry_dns ->get_text();
2212 if (( $ipversion == 4) && ( $text =~ m!^($IPV4RE)$!)) {
2214 } elsif (( $ipversion == 6) && ( $text =~ m!^($IPV6RE)$!)) {
2217 display_message("DNS server is not valid.");
2218 $ipconf_entry_dns ->grab_focus();
2221 $config ->{dnsserver} = $dnsserver ;
2223 #print "TEST $ipaddress $netmask $gateway $dnsserver\n ";
2229 $hostentry ->grab_focus();
2232 sub create_ack_view {
2236 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
2237 my $ack_html = "${proxmox_libdir}/html/ $steps [ $step_number ]->{html}";
2238 my $html_data = file_get_contents( $ack_template );
2240 my %config_values = (
2241 __target_hd__ => join(' | ', @{ $config_options ->{target_hds}}),
2242 __target_fs__ => $config_options ->{filesys},
2243 __country__ => $cmap ->{country}->{ $country }->{name},
2244 __timezone__ => $timezone,
2245 __keymap__ => $keymap,
2246 __mailto__ => $mailto,
2247 __interface__ => $ipconf ->{ifaces}->{ $ipconf ->{selected}}->{name},
2248 __hostname__ => $hostname,
2249 __ip__ => $ipaddress,
2250 __netmask__ => $netmask,
2251 __gateway__ => $gateway,
2252 __dnsserver__ => $dnsserver,
2255 while ( my ( $k, $v ) = each %config_values ) {
2256 $html_data =~ s/$k/$v/g;
2259 write_config( $html_data, $ack_html );
2263 set_next(undef, sub {
2265 create_extract_view();
2269 sub get_device_desc {
2270 my ( $devname, $size, $model ) = @_ ;
2272 if ( $size && ( $size > 0)) {
2273 $size = int( $size/2048 ); # size in MB, from 512B "sectors"
2275 my $text = " $devname (";
2276 if ( $size >= 1024) {
2277 $size = int( $size/1024 ); # size in GB
2278 $text .= "${size}GB";
2280 $text .= "${size}MB";
2283 $text .= ", $model " if $model ;
2292 my ( $cb, $kmap ) = @_ ;
2297 my $kmaphash = $cmap ->{kmaphash};
2298 foreach my $layout (sort keys %$kmaphash ) {
2299 $def = $i if $kmaphash ->{ $layout } eq 'en-us';
2300 $ind = $i if $kmap && $kmaphash ->{ $layout } eq $kmap ;
2304 $cb ->set_active( $ind || $def || 0);
2308 sub update_zonelist {
2309 my ( $box, $cc ) = @_ ;
2311 my $cczones = $cmap ->{cczones};
2312 my $zones = $cmap ->{zones};
2316 $sel = $lastzonecb ->get_active_text();
2317 $box ->remove ( $lastzonecb );
2319 $sel = $timezone ; # used once to select default
2322 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
2323 $cb ->set_size_request(200, -1);
2325 $cb ->signal_connect('changed' => sub {
2326 $timezone = $cb ->get_active_text();
2330 if ( $cc && defined ( $cczones ->{ $cc })) {
2331 @za = keys %{ $cczones ->{ $cc }};
2337 foreach my $zone (sort @za ) {
2338 $ind = $i if $sel && $zone eq $sel ;
2339 $cb ->append_text( $zone );
2343 $cb ->set_active( $ind || 0);
2346 $box ->pack_start( $cb, 0, 0, 0);
2349 sub create_password_view {
2353 my $vbox2 = Gtk3::VBox->new(0, 0);
2354 $inbox ->pack_start( $vbox2, 1, 0, 0);
2355 my $vbox = Gtk3::VBox->new(0, 0);
2356 $vbox2 ->pack_start( $vbox, 0, 0, 10);
2358 my $hbox1 = Gtk3::HBox->new(0, 0);
2359 my $label = Gtk3::Label->new("Password");
2360 $label ->set_size_request(150, -1);
2361 $label ->set_alignment(1, 0.5);
2362 $hbox1 ->pack_start( $label, 0, 0, 10);
2363 my $pwe1 = Gtk3::Entry->new();
2364 $pwe1 ->set_visibility(0);
2365 $pwe1 ->set_text( $password ) if $password ;
2366 $pwe1 ->set_size_request(200, -1);
2367 $hbox1 ->pack_start( $pwe1, 0, 0, 0);
2369 my $hbox2 = Gtk3::HBox->new(0, 0);
2370 $label = Gtk3::Label->new("Confirm");
2371 $label ->set_size_request(150, -1);
2372 $label ->set_alignment(1, 0.5);
2373 $hbox2 ->pack_start( $label, 0, 0, 10);
2374 my $pwe2 = Gtk3::Entry->new();
2375 $pwe2 ->set_visibility(0);
2376 $pwe2 ->set_text( $password ) if $password ;
2377 $pwe2 ->set_size_request(200, -1);
2378 $hbox2 ->pack_start( $pwe2, 0, 0, 0);
2380 my $hbox3 = Gtk3::HBox->new(0, 0);
2381 $label = Gtk3::Label->new("E-Mail");
2382 $label ->set_size_request(150, -1);
2383 $label ->set_alignment(1, 0.5);
2384 $hbox3 ->pack_start( $label, 0, 0, 10);
2385 my $eme = Gtk3::Entry->new();
2386 $eme ->set_size_request(200, -1);
2387 $eme ->set_text( $mailto );
2388 $hbox3 ->pack_start( $eme, 0, 0, 0);
2391 $vbox ->pack_start( $hbox1, 0, 0, 5);
2392 $vbox ->pack_start( $hbox2, 0, 0, 5);
2393 $vbox ->pack_start( $hbox3, 0, 0, 15);
2399 set_next (undef, sub {
2401 my $t1 = $pwe1 ->get_text;
2402 my $t2 = $pwe2 ->get_text;
2404 if (length ( $t1 ) < 5) {
2405 display_message("Password is too short.");
2406 $pwe1 ->grab_focus();
2411 display_message("Password does not match.");
2412 $pwe1 ->grab_focus();
2416 my $t3 = $eme ->get_text;
2417 if ( $t3 !~ m/^\S+\@\S+\.\S+$/) {
2418 display_message("E-Mail does not look like a valid address" .
2419 " (user\ @domain .tld)");
2424 if ( $t3 eq 'mail @example .invalid') {
2425 display_message("Please enter a valid E-Mail address");
2434 create_ipconf_view();
2437 $pwe1 ->grab_focus();
2441 sub create_country_view {
2445 my $countryhash = $cmap ->{countryhash};
2446 my $ctr = $cmap ->{country};
2448 my $vbox2 = Gtk3::VBox->new(0, 0);
2449 $inbox ->pack_start( $vbox2, 1, 0, 0);
2450 my $vbox = Gtk3::VBox->new(0, 0);
2451 $vbox2 ->pack_start( $vbox, 0, 0, 10);
2453 my $w = Gtk3::Entry->new();
2454 $w ->set_size_request(200, -1);
2456 my $c = Gtk3::EntryCompletion->new();
2457 $c ->set_text_column(0);
2458 $c ->set_minimum_key_length(0);
2459 $c ->set_popup_set_width(1);
2460 $c ->set_inline_completion(1);
2462 my $hbox2 = Gtk3::HBox->new(0, 0);
2463 my $label = Gtk3::Label->new("Time zone");
2464 $label ->set_size_request(150, -1);
2465 $label ->set_alignment(1, 0.5);
2466 $hbox2 ->pack_start( $label, 0, 0, 10);
2467 update_zonelist ( $hbox2 );
2469 my $hbox3 = Gtk3::HBox->new(0, 0);
2470 $label = Gtk3::Label->new("Keyboard Layout");
2471 $label ->set_size_request(150, -1);
2472 $label ->set_alignment(1, 0.5);
2473 $hbox3 ->pack_start( $label, 0, 0, 10);
2475 my $kmapcb = Gtk3::ComboBoxText->new();
2476 $kmapcb ->set_size_request (200, -1);
2477 foreach my $layout (sort keys %{ $cmap ->{kmaphash}}) {
2478 $kmapcb ->append_text ( $layout );
2481 update_layout( $kmapcb );
2482 $hbox3 ->pack_start ( $kmapcb, 0, 0, 0);
2484 $kmapcb ->signal_connect ('changed' => sub {
2485 my $sel = $kmapcb ->get_active_text();
2486 if (my $kmap = $cmap ->{kmaphash}->{ $sel }) {
2487 my $xkmap = $cmap ->{kmap}->{ $kmap }->{x11};
2488 my $xvar = $cmap ->{kmap}->{ $kmap }->{x11var};
2489 syscmd ("setxkbmap $xkmap $xvar ") if ! $opt_testmode ;
2494 $w ->signal_connect ('changed' => sub {
2495 my ( $entry, $event ) = @_ ;
2496 my $text = $entry ->get_text;
2498 if (my $cc = $countryhash ->{lc( $text )}) {
2499 update_zonelist( $hbox2, $cc );
2500 my $kmap = $ctr ->{ $cc }->{kmap} || 'en-us';
2501 update_layout( $kmapcb, $kmap );
2505 $w ->signal_connect (key_press_event => sub {
2506 my ( $entry, $event ) = @_ ;
2507 my $text = $entry ->get_text;
2509 my $val = $event ->get_keyval;
2511 if ( $val == Gtk3::Gdk::KEY_Tab) {
2512 my $cc = $countryhash ->{lc( $text )};
2519 $compl = $ctr ->{ $cc }->{name};
2521 foreach my $cc (keys %$ctr ) {
2522 my $ct = $ctr ->{ $cc }->{name};
2523 if ( $ct =~ m/^\Q$text\E.*$/i) {
2532 $entry ->set_text( $compl );
2537 print chr(7); # beep ?
2542 my $buf = $w ->get_buffer();
2543 $buf ->insert_text(-1, '', -1); # popup selection
2551 my $ls = Gtk3::ListStore->new('Glib::String');
2552 foreach my $cc (sort { $ctr ->{ $a }->{name} cmp $ctr ->{ $b }->{name} } keys %$ctr ) {
2553 my $iter = $ls ->append();
2554 $ls ->set ( $iter, 0, $ctr ->{ $cc }->{name});
2556 $c ->set_model ( $ls );
2558 $w ->set_completion ( $c );
2560 my $hbox = Gtk3::HBox->new(0, 0);
2562 $label = Gtk3::Label->new("Country");
2563 $label ->set_alignment(1, 0.5);
2564 $label ->set_size_request(150, -1);
2565 $hbox ->pack_start( $label, 0, 0, 10);
2566 $hbox ->pack_start( $w, 0, 0, 0);
2568 $vbox ->pack_start( $hbox, 0, 0, 5);
2569 $vbox ->pack_start( $hbox2, 0, 0, 5);
2570 $vbox ->pack_start( $hbox3, 0, 0, 5);
2572 if ( $country && $ctr ->{ $country }) {
2573 $w ->set_text ( $ctr ->{ $country }->{name});
2579 set_next (undef, sub {
2581 my $text = $w ->get_text;
2583 if (my $cc = $countryhash ->{lc( $text )}) {
2586 create_password_view();
2589 display_message("Please select a country first.");
2597 my $target_hd_combo ;
2598 my $target_hd_label ;
2600 my $hdoption_first_setup = 1;
2602 my $create_basic_grid = sub {
2603 my $grid = Gtk3::Grid->new();
2604 $grid ->set_visible(1);
2605 $grid ->set_column_spacing(10);
2606 $grid ->set_row_spacing(10);
2607 $grid ->set_hexpand(1);
2609 $grid ->set_margin_start(5);
2610 $grid ->set_margin_end(5);
2611 $grid ->set_margin_top(5);
2612 $grid ->set_margin_bottom(5);
2617 my $create_label_widget_grid = sub {
2618 my ( $labeled_widgets ) = @_ ;
2620 my $grid = & $create_basic_grid ();
2623 for (my $i = 0; $i < @$labeled_widgets ; $i += 2) {
2624 my $widget = @$labeled_widgets [ $i+1 ];
2625 my $label = Gtk3::Label->new( @$labeled_widgets [ $i ]);
2626 $label ->set_visible(1);
2627 $label ->set_alignment (1, 0.5);
2628 $grid ->attach( $label, 0, $row, 1, 1);
2629 $widget ->set_visible(1);
2630 $grid ->attach( $widget, 1, $row, 1, 1);
2637 my $create_raid_disk_grid = sub {
2638 my $disk_labeled_widgets = [];
2639 for (my $i = 0; $i < @$hds ; $i++ ) {
2640 my $disk_selector = Gtk3::ComboBoxText->new();
2641 $disk_selector ->append_text("-- do not use --");
2642 $disk_selector ->set_active(0);
2643 $disk_selector ->set_visible(1);
2644 foreach my $hd ( @$hds ) {
2645 my ( $disk, $devname, $size, $model ) = @$hd ;
2646 $disk_selector ->append_text(get_device_desc ( $devname, $size, $model ));
2647 $disk_selector ->{pve_disk_id} = $i ;
2648 $disk_selector ->signal_connect (changed => sub {
2650 my $diskid = $w ->{pve_disk_id};
2651 my $a = $w ->get_active - 1;
2652 $config_options ->{"disksel${diskid}"} = ( $a >= 0) ? $hds ->[ $a ] : undef;
2656 if ( $hdoption_first_setup ) {
2657 $disk_selector ->set_active ( $i+1 ) if $hds ->[ $i ];
2660 if (my $cur_hd = $config_options ->{"disksel $i "}) {
2661 foreach my $hd ( @$hds ) {
2662 if ( @$hd [1] eq @$cur_hd [1]) {
2663 $disk_selector ->set_active( $hdind+1 );
2671 push @$disk_labeled_widgets, "Harddisk $i ", $disk_selector ;
2674 my $scrolled_window = Gtk3::ScrolledWindow->new();
2675 $scrolled_window ->set_hexpand(1);
2676 $scrolled_window ->set_propagate_natural_height(1) if @$hds > 4;
2677 $scrolled_window ->add(& $create_label_widget_grid ( $disk_labeled_widgets ));
2678 $scrolled_window ->set_policy('never', 'automatic');
2680 return $scrolled_window ;
2681 # & $create_label_widget_grid ( $disk_labeled_widgets )
2684 # shared between different ui parts (e.g., ZFS and "normal" single disk FS)
2685 my $hdsize_size_adj ;
2686 my $hdsize_entry_buffer ;
2688 my $get_hdsize_spinbtn = sub {
2691 $hdsize_entry_buffer //= Gtk3::EntryBuffer->new(undef, 1);
2693 if (defined( $hdsize )) {
2694 $hdsize_size_adj = Gtk3::Adjustment->new( $config_options ->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
2696 die "called get_hdsize_spinbtn with \ $hdsize_size_adj not defined but did not pass hdsize! \n "
2697 if !defined( $hdsize_size_adj );
2700 my $spinbutton_hdsize = Gtk3::SpinButton->new( $hdsize_size_adj, 1, 1);
2701 $spinbutton_hdsize ->set_buffer( $hdsize_entry_buffer );
2702 $spinbutton_hdsize ->set_adjustment( $hdsize_size_adj );
2703 $spinbutton_hdsize ->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2704 return $spinbutton_hdsize ;
2707 my $create_raid_advanced_grid = sub {
2708 my $labeled_widgets = [];
2709 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2710 $spinbutton_ashift ->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2711 $spinbutton_ashift ->signal_connect ("value-changed" => sub {
2713 $config_options ->{ashift} = $w ->get_value_as_int();
2715 $config_options ->{ashift} = 12 if ! defined( $config_options ->{ashift});
2716 $spinbutton_ashift ->set_value( $config_options ->{ashift});
2717 push @$labeled_widgets, "ashift";
2718 push @$labeled_widgets, $spinbutton_ashift ;
2720 my $combo_compress = Gtk3::ComboBoxText->new();
2721 $combo_compress ->set_tooltip_text("zfs compression algorithm for rpool dataset");
2722 # note: gzip / lze not allowed for bootfs vdevs
2723 my $comp_opts = ["on","off","lzjb","lz4"];
2724 foreach my $opt ( @$comp_opts ) {
2725 $combo_compress ->append( $opt, $opt );
2727 $config_options ->{compress} = "on" if !defined( $config_options ->{compress});
2728 $combo_compress ->set_active_id( $config_options ->{compress});
2729 $combo_compress ->signal_connect (changed => sub {
2731 $config_options ->{compress} = $w ->get_active_text();
2733 push @$labeled_widgets, "compress";
2734 push @$labeled_widgets, $combo_compress ;
2736 my $combo_checksum = Gtk3::ComboBoxText->new();
2737 $combo_checksum ->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2738 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2739 foreach my $opt ( @$csum_opts ) {
2740 $combo_checksum ->append( $opt, $opt );
2742 $config_options ->{checksum} = "on" if !( $config_options ->{checksum});
2743 $combo_checksum ->set_active_id( $config_options ->{checksum});
2744 $combo_checksum ->signal_connect (changed => sub {
2746 $config_options ->{checksum} = $w ->get_active_text();
2748 push @$labeled_widgets, "checksum";
2749 push @$labeled_widgets, $combo_checksum ;
2751 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2752 $spinbutton_copies ->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2753 $spinbutton_copies ->signal_connect ("value-changed" => sub {
2755 $config_options ->{copies} = $w ->get_value_as_int();
2757 $config_options ->{copies} = 1 if !defined( $config_options ->{copies});
2758 $spinbutton_copies ->set_value( $config_options ->{copies});
2759 push @$labeled_widgets, "copies", $spinbutton_copies ;
2761 push @$labeled_widgets, "hdsize", $get_hdsize_spinbtn ->();
2762 return & $create_label_widget_grid ( $labeled_widgets );;
2765 sub create_hdoption_view {
2767 my $dialog = Gtk3::Dialog->new();
2769 $dialog ->set_title("Harddisk options");
2771 $dialog ->add_button("_OK", 1);
2773 my $contarea = $dialog ->get_content_area();
2775 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2776 $contarea ->pack_start( $hbox2, 1, 1, 10);
2778 my $grid = Gtk3::Grid->new();
2779 $grid ->set_column_spacing(10);
2780 $grid ->set_row_spacing(10);
2782 $hbox2 ->pack_start( $grid, 1, 0, 10);
2788 my $label0 = Gtk3::Label->new("Filesystem");
2789 $label0 ->set_alignment (1, 0.5);
2790 $grid ->attach( $label0, 0, $row, 1, 1);
2792 my $fstypecb = Gtk3::ComboBoxText->new();
2794 my $fstype = ['ext3', 'ext4', 'xfs',
2795 'zfs (RAID0)', 'zfs (RAID1)',
2796 'zfs (RAID10)', 'zfs (RAIDZ-1)',
2797 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2799 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
2800 if $setup ->{enable_btrfs};
2803 foreach my $tmp ( @$fstype ) {
2804 $fstypecb ->append_text( $tmp );
2805 $fstypecb ->set_active ( $tcount )
2806 if $config_options ->{filesys} eq $tmp ;
2810 $grid ->attach( $fstypecb, 1, $row, 1, 1);
2816 my $sep = Gtk3::HSeparator->new();
2817 $sep ->set_visible(1);
2818 $grid ->attach( $sep, 0, $row, 2, 1);
2821 my $hw_raid_note = Gtk3::Label->new("Note: ZFS is not compatible with disks backed by a hardware RAID controller. For details see the reference documentation.");
2822 $hw_raid_note ->set_line_wrap(1);
2823 $hw_raid_note ->set_max_width_chars(30);
2824 $hw_raid_note ->set_visible(0);
2825 $grid ->attach( $hw_raid_note, 0, $row++, 2, 1);
2827 my $hdsize_labeled_widgets = [];
2831 if ( -b $target_hd ) {
2832 $hdsize = int(hd_size ( $target_hd ) / (1024*1024.0)); # size in GB
2833 } elsif ( $target_hd ) {
2834 $hdsize = int((-s $target_hd ) / (1024*1024*1024.0));
2837 my $spinbutton_hdsize = $get_hdsize_spinbtn ->( $hdsize );
2838 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize ;
2840 my $entry_swapsize = Gtk3::Entry->new();
2841 $entry_swapsize ->set_tooltip_text("maximum SWAP size (GB)");
2842 $entry_swapsize ->signal_connect (key_press_event => \&check_float);
2843 $entry_swapsize ->set_text( $config_options ->{swapsize}) if defined( $config_options ->{swapsize});
2844 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize ;
2846 my $entry_maxroot = Gtk3::Entry->new();
2847 if ( $setup ->{product} eq 'pve') {
2848 $entry_maxroot ->set_tooltip_text("maximum size (GB) for LVM root volume");
2849 $entry_maxroot ->signal_connect (key_press_event => \&check_float);
2850 $entry_maxroot ->set_text( $config_options ->{maxroot}) if $config_options ->{maxroot};
2851 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot ;
2854 my $entry_minfree = Gtk3::Entry->new();
2855 $entry_minfree ->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
2856 $entry_minfree ->signal_connect (key_press_event => \&check_float);
2857 $entry_minfree ->set_text( $config_options ->{minfree}) if defined( $config_options ->{minfree});
2858 push @$hdsize_labeled_widgets, "minfree", $entry_minfree ;
2861 if ( $setup ->{product} eq 'pve') {
2862 $entry_maxvz = Gtk3::Entry->new();
2863 $entry_maxvz ->set_tooltip_text("maximum size (GB) for LVM data volume");
2864 $entry_maxvz ->signal_connect (key_press_event => \&check_float);
2865 $entry_maxvz ->set_text( $config_options ->{maxvz}) if defined( $config_options ->{maxvz});
2866 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz ;
2869 my $options_stack = Gtk3::Stack->new();
2870 $options_stack ->set_visible(1);
2871 $options_stack ->set_hexpand(1);
2872 $options_stack ->set_vexpand(1);
2873 $options_stack ->add_titled(& $create_raid_disk_grid (), "raiddisk", "Disk Setup");
2874 $options_stack ->add_titled(& $create_label_widget_grid ( $hdsize_labeled_widgets ), "hdsize", "Size Options");
2875 $options_stack ->add_titled(& $create_raid_advanced_grid ("zfs"), "raidzfsadvanced", "Advanced Options");
2876 $options_stack ->set_visible_child_name("raiddisk");
2877 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2878 $options_stack_switcher ->set_halign('center');
2879 $options_stack_switcher ->set_stack( $options_stack );
2880 $grid ->attach( $options_stack_switcher, 0, $row, 2, 1);
2882 $grid ->attach( $options_stack, 0, $row, 2, 1);
2885 $hdoption_first_setup = 0;
2887 my $switch_view = sub {
2888 my $raid = $config_options ->{filesys} =~ m/zfs|btrfs/;
2889 my $enable_zfs_opts = $config_options ->{filesys} =~ m/zfs/;
2891 $target_hd_combo ->set_visible(! $raid );
2892 $options_stack ->get_child_by_name("hdsize")->set_visible(! $raid );
2893 $options_stack ->get_child_by_name("raiddisk")->set_visible( $raid );
2894 $hw_raid_note ->set_visible( $raid );
2895 $options_stack_switcher ->set_visible( $enable_zfs_opts );
2896 $options_stack ->get_child_by_name("raidzfsadvanced")->set_visible( $enable_zfs_opts );
2898 $target_hd_label ->set_text("Target: $config_options ->{filesys} ");
2899 $options_stack ->set_visible_child_name("raiddisk");
2901 $target_hd_label ->set_text("Target Harddisk: ");
2903 my (undef, $pref_width ) = $dialog ->get_preferred_width();
2904 my (undef, $pref_height ) = $dialog ->get_preferred_height();
2905 $pref_height = 750 if $pref_height > 750;
2906 $dialog ->resize( $pref_width, $pref_height );
2911 $fstypecb ->signal_connect (changed => sub {
2912 $config_options ->{filesys} = $fstypecb ->get_active_text();
2916 my $sep2 = Gtk3::HSeparator->new();
2917 $sep2 ->set_visible(1);
2918 $contarea ->pack_end( $sep2, 1, 1, 10);
2924 my $get_float = sub {
2927 my $text = $entry ->get_text();
2928 return undef if !defined( $text );
2933 return undef if $text !~ m/^\d+(\.\d+)?$/;
2940 if (( $tmp = & $get_float ( $spinbutton_hdsize )) && ( $tmp != $hdsize )) {
2941 $config_options ->{hdsize} = $tmp ;
2943 delete $config_options ->{hdsize};
2946 if (defined( $tmp = & $get_float ( $entry_swapsize ))) {
2947 $config_options ->{swapsize} = $tmp ;
2949 delete $config_options ->{swapsize};
2952 if (defined( $tmp = & $get_float ( $entry_maxroot ))) {
2953 $config_options ->{maxroot} = $tmp ;
2955 delete $config_options ->{maxroot};
2958 if (defined( $tmp = & $get_float ( $entry_minfree ))) {
2959 $config_options ->{minfree} = $tmp ;
2961 delete $config_options ->{minfree};
2964 if ( $entry_maxvz && defined( $tmp = & $get_float ( $entry_maxvz ))) {
2965 $config_options ->{maxvz} = $tmp ;
2967 delete $config_options ->{maxvz};
2973 my $get_raid_devlist = sub {
2975 my $dev_name_hash = {};
2978 for (my $i = 0; $i < @$hds ; $i++ ) {
2979 if (my $hd = $config_options ->{"disksel $i "}) {
2980 my ( $disk, $devname, $size, $model ) = @$hd ;
2981 die "device ' $devname ' is used more than once \n "
2982 if $dev_name_hash ->{ $devname };
2983 $dev_name_hash ->{ $devname } = $hd ;
2984 push @$devlist, $hd ;
2991 sub zfs_mirror_size_check {
2992 my ( $expected, $actual ) = @_ ;
2994 die "mirrored disks must have same size \n "
2995 if abs( $expected - $actual ) > $expected / 10;
2998 sub get_zfs_raid_setup {
3000 my $filesys = $config_options ->{filesys};
3002 my $devlist = & $get_raid_devlist ();
3004 my $diskcount = scalar( @$devlist );
3005 die " $filesys needs at least one device \n " if $diskcount < 1;
3007 my $bootdevlist = [];
3010 if ( $filesys eq 'zfs (RAID0)') {
3011 push @$bootdevlist, @$devlist [0];
3012 foreach my $hd ( @$devlist ) {
3015 } elsif ( $filesys eq 'zfs (RAID1)') {
3016 die "zfs (RAID1) needs at least 2 device \n " if $diskcount < 2;
3018 my $hd = @$devlist [0];
3019 my $expected_size = @$hd [2]; # all disks need approximately same size
3020 foreach $hd ( @$devlist ) {
3021 zfs_mirror_size_check( $expected_size, @$hd [2]);
3023 push @$bootdevlist, $hd ;
3025 } elsif ( $filesys eq 'zfs (RAID10)') {
3026 die "zfs (RAID10) needs at least 4 device \n " if $diskcount < 4;
3027 die "zfs (RAID10) needs an even number of devices \n " if $diskcount & 1;
3029 push @$bootdevlist, @$devlist [0], @$devlist [1];
3031 for (my $i = 0; $i < $diskcount ; $i+ =2) {
3032 my $hd1 = @$devlist [ $i ];
3033 my $hd2 = @$devlist [ $i+1 ];
3034 zfs_mirror_size_check( @$hd1 [2], @$hd2 [2]); # pairs need approximately same size
3035 $cmd .= ' mirror ' . @$hd1 [1] . ' ' . @$hd2 [1];
3038 } elsif ( $filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3040 my $mindisks = 2 + $level ;
3041 die "zfs (RAIDZ- $level ) needs at least $mindisks devices \n " if scalar( @$devlist ) < $mindisks ;
3042 my $hd = @$devlist [0];
3043 my $expected_size = @$hd [2]; # all disks need approximately same size
3044 $cmd .= " raidz $level ";
3045 foreach $hd ( @$devlist ) {
3046 zfs_mirror_size_check( $expected_size, @$hd [2]);
3048 push @$bootdevlist, $hd ;
3051 die "unknown zfs mode ' $filesys ' \n ";
3054 return ( $devlist, $bootdevlist, $cmd );
3057 sub get_btrfs_raid_setup {
3059 my $filesys = $config_options ->{filesys};
3061 my $devlist = & $get_raid_devlist ();
3063 my $diskcount = scalar( @$devlist );
3064 die " $filesys needs at least one device \n " if $diskcount < 1;
3068 if ( $diskcount == 1) {
3071 if ( $filesys eq 'btrfs (RAID0)') {
3073 } elsif ( $filesys eq 'btrfs (RAID1)') {
3074 die "btrfs (RAID1) needs at least 2 device \n " if $diskcount < 2;
3076 } elsif ( $filesys eq 'btrfs (RAID10)') {
3077 die "btrfs (RAID10) needs at least 4 device \n " if $diskcount < 4;
3080 die "unknown btrfs mode ' $filesys ' \n ";
3084 return ( $devlist, $mode );
3087 my $last_hd_selected = 0;
3088 sub create_hdsel_view {
3090 $prev_btn ->set_sensitive(1); # enable previous button at this point
3094 my $vbox = Gtk3::VBox->new(0, 0);
3095 $inbox ->pack_start( $vbox, 1, 0, 0);
3096 my $hbox = Gtk3::HBox->new(0, 0);
3097 $vbox ->pack_start( $hbox, 0, 0, 10);
3099 my ( $disk, $devname, $size, $model ) = @{ @$hds [0]};
3100 $target_hd = $devname if !defined( $target_hd );
3102 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3103 $hbox ->pack_start( $target_hd_label, 0, 0, 0);
3105 $target_hd_combo = Gtk3::ComboBoxText->new();
3107 foreach my $hd ( @$hds ) {
3108 ( $disk, $devname, $size, $model ) = @$hd ;
3109 $target_hd_combo ->append_text (get_device_desc( $devname, $size, $model ));
3112 my $raid = $config_options ->{filesys} =~ m/zfs|btrfs/;
3114 $target_hd_label ->set_text("Target: $config_options ->{filesys} ");
3115 $target_hd_combo ->set_visible(0);
3116 $target_hd_combo ->set_no_show_all(1);
3118 $target_hd_combo ->set_active( $last_hd_selected );
3119 $target_hd_combo ->signal_connect(changed => sub {
3120 $a = shift->get_active;
3121 my ( $disk, $devname ) = @{ @$hds [ $a ]};
3122 $last_hd_selected = $a ;
3123 $target_hd = $devname ;
3126 $hbox ->pack_start( $target_hd_combo, 0, 0, 10);
3128 my $options = Gtk3::Button->new('_Options');
3129 $options ->signal_connect (clicked => \&create_hdoption_view);
3130 $hbox ->pack_start ( $options, 0, 0, 0);
3137 set_next(undef, sub {
3139 if ( $config_options ->{filesys} =~ m/zfs/) {
3140 my ( $devlist ) = eval { get_zfs_raid_setup() };
3142 display_message("Warning: $err\nPlease fix ZFS setup first.");
3145 $config_options ->{target_hds} = [ map { $_ ->[1] } @$devlist ];
3146 } elsif ( $config_options ->{filesys} =~ m/btrfs/) {
3147 my ( $devlist ) = eval { get_btrfs_raid_setup() };
3149 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3152 $config_options ->{target_hds} = [ map { $_ ->[1] } @$devlist ];
3154 $config_options ->{target_hds} = [ $target_hd ];
3158 create_country_view();
3162 sub create_extract_view {
3168 $next ->set_sensitive(0);
3169 $prev_btn ->set_sensitive(0);
3172 my $vbox = Gtk3::VBox->new(0, 0);
3173 $inbox ->pack_start ( $vbox, 1, 0, 0);
3174 my $hbox = Gtk3::HBox->new(0, 0);
3175 $vbox ->pack_start ( $hbox, 0, 0, 10);
3177 my $vbox2 = Gtk3::VBox->new(0, 0);
3178 $hbox ->pack_start ( $vbox2, 0, 0, 0);
3180 $progress_status = Gtk3::Label->new('');
3181 $vbox2 ->pack_start ( $progress_status, 1, 1, 0);
3183 $progress = Gtk3::ProgressBar->new;
3184 $progress ->set_show_text(1);
3185 $progress ->set_size_request (600, -1);
3187 $vbox2 ->pack_start( $progress, 0, 0, 0);
3191 my $tdir = $opt_testmode ? "target" : "/target";
3193 my $base = "${proxmox_cddir}/ $setup ->{product}-base.squashfs";
3195 eval { extract_data( $base, $tdir ); };
3198 $next ->set_sensitive(1);
3200 set_next("_Reboot", sub { exit (0); } );
3203 display_html("fail.htm");
3204 display_error( $err );
3207 display_html("success.htm");
3211 sub create_intro_view {
3213 $prev_btn ->set_sensitive(0);
3217 if ( $setup ->{product} eq 'pve') {
3219 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3220 if ( $cpuinfo && !( $cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
3221 display_error("No support for KVM virtualisation detected. \n\n " .
3222 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3230 set_next("I a_gree", \&create_hdsel_view);
3233 $ipconf = get_ip_config();
3235 $country = detect_country() if $ipconf ->{default} || $opt_testmode ;
3237 # read country, kmap and timezone infos
3238 $cmap = read_cmap();
3240 if (!defined( $cmap ->{country}->{ $country })) {
3241 print $logfd "ignoring detected country ' $country ', invalid or unknown \n ";
3245 create_main_window ();
3247 my $initial_error = 0;
3249 if (!defined ( $hds ) || (scalar ( @$hds ) <= 0)) {
3250 print "no hardisks found \n ";
3252 display_html("nohds.htm");
3253 set_next("Reboot", sub { exit(0); } );
3255 foreach my $hd ( @$hds ) {
3256 my ( $disk, $devname ) = @$hd ;
3257 next if $devname =~ m|^/dev/md\d+$|;
3258 print "found Disk $disk N: $devname\n ";
3262 if (! $initial_error && (scalar keys %{ $ipconf ->{ifaces} } == 0)) {
3263 print "no network interfaces found \n ";
3265 display_html("nonics.htm");
3266 set_next("Reboot", sub { exit(0); } );
3269 create_intro_view () if ! $initial_error ;