#!/bin/sh VERSION="1.7" declare -a OPTS OPTS=( h,usage a,getAllConfigs f,USEFORCE="YES" r,REVERT="YES" d,DOWNLOADONLY="YES" i,SETUSERINSTALLPATH="YES" I,INSTALLCONFIG="YES" l,LISTCONFIGVAR="YES" R,RESUME="YES" c,CLEANDOWNLOADS="YES" D,RUNAPP="NO" p,PROBLEMFLAG="IGNORE" v,version t,EDITCONFIG="YES" L,listInstalledConfigs q,QUIET="YES" ) function getAllConfigs() { INSTALLALL="yes" PRODUCTS=$( find ~/.thirdphase/config/ -type f -exec basename {} \+ ) } function handlearg() { while [ $1 ]; do for opt in ${OPTS[*]}; do ARGLINE=$( echo $opt | grep "^$1" ) if [ "$ARGLINE" ]; then break fi done if [ "$ARGLINE" ]; then BOOLEAN=$( echo $ARGLINE | grep -q =$ && echo "no" || echo "yes" ) CURRENTARG=$( echo $ARGLINE | cut -f 1 -d , ) eval $( echo $ARGLINE | cut -f 2 -d , ) else usage "Invalid option: $1" fi shift done } function processarg() { ARG=$* echo $1 | grep -q ^-. while [ $? -eq 0 ]; do echo $1 | grep -qE ^-- if [ $? -eq 0 ]; then OPTIONS=$( echo $1 | sed "s/--//" ) else OPTIONS=$( echo "$1" | sed 's/\(.\)/\1 /g' | cut -c 3- ) fi echo $2 | grep -q ^-. if [ $? != 0 ]; then ARG="$2" shift fi handlearg ${OPTIONS} shift echo $1 | grep -q ^-. done } function version() { echo "thirdphase $VERSION"; exit 0; } output() { if [ "$QUIET" != "YES" ]; then printf "$*" fi } findFileType() # returns a number between 1 and 13. { # 1 through 13 will match to a file extension from POSSIBLEEXTENSIONS. local i=1 # 14 is when the file extension is not supported FILEEXTENSION="$1" shift while [ "$1" ]; do # Break when we find a match or exit loop once we exhaust POSSIBLEEXTENSIONS echo "$FILEEXTENSION" | grep "$1$" 1>/dev/null && break || i=$((i+1)); shift done return $i } fileHandler() # Handle a file based on it's file extension { # Handled extensions are located in the variable POSSIBLEEXTENSIONS ############## YEAH I KNOW WHAT 'file' IS BUT I DON'T TRUST IT ############## ################### 1 2------3 4 5-----------------8 9-10 11 12 13 POSSIBLEEXTENSIONS="tar tgz tar.gz gz tbz tbz2 tar.bz2 tar.bz bz bz2 tar.Z Z dmg" FILEEXTENSION=$( echo ${1} | cut -d . -f 2- ) findFileType "$FILEEXTENSION" ${POSSIBLEEXTENSIONS} case "$?" in 1) tarFUNCTION;; 2|3) gunzipFUNCTION checkErrorCode "$?" tarFUNCTION;; 4) gunzipFUNCTION;; [5-8]) bunzip2FUNCTION checkErrorCode "$?" tarFUNCTION;; 9|10) bunzip2FUNCTION;; 11) uncompressFUNCTION checkErrorCode "$?" tarFUNCTION;; 12) uncompressFUNCTION;; 13) true;; *) echo "File type not supported"; return 50; esac checkErrorCode "$?" ########################## THIS MIGHT NEED REDONE SOMEHOW ##################### } clearWORKINGDIR() # Remove the working directory { if [ -d "${WORKINGDIR}" ]; then rm -r "${WORKINGDIR}" || { echo "Can't remove $WORKINGDIR"; return 31; } fi } checkErrorCode() # Check exit status of commands { # Some exit codes will add an * to the date file so we know if a download is problematic if [ "$1" -ne 0 ]; then if [ "$1" = "100" ]; then return 100 fi if [ "$MOUNTED" = "YES" ]; then rm "$LOCALDOWNLOAD" unmountDMG fi # These are the problematic exit returns #if [[ "$1" = 7 || "$1" = 8 || "$1" = 10 || "$1" = 16 || "$1" = 17 || "$1" = 29 || "$1" = 127 ]]; then if [[ "$PROBLEMATIC" = "YES" ]]; then writeDATEFILE "*" fi clearWORKINGDIR return "$1" fi return 0 } checkDirectories() # From the list of arguments we check if the directory exists if not create it { while [ "$1" ]; do if [ ! -d "$1" ]; then echo "Creating directory $1" mkdir "$1" || { echo "Can't make $1"; return 3; } # If there's a problem creating the directory exit from checkDirectories fi shift done return 0 } readINSTALLPATH() # If the -i option is given thirdphase will ask for a custom install path if nothing entered userINSTALLPATH is 'rm'ed { # isntall path is written to $PRODUCTDIR/userINSTALLPATH echo -n "Enter your personal install path: " read INSTALLPATH if [ "$INSTALLPATH" ]; then echo "INSTALLPATH=${INSTALLPATH}" > "${PRODUCTDIR}/userINSTALLPATH" || return 23 echo "Your install path is now ${INSTALLPATH}" else rm "${PRODUCTDIR}/userINSTALLPATH" 2>/dev/null echo "Returning install path to $INSTALLPATH" fi } checkForUserINSTALLPATH() # Check for userINSTALLPATH if found souce the file { if [ -f "${PRODUCTDIR}/userINSTALLPATH" ]; then . "${PRODUCTDIR}/userINSTALLPATH" fi } listInstalledConfigs() # outputs all installed configs from $CONFIGDIR { echo "$( ls ${CONFIGDIR} )" exit } usage() # displayed when you use -h { if [ "$*" ]; then echo $* fi echo "Usage: $SCRIPTNAME [-cdDfiIhlLpqrRtv] [-F filename] product" echo -e "product:\n\ta config file name located in `echo -n \~/.; echo $CONFIGDIR | cut -d . -f 2,2`"\ "\noptions:"\ "\n\ta - update all products installed"\ "\n\tc - cleanup the downloads folder"\ "\n\td - download only (No decompression or installation)"\ "\n\tD - Don't run the app once installed"\ "\n\tf - force download (this option will not backup your current app)"\ "\n\ti - set a personal install path (defaults are set in config files)"\ "\n\t enter a empty string to return to default"\ "\n\tI - install a config file to ~/.nightly/config/"\ "\n\th - help (this message)"\ "\n\tl - list settings for a config"\ "\n\tL - list all installed configs"\ "\n\tp - Ignore if a product's download has been marked problematic"\ "\n\tq - quiet thirdphase's output"\ "\n\tr - revert to backup (should be used without any other options)"\ "\n\tR - if your sever supports resume offset downloading use this option"\ "\n\tt - Edit the config file of the given product"\ "\n\tv - version" exit } downloadPRODUCT() # Download $URL { if [ "${FROMFILE}" != "YES" ]; then output "Downloading ${URL}\n" DOWNLOADCOMMAND="curl -L -o ${LOCALDOWNLOAD}" if [ "$RESUME" = "YES" ]; then # construct the curl command DOWNLOADCOMMAND="${DOWNLOADCOMMAND} -C - ${URL}" else DOWNLOADCOMMAND="${DOWNLOADCOMMAND} ${URL}" fi if [ "$QUIET" = "YES" ]; then DOWNLOADCOMMAND="${DOWNLOADCOMMAND} -s" fi $DOWNLOADCOMMAND local i=1; while [ "$?" -ne "0" -a $i -le 5 ]; do # If we had problems downloading try a couple more times echo "We had problems trying to download ${FILENAME}. Retrying." sleep 5 $DOWNLOADCOMMAND i=$(( i+1 )) done if [ $i -ge 5 ]; then # If i is 5 or more then (should never be more then 5 but just to be safe) make PROBLEMATIC to YES and return an error PROBLEMATIC="YES" return 32 fi fi return 0 } killPRODUCT() { output "Quiting any open ${BINARY}\n" case "$OS" in "macos") # Quit a MacOS app using Applescript ISOURAPPRUNNING=$( ps -xwww -o command | grep "${BINARY}" | grep -v grep ) if [ "${ISOURAPPRUNNING}" ]; then KILLSCRIPT='tell app "'${BINARY}'" to quit' osascript -e "$KILLSCRIPT" 2>/dev/null sleep 3 #Sometimes it might take a couple second for an app to quit... we'll wait fi ;; "unix") killall "$BINARY" 2>/dev/null ;; esac } backupPRODUCT() { if [ -d "$APPPATH" ]; then killPRODUCT if [ "$USEFORCE" ]; then output "Using force download there will be no back-up generated from this session\n" rm -rf "$APPPATH" return fi output "Backing up ${APPPATH}\n" mv -f "$APPPATH" "$BACKUPDIR" || return 3 cd "$BACKUPDIR" tar -cf "${APPDIR}.tar" "$APPDIR" || return 4 rm -rf "${APPDIR}" || echo -e "Problems removing `pwd`/${APPDIR}\nTry 'rm -rf \"`pwd`/${APPDIR}\"'" fi } unmountDMG() # Unmount a disk image MacOS X only { output "Unmounting ${MACVOL}\n" hdiutil detach -quiet "$MACVOL" && MOUNTED="NO" || return 12 } macosInstall() # Install from a disk image MacOS X only { output "Mounting ${LOCALDOWNLOAD}\n" MACVOL="/$(echo y $(echo)| hdiutil mount "$LOCALDOWNLOAD" | tail -n 1 | cut -f4- -d/ )" if [ ! "$MACVOL" ]; then echo "Can't mount ${LOCALDOWNLOAD}" PROBLEMATIC="YES"; return 7 fi MOUNTED="YES" cp -RP "${MACVOL}/${APPDIR}" "${WORKINGDIR}" || { PROBLEMATIC="YES"; return 8; } backupPRODUCT checkErrorCode "$?" output "Installing new ${APPDIR}\n" mv "${WORKINGDIR}/${APPDIR}" "${INSTALLPATH}" || { PROBLEMATIC="YES"; return 10; } unmountDMG rm "${LOCALDOWNLOAD}" || return 11 return 0 } unixInstall() # Installing from a directory { mv "${LOCALDOWNLOAD}" "${WORKINGDIR}" checkErrorCode "$?" backupPRODUCT checkErrorCode "$?" output "Installing new ${APPDIR}\n" mv "${WORKINGDIR}/${LOCALDOWNLOAD}" "${INSTALLPATH}" checkErrorCode "$?" return 0 } revert() # Revert back to $BACKUPFILE { output "Reverting to backup\n" if [ -f "$BACKUPFILE" ]; then LOCALDOWNLOAD="${BACKUPDIR}/"`cat "$BACKUPFILE"` else echo "Can't find backup configure file" return 14 fi if [ -f "$LOCALDOWNLOAD" ]; then killPRODUCT if [ -d "$APPPATH" ]; then mv "$APPPATH" "$BACKUPDIR" fi echo "Restoring ${PRODUCT} from backup" tar -xf "$LOCALDOWNLOAD" -C "$INSTALLPATH" if [ "$?" -ne 0 ]; then echo "Problems with 'tar -xf $LOCALDOWNLOAD -C $INSTALLPATH'" return 9 fi if [ -f "$LOCALDOWNLOAD" ]; then rm -rf "${BACKUPDIR}/${APPDIR}" "$LOCALDOWNLOAD" fi else echo "Nothing to revert to. ${BACKUPDIR}/${APPDIR}.tar is not there" return 14 fi rm "$DATEFILE" rm "$BACKUPFILE" return 0 } openAPP() { if [ "$RUNAPP" != "NO" ]; then # Run app bases on $OS output "Running " case "$OS" in "macos") output "${APPPATH}\n" open -a "${APPPATH}";; "unix") output "${APPPATH}/${RUNNER}\n" if [ -f "${APPPATH}/${RUNNER}" ]; then "${APPPATH}/${RUNNER}" 2>/dev/null 1>/dev/null & else echo "${APPPATH}/${RUNNER} not found" checkErrorCode 127 fi;; esac fi if [ "$QUIET" = "YES" ]; then "${PRODUCT} updated" fi } readCONFIG() # Source config file and validate OS { if [ -f "$CONFIGFILE" ]; then POSSIBLEOS="macos unix" . "$CONFIGFILE" checkCONFIG "OS" "$OS" ${POSSIBLEOS} if [ "$?" -ne 0 ]; then return 26 fi else echo "Can't find $CONFIGFILE" usage return 18 fi while [ ! "$NEEDNEWURL" ]; do NEWURL=$( curl -s -I "${URL}" | grep ^Location: | cut -d ' ' -f 2 | perl -p -e 's/\r//') if [ "$NEWURL" ]; then URL="${NEWURL}" else NEEDNEWURL="NO" fi done FILENAME=`basename ${URL}` } getNIGHTLYDATE() # Grabs the Last-Modified date using curl that we use to check if thirdphase need to udpate the product or not { DATE=`curl -L -s -I ${URL} | grep Last-Modified | sed 's/Last-Modified: //' | sed 's/\ //g'` while [ ! "$DATE" ]; do echo "We had problems grabbing ${FILENAME}'s Last-Modified date. Retrying." sleep 5 DATE=`curl -L -s -I ${URL} | grep Last-Modified | sed 's/Last-Modified: //' | sed 's/\ //g'` done } installCONFIGFILE() # Install a config file to ~/.thirdphase/config/ { if [ "$PRODUCT" ]; then while [ "$1" ]; do if [ -f "$1" ]; then output "Installing ${1} to ${CONFIGDIR}\n" mv "${1}" "${CONFIGDIR}" if [ "$?" != "0" ]; then echo "Can't install ${1} to ${CONFIGDIR}" return 19 fi echo "${1} installed to ${CONFIGDIR}" else echo "${1} is not a valid file" echo "${1} is a `file ${1}|sed s/${1}:\ //`" return 19 fi shift done else usage fi return 0 } listConfigSettings() # List installed config files from ~/.thirdphase/config/ { if [ -f "$CONFIGFILE" ]; then cat "$CONFIGFILE" else echo "Can't find ${CONFIGFILE}" listInstalledConfigs return 18 fi return 0 } cleanDownlownProduct() { if [ -f "${DOWNLOADDIR}/${FILENAME}" ]; then output rm "${DOWNLOADDIR}/${FILENAME}\n" rm "${DOWNLOADDIR}/${FILENAME}" if [ "$?" -eq 1 ]; then echo "Can't remove ${FILENAME}" return 20 fi else output "${FILENAME} not there. Nothing to clean up\n" return 21 fi } checkUSAGE() { if [ "$USAGE" = "YES" ]; then usage exit 0 fi } checkLISTINSTALLEDCONFIGS() { if [ "$LISTINSTALLEDCONFIGS" = "YES" ]; then listInstalledConfigs exit 0 fi } checkPRODUCT() { if [ ! "$PRODUCT" ]; then echo "No product given" usage return 1 fi return 0 } checkLISTCONFIGVAR() { if [ "$LISTCONFIGVAR" = "YES" ]; then listConfigSettings if [ "$?" -ne 0 ]; then checkErrorCode "$?" else exit 0 fi fi } checkINSTALLCONFIG() { if [ "$INSTALLCONFIG" = "YES" ]; then installCONFIGFILE $PRODUCT checkErrorCode "$?" exit 0 fi } checkCLEANDOWNLOADS() { if [ "$CLEANDOWNLOADS" = "YES" ]; then cleanDownlownProduct checkErrorCode "$?" exit 0 fi } checkSETUSERINSTALLPATH() { if [ "$SETUSERINSTALLPATH" = "YES" ]; then readINSTALLPATH checkErrorCode "$?" exit 0 fi } writeDATEFILE() { echo "${1}${DATE}" > "$DATEFILE" if [ "$?" -ne 0 ]; then return 24 fi return 0 } writeBACKUPFILE() { echo "${APPDIR}.tar" > "$BACKUPFILE" if [ "$?" -ne 0 ]; then return 25 fi return 0 } gunzipFUNCTION() { output "gunzip ${LOCALDOWNLOAD}\n" local FOO=`gunzip -v ${LOCALDOWNLOAD} 2>&1 | awk '{ print $6 }'` if [ "$?" -ne 0 ]; then echo "Unable to gunzip ${LOCALDOWNLOAD}" return 6 else LOCALDOWNLOAD="$FOO" fi return 0 } tarFUNCTION() { local FOO=`tar -tf ${LOCALDOWNLOAD} | head -n 1` output "tar -C ${DOWNLOADDIR} -xf ${LOCALDOWNLOAD}\n" tar -C "$DOWNLOADDIR" -xf "$LOCALDOWNLOAD" if [ "$?" -ne 0 ]; then echo "Unable to tar -xf ${LOCALDOWNLOAD}" PROBLEMATIC="YES" return 17 fi rm "$LOCALDOWNLOAD" LOCALDOWNLOAD="$FOO" return 0 } uncompressFUNCTION() { output "uncompress ${LOCALDOWNLOAD}\n" uncompress "$LOCALDOWNLOAD" if [ "$?" -ne 0 ]; then echo "Unable to uncompress ${LOCALDOWNLOAD}" PROBLEMATIC="YES" return 29 fi LOCALDOWNLOAD=`echo ${FILE} | sed s/.Z//` return 0 } bunzip2FUNCTION() { output "bunzip2 ${LOCALDOWNLOAD}\n" local BUNZIPFILE="`dirname $LOCALDOWNLOAD`/bunzipfile" bunzip2 -c "${LOCALDOWNLOAD}" > "$BUNZIPFILE" if [ "$?" -ne 0 ]; then echo "Unable to bunzip2 ${LOCALDOWNLOAD}" PROBLEMATIC="YES" return 16 fi rm "$LOCALDOWNLOAD" LOCALDOWNLOAD="$BUNZIPFILE" return 0 } installProduct() { ${OS}Install # unixInstall or macosInstall } doesTheDownloadFileExist() # Check to see if $URL is there { curl -L -I "$1" 2>/dev/null | grep -q ^Content-Length if [ "$?" = "1" ]; then echo "${1} not found" checkErrorCode 404 fi } checkPROBLEMFLAG() { cat ${DATEFILE} | grep \* > /dev/null || PROBLEMFLAG="NONEED" } checkREVERTorINSTALL() # Check to see if we used the revert option if not install { if [ "$REVERT" = "YES" ]; then revert else output "Checking for ${APPDIR}. " doesTheDownloadFileExist $URL # Check to see if $URL is there if [ $? -eq 0 ]; then if [ -f "$DATEFILE" -a ! "$USEFORCE" -a ! "$DOWNLOADONLY" ] ; then # If DATEFILE exists and we're not using force (-f) and not download only (-d) getNIGHTLYDATE # Get the last modification date of $URL output "${APPDIR} " # if last modification and $DATEFILE are the same and we are not checkPROBLEMFLAG if [ $( cat ${DATEFILE}| sed s/\\*// ) = "$DATE" -a "$PROBLEMFLAG" != "IGNORE" ]; then cat ${DATEFILE} | grep \* > /dev/null && { echo -e "\b's download has been marked problematic\n\tUse the -p option to ignore this"; return 30; } output "is up-to-date!\n" # Check DATEFILE to see if it contains a '*' (problematic download) return 100 else echo "needs updated." if [ "$PROBLEMFLAG" = "IGNORE" ]; then output "Using -p to ignore a possible problematic download\n" fi downloadPRODUCT checkErrorCode $? fi else downloadPRODUCT if [ "$DOWNLOADONLY" = "YES" ]; then exit 0 fi getNIGHTLYDATE fi else return 100 fi fileHandler "$FILENAME" checkErrorCode "$?" WORKINGDIR="${NIGHTLYDIR}/working" mkdir "${WORKINGDIR}" installProduct checkErrorCode "$?" writeDATEFILE checkErrorCode "$?" writeBACKUPFILE checkErrorCode "$?" fi return "$?" } runPREINSTALLSCRIPTS() { local SHOULDWERUN=`ls ${PREINSTALLDIR}` if [ "$SHOULDWERUN" ]; then output "Running any pre-install scripts for ${APPDIR}\n" for scriptname in `ls ${PREINSTALLDIR}/`; do "${PREINSTALLDIR}/$scriptname"; done fi } runPOSTINSTALLSCRIPTS() { local SHOULDWERUN=`ls ${POSTINSTALLDIR}` if [ "$SHOULDWERUN" ]; then output "Running any post-install scripts for ${APPDIR}\n" for scriptname in `ls ${POSTINSTALLDIR}/`; do "${POSTINSTALLDIR}/$scriptname"; done fi } checkCONFIG() { VARNAME="$1"; shift VARVALUE="$1"; shift while [ "$1" ]; do if [ "$1" = "$VARVALUE" ]; then return 0 fi POSSIBLES="${1} ${POSSIBLES}" shift done echo "Bad ${VARNAME}: $VARVALUE" echo "Posible ${VARNAME}s: $POSSIBLES" return 1 } checkAPPS() { while [ "$1" ]; do WEHAVEIT=`which "$1" | grep -v ^no` if [ "$WEHAVEIT" ]; then shift; else echo "${1} was not found in `echo ${PATH} | sed s/:/\ /g`" return 28 fi done return 0 } editCONFIG() { if [ "$EDITCONFIG" = "YES" ]; then if [ $EDITOR ]; then $EDITOR "${CONFIGFILE}" else vi "${CONFIGFILE}" fi fi } #ENDOFFUNCTIONS ##########START OF THE SCRIPT########## SCRIPTNAME=`basename $0` NIGHTLYDIR="${HOME}/.thirdphase" CONFIGDIR="${NIGHTLYDIR}/config" DOWNLOADDIR="${NIGHTLYDIR}/download" REQUIREDAPPS="curl" checkAPPS ${REQUIREDAPPS} checkErrorCode "$?" checkDirectories "$NIGHTLYDIR" "$CONFIGDIR" "$DOWNLOADDIR" checkErrorCode "$?" processarg $* if [ ! "$INSTALLALL" ]; then PRODUCTS=$ARG if [ ! "$PRODUCTS" ]; then usage fi fi for PRODUCT in $PRODUCTS; do checkLISTINSTALLEDCONFIGS checkINSTALLCONFIG checkPRODUCT checkErrorCode "$?" CONFIGFILE="${CONFIGDIR}/${PRODUCT}" PRODUCTDIR="${NIGHTLYDIR}/${PRODUCT}" editCONFIG readCONFIG #New varibles: URL, APPDIR, INSTALLPATH, OS, BINARY, RUNNER checkErrorCode "$?" checkDirectories "$PRODUCTDIR" checkErrorCode "$?" BACKUPDIR="${PRODUCTDIR}/backup" checkDirectories "$BACKUPDIR" checkErrorCode "$?" checkSETUSERINSTALLPATH checkLISTCONFIGVAR checkCLEANDOWNLOADS checkForUserINSTALLPATH checkDirectories "$INSTALLPATH" checkErrorCode "$?" BACKUPFILE="${PRODUCTDIR}/backupfile" PREINSTALLDIR="${PRODUCTDIR}/preinstall" POSTINSTALLDIR="${PRODUCTDIR}/postinstall" DATEFILE="${PRODUCTDIR}/date" APPPATH="${INSTALLPATH}/${APPDIR}" if [ "${FROMFILE}" != "YES" ]; then LOCALDOWNLOAD="${DOWNLOADDIR}/${FILENAME}" fi checkDirectories "$PREINSTALLDIR" "$POSTINSTALLDIR" runPREINSTALLSCRIPTS checkREVERTorINSTALL checkErrorCode "$?" if [ $? -ne 100 ]; then runPOSTINSTALLSCRIPTS openAPP clearWORKINGDIR fi done exit 0 ###########END OF THE SCRIPT########### # error codes # 0 - No error # 1 - Did not provide a product # 2 - Not able to make a directory # 3 - Can't move current app to backup dir # 4 - Can't create a tar backup of current app # 6 - Can't gunzip LOCALDOWNLOAD # 7 - Can't mount dmg image (MacOSX only) **** # 8 - Can't copy app from mounted disk image to install path **** # 10 - Can't move app to install path **** # 11 - Can't remove what we downloaded # 12 - Problem unmounting dmg image (MacOSX only) # 13 - Invalid option # 14 - Nothing to revert back to or we can't find the backup config file # 16 - Can't bunzip2 LOCALDOWNLOAD **** # 17 - Can't untar LOCALDOWNLOAD **** # 18 - Can't find a config file # 19 - Can't install a config file to ~/.nightly/config/ # 20 - Can't clean the download directory # 21 - Nothing in the download directory to remove # 22 - Premature exit from makeConfigFile() # 23 - Can't write to userINSTALLPATH # 24 - Can't write DATEFILE # 25 - Can't write BACKUPFILE # 27 - Can't move, problem in unixInstall # 28 - A required app is not installed (curl) # 29 - Problems with uncompress **** # 30 - The download is marked with problems # 31 - Can't remove working directory ($WORKINGDIR) # 32 - Problems downloading **** # 404 - $URL not found