#!/bin/sh
eval 'if [ -x /usr/local/cpanel/3rdparty/bin/perl ]; then exec /usr/local/cpanel/3rdparty/bin/perl -x -- $0 ${1+"$@"}; else exec /usr/bin/perl -x $0 ${1+"$@"}; fi;'
if 0;

#!/usr/bin/perl
#######################################
# cPGS Server Package File (un)Packer
# darren@cpanel.net
#######################################
# This script takes a gzipped tarball named "somefile.cpgs" and finds out the data needed from it for
# the gui installer of cPGS and appends the data to the tarball so that the installer can read
# the data even if the file is renamed.
# It can also take a directory containing a game server installation (vanilla or with mods) and package it
# into a .cpgs file fo easy installation of custom setups for customers.
#######################################

BEGIN {
    unshift( @INC, '/var/cpanel/cpgs', '/var/cpanel/cpgs/games' );
}

use strict;
use Fcntl qw(:DEFAULT :flock);
use cPGSLib;

my $action = shift;
if(!$action) { showusage(); }

my %args;
foreach my $varval ( @ARGV ) {
    my ($var,$val) = split(/=/,$varval,2);
    $args{$var} = $val;
}

#########################################################
#[ Pack the header information into the server package ]#
#########################################################

if($action eq '-p') {

    print "Packing header information into the server file\n\n";

    my $response = cPGSLib::buildpkg(\%args);

    if( $$response{'retcode'} == 1 ) {
        print "Error encountered while trying to build package file: " . $$response{'msg'} . "\n";
        exit;
    }

    foreach my $rep ( keys %{$response} ) {
        print "$rep = $$response{$rep} \n";
    }

    exit;

##############################################
#[  Show header info from package file      ]#
##############################################

} elsif ( $action eq '-s') {

    if( ! $args{'file'} && -f $ARGV[0] ) {
        $args{'file'} = $ARGV[0];
        print "\nAssuming you mean file=$args{'file'}..\n\n";
    } elsif( ! $args{'file'} ) {
        print "\n ! ! ! Error: Unknown file. ! ! !\n\n";
        showusage();
        exit;
    }
    print "Getting cPGS Information from $args{'file'}\n\n";
    my $pkg_ref = cPGSLib::getpkginfo('list',$args{'file'});

    if( $$pkg_ref{'pre'} ) {
        $$pkg_ref{'pre'} =~ s/###/\n/g;
        my $pre_script = cPGSLib::decode_base64($$pkg_ref{'pre'});
        print '-' x 50 . "\nPre install/update script:\n" . '-' x 50 . "[ start ]\n$pre_script\n" . '-' x 50 . "[ end ]\n";
        delete $$pkg_ref{'pre'};
    }
    if( $$pkg_ref{'post'} ) {
        $$pkg_ref{'post'} =~ s/###/\n/g;
        my $post_script = cPGSLib::decode_base64($$pkg_ref{'post'});
        print '-' x 50 . "\nPost install/update script:\n" . '-' x 50 . "[ start ]\n$post_script\n" . '-' x 50 . "[ end ]\n";
        delete $$pkg_ref{'post'};
    }
    foreach my $hdr(keys %{$pkg_ref}) {
        print "$hdr = $$pkg_ref{$hdr}\n";
    }

##############################################
#[  Unpack the file and extract header info ]#
##############################################

} elsif ( $action eq '-u' ) {

    if( ! $args{'file'} && -f $ARGV[0] ) {
        $args{'file'} = $ARGV[0];
        print "\nAssuming you mean file=$args{'file'}..\n\n";
    } elsif( ! $args{'file'} ) {
        print "\n ! ! ! Error: Unknown file. ! ! !\n\n";
        showusage();
        exit;
    }
    print "Removing cPGS meta data from $args{'file'}\n\n";
    my $pkg_ref = cPGSLib::getpkginfo('truncate',$args{'file'});
    if($$pkg_ref{'retcode'} == 0) {
        print "Done, $args{'file'} no longer has meta data appended to it.\n";
        print "The header data was :\n";

        if( $$pkg_ref{'pre'} ) {
            $$pkg_ref{'pre'} =~ s/###/\n/g;
            my $pre_script = cPGSLib::decode_base64($$pkg_ref{'pre'});
            print '-' x 50 . "\nPre install/update script:\n" . '-' x 50 . "[ start ]\n$pre_script\n" . '-' x 50 . "[ end ]\n";
            delete $$pkg_ref{'pre'};
        }
        if( $$pkg_ref{'post'} ) {
            $$pkg_ref{'post'} =~ s/###/\n/g;
            my $post_script = cPGSLib::decode_base64($$pkg_ref{'post'});
            print '-' x 50 . "\nPost install/update script:\n" . '-' x 50 . "[ start ]\n$post_script\n" . '-' x 50 . "[ end ]\n";
            delete $$pkg_ref{'post'};
        }
        foreach my $hdr(keys %{$pkg_ref}) {
            print "$hdr = $$pkg_ref{$hdr}\n";
        }

    } else {
        print "Could not truncate $args{'file'} properly : $!\n";
    }
    exit;

##############################################
#[  Extract server files                    ]#
##############################################

} elsif ( $action eq '-e' ) {

    if( ! $args{'file'} && -f $ARGV[0] ) {
        $args{'file'} = $ARGV[0];
        print "\nAssuming you mean file=$args{'file'}..\n\n";
    } elsif( ! $args{'file'} ) {
        print "\n ! ! ! Error: Unknown file. ! ! !\n\n";
        showusage();
        exit;
    }
    if( ! $args{'dest'} ) {
        print "\n ! ! ! Error: Unknown destination location, add dest=/path/to/dir where you want package files extracted ! ! !\n\n";
        showusage();
        exit;
    }

    my $rep = cPGSLib::extractpkg(\%args);
    if( $$rep{'pkg_ref'}{'pre'} ) {
        $$rep{'pkg_ref'}{'pre'} =~ s/###/\n/g;
        my $pre_script = cPGSLib::decode_base64($$rep{'pkg_ref'}{'pre'});
        my $pre_file = $$rep{'relative'} != 1 ? "$$rep{'topdir'}/$$rep{'dir'}" . '/.cpgs_pre' : '.cpgs_pre';
        my $omask = umask(0027);
        if( ! -e $pre_file && sysopen( my $pre_fh,$pre_file,&Fcntl::O_WRONLY | &Fcntl::O_TRUNC | &Fcntl::O_CREAT,0777) ) {
            syswrite($pre_fh, $pre_script, length($pre_script));
            close($pre_fh);
            print "Pre install/update script saved to $pre_file\n";
        } else {
            print "Problem writing pre script, either it already exists or : $!\n";
        }
        umask($omask);
        chmod(0750,$pre_file); # just in case
    }
    if( $$rep{'pkg_ref'}{'post'} ) {
        $$rep{'pkg_ref'}{'post'} =~ s/###/\n/g;
        my $post_script = cPGSLib::decode_base64($$rep{'pkg_ref'}{'post'});
        my $post_file = $$rep{'relative'} != 1 ? "$$rep{'topdir'}/$$rep{'dir'}" . '/.cpgs_post' : '.cpgs_post';
        my $omask = umask(0027);
        if( ! -e $post_file && sysopen( my $post_fh,$post_file,&Fcntl::O_WRONLY | &Fcntl::O_TRUNC | &Fcntl::O_CREAT,0777) ) {
            syswrite($post_fh, $post_script, length($post_script));
            close($post_fh);
            print "Post install/update script saved to $post_file\n";
        } else {
            print "Problem writing post script, either it already exists or : $!\n";
        }
        umask($omask);
        chmod(0750,$post_file);
    }

    print $$rep{'msg'};


##############################################
#[  Apply package file                      ]#
##############################################

} elsif ( $action eq '-ap' ) {
    print "Applying patch from $args{'file'} to $args{'dest'}\n ";

    if( ! $args{'file'} && -f $ARGV[0] ) {
        $args{'file'} = $ARGV[0];
        print "\nAssuming you mean file=$args{'file'}..\n\n";
    } elsif( ! $args{'file'} ) {
        print "\n ! ! ! Error: Unknown file. ! ! !\n\n";
        showusage();
        exit;
    }
    if( ! $args{'dest'} ) {
        print "\n ! ! ! Error: Unknown destination location, add dest=/path/to/dir where you want package files extracted ! ! !\n\n";
        showusage();
        exit;
    }
    if( ! -d $args{'dest'} ) {
        print "\n ! ! ! Error: No such directory \"$args{'dest'} ! ! !\"\n\n";
        showusage();
        exit;
    }

    my $pwd;
    if( $args{'file'} !~ m/^\// ) {
        chomp($pwd = `pwd`);
        $args{'file'} = $pwd . '/' . $args{'file'};
    }

    print "Getting cPGS Information from $args{'file'}\n\n";
    my $pkg_ref = cPGSLib::getpkginfo('list',$args{'file'});

    if($$pkg_ref{'type'} ne 'patch') {
        print "\n ! ! ! Error: Patch file $args{'file'} does not appear to be a patch file (type = $$pkg_ref{'type'}) ! ! ! \n";
        exit;
    }

    print "Unpacking pre/post update scripts..\n";

    #~ If running as root and given a user argument, drop to that user before unpacking
    if( $< == 0 ) {
        if( defined($args{'user'}) ) {
            print "Dropping privileges to $args{'user'}.. \n";
            my ( undef, undef, $uid, $gid, undef, undef, undef, $home, undef, undef ) = getpwnam($args{'user'});
            if( defined($uid) and defined($gid) and $home) {
                $) = "$gid $gid";
                $( = "$gid $gid";
                $> = $uid;
                $< = $uid;

               if ( $< != $uid || $( != $gid ) {
                    print "Failed to drop privileges to $uid:$gid : $!\n";
                    exit;
                }
            }
        } else {
            print "Running as root requires a user=<user> argument, I will not run update scripts as root.\n";
            exit;
        }
    }

    #~ Take the pre and post scripts and put into the directory to run before and after unpacking the files

    my $shortrand = cPGSLib::get_rand_string(5); # helps to ID the pre and post scripts are from the same update

    if( ! chdir($args{'dest'})) {
        print "Could not change directory to $args{'dest'} : $!";
        exit;
    }

    if( $$pkg_ref{'pre'} ) {
        $$pkg_ref{'pre'} =~ s/###/\n/g;
        my $pre_script = cPGSLib::decode_base64($$pkg_ref{'pre'});
        if( $pre_script ) {
            my $rand = cPGSLib::get_rand_string(20);
            my $tmp_path = $args{'dest'} .'/.cpgs_pre_' . $shortrand . '-' . $rand . '.pl';
            if(open(my $pre_fh,'>',$tmp_path)) {
                print {$pre_fh} $pre_script;
                close($pre_fh);
                chmod 0755, $tmp_path;
                if( -x $tmp_path ) {
                    print "Running pre update script $tmp_path ..\n";
                    my $ret = system($tmp_path);
                    print "Pre update script exited with status $ret\n";
                }
                unlink($tmp_path) unless $args{'savescripts'};
            } else {
                print "Could not write pre update script to $tmp_path : $!\n";
                exit;
            }
        }
        delete $$pkg_ref{'pre'};
    }

    print "Extracting packaged files..\n";

    system('tar','xzvf',$args{'file'});

    if( $$pkg_ref{'post'} ) {
        $$pkg_ref{'post'} =~ s/###/\n/g;
        my $post_script = cPGSLib::decode_base64($$pkg_ref{'post'});
        if( $post_script ) {
            my $rand = cPGSLib::get_rand_string(20);
            my $tmp_path = $args{'dest'} .'/.cpgs_post_' . $shortrand . '-' . $rand . '.pl';
            if(open(my $post_fh,'>',$tmp_path)) {
                print {$post_fh} $post_script;
                close($post_fh);
                chmod 0755, $tmp_path;
                if( -x $tmp_path ) {
                    print "Running post update script $tmp_path ..\n";
                    my $ret = system($tmp_path);
                    print "Post update script exited with status $ret\n";
                }
                unlink($tmp_path) unless $args{'savescripts'};
            } else {
                print "Could not write post update script to $tmp_path : $!\n";
                exit;
            }
        }
        delete $$pkg_ref{'post'};
    }

    print "Done.\n";

##############################################
#[ Catch-all                                ]#
##############################################

} else {
    showusage();
}

exit;

##############################################
#[ Subroutines                              ]#
##############################################

sub showusage {
    print <<EOH;

Usage:  $0 <action> <options>

    Actions:
        -p Package Server with meta data.

            Options for -p:
                # mandatory data:
                #   itag    - short name of game server such as 'cstrike' or 'ts3'
                #   name    - long name of game server such as 'Counter Strike' or 'Teamspeak 3'
                #   vers    - version of the game server package
                #   gdir    - full path to directory to build package from, such as /home/user/cstrike_maps_maniadmin/
                #   file    - path/filename of resulting package file

                # optional data:
                #   arch    - 32 or 64 for 32bit/64bit binary files
                #   os      - Operating System the package is for, linux, bsd, win (although Windows is in no way supported right now)
                #   desc    - long-ish description of what is in the package, changes made, etc
                #   note    - short description of the package, ideally ~5 or so words
                #   cr_mail - contact email address of the package creator
                #   cr_name - name of person or company/organization that created the package
                #   infourl - URL to website, forum thread, whatever with any extra info related to the package (or just to give credit)
                #   pre     - file name of script to run before extracting package files
                #   post    - file name of script to run after extracting package files

                # Other data may be added if you'd like for internal uses, but will be ignored by cPGS.

                * pre and post scripts are used for updates and handling any special game-specific issues, like backing up config files before unpacking, then
                  moving the configs back in place after the update, or to manipulate a config file to bring it up to spec for a new version of the game. These
                  scripts run as the user, not root, so they won't be able to do everything (like add tmpfs to /etc/fstab and mount /dev/shm.)
                  These scripts can be executed as they are, so use a shebang like #!/usr/bin/perl or #!/bin/sh to let the system know how to run it.
                  Great caution should be taken when allowing use of these scripts from untrusted sources. Use the -s action to see the content of the scripts
                  in the package file before using it.

        -u  Unpack cPGS meta data from server package file (returns it to a regular tarball)
        -s  Show cPGS information in server package file (dumps meta data in no particular order)
        -e  Extract server files to current directory (basically runs `tar xzvf \$file`)
        -ap Apply patch file to update a server install

            Options -u, -s, -e and -ap :
                #   file    - path/filename of game server package file
                #   dest    - full destination for extracted files, something like /home/user/.gameservers/cstrike_test . This script will only create the game directory (cstrike_test in this case)
                              directory if the higher level directory already exists (/home/user/.gameservers) , it will not create the full path.

            Options -ap :
                #   savescripts - Do not delete the pre or post update scripts in the directory it is applied to when savescripts=yes
                #   user        - Username of the account which the game server runs as, only applicable if running update as root, ignored otherwise

    Examples:
        $0 -p itag=cstrike name="Counter Strike" vers="1.6" gdir=/home/testuser/cstrike_with_custom_mods/ file=/home/gameservers/cstrike_mods1.cpgs desc="vanilla CS with mani admin and Arctic map pack"
        $0 -ap file=/path/to/patchfile.cpgs dest=/path/to/gameserver_installtoupdate savescripts=yes user=regularuser
        $0 -s file=cstrike.cpgs
        $0 -u file=cstrike.cpgs
        $0 -e file=cstrike.cpgs dest=/home/user/.gameservers/cstrike_3

EOH

    exit;
}



