#!/bin/bash -e # # JoeyBuild # Copyright (C) 2018-2023 Scott Duensing # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. # # # This is intended to be used on a clean install of ubuntu-20.04.x-live-server-amd64.iso # G_HTTP_PORT=6502 # Port to use for HTTP server. Must be > 1024. G_EHOME="$(getent passwd $(logname) | cut -d: -f6)" # Home for this user. G_SRC="${G_EHOME}/joeylib/joeylib/src" # Location of JoeyLib source. G_TEMP="${G_EHOME}/temp" # Directory to store temporary data. G_TITLE="JoeyBuild" # Title of application. G_ORIGINAL_PATH=${PATH} # Original system path. G_TARGET= # Current target. G_BUILD_PROJECT= G_BUILD_PLATFORMS= G_BUILD_RESULTS= function addBuildUser() { local USER=$1 local PASS=$2 local SALT=$(LC_ALL=C tr -cd "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" < /dev/urandom | head -c 8) local CRYPT=$(perl -e 'print crypt($ARGV[0], $ARGV[1])' ${PASS} ${SALT}) tSudo useradd -m -G sftponly -s /usr/bin/false -p "${CRYPT}" "${USER}" tSudo chown root:root /home/${USER} tSudo chmod 755 /home/${USER} tSudo mkdir -p /home/${USER}/build tSudo chown ${USER}:${USER} /home/${USER}/build tSudo chmod u+rwX /home/${USER}/build tSudo chmod go-rwx /home/${USER}/build } function call() { local _RESULT=$1 local _MODULE=$2 local _FUNCTION=$3 local _ARGS=${*:4} local _VALUE= G_TARGET=${_MODULE} _VALUE="$( source ${G_EHOME}/targets/${_MODULE}.target && ${_FUNCTION} ${_ARGS} )" G_TARGET= eval $_RESULT=\${_VALUE} } function configureSFTP() { if [[ ! -f /etc/ssh/sftponly_ready ]]; then tSudo addgroup sftponly || true tSudo sed -i 's/^Subsystem/#Subsystem/g' /etc/ssh/sshd_config echo "Subsystem sftp internal-sftp -f AUTH -l VERBOSE" | tSudo tee -a /etc/ssh/sshd_config echo "Match Group sftponly" | tSudo tee -a /etc/ssh/sshd_config echo -e "\tChrootDirectory %h" | tSudo tee -a /etc/ssh/sshd_config echo -e "\tForceCommand internal-sftp" | tSudo tee -a /etc/ssh/sshd_config echo -e "\tAllowTcpForwarding no" | tSudo tee -a /etc/ssh/sshd_config echo -e "\tX11Forwarding no" | tSudo tee -a /etc/ssh/sshd_config tSudo systemctl restart sshd tSudo touch /etc/ssh/sftponly_ready fi } function delBuildUser() { local USER=$1 tSudo userdel -f -r ${USER} } function doBuild() { local SOURCE=$1 local LINE= local FILE= local EXTENSION= local AFILES=() local CFILES=() local OFILES=() local DFILES=() local TEMP= local TARGET= local RESULT= local LOG= local PASS= local PROJECT_TYPE= set -x pushd "${SOURCE}" # Are there old results to clean up? if [[ -f build.finished ]]; then rm build.finished fi if [[ -d results ]]; then rm -rf results fi mkdir -p results G_BUILD_RESULTS=${SOURCE}/results G_BUILD_PLATFORMS="[" G_BUILD_PROJECT= PROJECT_TYPE= # Read project information. while IFS= read -r LINE; do # If we don't have a project type, grab it from the first line of the project file and set things up. if [[ -z ${PROJECT_TYPE} ]]; then PROJECT_TYPE=${LINE} # Skip to next loop pass. continue fi # If we don't have a project name, grab it from the second line of the project file and set things up. if [[ -z ${G_BUILD_PROJECT} ]]; then G_BUILD_PROJECT=${LINE} # Generate a list of non-source files. for FILE in $(ls -1B); do if [[ -f "${FILE}" ]]; then if [[ "${FILE}" != "build.start" && "${FILE}" != "build.temp" && "${FILE}" != "build.tar.bz2" ]]; then EXTENSION="${FILE##*.}" if [[ "${EXTENSION}" != "c" && "${EXTENSION}" != "h" && "${EXTENSION}" != "asm" && "${EXTENSION}" != "macro" && "${EXTENSION}" != "inc" ]]; then DFILES+=(${FILE}) fi fi fi done # Generate a list of C files to compile. TEMP=$(ls -1 *.c 2>/dev/null | wc -l) if [[ ${TEMP} -ne 0 ]]; then CFILES=($(ls -1 *.c)) fi # Generate a list of ASM files to assemble. TEMP=$(ls -1 *.asm 2>/dev/null | wc -l) if [[ ${TEMP} -ne 0 ]]; then AFILES=($(ls -1 *.asm)) fi # Skip to next loop pass. continue fi # This is for the 3rd and later lines of the project that list which targets to build. TARGET= for TEMP in ${LINE}; do if [[ -z ${TARGET} ]]; then TARGET=${TEMP} else for PASS in "debug" "release"; do tBoldBox tPURPLE "Building \"${G_BUILD_PROJECT}\" ${TARGET} ${TEMP} (${PASS})..." G_DIST=dist/${TARGET}-${TEMP}/${PASS} # We at least tried to build this target. G_BUILD_PLATFORMS="${G_BUILD_PLATFORMS}${TARGET}.${TEMP}.${PASS} " # Make sure we have the official JoeyLib header. cp -f "${G_EHOME}/dist/joey.h" . # Log file. LOG="${G_BUILD_RESULTS}/build.${TARGET}.${TEMP}.${PASS}" # Compile C files and generate object list. if [[ "${#CFILES[@]}" != "0" ]]; then call RESULT ${TARGET} compile ${TEMP} ${PASS} ${LOG} "${CFILES[@]}" OFILES=(${RESULT}) fi # Assemble ASM files and generate object list. if [[ "${#AFILES[@]}" != "0" ]]; then call RESULT ${TARGET} assemble ${TEMP} ${PASS} ${LOG} "${AFILES[@]}" OFILES+=(${RESULT}) fi # Link with JoeyLib. call RESULT ${TARGET} link ${TEMP} ${PASS} ${LOG} "${OFILES[@]}" # Process game data files. if [[ "${#DFILES[@]}" != "0" ]]; then call RESULT ${TARGET} package ${TEMP} ${PASS} ${LOG} "${DFILES[@]}" fi done fi done done < build.start popd tTrim TEMP "${G_BUILD_PLATFORMS}" G_BUILD_PLATFORMS="${TEMP}]" } function doInstall() { git config --global user.email "no-reply@kangaroopunch.com" git config --global user.name "JoeyBuild VM Installer" updateSystem configureSFTP # Add 'false' as a valid shell. echo "/usr/bin/false" | tSudo tee -a /etc/shells withTargets install "Installing SDK" rebuildJoeyLib # Start build server on reboot. if [[ ! -f /etc/rc.local ]]; then echo "#!/bin/bash" | tSudo tee /etc/rc.local > /dev/null echo "cd ${G_HOME}" | tSudo tee -a /etc/rc.local > /dev/null echo "sudo -u $(logname) ${BASH_SOURCE[0]} server &> /dev/null &" | tSudo tee -a /etc/rc.local > /dev/null echo "exit 0" | tSudo tee -a /etc/rc.local > /dev/null fi } function rebuildJoeyLib() { # Do we have JoeyLib yet? if [[ ! -f ${G_EHOME}/joeylib/LICENSE ]]; then tBoldBox tBLUE "Downloading JoeyLib source..." git clone https://skunkworks.kangaroopunch.com/skunkworks/joeylib.git ${G_EHOME}/joeylib &> /dev/null fi withTargets buildJoeyLib "Building JoeyLib" mkdir -p dist cp -f ${G_SRC}/joey.h dist/. mkdir -p dist/3rdparty/memwatch cp -f ${G_SRC}/3rdparty/memwatch/* dist/3rdparty/memwatch/. } function startup() { local ARGS=$@ local ACTION=$1 local NAME="$(basename $0)" # Do we have Towel yet? if [[ ! -f "${G_EHOME}/towel/towel.sh" ]]; then # Do we have GIT? if [[ "$(which git || true)" == "" ]]; then echo "Installing git..." #***TODO*** This should be the only use of non-Towel sudo. sudo apt-get -y install git fi echo "Downloading towel.sh support library..." git clone https://skunkworks.kangaroopunch.com/skunkworks/towel.git "${G_EHOME}/towel" &> /dev/null fi # Load Towel source "${G_EHOME}/towel/towel.sh" # Give Towel a chance to handle arguments. tArgsHandler ${ARGS} # Anything after this, don't run as root. if [[ ${EUID} -eq 0 ]]; then echo "Do not run this script as root." exit 1 fi # Do we have an automation file? if [[ ! -f "${G_EHOME}/automated.install" ]]; then tBoldBox tRED "Cannot find automated.install file!" exit 1 fi source "${G_EHOME}/automated.install" tSudoSetPassword "${AUTOMATED_SUDO}" # Be sure we can silently sudo. (for mountORCA) tSudo case ${ACTION} in add) addBuildUser "${2}" "${3}" ;; build) doBuild "${2}" ;; del) delBuildUser "${2}" ;; install) doInstall ;; rebuild) rebuildJoeyLib ;; server) startBuildServer ;; *) #set +x tBoldBox tGREEN "${G_TITLE} Options" echo "${NAME} add USER PASS" echo "${NAME} build SRC" echo "${NAME} del USER" echo "${NAME} install" echo "${NAME} rebuild" echo "${NAME} server" #set -x ;; esac } function startBuildServer() { local TARGET= local RESULT= local NAME= local ARCHS= local DESCRIPTION= local USERNAME= local FILE= cd ${G_HOME} # Log startup. echo "$(date) - Startup ${0}" >> "${LOG}" mkdir -p http # Build supported project types and target details for JoeyDev. echo "1.0" > http/joeydev.info echo "------------------------------------------------------------------------------" >> http/joeydev.info echo "project application \"JoeyLib Application\"" >> http/joeydev.info # echo "project joeylib \"JoeyLib Itself\"" >> http/joeydev.info for TARGET in ${G_EHOME}/targets/*.target; do NAME=$(basename -s .target ${TARGET}) call RESULT ${NAME} enabled if [[ ${RESULT} -eq 1 ]]; then call ARCHS ${NAME} architectures call DESCRIPTION ${NAME} friendlyName echo "target ${NAME} \"${DESCRIPTION}\" ${ARCHS}" >> http/joeydev.info fi done # Start the PHP web server if it's not already running. php -S 0.0.0.0:${G_HTTP_PORT} -t http >> joeybuild.log 2>&1 & # Start the actual build server. cd /home while true; do # Find users with a "build.start" file. for USERNAME in $(ls -1); do mkdir -p "${USERNAME}/build" chown -R ${USERNAME}:${USERNAME} "${USERNAME}/build" chmod u+rwX "${USERNAME}/build" chmod go-rwx "${USERNAME}/build" # Handle building JoeyLib Applications. if [[ -f "${USERNAME}/build/build.application" ]]; then # Are there old reults to clean up? if [[ -f build.tar.bz2 ]]; then rm build.tar.bz2 fi doBuild /home/"${USERNAME}/build" pushd "${USERNAME}/build" # Compress the results. tar cJf build.temp results chown ${USERNAME}:${USERNAME} build.temp # Erase everything except the temp file. rm -rf "${G_BUILD_RESULTS}" for FILE in $(ls -1); do if [[ -f "${FILE}" ]]; then if [[ "${FILE}" != "build.temp" ]]; then rm "${FILE}" fi else if [[ -d "${FILE}" ]]; then rm -r "${FILE}" fi fi done # Signal JoeyDev we're done. mv build.temp build.tar.bz2 # Log it. echo "$(date) - Compiled ${G_BUILD_PROJECT} for ${USERNAME} on ${G_BUILD_PLATFORMS}" >> joeybuild.log popd fi done sleep 1 done } function updateSystem() { local MISSING= tBoldBox tBLUE "Examining system..." tSudo apt-get update tSudo apt-get -y upgrade tSudo apt-get -y dist-upgrade # Shut Canonical up. tSudo pro config set apt_news=false #***TODO*** Split this into target modules. tSudo dpkg --add-architecture i386 tCheckPackages MISSING \ attr \ autoconf \ bison \ build-essential \ bzip2 \ clang \ cmake \ cpio \ flex \ gawk \ gcc-multilib \ git \ gzip \ libbz2-dev \ liblzma-dev \ libssl-dev \ libxml2-dev \ libzstd-dev \ llvm \ mingw-w64 \ mtools \ nasm \ patch \ php-cli \ sed \ unzip \ uuid-dev \ xz-utils \ zlib1g-dev \ genisoimage \ jfsutils \ msitools \ ragel if [[ "${MISSING}" != "" ]]; then tSudo apt-get -y install ${MISSING} fi } function withTargets() { local FUNCTION=$1 local ACTION="$2" local ARGS=${*:3} local PASS= local TARGET= local RESULT= local NAME= local ARCHS= local ARCH= for TARGET in ${G_EHOME}/targets/*.target; do NAME=$(basename -s .target ${TARGET}) call RESULT ${NAME} enabled if [[ ${RESULT} -eq 1 ]]; then call ARCHS ${NAME} architectures for ARCH in ${ARCHS}; do for PASS in "debug" "release"; do tBoldBox tPURPLE "${ACTION} ${NAME} ${ARCH} (${PASS})..." G_TARGET=${NAME} G_INSTALLED=${G_EHOME}/installed/${NAME}-${ARCH}/${PASS} G_DIST=dist/${G_TARGET}-${ARCH}/${PASS} call RESULT ${NAME} ${FUNCTION} ${ARCH} ${PASS} ${ARGS} G_DIST= G_INSTALLED= G_TARGET= done done fi done } # At the very end so we can stream this script from a web server. startup $@ # 2>&1 | tee lastrun.log