refactored updater
This commit is contained in:
369
updater.lua
369
updater.lua
@@ -1,128 +1,115 @@
|
|||||||
local args = {...}
|
local args = {...}
|
||||||
local internet=require("internet")
|
local internet=require("internet")
|
||||||
local text=require("text")
|
|
||||||
local filesystem=require("filesystem")
|
local filesystem=require("filesystem")
|
||||||
local unicode=require("unicode")
|
local unicode=require("unicode")
|
||||||
local term=require("term")
|
|
||||||
local event=require("event")
|
|
||||||
local keyboard=require("keyboard")
|
|
||||||
|
|
||||||
local repository = "seesberger/PowerManager"
|
YES = {"y","yes","Y","Yes","YES"}
|
||||||
local repoTargetPath = "/usr/PowerManager/"
|
NO = {"n","no","N","No","NO"}
|
||||||
local libTargetPath = "/lib/"
|
|
||||||
|
|
||||||
local helpText = "This updater pulls the git files for installation and application updates.\n"..
|
SupportedRemotes = {
|
||||||
"Usage:\n" ..
|
Github = {
|
||||||
"updater <option> - no args: manual update and install\n"..
|
BaseUrl = "https://github.com/",
|
||||||
" '' -h - this help text\n" ..
|
Implemented = true
|
||||||
" '' -a - automatic update no install\n" ..
|
},
|
||||||
" '' -i - automatic update and install"
|
Gitea = {
|
||||||
|
BaseUrl = "https://git.realrobin.io",
|
||||||
|
Implemented = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function manualUpdate()
|
Repository = {}
|
||||||
print("Manual repo pull...")
|
|
||||||
downloadRepo(repository, repoTargetPath, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
function manualInstall()
|
---default repo to update
|
||||||
print("installing shortcut...")
|
DefaultRepository = {
|
||||||
os.execute("mv "..repoTargetPath.."shortcut.lua /usr/bin/powerman.lua")
|
Owner = "seesberger",
|
||||||
end
|
Name = "PowerManager",
|
||||||
|
ShortName = "powerman",
|
||||||
|
RepoIdentifier = "seesberger/PowerManager",
|
||||||
|
Remote = SupportedRemotes.Github,
|
||||||
|
CurrentBranch = "master",
|
||||||
|
CurrentLocalPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
function automaticUpdate()
|
DefaultTemporaryDownloadPath = "/home/.tmp/git/"
|
||||||
print("Auto repo pull...")
|
DefaultInstallationPath = "/usr/"
|
||||||
downloadRepo(repository, repoTargetPath, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
function automaticInstall()
|
local function askYesOrNoQuestion(question, expectedTrue, expectedFalse, defaultYesOnEnter)
|
||||||
print("downloading GUI-API")
|
local function checkContains(array,value)
|
||||||
downloadDependencies()
|
for idx, val in array do
|
||||||
manualInstall()
|
if value == val then return true
|
||||||
end
|
end
|
||||||
|
return false
|
||||||
function downloadDependencies(automatic)
|
|
||||||
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/shapes_default.lua /lib/shapes_default.lua")
|
|
||||||
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/GUI.lua /lib/GUI.lua")
|
|
||||||
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/term_mod.lua /lib/term_mod.lua")
|
|
||||||
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/tech_demo.lua /home/GUI_tech_demo.lua")
|
|
||||||
end
|
|
||||||
|
|
||||||
function downloadRepo(repo, target, automatic)
|
|
||||||
|
|
||||||
if not repo:match("^[%w-.]*/[%w-.]*$") then
|
|
||||||
print('"'..repo..'" does not look like a valid repo identifier.\nShould be <owner>/<reponame>')
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
target=target and ("/"..target:match("^/?(.-)/?$").."/") or "/tmp/"..repo
|
if defaultYesOnEnter==true then print(question.." [Y/n]") else print(question.." [y/N]") end
|
||||||
|
while true do
|
||||||
|
local userInput = io.read("l")
|
||||||
|
if checkContains({""}, userInput) then return defaultYesOnEnter end
|
||||||
|
if checkContains(expectedTrue, userInput) then return true end
|
||||||
|
if checkContains(expectedFalse, userInput) then return false end
|
||||||
|
print("Please answer with yes or no. You can also press ENTER to choose the default option.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function askTextQuestion(question, defaultAnswerOnEnter, allowOnly)
|
||||||
|
local allowedInputsString = ""
|
||||||
|
for idx, entry in allowOnly do allowedInputsString = allowedInputsString..entry end
|
||||||
|
if allowOnly then print(question.." ["..allowedInputsString.."]")
|
||||||
|
else print(question) end
|
||||||
|
local userInput = nil
|
||||||
|
local found = false
|
||||||
|
repeat
|
||||||
|
userInput = io.read("l")
|
||||||
|
if allowOnly then
|
||||||
|
for idx, entry in allowOnly do if entry == userInput then found = true end end
|
||||||
|
else break end
|
||||||
|
until found
|
||||||
|
if userInput == "" then return defaultAnswerOnEnter else return userInput end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- todo: pcall and catch errors
|
||||||
|
local function downloadRepo(repository, autoOverride)
|
||||||
|
|
||||||
|
local function validateRepositoryIdentifier(repository)
|
||||||
|
if not repository.RepoIdentifier:match("^[%w-.]*/[%w-.]*$") then
|
||||||
|
print('"'..repository.RepoIdentifier..'" does not look like a valid repo identifier.\nShould be <owner>/<reponame>')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
validateRepositoryIdentifier(repository)
|
||||||
|
|
||||||
|
local function makeDirIfNotExists(target)
|
||||||
if filesystem.exists(target) then
|
if filesystem.exists(target) then
|
||||||
if not filesystem.isDirectory(target) then
|
if not filesystem.isDirectory(target) then error("target directory already exists and is not a directory.") end
|
||||||
print("target directory already exists and is not a directory.")
|
if filesystem.get(target).isReadOnly() then error("target directory is read-only.") end
|
||||||
return
|
|
||||||
end
|
|
||||||
if filesystem.get(target).isReadOnly() then
|
|
||||||
print("target directory is read-only.")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
if not filesystem.makeDirectory(target) then
|
if not filesystem.makeDirectory(target) then error("directory creation failed") end
|
||||||
print("target directory is read-only")
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- FIXME: If download only mode is enabled, set this to /home. still todo
|
||||||
|
local targetDownloadPath = DefaultTemporaryDownloadPath..repository.RepoIdentifier
|
||||||
|
repository.CurrentLocalPath = targetDownloadPath
|
||||||
|
local success, err = pcall(makeDirIfNotExists, targetDownloadPath)
|
||||||
|
if not success then error("the download failed because of filesystem errors.") end
|
||||||
|
|
||||||
|
local function fetchFilesAndSubdirs(repository, remoteType, dir)
|
||||||
-- this isn't acually used, but it is tested and works on decoding the base64 encoded data that github
|
dir = dir or "" -- default value, start at root dir
|
||||||
--sends for some queries, leaving it in here for possible future/related use, might be able to pull
|
if remoteType == SupportedRemotes.Github then
|
||||||
--and display difs and things like that?
|
print("fetching contents for "..repository.RepoIdentifier..dir)
|
||||||
local symb="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
local githubApiUrl="https://api.github.com/repos/"..repository.RepoIdentifier.."/contents"..dir
|
||||||
|
local success,chunks=pcall(internet.request,githubApiUrl)
|
||||||
function decode64(text)
|
|
||||||
local val,bits=0,0
|
|
||||||
local output=""
|
|
||||||
for ch in text:gmatch(".") do
|
|
||||||
if symb:find(ch) then
|
|
||||||
--print("ch "..ch.."-> "..(symb:find(ch)-1))
|
|
||||||
val=bit32.lshift(val,6)+symb:find(ch)-1
|
|
||||||
else
|
|
||||||
print(ch.."?")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
bits=bits+6
|
|
||||||
--print("bits : "..bits)
|
|
||||||
--print(string.format("val : 0x%04x",val))
|
|
||||||
if bits>=8 then
|
|
||||||
local och=unicode.char(bit32.rshift(val,bits-8))
|
|
||||||
--print("os<<"..och)
|
|
||||||
--print("& with "..(2^(bits-8)-1))
|
|
||||||
val=bit32.band(val,2^(bits-8)-1)
|
|
||||||
bits=bits-8
|
|
||||||
--print(string.format("val : 0x%04x",val))
|
|
||||||
output=output..och
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return output
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local function gitContents(repo, dir)
|
|
||||||
print("fetching contents for "..repo..dir)
|
|
||||||
local url="https://api.github.com/repos/"..repo.."/contents"..dir
|
|
||||||
local result,response=pcall(internet.request,url)
|
|
||||||
local raw=""
|
local raw=""
|
||||||
local files={}
|
local files={}
|
||||||
local directories={}
|
local directories={}
|
||||||
|
|
||||||
if result then
|
if success then for chunk in chunks do raw=raw..chunk end
|
||||||
for chunk in response do
|
else error("you've been cut off. Serves you right.") end
|
||||||
raw=raw..chunk
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error("you've been cut off. Serves you right.")
|
|
||||||
end
|
|
||||||
|
|
||||||
response=nil
|
--- do not question the magic of the outer gods
|
||||||
|
--- turns raw response into t, which has usable fields.
|
||||||
raw=raw:gsub("%[","{"):gsub("%]","}"):gsub("(\".-\"):(.-[,{}])",function(a,b) return "["..a.."]="..b end)
|
raw=raw:gsub("%[","{"):gsub("%]","}"):gsub("(\".-\"):(.-[,{}])",function(a,b) return "["..a.."]="..b end)
|
||||||
local t=load("return "..raw)()
|
local t=load("return "..raw)()
|
||||||
|
|
||||||
@@ -130,7 +117,7 @@ function downloadRepo(repo, target, automatic)
|
|||||||
if t[i].type=="dir" then
|
if t[i].type=="dir" then
|
||||||
table.insert(directories,dir.."/"..t[i].name)
|
table.insert(directories,dir.."/"..t[i].name)
|
||||||
|
|
||||||
local subfiles,subdirs=gitContents(repo,dir.."/"..t[i].name)
|
local subfiles,subdirs=fetchFilesAndSubdirs(repository.RepoIdentifier,dir.."/"..t[i].name)
|
||||||
for i=1,#subfiles do
|
for i=1,#subfiles do
|
||||||
table.insert(files,subfiles[i])
|
table.insert(files,subfiles[i])
|
||||||
end
|
end
|
||||||
@@ -143,108 +130,124 @@ function downloadRepo(repo, target, automatic)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return files, directories
|
return files, directories
|
||||||
|
else error("not Implemented") end
|
||||||
end
|
end
|
||||||
|
|
||||||
local files,dirs=gitContents(repo,"")
|
--- fetch and make dirs in the target download path recursively
|
||||||
|
local files,dirs=fetchFilesAndSubdirs(repository.RepoIdentifier, "", SupportedRemotes.Github)
|
||||||
for i=1,#dirs do
|
for i=1,#dirs do
|
||||||
print("making dir "..target..dirs[i])
|
local success, err = pcall(makeDirIfNotExists, targetDownloadPath..dirs[i])
|
||||||
if filesystem.exists(target..dirs[i]) then
|
if not success then error(("the download failed because of filesystem errors. %x"):format(err)) end
|
||||||
if not filesystem.isDirectory(target..dirs[i]) then
|
|
||||||
print("error: directory "..target..dirs[i].." blocked by file with the same name")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
else
|
|
||||||
filesystem.makeDirectory(target..dirs[i])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local replaceMode="ask"
|
local replaceMode="ask"
|
||||||
if automatic == true then replaceMode = "always" end
|
if autoOverride == true then replaceMode = "always" end
|
||||||
|
|
||||||
|
local function downloadFiles(files, targetDownloadPath, replaceMode)
|
||||||
for i=1,#files do
|
for i=1,#files do
|
||||||
local replace=nil
|
local replace=nil
|
||||||
if filesystem.exists(target..files[i]) then
|
if filesystem.exists(targetDownloadPath..files[i]) then
|
||||||
if filesystem.isDirectory(target..files[i]) then
|
--- FIXME dir löschen statt error
|
||||||
print("Error: file "..target..files[i].." blocked by directory with same name!")
|
if filesystem.isDirectory(targetDownloadPath..files[i]) then error("file "..targetDownloadPath..files[i].." blocked by directory with same name!") end
|
||||||
return
|
if not replaceMode == "ask" then
|
||||||
|
replace = (replaceMode=="always")
|
||||||
|
replace = not (replaceMode=="never")
|
||||||
end
|
end
|
||||||
if replaceMode=="always" then
|
else print("\nFile "..targetDownloadPath..files[i].." already exists.\nReplace with new version?") end
|
||||||
replace=true
|
|
||||||
elseif replaceMode=="never" then
|
|
||||||
replace=false
|
|
||||||
else
|
|
||||||
print("\nFile "..target..files[i].." already exists.\nReplace with new version?")
|
|
||||||
local response=""
|
local response=""
|
||||||
while replace==nil do
|
if replace == nil then
|
||||||
term.write("yes,no,always,skip all[ynAS]: ")
|
--- FIXME: ggf falsch und buggy
|
||||||
local char
|
local userInput = askTextQuestion("\nFile "..targetDownloadPath..files[i].." already exists.\nReplace with new version?","n",{"y","n","A","S"})
|
||||||
repeat
|
|
||||||
_,_,char=event.pull("key_down")
|
if userInput=="A" then
|
||||||
until not keyboard.isControl(char)
|
|
||||||
char=unicode.char(char)
|
|
||||||
print(char)
|
|
||||||
if char=="A" then
|
|
||||||
replaceMode="always"
|
replaceMode="always"
|
||||||
replace=true
|
userInput="y"
|
||||||
char="y"
|
elseif userInput=="S" then
|
||||||
elseif char=="S" then
|
|
||||||
replaceMode="never"
|
replaceMode="never"
|
||||||
replace=false
|
userInput="n"
|
||||||
char="n"
|
elseif userInput == "y" then replace=true
|
||||||
elseif char:lower()=="y" then
|
elseif userInput == "n" then replace=false
|
||||||
replace=true
|
|
||||||
elseif char:lower()=="n" then
|
|
||||||
replace=false
|
|
||||||
else
|
|
||||||
print("invalid response.")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
if replace then filesystem.remove(targetDownloadPath..files[i]) end
|
||||||
if replace then
|
if replace or replace == nil then
|
||||||
filesystem.remove(target..files[i])
|
--- TODO @Freddy: Coole Animation hinzufügen
|
||||||
end
|
print("downloading file "..files[i])
|
||||||
end
|
local url=repository.Remote.BaseUrl..repository.Name.."/"..repository.CurrentBranch..files[i]
|
||||||
if replace~=false then
|
local success,response=pcall(internet.request,url)
|
||||||
print("downloading "..files[i])
|
if success then
|
||||||
local url="https://raw.github.com/"..repo.."/master"..files[i]
|
|
||||||
local result,response=pcall(internet.request,url)
|
|
||||||
if result then
|
|
||||||
local raw=""
|
local raw=""
|
||||||
for chunk in response do
|
for chunk in response do
|
||||||
raw=raw..chunk
|
raw=raw..chunk
|
||||||
end
|
end
|
||||||
print("writing to "..target..files[i])
|
print("writing to "..targetDownloadPath..files[i])
|
||||||
local file=io.open(target..files[i],"w")
|
local file=io.open(targetDownloadPath..files[i],"w")
|
||||||
|
if file then -- might be nil under wierd circumstances
|
||||||
file:write(raw)
|
file:write(raw)
|
||||||
file:close()
|
file:close()
|
||||||
|
end
|
||||||
|
else error("a file did not download correctly. aborting") end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
success, err = pcall(downloadFiles, files, targetDownloadPath, replaceMode)
|
||||||
|
if success then print("All files downloaded successfully.") else error(err) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function installShortcut(currentRepoPath, shortcutName)
|
||||||
|
print("installing shortcut...")
|
||||||
|
os.execute("mv "..currentRepoPath.."shortcut.lua /usr/bin/"..shortcutName..".lua")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- todo: automatic read of dependency list (txt file containing lines with <link> <dst> or somethink like it)
|
||||||
|
--- removeme
|
||||||
|
local function legacyInstallDependencies(enabled)
|
||||||
|
if enabled==true then
|
||||||
|
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/shapes_default.lua /lib/shapes_default.lua")
|
||||||
|
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/GUI.lua /lib/GUI.lua")
|
||||||
|
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/term_mod.lua /lib/term_mod.lua")
|
||||||
|
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/tech_demo.lua /home/GUI_tech_demo.lua")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- fix legacy shit
|
||||||
|
local function runFullInstallTask(repository)
|
||||||
|
--- first, download the actual repo.
|
||||||
|
--- then, find dependencies - if then exist, download and install them
|
||||||
|
--- then, install the actual program
|
||||||
|
legacyInstallDependencies(true) --only for testing while new version not implemented yet
|
||||||
|
print("downloading "..repository.RepoIdentifier)
|
||||||
|
downloadRepo(repository, false) --enable autooverwrite in other situations still todo
|
||||||
|
installShortcut(repository.CurrentLocalPath)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printHelpText()
|
||||||
|
local helpText = "This updater pulls the git files for installation and application updates.\n"..
|
||||||
|
"Usage:\n" ..
|
||||||
|
"updater <option> - no args: manual update and install\n"..
|
||||||
|
" '' -h or --help - this help text"
|
||||||
|
print(helpText)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run(cliArgs)
|
||||||
|
if #cliArgs<1 then
|
||||||
|
print("No Arguments given. For help, please check -h or --help")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if cliArgs[1] == ("-h" or "--help") then
|
||||||
|
printHelpText()
|
||||||
|
return
|
||||||
|
elseif cliArgs[1] == ("--setup-default") then
|
||||||
|
--- ask user about repo
|
||||||
|
Repository = DefaultRepository
|
||||||
|
if askYesOrNoQuestion("Use default config? (github::seesberger/PowerManager)?",YES,NO,true) then runFullInstallTask(Repository) end
|
||||||
|
return
|
||||||
else
|
else
|
||||||
print("failed, skipping")
|
print('"'..cliArgs[1]..'" - Bad argument. Try --help')
|
||||||
end
|
return
|
||||||
end
|
print("Program exited.")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #args<1 then
|
run(args)
|
||||||
print("No Arguments given. Manual process...")
|
|
||||||
manualUpdate()
|
|
||||||
automaticInstall()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if args[1] == "-h" then
|
|
||||||
print(helpText)
|
|
||||||
return
|
|
||||||
elseif args[1] == "-a" then
|
|
||||||
automaticUpdate()
|
|
||||||
return
|
|
||||||
elseif args[1] == "-i" then
|
|
||||||
automaticUpdate()
|
|
||||||
automaticInstall()
|
|
||||||
return
|
|
||||||
else
|
|
||||||
print('"'..args[1]..'" - Bad argument.')
|
|
||||||
print(helpText)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user