diff --git a/data/computercraft/lua/bios.lua b/data/computercraft/lua/bios.lua index 99836a4..5199833 100644 --- a/data/computercraft/lua/bios.lua +++ b/data/computercraft/lua/bios.lua @@ -1,4 +1,4 @@ -_G._HOST = _G._HOST .. " (LeonOS 1.0.0)" +_G._HOST = _G._HOST .. " (LeonOS 1.0.1)" local fs = rawget(_G, "fs") _G._RC_ROM_DIR = _RC_ROM_DIR or (...) and fs.exists("/leonos") and "/leonos" or "/rom" @@ -32,7 +32,7 @@ local rc = { _VERSION = { major = 1, minor = 0, - patch = 0 + patch = 1 }, queueEvent = pull(os, "queueEvent"), startTimer = pull(os, "startTimer"), diff --git a/data/computercraft/lua/rom/programs/wget.lua b/data/computercraft/lua/rom/programs/wget.lua index dc95568..64aa699 100644 --- a/data/computercraft/lua/rom/programs/wget.lua +++ b/data/computercraft/lua/rom/programs/wget.lua @@ -37,6 +37,7 @@ if args[1] == "run" then if not _G.colors then _G.colors = require("colors") end if not _G.textutils then _G.textutils = require("textutils") end if not _G.shell then _G.shell = require("shell") end + if not _G.http then _G.http = require("http") end -- 执行下载的代码 local func, err = load(data, "=", "t", _G) diff --git a/installer.lua b/installer.lua index f2aa275..eda2755 100644 --- a/installer.lua +++ b/installer.lua @@ -1,5 +1,5 @@ -- LeonOS installer -local INSTALLER_VERSION = "1.0.0" +local INSTALLER_VERSION = "1.0.1 Beta 1" local DEFAULT_ROM_DIR = "/leonos" print("Start loading LeonOS installer ("..INSTALLER_VERSION..")...") diff --git a/updater.lua b/updater.lua index fe10da0..dec1fc5 100644 --- a/updater.lua +++ b/updater.lua @@ -1,182 +1,348 @@ --- LeonOS updater: stage 2 +-- LeonOS Updater +-- Version: 0.3.8 +-- This script ensures reliable updates for the LeonOS operating system -local fs = rawget(_G, "fs") -local term = rawget(_G, "term") +-- Ensure core APIs are available +local fs = rawget(_G, "fs") or error("Filesystem API not available") +local term = rawget(_G, "term") or error("Terminal API not available") local http = rawget(_G, "http") +if not http then + -- Try to load the http module if not directly available + local success + success, http = pcall(require, "http") + if not success then + error("HTTP API not available. Cannot perform update.") + end + _G.http = http +end +-- Configuration _G._RC_ROM_DIR = _RC_ROM_DIR or "/leonos" if _RC_ROM_DIR == "/rom" then _RC_ROM_DIR = "/leonos" end --- fail-safe -local start_rc = [[ - -local fs = rawget(_G, "fs") -local term = rawget(_G, "term") - -local w, h = term.getSize() -local function at(x,y) - term.setCursorPos(x,y) - return term -end - -term.setBackgroundColor(0x1) -at(1,1).clearLine() -at(1,h).clearLine() - -local title = "LeonOS Updater (Failure Notice)" -term.setTextColor(0x4000) -at(math.floor(w/2-#title/2), 1).write(title) - -for i=2, h-1, 1 do - term.setBackgroundColor(0x4000) - at(1,i).clearLine() -end - -term.setTextColor(0x1) -local message = { - "A LeonOS update has failed or", - "been interrupted. Your files are", - "intact.", - "", - "Press any key to revert to the ROM.", - "", - "", -} - -for i=1, #message, 1 do - at(3, i+2).write(message[i]) -end - -term.setCursorBlink(true) - -repeat local x = coroutine.yield() until x == "char" - -pcall(fs.delete, _RC_ROM_DIR) -pcall(fs.delete, "/.start_rc.lua") - -os.reboot() -while true do coroutine.yield() end - -]] - -local function at(x,y) - term.setCursorPos(x,y) - return term -end - -local handle = fs.open("/.start_rc.lua", "w") -handle.write(start_rc) -handle.close() - -assert(pcall(function() -local w, h = term.getSize() - -local function dl(f) - local hand, err = http.request(f, nil, nil, true) - local evt - repeat - evt = table.pack(coroutine.yield()) - until evt[1] == "http_success" or evt[1] == "http_failure" - - if evt[1] == "http_failure" then - term.at(1, h).write(evt[3] or "Unknown HTTP error") - - local id = os.startTimer(5) - repeat local _,i = coroutine.yield() until i == id +local REPO_OWNER = "Leonmmcoset" +local REPO_NAME = "LeonOS" +local BRANCH = "main" +local BASE_PATH = "data/computercraft/lua/" +-- Fail-safe mechanism +local function createFailsafe() + local start_rc = [[ + local fs = rawget(_G, "fs") + local term = rawget(_G, "term") + + local function setupScreen() + local w, h = term.getSize() + term.setBackgroundColor(0x4000) -- Red background + term.clear() + + local title = "LeonOS Updater (Failure Notice)" + term.setTextColor(0x1) -- White text + local x = math.floor(w/2 - #title/2) + term.setCursorPos(x > 1 and x or 1, 1) + term.write(title) + + local message = { + "A LeonOS update has failed or", + "been interrupted.", + "Your files should remain intact.", + "", + "Press any key to revert to the ROM." + } + + for i, line in ipairs(message) do + term.setCursorPos(math.floor(w/2 - #line/2), i + 3) + term.write(line) + end + term.setCursorBlink(true) + end + + -- Setup the screen and wait for user input + setupScreen() + repeat local event = {coroutine.yield()} until event[1] == "char" or event[1] == "key" + + -- Cleanup and reboot + pcall(fs.delete, _RC_ROM_DIR) + pcall(fs.delete, "/.start_rc.lua") + os.reboot() while true do coroutine.yield() end - - else - hand = evt[3] - - local data = hand.readAll() - hand.close() - - return data - end -end - -local function ghload(f, c) - return assert(loadstring(dl("https://raw.githubusercontent.com/"..f), - "=ghload("..(c or f)..")"))() -end - -local json = ghload("rxi/json.lua/master/json.lua", "json") - -local function header() - term.setTextColor(0x10) - at(1, 1).clearLine() - at(1, 1).write("LeonOS Updater (Stage 2)") - at(1, 2).clearLine() - at(1, 2).write("===========================") - term.setTextColor(0x1) -end - -local y = 1 -local function write(text) - if y > h-3 then - term.scroll(1) - header() - else - y = y + 1 - end - at(1, y+2).write(text) -end - -header() - -write("Getting repository tree...") - -local repodata = json.decode(dl("https://api.github.com/repos/Leonmmcoset/LeonOS/git/trees/primary?recursive=1")) - -write("Filtering files...") - -local look = "data/computercraft/lua/" -local to_dl = {} -for _, v in pairs(repodata.tree) do - if v.path and v.path:sub(1,#look) == look then - v.path = v.path:sub(#look+1) - v.real_path = v.path:gsub("^/?rom", _RC_ROM_DIR) - to_dl[#to_dl+1] = v - end -end - -write("Creating directories...") - -for i=#to_dl, 1, -1 do - local v = to_dl[i] - if v.type == "tree" then - fs.makeDir(fs.combine(v.real_path)) - table.remove(to_dl, i) - end -end - -write("Downloading files...") - -local function progress(a, b) - at(1, 3).clearLine() - term.setBackgroundColor(0x1) - at(1, 3).write((" "):rep(math.ceil((w-2) * (a/b)))) - term.setBackgroundColor(0x8000) -end - -for i=1, #to_dl do - local v = to_dl[i] - if v.type == "blob" and v.real_path ~= "unbios.lua" then - local data = dl("https://gh.catmak.name/https://raw.githubusercontent.com/Leonmmcoset/LeonOS/refs/heads/main/data/computercraft/lua/"..v.path) - write(v.real_path) - progress(i, #to_dl) - if v.real_path == "bios.lua" then - v.real_path = "/.start_rc.lua" - end - local handle = fs.open(v.real_path, "w") - handle.write(data) + ]] + + local handle, err = fs.open("/.start_rc.lua", "w") + if handle then + handle.write(start_rc) handle.close() + else + error("Failed to create failsafe: " .. (err or "unknown error")) end end -os.reboot() -while true do coroutine.yield() end +-- Helper function to set cursor position +local function at(x, y) + term.setCursorPos(x, y) + return term +end -end)) +-- HTTP request with error handling +local function httpGet(url, timeout) + timeout = timeout or 30 + + local request = http.request(url, nil, nil, true) + if not request then + return nil, "Failed to initiate HTTP request" + end + + local startTime = os.clock() + while true do + local event = table.pack(coroutine.yield()) + + if os.clock() - startTime > timeout then + return nil, "HTTP request timed out" + end + + if event[1] == "http_success" and event[2] == request then + local response = event[3] + local data = response.readAll() + response.close() + return data + elseif event[1] == "http_failure" and event[2] == request then + return nil, "HTTP request failed: " .. (event[3] or "unknown error") + end + end +end + +-- Load JSON library +local function loadJsonLib() + local jsonData, err = httpGet("https://raw.githubusercontent.com/rxi/json.lua/master/json.lua") + if not jsonData then + -- Try alternative source if first fails + jsonData, err = httpGet("https://raw.githubusercontent.com/meepdarknessmeep/json.lua/master/json.lua") + if not jsonData then + return nil, "Failed to load JSON library: " .. err + end + end + + local success, json = pcall(loadstring(jsonData, "json.lua")) + if not success then + return nil, "Failed to parse JSON library: " .. json + end + + return json() +end + +-- Get current version from installer.lua +local function getCurrentVersion() + if fs.exists(_RC_ROM_DIR .. "/installer.lua") then + local handle = fs.open(_RC_ROM_DIR .. "/installer.lua", "r") + if handle then + local content = handle.readAll() + handle.close() + + local version = content:match('local%s+version%s*=%s*"([^"]+)"') + if version then + return version + end + end + end + return "unknown" +end + +-- Main update function +local function updateSystem() + createFailsafe() + + -- Setup terminal + local w, h = term.getSize() + term.clear() + term.setBackgroundColor(0x8000) -- Blue background + term.setTextColor(0x1) -- White text + + -- Header function + local function header() + term.setBackgroundColor(0x8000) + term.setTextColor(0x10) -- Cyan text + at(1, 1).clearLine() + at(1, 1).write("LeonOS Updater") + at(1, 2).clearLine() + at(1, 2).write("=====================") + term.setTextColor(0x1) + end + + -- Write function with scrolling + local y = 1 + local function write(text) + term.setBackgroundColor(0x8000) + if y > h - 3 then + term.scroll(1) + header() + else + y = y + 1 + end + at(1, y + 2).write(text) + end + + -- Progress bar function + local function progress(a, b) + term.setBackgroundColor(0x8000) + at(1, 3).clearLine() + + local barWidth = w - 4 + local progressWidth = math.ceil(barWidth * (a / b)) + + term.setTextColor(0x1) -- White text for progress number + at(1, 3).write(string.format("%d/%d ", a, b)) + + term.setBackgroundColor(0x1) -- White background for progress bar + at(#string.format("%d/%d ", a, b) + 1, 3).write(" ":rep(progressWidth)) + + term.setBackgroundColor(0x8000) -- Reset to blue background + end + + -- Initialize header + header() + + -- Display current version + local currentVersion = getCurrentVersion() + write("Current version: " .. currentVersion) + + -- Load JSON library + write("Loading JSON library...") + local json, err = loadJsonLib() + if not json then + write("Error: " .. err) + write("Press any key to exit.") + term.setCursorBlink(true) + repeat local event = {coroutine.yield()} until event[1] == "char" or event[1] == "key" + return false + end + + -- Get repository tree + write("Getting repository file list...") + local repodata, err = httpGet("https://api.github.com/repos/" .. REPO_OWNER .. "/" .. REPO_NAME .. "/git/trees/" .. BRANCH .. "?recursive=1") + if not repodata then + write("Error: " .. err) + write("Press any key to exit.") + term.setCursorBlink(true) + repeat local event = {coroutine.yield()} until event[1] == "char" or event[1] == "key" + return false + end + + -- Parse repository data + local success, parsedData = pcall(json.decode, repodata) + if not success then + write("Error parsing repository data: " .. parsedData) + write("Press any key to exit.") + term.setCursorBlink(true) + repeat local event = {coroutine.yield()} until event[1] == "char" or event[1] == "key" + return false + end + + -- Filter files to download + write("Filtering files for download...") + local filesToDownload = {} + local directoriesToCreate = {} + + for _, entry in pairs(parsedData.tree) do + if entry.path and entry.path:sub(1, #BASE_PATH) == BASE_PATH then + local relativePath = entry.path:sub(#BASE_PATH + 1) + local realPath = relativePath:gsub("^/?rom", _RC_ROM_DIR) + + if entry.type == "tree" then + table.insert(directoriesToCreate, realPath) + elseif entry.type == "blob" and realPath ~= "unbios.lua" then + table.insert(filesToDownload, { + path = relativePath, + realPath = realPath + }) + end + end + end + + write("Found " .. #filesToDownload .. " files to update.") + + -- Create directories + write("Creating necessary directories...") + for _, dirPath in ipairs(directoriesToCreate) do + pcall(fs.makeDir, dirPath) + end + + -- Download and write files + write("Downloading and installing files...") + local successCount = 0 + local failCount = 0 + local lastProgress = 0 + + for i, fileInfo in ipairs(filesToDownload) do + local url = "https://raw.githubusercontent.com/" .. REPO_OWNER .. "/" .. REPO_NAME .. "/" .. BRANCH .. "/" .. BASE_PATH .. fileInfo.path + local content, err = httpGet(url) + + if content then + -- Ensure the directory exists + local dir = fs.getDir(fileInfo.realPath) + if not fs.isDir(dir) then + pcall(fs.makeDir, dir) + end + + -- Write the file + local handle, writeErr = fs.open(fileInfo.realPath, "w") + if handle then + handle.write(content) + handle.close() + successCount = successCount + 1 + + -- Update progress bar if it changed significantly + local currentProgress = math.floor((i / #filesToDownload) * 10) + if currentProgress > lastProgress then + lastProgress = currentProgress + progress(i, #filesToDownload) + end + else + failCount = failCount + 1 + write("Failed to write " .. fileInfo.realPath .. ": " .. (writeErr or "unknown error")) + end + else + failCount = failCount + 1 + write("Failed to download " .. fileInfo.path .. ": " .. err) + end + end + + -- Final status + progress(#filesToDownload, #filesToDownload) + write("") + write("Update completed!") + write(string.format("Success: %d, Failed: %d", successCount, failCount)) + + if failCount > 0 then + write("") + write("Some files failed to update.") + write("You may need to run the updater again.") + end + + write("") + write("Press any key to reboot and apply changes.") + term.setCursorBlink(true) + repeat local event = {coroutine.yield()} until event[1] == "char" or event[1] == "key" + + -- Cleanup and reboot + pcall(fs.delete, "/.start_rc.lua") + os.reboot() + while true do coroutine.yield() end +end + +-- Run the update with error handling +local success, err = pcall(updateSystem) +if not success then + term.clear() + term.setBackgroundColor(0x4000) + term.setTextColor(0x1) + at(1, 1).write("LeonOS Update Error") + at(1, 3).write("An error occurred during the update:") + at(1, 5).write(err) + at(1, 7).write("Press any key to exit.") + term.setCursorBlink(true) + repeat local event = {coroutine.yield()} until event[1] == "char" or event[1] == "key" + + -- Try to cleanup + pcall(fs.delete, "/.start_rc.lua") +end