Files
LeonOS/updater.lua
Leonmmcoset 3a4d5d449a feat(updater): 重写并增强更新器功能
重构更新器代码,增加错误处理和进度显示
添加版本检查、JSON库加载和文件下载功能
改进用户界面和失败安全机制
2025-09-07 20:38:26 +08:00

349 lines
9.9 KiB
Lua

-- LeonOS Updater
-- Version: 0.3.8
-- This script ensures reliable updates for the LeonOS operating system
-- 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
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
]]
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
-- 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