Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7156458c6f | ||
|
|
e0090fc1c3 | ||
|
|
a47b5c251c |
No files matched your search
@@ -21,7 +21,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
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
|
||||
uses: astral-sh/setup-uv@v5
|
||||
@@ -38,6 +38,15 @@ jobs:
|
||||
run: |
|
||||
./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
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
@@ -58,5 +67,12 @@ jobs:
|
||||
Device access still needs the udev rule once:
|
||||
`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.
|
||||
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
|
||||
@@ -105,6 +105,7 @@ temperature source (Tctl vs Tccd).
|
||||
- **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
|
||||
- **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)
|
||||
- **AppImage build** script (`packaging/build_appimage.sh`, PySide6 bundled)
|
||||
|
||||
@@ -208,6 +209,27 @@ docs/ reverse-engineering.md — full analysis (Indonesian)
|
||||
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
|
||||
|
||||
If this project is useful to you:
|
||||
|
||||
+42
-4
@@ -8,6 +8,7 @@ mengendalikannya dan memakai gamemax_report.py sebagai pengirim.
|
||||
gamemax_gui.py --tray-only # tanpa jendela
|
||||
gamemax_gui.py --selftest # uji muat tanpa tampilan (untuk build AppImage)
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
@@ -30,7 +31,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
||||
|
||||
APP_VERSION = "1.2.0"
|
||||
APP_VERSION = "1.2.3"
|
||||
REPO = "oxpoe/Gamemax-linux-apps"
|
||||
RELEASES_API = f"https://api.github.com/repos/{REPO}/releases/latest"
|
||||
UPDATE_ASSET = "GAMEMAX-LCD-x86_64.AppImage"
|
||||
@@ -84,8 +85,34 @@ def version_tuple(tag):
|
||||
return tuple(int(p) for p in parts) or (0,)
|
||||
|
||||
|
||||
def ensure_ca_bundle():
|
||||
"""Bundel PyInstaller tidak menemukan CA sistem sendiri; arahkan OpenSSL ke sana."""
|
||||
if os.environ.get("SSL_CERT_FILE"):
|
||||
return
|
||||
for path in ("/etc/ssl/certs/ca-certificates.crt", # Arch/Debian
|
||||
"/etc/pki/tls/certs/ca-bundle.crt", # Fedora/openSUSE
|
||||
"/etc/ssl/cert.pem"): # Alpine/BSD
|
||||
if os.path.exists(path):
|
||||
os.environ["SSL_CERT_FILE"] = path
|
||||
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():
|
||||
"""(tag, url aset) dari rilis terbaru GitHub — dipanggil dari thread pekerja."""
|
||||
ensure_ca_bundle()
|
||||
req = urllib.request.Request(RELEASES_API, headers={
|
||||
"Accept": "application/vnd.github+json", "User-Agent": "gamemax-lcd"})
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
@@ -487,8 +514,13 @@ class Window(QWidget):
|
||||
self.start_task(f"mengunduh {tag}", lambda: self.do_update(tag, url, target))
|
||||
|
||||
def do_update(self, tag, url, target):
|
||||
ensure_ca_bundle()
|
||||
tmp = target + ".new"
|
||||
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.replace(tmp, target) # ganti berkas secara atomik
|
||||
# sender di layanan juga diambil dari bundel baru
|
||||
@@ -500,7 +532,11 @@ class Window(QWidget):
|
||||
os.makedirs(INSTALL_DIR, exist_ok=True)
|
||||
shutil.copyfile(sender, os.path.join(INSTALL_DIR, "gamemax_report.py"))
|
||||
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
|
||||
def start_task(self, label, fn):
|
||||
@@ -671,6 +707,8 @@ class Window(QWidget):
|
||||
|
||||
def log(self, text):
|
||||
self.logview.appendPlainText(text)
|
||||
if os.environ.get("GAMEMAX_DEBUG"):
|
||||
print(text, flush=True)
|
||||
|
||||
def warn_once(self, text):
|
||||
if getattr(self, "_warned", None) != text:
|
||||
@@ -882,8 +920,8 @@ def _main():
|
||||
("periksa pembaruan", w.do_check_update))
|
||||
for name, fn in steps:
|
||||
try:
|
||||
fn()
|
||||
print(f"[ OK ] {name}")
|
||||
out = fn()
|
||||
print(f"[ OK ] {name}" + (f" — {out}" if isinstance(out, str) and out else ""))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"[GAGAL] {name}: {exc}")
|
||||
# jalur asinkron yang dipakai tombol: centang layar kosong + simpan
|
||||
|
||||
Reference in new issue
Block a user