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,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 REPO_OWNER = "Leonmmcoset"
local REPO_NAME = "LeonOS"
local BRANCH = "main"
local BASE_PATH = "data/computercraft/lua/"
local fs = rawget(_G, "fs")
local term = rawget(_G, "term")
-- Fail-safe mechanism
local function createFailsafe()
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
local function setupScreen()
local w, h = term.getSize()
term.setBackgroundColor(0x4000) -- Red background
term.clear()
term.setBackgroundColor(0x1)
at(1,1).clearLine()
at(1,h).clearLine()
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 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 = {
local message = {
"A LeonOS update has failed or",
"been interrupted. Your files are",
"intact.",
"been interrupted.",
"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
at(3, i+2).write(message[i])
end
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)
-- 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"
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
-- 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
hand = evt[3]
local data = hand.readAll()
hand.close()
return data
error("Failed to create failsafe: " .. (err or "unknown error"))
end
end
local function ghload(f, c)
return assert(loadstring(dl("https://raw.githubusercontent.com/"..f),
"=ghload("..(c or f)..")"))()
-- Helper function to set cursor position
local function at(x, y)
term.setCursorPos(x, y)
return term
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()
term.setTextColor(0x10)
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 (Stage 2)")
at(1, 1).write("LeonOS Updater")
at(1, 2).clearLine()
at(1, 2).write("===========================")
at(1, 2).write("=====================")
term.setTextColor(0x1)
end
end
local y = 1
local function write(text)
if y > h-3 then
-- 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
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
at(1, y + 2).write(text)
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))))
-- Progress bar function
local function progress(a, b)
term.setBackgroundColor(0x8000)
end
at(1, 3).clearLine()
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"
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
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()
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
os.reboot()
while true do coroutine.yield() 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"
end))
-- Try to cleanup
pcall(fs.delete, "/.start_rc.lua")
end