Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local unpack = unpack or table.unpack
- os.unloadAPI("backpack")
- local args = {...}
- local opwords = {
- install = true,
- remove = true,
- update = true,
- ["install-help"] = true,
- ["remove-help"] = true,
- ["update-help"] = true,
- list = true,
- search = true,
- bootstrap = true,
- }
- local argwords = {
- fetch = true,
- force = true,
- auto = true,
- --target = 1,
- }
- if #args < 1 or (not opwords[args[1]] and not argwords[args[1]]) then
- io.write("Usage:\n")
- io.write("packman [options] install <package name[s]>\n")
- io.write("packman [options] update <package name[s]>\n")
- io.write("packman [options] remove <package name[s]>\n")
- io.write("packman [options] list [pattern]\n")
- io.write("packman [options] search [pattern]\n")
- io.write("packman bootstrap\n")
- io.write("packman [options] install-help <package name[s]>\n")
- io.write("packman [options] update-help <package name[s]>\n")
- io.write("packman [options] remove-help <package name[s]>\n")
- io.write("\n")
- io.write("Options:\n")
- io.write("fetch\n")
- io.write(" Update repository and package lists before performing operations (can be used without an operation)\n")
- io.write("force\n")
- io.write(" Force requested operation, even if it appears to be unnecessary; answer all questions with yes\n")
- io.write("auto\n")
- io.write(" Automatically answer yes when manipulating packages\n")
- --io.write("target <directory>\n")
- --io.write(" Set root directory to install packages in\n")
- return
- end
- local config = {}
- function loadConfig()
- local path = "/etc/packman/packman.conf"
- --load a configuration file, given a fully-resolved path and an optional environment.
- if not fs.exists(path) or fs.isDir(path) then return nil, "not a file" end
- local env
- if not _env then
- --if we were not provided an environment, create one.
- env = setmetatable({}, {__index = _G})
- else
- env = _env
- end
- local fn, err = loadfile(path)
- if fn then
- setfenv(fn, env)
- local success, err = pcall(fn)
- if success then
- --strip the metatable from the environment before returning it.
- config = setmetatable(env, {})
- else
- return nil, err
- end
- else
- return nil, err
- end
- end
- function saveConfig()
- local path = "/etc/packman/packman.conf"
- if not config or type(config) ~= "table" then return nil, "Not a configuration" end
- local handle = io.open(path, "w")
- if handle then
- for k, v in pairs(config) do
- local success, str = pcall(textutils.serialize, v)
- if success then
- handle:write(k.." = "..str.."\n\n")
- end
- end
- handle:close()
- else
- return nil, "Could not write configuration."
- end
- end
- loadConfig()
- local mode = ""
- local forced = false
- local target = "/"
- local fetch = false
- local auto = false
- local argState = nil
- local argCount = 0
- local operation = {options = {}, arguments = {}}
- --lower all arguments
- for i = 1, #args do
- args[i] = string.lower(args[i])
- if argState == nil and args[i] == "fetch" then fetch = true end
- if argState == nil and args[i] == "force" then forced = true end
- if argState == nil and args[i] == "auto" then auto = true end
- if argwords[args[i]] and type(argwords[args[i]]) == "number" then
- operation.options[args[i]] = {}
- argState = args[i]
- argCount = argwords[args[i]]
- elseif opwords[args[i]] then
- mode = args[i]
- if mode == "bootstrap" then fetch = false break end
- argState = "arguments"
- argCount = 0
- elseif argState and argCount > 0 then
- --option arguments
- table.insert(operation.options[argState], args[i])
- argCount = argCount - 1
- if argCount == 0 then argState = nil end
- elseif argState == "arguments" then
- --operation arguments
- table.insert(operation.arguments, args[i])
- end
- end
- if operation.options.target then
- target = operation.options.target[1]
- end
- local function resetScreen()
- term.setTextColor(colors.white)
- term.setBackgroundColor(colors.black)
- end
- local function printError(errorText)
- if term.isColor() then term.setTextColor(colors.red) end
- io.write(errorText.."\n")
- term.setTextColor(colors.white)
- error()
- end
- local function printWarning(warningText)
- if term.isColor() then term.setTextColor(colors.yellow) end
- io.write(warningText.."\n")
- term.setTextColor(colors.white)
- end
- local function printInformation(infoText)
- if term.isColor() then term.setTextColor(colors.lime) end
- io.write(infoText.."\n")
- term.setTextColor(colors.white)
- end
- local function getUserInput(default)
- if auto then
- return default
- else
- return io.read()
- end
- end
- local function loadPackageAPI()
- if not backpack then if shell.resolveProgram("backpack") then os.loadAPI(shell.resolveProgram("backpack")) elseif fs.exists("usr/apis/backpack") then os.loadAPI("usr/apis/backpack") elseif not (fetch or mode == "bootstrap") then error("Could not load backpack API!") end end
- if backpack then
- resetScreen()
- io.write("Loading database...\n")
- backpack.installRoot = target
- local co = coroutine.create(backpack.load)
- local event, filter, passback = {}
- while true do
- if (filter and (filter == event[1] or event[1] == "terminate")) or not filter then
- passback = {coroutine.resume(co, unpack(event))}
- end
- if passback[1] == false then printWarning(passback[2]) end
- if coroutine.status(co) == "dead" then break end
- filter = nil
- if passback and passback[1] and passback[2] then
- filter = passback[2]
- end
- event = {os.pullEventRaw()}
- if event[1] == "package_status" then
- if event[2] == "info" then
- printInformation(event[3])
- elseif event[2] == "warning" then
- printWarning(event[3])
- elseif event[2] == "error" then
- printError(event[3])
- end
- end
- end
- end
- end
- loadPackageAPI()
- local categoryList = {}
- local categorySorted = {}
- if fetch then
- local queue
- if backpack then
- queue = backpack.newTransactionQueue("main/backpack")
- end
- io.write("Fetching Repository List\n")
- local repolistContent = ""
- remoteHandle = http.get("https://raw.githubusercontent.com/lyqyd/cc-packman/master/repolist")
- if remoteHandle then
- repolistContent = remoteHandle.readAll()
- if queue then
- queue:makeDir("/etc")
- queue:addFile("/etc/repolist", repolistContent)
- else
- local fileHandle = io.open("/etc/repolist", "w")
- if fileHandle then
- fileHandle:write(repolistContent)
- fileHandle:close()
- else
- printWarning("Could not write file /etc/repolist")
- end
- end
- remoteHandle.close()
- else
- printWarning("Could not retrieve remote file.")
- end
- if fs.exists("/etc/custom-repolist") then
- local fileHandle = io.open("/etc/custom-repolist", "r")
- if fileHandle then
- repolistContent = repolistContent.."\n"..fileHandle:read("*a")
- fileHandle:close()
- end
- end
- if repolistContent then
- if queue then
- queue:makeDir("/etc/repositories")
- else
- if not fs.exists("/etc/repositories") then fs.makeDir("/etc/repositories") end
- end
- for line in string.gmatch(repolistContent, "([^\n]+)\n?") do
- local file, url = string.match(line, "^(%S*)%s*(.*)")
- if file and url then
- io.write("Fetching Repository: "..file.."\n")
- local remoteHandle = http.get(url)
- if remoteHandle then
- if queue then
- queue:addFile(fs.combine("/etc/repositories", file), remoteHandle.readAll())
- else
- local fileHandle = io.open(fs.combine("/etc/repositories", file), "w")
- if fileHandle then
- fileHandle:write(remoteHandle.readAll())
- fileHandle:close()
- else
- printWarning("Could not write file: "..fs.combine("/etc/repositories", file))
- end
- end
- remoteHandle.close()
- else
- printWarning("Could not retrieve remote file: "..file)
- end
- end
- end
- end
- if queue then
- queue:finish()
- end
- fetch = false
- if #mode > 0 then
- --reload backpack API.
- os.unloadAPI("backpack")
- loadPackageAPI()
- end
- end
- if #mode > 0 and backpack then
- for n, v in pairs(backpack.list) do
- if v.category then
- for category in pairs(v.category) do
- if not categoryList[category] then
- categoryList[category] = {[n] = true}
- table.insert(categorySorted, category)
- else
- categoryList[category][n] = true
- end
- end
- end
- end
- table.sort(categorySorted)
- local badPackages = {}
- --flesh out dependencies
- for pName, pData in pairs(backpack.list) do
- if pData.dependencies then
- dependencies, errmsg = backpack.findDependencies(pName, {})
- if not dependencies then
- --if dependencies could not be resolved, remove the backpack.
- printWarning("Could not resolve dependency on "..errmsg.." in package "..pName)
- table.insert(badPackages, pName)
- else
- pData.dependencies = dependencies
- end
- end
- end
- --actual package removal and short-name lookup cleanup.
- for _, pack in pairs(badPackages) do
- local entry = backpack.list[pack]
- local name = entry.name
- local others, key = false
- for k, v in pairs(backpack.list[name]) do
- if v == entry then
- key = k
- else
- others = true
- end
- end
- if others then
- backpack.list[name][key] = nil
- else
- backpack.list[name] = nil
- end
- backpack.list[pack] = nil
- end
- end
- local function lookupPackage(name, installedOnly)
- if backpack.list[name] and not backpack.list[name].dependencies then
- local options = {}
- if installedOnly and backpack.installed[name] then
- for name, pack in pairs(backpack.installed[name]) do
- table.insert(options, name)
- end
- elseif installedOnly then
- --using installedOnly, but no packages of that name are installed.
- return false
- else
- for name, pack in pairs(backpack.list[name]) do
- table.insert(options, name)
- end
- end
- if #options > 1 then
- io.write("Package "..name.." is ambiguous.\n")
- for i = 1, #options do
- write(tostring(i)..": "..options[i].." ")
- end
- io.write("\n")
- io.write("Select option: \n")
- local selection = getUserInput("1")
- if tonumber(selection) and options[tonumber(selection)] then
- return options[tonumber(selection)].."/"..name
- end
- elseif #options == 1 then
- return options[1].."/"..name
- else
- return false
- end
- elseif backpack.list[name] then
- --since it must have a dependencies table, the name is already fully unique.
- return name
- else
- return false
- end
- end
- local function raw_package_operation(name, funcName)
- local pack = backpack.list[name]
- if not pack then return nil, "No such package" end
- local co = coroutine.create(function() return pack[funcName](pack, getfenv()) end)
- local event, filter, passback = {}
- while true do
- if (filter and (filter == event[1] or event[1] == "terminate")) or not filter then
- passback = {coroutine.resume(co, unpack(event))}
- end
- if passback[1] == false then printWarning(passback[2]) end
- if coroutine.status(co) == "dead" then return unpack(passback, 2) end
- filter = nil
- if passback and passback[1] and passback[2] then
- filter = passback[2]
- end
- event = {os.pullEventRaw()}
- if event[1] == "package_status" then
- if event[2] == "info" then
- printInformation(event[3])
- elseif event[2] == "warning" then
- printWarning(event[3])
- elseif event[2] == "error" then
- printError(event[3])
- end
- end
- end
- end
- local function install(name)
- if config.manageHelp then
- return raw_package_operation(name, "installFiles") and raw_package_operation(name, "installHelp") and raw_package_operation(name, "finalizeInstall")
- else
- return raw_package_operation(name, "install")
- end
- end
- local function installHelp(name)
- return raw_package_operation(name, "installHelp")
- end
- local function remove(name)
- if config.manageHelp then
- return raw_package_operation(name, "removeHelp") and raw_package_operation(name, "remove")
- else
- return raw_package_operation(name, "remove")
- end
- end
- local function removeHelp(name)
- return raw_package_operation(name, "removeHelp")
- end
- local function upgrade(name)
- if config.manageHelp then
- return raw_package_operation(name, "upgradeFiles") and raw_package_operation(name, "upgradeHelp") and raw_package_operation(name, "finalizeUpgrade")
- else
- return raw_package_operation(name, "upgrade")
- end
- end
- local function upgradeHelp(name)
- return raw_package_operation(name, "upgradeHelp")
- end
- if mode == "bootstrap" then
- --initial setup, results in main/packman and main/backpack packages being installed
- if not backpack or backpack.list["main/packman"] == nil then
- --grab the backpack API, since we have no transaction queue yet.
- io.write("Updating Backpack API\n")
- remoteHandle = http.get("https://raw.githubusercontent.com/lyqyd/cc-packman/master/backpack")
- local apiContents = ""
- if remoteHandle then
- if not fs.exists("/usr/apis") then fs.makeDir("/usr/apis") end
- local fileHandle = io.open("/usr/apis/backpack", "w")
- if fileHandle then
- apiContents = remoteHandle.readAll()
- fileHandle:write(apiContents)
- fileHandle:close()
- else
- printWarning("Could not write file /usr/apis/backpack")
- end
- remoteHandle.close()
- else
- printWarning("Could not retrieve remote file.")
- end
- io.write("Fetching main package list\n")
- remoteHandle = http.get("https://raw.githubusercontent.com/lyqyd/cc-packman/master/packlist")
- local packlistContents = ""
- if remoteHandle then
- if not fs.exists("/etc/repositories") then fs.makeDir("/etc/repositories") end
- local fileHandle = io.open("/etc/repositories/main", "w")
- if fileHandle then
- packlistContents = remoteHandle.readAll()
- fileHandle:write(packlistContents)
- fileHandle:close()
- else
- printWarning("Could not write file /etc/repositories/main")
- end
- remoteHandle.close()
- else
- printWarning("Could not retrieve remote file.")
- end
- os.unloadAPI("backpack")
- loadPackageAPI()
- if not backpack then printError("Backpack API required for bootstrap!") end
- --re-do the file writes for the files above so they end up in the package file list.
- local queue = backpack.newTransactionQueue("main/backpack")
- queue:makeDir("/usr")
- queue:makeDir("/usr/apis")
- queue:addFile("/usr/apis/backpack", apiContents)
- queue:makeDir("/etc")
- queue:makeDir("/etc/repositories")
- queue:addFile("/etc/repositories/main", packlistContents)
- queue:finish()
- end
- backpack.list["main/packman"]:install(getfenv())
- --reload backpack API.
- os.unloadAPI("backpack")
- loadPackageAPI()
- config.manageHelp = false
- saveConfig()
- elseif mode == "install" then
- if #operation.arguments >= 1 then
- local installList = {}
- for packageNumber, packageName in ipairs(operation.arguments) do
- local result = lookupPackage(packageName)
- if not result then
- printWarning("Could not install package "..packageName..".")
- else
- for k,v in pairs(backpack.list[result].dependencies) do
- if not backpack.installed[k] then
- installList[k] = true
- else
- if k == result then
- printInformation("Package "..k.." already installed")
- else
- printInformation("Dependency "..k.." already installed")
- end
- end
- end
- end
- end
- local installString = ""
- for k, v in pairs(installList) do
- installString = installString..k.." "
- end
- if #installString > 0 then
- if not forced then
- io.write("The following packages will be installed: "..installString.."\n")
- io.write("Continue? (Y/n)\n")
- local input = getUserInput("y")
- if string.sub(input:lower(), 1, 1) == "n" then
- return true
- end
- end
- for packageName in pairs(installList) do
- if not install(packageName) then
- printWarning("Could not "..mode.." package "..packageName)
- end
- end
- end
- end
- elseif mode == "update" then
- local updateList = {}
- local installList = {}
- if #operation.arguments >= 1 then
- for _, name in ipairs(operation.arguments) do
- local result = lookupPackage(name, true)
- if result then
- table.insert(updateList, result)
- end
- end
- else
- for k, v in pairs(backpack.installed) do
- if v.files then
- --filters out the disambiguation entries.
- table.insert(updateList, k)
- for name, info in pairs(backpack.list[k].dependencies) do
- if not backpack.installed[name] then
- installList[name] = true
- end
- end
- end
- end
- end
- local installString = ""
- for k, v in pairs(installList) do
- installString = installString..k.." "
- end
- if not forced then
- for i = #updateList, 1, -1 do
- if backpack.installed[updateList[i]].version == backpack.list[updateList[i]].version then
- table.remove(updateList, i)
- end
- end
- end
- if #updateList > 0 or #installString > 0 then
- local updateString = ""
- for i = 1, #updateList do
- updateString = updateString..updateList[i].." "
- end
- if not forced then
- io.write("The following packages will be updated: "..updateString.."\n")
- if #installString > 0 then
- io.write("The following packages will also be installed: "..installString.."\n")
- end
- io.write("Continue? (Y/n)\n")
- local input = getUserInput("y")
- if string.sub(input:lower(), 1, 1) == "n" then
- return true
- end
- end
- local failureCount = 0
- for packageName in pairs(installList) do
- if not install(packageName) then
- printWarning("Could not install package "..packageName)
- end
- end
- for _, packageName in pairs(updateList) do
- if not upgrade(packageName) then
- printWarning("Package "..packageName.." failed to update.")
- failureCount = failureCount + 1
- end
- end
- if failureCount > 0 then
- printWarning(failureCount.." packages failed to update.")
- else
- printInformation("Update complete!")
- end
- else
- io.write("Nothing to do!\n")
- return true
- end
- elseif mode == "remove" then
- if #operation.arguments >= 1 then
- local packageList = {}
- for _, name in ipairs(operation.arguments) do
- local result = lookupPackage(name, true)
- if result then
- table.insert(packageList, result)
- end
- end
- dependeesList = {}
- --find packages which depend on the packages we are removing.
- for pName, pData in pairs(backpack.installed) do
- if pData.version then
- if not packageList[pName] then
- for dName in pairs(backpack.list[pName].dependencies) do
- for _, packName in pairs(packageList) do
- if packName == dName then
- dependeesList[pName] = true
- break
- end
- end
- if dependeesList[pName] then
- break
- end
- end
- end
- end
- end
- local removeString = ""
- local dependeesString = ""
- for i = 1, #packageList do
- removeString = removeString..packageList[i].." "
- if dependeesList[packageList[i]] then
- dependeesList[packageList[i]] = nil
- end
- end
- for dName in pairs(dependeesList) do
- dependeesString = dependeesString..dName.." "
- end
- if #removeString > 0 then
- if not forced then
- io.write("The following packages will be removed: "..removeString.."\n")
- if #dependeesString > 0 then
- io.write("The following packages will also be removed due to missing dependencies: "..dependeesString.."\n")
- end
- io.write("Continue? (y/N)\n")
- local input = getUserInput("y")
- if string.sub(input:lower(), 1, 1) ~= "y" then
- return true
- end
- end
- for pName in pairs(dependeesList) do
- printInformation("Removing "..pName)
- remove(pName)
- end
- for _, pName in pairs(packageList) do
- printInformation("Removing "..pName)
- remove(pName)
- end
- else
- io.write("Nothing to do!\n")
- end
- end
- elseif mode == "install-help" then
- if #operation.arguments >= 1 then
- local installString = ""
- for packageNumber, packageName in ipairs(operation.arguments) do
- local result = lookupPackage(packageName)
- if not result then
- printWarning("Could not find package "..packageName..".")
- else
- installString = installString..result.." "
- end
- end
- if #installString > 0 then
- if not forced then
- io.write("The following packages' help files will be installed: "..installString.."\n")
- io.write("Continue? (Y/n)\n")
- local input = getUserInput("y")
- if string.sub(input:lower(), 1, 1) == "n" then
- return true
- end
- end
- for packageName in string.gmatch(installString, "%S+") do
- if not installHelp(packageName) then
- printWarning("Could not install help files for package "..packageName)
- end
- end
- end
- end
- elseif mode == "upgrade-help" then
- if #operation.arguments >= 1 then
- local upgradeString = ""
- for packageNumber, packageName in ipairs(operation.arguments) do
- local result = lookupPackage(packageName)
- if not result then
- printWarning("Could not find package "..packageName..".")
- else
- upgradeString = upgradeString..result.." "
- end
- end
- if #upgradeString > 0 then
- if not forced then
- io.write("The following packages' help files will be installed: "..upgradeString.."\n")
- io.write("Continue? (Y/n)\n")
- local input = getUserInput("y")
- if string.sub(input:lower(), 1, 1) == "n" then
- return true
- end
- end
- for packageName in string.gmatch(upgradeString, "%S+") do
- if not installHelp(packageName) then
- printWarning("Could not install help files for package "..packageName)
- end
- end
- end
- end
- elseif mode == "remove-help" then
- if #operation.arguments >= 1 then
- local removeString = ""
- for _, name in ipairs(operation.arguments) do
- local result = lookupPackage(name, true)
- if not result then
- printWarning("Could not find package "..name)
- else
- removeString = removeString..result.." "
- end
- end
- if #removeString > 0 then
- if not forced then
- io.write("The following packages' help files will be removed: "..removeString.."\n")
- io.write("Continue? (y/N)\n")
- local input = getUserInput("y")
- if string.sub(input:lower(), 1, 1) ~= "y" then
- return true
- end
- end
- for packageName in string.gmatch(removeString, "%S+") do
- printInformation("Removing help files for package "..packageName)
- removeHelp(packageName)
- end
- else
- io.write("Nothing to do!\n")
- end
- end
- elseif mode == "list" then
- --list all installed packages
- local match = ".*"
- if #operation.arguments == 1 then
- --list with matching.
- match = operation.arguments[1]
- end
- for name, info in pairs(backpack.installed) do
- if info.version then
- if string.match(name, match) then
- io.write(name.." "..info.version.."\n")
- end
- end
- end
- elseif mode == "search" then
- --search all available packages
- local match = ".*"
- if #operation.arguments == 1 then
- --search using a match
- match = operation.arguments[1]
- end
- for name, info in pairs(backpack.list) do
- if info.version then
- if string.match(name, match) then
- io.write((backpack.installed[name] and "I " or "A " )..name.." "..info.version.."\n")
- end
- end
- end
- end
Add Comment
Please, Sign In to add comment