diff --git a/installer.lua b/installer.lua index fbb8257..32931fd 100644 --- a/installer.lua +++ b/installer.lua @@ -1,47 +1,532 @@ -local basaltDL = http.get("https://basalt.madefor.cc/versions/latest.lua", _G._GIT_API_KEY and {Authorization = "token " .. _G._GIT_API_KEY}) -assert(basaltDL, "Basalt could not download into memory") -local basalt = load(basaltDL.readAll())() - -local main = basalt.createFrame():setBackground(colors.gray):show() -local steps = basalt.createFrame():setBackground(colors.gray):hide() -local loading = basalt.createFrame():setBackground(colors.gray):hide() - -local w, h = term.getSize() - -main:addLabel():setText("Kristify Installer"):setPosition(w / 2 - 8, 3):setForeground(colors.purple):show() -main:addButton():setText("Accept license & Continue"):setPosition(w / 2 - 14, 7):setSize(30, 5):setBackground(colors.lightBlue):onClick(function() - main:hide() - steps:show() -end):show() -main:addLabel():setText("License: MIT"):setPosition(w / 2 - 5, 13):setForeground(colors.purple):show() - -local status = loading:addLabel():setText("Status: Init"):setPosition(w / 2 - 7, math.floor(h / 2)):setForeground(colors.purple):show() - -steps:addLabel():setText("Steps to be run:"):setPosition(w / 2 - 7, 3):setForeground(colors.purple):show() -steps:addLabel():setText("1. Wipe computer"):setPosition(w / 2 - 7, 5):setForeground(colors.purple):show() -steps:addLabel():setText("2. Create folders"):setPosition(w / 2 - 7, 6):setForeground(colors.purple):show() -steps:addLabel():setText("3. Download source"):setPosition(w / 2 - 7, 7):setForeground(colors.purple):show() -steps:addButton():setText("Confirm installation"):setPosition(w / 2 - 11, 10):setSize(27, 3):setBackground(colors.purple):onClick(function() - steps:hide() - loading:show() - - status:setText("Status: Wiping") - shell.run("rm *") - - status:setText("Creating directories") - fs.makeDir("/src") - fs.makeDir("/data") - fs.makeDir("/data/pages") - - status:setText("Downloading files") - shell.run("wget [singlefileKristify] /src/kristify.lua") - shell.run("wget [configExample] /data/config.lua") - shell.run("wget [productsExample] /data/products.lua") - shell.run("wget [index.xml] /data/pages/index.xml") - shell.run("wget [widget.xml] /data/pages/widget.xml") - shell.run("wget [theme.lua] /data/pages/theme.lua") - - status:setText("Done. Exiting soon.\nPlease change the config\nPlease create a startup that runs `/src/kristify.lua`") -end):show() - +if not http then + error("This installer doesnt work without http API!") +end +print("Please wait...") + +local authenticate = _G._GIT_API_KEY and {Authorization = "Bearer ".._G._GIT_API_KEY} +local basaltDL = http.get("https://basalt.madefor.cc/versions/basalt-1.6.4.lua", _G._GIT_API_KEY and authenticate) +assert(basaltDL, "Couldn't load Basalt into memory!") +local basalt = load(basaltDL.readAll())() +local errors = false + +-- Disk space +local nBarW = 0 +local nLen = 0 +local nStartPos = 0 + +-- Basic frame construction +local base = basalt.createFrame() +local title = base:addLabel("_title") + :setText("Kristify") + :setPosition(2,2) + :setFontSize(2) +local page = base:addLabel("_page") + :setText("1/4") + :setPosition("parent.w-self.w-1",4) +base:addPane() + :setPosition(2,5) + :setSize("parent.w-2",1) + :setBackground(false, '\140', colors.gray) +local content = base:addFrame("_content") + :setPosition(2,6) + :setSize("parent.w-2","parent.h-6") + :setBackground(colors.lightGray) + +-- En- or disable keyboard input +local blockInput = false +basalt.onEvent(function(event, value) + if not blockInput then return end + if event == "char" + or (event:find("key") and value == keys.backspace or value == keys.enter) then + return false + end +end) + +local welcome,licence,destination,install,done = + content:addFrame("_welcome") :setBackground(colors.lightGray):show(), + content:addFrame("_licence") :setBackground(colors.lightGray):hide(), + content:addFrame("_destination"):setBackground(colors.lightGray):hide(), + content:addFrame("_install") :setBackground(colors.lightGray):hide(), + content:addFrame("_done") :setBackground(colors.lightGray):hide() + +local function justinWeHaveAProblem(err) + error(err) +end + +--[[ Actuall installer ]] +local nRequired = 0 + +local tURLs = {} +tURLs.owner = "1Turtle" +tURLs.repo = "test" +tURLs.branch = "main" +tURLs.tree = "https://api.github.com/repos/"..tURLs.owner.."/"..tURLs.repo.."/contents/?ref="..tURLs.branch +tURLs.infos = "https://api.github.com/repos/"..tURLs.owner.."/"..tURLs.repo + +local function httpError(response,err,errResponse) + if not response then + errors = true + justinWeHaveAProblem("Request to GitHub denied; Reason: \'.."..err.."..\' (code "..errResponse.getResponseCode()..").") + return false + end + return true +end + +local function getJSON(response) + if not response then return {} end + local tData = response.readAll() + response.close() + return textutils.unserialiseJSON(tData) +end + +local function generateTree(sURL) + sURL = sURL or tURLs.tree + local function convertItem(item) + if item.type == "file" then + return item.name, item.download_url + elseif item.type == "dir" then + return item.name, generateTree(item.url) + end + end + + local response,sErr,errResponse = http.get(sURL, authenticate) + httpError(response,sErr,errResponse) + + local tData = getJSON(response) + local tTree = { } + + for _,v in pairs(tData) do + local sName,tItem = convertItem(v) + -- Filter stuff that is not needed + if not (sName:sub(1,1) == '.' or sName:find(".md") or sName == "installer.lua" or sName == "docs") then + tTree[sName] = tItem + end + end + + return tTree +end + +local function downloadBlob(sURL, sPath) + local response,sErr,errResponse = http.get(sURL, authenticate) + if not httpError(response,sErr,errResponse) then + return false + end + + local sData = response.readAll() + response.close() + + local file = fs.open(sPath, 'w') + file.write(sData) + file.close() + + return true +end + +-- Installs kristify +local pathToInstall = "" +local function installKristify(install) + sleep(0.2) + if pathToInstall == "" then pathToInstall = "/" end + + local status = install:getObject("_status") + status:editLine(1,"Status: Create folder..") + + justinWeHaveAProblem = function(errMsg) + status:editLine(2,"ERROR! "..errMsg) + return true + end + + fs.delete(pathToInstall) + fs.makeDir(pathToInstall) + + status:editLine(1,"Status: Generating tree..") + status:addLine("") + status:addLine(" \026 "..(pathToInstall:sub(1,1) == "/" and pathToInstall:sub(2) or pathToInstall)) + local tree = generateTree() + + local function listItems(tree,depth) + local size = 0 + for _,_ in pairs(tree) do size = size+1 end + + local index = 1 + for name,item in pairs(tree) do + local begin = " "..("\149 "):rep(depth-1)..(index>=size and '\141' or '\157'):rep(depth-(depth-1))..' ' + + status:addLine(begin..name) + if type(item) == "table" then + listItems(item, depth+1) + end + index = index+1 + end + end + + listItems(tree, 1) + + status:editLine(1,"Status: Download files..") + local index = 3 + local function nextStep(char) + local line = status:getLine(index) + if line then + line = " "..char.." "..line:sub(4) + status:editLine(index, line) + end + + index = index+1 + line = status:getLine(index) + if line then + line = " \026 "..line:sub(4) + status:editLine(index, line) + end + end + nextStep(' ') + + local function downloadItems(tree,sPath) + sleep(0.3) + for name,item in pairs(tree) do + local nextPath = fs.combine(sPath,name) + if type(item) == "table" then + nextStep(' ') + downloadItems(item,nextPath) + else + nextStep(downloadBlob(item,nextPath) and '\183' or '\019') + end + end + end + + downloadItems(tree,pathToInstall) + + for _=1,3 do + status:addLine("") + end + + -- Next page + install:addButton() + :setPosition("parent.w-9","parent.h-2") + :setSize(10,3) + :setText("Done") + :setBackground(colors.green) + :setForeground(colors.white) + :onClick(function() + if errors then + os.queueEvent("terminate") + return + end + install:hide() + title:setText("Done") + page:setText(" :)") + done:show() + blockInput = false + end) +end + + +local function addFrame(frame,nX,nY, nW) + frame:addLabel() + :setPosition(nX,nY) + :setBackground(colors.gray) + :setForeground(colors.lightGray) + :setText('\159') + frame:addPane() + :setPosition(nX,nY) + :setSize(nX+nW,1) + :setBackground(colors.gray, '\143', colors.lightGray) + frame:addLabel() + :setPosition(nX+nW+1,nY) + :setBackground(colors.lightGray) + :setForeground(colors.gray) + :setText('\144') + frame:addLabel() + :setPosition(nX,nY+1) + :setBackground(colors.gray) + :setForeground(colors.lightGray) + :setText("\149") + frame:addLabel() + :setPosition(nX+nW+1,nY+1) + :setBackground(colors.lightGray) + :setForeground(colors.gray) + :setText("\149") + frame:addPane() + :setPosition(nX,nY+2) + :setSize(nX+nW,1) + :setBackground(colors.lightGray, '\131', colors.gray) + frame:addLabel() + :setPosition(nX,nY+2) + :setBackground(colors.lightGray) + :setForeground(colors.gray) + :setText('\130') + frame:addLabel() + :setPosition(nX+nW+1,nY+2) + :setBackground(colors.lightGray) + :setForeground(colors.gray) + :setText('\129') +end + +--[[ Welcome ]] +welcome:addLabel() + :setPosition(1,1) + :setSize("parent.w-2","4") + :setText("Welcome! Thank you for choosing this product! With Kristify you are able to set up a shop easily and comfortable, bringing you much success through the currency Krist!") +welcome:addLabel() + :setPosition(1,6) + :setSize("parent.w-2","3") + :setText("To continue with the installation, click on \"Next\" below. To cancel the installation process, press CTRL+T at any time.") +-- Next page + welcome:addButton() + :setPosition("parent.w-9","parent.h-2") + :setSize(10,3) + :setText("Next") + :setBackground(colors.green) + :setForeground(colors.white) + :onClick(function() + welcome:hide() + title:setText("Licence") + page:setText("2/4") + licence:show() + blockInput = true + end) + +--[[ Licence agreement ]] +local textfield = licence:addTextfield() + :setPosition(1,2) + :setSize("parent.w","parent.h-5") + :setForeground(colors.white) +do + local sKristifyLicence = http.get("https://raw.githubusercontent.com/Kristify/kristify/main/LICENSE") + repeat + local line = sKristifyLicence.readLine() + if line then + textfield:addLine(line) + end + until not line + sKristifyLicence.close() +end +-- Checkbox frame +local nW,nH = licence:getSize() +addFrame(licence, 1, nH-2, 1) +-- Checkbox label +licence:addLabel() + :setPosition(4,"parent.h-1") + :setText("I accept the agreement") +-- Instruction +licence:addLabel() + :setPosition(1,1) + :setText("Read the licence agreement carefully.") +-- Checkbox +local checkbox = licence:addCheckbox() +:setPosition(2,"parent.h-1") +:setBackground(colors.white) +:setSymbol('x') +-- Next page +local nextBtn = licence:addButton() + :setPosition("parent.w-9","parent.h-2") + :setSize(10,3) + :setText("Next") + :setBackground(colors.gray) + :setForeground(colors.black) + :onClick(function() + if checkbox:getValue() then + licence:hide() + title:setText("Destination") + page:setText("3/4") + destination:show() + blockInput = false + + local note = destination:getObject("_note") + justinWeHaveAProblem = function(err) + note:setText(err) + destination:removeObject("_next") + end + + local response,sErr,errResponse = http.get(tURLs.infos, authenticate) + httpError(response,sErr,errResponse) + + local tData = getJSON(response) + nRequired = tData.size or 0 + if nRequired == 0 then + destination:removeObject("_next") + note:setText("Something went wrong! Reason: \'"..sErr.."\' ("..errResponse.getResponseCode()..')') + end + -- Required space + local nRequiredLen = (nRequired*1000)/fs.getCapacity('/')*100 + destination:addFrame() + :setPosition(nStartPos,8) + :setSize(nRequiredLen,1) + :setBackground(colors.lightGray) + :addPane() + :setPosition(1,1) + :setSize("parent.w",1) + :setBackground(false, '\140', colors.lime) + destination:addLabel() + :setPosition(nStartPos+1,9) + :setText("\024Required") + if nRequiredLen+nLen >= 95 then + destination:removeObject("_next") + destination:removeObject("_freeLabel") + note:setText("Not enough space left on disk! At least ~"..nRequired.."kb must be free.") + end + + -- Cooldown + if destination:getObject("_next") then + destination:addThread() + :start(function() + local btn = destination:getObject("_next") + for i=3,1,-1 do + btn:setText(tostring(i)) + sleep(0.8) + end + btn:setText("install") + :setBackground(colors.green) + :setForeground(colors.white) + end) + end + end + end) +-- Checkbox +checkbox:onChange(function(self) + if self:getValue() then + nextBtn + :setBackground(colors.green) + :setForeground(colors.white) + else + nextBtn + :setBackground(colors.gray) + :setForeground(colors.black) + end +end) + +--[[ Destination ]] +local nW = destination:getSize() +destination:addLabel() + :setPosition(1,1) + :setText("Choose where Kristify should be installed:") +-- Input frame +addFrame(destination, 2,2, nW-4) +-- Input +local input = destination:addInput() + :setPosition(3,3) + :setSize("parent.w-4") + :setBackground(colors.white) + :setInputType("text") + :setDefaultText("/") + :setValue("/kristify") +-- Label + Size (from/to) +destination:addLabel() + :setPosition(1,5) + :setText("Local Disk Drive:") +destination:addLabel() + :setPosition(1,8) + :setText("0b") +local maxMb = destination:addLabel() + :setPosition("parent.w-self.w+1",8) + :setText(math.floor(fs.getCapacity('/')/1000000).."mb") +-- Disk Space +local spaceLeft = destination:addProgressbar() + :setPosition(3,8) + :setSize(nW-(2+maxMb:getSize()),1) + :setDirection(0) + :setProgressBar(colors.lightGray, "\140", colors.green) + :setBackground(colors.lightGray) + :setForeground(colors.gray) + :setBackgroundSymbol('\140') + :setProgress( (fs.getCapacity('/')-fs.getFreeSpace('/'))/fs.getCapacity('/')*100+1 ) +-- Disk space labels +nBarW = spaceLeft:getSize() +nLen = math.floor(spaceLeft:getProgress()-0.9) +nStartPos = 3+(nBarW/100*nLen) +destination:addLabel() + :setPosition(nStartPos-1+(nLen<=1 and 1 or 0),7) + :setForeground(colors.gray) + :setText("\025Used") +destination:addLabel("_freeLabel") + :setPosition("parent.w-8",7) + :setForeground(colors.gray) + :setText("Free\025") +-- Note +destination:addLabel("_note") + :setPosition(1,11) + :setSize("parent.w-10",3) + :setForeground(colors.red) + :setText("\026Note: The choosen folder will be completely erased during install!") +-- Next page +destination:addButton("_next") +:setPosition("parent.w-8","parent.h-2") +:setSize(9,3) +:setText("") +:setBackground(colors.gray) +:setForeground(colors.black) +:onClick(function(self) + if self:getBackground() ~= colors.gray then + destination:hide() + title:setText("Installing") + page:setText("4/4") + install:show() + blockInput = true + + pathToInstall = input:getValue() + + -- Install script + destination:addThread() + :start(function() + installKristify(install) + end) + end +end) + +--[[ Install ]] +install:addLabel() + :setPosition(1,1) + :setSize("parent.w",1) + :setText("Take a seat and wait until the magic happened!") +install:addTextfield("_status") + :setPosition(1,2) + :setSize("parent.w","parent.h") + :setForeground(colors.white) + :addKeywords(colors.red, {"ERROR!"}) + :addRule("[\149\157\141]", colors.lightGray) + :addRule("\183", colors.green) + :addRule("\019", colors.red) + +--[[ Done ]] +done:addLabel() + :setPosition(1,1) + :setText("And thats it! You now own a shop! ") +-- Checkbox label +local _,nH = done:getSize() +addFrame(done, 1, nH-2, 1) +done:addLabel() + :setPosition(4,"parent.h-1") + :setText("Enable start on boot") +-- Checkbox +local checkbox = done:addCheckbox() + :setPosition(2,"parent.h-1") + :setBackground(colors.white) + :setSymbol('x') + :setValue(true) + +-- Exit +done:addButton() + :setPosition("parent.w-9","parent.h-2") + :setSize(10,3) + :setText("Exit") + :setBackground(colors.green) + :setForeground(colors.white) + :onClick(function() + local script = "shell.openTab( \""..fs.combine(pathToInstall,"src","init.lua").."\" )" + local file = "kristify.lua" + if checkbox:getValue() then + if fs.exists("/startup.lua") then + file = fs.open("/startup.lua",'a') + else + fs.makeDir("startup") + file = fs.open(fs.combine("startup","kristify.lua"),'w') + end + else + file = fs.open(file, 'w') + end + file.write(script) + file.close() + + os.queueEvent("terminate") + end) + basalt.autoUpdate() \ No newline at end of file