Skip to content

Instantly share code, notes, and snippets.

@Sap333
Last active November 11, 2015 20:10
Show Gist options
  • Save Sap333/364bd3286d7909c21b0f to your computer and use it in GitHub Desktop.
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.
#!/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