2 Commits
Author SHA1 Message Date
oxpoe 7156458c6f feat(updater): verifikasi SHA-256 sebelum mengganti AppImage; CI menerbitkan .sha256 + .zsync
- do_update: unduh → verifikasi checksum rilis → baru ganti berkas (mismatch = dibatalkan)
- workflow: hasilkan GAMEMAX-LCD-x86_64.AppImage.sha256 dan .zsync (zsyncmake), lampirkan ketiganya
- README: bagian Updates menjelaskan checksum + zsync (delta update opsional)
- versi 1.2.3
2026-10-13 08:10:05 +07:00
oxpoe e0090fc1c3 feat: suite uji mencetak pesan hasil + GAMEMAX_DEBUG menyalin log ke stdout
docs: bagian Updates (pemeriksaan & pembaruan satu klik dari GitHub Releases)
versi 1.2.2
2026-10-13 07:53:51 +07:00
3 changed files with 68 additions and 6 deletions

No files matched your search

+18 -2
View File
@@ -21,7 +21,7 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
libgl1 libegl1 libxkbcommon0 libdbus-1-3 libfuse2 fuse libgl1 libegl1 libxkbcommon0 libdbus-1-3 libfuse2 fuse zsync
- name: Set up uv - name: Set up uv
uses: astral-sh/setup-uv@v5 uses: astral-sh/setup-uv@v5
@@ -38,6 +38,15 @@ jobs:
run: | run: |
./dist/GAMEMAX-LCD-x86_64.AppImage --selftest ./dist/GAMEMAX-LCD-x86_64.AppImage --selftest
- name: Checksum and zsync metadata
run: |
cd dist
sha256sum GAMEMAX-LCD-x86_64.AppImage > GAMEMAX-LCD-x86_64.AppImage.sha256
zsyncmake -u "https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/GAMEMAX-LCD-x86_64.AppImage" \
-o GAMEMAX-LCD-x86_64.AppImage.zsync GAMEMAX-LCD-x86_64.AppImage
cat GAMEMAX-LCD-x86_64.AppImage.sha256
ls -l
- name: Publish release - name: Publish release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
@@ -58,5 +67,12 @@ jobs:
Device access still needs the udev rule once: Device access still needs the udev rule once:
`sudo cp packaging/70-gamemax.rules /etc/udev/rules.d/ && sudo udevadm control --reload` `sudo cp packaging/70-gamemax.rules /etc/udev/rules.d/ && sudo udevadm control --reload`
**Integrity:** verify the download with the published SHA-256, or use
`appimageupdatetool`/AppImageUpdate with the `.zsync` asset for delta updates.
The built-in updater (v1.2.3+) also verifies that checksum before replacing itself.
Independent project, not affiliated with the vendor — see DISCLAIMER.md. Independent project, not affiliated with the vendor — see DISCLAIMER.md.
files: dist/GAMEMAX-LCD-x86_64.AppImage files: |
dist/GAMEMAX-LCD-x86_64.AppImage
dist/GAMEMAX-LCD-x86_64.AppImage.sha256
dist/GAMEMAX-LCD-x86_64.AppImage.zsync
+22
View File
@@ -105,6 +105,7 @@ temperature source (Tctl vs Tccd).
- **Plug and play**: the service waits for the device, streams while present and keeps - **Plug and play**: the service waits for the device, streams while present and keeps
waiting through unplug/replug; a udev rule grants access to the logged-in user waiting through unplug/replug; a udev rule grants access to the logged-in user
- **Single instance**: launching the app again just raises the running window - **Single instance**: launching the app again just raises the running window
- **Self-update from GitHub Releases** (check + one-click update)
- **Donate / GitHub buttons** in the app (window and tray menu) - **Donate / GitHub buttons** in the app (window and tray menu)
- **AppImage build** script (`packaging/build_appimage.sh`, PySide6 bundled) - **AppImage build** script (`packaging/build_appimage.sh`, PySide6 bundled)
@@ -208,6 +209,27 @@ docs/ reverse-engineering.md — full analysis (Indonesian)
assets/ previews and icon assets/ previews and icon
``` ```
## Updates
The app keeps itself current from GitHub Releases:
- On start (when running from an AppImage) and from the **Periksa pembaruan** button or the
tray menu, it asks the GitHub API for the latest release and compares it with its version.
- When a newer release exists, a **Perbarui ke vX.Y.Z** button appears. One click downloads
the release asset, replaces the running AppImage atomically, refreshes the sender from the
new bundle and restarts the service. Restart the app to use the new version.
- The download is verified against the release's published **SHA-256** before the AppImage is
replaced; a mismatch aborts the update. Older releases without a checksum are updated with a
warning in the log.
- Nothing is downloaded without that click, and no other files are touched.
Each release publishes three assets: the AppImage, its `.sha256`, and a `.zsync` file for
`appimageupdatetool` / AppImageUpdate (delta updates). The zsync file is optional — the
built-in updater does not need it.
If the check fails (offline, proxy), the message appears in the app log. `GAMEMAX_DEBUG=1`
also mirrors the log to stdout for headless runs.
## Support ## Support
If this project is useful to you: If this project is useful to you:
+28 -4
View File
@@ -8,6 +8,7 @@ mengendalikannya dan memakai gamemax_report.py sebagai pengirim.
gamemax_gui.py --tray-only # tanpa jendela gamemax_gui.py --tray-only # tanpa jendela
gamemax_gui.py --selftest # uji muat tanpa tampilan (untuk build AppImage) gamemax_gui.py --selftest # uji muat tanpa tampilan (untuk build AppImage)
""" """
import hashlib
import json import json
import math import math
import os import os
@@ -30,7 +31,7 @@ from PySide6.QtWidgets import (
) )
from PySide6.QtNetwork import QLocalServer, QLocalSocket from PySide6.QtNetwork import QLocalServer, QLocalSocket
APP_VERSION = "1.2.1" APP_VERSION = "1.2.3"
REPO = "oxpoe/Gamemax-linux-apps" REPO = "oxpoe/Gamemax-linux-apps"
RELEASES_API = f"https://api.github.com/repos/{REPO}/releases/latest" RELEASES_API = f"https://api.github.com/repos/{REPO}/releases/latest"
UPDATE_ASSET = "GAMEMAX-LCD-x86_64.AppImage" UPDATE_ASSET = "GAMEMAX-LCD-x86_64.AppImage"
@@ -96,6 +97,19 @@ def ensure_ca_bundle():
return return
def verify_sha256(path, sha_url):
"""True/False bila berkas checksum tersedia, None bila rilis tidak menyediakannya."""
try:
with urllib.request.urlopen(sha_url, timeout=10) as resp:
expected = resp.read().decode().split()[0].strip().lower()
except Exception: # noqa: BLE001
return None
if len(expected) != 64:
return None
actual = hashlib.sha256(open(path, "rb").read()).hexdigest()
return actual == expected
def latest_release(): def latest_release():
"""(tag, url aset) dari rilis terbaru GitHub — dipanggil dari thread pekerja.""" """(tag, url aset) dari rilis terbaru GitHub — dipanggil dari thread pekerja."""
ensure_ca_bundle() ensure_ca_bundle()
@@ -503,6 +517,10 @@ class Window(QWidget):
ensure_ca_bundle() ensure_ca_bundle()
tmp = target + ".new" tmp = target + ".new"
urllib.request.urlretrieve(url, tmp) urllib.request.urlretrieve(url, tmp)
checked = verify_sha256(tmp, url + ".sha256")
if checked is False:
os.unlink(tmp)
raise RuntimeError("checksum SHA-256 tidak cocok — unduhan dibatalkan")
os.chmod(tmp, 0o755) os.chmod(tmp, 0o755)
os.replace(tmp, target) # ganti berkas secara atomik os.replace(tmp, target) # ganti berkas secara atomik
# sender di layanan juga diambil dari bundel baru # sender di layanan juga diambil dari bundel baru
@@ -514,7 +532,11 @@ class Window(QWidget):
os.makedirs(INSTALL_DIR, exist_ok=True) os.makedirs(INSTALL_DIR, exist_ok=True)
shutil.copyfile(sender, os.path.join(INSTALL_DIR, "gamemax_report.py")) shutil.copyfile(sender, os.path.join(INSTALL_DIR, "gamemax_report.py"))
systemctl("restart", SERVICE) systemctl("restart", SERVICE)
return f"diperbarui ke {tag} — tutup jendela ini lalu buka lagi untuk memakai versi baru" note = "" if checked is None else " (checksum SHA-256 terverifikasi)"
if checked is None:
note = " (rilis ini tidak menyertakan checksum)"
return (f"diperbarui ke {tag}{note} — tutup jendela ini lalu buka lagi "
"untuk memakai versi baru")
# -------------------------------------------------------------- tugas latar # -------------------------------------------------------------- tugas latar
def start_task(self, label, fn): def start_task(self, label, fn):
@@ -685,6 +707,8 @@ class Window(QWidget):
def log(self, text): def log(self, text):
self.logview.appendPlainText(text) self.logview.appendPlainText(text)
if os.environ.get("GAMEMAX_DEBUG"):
print(text, flush=True)
def warn_once(self, text): def warn_once(self, text):
if getattr(self, "_warned", None) != text: if getattr(self, "_warned", None) != text:
@@ -896,8 +920,8 @@ def _main():
("periksa pembaruan", w.do_check_update)) ("periksa pembaruan", w.do_check_update))
for name, fn in steps: for name, fn in steps:
try: try:
fn() out = fn()
print(f"[ OK ] {name}") print(f"[ OK ] {name}" + (f" — {out}" if isinstance(out, str) and out else ""))
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
print(f"[GAGAL] {name}: {exc}") print(f"[GAGAL] {name}: {exc}")
# jalur asinkron yang dipakai tombol: centang layar kosong + simpan # jalur asinkron yang dipakai tombol: centang layar kosong + simpan