#!/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_TITLE="JoeyBuild" # Title of application. G_ORIGINAL_PATH=${PATH} # Original system path. G_EHOME= # Home for this user. G_SRC= # Location of JoeyLib source. G_TEMP= # Directory to store temporary data. G_TARGET= # Current target. G_BUILD_PROJECT= # Used by build. G_BUILD_PLATFORMS= # Used by build. G_BUILD_RESULTS= # Used by build. 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 USERNAME=$1 local SOURCE=/home/"${USERNAME}/build" 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= local PROJECT_JOEYLIB= 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= PROJECT_JOEYLIB= # Sample build.start file: # # Application # Warehouse # Latest (or name of custom JoeyLib) # IIgs 65816 # Linux i386 x86_64 # Windows i686 x86_64 # MacOS i386 x86_64 aarch64 # 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 # If we don't have a JoeyLib to use, grab it from the third line. if [[ -z ${PROJECT_JOEYLIB} ]]; then PROJECT_JOEYLIB=${LINE} #***TODO*** Deal with this. fi # This is for the 4th and later lines of the project that list which targets to build. TARGET= for TEMP in ${LINE}; do if [[ -z ${TARGET} ]]; then TARGET=${TEMP} call RESULT ${TARGET} enabled if [[ ${RESULT} -ne 1 ]]; then # Target is not enabled, stop. break fi 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 /dev/null #echo "sudo -iHu ${USER} ${BASH_SOURCE[0]} server &> /dev/null &" | tSudo tee -a /etc/rc.local > /dev/null echo "cd /home/${USER}" | tSudo tee -a /etc/rc.local > /dev/null echo "${BASH_SOURCE[0]} server ${USER} &> /dev/null &" | tSudo tee -a /etc/rc.local > /dev/null echo "exit 0" | tSudo tee -a /etc/rc.local > /dev/null tSudo chmod +x /etc/rc.local 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/memwatch.h dist/3rdparty/memwatch/. } function startup() { local ARGS=$@ local ACTION=$1 local NAME="$(basename $0)" # Don't allow anything other than "server" to run as root. if [[ ${EUID} -eq 0 ]]; then if [[ "${ACTION}" != "server" ]]; then echo "Do not run this script as root." exit 1 fi # We're running as root, so use specified G_EHOME. G_EHOME="$(getent passwd ${2} | cut -d: -f6)" else if [[ "${ACTION}" == "server" ]]; then echo "The \"server\" option must be run as root." exit 1 fi # Not root, use standard G_EHOME. G_EHOME="$(getent passwd ${USER} | cut -d: -f6)" fi G_SRC="${G_EHOME}/joeylib/joeylib/src" G_TEMP="${G_EHOME}/temp" # 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..." # 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} # Do we have a config file? if [[ ! -f "${G_EHOME}/joeybuild.config" ]]; then tBoldBox tRED "Cannot find joeybuild.config file!" exit 1 fi source "${G_EHOME}/joeybuild.config" tSudoSetPassword "${CONFIG_SUDO}" 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 USER" echo "${NAME} del USER" echo "${NAME} install" echo "${NAME} rebuild" echo "${NAME} server USER" #set -x ;; esac } function startBuildServer() { local TARGET= local RESULT= local NAME= local ARCHS= local DESCRIPTION= local USERNAME= local LOG=${G_EHOME}/joeybuild.log # NOTE: This function is run as root! cd ${G_EHOME} # Log startup. echo "$(date) - Startup ${0}" >>${LOG} # Sample info file: # # 1.0 # ------------------------------------------------------------------------------ # project application "JoeyLib Application" # target IIgs "Apple IIgs" 65816 # target Linux "Linux" i386 x86_64 # target MacOS "Modern MacOS" i386 x86_64 aarch64 # target Windows "Windows XP+" i686 x86_64 # Build supported project types and target details for JoeyDev. echo "1.0" >dist/joeydev.info echo "------------------------------------------------------------------------------" >>dist/joeydev.info echo "project application \"JoeyLib Application\"" >>dist/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}" >>dist/joeydev.info fi done # Identify ourselves. echo "JoeyBuild ServerThis is a JoeyLib Build Server." >dist/index.html # Start the PHP web server if it's not already running. php -S 0.0.0.0:${G_HTTP_PORT} -t dist >>${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.start" ]]; then # Are there old results to clean up? if [[ -f build.tar.bz2 ]]; then rm build.tar.bz2 fi doBuild ${USERNAME} # Log it. echo "$(date) - Compiled ${G_BUILD_PROJECT} for ${USERNAME} on ${G_BUILD_PLATFORMS}" >>${LOG} 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. # shellcheck disable=SC2068 startup $@ 2>&1 | tee lastrun.log