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")
_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"),

View File

@@ -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, "=<wget-run>", "t", _G)

View File

@@ -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..")...")

View File

@@ -1,120 +1,178 @@
-- 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 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()
local function at(x,y)
term.setCursorPos(x,y)
return term
end
term.setBackgroundColor(0x1)
at(1,1).clearLine()
at(1,h).clearLine()
term.setBackgroundColor(0x4000) -- Red background
term.clear()
local title = "LeonOS Updater (Failure Notice)"
term.setTextColor(0x4000)
at(math.floor(w/2-#title/2), 1).write(title)
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)
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.",
"",
"been interrupted.",
"Your files should remain intact.",
"",
"Press any key to revert to the ROM."
}
for i=1, #message, 1 do
at(3, i+2).write(message[i])
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
term.setCursorBlink(true)
repeat local x = coroutine.yield() until x == "char"
-- 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
]]
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
-- Helper function to set cursor position
local function at(x, y)
term.setCursorPos(x, y)
return term
end
local handle = fs.open("/.start_rc.lua", "w")
handle.write(start_rc)
-- 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()
assert(pcall(function()
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
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()
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")
-- Header function
local function header()
term.setTextColor(0x10)
term.setBackgroundColor(0x8000)
term.setTextColor(0x10) -- Cyan text
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).write("===========================")
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()
@@ -124,59 +182,167 @@ local function write(text)
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()
write("Getting repository tree...")
-- Display current version
local currentVersion = getCurrentVersion()
write("Current version: " .. currentVersion)
local repodata = json.decode(dl("https://api.github.com/repos/Leonmmcoset/LeonOS/git/trees/primary?recursive=1"))
-- 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
write("Filtering files...")
-- 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
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
-- 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("Creating directories...")
write("Found " .. #filesToDownload .. " files to update.")
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
-- Create directories
write("Creating necessary directories...")
for _, dirPath in ipairs(directoriesToCreate) do
pcall(fs.makeDir, dirPath)
end
write("Downloading files...")
-- Download and write files
write("Downloading and installing files...")
local successCount = 0
local failCount = 0
local lastProgress = 0
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)
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
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)
-- 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
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