ajhahn.de
← the-way-out commits

Commit

the-way-out

v0.2.15

ajhahnde · May 2026 · 11682d6ad0e359f32ed5b893c992e6e8be7b865d · parent: 6a57f9f · view on GitHub →

modified CHANGELOG.md
@@ -1,5 +1,45 @@
# CHANGELOG
## v0.2.15
A maintenance release. No gameplay, tools, or save-file format
changes; existing saves and custom levels load as-is.
### Fixes
- Update flow: in-game **UPDATE** now works on packaged macOS `.app`
installs that have no system CA bundle (Intel Macs on stock
Monterey, arm64 machines without homebrew openssl). The previous
build silently fell back to Python's default verify paths, which
point at a `cert.pem` that only the python.org installer creates —
so HTTPS to `api.github.com` failed `CERTIFICATE_VERIFY_FAILED`,
was swallowed as `OSError`, and the player saw **"Update server
unreachable - try again later."** on a working network. The build
now bundles `certifi`'s CA roots via PyInstaller's `--collect-all
certifi`, and `updater.py` pins its TLS context to
`certifi.where()` (with a system-default fallback so dev runs on a
homebrew openssl or Linux distro keep working).
## v0.2.14
A maintenance release. No gameplay, tools, or save-file format
changes; existing saves and custom levels load as-is.
### Fixes
- Level editor: the hover-driven tile palette no longer slides open
while the level filename is being edited inline. The hover test
now ignores cursor presence over the drawer rail when
`editing_name` is true, so typing past the right edge of the field
does not accidentally pop the palette out from under the cursor.
- Update flow: on the packaged macOS `.app`, a post-update restart
that can't resolve the bundle path now exits cleanly instead of
falling through to `os.execv`. The `os.execv` path on a frozen
darwin build could reproduce B28 ("two windows"); the new
`SystemExit(0)` branch closes that hole. The user re-launches
manually in the rare case the bundle path can't be derived (dev
runs and `/Applications` installs are unaffected).
## v0.2.13
A maintenance release. No gameplay, tools, or save-file format changes;
modified VERSION
@@ -1 +1 @@
v0.2.13
v0.2.15
modified build_mac.sh
@@ -8,7 +8,7 @@ cd "$(dirname "$0")"
PY=".venv/bin/python" # NEVER system python3 (3.14)
[ -x "$PY" ] || { echo "missing $PY"; exit 1; }
"$PY" -m pip install --quiet --upgrade pyinstaller
"$PY" -m pip install --quiet --upgrade pyinstaller certifi
APPNAME="The Way Out"
SEEDPARENT="$(mktemp -d)"; SEED="$SEEDPARENT/_seed"; mkdir -p "$SEED"
@@ -24,6 +24,7 @@ rm -rf build dist "$APPNAME.spec"
--osx-bundle-identifier de.ajhahn.thewayout \
--icon assets/icon.icns \
--collect-all pygame \
--collect-all certifi \
--add-data "$SEED:_seed" \
launcher.py
modified build_mac_intel.sh
@@ -28,7 +28,7 @@ MACH="$(arch -x86_64 "$PY" -c 'import platform;print(platform.machine())')"
[ "$MACH" = "x86_64" ] \
|| { echo "venv python is not x86_64 under Rosetta (got: $MACH)"; exit 1; }
arch -x86_64 "$PY" -m pip install --quiet --upgrade pyinstaller
arch -x86_64 "$PY" -m pip install --quiet --upgrade pyinstaller certifi
APPNAME="The Way Out"
@@ -57,6 +57,7 @@ arch -x86_64 "$PY" -m PyInstaller --noconfirm --windowed --clean \
--osx-bundle-identifier de.ajhahn.thewayout \
--icon assets/icon.icns \
--collect-all pygame \
--collect-all certifi \
--target-architecture x86_64 \
--add-data "$SEED:_seed" \
launcher.py
modified updater.py
@@ -20,6 +20,7 @@ import json
import os
import shutil
import socket
import ssl
import tempfile
import time
import urllib.error
@@ -40,6 +41,29 @@ _ZIP_BASE = f"https://codeload.github.com/{REPO}/zip"
_HEADERS = {"User-Agent": "the-way-out-updater"} # GitHub API needs a UA
def _ssl_context():
"""TLS context that actually verifies on stock macOS installs.
The packaged .app ships Python.framework but no CA bundle, so
``ssl.create_default_context()`` ends up with an empty trust store
and every HTTPS call to GitHub fails CERTIFICATE_VERIFY_FAILED —
which the urlopen callers below swallow as OSError, leaving the
user looking at "Update server unreachable" on a working network.
Pin the context to certifi's bundle when it's importable (it is in
the frozen build via PyInstaller's --collect-all certifi); fall
back to the platform default so dev runs with a system openssl
(homebrew, Linux distros) keep working.
"""
try:
import certifi
return ssl.create_default_context(cafile=certifi.where())
except (ImportError, OSError):
return ssl.create_default_context()
_SSL_CTX = _ssl_context()
def app_dir() -> Path:
return APP_DIR
@@ -61,7 +85,8 @@ def remote_sha(timeout: float = 6):
"""Latest commit sha on the branch, or None if offline/blocked."""
try:
req = urllib.request.Request(_API_COMMIT, headers=_HEADERS)
with urllib.request.urlopen(req, timeout=timeout) as resp:
with urllib.request.urlopen(
req, timeout=timeout, context=_SSL_CTX) as resp:
return json.load(resp).get("sha")
except (urllib.error.URLError, OSError, ValueError, TimeoutError):
return None
@@ -138,7 +163,8 @@ def should_check(min_interval_s: float = 86400.0) -> bool:
def _download_zip(ref: str, timeout: float) -> bytes:
req = urllib.request.Request(f"{_ZIP_BASE}/{ref}", headers=_HEADERS)
with urllib.request.urlopen(req, timeout=timeout) as resp:
with urllib.request.urlopen(
req, timeout=timeout, context=_SSL_CTX) as resp:
return resp.read()