From c6fd78e76fbc88cb226580480739c2638595a2d6 Mon Sep 17 00:00:00 2001 From: Jose Falanga Date: Thu, 15 Oct 2026 11:37:51 -0300 Subject: [PATCH 1/3] ci: extract the Godot build with python3 instead of unzip The runner image is Debian 11 based, not ubuntu-24.04, and its apt index points at an unzip build that is no longer in the security pool, so the dependency install 404s and the build never starts. --- .forgejo/workflows/build.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 14ba83d..8fe5fda 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -16,15 +16,17 @@ jobs: - name: Install dependencies run: | - apt-get update -qq - apt-get install -y -qq make git wget unzip + apt-get update + apt-get install -y -qq make git wget - name: Install Godot run: | wget -q https://github.com/godotengine/godot/releases/download/4.4.1-stable/Godot_v4.4.1-stable_linux.x86_64.zip - unzip -q Godot_v4.4.1-stable_linux.x86_64.zip + # python3 instead of unzip: the runner's apt index goes stale and the + # pinned unzip deb 404s in the security pool. + python3 -m zipfile -e Godot_v4.4.1-stable_linux.x86_64.zip godot-dir mkdir -p "$HOME/.local/bin" - mv Godot_v4.4.1-stable_linux.x86_64 "$HOME/.local/bin/godot" + mv godot-dir/Godot_v4.4.1-stable_linux.x86_64 "$HOME/.local/bin/godot" chmod +x "$HOME/.local/bin/godot" echo "$HOME/.local/bin" >> "$GITHUB_PATH" From bb4ddb0ea5058a1d17cd17e97e3dfd92a78e5a42 Mon Sep 17 00:00:00 2001 From: Jose Falanga Date: Thu, 15 Oct 2026 11:41:09 -0300 Subject: [PATCH 2/3] Keep the butler binary up to date with itch's broth channel butler was only downloaded when the binary was missing, so a device that installed it once kept that build forever (brutha was still on v15.30.0 from July while broth ships 15.32.0). bootstrap now compares the installed version against broth's LATEST and refreshes it when behind, keeping the installed binary when the channel can't be reached or the version can't be read, so a network hiccup never replaces a working butler. --- core/itch_client.gd | 78 +++++++++++++++++++++++++++++++++++---------- plugin.json | 2 +- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/core/itch_client.gd b/core/itch_client.gd index 39490df..f9d92e2 100644 --- a/core/itch_client.gd +++ b/core/itch_client.gd @@ -83,7 +83,8 @@ func _ready() -> void: bootstrap() -## Bootstraps the butler binary if it isn't present, then spawns butlerd. +## Bootstraps the butler binary (downloading it when missing or outdated), then +## spawns butlerd. func bootstrap() -> void: var butler_dir_global := ProjectSettings.globalize_path(butler_dir) _migrate_butler(butler_dir_global) @@ -96,6 +97,12 @@ func bootstrap() -> void: bootstrap_finished.emit() return logger.info("Successfully installed butler") + elif await _butler_is_outdated(butler_bin): + logger.info("Updating the butler binary to " + str(await _fetch_latest_butler_version()) + ".") + var success := await _install_butler() + if not success: + # Not fatal: the installed butler still works, it is just behind. + logger.warn("Unable to update butler, keeping the installed one.") var dbpath := "/".join([butler_dir_global, "butler.db"]) var args := [ @@ -139,9 +146,8 @@ func _migrate_butler(dest_dir: String) -> void: logger.info("Migrated butler from " + old_dir + " to " + dest_dir) -## Downloads a butler binary for this platform from itch's broth distribution -## channel (the same one the official itch.io app uses to self-update). -func _install_butler() -> bool: +## broth's slug for the platform we are running on, e.g. "linux-amd64". +func _butler_platform_slug() -> String: var goos := "linux" var goarch := "amd64" if OS.get_name() == "Windows": @@ -155,10 +161,13 @@ func _install_butler() -> bool: var arch: String = Engine.get_architecture_name() if "arm64" in arch or "aarch64" in arch: goarch = "arm64" + return goos + "-" + goarch - var platform_slug := goos + "-" + goarch - var latest_url := "/".join([broth_base, platform_slug, "LATEST"]) +## Asks itch's broth channel which butler version is current. Returns "" when +## the channel can't be reached, so callers can tell "unknown" from "current". +func _fetch_latest_butler_version() -> String: + var latest_url := "/".join([broth_base, _butler_platform_slug(), "LATEST"]) var http := HTTPRequest.new() add_child.call_deferred(http) await http.ready @@ -166,28 +175,65 @@ func _install_butler() -> bool: logger.error("Error fetching latest butler version: " + latest_url) remove_child(http) http.queue_free() - return false + return "" var args: Array = await http.request_completed var result: int = args[0] var response_code: int = args[1] var body: PackedByteArray = args[3] + remove_child(http) + http.queue_free() if result != HTTPRequest.RESULT_SUCCESS or response_code != 200: - logger.error("Unable to determine latest butler version for " + platform_slug) - remove_child(http) - http.queue_free() - return false - var version := body.get_string_from_utf8().strip_edges() + logger.error("Unable to determine the latest butler version") + return "" + return body.get_string_from_utf8().strip_edges() + +## Reads the version of an installed butler binary, e.g. "15.30.0" out of +## "v15.30.0, built on Jul 27 2026". Returns "" when the binary can't be run. +func _butler_installed_version(butler_bin: String) -> String: + var out := [] + if OS.execute(butler_bin, ["version"], out) != OK or out.is_empty(): + return "" + var reported: String = (out[0] as String).strip_edges().get_slice(",", 0) + return reported.trim_prefix("v").strip_edges() + + +## True when the installed butler is behind broth's current version. The +## binary used to be downloaded only when missing, so it silently rotted for +## months and missed fixes. Unknown version (channel unreachable, binary not +## runnable) counts as up to date: never replace a working butler on a guess. +func _butler_is_outdated(butler_bin: String) -> bool: + var latest := await _fetch_latest_butler_version() + if latest == "": + return false + var installed := _butler_installed_version(butler_bin) + if installed == "" or installed == latest: + return false + logger.info("butler " + installed + " is behind " + latest + ". Updating it.") + return true + + +## Downloads a butler binary for this platform from itch's broth distribution +## channel (the same one the official itch.io app uses to self-update). +func _install_butler() -> bool: + var platform_slug := _butler_platform_slug() + var version := await _fetch_latest_butler_version() + if version == "": + return false + + var http := HTTPRequest.new() + add_child.call_deferred(http) + await http.ready var archive_url := "/".join([broth_base, platform_slug, version, "archive", "default"]) if http.request(archive_url) != OK: logger.error("Error downloading butler: " + archive_url) remove_child(http) http.queue_free() return false - args = await http.request_completed - result = args[0] - response_code = args[1] - body = args[3] + var args: Array = await http.request_completed + var result: int = args[0] + var response_code: int = args[1] + var body: PackedByteArray = args[3] remove_child(http) http.queue_free() if result != HTTPRequest.RESULT_SUCCESS or response_code != 200: diff --git a/plugin.json b/plugin.json index 3826d28..3e013b2 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "plugin.id": "itch", "plugin.name": "itch.io", - "plugin.version": "0.1.41", + "plugin.version": "0.1.42", "plugin.min-api-version": "1.1.0", "plugin.link": "https://forge.thergic.ar/jose/itchio-opengamepadui-plugin", "plugin.source": "https://forge.thergic.ar/jose/itchio-opengamepadui-plugin", From 9121b54fb3e11a1624872097a8d455885b13e3fe Mon Sep 17 00:00:00 2001 From: Jose Falanga Date: Thu, 15 Oct 2026 11:46:00 -0300 Subject: [PATCH 3/3] Bump version to v0.1.43 and report why a tag build cannot publish its release curl -sf swallowed the API answer, so when the release POST failed the run just went red with no reason and devices stayed on the previous release. --- .forgejo/workflows/build.yml | 23 +++++++++++++++++------ plugin.json | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 8fe5fda..5756dc3 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -43,21 +43,32 @@ jobs: TAG="${GITHUB_REF_NAME}" API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" - # Create the release - curl -sf -X POST "${API}/releases" \ + # Create the release. `curl -sf` hid the reason a tag build could not + # publish, which left devices on the previous release with no clue, + # so report the status code and the API answer. + HTTP=$(curl -sS -o /tmp/release.json -w "%{http_code}" -X POST "${API}/releases" \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Content-Type: application/json" \ - -d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\"}" \ - -o /tmp/release.json + -d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\"}") + if [ "${HTTP}" != "201" ] && [ "${HTTP}" != "200" ]; then + echo "Release creation for ${TAG} failed with HTTP ${HTTP}:" + cat /tmp/release.json + exit 1 + fi RELEASE_ID=$(python3 -c "import json; print(json.load(open('/tmp/release.json'))['id'])") # Upload assets for FILE in dist/itch.zip dist/itch.zip.sha256.txt; do NAME=$(basename "$FILE") - curl -sf -X POST \ + HTTP=$(curl -sS -o /tmp/asset.json -w "%{http_code}" -X POST \ "${API}/releases/${RELEASE_ID}/assets?name=${NAME}" \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Content-Type: application/octet-stream" \ - --data-binary "@${FILE}" + --data-binary "@${FILE}") + if [ "${HTTP}" != "201" ] && [ "${HTTP}" != "200" ]; then + echo "Asset upload of ${NAME} failed with HTTP ${HTTP}:" + cat /tmp/asset.json + exit 1 + fi done diff --git a/plugin.json b/plugin.json index 3e013b2..a9793d1 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "plugin.id": "itch", "plugin.name": "itch.io", - "plugin.version": "0.1.42", + "plugin.version": "0.1.43", "plugin.min-api-version": "1.1.0", "plugin.link": "https://forge.thergic.ar/jose/itchio-opengamepadui-plugin", "plugin.source": "https://forge.thergic.ar/jose/itchio-opengamepadui-plugin",