feat(updater): 重写并增强更新器功能

重构更新器代码,增加错误处理和进度显示
添加版本检查、JSON库加载和文件下载功能
改进用户界面和失败安全机制
This commit is contained in:
2025-09-07 20:38:26 +08:00
parent 8745f2ee63
commit 3a4d5d449a
4 changed files with 338 additions and 171 deletions

View File

@@ -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") local fs = rawget(_G, "fs")
_G._RC_ROM_DIR = _RC_ROM_DIR or (...) and fs.exists("/leonos") and "/leonos" or "/rom" _G._RC_ROM_DIR = _RC_ROM_DIR or (...) and fs.exists("/leonos") and "/leonos" or "/rom"
@@ -32,7 +32,7 @@ local rc = {
_VERSION = { _VERSION = {
major = 1, major = 1,
minor = 0, minor = 0,
patch = 0 patch = 1
}, },
queueEvent = pull(os, "queueEvent"), queueEvent = pull(os, "queueEvent"),
startTimer = pull(os, "startTimer"), startTimer = pull(os, "startTimer"),

View File

@@ -37,6 +37,7 @@ if args[1] == "run" then
if not _G.colors then _G.colors = require("colors") end if not _G.colors then _G.colors = require("colors") end
if not _G.textutils then _G.textutils = require("textutils") end if not _G.textutils then _G.textutils = require("textutils") end
if not _G.shell then _G.shell = require("shell") 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, "=<wget-run>", "t", _G) local func, err = load(data, "=<wget-run>", "t", _G)

View File

@@ -1,5 +1,5 @@
-- LeonOS installer -- LeonOS installer
local INSTALLER_VERSION = "1.0.0" local INSTALLER_VERSION = "1.0.1 Beta 1"
local DEFAULT_ROM_DIR = "/leonos" local DEFAULT_ROM_DIR = "/leonos"
print("Start loading LeonOS installer ("..INSTALLER_VERSION..")...") print("Start loading LeonOS installer ("..INSTALLER_VERSION..")...")

View File

@@ -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") -- Ensure core APIs are available
local term = rawget(_G, "term") 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") 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" _G._RC_ROM_DIR = _RC_ROM_DIR or "/leonos"
if _RC_ROM_DIR == "/rom" then _RC_ROM_DIR = "/leonos" end if _RC_ROM_DIR == "/rom" then _RC_ROM_DIR = "/leonos" end
-- fail-safe local REPO_OWNER = "Leonmmcoset"
local start_rc = [[ local REPO_NAME = "LeonOS"
local BRANCH = "main"
local BASE_PATH = "data/computercraft/lua/"
local fs = rawget(_G, "fs") -- Fail-safe mechanism
local term = rawget(_G, "term") local function createFailsafe()
local start_rc = [[
local fs = rawget(_G, "fs")
local term = rawget(_G, "term")
local w, h = term.getSize() local function setupScreen()
local function at(x,y) local w, h = term.getSize()
term.setCursorPos(x,y) term.setBackgroundColor(0x4000) -- Red background
return term term.clear()
end
term.setBackgroundColor(0x1) local title = "LeonOS Updater (Failure Notice)"
at(1,1).clearLine() term.setTextColor(0x1) -- White text
at(1,h).clearLine() local x = math.floor(w/2 - #title/2)
term.setCursorPos(x > 1 and x or 1, 1)
term.write(title)
local title = "LeonOS Updater (Failure Notice)" local message = {
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", "A LeonOS update has failed or",
"been interrupted. Your files are", "been interrupted.",
"intact.", "Your files should remain intact.",
"", "",
"Press any key to revert to the ROM.", "Press any key to revert to the ROM."
"", }
"",
}
for i=1, #message, 1 do for i, line in ipairs(message) do
at(3, i+2).write(message[i]) term.setCursorPos(math.floor(w/2 - #line/2), i + 3)
end term.write(line)
end
term.setCursorBlink(true)
end
term.setCursorBlink(true) -- Setup the screen and wait for user input
setupScreen()
repeat local event = {coroutine.yield()} until event[1] == "char" or event[1] == "key"
repeat local x = coroutine.yield() until x == "char" -- Cleanup and reboot
pcall(fs.delete, _RC_ROM_DIR)
pcall(fs.delete, _RC_ROM_DIR) pcall(fs.delete, "/.start_rc.lua")
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
os.reboot() os.reboot()
while true do coroutine.yield() end while true do coroutine.yield() end
]]
local handle, err = fs.open("/.start_rc.lua", "w")
if handle then
handle.write(start_rc)
handle.close()
else else
hand = evt[3] error("Failed to create failsafe: " .. (err or "unknown error"))
local data = hand.readAll()
hand.close()
return data
end end
end end
local function ghload(f, c) -- Helper function to set cursor position
return assert(loadstring(dl("https://raw.githubusercontent.com/"..f), local function at(x, y)
"=ghload("..(c or f)..")"))() term.setCursorPos(x, y)
return term
end end
local json = ghload("rxi/json.lua/master/json.lua", "json") -- HTTP request with error handling
local function httpGet(url, timeout)
timeout = timeout or 30
local function header() local request = http.request(url, nil, nil, true)
term.setTextColor(0x10) 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).clearLine()
at(1, 1).write("LeonOS Updater (Stage 2)") at(1, 1).write("LeonOS Updater")
at(1, 2).clearLine() at(1, 2).clearLine()
at(1, 2).write("===========================") at(1, 2).write("=====================")
term.setTextColor(0x1) term.setTextColor(0x1)
end end
local y = 1 -- Write function with scrolling
local function write(text) local y = 1
if y > h-3 then local function write(text)
term.setBackgroundColor(0x8000)
if y > h - 3 then
term.scroll(1) term.scroll(1)
header() header()
else else
y = y + 1 y = y + 1
end end
at(1, y+2).write(text) 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
end
write("Creating directories...") -- Progress bar function
local function progress(a, b)
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) term.setBackgroundColor(0x8000)
end at(1, 3).clearLine()
for i=1, #to_dl do local barWidth = w - 4
local v = to_dl[i] local progressWidth = math.ceil(barWidth * (a / b))
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) term.setTextColor(0x1) -- White text for progress number
write(v.real_path) at(1, 3).write(string.format("%d/%d ", a, b))
progress(i, #to_dl)
if v.real_path == "bios.lua" then term.setBackgroundColor(0x1) -- White background for progress bar
v.real_path = "/.start_rc.lua" at(#string.format("%d/%d ", a, b) + 1, 3).write(" ":rep(progressWidth))
term.setBackgroundColor(0x8000) -- Reset to blue background
end end
local handle = fs.open(v.real_path, "w")
handle.write(data) -- 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() 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 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 end
os.reboot() -- Run the update with error handling
while true do coroutine.yield() end 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"
end)) -- Try to cleanup
pcall(fs.delete, "/.start_rc.lua")
end