Last active
November 11, 2015 20:10
-
-
Save Sap333/364bd3286d7909c21b0f to your computer and use it in GitHub Desktop.
Setup bundle builder. Creates/makes runnable installer (bash script) which contains an installer script (bash) and embedded base64 encoded tar archive.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# I need a job! | |
declare VERSION='1.2' # Script version. | |
function eexit { | |
local EXITCODE=2; [ "$1" -eq "$1" ] 2>/dev/null && EXITCODE="$1" && shift | |
[ $# -ne 0 ] && echo 'Error:' "$@" >&2 | |
exit "$EXITCODE" ; exit 1 | |
} | |
# Print usage inforamtion for bundle making mode. | |
# $1 - exit code. 0 if omitted. | |
function printUsage { | |
echo "\ | |
Makes bash-shell installation bundle. | |
Version $VERSION | |
Usage: | |
$(basename "$(BASH_SOURCE[0])") [OPTIONS] <OUTPUT_BUNDLE_NAME> [INSTALLER_SCRIPT_NAME] | |
$(basename "$(BASH_SOURCE[0])") [OPTIONS] -L <OUTPUT_BUNDLE_NAME> <INSTALLER_SCRIPT_NAME> <FILES>... | |
Possible options: | |
-a <BASH_CODE_LINE> - Add specified line to the beginning of the bundle. This | |
option can be repeated several times. Be carefull about chracter encoding | |
and escaping! | |
-h --help - Print this help. | |
-d <DATE_STR> - Set build date to DATE_STR. Escape $ and \" symbols | |
with a \"\\\" (backslash). The string can be readed from installing script | |
from the \"BUNDLE_DATE\" variable. | |
-e <ENCODING> - If specified then the installer script will be converted from | |
UTF-8 to the specified encoding using \"iconv\" tool. | |
-L - Get file for embedded archive from the command line (FILES items). | |
-v <VERSION_STR> - Set version information to VERSION_STR. Escape $ and \" | |
symbols with a \"\\\" (backslash). The string can be readed from installing | |
script from the \"BUNDLE_NAME\" variable. | |
-V - Verbose output. | |
-x - Use xz-archive (with \"-2\" compression level) instead of gzip (with | |
default compression level). | |
-X - Use xz-archive (with default compression level) instead of gzip (with | |
default compression level). | |
-- - End of options in the command line. | |
Parameters: | |
OUTPUT_BUNDLE_NAME - The name (and path) of the bundle to be created. | |
INSTALLER_SCRIPT_NAME - Installing script wich is run when the bundle starts. | |
Must be a bash script. The script should call bundle_unpack function to | |
get the bundle's content excracted into temporary directory. The directory | |
name will be returned in \"BUNDLE_TMPDIR\" variable. In case of an error | |
function returns non-zero error code. If is omitted then bundle on sart will | |
unpack the embedded archive into a temporary directory and start | |
\"installer.sh\" script | |
In the file-list mode (\"-L\" option) the installer script can not be | |
omitted. | |
FILES - Only for file-list mode (\"-L\" option). The files for the embedded | |
archive. Parameters are passed to tar archiver as files to addd into the | |
embedded archive. | |
Embedded archive: | |
In a file-list mode (\"-L\" option) the embedded archive is made automati- | |
cally. | |
Otherwise (no \"-L\" option), a gzipped or xz-compressed (if \"-x\" or \"-X\" | |
option was specified) TAR-archive for the bundle must be passed to stdin of | |
this script. | |
Notes: | |
The bundle accepts options \"-v\" and \"--version\" and prints the bundle | |
version information. The options are accepted only if version and/or build date | |
was specified for the bundle. | |
The bundle can be unpacked by specifying \"--unpack_bundle\" followed by the | |
target directory. | |
The installation script has to call \"unpack_bundle\" function to unpack | |
the embedded archive. The target directory for unpacking can be specified for | |
the function. If the target directory is not specified then tamporary directory | |
will be created (EXIT trap that deletes the temporary directory is set up in | |
this case). The target directory is written info \"BUNDLE_UNPACK_PATH\" by | |
the unpacking fucntion. | |
Is case of error the unpacking function prints error message end returns | |
a non-zero return code. | |
The version of the bundle is stored in the \"BUNDLE_VERSION\" variable. | |
The build date information of the bundle is stored in the \"BUNDLE_DATE\" | |
variable. | |
The bundle stub also uses \"BUNDLE_USE_XZ\" variable. | |
In file-list mode with \"-x\" option the extrnal \"xz\" tool is used when | |
the bundle is built. " >&2 | |
[ -n $1 ] && exit $1 | |
exit 0 | |
} | |
# Process comannd line parameters. | |
[ "$1" == '-h' ] || [ "$1" == '--help' ] && printUsage 0 | |
[ "$#" -eq 0 ] && eexit 1 "Wrong command line parameters." | |
declare VERBOSE= VERSION_STR= DATE_STR= NAME_STR= ENCODING= USE_XZ_COMPRESSION= | |
declare USE_XZ_DEFAULT_COMPRESSION= | |
declare USE_FILE_LIST= | |
declare -a USER_CODE=( ) | |
while getopts ":a:e:d:hLv:VxX-" opt; do | |
case $opt in | |
a) USER_CODE+=( "$OPTARG" ) ;; | |
e) ENCODING="$OPTARG" ;; | |
d) DATE_STR="$OPTARG" ;; | |
h) printUsage 0 ;; | |
L) USE_FILE_LIST=yes ;; | |
v) VERSION_STR="$OPTARG" ;; | |
V) VERBOSE=1 ;; | |
x) USE_XZ_COMPRESSION='level 2' ; USE_XZ_DEFAULT_COMPRESSION= ;; | |
X) USE_XZ_COMPRESSION=yes ; USE_XZ_DEFAULT_COMPRESSION=1 ;; | |
-) break ;; | |
\?) eexit 1 "Invalid option: \"$OPTARG\"" ;; | |
:) eexit 1 "Option \"-$OPTARG\" requires an argument." ;; | |
esac | |
done | |
shift $((OPTIND - 1)); | |
# Get bundle's name. | |
[ -z "$1" ] && eexit "Bundle file's name is not specified." | |
declare -r BUNDLE_FILE="$1" | |
shift | |
# Process installer script and file list in the command line. | |
if [ -n "$USE_FILE_LIST" ] ;then | |
# We are using file list from command line - the installer script MUST be specified. | |
[ -n "$1" ] || eexit "In file-list mode the installer script must be specified." | |
[ -f "$1" ] || eexit "Installer script \"$1\" is not present." | |
grep -q -m 1 -E '^#!.*/bash[[:space:]]*$' "$1" &>/dev/null \ | |
|| eexit "Installer script \"$1\" is not a bash script." | |
declare -r INST_SCRIPT="$1" | |
shift | |
# Get the file list from the command line. | |
declare -a FILE_LIST=( "$@" ) | |
else | |
# Process the installer script name - it might be omitted. | |
if [ -n "$1" ]; then | |
[ -f "$1" ] || eexit "Installer script \"$1\" is not present." | |
grep -q -m 1 -E '^#!.*/bash[[:space:]]*$' "$1" &>/dev/null \ | |
|| eexit "Installer script \"$1\" is not a bash script." | |
declare -r INST_SCRIPT="$1" | |
shift | |
[ -n "$1" ] && eexit "Unknown command line option \"$1\"" | |
else | |
# Installer script is not specified. | |
declare -r INST_SCRIPT= | |
[ -n "$ENCODING" ] && eexit "The installer script has to be specified if it's encoding is specified." | |
fi | |
fi | |
echo "Creating an installation bundle." | |
[ -n "$VERBOSE" ] && echo "\ | |
Bundle information: | |
Bundle creator version: $VERSION | |
Version: ${VERSION_STR:-Absent} | |
Build date: ${DATE_STR:-Absent} | |
Installer script: \"${INST_SCRIPT:-Use installer.sh from the archive}\" | |
Installer script encoding: ${ENCODING:-none} | |
File-list mode: ${USE_FILE_LIST:-no} | |
Use xz-compression: ${USE_XZ_COMPRESSION:-no} | |
Bundle's name: \"$BUNDLE_FILE\"" | |
# Delete bundle if exists. | |
[ -f "$BUNDLE_FILE" ] && { | |
echo "Bundle file exists - deleting." | |
rm -f "$BUNDLE_FILE" || eexit "Can't delete existing bundle file." | |
} | |
# Creating the bundle. | |
# Remove bundle on script termination. | |
trap " [ -f \"$BUNDLE_FILE\" ] && rm -f \"$BUNDLE_FILE\" &>/dev/null" EXIT | |
# Write the shebang and bundle information. | |
echo "#!/bin/bash | |
# Bash-bundle based installer. | |
BUNDLE_VERSION=\"$VERSION_STR\" | |
BUNDLE_DATE=\"$DATE_STR\" | |
BUNDLE_USE_EMBEDDED_INSTALLER=${INST_SCRIPT:+1} | |
BUNDLE_USE_XZ=${USE_XZ_COMPRESSION:+1}" >"$BUNDLE_FILE" || eexit "Can't create the bundle file." | |
# Add the user code if have any. | |
if [ ${#USER_CODE[@]} -ne 0 ]; then | |
echo " | |
# User code:" >>"$BUNDLE_FILE" || eexit "Can't write into the bundle file." | |
for V in "${USER_CODE[@]}" ; do | |
echo "$V" >>"$BUNDLE_FILE" || eexit "Can't write into the bundle file." | |
done | |
fi | |
echo " | |
########################################################################################################################" \ | |
>>"$BUNDLE_FILE" || eexit "Can't write into the bundle file." | |
# Add this script as a bundle stub (will operate as a stub because "_BUNDLE_STUB_MODE_" varibale is set. | |
grep -F "#"" Bundle stub code." -m 1 -A 1024 "${BASH_SOURCE}" >> "$BUNDLE_FILE" || eexit "Can't write into the bundle file." | |
if [ -n "$INST_SCRIPT" ]; then | |
# Add installer script to the bundle. | |
if [ -z "$ENCODING" ]; then | |
# Add the script "as is". | |
cat "$INST_SCRIPT" >> "$BUNDLE_FILE" || eexit "Can't write an installer script into the bundle file." | |
else | |
# Convert installer scripts' encoding. | |
iconv -f 'UTF-8' -t "$ENCODING" "$INST_SCRIPT" >> "$BUNDLE_FILE" \ | |
|| eexit "Can't write an encoded installer script into the bundle file." | |
fi | |
fi | |
# Add embedded archive start mark to the bundle. | |
echo " | |
) | |
exit $? | |
exit 1 | |
#::START_OF_TAR_ARCHIVE::MAGIC748374938::BASE64::" >>"$BUNDLE_FILE" || eexit "Can't write into the bundle file." | |
if [ -n "$USE_FILE_LIST" ]; then | |
# Use file-list and make the archive by ourselves. Encode it with base64. | |
if [ -z "$USE_XZ_COMPRESSION" ] ; then | |
# Compress with gzip with default compression. | |
tar -cz -h "${FILE_LIST[@]}" | base64 -w 120 >>"$BUNDLE_FILE" || eexit "Can't encode archive to bundle file." | |
elif [ -n "$USE_XZ_COMPRESSION" ] && [ -n "$USE_XZ_DEFAULT_COMPRESSION" ]; then | |
# Compress with xz with default compression. | |
tar -cJ -h "${FILE_LIST[@]}" | base64 -w 120 >>"$BUNDLE_FILE" || eexit "Can't encode archive to bundle file." | |
else | |
# Compress with xz (external tool) with compression level 2. | |
tar -c -h "${FILE_LIST[@]}" | xz -2 | base64 -w 120 >>"$BUNDLE_FILE" || eexit "Can't encode archive to bundle file." | |
fi | |
else | |
# Add base64 encoded archive from stdin. | |
base64 -w 120 >>"$BUNDLE_FILE" || eexit "Can't encode archive to bundle file." | |
fi | |
# Mark the created bundle as an executable. | |
chmod a+x "$BUNDLE_FILE" || eexit "Can't make the bundle executable." | |
# Successfull exit - remove exit trap that deletes bundle file. | |
trap - EXIT | |
# Done! | |
[ -n "$VERBOSE" ] & echo "The installation bundle has been created succefully." | |
exit 0 | |
######################################################################################################################## | |
# Bundle stub code. | |
######################################################################################################################## | |
# Here the used path is stored after bundle was unpacked. | |
declare BUNDLE_UNPACK_PATH= | |
# Unpacks bundle into specified ($1) directory. | |
# If no directory specified, then detects and creates temporary directory, | |
# Writes used directory into "BUNDLE_UNPACK_PATH" variable. | |
function bundle_unpack { | |
# The line number of base64 encoded archive in the bundle. | |
local START_OFFSET= | |
START_OFFSET="$(grep -m 1 -b -x -E '^#:'':START_OF_TAR_ARCHIVE::MAGIC748374938::BASE64:'':$' "${BASH_SOURCE}" 2>/dev/null)" \ | |
|| { echo "Error: The installer is damaged - no embedded archive is found!" >&2 ; return 2 ; } | |
START_OFFSET="${START_OFFSET%%:*}" | |
[ "$START_OFFSET" -eq "$START_OFFSET" ] 2>/dev/null \ | |
|| { echo "Error: Can't detect the embedded archive!" >&2 ; return 2 ; } | |
# Skip the archive start mark as well. | |
let START_OFFSET+=51 | |
if [ -z "$1" ]; then | |
# No target directory specified for unpacking - create a temporary one. | |
local BASE_NAME="$(basename "${BASH_SOURCE[0]}")" | |
local V= | |
[ -d /tmp ] && [ -w /tmp ] && V=/tmp \ | |
|| { echo "Error: \"\tmp\" directory is not writable!" >&2 ; return 3 ; } | |
[ -f "${V}/~${BASE_NAME}.$USER.inst" ] || BUNDLE_UNPACK_PATH="${V}/~${BASE_NAME}.$USER.inst" | |
[ -n "$BUNDLE_UNPACK_PATH" ] || [ -f "${V}/~${BASE_NAME}.$USER.inst2" ] || BUNDLE_UNPACK_PATH="${V}/~${BASE_NAME}.$USER.inst2" | |
BUNDLE_UNPACK_PATH="${V}/~${BASE_NAME}.$USER.$$" | |
# Make a trap - remove temporary directory on exit. | |
trap "[ -d '$BUNDLE_UNPACK_PATH' ] && rm -Rf '$BUNDLE_UNPACK_PATH' &>/dev/null" EXIT | |
mkdir "$BUNDLE_UNPACK_PATH" || { echo "Error: Can't create temporary directory!" >&2 ; return 3 ; } | |
else | |
BUNDLE_UNPACK_PATH="$1" | |
[ -d "$BUNDLE_UNPACK_PATH" ] || { echo "Error: Target directory doesn't exist!" >&2 ; return 3 ; } | |
fi | |
local SOURCE=$(readlink -e "${BASH_SOURCE[0]}") || { echo "Error: Can't detect source path!" >&2 ; return 2 ; } | |
( | |
cd "$BUNDLE_UNPACK_PATH" || { echo "Error: Can't enter target directory for unpacking!" >&2 ; exit 3 ; } | |
if [ -z "$BUNDLE_USE_XZ" ]; then | |
# Decompress as a gzipped-archvie. | |
tail -q -c "+$START_OFFSET" "$SOURCE" | base64 -d | tar --no-same-owner -xz && exit 0 | |
else | |
# Decompress as an xz-archvie. | |
tail -q -c "+$START_OFFSET" "$SOURCE" | base64 -d | tar --no-same-owner -xJ && exit 0 | |
fi | |
# Error! | |
echo "Error: Can't unpack embedded archive!" >&2 | |
) || return $? | |
return 0 | |
} | |
# Process '-v' and '--version' options - print version information. | |
[ "$1" == '-v' ] || [ "$1" == '--version' ] && [ -n "$BUNDLE_VERSION" -o -n "$BUNDLE_DATE" ] \ | |
&& { | |
declare V= | |
[ -n "$BUNDLE_VERSION" ] && V="v${BUNDLE_VERSION}" | |
[ -n "$BUNDLE_DATE" ] && V="$V $BUNDLE_DATE" | |
[ -n "$V" ] && echo "$V" >&2 | |
exit 0 | |
} | |
# Process '--unpack_bundle' option. | |
if [ "$1" == '--unpack_bundle' ]; then | |
[ -z "$2" ] && { echo "Error: No target directory for unpacking is specified!" >&2 ; exit 3 ; } | |
[ -d "$2" ] || { echo "Error: Target directory for unpacking doesn't exist!" >&2 ; exit 3 ; } | |
bundle_unpack "$2" | |
exit $? | |
fi | |
# If no user script is attached then unpack the embedded archive and run "installer.sh" | |
if [ -z "$BUNDLE_USE_EMBEDDED_INSTALLER" ]; then | |
# Unpacking the emebdded archive. | |
bundle_unpack || exit $? | |
# Search and execute script. | |
[ -f "$BUNDLE_UNPACK_PATH/installer.sh" ] \ | |
|| { echo "Error: No installer script is found in the bundle!" >&2 ; exit 2 ; } | |
unset -v BUNDLE_USE_EMBEDDED_INSTALLER | |
. "$BUNDLE_UNPACK_PATH/installer.sh" "$@" | |
exit $? | |
fi | |
# Execute attached installer script. | |
unset -v BUNDLE_USE_EMBEDDED_INSTALLER | |
######################################################################################################################## | |
# End of bundle stub code. | |
######################################################################################################################## | |
( |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment