From 3eae2b8d30448cf7151912263dc83da52cb58b0a Mon Sep 17 00:00:00 2001 From: DerEchteAlec Date: Wed, 19 Aug 2026 20:57:02 +0200 Subject: [PATCH] ADD - check GitHub releases on startup (#10) Compare the fxmanifest version against the latest published sky_phone release tag and report update, current, ahead, and failure states without blocking resource startup. Document the startup check and cover the HTTP and version comparison behavior with a focused Lua test. --- README.md | 5 ++ sky_phone/fxmanifest.lua | 1 + sky_phone/source/server/update_check.lua | 91 ++++++++++++++++++++++++ tests/update_check.lua | 69 ++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 sky_phone/source/server/update_check.lua create mode 100644 tests/update_check.lua diff --git a/README.md b/README.md index 8722066..8a0af49 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,11 @@ When enabled, Sky Phone prints debug and informational messages. Warnings and er The short LB Phone detection notice also remains visible when debug mode is disabled. +On every resource start, Sky Phone compares the `version` in `fxmanifest.lua` with the tag of the +latest published [GitHub release](https://github.com/sky-systems/sky_phone/releases/latest). The +server console reports whether the installed version is current and shows the release link when an +update is available. A failed GitHub request is reported but does not prevent the phone from starting. + ## Security values Sky Phone ships with stable generated defaults in `Config.Server`: diff --git a/sky_phone/fxmanifest.lua b/sky_phone/fxmanifest.lua index afb4c8c..cb8bca6 100644 --- a/sky_phone/fxmanifest.lua +++ b/sky_phone/fxmanifest.lua @@ -55,6 +55,7 @@ server_scripts { 'config/media.lua', 'config/locales/en.lua', 'config/locales/de.lua', + 'source/server/update_check.lua', 'source/bridge/server/database.lua', 'source/bridge/server/migrations.lua', 'source/bridge/server/callbacks.lua', diff --git a/sky_phone/source/server/update_check.lua b/sky_phone/source/server/update_check.lua new file mode 100644 index 0000000..0cbeb09 --- /dev/null +++ b/sky_phone/source/server/update_check.lua @@ -0,0 +1,91 @@ +local RELEASE_API_URL = "https://api.github.com/repos/sky-systems/sky_phone/releases/latest" +local RELEASE_PAGE_URL = "https://github.com/sky-systems/sky_phone/releases/latest" + +local function parse_version(version) + if type(version) ~= "string" then + return nil + end + + local major, minor, patch = version:match("^v?(%d+)%.(%d+)%.(%d+)$") + if not major then + return nil + end + + return tonumber(major), tonumber(minor), tonumber(patch) +end + +local function compare_versions(installed_version, release_version) + local installed_major, installed_minor, installed_patch = parse_version(installed_version) + local release_major, release_minor, release_patch = parse_version(release_version) + if not installed_major or not release_major then + return nil + end + + if installed_major ~= release_major then + return installed_major < release_major and -1 or 1 + end + + if installed_minor ~= release_minor then + return installed_minor < release_minor and -1 or 1 + end + + if installed_patch ~= release_patch then + return installed_patch < release_patch and -1 or 1 + end + + return 0 +end + +local function print_update_notice(installed_version, release_version) + local border = "======================================================================" + print(([[ +^1%s^0 +^1 SKY PHONE UPDATE AVAILABLE ^0 +^1%s^0 +^3 Installed version: ^1%s^0 +^3 Latest release: ^2%s^0 + +^5 Download: %s^0 +^1%s^0]]):format(border, border, installed_version, release_version, RELEASE_PAGE_URL, border)) +end + +local function check_for_update() + local resource_name = GetCurrentResourceName() + local installed_version = GetResourceMetadata(resource_name, "version", 0) + if not parse_version(installed_version) then + print(("^3[sky_phone] Update check skipped because fxmanifest.lua has an invalid version: %s.^0") + :format(tostring(installed_version))) + return + end + + PerformHttpRequest(RELEASE_API_URL, function(status_code, response_body) + if status_code ~= 200 then + print(("^3[sky_phone] GitHub update check failed with HTTP %s.^0"):format(tostring(status_code))) + return + end + + local decoded, release = pcall(json.decode, response_body) + local release_version = decoded and type(release) == "table" and release.tag_name or nil + local comparison = compare_versions(installed_version, release_version) + if not comparison then + print(("^3[sky_phone] GitHub update check returned an invalid release tag: %s.^0") + :format(tostring(release_version))) + return + end + + if comparison < 0 then + print_update_notice(installed_version, release_version) + elseif comparison > 0 then + print(("^3[sky_phone] Installed version %s is newer than the latest GitHub release %s.^0") + :format(installed_version, release_version)) + else + print(("^2[sky_phone] Version %s is up to date.^0"):format(installed_version)) + end + end, "GET", "", { + ["Accept"] = "application/vnd.github+json", + ["User-Agent"] = "sky_phone-update-check", + ["X-GitHub-Api-Version"] = "2022-11-28", + }) +end + +CreateThread(check_for_update) diff --git a/tests/update_check.lua b/tests/update_check.lua new file mode 100644 index 0000000..b8266aa --- /dev/null +++ b/tests/update_check.lua @@ -0,0 +1,69 @@ +local source_path = "sky_phone/source/server/update_check.lua" +local original_print = print + +local function run_check(installed_version, status_code, release_version) + local output = {} + local request + + print = function(message) + output[#output + 1] = tostring(message) + end + GetCurrentResourceName = function() + return "sky_phone" + end + GetResourceMetadata = function(resource_name, key, index) + assert(resource_name == "sky_phone", "update check must read its own resource metadata") + assert(key == "version" and index == 0, "update check must read the fxmanifest version") + return installed_version + end + PerformHttpRequest = function(url, callback, method, body, headers) + request = { + url = url, + callback = callback, + method = method, + body = body, + headers = headers, + } + end + CreateThread = function(callback) + callback() + end + json = { + decode = function() + return { tag_name = release_version } + end, + } + + dofile(source_path) + + if request then + assert(request.url == "https://api.github.com/repos/sky-systems/sky_phone/releases/latest") + assert(request.method == "GET" and request.body == "", "update check must use a read-only GET request") + assert(request.headers["Accept"] == "application/vnd.github+json") + assert(request.headers["User-Agent"] == "sky_phone-update-check") + request.callback(status_code, "{}") + end + + return table.concat(output, "\n"), request +end + +local current_output = run_check("0.1.0", 200, "0.1.0") +assert(current_output:find("Version 0.1.0 is up to date", 1, true), "matching versions must report up to date") + +local outdated_output = run_check("0.1.0", 200, "0.2.0") +assert(outdated_output:find("SKY PHONE UPDATE AVAILABLE", 1, true), "newer releases must show an update notice") +assert(outdated_output:find("Installed version: ^10.1.0", 1, true), "notice must show the manifest version") +assert(outdated_output:find("Latest release: ^20.2.0", 1, true), "notice must show the release tag") + +local ahead_output = run_check("1.0.0", 200, "0.9.9") +assert(ahead_output:find("newer than the latest GitHub release", 1, true), "ahead versions must be distinguished") + +local invalid_output, invalid_request = run_check("development", 200, "0.1.0") +assert(not invalid_request, "invalid manifest versions must not make an HTTP request") +assert(invalid_output:find("fxmanifest.lua has an invalid version", 1, true), "invalid manifests must be visible") + +local failed_output = run_check("0.1.0", 429, "0.1.0") +assert(failed_output:find("GitHub update check failed with HTTP 429", 1, true), "HTTP failures must be visible") + +print = original_print +io.write("Sky Phone update check tests passed\n")