local PROGRAM_VERSION = [["1.3"]] local PROGRAM_VERSION_CONVERTED = "local PROGRAM_VERSION = ".."[["..PROGRAM_VERSION.."]]" local PROGRAM_LINK = "1RzY7DFx" -- downloads and installs all Libraries and APIs, downloads any programs that the computer should have based on its label, also updates this very file - how nifty -- try `wget` shell command, apparently can save a url to file, is shell.wget(url, filename), works, for pastebin use raw links -- TODO: migrate all ["link"]'s to gitlab links, make repo public, this will avoid silly restrictions on pastebin -- or use pipelines to push live files to gitlab pages and collect them there via `shell.run("wget", url, filename)` -- TODO: change over checkVersion() & checkSelf() to wget so files can be collected from gitlab pages -- TODO: if api is already loaded then should reload it, as it may have been updated -- TODO: adds checks for remaining disk space (also check type of computer to get total diskspace, or is there a total disk space command?) -- TODO: add check to see if computer has a label, if not exit and ask user to label computer -- a list of all dependencies (libraries/apis) that wishers programs use local DEPENDENCIES = { [1] = { ["file"] = "wLibMining", ["link"] = "XkajLyXx", ["path"] = "wisher/" }, [2] = { ["file"] = "wLibVariables", ["link"] = "FuGd7TXY", ["path"] = "wisher/" } } -- list of all programs -- programs are nested again to allow downloading multiple programs -- these programs should sit in the base directory unlike library/apis which sit in the wisher/ directory local COMPUTER_PROGRAMS = { [1] = { ["label"] = "stripMine", [1] = { ["file"] = "stripMine", ["link"] = "nVAXKrBz", ["autorun"] = false }, [2] = { ["file"] = "exhaust", ["link"] = "HcBLDp4r", ["autorun"] = false } }, [2] = { ["label"] = "tester", [1] = { ["file"] = "exhaust", ["link"] = "nVAXKrBz", ["autorun"] = false } }, [3] = { ["label"] = "exhaust", [1] = { ["file"] = "exhaust", ["link"] = "nVAXKrBz", ["autorun"] = true } }, [4] = { ["label"] = "b_unirail", [1] = { ["file"] = "botania_uniRail", ["link"] = "0XHwwCkU", ["autorun"] = true } } } -- table which stores what happened to each file during startup, used for verbose, could be used for debugging or other useful things too local fileStatus = { [1] = { ["type"] = "Dependencies", [1] = { ["status"] = "Fresh Download: " }, [2] = { ["status"] = "Loaded: " }, [3] = { -- in-case startup is manually executed after actual startup for some reason ["status"] = "Already Loaded: " }, [4] = { ["status"] = "Updated: " } }, [2] = { -- Programs ["type"] = "Programs", [1] = { ["status"] = "Fresh Download: " }, [2] = { ["status"] = "Loaded: " }, [3] = { -- in-case startup is manually executed after actual startup for some reason ["status"] = "Already Loaded: " }, [4] = { ["status"] = "Updated: " } } } -- fileStatusIndexType - used to determin whether for Dependency or Programs, range 1-2 || MANUALLY SET TO VALUE REQUIRED IN FUNCTION WHICH UTILIZES -- fileStatusIndexStatus - used to determin whether for Downloaded, Loaded, Already Loaded or updated files range 1-4 in order -- fileStatusIndexFilename - needs to be logged locally for each IndexStatus -- set this to true if any file fails something it should succeed, e.g. loading api/lib, freshly downloading program, updating program etc. -- if this is true then print a message to check a log file. and open another window (unfocused) with log?? how do windows work on these computers -- this is set true inside logFailed() as that is only called when something fails local fileStatusFailed = false -- used to delete the log on each boot local logIndex = 0 -- helper function to get a particular type of application state, and persist the default if it's not set function getState(type) local DEFAULT_STATE = 0 local filepath = "wisher/_states/_" .. type if (fs.exists(filepath)) then local handle = fs.open(filepath, "r") local state = tonumber(handle.read()) handle.close() return state else -- persist the default if no state has been persisted already return setState(type, DEFAULT_STATE) end end -- helper function to persist a particular type of application state function setState(type, state) local filepath = "wisher/_states/_" .. type local handle = fs.open(filepath, "w") handle.write(state) handle.close() return state end -- returns the state, can be used for persistence accross file being closed (ie self update) or computer shutting down (chunk unloaded, server restarted etc.) function getStartupState() return getState("startup") end -- sets the state, used for file persistence function setStartupState(state) setState("startup", state) end -- used by checkSelf(), start() to determin what to do, 1=upToDate, 2=needs updating, 3=error(file not downloaded/copied), 4=Updated this boot function getStartupUpdate() return getState("startupUpdate") end -- sets the update status function setStartupUpdate(state) setState("startupUpdate", state) end -- returns the file PROGRAM_VERSION as a string for a specified file, this should be the first line in any live file function getVersion(filepath) local handle = fs.open(filepath, "r") local version = tostring(handle.readLine()) handle.close() return version end -- Appends the filenames of any failed files to a log with the server system date at the top of the file ie -- "programName: ".."status".." ".."failed" -- will mention whether it failed to download, update etc. -- sets fileStatusFailed to true which is used to print a message to read the log function logFailed(programName, programStatus) local filepath = "wisher/_log/_startup" logIndex = logIndex + 1 if (logIndex == 1) then -- if this is the first file to be logged this boot then delete the previous log file to save storage space fs.delete(filepath) end local handle = fs.open(filepath, "a") if (logIndex == 1) then handle.writeLine(os.date()) end -- top line should be date for clarification handle.writeLine(programName .. ": " .. programStatus .. "Failed") handle.close() fileStatusFailed = true end -- checks if a file needs updating by downloading and comparing the version number -- logs updates into fileStatus{}, logs fails to logFailed() -- we can always assume the 2nd fileStatus index is 4 since 4 = updated function checkVersion(fileCurrent, fileLink, verboseIndexType, verboseIndexFilename, ...) --make sure to pass file path and file name if library for fileCurrent local fileBasename = fileCurrent local isUpdate = false if (#arg == 5) then fileBasename = arg[5] -- used for checkDependecies where the fileCurrent includes path, this is to neaten verbose end local remoteFileName = fileCurrent .. "VerCheck" local localFileVersion = getVersion(fileCurrent) shell.run("pastebin", "get", fileLink, remoteFileName) if (not fs.exists(remoteFileName)) then -- if the file failed to download then log updated failed logFailed(fileBasename, fileStatus[verboseIndexType][4]) end if (fs.exists(remoteFileName)) then -- if the file successfully downloaded then proceed with logic and log the fileStatus local remoteFileVersion = getVersion(remoteFileName) if (not (localFileVersion == remoteFileVersion)) then -- if the remoteFile version is different then delete current and rename new fs.delete(fileCurrent) fs.move(remoteFileName, fileCurrent) fileStatus[verboseIndexType][4][verboseIndexFilename] = fileBasename isUpdate = true end if (localFileVersion == remoteFileVersion) then -- if file versions match then delete the comparison file fs.delete(remoteFileName) end end return isUpdate end -- Checks to see if this very program needs updating -- sets a value in a file to used persistently to determin whether the file needs updating -- refer to getStartupUpdate() comments to know what the numbers mean function checkSelf() local fileCurrent = "startup" local remoteFileName = fileCurrent .. "VerCheck" shell.run("pastebin", "get", PROGRAM_LINK, remoteFileName) if (not fs.exists(remoteFileName)) then setStartupUpdate(3) return end if (fs.exists(remoteFileName)) then local remoteFileVersion = getVersion(remoteFileName) if (not (PROGRAM_VERSION_CONVERTED == remoteFileVersion)) then fs.copy(fileCurrent, fileCurrent .. "Updater") if (fs.exists(fileCurrent .. "Updater")) then setStartupUpdate(2) return end if (not (fs.exists(fileCurrent .. "Updater"))) then setStartupUpdate(3) return end end if (PROGRAM_VERSION_CONVERTED == remoteFileVersion) then fs.delete(remoteFileName) setStartupUpdate(1) return end end end -- Ensures all Libraries/Apis are downloaded, logs successes to fileStatus{}, logs failures to logFailed() function checkDependencies() local libList = {} -- for libs physically located on the robot local apiExist = false local fileStatusIndexUpdate = 1 -- needs to be logged locally for each IndexStatus, index for the Filename, starts at 1 since we pass this into checkVersion() args, update if returns true, therefore first run must be 1 not 0 local fileStatusIndexDownload = 0 for i, v in ipairs(DEPENDENCIES) do local libName = DEPENDENCIES[i]["path"] .. DEPENDENCIES[i]["file"] local libLink = DEPENDENCIES[i]["link"] libList[i] = shell.resolveProgram(libName) -- attempts to add the filename of dependency[i] to libList to prove it exists if (libList[i] == libName) then --if the lib exists then check its version apiExist = true if (checkVersion(libName, libLink, 1, fileStatusIndexUpdate, DEPENDENCIES[i]["file"])) then -- only update index if checkVersion actually updates fileStatusIndexUpdate = fileStatusIndexUpdate + 1 end end if (apiExist == false) then -- if the lib doesnt exist, download it by using the link in dependancy[i] shell.run("pastebin", "get", libLink, libName) if (fs.exists(libName)) then fileStatusIndexDownload = fileStatusIndexDownload + 1 fileStatus[1][1][fileStatusIndexDownload] = DEPENDENCIES[i]["file"] end if (not fs.exists(libName)) then -- if the file did not download then log this logFailed(DEPENDENCIES[i]["file"], fileStatus[1][1]) end end apiExist = false end end -- Dynamically initializes DEPENDENCIES globally, only if one with the same name doesnt already exist, logs successes to fileStatus{}, failures to logFailed() function initializeDependencies() local fileStatusIndexAlready = 0 -- needs to be logged locally for each IndexStatus local fileStatusIndexLoaded = 0 for keyLibIndex, valueLibName in ipairs(DEPENDENCIES) do local libraryName = DEPENDENCIES[keyLibIndex]["file"] local libraryPath = DEPENDENCIES[keyLibIndex]["path"] .. DEPENDENCIES[keyLibIndex]["file"] if (_G[libraryName]) then fileStatusIndexAlready = fileStatusIndexAlready + 1 fileStatus[1][3][fileStatusIndexAlready] = libraryName end if (not _G[libraryName]) then _G[libraryName] = require(libraryPath) fileStatusIndexLoaded = fileStatusIndexLoaded + 1 fileStatus[1][2][fileStatusIndexLoaded] = libraryName if (not _G[libraryName]) then -- if the file still isn't loaded then log it didnt load, it should have loaded by now logFailed(libraryName, fileStatus[1][2]) end end end end -- Checks label of computer and downloads appropriate program/s, logs successes to fileStatus{}, logs failures to logFailed() function checkPrograms() local computerType = os.getComputerLabel() computerType = string.lower(computerType) local programList = {} local programListIndex = 0 -- need to manually index this since our loops are inside loops and the numbers would get funky if we used their iterator local programExist = false local fileStatusIndexUpdate = 1 -- needs to start at 1 as checkVersion() parses this, only update if true is received back local fileStatusIndexDownload = 0 for iOne, vOne in ipairs(COMPUTER_PROGRAMS) do local computerLabel = COMPUTER_PROGRAMS[iOne]["label"] computerLabel = string.lower(computerLabel) for iTwo, vTwo in ipairs(COMPUTER_PROGRAMS[iOne]) do programListIndex = programListIndex + 1 local programName = COMPUTER_PROGRAMS[iOne][iTwo]["file"] local programLink = COMPUTER_PROGRAMS[iOne][iTwo]["link"] programList[programListIndex] = shell.resolveProgram(programName) if (string.find(computerType, computerLabel)) then -- only download programs if the computer label matches the table label if (programList[programListIndex] == programName) then -- checks if file already exists programExist = true if checkVersion(programName, programLink, 2, fileStatusIndexUpdate) then fileStatusIndexUpdate = fileStatusIndexUpdate + 1 end end if (programExist == false) then -- if file doesnt exist, download shell.run("pastebin", "get", programLink, programName) if (fs.exists(programName)) then fileStatusIndexDownload = fileStatusIndexDownload + 1 fileStatus[2][1][fileStatusIndexDownload] = programName end if (not fs.exists(programName)) then logFailed(programName, fileStatus[2][1]) end end end programExist = false end end end -- cleans the screen then prints how each file was handled, ie Downloaded, Loaded, Already Loaded, Updated, also informs startup version function verboseFileStatus() -- need 3 loops? Type loop, Status loop, filename loop shell.run("clear") for iType, vType in ipairs(fileStatus) do print(fileStatus[iType]["type"]) for iStatus, vStatus in ipairs(fileStatus[iType]) do write(fileStatus[iType][iStatus]["status"]) for iFilename, vFilename in ipairs(fileStatus[iType][iStatus]) do write(vFilename .. " ") end write("\n") end end print("Startup File Version: " .. PROGRAM_VERSION) end -- returns the program name to run, or false if none. -- only returns program name if ONLY 1 program is set to autorun, if more than 1 progam should be run automatically than handle within THAT program function shouldInitializeProgram() local computerLabel = os.getComputerLabel() local programName local isAutorun = false local autorunCount = 0 for i, v in pairs(COMPUTER_PROGRAMS) do if (computerLabel == COMPUTER_PROGRAMS[i]["label"]) then for ia, va in pairs(COMPUTER_PROGRAMS[i]) do if (COMPUTER_PROGRAMS[i][ia]["autorun"]) then programName = COMPUTER_PROGRAMS[i][ia]["file"] isAutorun = true autorunCount = autorunCount + 1 end end end end if (isAutorun) and (autorunCount == 1) then return programName end if (autorunCount > 1) then isAutorun = false return isAutorun end if (not isAutorun) then return isAutorun end end -- Main function, first checks if it needs to update itself, then updates dependencies, initializes dependencies, updates programs, verboses what happened to all prior, then initializes autorun program if there is ONE function start() local fileCurrent = "startup" local fileUpdater = fileCurrent .. "Updater" local fileRemote = fileCurrent .. "VerCheck" -- state list: 0=start of file, 1=open copy to update self, 2=close original update original,3=open original and close updater and delete files, 4=Check/Init Dependencies - Check programs - verbose - autorun if (getStartupState() == 0) then -- if just booted checkSelf() if (getStartupUpdate() ~= 2) then -- if startup is upToDate OR failed to check properly, then do everything else setStartupState(4) end if (getStartupUpdate() == 2) then -- if startup needs updating then begin that process setStartupState(1) shell.run(fileUpdater) return end end if (getStartupState() == 1) then -- if needs updating then --rename updated file to startup, delete original startup, launch startup again fs.delete(fileCurrent) if (not fs.exists(fileCurrent)) then fs.move(fileRemote, fileCurrent) end setStartupState(2) shell.run(fileCurrent) return end if (getStartupState() == 2) then --if updated then -- delete updater, then continue with normal startup fs.delete(fileUpdater) setStartupState(3) os.reboot() end if (getStartupState() == 3) then -- if finished updating & rebooted then set that value and finish remaining startup processes setStartupUpdate(4) setStartupState(4) end if (getStartupState() == 4) then -- normal startup stuff checkDependencies() initializeDependencies() checkPrograms() verboseFileStatus() if (getStartupUpdate() == 4) then print("Startup was updated!") end if (getStartupUpdate() == 3) then -- if self-update failed, log to logFailed() logFailed("Startup", "Updated: ") end if (fileStatusFailed) then print("Something went wrong, check log for details") end end setStartupState(0) -- so the next boot sequence or run of startup will continue from the top of start() fs.delete("wisher/_states/_startupUpdate") -- regenerate startupUpdate information each completed boot sequence if (shouldInitializeProgram() ~= false) then -- if shouldInitializeProgram returns any value that ISNT false sleep(5) -- time to read screen verbose before executing autorun shell.run(shouldInitializeProgram()) return end end start()