Compare commits

..

3 commits

Author SHA1 Message Date
9121b54fb3 Bump version to v0.1.43 and report why a tag build cannot publish its release
All checks were successful
Build plugin / build (push) Successful in 1m10s
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.
2026-10-15 11:46:00 -03:00
bb4ddb0ea5 Keep the butler binary up to date with itch's broth channel
All checks were successful
Build plugin / build (push) Successful in 1m10s
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.
2026-10-15 11:41:09 -03:00
c6fd78e76f ci: extract the Godot build with python3 instead of unzip
All checks were successful
Build plugin / build (push) Successful in 1m6s
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.
2026-10-15 11:37:51 -03:00
3 changed files with 86 additions and 27 deletions

View file

@ -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"
@ -41,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

View file

@ -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:

View file

@ -1,7 +1,7 @@
{
"plugin.id": "itch",
"plugin.name": "itch.io",
"plugin.version": "0.1.41",
"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",