feat: add station auto update publishing
Publish the latest station script from the console so online hosts download and reboot immediately while offline hosts update on their next load.
This commit is contained in:
+180
-2
@@ -2,11 +2,12 @@
|
||||
-- ============================================================================
|
||||
-- 1. Configuration & Constants
|
||||
-- ============================================================================
|
||||
local APP_VERSION_TEXT = "v1.0.44"
|
||||
local APP_VERSION_TEXT = "v1.0.45"
|
||||
|
||||
local CONFIG = {
|
||||
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
||||
API_URL = "http://stabadmin.fse-media.group",
|
||||
STARTUP_PATH = "/startup",
|
||||
SYNC_INTERVAL = 30,
|
||||
AUDIO_BASE = "Audio/",
|
||||
AUDIO_STATION = "Audio/Station/",
|
||||
@@ -55,7 +56,8 @@ local State = {
|
||||
currentPlaying = nil,
|
||||
currentPriority = -1,
|
||||
interruptSeq = 0,
|
||||
tickRate = 20
|
||||
tickRate = 20,
|
||||
updateInProgress = false
|
||||
}
|
||||
|
||||
-- ============================================================================
|
||||
@@ -1813,6 +1815,162 @@ function Utils.getStationAudio(code, langPrefix)
|
||||
return langPrefix .. code
|
||||
end
|
||||
|
||||
local AutoUpdate = {}
|
||||
|
||||
function AutoUpdate.fetch(url)
|
||||
if not http then
|
||||
return nil, "HTTP API unavailable"
|
||||
end
|
||||
|
||||
if http.checkURL then
|
||||
local ok, err = http.checkURL(url)
|
||||
if not ok then
|
||||
return nil, err or "URL blocked"
|
||||
end
|
||||
end
|
||||
|
||||
local res, err = http.get(url)
|
||||
if not res then
|
||||
return nil, err or "request failed"
|
||||
end
|
||||
|
||||
local body = res.readAll()
|
||||
res.close()
|
||||
|
||||
if type(body) ~= "string" or #body == 0 then
|
||||
return nil, "empty response"
|
||||
end
|
||||
|
||||
return body
|
||||
end
|
||||
|
||||
function AutoUpdate.writeFile(path, content)
|
||||
local dir = fs.getDir(path)
|
||||
if dir and dir ~= "" and not fs.exists(dir) then
|
||||
fs.makeDir(dir)
|
||||
end
|
||||
|
||||
local handle = fs.open(path, "w")
|
||||
if not handle then
|
||||
return nil, "cannot open " .. path
|
||||
end
|
||||
|
||||
handle.write(content)
|
||||
handle.close()
|
||||
return true
|
||||
end
|
||||
|
||||
function AutoUpdate.getStartupPath()
|
||||
if shell and shell.resolve then
|
||||
return shell.resolve(CONFIG.STARTUP_PATH)
|
||||
end
|
||||
return CONFIG.STARTUP_PATH
|
||||
end
|
||||
|
||||
function AutoUpdate.getDownloadUrl(info)
|
||||
if type(info) == "table" then
|
||||
local direct = Utils.trim(info.downloadUrl)
|
||||
if direct ~= "" then
|
||||
return direct
|
||||
end
|
||||
|
||||
local path = Utils.trim(info.downloadPath)
|
||||
if path ~= "" then
|
||||
return CONFIG.API_URL .. path
|
||||
end
|
||||
end
|
||||
|
||||
return CONFIG.API_URL .. "/downloads/station_broadcast.lua"
|
||||
end
|
||||
|
||||
function AutoUpdate.fetchInfo()
|
||||
local body, err = AutoUpdate.fetch(CONFIG.API_URL .. "/api/station-update")
|
||||
if not body then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local ok, data = pcall(textutils.unserializeJSON, body)
|
||||
if not ok or type(data) ~= "table" then
|
||||
return nil, "invalid update response"
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function AutoUpdate.getTargetVersion(info)
|
||||
if type(info) ~= "table" then
|
||||
return ""
|
||||
end
|
||||
return Utils.trim(info.targetVersion or info.version)
|
||||
end
|
||||
|
||||
function AutoUpdate.needsUpdate(info)
|
||||
local targetVersion = AutoUpdate.getTargetVersion(info)
|
||||
return targetVersion ~= "" and targetVersion ~= APP_VERSION_TEXT
|
||||
end
|
||||
|
||||
function AutoUpdate.apply(info, reason)
|
||||
if State.updateInProgress then
|
||||
return false, "update already in progress"
|
||||
end
|
||||
|
||||
local targetVersion = AutoUpdate.getTargetVersion(info)
|
||||
if targetVersion == "" then
|
||||
return false, "missing target version"
|
||||
end
|
||||
|
||||
local downloadUrl = AutoUpdate.getDownloadUrl(info)
|
||||
local startupPath = AutoUpdate.getStartupPath()
|
||||
|
||||
State.updateInProgress = true
|
||||
Utils.log("WARN", "Applying update " .. targetVersion .. " via " .. tostring(reason or "manual"))
|
||||
|
||||
if Audio and Audio.stop then
|
||||
pcall(Audio.stop, "all")
|
||||
end
|
||||
if State.ws then
|
||||
pcall(State.ws.close)
|
||||
State.ws = nil
|
||||
end
|
||||
|
||||
local body, err = AutoUpdate.fetch(downloadUrl)
|
||||
if not body then
|
||||
State.updateInProgress = false
|
||||
return false, err
|
||||
end
|
||||
|
||||
local ok, writeErr = AutoUpdate.writeFile(startupPath, body)
|
||||
if not ok then
|
||||
State.updateInProgress = false
|
||||
return false, writeErr
|
||||
end
|
||||
|
||||
Utils.log("SUCCESS", "Updated startup to " .. targetVersion)
|
||||
Utils.log("WARN", "Rebooting to finish update")
|
||||
os.sleep(1)
|
||||
os.reboot()
|
||||
return true
|
||||
end
|
||||
|
||||
function AutoUpdate.checkOnBoot()
|
||||
local info, err = AutoUpdate.fetchInfo()
|
||||
if not info then
|
||||
Utils.log("WARN", "Update check skipped: " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
|
||||
if not AutoUpdate.needsUpdate(info) then
|
||||
return false
|
||||
end
|
||||
|
||||
Utils.log("INFO", "Found published update " .. AutoUpdate.getTargetVersion(info) .. " on boot")
|
||||
local ok, applyErr = AutoUpdate.apply(info, "boot")
|
||||
if not ok then
|
||||
Utils.log("ERROR", "Boot update failed: " .. tostring(applyErr))
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- 6. Network System
|
||||
-- ============================================================================
|
||||
@@ -1990,6 +2148,25 @@ function Network.messageLoop()
|
||||
local flushBelow = data.flushBelow
|
||||
Audio.stop(mode, flushBelow)
|
||||
end
|
||||
elseif data.type == "station_update" then
|
||||
local targetVersion = AutoUpdate.getTargetVersion(data)
|
||||
if targetVersion == "" then
|
||||
Utils.log("WARN", "Ignored station update without version")
|
||||
elseif targetVersion == APP_VERSION_TEXT then
|
||||
Utils.log("INFO", "Received update notice for current version " .. targetVersion)
|
||||
else
|
||||
Utils.log("WARN", "Received published update " .. targetVersion)
|
||||
local okUpdate, updateErr = AutoUpdate.apply({
|
||||
targetVersion = targetVersion,
|
||||
version = targetVersion,
|
||||
releaseId = data.releaseId,
|
||||
downloadPath = data.downloadPath,
|
||||
downloadUrl = data.downloadUrl
|
||||
}, "publish")
|
||||
if not okUpdate then
|
||||
Utils.log("ERROR", "Station update failed: " .. tostring(updateErr))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if State.audioRx then
|
||||
@@ -2842,6 +3019,7 @@ end
|
||||
-- ============================================================================
|
||||
-- 9. Bootstrap
|
||||
-- ============================================================================
|
||||
AutoUpdate.checkOnBoot()
|
||||
Peripherals.init()
|
||||
Utils.ensureSlaveIdentityConfigured()
|
||||
Audio.loadStationList()
|
||||
|
||||
Reference in New Issue
Block a user