From b0361fc366b9eacbf947b46323a1d3295c2a740d Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Sat, 11 Jul 2026 10:50:00 +0800 Subject: [PATCH] feat: simplify OTA updates with timestamp --- README_ZH.md | 8 +++++ gate.lua | 53 ++++++++++++++++++++++++++------ server/routes/api.js | 3 ++ server/routes/public.js | 3 +- ticketmachine.lua | 68 +++++++++++++++++++++++++---------------- web/index.js | 11 +++---- 6 files changed, 102 insertions(+), 44 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 1ad5846..233aa81 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -1,3 +1,11 @@ + # FTC 票务系统 diff --git a/gate.lua b/gate.lua index c9e5114..187ee43 100644 --- a/gate.lua +++ b/gate.lua @@ -127,17 +127,49 @@ local function setLastUpdateVersion(v) if f then f.write(tostring(v)); f.close() end end -local function runSilentGateUpdate() - if not fs.exists("update_gate.lua") then - return false, "update_gate.lua not found" +local function checkAndApplyUpdate() + if type(CFG) ~= 'table' then return end + local remoteTs = tonumber(CFG.update_timestamp) + + if not remoteTs or remoteTs <= 0 then return end + + -- Prevent infinite reboot loops: only update once per timestamp + local attemptFile = ".last_update_ts" + if fs.exists(attemptFile) then + local f = fs.open(attemptFile, "r") + local tried = tonumber(f and f.readAll() or "0") or 0 + if f then f.close() end + if tried >= remoteTs then + return + end end - local baseEnv = (getfenv and getfenv()) or _ENV or _G - local env = setmetatable({ AUTO_UPDATE_SILENT = true }, { __index = baseEnv }) + + -- We have a NEW remote timestamp. Trigger update! + isUpdating = true + pcall(drawReadyScreen) + sleep(0.5) + + -- Record that we are attempting this timestamp + local f = fs.open(attemptFile, "w") + if f then f.write(tostring(remoteTs)); f.close() end + + -- Load and run update script + local env = setmetatable({ AUTO_UPDATE_SILENT = true }, { __index = _G or getfenv() or _ENV }) local fn, err = loadfile("update_gate.lua", env) - if not fn then return false, err end - local ok, res = pcall(fn, "--silent") - if not ok then return false, tostring(res) end - return true, true + if fn then + local ok, res = pcall(fn, "--silent") + if not ok then + lastUpdateError = string.sub(tostring(res), 1, 30) + end + else + lastUpdateError = "loadfile failed: " .. tostring(err) + end + + isUpdating = false + pcall(drawReadyScreen) + + -- Reboot + os.reboot() end local function setServerConnected(ok) @@ -422,9 +454,10 @@ end local function refreshRemoteLuaVersion(serverBase) serverBase = trim(serverBase or "") if #serverBase == 0 then return false end - local url = serverBase:gsub("/+$", "") .. "/api/public/config" + local url = serverBase:gsub("/+$", "") .. "/api/config?_t=" .. tostring(nowMs() + math.random(1, 999999)) local ok, parsed = getJSON(url) if not ok or type(parsed) ~= "table" then return false end + _G.CFG = parsed local remote = normalizeVersionTag(type(parsed.lua_versions) == "table" and parsed.lua_versions.gate or nil) remoteForceUpdate = (parsed.force_update == true) if #remote == 0 then diff --git a/server/routes/api.js b/server/routes/api.js index 1c8a25d..8042da0 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -189,6 +189,9 @@ router.put('/config', async (req, res) => { if (typeof incoming.force_update === 'boolean') { cfg.force_update = incoming.force_update; } + if (incoming.update_timestamp) { + cfg.update_timestamp = incoming.update_timestamp; + } await DataService.saveConfig(cfg); appendReqLog(req, { category: 'admin', type: 'update_config_generic', detail: incoming }); io.emit('config:updated', cfg); diff --git a/server/routes/public.js b/server/routes/public.js index 5e2b759..a7c401d 100644 --- a/server/routes/public.js +++ b/server/routes/public.js @@ -229,7 +229,8 @@ router.get('/config', (req, res) => { res.json({ promotion: { name: cfg.promotion?.name || '', discount: cfg.promotion?.discount ?? 1 }, lua_versions: cfg.lua_versions || {}, - force_update: cfg.force_update === true + force_update: cfg.force_update === true, + update_timestamp: cfg.update_timestamp || 0 }); }); diff --git a/ticketmachine.lua b/ticketmachine.lua index e0a53f3..ed0330a 100644 --- a/ticketmachine.lua +++ b/ticketmachine.lua @@ -14,8 +14,6 @@ end local serverConnected = nil local versionMismatch = nil -local failedUpdateVersion = nil -local pendingMachineUpdate = false local isUpdating = false local state = {} @@ -716,31 +714,49 @@ local function drawVersionIndicator() termDev.setTextColor(colors.white) end +local function checkAndApplyUpdate() + if type(CFG) ~= 'table' then return end + local remoteTs = tonumber(CFG.update_timestamp) + + if not remoteTs or remoteTs <= 0 then return end + + local attemptFile = ".last_update_ts" + if fs.exists(attemptFile) then + local f = fs.open(attemptFile, "r") + local tried = tonumber(f and f.readAll() or "0") or 0 + if f then f.close() end + if tried >= remoteTs then + return + end + end + isUpdating = true + pcall(drawVersionIndicator) + sleep(0.5) + + local f = fs.open(attemptFile, "w") + if f then f.write(tostring(remoteTs)); f.close() end + + local env = setmetatable({ AUTO_UPDATE_SILENT = true }, { __index = _G or getfenv() or _ENV }) + local fn, err = loadfile("update_machine.lua", env) + if fn then + local ok, res = pcall(fn, "--silent") + if not ok then + lastUpdateError = string.sub(tostring(res), 1, 30) + end + else + lastUpdateError = "loadfile failed: " .. tostring(err) + end + + isUpdating = false + pcall(drawVersionIndicator) + + os.reboot() +end + local function backgroundSyncTask() pcall(refreshConfigOnce) while true do - if type(CFG) == 'table' and type(CFG.force_update) == 'boolean' and CFG.force_update == true and versionMismatch == true then - isUpdating = true - pcall(drawVersionIndicator) - sleep(0.5) - - -- Extreme fallback: just run the update script directly - local baseEnv = (getfenv and getfenv()) or _ENV or _G - local env = setmetatable({ AUTO_UPDATE_SILENT = true }, { __index = baseEnv }) - local fn = loadfile('update_machine.lua', env) - if fn then - local ok, err = pcall(fn, '--silent') - if not ok then - lastUpdateError = string.sub(tostring(err), 1, 30) - end - else - lastUpdateError = "loadfile failed" - end - - isUpdating = false - pcall(drawVersionIndicator) - sleep(10) -- Sleep 10s to prevent spamming - end + pcall(checkAndApplyUpdate) sleep(UPDATE_CHECK_INTERVAL) pcall(refreshConfigOnce) end @@ -910,7 +926,7 @@ local function sameLogicalStation(a, b) end local function fetchConfig() - local txt = fetchHTTP(API_BASE .. '/config') + local txt = fetchHTTP(API_BASE .. '/config?_t=' .. tostring(os.epoch and os.epoch("utc") or math.random(1, 999999))) return txt and jsonDecode(txt) or nil end @@ -1033,7 +1049,7 @@ local function waitButtons() end end elseif ev == 'trigger_update' then - pendingMachineUpdate = true + -- obsolete elseif ev == 'config_updated' then if state and state.page == 'home' then return end end diff --git a/web/index.js b/web/index.js index 9283253..8d3185c 100644 --- a/web/index.js +++ b/web/index.js @@ -1483,19 +1483,16 @@ createApp({ isPushingUpdate.value = true; - // 自动提升版本号 & 开启 force_update - if (!config.lua_versions.ticketmachine) config.lua_versions.ticketmachine = 'v1.0.0'; - if (!config.lua_versions.gate) config.lua_versions.gate = 'v1.0.0'; - config.lua_versions.ticketmachine = bumpPatchVersion(config.lua_versions.ticketmachine); - config.lua_versions.gate = bumpPatchVersion(config.lua_versions.gate); + // 放弃版本号验证,直接使用时间戳强制下发更新指令 + config.update_timestamp = Date.now(); config.force_update = true; try { await requestJson('/api/config', { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(config) }, { expectOk: true }); - toast('推送指令发送完毕,终端将陆续更新', 'success'); + alert('推送指令发送完毕,终端将陆续更新'); } catch (err) { console.error(err); - toast('推送更新失败', 'error'); + alert('推送更新失败: ' + err.message); } finally { isPushingUpdate.value = false; }