#!/usr/bin/env bash # Pkgself builds self-extracting installers # Copyright (C) 2018 Aaron Ball # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set -e export COMPRESS='xz -z -c -T 0' # Command to compress data export DECOMPRESS='xz -d -c' # Command to decompress data export COMPRESSEXT='xz' # File extension for compressed data download_src() { local src="${1:-}" local file [ ! -d '.source' ] && mkdir '.source' # Set the file path buffer for better error messages # This will store local files verbatim, but remote files will be parsed out # of their URIs # If we don't store this value, we would need to recompute src basename a # great many times. file="$(basename ${src})" # Download external files if [ ! -f ".source/$(basename ${src})" ]; then if [ "${src:0:4}" == "http" ] || [ "${src:0:3}" == "ftp" ]; then printf "Downloading %s\n" "${file}" curl -k -L -q -# "${src}" -o ".source/${file}" elif [ "${src:0:6}" == "ssh://" ]; then printf "Downloading %s\n" "${file}" scp -o LogLevel=Error "${src:6}" ".source/${file}" fi else printf "Source file '%s' already downloaded.\n" "${file}" fi if [ -e ".source/${file}" ]; then cp -r ".source/${file}" "${PKGSRC}/${file}" elif [ -e "${src}" ]; then cp -r "${src}" "${PKGSRC}/${file}" else printf "Error: Source '%s' does not exist\n" "${file}" return 1 fi } main() { local pkg="${1:-}" local selfdir # Run directory of builder script local pkgdir # Path to the parent directory of the Pkgfile local tmp # Path to the temp directory containing build resource local output # Path to the output file if [ -z "${pkg:-}" ]; then printf "Package file must be specified\n" return 1 fi # Absolute path to the parent directory of the build script (me!) selfdir="$(cd $(dirname ${0}) && pwd)" # Get the absolute path to the Pkgfile parent directory pkgdir="$(cd $(dirname ${pkg}) && pwd)" # Convert pkg to absolute path pkg="${pkgdir}/$(basename ${pkg})" tmp="$(mktemp -d /tmp/builder-XXXXXX)" export PKGSRC=${tmp}/src export PKG=${tmp}/pkg mkdir -p "${PKGSRC}" mkdir -p "${PKG}" # Source and validate the Pkgfile source ${pkg} if [ -z "${name}" ]; then printf "Variable 'name' is required in Pkgfile\n" return 1 fi if [ -z "${version}" ]; then printf "Variable 'version' is required in Pkgfile\n" return 1 fi if [ -z "${release}" ]; then printf "Variable 'release' is required in Pkgfile\n" return 1 fi if [ "$(type -t build)" != 'function' ]; then printf "Function 'build' is required in Pkgfile\n" return 1 fi # Build the output filename local output="${pkgdir}/${name}_${version}-${release}.sh" # Copy the installer header script into the tmp directory, with the right name cp ${selfdir}/install-header.sh "${output}" # Copy in libinstall directory to include useful install libraries cp -r ${selfdir}/libinstall ${tmp}/libinstall # Copy the run script into the tmp dir install "${pkgdir}/run.sh" "${PKGSRC}/run.sh" # Download all the source files (if needed) cd "${pkgdir}" for src in ${source[@]}; do download_src "${src}" if [ $? -gt 0 ]; then return 1 fi done cd "${PKGSRC}" build cd "${tmp}" printf "Reticulating splines...\n" # Package up the libinstall libraries tar -C "${tmp}" -c --mtime='1970-01-01' libinstall \ | ${COMPRESS} > ${tmp}/libinstall.tar.${COMPRESSEXT} libinstallsize=$(wc -c < ${tmp}/libinstall.tar.${COMPRESSEXT}) # Compress and calculate byte size for run.sh ${COMPRESS} ${PKGSRC}/run.sh > ${tmp}/run.sh.${COMPRESSEXT} runsize="$(wc -c < ${tmp}/run.sh.${COMPRESSEXT})" # Compress and calculate byte size for payload tar -c --mtime='1970-01-01' "$(basename ${PKG})" \ | ${COMPRESS} -v > ${PKG}.tar.${COMPRESSEXT} payloadsize="$(wc -c < ${PKG}.tar.${COMPRESSEXT})" # Replace compress and decompress command variables sed -i "s/{{ DECOMPRESS }}/${DECOMPRESS}/g" "${output}" # NOTE: All variable interpolations in the header MUST occur before this line. # This solely exclides the 'lens' array interpolation headsize=$(wc -c < ${output}) # Print space padded values to meet expected character length. # Expected length: 8 + 8 + 12 + 2 == 30 # 1. header: 8 byte digits allows a 95 MB header # 2. run script: 8 byte digits allows a 95 MB run script # 3. payload: 12 byte digits allows for a 931 GB file lens="$(printf '%8s %8s %8s %12s' ${headsize} ${libinstallsize} ${runsize} ${payloadsize})" # Interpolate chunk lengths # This space between ( and ) must be 60 chars so as to not change the header # lenth after interpolation. sed -i "s/LENS=(.*)/LENS=(${lens})/g" "${output}" # Append chunks to output file cat "${tmp}/libinstall.tar.${COMPRESSEXT}" >> "${output}" cat "${tmp}/run.sh.${COMPRESSEXT}" >> "${output}" cat "${PKG}.tar.${COMPRESSEXT}" >> "${output}" printf 'Calculating installer checksums\n' cd $(dirname ${output}) sha512sum $(basename ${output}) > "${output}.sha512" md5sum $(basename ${output}) > "${output}.md5" # Cleanup rm -rf ${tmp} } main ${@}