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.
This commit is contained in:
DerEchteAlec
2026-08-19 20:57:02 +02:00
committed by GitHub
parent 734f4651cb
commit 3eae2b8d30
4 changed files with 166 additions and 0 deletions
+5
View File
@@ -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`:
+1
View File
@@ -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',
+91
View File
@@ -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)
+69
View File
@@ -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")