]>
git.proxmox.com Git - pve-installer.git/blob - proxinstall
3891338cd8556f24ce25f3b8f9f5faf75aac9696
3 $ENV { DEBIAN_FRONTEND
} = 'noninteractive' ;
18 use String
:: ShellQuote
;
23 use ProxmoxInstallerSetup
;
25 my $setup = ProxmoxInstallerSetup
:: setup
();
29 if (! $ENV { G_SLICE
} || $ENV { G_SLICE
} ne "always-malloc" ) {
30 die "do not use slice allocator (run with 'G_SLICE=always-malloc ./proxinstall ...') \n " ;
33 if (! GetOptions
( 'testmode=s' => \
$opt_testmode )) {
38 my $zfstestpool = "test_rpool" ;
39 my $zfspoolname = $opt_testmode ?
$zfstestpool : 'rpool' ;
40 my $zfsrootvolname = " $setup ->{product}-1" ;
42 my $storage_cfg_zfs = <<__EOD__;
45 content iso,vztmpl,backup
48 pool $zfspoolname/data
50 content images,rootdir
53 my $storage_cfg_btrfs = <<__EOD__;
56 content iso,vztmpl,backup
60 path /var/lib/pve/local-btrfs
61 content iso,vztmpl,backup,images,rootdir
64 my $storage_cfg_lvmthin = <<__EOD__;
67 content iso,vztmpl,backup
72 content rootdir,images
75 my $storage_cfg_local = <<__EOD__;
78 content iso,vztmpl,backup,rootdir,images
81 sub file_read_firstline
{
84 my $fh = IO
:: File-
> new ( $filename, "r" );
92 my $logfd = IO
:: File-
> new ( ">/tmp/install.log" );
94 my $proxmox_libdir = $opt_testmode ?
95 Cwd
:: cwd
() . "/testdir/var/lib/pve-installer" : "/var/lib/pve-installer" ;
96 my $proxmox_cddir = $opt_testmode ?
"../pve-cd-builder/tmp/data-gz/" : "/cdrom" ;
97 my $proxmox_pkgdir = "${proxmox_cddir}/proxmox/packages/" ;
99 my $grub_plattform = "pc" ; # pc, efi-amd64 or efi-ia32
101 $grub_plattform = "efi-amd64" if - d
"/sys/firmware/efi" ;
103 my $IPV4OCTET = "(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9])?[0-9])" ;
104 my $IPV4RE = "(?:(?: $IPV4OCTET\\ .){3} $IPV4OCTET )" ;
105 my $IPV6H16 = "(?:[0-9a-fA-F]{1,4})" ;
106 my $IPV6LS32 = "(?:(?: $IPV4RE | $IPV6H16 : $IPV6H16 ))" ;
109 "(?:(?:" . "(?: $IPV6H16 :){6}) $IPV6LS32 )|" .
110 "(?:(?:" . "::(?: $IPV6H16 :){5}) $IPV6LS32 )|" .
111 "(?:(?:(?:" . " $IPV6H16 )?::(?: $IPV6H16 :){4}) $IPV6LS32 )|" .
112 "(?:(?:(?:(?: $IPV6H16 :){0,1} $IPV6H16 )?::(?: $IPV6H16 :){3}) $IPV6LS32 )|" .
113 "(?:(?:(?:(?: $IPV6H16 :){0,2} $IPV6H16 )?::(?: $IPV6H16 :){2}) $IPV6LS32 )|" .
114 "(?:(?:(?:(?: $IPV6H16 :){0,3} $IPV6H16 )?::(?: $IPV6H16 :){1}) $IPV6LS32 )|" .
115 "(?:(?:(?:(?: $IPV6H16 :){0,4} $IPV6H16 )?::" . ") $IPV6LS32 )|" .
116 "(?:(?:(?:(?: $IPV6H16 :){0,5} $IPV6H16 )?::" . ") $IPV6H16 )|" .
117 "(?:(?:(?:(?: $IPV6H16 :){0,6} $IPV6H16 )?::" . ")))" ;
119 my $IPRE = "(?: $IPV4RE | $IPV6RE )" ;
122 my $ipv4_mask_hash = {
139 '255.255.128.0' => 17 ,
140 '255.255.192.0' => 18 ,
141 '255.255.224.0' => 19 ,
142 '255.255.240.0' => 20 ,
143 '255.255.248.0' => 21 ,
144 '255.255.252.0' => 22 ,
145 '255.255.254.0' => 23 ,
146 '255.255.255.0' => 24 ,
147 '255.255.255.128' => 25 ,
148 '255.255.255.192' => 26 ,
149 '255.255.255.224' => 27 ,
150 '255.255.255.240' => 28 ,
151 '255.255.255.248' => 29 ,
152 '255.255.255.252' => 30 ,
153 '255.255.255.254' => 31 ,
154 '255.255.255.255' => 32
157 my $ipv4_reverse_mask = [
193 my $step_number = 0 ; # Init number for global function list
198 html
=> 'license.htm' ,
199 next_button
=> 'I a_gree' ,
200 function
=> \
& create_intro_view
,
205 function
=> \
& create_hdsel_view
,
209 html
=> 'country.htm' ,
210 function
=> \
& create_country_view
,
214 html
=> 'passwd.htm' ,
215 function
=> \
& create_password_view
,
219 html
=> 'ipconf.htm' ,
220 function
=> \
& create_ipconf_view
,
225 next_button
=> '_Install' ,
226 function
=> \
& create_ack_view
,
230 next_button
=> '_Reboot' ,
231 function
=> \
& create_extract_view
,
235 # GUI global variables
236 my ( $window, $cmdbox, $inbox, $htmlview );
238 my ( $next, $next_fctn, $target_hd );
239 my ( $progress, $progress_status );
241 my ( $ipversion, $ipaddress, $ipconf_entry_addr );
242 my ( $netmask, $ipconf_entry_mask );
243 my ( $gateway, $ipconf_entry_gw );
244 my ( $dnsserver, $ipconf_entry_dns );
245 my $hostname = 'proxmox' ;
246 my $domain = 'domain.tld' ;
247 my $cmdline = file_read_firstline
( "/proc/cmdline" );
250 my $timezone = 'Europe/Vienna' ;
251 my $keymap = 'en-us' ;
253 my $mailto = 'mail @example .invalid' ;
257 # TODO: add all the user-provided options for previous button
259 timezone
=> $timezone,
262 password
=> $password,
266 hostname
=> $hostname,
273 # parse command line args
275 my $config_options = {};
277 if ( $cmdline =~ m/\s(ext3|ext4|xfs)(\s.*)?$/ ) {
278 $config_options ->{ filesys
} = $1 ;
280 $config_options ->{ filesys
} = 'ext4' ;
283 if ( $cmdline =~ m/hdsize=(\d+(\.\d+)?)[\s\n]/i ) {
284 $config_options ->{ hdsize
} = $1 ;
287 if ( $cmdline =~ m/swapsize=(\d+(\.\d+)?)[\s\n]/i ) {
288 $config_options ->{ swapsize
} = $1 ;
291 if ( $cmdline =~ m/maxroot=(\d+(\.\d+)?)[\s\n]/i ) {
292 $config_options ->{ maxroot
} = $1 ;
295 if ( $cmdline =~ m/minfree=(\d+(\.\d+)?)[\s\n]/i ) {
296 $config_options ->{ minfree
} = $1 ;
299 if ( $setup ->{ product
} eq 'pve' ) {
300 if ( $cmdline =~ m/maxvz=(\d+(\.\d+)?)[\s\n]/i ) {
301 $config_options ->{ maxvz
} = $1 ;
305 my $postfix_main_cf = <<_EOD;
306 # See /usr/share/postfix/main.cf.dist for a commented, more complete version
310 smtpd_banner = \ $myhostname ESMTP \ $mail_name (Debian/GNU)
313 # appending .domain is the MUA's job.
314 append_dot_mydomain = no
316 # Uncomment the next line to generate "delayed mail" warnings
317 #delay_warning_time = 4h
319 alias_maps = hash:/etc/aliases
320 alias_database = hash:/etc/aliases
321 mydestination = \ $myhostname, localhost.\ $mydomain, localhost
323 mynetworks = 127.0.0.0/8
324 inet_interfaces = loopback-only
325 recipient_delimiter = +
332 return String
:: ShellQuote
:: shell_quote
( $str );
338 die "no arguments" if ! $cmd ;
340 return $cmd if ! ref ( $cmd );
343 foreach my $arg ( @$cmd ) { push @qa, shellquote
( $arg ); }
345 return join ( ' ' , @qa );
351 return run_command
( $cmd, undef , undef , 1 );
355 my ( $cmd, $func, $input, $noout ) = @_ ;
361 # see 'man bash' for option pipefail
362 $cmd = [ '/bin/bash' , '-c' , "set -o pipefail && $cmd " ];
367 $cmdstr = cmd2string
( $cmd );
371 if ( $input && ( $cmdstr !~ m/chpasswd/ )) {
372 $cmdtxt = "# $cmdstr <<EOD \n $input " ;
374 $cmdtxt .= " \n EOD \n " ;
376 $cmdtxt = "# $cmdstr\n " ;
384 print $logfd $cmdtxt ;
386 my $reader = IO
:: File-
> new ();
387 my $writer = IO
:: File-
> new ();
388 my $error = IO
:: File-
> new ();
394 $pid = open3
( $writer, $reader, $error, @$cmd ) || die $! ;
400 if ( $orig_pid != $$ ) {
407 print $writer $input if defined $input ;
410 my $select = new IO
:: Select
;
411 $select -> add ( $reader );
412 $select -> add ( $error );
414 my ( $ostream, $logout ) = ( '' , '' , '' );
416 while ( $select -> count ) {
417 my @handles = $select -> can_read ( 0.2 );
419 Gtk3
:: main_iteration
() while Gtk3
:: events_pending
();
421 next if ! scalar ( @handles ); # timeout
423 foreach my $h ( @handles ) {
425 my $count = sysread ( $h, $buf, 4096 );
426 if (! defined ( $count )) {
430 die "command ' $cmd ' failed: $err " ;
432 $select -> remove ( $h ) if ! $count ;
434 $ostream .= $buf if !( $noout || $func );
436 while ( $logout =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s ) {
438 & $func ( $line ) if $func ;
441 } elsif ( $h eq $error ) {
442 $ostream .= $buf if !( $noout || $func );
450 & $func ( $logout ) if $func ;
452 my $rv = waitpid ( $pid, 0 );
454 return $? if $noout ; # behave like standard system();
457 die "command ' $cmdstr ' failed to execute \n " ;
458 } elsif ( my $sig = ( $? & 127 )) {
459 die "command ' $cmdstr ' failed - got signal $sig\n " ;
460 } elsif ( my $exitcode = ( $? >> 8 )) {
461 die "command ' $cmdstr ' failed with exit code $exitcode " ;
469 print "trying to detect country... \n " ;
470 my $cpid = open2
( \
* TMP
, undef , "traceroute -N 1 -q 1 -n 8.8.8.8" );
471 return undef if ! $cpid ;
475 my $previous_alarm = alarm ( 10 );
477 local $SIG { ALRM
} = sub { die "timed out! \n " };
479 while ( defined ( $line = < TMP
>)) {
480 print $logfd "DC TRACEROUTE: $line " ;
481 if ( $line =~ m/\s*\d+\s+(\d+\.\d+\.\d+\.\d+)\s/ ) {
482 my $geoip = `geoiplookup $1` ;
483 print $logfd "DC GEOIP: $geoip ";
484 if ( $geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) {
486 print $logfd "DC FOUND: $country\n ";
495 alarm ( $previous_alarm );
500 print "unable to detect country - $err\n ";
502 print "detected country: " . uc( $country ) . " \n ";
504 print "unable to detect country \n ";
512 open (MEMINFO, "/proc/meminfo");
514 my $res = 512; # default to 512 if something goes wrong
515 while (my $line = <MEMINFO>) {
516 if ( $line =~ m/^MemTotal:\s+(\d+)\s*kB/i) {
517 $res = int ( $1 / 1024);
526 my $total_memory = get_memtotal();
529 my ( $src, $dest ) = @_ ;
531 my ( $dev1,$ino1 ) = stat ( $src );
532 my ( $dev2,$ino2 ) = stat ( $dest );
534 return 0 if !( $dev1 && $dev2 && $ino1 && $ino2 );
536 return $ino1 == $ino2 && $dev1 == $dev2 ;
539 sub find_stable_path {
540 my ( $stabledir, $bdev ) = @_ ;
542 foreach my $path (< $stabledir/* >) {
543 if (link_points_to( $path, $bdev )) {
544 return wantarray ? ( $path, basename( $path )) : $path ;
549 sub find_dev_by_uuid {
552 my ( $full_path, $name ) = find_stable_path("/dev/disk/by-uuid", $bdev );
562 my @disks = split /,/, $opt_testmode ;
564 for my $disk ( @disks ) {
565 push @$res, [-1, $disk, int((-s $disk )/512), "TESTDISK"];
572 foreach my $bd (</sys/block/*>) {
573 next if $bd =~ m|^/sys/block/ram\d+$|;
574 next if $bd =~ m|^/sys/block/loop\d+$|;
575 next if $bd =~ m|^/sys/block/md\d+$|;
576 next if $bd =~ m|^/sys/block/dm-.*$|;
577 next if $bd =~ m|^/sys/block/fd\d+$|;
578 next if $bd =~ m|^/sys/block/sr\d+$|;
580 my $dev = file_read_firstline(" $bd/dev ");
585 my $info = ` udevadm info
-- path
$bd -- query all
`;
588 next if $info !~ m/^E: DEVTYPE=disk$/m;
590 next if $info =~ m/^E: ID_CDROM/m;
592 my ( $name ) = $info =~ m/^N: (\S+)$/m;
595 my $real_name = "/dev/ $name ";
597 my $size = file_read_firstline(" $bd/size ");
599 $size = undef if !( $size && $size =~ m/^\d+$/);
601 my $model = file_read_firstline(" $bd/device/model ") || '';
604 if (length ( $model ) > 30) {
605 $model = substr ( $model, 0, 30);
607 push @$res, [ $count++, $real_name, $size, $model ] if $size ;
609 print STDERR "ERROR: unable to map device $dev ( $bd ) \n ";
617 my $countryfn = "${proxmox_libdir}/country.dat";
618 open (TMP, "< $countryfn ") || die "unable to open ' $countryfn ' - $!\n ";
621 my $countryhash = {};
624 while (defined ( $line = <TMP>)) {
625 if ( $line =~ m|^map:([^\s:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]*):$|) {
633 $kmaphash ->{ $2 } = $1 ;
634 } elsif ( $line =~ m|^([a-z]{2}):([^:]+):([^:]*):([^:]*):$|) {
640 $countryhash ->{lc( $2 )} = $1 ;
642 warn "unable to parse 'country.dat' line: $line ";
649 my $zonefn = "/usr/share/zoneinfo/zone.tab";
650 open (TMP, "< $zonefn ") || die "unable to open ' $zonefn ' - $!\n ";
651 while (defined ( $line = <TMP>)) {
652 next if $line =~ m/^\#/;
653 next if $line =~ m/^\s*$/;
654 if ( $line =~ m|^([A-Z][A-Z])\s+\S+\s+(([^/]+)/\S+)\s|) {
656 $cczones ->{ $cc }->{ $2 } = 1;
657 $country ->{ $cc }->{zone} = $2 if !defined ( $country ->{ $cc }->{zone});
668 countryhash => $countryhash,
670 kmaphash => $kmaphash,
674 # search for Harddisks
680 foreach my $hd ( @$hds ) {
681 my ( $disk, $devname, $size, $model ) = @$hd ;
682 # size is always in 512B "sectors"! convert to KB
683 return int( $size/2 ) if $devname eq $dev ;
686 die "no such device ' $dev ' \n ";
689 sub get_partition_dev {
690 my ( $dev, $partnum ) = @_ ;
692 if ( $dev =~ m|^/dev/sd([a-h]?[a-z]\|i[a-v])$|) {
693 return "${dev} $partnum ";
694 } elsif ( $dev =~ m|^/dev/xvd[a-z]$|) {
695 # Citrix Hypervisor blockdev
696 return "${dev} $partnum ";
697 } elsif ( $dev =~ m|^/dev/[hxev]d[a-z]$|) {
698 return "${dev} $partnum ";
699 } elsif ( $dev =~ m|^/dev/[^/]+/c\d+d\d+$|) {
700 return "${dev}p $partnum ";
701 } elsif ( $dev =~ m|^/dev/[^/]+/d\d+$|) {
702 return "${dev}p $partnum ";
703 } elsif ( $dev =~ m|^/dev/[^/]+/hd[a-z]$|) {
704 return "${dev} $partnum ";
705 } elsif ( $dev =~ m|^/dev/nvme\d+n\d+$|) {
706 return "${dev}p $partnum ";
708 die "unable to get device for partition $partnum on device $dev\n ";
713 sub file_get_contents {
714 my ( $filename, $max ) = @_ ;
716 my $fh = IO::File->new( $filename, "r") ||
717 die "can't open ' $filename ' - $!\n ";
719 local $/ ; # slurp mode
729 my ( $text, $filename ) = @_ ;
731 my $fd = IO::File->new("> $filename ") ||
732 die "unable to open file ' $filename ' - $!\n ";
737 sub update_progress {
738 my ( $frac, $start, $end, $text ) = @_ ;
740 my $part = $end - $start ;
741 my $res = $start + $frac*$part ;
743 $progress ->set_fraction ( $res );
744 $progress ->set_text (sprintf (" %d%% ", int ( $res*100 )));
745 $progress_status ->set_text ( $text ) if defined ( $text );
747 display_info() if $res < 0.9;
749 Gtk3::main_iteration() while Gtk3::events_pending();
754 mkfs => 'mkfs.ext3 -F',
756 mkfs_data_opt => '-m 0',
757 root_mountopt => 'errors=remount-ro',
760 mkfs => 'mkfs.ext4 -F',
762 mkfs_data_opt => '-m 0',
763 root_mountopt => 'errors=remount-ro',
766 mkfs => 'mkfs.xfs -f',
773 sub create_filesystem {
774 my ( $dev, $name, $type, $start, $end, $fs, $fe ) = @_ ;
776 my $range = $end - $start ;
777 my $rs = $start + $range*$fs ;
778 my $re = $start + $range*$fe ;
781 my $fsdata = $fssetup ->{ $type } || die "internal error - unknown file system ' $type '";
782 my $opts = $name eq 'root' ? $fsdata ->{mkfs_root_opt} : $fsdata ->{mkfs_data_opt};
784 update_progress(0, $rs, $re, "creating $name filesystem");
786 run_command(" $fsdata ->{mkfs} $opts $dev ", sub {
789 if ( $line =~ m/Writing inode tables:\s+(\d+)\/(\d+)/) {
791 } elsif ( $max && $line =~ m/(\d+)\/ $max/ ) {
792 update_progress(( $1/$max )*0.9, $rs, $re );
793 } elsif ( $line =~ m/Creating journal.*done/) {
794 update_progress(0.95, $rs, $re );
795 } elsif ( $line =~ m/Writing superblocks and filesystem.*done/) {
796 update_progress(1, $rs, $re );
802 my ( $targetdir, $dcdata ) = @_ ;
804 my $cfgfile = "/tmp/debconf.txt";
805 write_config( $dcdata, " $targetdir/$cfgfile ");
806 syscmd("chroot $targetdir debconf-set-selections $cfgfile ");
807 unlink " $targetdir/$cfgfile ";
811 my ( $targetdir, $cmd, $new_cmd ) = @_ ;
813 syscmd("chroot $targetdir dpkg-divert --package proxmox " .
814 "--add --rename $cmd ") == 0 ||
815 die "unable to exec dpkg-divert \n ";
817 syscmd("ln -sf ${new_cmd} $targetdir/$cmd ") == 0 ||
818 die "unable to link diversion to ${new_cmd} \n ";
821 sub diversion_remove {
822 my ( $targetdir, $cmd ) = @_ ;
824 syscmd("mv $targetdir/$ {cmd}.distrib $targetdir/$ {cmd};") == 0 ||
825 die "unable to remove $cmd diversion \n ";
827 syscmd("chroot $targetdir dpkg-divert --remove $cmd ") == 0 ||
828 die "unable to remove $cmd diversion \n ";
832 my ( $partitions, $mode ) = @_ ;
834 die "unknown btrfs mode ' $mode '"
835 if !( $mode eq 'single' || $mode eq 'raid0' ||
836 $mode eq 'raid1' || $mode eq 'raid10');
838 my $cmd = ['mkfs.btrfs', '-f'];
840 push @$cmd, '-d', $mode, '-m', $mode ;
842 push @$cmd, @$partitions ;
847 sub zfs_create_rpool {
850 my $cmd = "zpool create -f -o cachefile=none";
852 $cmd .= " -o ashift= $config_options ->{ashift}"
853 if defined( $config_options ->{ashift});
855 syscmd(" $cmd $zfspoolname $vdev ") == 0 ||
856 die "unable to create zfs root pool \n ";
858 syscmd("zfs create $zfspoolname/ROOT ") == 0 ||
859 die "unable to create zfs $zfspoolname/ROOT volume \n ";
861 if ( $setup ->{product} eq 'pve') {
862 syscmd("zfs create $zfspoolname/data ") == 0 ||
863 die "unable to create zfs $zfspoolname/data volume \n ";
866 syscmd("zfs create $zfspoolname/ROOT/$zfsrootvolname ") == 0 ||
867 die "unable to create zfs $zfspoolname/ROOT/$zfsrootvolname volume \n ";
869 # disable atime during install
870 syscmd("zfs set atime=off $zfspoolname ") == 0 ||
871 die "unable to set zfs properties \n ";
873 my $value = $config_options ->{compress};
874 syscmd("zfs set compression= $value $zfspoolname ")
875 if defined( $value ) && $value ne 'off';
877 $value = $config_options ->{checksum};
878 syscmd("zfs set checksum= $value $zfspoolname ")
879 if defined( $value ) && $value ne 'on';
881 $value = $config_options ->{copies};
882 syscmd("zfs set copies= $value $zfspoolname ")
883 if defined( $value ) && $value != 1;
886 my $udevadm_trigger_block = sub {
889 sleep(1) if ! $nowait ; # give kernel time to reread part table
891 # trigger udev to create /dev/disk/by-uuid
892 syscmd("udevadm trigger --subsystem-match block");
893 syscmd("udevadm settle --timeout 10");
896 my $clean_disk = sub {
899 my $partitions = ` lsblk
-- output kname
-- noheadings
-- path
-- list
$disk` ;
900 foreach my $part ( split " \n " , $partitions ) {
901 next if $part eq $disk ;
902 next if $part !~ /^\Q$disk\E/ ;
903 eval { syscmd
( "pvremove -ff -y $part " ); };
904 eval { syscmd
( "dd if=/dev/zero of= $part bs=1M count=16" ); };
908 sub partition_bootable_disk
{
909 my ( $target_dev, $maxhdsizegb, $ptype ) = @_ ;
911 die "too dangerous" if $opt_testmode ;
913 die "unknown partition type ' $ptype '"
914 if !( $ptype eq '8E00' || $ptype eq '8300' || $ptype eq 'BF01' );
916 syscmd
( "sgdisk -Z ${target_dev}" );
917 my $hdsize = hd_size
( $target_dev ); # size in KB (1024 bytes)
919 my $restricted_hdsize_mb = 0 ; # 0 ==> end of partition
921 my $maxhdsize = $maxhdsizegb * 1024 * 1024 ;
922 if ( $maxhdsize < $hdsize ) {
923 $hdsize = $maxhdsize ;
924 $restricted_hdsize_mb = int ( $hdsize/1024 ) . 'M' ;
928 my $hdgb = int ( $hdsize/ ( 1024 * 1024 ));
929 die "hardisk ' $target_dev ' too small (${hdgb}GB) \n " if $hdgb < 8 ;
931 # 1 - BIOS boot partition (Grub Stage2): first free 1M
932 # 2 - EFI ESP: next free 512M
933 # 3 - OS/Data partition: rest, up to $maxhdsize in MB
935 my $grubbootdev = get_partition_dev
( $target_dev, 1 );
936 my $efibootdev = get_partition_dev
( $target_dev, 2 );
937 my $osdev = get_partition_dev
( $target_dev, 3 );
939 my $pcmd = [ 'sgdisk' ];
942 push @$pcmd, "-n${pnum}:1M:+512M" , "-t $pnum :EF00" ;
945 push @$pcmd, "-n${pnum}:513M:${restricted_hdsize_mb}" , "-t $pnum : $ptype " ;
947 push @$pcmd, $target_dev ;
949 my $os_size = $hdsize - 513 * 1024 ; # 512M efi + 1M bios_boot + 1M alignment
951 syscmd
( $pcmd ) == 0 ||
952 die "unable to partition harddisk '${target_dev}' \n " ;
955 $pcmd = [ 'sgdisk' , '-a1' , "-n $pnum :34:2047" , "-t $pnum :EF02" , $target_dev ];
957 syscmd
( $pcmd ) == 0 ||
958 die "unable to create bios_boot partition '${target_dev}' \n " ;
960 & $udevadm_trigger_block ();
962 foreach my $part ( $efibootdev, $osdev ) {
963 syscmd
( "dd if=/dev/zero of= $part bs=1M count=256" ) if - b
$part ;
966 return ( $os_size, $osdev, $efibootdev );
969 sub create_lvm_volumes
{
970 my ( $lvmdev, $os_size, $swap_size ) = @_ ;
972 my $vgname = $setup ->{ product
};
974 my $rootdev = "/dev/ $vgname/root " ;
975 my $datadev = "/dev/ $vgname/data " ;
978 # we use --metadatasize 250k, which results in "pe_start = 512"
979 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
980 syscmd
( "/sbin/pvcreate --metadatasize 250k -y -ff $lvmdev " ) == 0 ||
981 die "unable to initialize physical volume $lvmdev\n " ;
982 syscmd
( "/sbin/vgcreate $vgname $lvmdev " ) == 0 ||
983 die "unable to create volume group ' $vgname ' \n " ;
985 my $hdgb = int ( $os_size/ ( 1024 * 1024 ));
986 my $space = (( $hdgb > 128 ) ?
16 : ( $hdgb/8 ))* 1024 * 1024 ;
991 if ( $setup ->{ product
} eq 'pve' ) {
994 if ( $config_options ->{ maxroot
}) {
995 $maxroot = $config_options ->{ maxroot
};
1000 $rootsize = (( $hdgb > ( $maxroot*4 )) ?
$maxroot : $hdgb/4 )* 1024 * 1024 ;
1002 my $rest = $os_size - $swap_size - $rootsize ; # in KB
1005 if ( defined ( $config_options ->{ minfree
})) {
1006 $minfree = (( $config_options ->{ minfree
}* 1024 * 1024 ) >= $rest ) ?
$space :
1007 $config_options ->{ minfree
}* 1024 * 1024 ;
1012 $rest = $rest - $minfree ;
1014 if ( defined ( $config_options ->{ maxvz
})) {
1015 $rest = (( $config_options ->{ maxvz
}* 1024 * 1024 ) <= $rest ) ?
1016 $config_options ->{ maxvz
}* 1024 * 1024 : $rest ;
1022 my $minfree = defined ( $config_options ->{ minfree
}) ?
$config_options ->{ minfree
}* 1024 * 1024 : $space ;
1023 $rootsize = $os_size - $minfree - $swap_size ; # in KB
1027 syscmd
( "/sbin/lvcreate -L${swap_size}K -nswap $vgname " ) == 0 ||
1028 die "unable to create swap volume \n " ;
1030 $swapfile = "/dev/ $vgname/swap " ;
1033 syscmd
( "/sbin/lvcreate -L${rootsize}K -nroot $vgname " ) == 0 ||
1034 die "unable to create root volume \n " ;
1036 if ( $datasize > 4 * 1024 * 1024 ) {
1037 my $metadatasize = $datasize/100 ; # default 1% of data
1038 $metadatasize = 1024 * 1024 if $metadatasize < 1024 * 1024 ; # but at least 1G
1039 $metadatasize = 16 * 1024 * 1024 if $metadatasize > 16 * 1024 * 1024 ; # but at most 16G
1041 # otherwise the metadata is taken out of $minfree
1042 $datasize -= 2 * $metadatasize ;
1044 # 1 4MB PE to allow for rounding
1045 $datasize -= 4 * 1024 ;
1047 syscmd
( "/sbin/lvcreate -L${datasize}K -ndata $vgname " ) == 0 ||
1048 die "unable to create data volume \n " ;
1050 syscmd
( "/sbin/lvconvert --yes --type thin-pool --poolmetadatasize ${metadatasize}K $vgname/data " ) == 0 ||
1051 die "unable to create data thin-pool \n " ;
1056 syscmd
( "/sbin/vgchange -a y $vgname " ) == 0 ||
1057 die "unable to activate volume group \n " ;
1059 return ( $rootdev, $swapfile, $datadev );
1062 sub compute_swapsize
{
1065 my $hdgb = int ( $hdsize/ ( 1024 * 1024 ));
1068 if ( defined ( $config_options ->{ swapsize
})) {
1069 $swapsize = $config_options ->{ swapsize
}* 1024 * 1024 ;
1071 my $ss = int ( $total_memory / 1024 );
1073 $ss = ( $hdgb/8 ) if $ss > ( $hdgb/8 );
1075 $swapsize = $ss*1024*1024 ;
1083 my ( $basefile, $targetdir ) = @_ ;
1085 die "target ' $targetdir ' does not exist \n " if ! - d
$targetdir ;
1087 my $starttime = [ Time
:: HiRes
:: gettimeofday
];
1089 my $bootdevinfo = [];
1098 my $filesys = $config_options ->{ filesys
};
1100 if ( $filesys =~ m/zfs/ ) {
1101 $target_hd = undef ; # do not use this config
1103 $targetdir = "/ $zfspoolname/ROOT/$zfsrootvolname " ;
1104 } elsif ( $filesys =~ m/btrfs/ ) {
1105 $target_hd = undef ; # do not use this config
1111 for ( $i = 5 ; $i > 0 ; $i --) {
1112 syscmd
( "modprobe zfs" );
1113 last if - c
"/dev/zfs" ;
1117 die "unable to load zfs kernel module \n " if ! $i ;
1125 update_progress
( 0 , 0 , $maxper, "create partitions" );
1127 syscmd
( "vgchange -an" ) if ! $opt_testmode ; # deactivate all detected VGs
1129 if ( $opt_testmode ) {
1131 $rootdev = abs_path
( $opt_testmode );
1132 syscmd
( "umount $rootdev " );
1136 die "unsupported btrfs mode (for testing environment) \n "
1137 if $filesys ne 'btrfs (RAID0)' ;
1139 btrfs_create
([ $rootdev ], 'single' );
1141 } elsif ( $use_zfs ) {
1143 die "unsupported zfs mode (for testing environment) \n "
1144 if $filesys ne 'zfs (RAID0)' ;
1146 syscmd
( "zpool destroy $zfstestpool " );
1148 zfs_create_rpool
( $rootdev );
1155 } elsif ( $use_btrfs ) {
1157 my ( $devlist, $btrfs_mode ) = get_btrfs_raid_setup
();
1158 my $btrfs_partitions = [];
1160 foreach my $hd ( @$devlist ) {
1161 my $devname = @$hd [ 1 ];
1162 & $clean_disk ( $devname );
1163 my ( $size, $osdev, $efidev ) =
1164 partition_bootable_disk
( $devname, undef , '8300' );
1165 $rootdev = $osdev if ! defined ( $rootdev ); # simply point to first disk
1166 my $by_id = find_stable_path
( "/dev/disk/by-id" , $devname );
1167 push @$bootdevinfo, { esp
=> $efidev, devname
=> $devname,
1168 osdev
=> $osdev, by_id
=> $by_id };
1169 push @$btrfs_partitions, $osdev ;
1173 & $udevadm_trigger_block ();
1175 btrfs_create
( $btrfs_partitions, $btrfs_mode );
1177 } elsif ( $use_zfs ) {
1179 my ( $devlist, $bootdevlist, $vdev ) = get_zfs_raid_setup
();
1182 foreach my $hd ( @$devlist ) {
1183 & $clean_disk ( @$hd [ 1 ]);
1185 foreach my $hd ( @$bootdevlist ) {
1186 my $devname = @$hd [ 1 ];
1188 my ( $size, $osdev ) =
1189 partition_bootable_disk
( $devname, $config_options ->{ hdsize
}, 'BF01' );
1190 zfs_mirror_size_check
( $disksize, $size ) if $disksize ;
1191 push @$bootdevinfo, { devname
=> $devname, osdev
=> $osdev };
1195 & $udevadm_trigger_block ();
1197 foreach my $di ( @$bootdevinfo ) {
1198 my $devname = $di ->{ devname
};
1199 $di ->{ by_id
} = find_stable_path
( "/dev/disk/by-id" , $devname );
1201 # Note: using /dev/disk/by-id/ does not work for unknown reason, we get
1202 # cannot create 'rpool': no such pool or dataset
1203 #my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
1205 my $osdev = $di ->{ osdev
};
1206 $vdev =~ s/ $devname/ $osdev/ ;
1209 zfs_create_rpool
( $vdev );
1213 die "target ' $target_hd ' is not a valid block device \n " if ! - b
$target_hd ;
1215 & $clean_disk ( $target_hd );
1217 my ( $os_size, $osdev, $efidev );
1218 ( $os_size, $osdev, $efidev ) =
1219 partition_bootable_disk
( $target_hd, $config_options ->{ hdsize
}, '8E00' );
1221 & $udevadm_trigger_block ();
1223 my $by_id = find_stable_path
( "/dev/disk/by-id" , $target_hd );
1224 push @$bootdevinfo, { esp
=> $efidev, devname
=> $target_hd,
1225 osdev
=> $osdev, by_id
=> $by_id };
1227 my $swap_size = compute_swapsize
( $os_size );
1228 ( $rootdev, $swapfile, $datadev ) =
1229 create_lvm_volumes
( $osdev, $os_size, $swap_size );
1231 # trigger udev to create /dev/disk/by-uuid
1232 & $udevadm_trigger_block ( 1 );
1236 # to be fast during installation
1237 syscmd
( "zfs set sync=disabled $zfspoolname " ) == 0 ||
1238 die "unable to set zfs properties \n " ;
1241 update_progress
( 0.03 , 0 , $maxper, "create swap space" );
1243 syscmd
( "mkswap -f $swapfile " ) == 0 ||
1244 die "unable to create swap space \n " ;
1247 update_progress
( 0.05 , 0 , $maxper, "creating filesystems" );
1249 foreach my $di ( @$bootdevinfo ) {
1250 next if ! $di ->{ esp
};
1251 syscmd
( "mkfs.vfat -F32 $di ->{esp}" ) == 0 ||
1252 die "unable to initialize EFI ESP on device $di ->{esp} \n " ;
1257 } elsif ( $use_btrfs ) {
1260 create_filesystem
( $rootdev, 'root' , $filesys, 0.05 , $maxper, 0 , 1 );
1263 update_progress
( 1 , 0.05 , $maxper, "mounting target $rootdev " );
1268 my $mount_opts = 'noatime' ;
1269 $mount_opts .= ',nobarrier'
1270 if $use_btrfs || $filesys =~ /^ext\d$/ ;
1272 syscmd
( "mount -n $rootdev -o $mount_opts $targetdir " ) == 0 ||
1273 die "unable to mount $rootdev\n " ;
1276 mkdir " $targetdir/boot " ;
1277 mkdir " $targetdir/boot/efi " ;
1279 mkdir " $targetdir/var " ;
1280 mkdir " $targetdir/var/lib " ;
1282 if ( $setup ->{ product
} eq 'pve' ) {
1283 mkdir " $targetdir/var/lib/vz " ;
1284 mkdir " $targetdir/var/lib/pve " ;
1287 syscmd
( "btrfs subvolume create $targetdir/var/lib/pve/local -btrfs" ) == 0 ||
1288 die "unable to create btrfs subvolume \n " ;
1292 mkdir " $targetdir/mnt " ;
1293 mkdir " $targetdir/mnt/hostrun " ;
1294 syscmd
( "mount --bind /run $targetdir/mnt/hostrun " ) == 0 ||
1295 die "unable to bindmount run on $targetdir/mnt/hostrun\n " ;
1297 update_progress
( 1 , 0.05 , $maxper, "extracting base system" );
1299 my ( $dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size ) = stat ( $basefile );
1300 $ino || die "unable to open file ' $basefile ' - $!\n " ;
1302 my $files = file_read_firstline
( "${proxmox_cddir}/proxmox/ $setup ->{product}-base.cnt" ) ||
1303 die "unable to read base file count \n " ;
1308 run_command
( "unsquashfs -f -dest $targetdir -i $basefile " , sub {
1310 return if $line !~ m/^$targetdir/ ;
1312 my $nper = int (( $count * 100 )/ $files );
1313 if ( $nper != $per ) {
1315 my $frac = $per > 100 ?
1 : $per/100 ;
1316 update_progress
( $frac, $maxper, 0.5 );
1320 syscmd
( "mount -n -t tmpfs tmpfs $targetdir/tmp " ) == 0 ||
1321 die "unable to mount tmpfs on $targetdir/tmp\n " ;
1322 syscmd
( "mount -n -t proc proc $targetdir/proc " ) == 0 ||
1323 die "unable to mount proc on $targetdir/proc\n " ;
1324 syscmd
( "mount -n -t sysfs sysfs $targetdir/sys " ) == 0 ||
1325 die "unable to mount sysfs on $targetdir/sys\n " ;
1326 syscmd
( "chroot $targetdir mount --bind /mnt/hostrun /run" ) == 0 ||
1327 die "unable to re-bindmount hostrun on /run in chroot \n " ;
1329 update_progress
( 1 , $maxper, 0.5 , "configuring base system" );
1334 "127.0.0.1 localhost.localdomain localhost \n " .
1335 " $ipaddress $hostname . $domain $hostname\n\n " .
1336 "# The following lines are desirable for IPv6 capable hosts \n\n " .
1337 "::1 ip6-localhost ip6-loopback \n " .
1338 "fe00::0 ip6-localnet \n " .
1339 "ff00::0 ip6-mcastprefix \n " .
1340 "ff02::1 ip6-allnodes \n " .
1341 "ff02::2 ip6-allrouters \n " .
1342 "ff02::3 ip6-allhosts \n " ;
1344 write_config
( $hosts, " $targetdir/etc/hosts " );
1346 write_config
( " $hostname\n " , " $targetdir/etc/hostname " );
1348 syscmd
( "/bin/hostname $hostname " ) if ! $opt_testmode ;
1350 # configure interfaces
1352 my $ifaces = "auto lo \n iface lo inet loopback \n\n " ;
1354 my $ntype = $ipversion == 4 ?
'inet' : 'inet6' ;
1356 my $ethdev = $ipconf ->{ ifaces
}->{ $ipconf ->{ selected
}}->{ name
};
1358 if ( $setup ->{ bridged_network
}) {
1359 $ifaces .= "iface $ethdev $ntype manual \n " ;
1362 " \n auto vmbr0 \n iface vmbr0 $ntype static \n " .
1363 " \t address $ipaddress\n " .
1364 " \t netmask $netmask\n " .
1365 " \t gateway $gateway\n " .
1366 " \t bridge_ports $ethdev\n " .
1367 " \t bridge_stp off \n " .
1370 $ifaces .= "auto $ethdev\n " .
1371 "iface $ethdev $ntype static \n " .
1372 " \t address $ipaddress\n " .
1373 " \t netmask $netmask\n " .
1374 " \t gateway $gateway\n " ;
1377 foreach my $iface ( sort keys %{ $ipconf ->{ ifaces
}}) {
1378 my $name = $ipconf ->{ ifaces
}->{ $iface }->{ name
};
1379 next if $name eq $ethdev ;
1381 $ifaces .= " \n iface $name $ntype manual \n " ;
1384 write_config
( $ifaces, " $targetdir/etc/network/interfaces " );
1388 my $resolvconf = "search $domain\nnameserver $dnsserver\n " ;
1389 write_config
( $resolvconf, " $targetdir/etc/resolv .conf" );
1393 my $fstab = "# <file system> <mount point> <type> <options> <dump> <pass> \n " ;
1397 } elsif ( $use_btrfs ) {
1399 my $cmd = "blkid -u filesystem -t TYPE=btrfs -o export $rootdev " ;
1400 run_command
( $cmd, sub {
1403 if ( $line =~ m/^UUID=([A-Fa-f0-9\-]+)$/ ) {
1408 die "unable to detect FS UUID" if ! defined ( $fsuuid );
1410 $fstab .= "UUID= $fsuuid / btrfs defaults 0 1 \n " ;
1412 my $root_mountopt = $fssetup ->{ $filesys }->{ root_mountopt
} || 'defaults' ;
1413 $fstab .= " $rootdev / $filesys ${root_mountopt} 0 1 \n " ;
1417 # Note: this is required by current grub, but really dangerous, because
1418 # vfat does not have journaling, so it triggers manual fsck after each crash
1419 # so we only mount /boot/efi if really required (efi systems).
1420 if ( $grub_plattform =~ m/^efi-/ ) {
1421 if ( scalar ( @$bootdevinfo )) {
1422 my $di = @$bootdevinfo [ 0 ]; # simply use first disk
1424 my $efi_boot_uuid = $di ->{ esp
};
1425 if ( my $uuid = find_dev_by_uuid
( $di ->{ esp
})) {
1426 $efi_boot_uuid = "UUID= $uuid " ;
1429 $fstab .= "${efi_boot_uuid} /boot/efi vfat defaults 0 1 \n " ;
1435 $fstab .= " $swapfile none swap sw 0 0 \n " if $swapfile ;
1437 $fstab .= "proc /proc proc defaults 0 0 \n " ;
1439 write_config
( $fstab, " $targetdir/etc/fstab " );
1440 write_config
( "" , " $targetdir/etc/mtab " );
1442 syscmd
( "cp ${proxmox_libdir}/policy-disable-rc.d " .
1443 " $targetdir/usr/sbin/policy -rc.d" ) == 0 ||
1444 die "unable to copy policy-rc.d \n " ;
1445 syscmd
( "cp ${proxmox_libdir}/fake-start-stop-daemon " .
1446 " $targetdir/sbin/ " ) == 0 ||
1447 die "unable to copy start-stop-daemon \n " ;
1449 diversion_add
( $targetdir, "/sbin/start-stop-daemon" , "/sbin/fake-start-stop-daemon" );
1450 diversion_add
( $targetdir, "/usr/sbin/update-grub" , "/bin/true" );
1451 diversion_add
( $targetdir, "/usr/sbin/update-initramfs" , "/bin/true" );
1453 syscmd
( "touch $targetdir/proxmox_install_mode " );
1455 my $grub_install_devices_txt = '' ;
1456 foreach my $di ( @$bootdevinfo ) {
1457 $grub_install_devices_txt .= ', ' if $grub_install_devices_txt ;
1458 $grub_install_devices_txt .= $di ->{ by_id
} || $di ->{ devname
};
1461 # Note: keyboard-configuration/xbkb-keymap is used by console-setup
1462 my $xkmap = $cmap ->{ kmap
}->{ $keymap }->{ x11
} // 'us' ;
1464 debconfig_set
( $targetdir, <<_EOD);
1465 locales locales/default_environment_locale select en_US.UTF-8
1466 locales locales/locales_to_be_generated select en_US.UTF-8 UTF-8
1467 samba-common samba-common/dhcp boolean false
1468 samba-common samba-common/workgroup string WORKGROUP
1469 postfix postfix/main_mailer_type select No configuration
1470 keyboard-configuration keyboard-configuration/xkb-keymap select $xkmap
1471 d-i debian-installer/locale select en_US.UTF-8
1472 grub-pc grub-pc/install_devices select $grub_install_devices_txt
1476 while (<${ proxmox_pkgdir
}/*. deb
>) { $pkg_count++ };
1478 # btrfs/dpkg is extremely slow without --force-unsafe-io
1479 my $dpkg_opts = $use_btrfs ?
"--force-unsafe-io" : "" ;
1482 while (<${ proxmox_pkgdir
}/*. deb
>) {
1485 my ( $deb ) = $path =~ m/${proxmox_pkgdir}\/ (.* \
. deb
)/;
1486 # if ($deb =~ m/^grub-efi-/ && $deb !~ m/^grub-${grub_plattform}/) {
1490 update_progress
( $count/$pkg_count, 0.5 , 0.75 , "extracting $deb " );
1491 print "extracting: $deb\n " ;
1492 syscmd
( "cp $path $targetdir/tmp/$deb " ) == 0 ||
1493 die "installation of package $deb failed \n " ;
1494 syscmd
( "chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/ $deb " ) == 0 ||
1495 die "installation of package $deb failed \n " ;
1496 update_progress
((++ $count )/ $pkg_count, 0.5 , 0.75 );
1499 # needed for postfix postinst in case no other NIC is active
1500 syscmd
( "chroot $targetdir ifup lo" );
1502 my $cmd = "chroot $targetdir dpkg $dpkg_opts --force-confold --configure -a" ;
1504 run_command
( $cmd, sub {
1506 if ( $line =~ m/Setting up\s+(\S+)/ ) {
1507 update_progress
((++ $count )/ $pkg_count, 0.75 , 0.95 ,
1512 unlink " $targetdir/etc/mailname " ;
1513 $postfix_main_cf =~ s/__FQDN__/${hostname}.${domain}/ ;
1514 write_config
( $postfix_main_cf, " $targetdir/etc/postfix/main .cf" );
1516 # make sure we have all postfix directories
1517 syscmd
( "chroot $targetdir /usr/sbin/postfix check" );
1518 # cleanup mail queue
1519 syscmd
( "chroot $targetdir /usr/sbin/postsuper -d ALL" );
1521 # enable NTP (timedatectl set-ntp true does not work without DBUS)
1522 syscmd
( "chroot $targetdir /bin/systemctl enable systemd-timesyncd.service" );
1524 unlink " $targetdir/proxmox_install_mode " ;
1527 unlink ( " $targetdir/etc/localtime " );
1528 symlink ( "/usr/share/zoneinfo/ $timezone " , " $targetdir/etc/localtime " );
1529 write_config
( " $timezone\n " , " $targetdir/etc/timezone " );
1532 if ( my $mirror = $cmap ->{ country
}->{ $country }->{ mirror
}) {
1533 my $fn = " $targetdir/etc/apt/sources .list" ;
1534 syscmd
( "sed -i 's/ftp\\.debian\\.org/$mirror/' ' $fn '" );
1537 # create extended_states for apt (avoid cron job warning if that
1538 # file does not exist)
1539 write_config
( '' , " $targetdir/var/lib/apt/extended_states " );
1541 # allow ssh root login
1542 syscmd
([ 'sed' , '-i' , 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' , " $targetdir/etc/ssh/sshd_config " ]);
1544 if ( $setup ->{ product
} eq 'pmg' ) {
1545 # install initial clamav DB
1546 my $srcdir = "${proxmox_cddir}/proxmox/clamav" ;
1547 foreach my $fn ( "main.cvd" , "bytecode.cvd" , "daily.cvd" , "safebrowsing.cvd" ) {
1548 syscmd
( "cp \" $srcdir/$fn\ " \" $targetdir/var/lib/clamav\ "" ) == 0 ||
1549 die "installation of clamav db file ' $fn ' failed \n " ;
1551 syscmd
( "chroot $targetdir /bin/chown clamav:clamav -R /var/lib/clamav" ) == 0 ||
1552 die "unable to set owner for clamav database files \n " ;
1555 if ( $setup ->{ product
} eq 'pve' ) {
1556 # save installer settings
1557 my $ucc = uc ( $country );
1558 debconfig_set
( $targetdir, "pve-manager pve-manager/country string $ucc\n " );
1561 update_progress
( 0.8 , 0.95 , 1 , "make system bootable" );
1564 syscmd
( "sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT \\ / $zfsrootvolname boot=zfs \" /' $targetdir/etc/default/grub " ) == 0 ||
1565 die "unable to update /etc/default/grub \n " ;
1569 diversion_remove
( $targetdir, "/usr/sbin/update-grub" );
1570 diversion_remove
( $targetdir, "/usr/sbin/update-initramfs" );
1573 foreach my $fn (< $targetdir/lib/modules/* >) {
1574 if ( $fn =~ m!/(\d+\.\d+\.\d+-\d+-pve)$! ) {
1575 die "found multiple kernels \n " if defined ( $kapi );
1579 die "unable to detect kernel version \n " if ! defined ( $kapi );
1581 if (! $opt_testmode ) {
1583 unlink ( " $targetdir/etc/mtab " );
1584 symlink ( "/proc/mounts" , " $targetdir/etc/mtab " );
1585 syscmd
( "mount -n --bind /dev $targetdir/dev " );
1587 syscmd
( "chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi " ) == 0 ||
1588 die "unable to install initramfs \n " ;
1590 foreach my $di ( @$bootdevinfo ) {
1591 my $dev = $di ->{ devname
};
1592 syscmd
( "chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev " ) == 0 ||
1593 die "unable to install the i386-pc boot loader on ' $dev ' \n " ;
1596 syscmd
( "mount -n $di ->{esp} -t vfat $targetdir/boot/efi " ) == 0 ||
1597 die "unable to mount $di ->{esp} \n " ;
1598 my $rc = syscmd
( "chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev " );
1600 if (- d
'/sys/firmware/efi' ) {
1601 die "unable to install the EFI boot loader on ' $dev ' \n " ;
1603 warn "unable to install the EFI boot loader on ' $dev ', ignoring (not booted using UEFI) \n " ;
1606 # also install fallback boot file (OVMF does not boot without)
1607 mkdir ( " $targetdir/boot/efi/EFI/BOOT " );
1608 syscmd
( "cp $targetdir/boot/efi/EFI/proxmox/grubx64 .efi $targetdir/boot/efi/EFI/BOOT/BOOTx64 .EFI" ) == 0 ||
1609 die "unable to copy efi boot loader \n " ;
1611 syscmd
( "umount $targetdir/boot/efi " ) == 0 ||
1612 die "unable to umount $targetdir/boot/efi\n " ;
1616 syscmd
( "chroot $targetdir /usr/sbin/update-grub" ) == 0 ||
1617 die "unable to update boot loader config \n " ;
1619 syscmd
( "umount $targetdir/dev " );
1624 unlink " $targetdir/usr/sbin/policy -rc.d" ;
1626 diversion_remove
( $targetdir, "/sbin/start-stop-daemon" );
1629 my $octets = encode
( "utf-8" , $password );
1630 run_command
( "chroot $targetdir /usr/sbin/chpasswd" , undef ,
1633 if ( $setup ->{ product
} eq 'pmg' ) {
1635 write_config
( "section: admin \n\t email ${mailto} \n " ,
1636 " $targetdir/etc/pmg/pmg .conf" );
1638 } elsif ( $setup ->{ product
} eq 'pve' ) {
1642 my $tmpdir = " $targetdir/tmp/pve " ;
1645 # write vnc keymap to datacenter.cfg
1646 my $vnckmap = $cmap ->{ kmap
}->{ $keymap }->{ kvm
} || 'en-us' ;
1647 write_config
( "keyboard: $vnckmap\n " ,
1648 " $tmpdir/datacenter .cfg" );
1651 write_config
( "user:root\ @pam :1:0:::${mailto}:: \n " ,
1652 " $tmpdir/user .cfg" );
1655 my $storage_cfg_fn = " $tmpdir/storage .cfg" ;
1657 write_config
( $storage_cfg_zfs, $storage_cfg_fn );
1658 } elsif ( $use_btrfs ) {
1659 write_config
( $storage_cfg_btrfs, $storage_cfg_fn );
1660 } elsif ( $datadev ) {
1661 write_config
( $storage_cfg_lvmthin, $storage_cfg_fn );
1663 write_config
( $storage_cfg_local, $storage_cfg_fn );
1666 run_command
( "chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db" );
1668 syscmd
( "rm -rf $tmpdir " );
1674 update_progress
( 1 , 0 , 1 , "" );
1678 if ( $opt_testmode ) {
1679 my $elapsed = Time
:: HiRes
:: tv_interval
( $starttime );
1680 print "Elapsed extract time: $elapsed\n " ;
1682 syscmd
( "chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package} \n '> final.pkglist" );
1685 syscmd
( "umount $targetdir/run " );
1686 syscmd
( "umount $targetdir/mnt/hostrun " );
1687 syscmd
( "umount $targetdir/tmp " );
1688 syscmd
( "umount $targetdir/proc " );
1689 syscmd
( "umount $targetdir/sys " );
1692 syscmd
( "zfs umount -a" ) == 0 ||
1693 die "unable to unmount zfs \n " ;
1695 syscmd
( "umount -d $targetdir " );
1698 if (! $err && $use_zfs ) {
1699 syscmd
( "zfs set sync=standard $zfspoolname " ) == 0 ||
1700 die "unable to set zfs properties \n " ;
1702 syscmd
( "zfs set mountpoint=/ $zfspoolname/ROOT/$zfsrootvolname " ) == 0 ||
1703 die "zfs set mountpoint failed \n " ;
1705 syscmd
( "zpool set bootfs= $zfspoolname/ROOT/$zfsrootvolname $zfspoolname " ) == 0 ||
1706 die "zfs set bootfs failed \n " ;
1707 syscmd
( "zpool export $zfspoolname " );
1713 my $last_display_change = 0 ;
1715 my $display_info_counter = 0 ;
1717 my $display_info_items = [
1718 "extract1-license.htm" ,
1719 "extract2-rulesystem.htm" ,
1720 "extract3-spam.htm" ,
1721 "extract4-virus.htm" ,
1726 my $min_display_time = 15 ;
1730 return if ( $ctime - $last_display_change ) < $min_display_time ;
1732 my $page = $display_info_items ->[ $display_info_counter % scalar ( @$display_info_items )];
1734 $display_info_counter++ ;
1736 display_html
( $page );
1740 my ( $filename ) = @_ ;
1742 $filename = $steps [ $step_number ]->{ html
} if ! $filename ;
1744 my $path = "${proxmox_libdir}/html/ $filename " ;
1746 my $url = "file:// $path " ;
1748 my $data = file_get_contents
( $path );
1750 if ( $filename eq 'license.htm' ) {
1751 my $license = eval { decode
( 'utf8' , file_get_contents
( "${proxmox_cddir}/EULA" )) };
1753 die $err if ! $opt_testmode ;
1754 $license = "TESTMODE: Ignore non existent EULA... \n " ;
1756 my $title = "END USER LICENSE AGREEMENT (EULA)" ;
1757 $data =~ s/__LICENSE__/$license/ ;
1758 $data =~ s/__LICENSE_TITLE__/$title/ ;
1761 $htmlview -> load_html ( $data, $url );
1763 $last_display_change = time ();
1768 my ( $text, $fctn ) = @_ ;
1770 $fctn = $step_number if ! $fctn ;
1771 $text = "_Previous" if ! $text ;
1772 $prev_btn -> set_label ( $text );
1775 $steps [ $step_number ]->{ function
}();
1777 $prev_btn -> grab_focus ();
1781 my ( $text, $fctn ) = @_ ;
1784 my $step = $steps [ $step_number ];
1785 $text // = $steps [ $step_number ]->{ next_button
} // '_Next' ;
1786 $next -> set_label ( $text );
1788 $next -> grab_focus ();
1791 sub create_main_window
{
1793 $window = Gtk3
:: Window-
> new ();
1794 $window -> set_default_size ( 1024 , 768 );
1795 $window -> set_has_resize_grip ( 0 );
1796 $window -> set_decorated ( 0 ) if ! $opt_testmode ;
1798 my $vbox = Gtk3
:: VBox-
> new ( 0 , 0 );
1800 my $logofn = " $setup ->{product}-banner.png" ;
1801 my $image = Gtk3
:: Image-
> new_from_file ( "${proxmox_libdir}/ $logofn " );
1802 $vbox -> pack_start ( $image, 0 , 0 , 0 );
1804 my $hbox = Gtk3
:: HBox-
> new ( 0 , 0 );
1805 $vbox -> pack_start ( $hbox, 1 , 1 , 0 );
1807 # my $f1 = Gtk3::Frame->new ('test');
1808 # $f1->set_shadow_type ('none');
1809 # $hbox->pack_start ($f1, 1, 1, 0);
1811 my $sep1 = Gtk3
:: HSeparator-
> new ();
1812 $vbox -> pack_start ( $sep1, 0 , 0 , 0 );
1814 $cmdbox = Gtk3
:: HBox-
> new ();
1815 $vbox -> pack_start ( $cmdbox, 0 , 0 , 10 );
1817 $next = Gtk3
:: Button-
> new ( '_Next' );
1818 $next -> signal_connect ( clicked
=> sub { $last_display_change = 0 ; & $next_fctn (); });
1819 $cmdbox -> pack_end ( $next, 0 , 0 , 10 );
1822 $prev_btn = Gtk3
:: Button-
> new ( '_Previous' );
1823 $prev_btn -> signal_connect ( clicked
=> sub { $last_display_change = 0 ; & prev_function
(); });
1824 $cmdbox -> pack_end ( $prev_btn, 0 , 0 , 10 );
1827 my $abort = Gtk3
:: Button-
> new ( '_Abort' );
1828 $abort -> set_can_focus ( 0 );
1829 $cmdbox -> pack_start ( $abort, 0 , 0 , 10 );
1830 $abort -> signal_connect ( clicked
=> sub { exit (- 1 ); });
1832 my $vbox2 = Gtk3
:: VBox-
> new ( 0 , 0 );
1835 $htmlview = Gtk3
:: WebKit2
:: WebView-
> new ();
1836 my $scrolls = Gtk3
:: ScrolledWindow-
> new ();
1837 $scrolls -> add ( $htmlview );
1839 my $hbox2 = Gtk3
:: HBox-
> new ( 0 , 0 );
1840 $hbox2 -> pack_start ( $scrolls, 1 , 1 , 0 );
1842 $vbox2 -> pack_start ( $hbox2, 1 , 1 , 0 );
1844 my $vbox3 = Gtk3
:: VBox-
> new ( 0 , 0 );
1845 $vbox2 -> pack_start ( $vbox3, 0 , 0 , 0 );
1847 my $sep2 = Gtk3
:: HSeparator-
> new ;
1848 $vbox3 -> pack_start ( $sep2, 0 , 0 , 0 );
1850 $inbox = Gtk3
:: HBox-
> new ( 0 , 0 );
1851 $vbox3 -> pack_start ( $inbox, 0 , 0 , 0 );
1853 $window -> add ( $vbox );
1860 $inbox -> foreach ( sub {
1862 $inbox -> remove ( $child );
1866 # fixme: newer GTK3 has special properties to handle numbers with Entry
1867 # only allow floating point numbers with Gtk3::Entry
1870 my ( $entry, $event ) = @_ ;
1872 return check_number
( $entry, $event, 1 );
1876 my ( $entry, $event ) = @_ ;
1878 return check_number
( $entry, $event, 0 );
1882 my ( $entry, $event, $float ) = @_ ;
1884 my $val = $event -> get_keyval ;
1886 if (( $float && $val == ord '.' ) ||
1887 $val == Gtk3
:: Gdk
:: KEY_ISO_Left_Tab
||
1888 $val == Gtk3
:: Gdk
:: KEY_Shift_L
||
1889 $val == Gtk3
:: Gdk
:: KEY_Tab
||
1890 $val == Gtk3
:: Gdk
:: KEY_Left
||
1891 $val == Gtk3
:: Gdk
:: KEY_Right
||
1892 $val == Gtk3
:: Gdk
:: KEY_BackSpace
||
1893 $val == Gtk3
:: Gdk
:: KEY_Delete
||
1894 ( $val >= ord '0' && $val <= ord '9' ) ||
1895 ( $val >= Gtk3
:: Gdk
:: KEY_KP_0
&&
1896 $val <= Gtk3
:: Gdk
:: KEY_KP_9
)) {
1903 sub create_text_input
{
1904 my ( $default, $text ) = @_ ;
1906 my $hbox = Gtk3
:: HBox-
> new ( 0 , 0 );
1908 my $label = Gtk3
:: Label-
> new ( $text );
1909 $label -> set_size_request ( 150 , - 1 );
1910 $label -> set_alignment ( 1 , 0.5 );
1911 $hbox -> pack_start ( $label, 0 , 0 , 10 );
1912 my $e1 = Gtk3
:: Entry-
> new ();
1913 $e1 -> set_width_chars ( 30 );
1914 $hbox -> pack_start ( $e1, 0 , 0 , 0 );
1915 $e1 -> set_text ( $default );
1917 return ( $hbox, $e1 );
1925 my $links = `ip -o l` ;
1926 foreach my $l ( split /\n/ , $links ) {
1927 my ( $index, $name, $flags, $state, $mac ) = $l =~ m/^(\d+):\s+(\S+):\s+<(\S+)>.*\s+state\s+(\S+)\s+.*\s+link\/ ether\s
+( \S
+) \s
+/;
1928 next if ! $name || $name eq 'lo' ;
1930 my $driver = readlink "/sys/class/net/ $name/device/driver " || 'unknown' ;
1931 $driver =~ s!^.*/!! ;
1933 $ifaces ->{ " $index " } = {
1941 my $addresses = `ip -o a s $name` ;
1942 foreach my $a (split / \n /, $addresses ) {
1943 my ( $family, $ip, $prefix ) = $a =~ m/^\Q$index\E:\s+\Q$name\E\s+(inet|inet6)\s+($IPRE)\/(\d+)\s+/;
1945 next if $a =~ /scope\s+link/; # ignore link local
1949 if ( $family eq 'inet') {
1950 next if ! $ip =~ / $IPV4RE/ ;
1951 next if $prefix < 8 || $prefix > 32;
1952 $mask = @$ipv4_reverse_mask [ $prefix ];
1954 next if ! $ip =~ / $IPV6RE/ ;
1957 $default = $index if ! $default ;
1959 $ifaces ->{" $index "}->{" $family "} = {
1967 my $route = ` ip route
`;
1968 my ( $gateway ) = $route =~ m/^default\s+via\s+(\S+)\s+/m;
1970 my $resolvconf = ` cat
/etc/ resolv
. conf
`;
1971 my ( $dnsserver ) = $resolvconf =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/m;
1972 my ( $domain ) = $resolvconf =~ m/^domain\s+(\S+)$/m;
1975 default => $default,
1977 gateway => $gateway,
1978 dnsserver => $dnsserver,
1983 sub display_message {
1986 my $dialog = Gtk3::MessageDialog->new( $window, 'modal',
1987 'info', 'ok', $msg );
1995 my $dialog = Gtk3::MessageDialog->new( $window, 'modal',
1996 'error', 'ok', $msg );
2001 my $ipconf_first_view = 1;
2003 sub create_ipconf_view {
2008 my $vbox = Gtk3::VBox->new(0, 0);
2009 $inbox ->pack_start( $vbox, 1, 0, 0);
2010 my $hbox = Gtk3::HBox->new(0, 0);
2011 $vbox ->pack_start( $hbox, 0, 0, 10);
2012 my $vbox2 = Gtk3::VBox->new(0, 0);
2015 my $ipaddr_text = $config ->{ipaddress} // "192.168.100.2";
2017 ( $ipbox, $ipconf_entry_addr ) =
2018 create_text_input( $ipaddr_text, 'IP Address:');
2020 my $netmask_text = $config ->{netmask} // "255.255.255.0";
2022 ( $maskbox, $ipconf_entry_mask ) =
2023 create_text_input( $netmask_text, 'Netmask:');
2025 my $device_cb = Gtk3::ComboBoxText->new();
2026 $device_cb ->set_active(0);
2027 $device_cb ->set_visible(1);
2029 my $get_device_desc = sub {
2031 return " $iface ->{name} - $iface ->{mac} ( $iface ->{driver})";
2034 my $device_active_map = {};
2035 my $device_active_reverse_map = {};
2037 my $device_change_handler = sub {
2038 my $current = shift;
2040 my $new = $device_active_map ->{ $current ->get_active()};
2041 return if defined( $ipconf ->{selected}) && $new eq $ipconf ->{selected};
2043 $ipconf ->{selected} = $new ;
2044 my $iface = $ipconf ->{ifaces}->{ $ipconf ->{selected}};
2045 $config ->{mngmt_nic} = $iface ->{name};
2046 $ipconf_entry_addr ->set_text( $iface ->{inet}->{addr} || $iface ->{inet6}->{addr})
2047 if $iface ->{inet}->{addr} || $iface ->{inet6}->{addr};
2048 $ipconf_entry_mask ->set_text( $iface ->{inet}->{mask} || $iface ->{inet6}->{mask})
2049 if $iface ->{inet}->{mask} || $iface ->{inet6}->{mask};
2053 foreach my $index (sort keys %{ $ipconf ->{ifaces}}) {
2054 $device_cb ->append_text(& $get_device_desc ( $ipconf ->{ifaces}->{ $index }));
2055 $device_active_map ->{ $i } = $index ;
2056 $device_active_reverse_map ->{ $ipconf ->{ifaces}->{ $index }->{name}} = $i ;
2057 if ( $ipconf_first_view && $index == $ipconf ->{default}) {
2058 $device_cb ->set_active( $i );
2059 & $device_change_handler ( $device_cb );
2060 $ipconf_first_view = 0;
2062 $device_cb ->signal_connect('changed' => $device_change_handler );
2066 if (my $nic = $config ->{mngmt_nic}) {
2067 $device_cb ->set_active( $device_active_reverse_map ->{ $nic } // 0);
2069 $device_cb ->set_active(0);
2072 my $devicebox = Gtk3::HBox->new(0, 0);
2073 my $label = Gtk3::Label->new("Management Interface:");
2074 $label ->set_size_request(150, -1);
2075 $label ->set_alignment(1, 0.5);
2076 $devicebox ->pack_start( $label, 0, 0, 10);
2077 $devicebox ->pack_start( $device_cb, 0, 0, 0);
2079 $vbox2 ->pack_start( $devicebox, 0, 0, 2);
2081 my $hn = $config ->{fqdn} // " $setup ->{product}." . ( $ipconf ->{domain} // "example.invalid");
2083 my ( $hostbox, $hostentry ) =
2084 create_text_input( $hn, 'Hostname (FQDN):');
2085 $vbox2 ->pack_start( $hostbox, 0, 0, 2);
2087 $vbox2 ->pack_start( $ipbox, 0, 0, 2);
2089 $vbox2 ->pack_start( $maskbox, 0, 0, 2);
2091 $gateway = $config ->{gateway} // $ipconf ->{gateway} || '192.168.100.1';
2094 ( $gwbox, $ipconf_entry_gw ) =
2095 create_text_input( $gateway, 'Gateway:');
2097 $vbox2 ->pack_start( $gwbox, 0, 0, 2);
2099 $dnsserver = $config ->{dnsserver} // $ipconf ->{dnsserver} || $gateway ;
2102 ( $dnsbox, $ipconf_entry_dns ) =
2103 create_text_input( $dnsserver, 'DNS Server:');
2105 $vbox2 ->pack_start( $dnsbox, 0, 0, 0);
2108 set_next(undef, sub {
2112 my $text = $hostentry ->get_text();
2117 $config ->{fqdn} = $text ;
2119 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
2121 # Debian does not support purely numeric hostnames
2122 if ( $text && $text =~ /^[0-9]+(?:\.|$)/) {
2123 display_message("Purely numeric hostnames are not allowed.");
2124 $hostentry ->grab_focus();
2128 if ( $text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ &&
2129 $text =~ m/^([^\.]+)\.(\S+)$/) {
2133 display_message("Hostname does not look like a fully qualified domain name.");
2134 $hostentry ->grab_focus();
2140 $text = $ipconf_entry_addr ->get_text();
2143 if ( $text =~ m!^($IPV4RE)$!) {
2146 } elsif ( $text =~ m!^($IPV6RE)$!) {
2150 display_message("IP address is not valid.");
2151 $ipconf_entry_addr ->grab_focus();
2154 $config ->{ipaddress} = $ipaddress ;
2156 $text = $ipconf_entry_mask ->get_text();
2159 if (( $ipversion == 6) && ( $text =~ m/^(\d+)$/) && ( $1 >= 8) && ( $1 <= 126)) {
2161 } elsif (( $ipversion == 4) && defined( $ipv4_mask_hash ->{ $text })) {
2164 display_message("Netmask is not valid.");
2165 $ipconf_entry_mask ->grab_focus();
2168 $config ->{netmask} = $netmask ;
2170 $text = $ipconf_entry_gw ->get_text();
2173 if (( $ipversion == 4) && ( $text =~ m!^($IPV4RE)$!)) {
2175 } elsif (( $ipversion == 6) && ( $text =~ m!^($IPV6RE)$!)) {
2178 display_message("Gateway is not valid.");
2179 $ipconf_entry_gw ->grab_focus();
2182 $config ->{gateway} = $gateway ;
2184 $text = $ipconf_entry_dns ->get_text();
2187 if (( $ipversion == 4) && ( $text =~ m!^($IPV4RE)$!)) {
2189 } elsif (( $ipversion == 6) && ( $text =~ m!^($IPV6RE)$!)) {
2192 display_message("DNS server is not valid.");
2193 $ipconf_entry_dns ->grab_focus();
2196 $config ->{dnsserver} = $dnsserver ;
2198 #print "TEST $ipaddress $netmask $gateway $dnsserver\n ";
2204 $hostentry ->grab_focus();
2207 sub create_ack_view {
2211 my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
2212 my $ack_html = "${proxmox_libdir}/html/ $steps [ $step_number ]->{html}";
2213 my $html_data = file_get_contents( $ack_template );
2215 my %config_values = (
2216 __target_hd__ => join(' | ', @{ $config_options ->{target_hds}}),
2217 __target_fs__ => $config_options ->{filesys},
2218 __country__ => $cmap ->{country}->{ $country }->{name},
2219 __timezone__ => $timezone,
2220 __keymap__ => $keymap,
2221 __mailto__ => $mailto,
2222 __interface__ => $ipconf ->{ifaces}->{ $ipconf ->{selected}}->{name},
2223 __hostname__ => $hostname,
2224 __ip__ => $ipaddress,
2225 __netmask__ => $netmask,
2226 __gateway__ => $gateway,
2227 __dnsserver__ => $dnsserver,
2230 while ( my ( $k, $v ) = each %config_values ) {
2231 $html_data =~ s/$k/$v/g;
2234 write_config( $html_data, $ack_html );
2238 set_next(undef, sub {
2240 create_extract_view();
2244 sub get_device_desc {
2245 my ( $devname, $size, $model ) = @_ ;
2247 if ( $size && ( $size > 0)) {
2248 $size = int( $size/2048 ); # size in MB, from 512B "sectors"
2250 my $text = " $devname (";
2251 if ( $size >= 1024) {
2252 $size = int( $size/1024 ); # size in GB
2253 $text .= "${size}GB";
2255 $text .= "${size}MB";
2258 $text .= ", $model " if $model ;
2267 my ( $cb, $kmap ) = @_ ;
2272 my $kmaphash = $cmap ->{kmaphash};
2273 foreach my $layout (sort keys %$kmaphash ) {
2274 $def = $i if $kmaphash ->{ $layout } eq 'en-us';
2275 $ind = $i if $kmap && $kmaphash ->{ $layout } eq $kmap ;
2279 $cb ->set_active( $ind || $def || 0);
2283 sub update_zonelist {
2284 my ( $box, $cc ) = @_ ;
2286 my $cczones = $cmap ->{cczones};
2287 my $zones = $cmap ->{zones};
2291 $sel = $lastzonecb ->get_active_text();
2292 $box ->remove ( $lastzonecb );
2294 $sel = $timezone ; # used once to select default
2297 my $cb = $lastzonecb = Gtk3::ComboBoxText->new();
2298 $cb ->set_size_request(200, -1);
2300 $cb ->signal_connect('changed' => sub {
2301 $timezone = $cb ->get_active_text();
2305 if ( $cc && defined ( $cczones ->{ $cc })) {
2306 @za = keys %{ $cczones ->{ $cc }};
2312 foreach my $zone (sort @za ) {
2313 $ind = $i if $sel && $zone eq $sel ;
2314 $cb ->append_text( $zone );
2318 $cb ->set_active( $ind || 0);
2321 $box ->pack_start( $cb, 0, 0, 0);
2324 sub create_password_view {
2328 my $vbox2 = Gtk3::VBox->new(0, 0);
2329 $inbox ->pack_start( $vbox2, 1, 0, 0);
2330 my $vbox = Gtk3::VBox->new(0, 0);
2331 $vbox2 ->pack_start( $vbox, 0, 0, 10);
2333 my $hbox1 = Gtk3::HBox->new(0, 0);
2334 my $label = Gtk3::Label->new("Password");
2335 $label ->set_size_request(150, -1);
2336 $label ->set_alignment(1, 0.5);
2337 $hbox1 ->pack_start( $label, 0, 0, 10);
2338 my $pwe1 = Gtk3::Entry->new();
2339 $pwe1 ->set_visibility(0);
2340 $pwe1 ->set_text( $password ) if $password ;
2341 $pwe1 ->set_size_request(200, -1);
2342 $hbox1 ->pack_start( $pwe1, 0, 0, 0);
2344 my $hbox2 = Gtk3::HBox->new(0, 0);
2345 $label = Gtk3::Label->new("Confirm");
2346 $label ->set_size_request(150, -1);
2347 $label ->set_alignment(1, 0.5);
2348 $hbox2 ->pack_start( $label, 0, 0, 10);
2349 my $pwe2 = Gtk3::Entry->new();
2350 $pwe2 ->set_visibility(0);
2351 $pwe2 ->set_text( $password ) if $password ;
2352 $pwe2 ->set_size_request(200, -1);
2353 $hbox2 ->pack_start( $pwe2, 0, 0, 0);
2355 my $hbox3 = Gtk3::HBox->new(0, 0);
2356 $label = Gtk3::Label->new("E-Mail");
2357 $label ->set_size_request(150, -1);
2358 $label ->set_alignment(1, 0.5);
2359 $hbox3 ->pack_start( $label, 0, 0, 10);
2360 my $eme = Gtk3::Entry->new();
2361 $eme ->set_size_request(200, -1);
2362 $eme ->set_text( $mailto );
2363 $hbox3 ->pack_start( $eme, 0, 0, 0);
2366 $vbox ->pack_start( $hbox1, 0, 0, 5);
2367 $vbox ->pack_start( $hbox2, 0, 0, 5);
2368 $vbox ->pack_start( $hbox3, 0, 0, 15);
2374 set_next (undef, sub {
2376 my $t1 = $pwe1 ->get_text;
2377 my $t2 = $pwe2 ->get_text;
2379 if (length ( $t1 ) < 5) {
2380 display_message("Password is too short.");
2381 $pwe1 ->grab_focus();
2386 display_message("Password does not match.");
2387 $pwe1 ->grab_focus();
2391 my $t3 = $eme ->get_text;
2392 if ( $t3 !~ m/^\S+\@\S+\.\S+$/) {
2393 display_message("E-Mail does not look like a valid address" .
2394 " (user\ @domain .tld)");
2399 if ( $t3 eq 'mail @example .invalid') {
2400 display_message("Please enter a valid E-Mail address");
2409 create_ipconf_view();
2412 $pwe1 ->grab_focus();
2416 sub create_country_view {
2420 my $countryhash = $cmap ->{countryhash};
2421 my $ctr = $cmap ->{country};
2423 my $vbox2 = Gtk3::VBox->new(0, 0);
2424 $inbox ->pack_start( $vbox2, 1, 0, 0);
2425 my $vbox = Gtk3::VBox->new(0, 0);
2426 $vbox2 ->pack_start( $vbox, 0, 0, 10);
2428 my $w = Gtk3::Entry->new();
2429 $w ->set_size_request(200, -1);
2431 my $c = Gtk3::EntryCompletion->new();
2432 $c ->set_text_column(0);
2433 $c ->set_minimum_key_length(0);
2434 $c ->set_popup_set_width(1);
2435 $c ->set_inline_completion(1);
2437 my $hbox2 = Gtk3::HBox->new(0, 0);
2438 my $label = Gtk3::Label->new("Time zone");
2439 $label ->set_size_request(150, -1);
2440 $label ->set_alignment(1, 0.5);
2441 $hbox2 ->pack_start( $label, 0, 0, 10);
2442 update_zonelist ( $hbox2 );
2444 my $hbox3 = Gtk3::HBox->new(0, 0);
2445 $label = Gtk3::Label->new("Keyboard Layout");
2446 $label ->set_size_request(150, -1);
2447 $label ->set_alignment(1, 0.5);
2448 $hbox3 ->pack_start( $label, 0, 0, 10);
2450 my $kmapcb = Gtk3::ComboBoxText->new();
2451 $kmapcb ->set_size_request (200, -1);
2452 foreach my $layout (sort keys %{ $cmap ->{kmaphash}}) {
2453 $kmapcb ->append_text ( $layout );
2456 update_layout( $kmapcb );
2457 $hbox3 ->pack_start ( $kmapcb, 0, 0, 0);
2459 $kmapcb ->signal_connect ('changed' => sub {
2460 my $sel = $kmapcb ->get_active_text();
2461 if (my $kmap = $cmap ->{kmaphash}->{ $sel }) {
2462 my $xkmap = $cmap ->{kmap}->{ $kmap }->{x11};
2463 my $xvar = $cmap ->{kmap}->{ $kmap }->{x11var};
2464 syscmd ("setxkbmap $xkmap $xvar ") if ! $opt_testmode ;
2469 $w ->signal_connect ('changed' => sub {
2470 my ( $entry, $event ) = @_ ;
2471 my $text = $entry ->get_text;
2473 if (my $cc = $countryhash ->{lc( $text )}) {
2474 update_zonelist( $hbox2, $cc );
2475 my $kmap = $ctr ->{ $cc }->{kmap} || 'en-us';
2476 update_layout( $kmapcb, $kmap );
2480 $w ->signal_connect (key_press_event => sub {
2481 my ( $entry, $event ) = @_ ;
2482 my $text = $entry ->get_text;
2484 my $val = $event ->get_keyval;
2486 if ( $val == Gtk3::Gdk::KEY_Tab) {
2487 my $cc = $countryhash ->{lc( $text )};
2494 $compl = $ctr ->{ $cc }->{name};
2496 foreach my $cc (keys %$ctr ) {
2497 my $ct = $ctr ->{ $cc }->{name};
2498 if ( $ct =~ m/^\Q$text\E.*$/i) {
2507 $entry ->set_text( $compl );
2512 print chr(7); # beep ?
2517 my $buf = $w ->get_buffer();
2518 $buf ->insert_text(-1, '', -1); # popup selection
2526 my $ls = Gtk3::ListStore->new('Glib::String');
2527 foreach my $cc (sort { $ctr ->{ $a }->{name} cmp $ctr ->{ $b }->{name} } keys %$ctr ) {
2528 my $iter = $ls ->append();
2529 $ls ->set ( $iter, 0, $ctr ->{ $cc }->{name});
2531 $c ->set_model ( $ls );
2533 $w ->set_completion ( $c );
2535 my $hbox = Gtk3::HBox->new(0, 0);
2537 $label = Gtk3::Label->new("Country");
2538 $label ->set_alignment(1, 0.5);
2539 $label ->set_size_request(150, -1);
2540 $hbox ->pack_start( $label, 0, 0, 10);
2541 $hbox ->pack_start( $w, 0, 0, 0);
2543 $vbox ->pack_start( $hbox, 0, 0, 5);
2544 $vbox ->pack_start( $hbox2, 0, 0, 5);
2545 $vbox ->pack_start( $hbox3, 0, 0, 5);
2547 if ( $country && $ctr ->{ $country }) {
2548 $w ->set_text ( $ctr ->{ $country }->{name});
2554 set_next (undef, sub {
2556 my $text = $w ->get_text;
2558 if (my $cc = $countryhash ->{lc( $text )}) {
2561 create_password_view();
2564 display_message("Please select a country first.");
2572 my $target_hd_combo ;
2573 my $target_hd_label ;
2575 my $hdoption_first_setup = 1;
2577 my $create_basic_grid = sub {
2578 my $grid = Gtk3::Grid->new();
2579 $grid ->set_visible(1);
2580 $grid ->set_column_spacing(10);
2581 $grid ->set_row_spacing(10);
2582 $grid ->set_hexpand(1);
2584 $grid ->set_margin_start(5);
2585 $grid ->set_margin_end(5);
2586 $grid ->set_margin_top(5);
2587 $grid ->set_margin_bottom(5);
2592 my $create_label_widget_grid = sub {
2593 my ( $labeled_widgets ) = @_ ;
2595 my $grid = & $create_basic_grid ();
2598 for (my $i = 0; $i < @$labeled_widgets ; $i += 2) {
2599 my $widget = @$labeled_widgets [ $i+1 ];
2600 my $label = Gtk3::Label->new( @$labeled_widgets [ $i ]);
2601 $label ->set_visible(1);
2602 $label ->set_alignment (1, 0.5);
2603 $grid ->attach( $label, 0, $row, 1, 1);
2604 $widget ->set_visible(1);
2605 $grid ->attach( $widget, 1, $row, 1, 1);
2612 my $create_raid_disk_grid = sub {
2613 my $disk_labeled_widgets = [];
2614 for (my $i = 0; $i < @$hds ; $i++ ) {
2615 my $disk_selector = Gtk3::ComboBoxText->new();
2616 $disk_selector ->append_text("-- do not use --");
2617 $disk_selector ->set_active(0);
2618 $disk_selector ->set_visible(1);
2619 foreach my $hd ( @$hds ) {
2620 my ( $disk, $devname, $size, $model ) = @$hd ;
2621 $disk_selector ->append_text(get_device_desc ( $devname, $size, $model ));
2622 $disk_selector ->{pve_disk_id} = $i ;
2623 $disk_selector ->signal_connect (changed => sub {
2625 my $diskid = $w ->{pve_disk_id};
2626 my $a = $w ->get_active - 1;
2627 $config_options ->{"disksel${diskid}"} = ( $a >= 0) ? $hds ->[ $a ] : undef;
2631 if ( $hdoption_first_setup ) {
2632 $disk_selector ->set_active ( $i+1 ) if $hds ->[ $i ];
2635 if (my $cur_hd = $config_options ->{"disksel $i "}) {
2636 foreach my $hd ( @$hds ) {
2637 if ( @$hd [1] eq @$cur_hd [1]) {
2638 $disk_selector ->set_active( $hdind+1 );
2646 push @$disk_labeled_widgets, "Harddisk $i ", $disk_selector ;
2649 my $scrolled_window = Gtk3::ScrolledWindow->new();
2650 $scrolled_window ->set_hexpand(1);
2651 $scrolled_window ->set_propagate_natural_height(1) if @$hds > 4;
2652 $scrolled_window ->add(& $create_label_widget_grid ( $disk_labeled_widgets ));
2653 $scrolled_window ->set_policy('never', 'automatic');
2655 return $scrolled_window ;
2656 # & $create_label_widget_grid ( $disk_labeled_widgets )
2659 # shared between different ui parts (e.g., ZFS and "normal" single disk FS)
2660 my $hdsize_size_adj ;
2661 my $hdsize_entry_buffer ;
2663 my $get_hdsize_spinbtn = sub {
2666 $hdsize_entry_buffer //= Gtk3::EntryBuffer->new(undef, 1);
2668 if (defined( $hdsize )) {
2669 $hdsize_size_adj = Gtk3::Adjustment->new( $config_options ->{hdsize} || $hdsize, 0, $hdsize+1, 1, 1, 1);
2671 die "called get_hdsize_spinbtn with \ $hdsize_size_adj not defined but did not pass hdsize! \n "
2672 if !defined( $hdsize_size_adj );
2675 my $spinbutton_hdsize = Gtk3::SpinButton->new( $hdsize_size_adj, 1, 1);
2676 $spinbutton_hdsize ->set_buffer( $hdsize_entry_buffer );
2677 $spinbutton_hdsize ->set_adjustment( $hdsize_size_adj );
2678 $spinbutton_hdsize ->set_tooltip_text("only use specified size (GB) of the harddisk (rest left unpartitioned)");
2679 return $spinbutton_hdsize ;
2682 my $create_raid_advanced_grid = sub {
2683 my $labeled_widgets = [];
2684 my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
2685 $spinbutton_ashift ->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
2686 $spinbutton_ashift ->signal_connect ("value-changed" => sub {
2688 $config_options ->{ashift} = $w ->get_value_as_int();
2690 $config_options ->{ashift} = 12 if ! defined( $config_options ->{ashift});
2691 $spinbutton_ashift ->set_value( $config_options ->{ashift});
2692 push @$labeled_widgets, "ashift";
2693 push @$labeled_widgets, $spinbutton_ashift ;
2695 my $combo_compress = Gtk3::ComboBoxText->new();
2696 $combo_compress ->set_tooltip_text("zfs compression algorithm for rpool dataset");
2697 # note: gzip / lze not allowed for bootfs vdevs
2698 my $comp_opts = ["on","off","lzjb","lz4"];
2699 foreach my $opt ( @$comp_opts ) {
2700 $combo_compress ->append( $opt, $opt );
2702 $config_options ->{compress} = "on" if !defined( $config_options ->{compress});
2703 $combo_compress ->set_active_id( $config_options ->{compress});
2704 $combo_compress ->signal_connect (changed => sub {
2706 $config_options ->{compress} = $w ->get_active_text();
2708 push @$labeled_widgets, "compress";
2709 push @$labeled_widgets, $combo_compress ;
2711 my $combo_checksum = Gtk3::ComboBoxText->new();
2712 $combo_checksum ->set_tooltip_text("zfs checksum algorithm for rpool dataset");
2713 my $csum_opts = ["on", "off","fletcher2", "fletcher4", "sha256"];
2714 foreach my $opt ( @$csum_opts ) {
2715 $combo_checksum ->append( $opt, $opt );
2717 $config_options ->{checksum} = "on" if !( $config_options ->{checksum});
2718 $combo_checksum ->set_active_id( $config_options ->{checksum});
2719 $combo_checksum ->signal_connect (changed => sub {
2721 $config_options ->{checksum} = $w ->get_active_text();
2723 push @$labeled_widgets, "checksum";
2724 push @$labeled_widgets, $combo_checksum ;
2726 my $spinbutton_copies = Gtk3::SpinButton->new_with_range(1,3,1);
2727 $spinbutton_copies ->set_tooltip_text("zfs copies property for rpool dataset (in addition to RAID redundancy!)");
2728 $spinbutton_copies ->signal_connect ("value-changed" => sub {
2730 $config_options ->{copies} = $w ->get_value_as_int();
2732 $config_options ->{copies} = 1 if !defined( $config_options ->{copies});
2733 $spinbutton_copies ->set_value( $config_options ->{copies});
2734 push @$labeled_widgets, "copies", $spinbutton_copies ;
2736 push @$labeled_widgets, "hdsize", $get_hdsize_spinbtn ->();
2737 return & $create_label_widget_grid ( $labeled_widgets );;
2740 sub create_hdoption_view {
2742 my $dialog = Gtk3::Dialog->new();
2744 $dialog ->set_title("Harddisk options");
2746 $dialog ->add_button("_OK", 1);
2748 my $contarea = $dialog ->get_content_area();
2750 my $hbox2 = Gtk3::Box->new('horizontal', 0);
2751 $contarea ->pack_start( $hbox2, 1, 1, 10);
2753 my $grid = Gtk3::Grid->new();
2754 $grid ->set_column_spacing(10);
2755 $grid ->set_row_spacing(10);
2757 $hbox2 ->pack_start( $grid, 1, 0, 10);
2763 my $label0 = Gtk3::Label->new("Filesystem");
2764 $label0 ->set_alignment (1, 0.5);
2765 $grid ->attach( $label0, 0, $row, 1, 1);
2767 my $fstypecb = Gtk3::ComboBoxText->new();
2769 my $fstype = ['ext3', 'ext4', 'xfs',
2770 'zfs (RAID0)', 'zfs (RAID1)',
2771 'zfs (RAID10)', 'zfs (RAIDZ-1)',
2772 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
2774 push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
2775 if $setup ->{enable_btrfs};
2778 foreach my $tmp ( @$fstype ) {
2779 $fstypecb ->append_text( $tmp );
2780 $fstypecb ->set_active ( $tcount )
2781 if $config_options ->{filesys} eq $tmp ;
2785 $grid ->attach( $fstypecb, 1, $row, 1, 1);
2791 my $sep = Gtk3::HSeparator->new();
2792 $sep ->set_visible(1);
2793 $grid ->attach( $sep, 0, $row, 2, 1);
2796 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.");
2797 $hw_raid_note ->set_line_wrap(1);
2798 $hw_raid_note ->set_max_width_chars(30);
2799 # $hw_raid_note ->set_size_request(150, -1);
2800 $hw_raid_note ->set_visible(0);
2801 $grid ->attach( $hw_raid_note, 0, $row++, 2, 1);
2803 my $hdsize_labeled_widgets = [];
2807 if ( -b $target_hd ) {
2808 $hdsize = int(hd_size ( $target_hd ) / (1024*1024.0)); # size in GB
2809 } elsif ( $target_hd ) {
2810 $hdsize = int((-s $target_hd ) / (1024*1024*1024.0));
2813 my $spinbutton_hdsize = $get_hdsize_spinbtn ->( $hdsize );
2814 push @$hdsize_labeled_widgets, "hdsize", $spinbutton_hdsize ;
2816 my $entry_swapsize = Gtk3::Entry->new();
2817 $entry_swapsize ->set_tooltip_text("maximum SWAP size (GB)");
2818 $entry_swapsize ->signal_connect (key_press_event => \&check_float);
2819 $entry_swapsize ->set_text( $config_options ->{swapsize}) if defined( $config_options ->{swapsize});
2820 push @$hdsize_labeled_widgets, "swapsize", $entry_swapsize ;
2822 my $entry_maxroot = Gtk3::Entry->new();
2823 if ( $setup ->{product} eq 'pve') {
2824 $entry_maxroot ->set_tooltip_text("maximum size (GB) for LVM root volume");
2825 $entry_maxroot ->signal_connect (key_press_event => \&check_float);
2826 $entry_maxroot ->set_text( $config_options ->{maxroot}) if $config_options ->{maxroot};
2827 push @$hdsize_labeled_widgets, "maxroot", $entry_maxroot ;
2830 my $entry_minfree = Gtk3::Entry->new();
2831 $entry_minfree ->set_tooltip_text("minimum free LVM space (GB, required for LVM snapshots)");
2832 $entry_minfree ->signal_connect (key_press_event => \&check_float);
2833 $entry_minfree ->set_text( $config_options ->{minfree}) if defined( $config_options ->{minfree});
2834 push @$hdsize_labeled_widgets, "minfree", $entry_minfree ;
2837 if ( $setup ->{product} eq 'pve') {
2838 $entry_maxvz = Gtk3::Entry->new();
2839 $entry_maxvz ->set_tooltip_text("maximum size (GB) for LVM data volume");
2840 $entry_maxvz ->signal_connect (key_press_event => \&check_float);
2841 $entry_maxvz ->set_text( $config_options ->{maxvz}) if defined( $config_options ->{maxvz});
2842 push @$hdsize_labeled_widgets, "maxvz", $entry_maxvz ;
2845 my $options_stack = Gtk3::Stack->new();
2846 $options_stack ->set_visible(1);
2847 $options_stack ->set_hexpand(1);
2848 $options_stack ->set_vexpand(1);
2849 $options_stack ->add_titled(& $create_raid_disk_grid (), "raiddisk", "Disk Setup");
2850 $options_stack ->add_titled(& $create_label_widget_grid ( $hdsize_labeled_widgets ), "hdsize", "Size Options");
2851 $options_stack ->add_titled(& $create_raid_advanced_grid ("zfs"), "raidzfsadvanced", "Advanced Options");
2852 $options_stack ->set_visible_child_name("raiddisk");
2853 my $options_stack_switcher = Gtk3::StackSwitcher->new();
2854 $options_stack_switcher ->set_halign('center');
2855 $options_stack_switcher ->set_stack( $options_stack );
2856 $grid ->attach( $options_stack_switcher, 0, $row, 2, 1);
2858 $grid ->attach( $options_stack, 0, $row, 2, 1);
2861 $hdoption_first_setup = 0;
2863 my $switch_view = sub {
2864 my $raid = $config_options ->{filesys} =~ m/zfs|btrfs/;
2865 my $enable_zfs_opts = $config_options ->{filesys} =~ m/zfs/;
2867 $target_hd_combo ->set_visible(! $raid );
2868 $options_stack ->get_child_by_name("hdsize")->set_visible(! $raid );
2869 $options_stack ->get_child_by_name("raiddisk")->set_visible( $raid );
2870 $hw_raid_note ->set_visible( $raid );
2871 $options_stack_switcher ->set_visible( $enable_zfs_opts );
2872 $options_stack ->get_child_by_name("raidzfsadvanced")->set_visible( $enable_zfs_opts );
2874 $target_hd_label ->set_text("Target: $config_options ->{filesys} ");
2875 $options_stack ->set_visible_child_name("raiddisk");
2877 $target_hd_label ->set_text("Target Harddisk: ");
2879 my (undef, $pref_width ) = $dialog ->get_preferred_width();
2880 my (undef, $pref_height ) = $dialog ->get_preferred_height();
2881 $pref_height = 750 if $pref_height > 750;
2882 $dialog ->resize( $pref_width, $pref_height );
2887 $fstypecb ->signal_connect (changed => sub {
2888 $config_options ->{filesys} = $fstypecb ->get_active_text();
2896 my $get_float = sub {
2899 my $text = $entry ->get_text();
2900 return undef if !defined( $text );
2905 return undef if $text !~ m/^\d+(\.\d+)?$/;
2912 if (( $tmp = & $get_float ( $spinbutton_hdsize )) && ( $tmp != $hdsize )) {
2913 $config_options ->{hdsize} = $tmp ;
2915 delete $config_options ->{hdsize};
2918 if (defined( $tmp = & $get_float ( $entry_swapsize ))) {
2919 $config_options ->{swapsize} = $tmp ;
2921 delete $config_options ->{swapsize};
2924 if (defined( $tmp = & $get_float ( $entry_maxroot ))) {
2925 $config_options ->{maxroot} = $tmp ;
2927 delete $config_options ->{maxroot};
2930 if (defined( $tmp = & $get_float ( $entry_minfree ))) {
2931 $config_options ->{minfree} = $tmp ;
2933 delete $config_options ->{minfree};
2936 if ( $entry_maxvz && defined( $tmp = & $get_float ( $entry_maxvz ))) {
2937 $config_options ->{maxvz} = $tmp ;
2939 delete $config_options ->{maxvz};
2945 my $get_raid_devlist = sub {
2947 my $dev_name_hash = {};
2950 for (my $i = 0; $i < @$hds ; $i++ ) {
2951 if (my $hd = $config_options ->{"disksel $i "}) {
2952 my ( $disk, $devname, $size, $model ) = @$hd ;
2953 die "device ' $devname ' is used more than once \n "
2954 if $dev_name_hash ->{ $devname };
2955 $dev_name_hash ->{ $devname } = $hd ;
2956 push @$devlist, $hd ;
2963 sub zfs_mirror_size_check {
2964 my ( $expected, $actual ) = @_ ;
2966 die "mirrored disks must have same size \n "
2967 if abs( $expected - $actual ) > $expected / 10;
2970 sub get_zfs_raid_setup {
2972 my $filesys = $config_options ->{filesys};
2974 my $devlist = & $get_raid_devlist ();
2976 my $diskcount = scalar( @$devlist );
2977 die " $filesys needs at least one device \n " if $diskcount < 1;
2979 my $bootdevlist = [];
2982 if ( $filesys eq 'zfs (RAID0)') {
2983 push @$bootdevlist, @$devlist [0];
2984 foreach my $hd ( @$devlist ) {
2987 } elsif ( $filesys eq 'zfs (RAID1)') {
2988 die "zfs (RAID1) needs at least 2 device \n " if $diskcount < 2;
2990 my $hd = @$devlist [0];
2991 my $expected_size = @$hd [2]; # all disks need approximately same size
2992 foreach $hd ( @$devlist ) {
2993 zfs_mirror_size_check( $expected_size, @$hd [2]);
2995 push @$bootdevlist, $hd ;
2997 } elsif ( $filesys eq 'zfs (RAID10)') {
2998 die "zfs (RAID10) needs at least 4 device \n " if $diskcount < 4;
2999 die "zfs (RAID10) needs an even number of devices \n " if $diskcount & 1;
3001 push @$bootdevlist, @$devlist [0], @$devlist [1];
3003 for (my $i = 0; $i < $diskcount ; $i+ =2) {
3004 my $hd1 = @$devlist [ $i ];
3005 my $hd2 = @$devlist [ $i+1 ];
3006 zfs_mirror_size_check( @$hd1 [2], @$hd2 [2]); # pairs need approximately same size
3007 $cmd .= ' mirror ' . @$hd1 [1] . ' ' . @$hd2 [1];
3010 } elsif ( $filesys =~ m/^zfs \(RAIDZ-([123])\)$/) {
3012 my $mindisks = 2 + $level ;
3013 die "zfs (RAIDZ- $level ) needs at least $mindisks devices \n " if scalar( @$devlist ) < $mindisks ;
3014 my $hd = @$devlist [0];
3015 my $expected_size = @$hd [2]; # all disks need approximately same size
3016 $cmd .= " raidz $level ";
3017 foreach $hd ( @$devlist ) {
3018 zfs_mirror_size_check( $expected_size, @$hd [2]);
3020 push @$bootdevlist, $hd ;
3023 die "unknown zfs mode ' $filesys ' \n ";
3026 return ( $devlist, $bootdevlist, $cmd );
3029 sub get_btrfs_raid_setup {
3031 my $filesys = $config_options ->{filesys};
3033 my $devlist = & $get_raid_devlist ();
3035 my $diskcount = scalar( @$devlist );
3036 die " $filesys needs at least one device \n " if $diskcount < 1;
3040 if ( $diskcount == 1) {
3043 if ( $filesys eq 'btrfs (RAID0)') {
3045 } elsif ( $filesys eq 'btrfs (RAID1)') {
3046 die "btrfs (RAID1) needs at least 2 device \n " if $diskcount < 2;
3048 } elsif ( $filesys eq 'btrfs (RAID10)') {
3049 die "btrfs (RAID10) needs at least 4 device \n " if $diskcount < 4;
3052 die "unknown btrfs mode ' $filesys ' \n ";
3056 return ( $devlist, $mode );
3059 my $last_hd_selected = 0;
3060 sub create_hdsel_view {
3062 $prev_btn ->set_sensitive(1); # enable previous button at this point
3066 my $vbox = Gtk3::VBox->new(0, 0);
3067 $inbox ->pack_start( $vbox, 1, 0, 0);
3068 my $hbox = Gtk3::HBox->new(0, 0);
3069 $vbox ->pack_start( $hbox, 0, 0, 10);
3071 my ( $disk, $devname, $size, $model ) = @{ @$hds [0]};
3072 $target_hd = $devname if !defined( $target_hd );
3074 $target_hd_label = Gtk3::Label->new("Target Harddisk: ");
3075 $hbox ->pack_start( $target_hd_label, 0, 0, 0);
3077 $target_hd_combo = Gtk3::ComboBoxText->new();
3079 foreach my $hd ( @$hds ) {
3080 ( $disk, $devname, $size, $model ) = @$hd ;
3081 $target_hd_combo ->append_text (get_device_desc( $devname, $size, $model ));
3084 my $raid = $config_options ->{filesys} =~ m/zfs|btrfs/;
3086 $target_hd_label ->set_text("Target: $config_options ->{filesys} ");
3087 $target_hd_combo ->set_visible(0);
3088 $target_hd_combo ->set_no_show_all(1);
3090 $target_hd_combo ->set_active( $last_hd_selected );
3091 $target_hd_combo ->signal_connect(changed => sub {
3092 $a = shift->get_active;
3093 my ( $disk, $devname ) = @{ @$hds [ $a ]};
3094 $last_hd_selected = $a ;
3095 $target_hd = $devname ;
3098 $hbox ->pack_start( $target_hd_combo, 0, 0, 10);
3100 my $options = Gtk3::Button->new('_Options');
3101 $options ->signal_connect (clicked => \&create_hdoption_view);
3102 $hbox ->pack_start ( $options, 0, 0, 0);
3109 set_next(undef, sub {
3111 if ( $config_options ->{filesys} =~ m/zfs/) {
3112 my ( $devlist ) = eval { get_zfs_raid_setup() };
3114 display_message("Warning: $err\nPlease fix ZFS setup first.");
3117 $config_options ->{target_hds} = [ map { $_ ->[1] } @$devlist ];
3118 } elsif ( $config_options ->{filesys} =~ m/btrfs/) {
3119 my ( $devlist ) = eval { get_btrfs_raid_setup() };
3121 display_message("Warning: $err\nPlease fix BTRFS setup first.");
3124 $config_options ->{target_hds} = [ map { $_ ->[1] } @$devlist ];
3126 $config_options ->{target_hds} = [ $target_hd ];
3130 create_country_view();
3134 sub create_extract_view {
3140 $next ->set_sensitive(0);
3141 $prev_btn ->set_sensitive(0);
3144 my $vbox = Gtk3::VBox->new(0, 0);
3145 $inbox ->pack_start ( $vbox, 1, 0, 0);
3146 my $hbox = Gtk3::HBox->new(0, 0);
3147 $vbox ->pack_start ( $hbox, 0, 0, 10);
3149 my $vbox2 = Gtk3::VBox->new(0, 0);
3150 $hbox ->pack_start ( $vbox2, 0, 0, 0);
3152 $progress_status = Gtk3::Label->new('');
3153 $vbox2 ->pack_start ( $progress_status, 1, 1, 0);
3155 $progress = Gtk3::ProgressBar->new;
3156 $progress ->set_show_text(1);
3157 $progress ->set_size_request (600, -1);
3159 $vbox2 ->pack_start( $progress, 0, 0, 0);
3163 my $tdir = $opt_testmode ? "target" : "/target";
3165 my $base = "${proxmox_cddir}/ $setup ->{product}-base.squashfs";
3167 eval { extract_data( $base, $tdir ); };
3170 $next ->set_sensitive(1);
3172 set_next("_Reboot", sub { exit (0); } );
3175 display_html("fail.htm");
3176 display_error( $err );
3179 display_html("success.htm");
3183 sub create_intro_view {
3185 $prev_btn ->set_sensitive(0);
3189 if ( $setup ->{product} eq 'pve') {
3191 my $cpuinfo = file_get_contents('/proc/cpuinfo');
3192 if ( $cpuinfo && !( $cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
3193 display_error("No support for KVM virtualisation detected. \n\n " .
3194 "Check BIOS settings for Intel VT / AMD-V / SVM.")
3202 set_next("I a_gree", \&create_hdsel_view);
3205 $ipconf = get_ip_config();
3207 $country = detect_country() if $ipconf ->{default} || $opt_testmode ;
3209 # read country, kmap and timezone infos
3210 $cmap = read_cmap();
3212 if (!defined( $cmap ->{country}->{ $country })) {
3213 print $logfd "ignoring detected country ' $country ', invalid or unknown \n ";
3217 create_main_window ();
3219 my $initial_error = 0;
3221 if (!defined ( $hds ) || (scalar ( @$hds ) <= 0)) {
3222 print "no hardisks found \n ";
3224 display_html("nohds.htm");
3225 set_next("Reboot", sub { exit(0); } );
3227 foreach my $hd ( @$hds ) {
3228 my ( $disk, $devname ) = @$hd ;
3229 next if $devname =~ m|^/dev/md\d+$|;
3230 print "found Disk $disk N: $devname\n ";
3234 if (! $initial_error && (scalar keys %{ $ipconf ->{ifaces} } == 0)) {
3235 print "no network interfaces found \n ";
3237 display_html("nonics.htm");
3238 set_next("Reboot", sub { exit(0); } );
3241 create_intro_view () if ! $initial_error ;