mirror of
https://github.com/Cogential/frametanium.git
synced 2026-10-23 07:00:32 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e1564df68 |
No files matched your search
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
tools/bundle.sh dist/app dist/bundle
|
||||
VERSION="$GITHUB_REF_NAME" tools/bundle.sh dist/app dist/bundle
|
||||
tar -C dist/bundle -czf frametanium-steam-frame-arm64.tar.gz .
|
||||
tar -tzf frametanium-steam-frame-arm64.tar.gz | grep -v '^./art/'
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ See README.md for what it does. Service: `manager.py` (stdlib Python, runs on th
|
||||
`ensure_secrets()`. Never add a loopback/no-password bypass, never accept secrets in the query string, and keep
|
||||
`host_ok()` (Host allow-list + Origin check). Any auth change must change app.c and page.html together, and old
|
||||
headset apps must still get a readable "#" notice in TSV replies.
|
||||
- **Self-update:** `check()` (every few hours, on each headset-app launch via `/api/refresh?launch=1`, and on
|
||||
request) installs a newer Frametanium release by itself (`self_update` in config.json, default on). Its installed
|
||||
version is the bundle's `VERSION` file (`tools/bundle.sh`; CI passes `VERSION=$GITHUB_REF_NAME`), not
|
||||
installed.json. `update_all` must keep Frametanium last (its install.sh restarts the service 3 s later).
|
||||
A bad release spreads to every Frame on its own now: test before tagging.
|
||||
- Write settings files only with `save_json` (unique temp + fsync; `private=True` for config.json) and do
|
||||
read-modify-writes under `files_lock`.
|
||||
- Test locally: run manager.py with `HOME=<scratch>` and a config.json with another port; build app.c natively
|
||||
@@ -21,4 +26,4 @@ Left, in this order: security MEDIUM 3 (Discover installs run install.sh without
|
||||
check: verify the GitHub asset `digest`; plan was an ed25519 signature for Frametanium's own updates, verified with
|
||||
`openssl pkeyutl -rawin`), 6 (tar symlink traversal, .tar.zst via GNU tar), 5 (workflow: SDL2 hash, pin
|
||||
actions by SHA), 7 (AppImage executed to unpack: prefer `unsquashfs -o <ELF end>`), LOW 8/9/11/14; then code-review
|
||||
bugs 1-9 and 12-16 (2: update Frametanium last in update_all; 4: Frametanium's own row needs a VERSION file).
|
||||
bugs 1, 3, 5-9 and 12-16 (2 and 4 were fixed with the self-update on 2026-10-21).
|
||||
@@ -34,11 +34,14 @@ and Frametanium keeps it up to date. Works from the headset or a browser.
|
||||
updates, **B** back/close.
|
||||
- **From a phone or PC:** `http://<frame-ip>:8643` (add apps, edit name/install style/program, remove).
|
||||
|
||||
Updates are checked every 6 hours and on demand.
|
||||
Updates are checked every 6 hours, each time you open the headset app, and on demand.
|
||||
|
||||
**Frametanium updates itself:** it's in its own list. Updating it downloads its latest
|
||||
[release](https://github.com/Cogential/frametanium/releases), reinstalls the service and the headset app, and
|
||||
restarts the service a few seconds later (reopen the headset app to get the new one).
|
||||
**Frametanium updates itself:** when a check finds a newer [release](https://github.com/Cogential/frametanium/releases)
|
||||
of Frametanium, it installs it without asking (unless something else is installing; the next check tries again):
|
||||
it reinstalls the service and the headset app and restarts the service a few seconds later. The headset app then
|
||||
says to close and reopen it to get the new version. Turn this off with `"self_update": false` in
|
||||
`~/.config/frametanium/config.json` (you can still update it from its row in the list). Its version comes from the
|
||||
`VERSION` file the bundle carries (`tools/bundle.sh`; releases set it from the tag).
|
||||
|
||||
## Files
|
||||
- `catalog.json`: the built-in ports.
|
||||
|
||||
@@ -801,7 +801,7 @@ int main(void) {
|
||||
static Found fd;
|
||||
static Disc disc[MAX_DISC];
|
||||
int napps = -1, sel = 0, nrels = 0, rsel = 0, msel = 0, asel = 0, nsug = 0, tried_start = 0;
|
||||
int ndisc = 0, dsel = 0, disc_running = 0, disc_new = 0, found_from = ADDING;
|
||||
int ndisc = 0, dsel = 0, disc_running = 0, disc_new = 0, found_from = ADDING, launch_checked = 0;
|
||||
char disc_msg[300] = "";
|
||||
Uint32 next_disc_poll = 0;
|
||||
int screen = LIST, kb_row = 1, kb_col = 0, notice_busy = 0;
|
||||
@@ -863,6 +863,10 @@ int main(void) {
|
||||
tried_start = 1;
|
||||
try_start_service();
|
||||
}
|
||||
if (napps >= 0 && !launch_checked) { /* check for updates (and let Frametanium update itself) on opening */
|
||||
launch_checked = 1;
|
||||
post("/api/refresh?launch=1", NULL, 0);
|
||||
}
|
||||
int busy = notice_busy;
|
||||
for (int i = 0; i < napps; i++) busy |= busy_status(apps[i].status);
|
||||
next_poll = SDL_GetTicks() + (busy ? 400 : 2000);
|
||||
|
||||
@@ -20,6 +20,7 @@ done
|
||||
|
||||
mkdir -p "$dest"
|
||||
cp "$here/manager.py" "$here/page.html" "$here/catalog.json" "$here/suggestions.json" "$dest/"
|
||||
if [ -f "$here/VERSION" ]; then cp "$here/VERSION" "$dest/"; else rm -f "$dest/VERSION"; fi
|
||||
rm -rf "$dest/art" && cp -r "$here/art" "$dest/art"
|
||||
cp "$here/frametanium.service" "$HOME/.config/systemd/user/"
|
||||
systemctl --user daemon-reload
|
||||
|
||||
+49
-5
@@ -56,6 +56,7 @@ DEFAULT_CONFIG = {
|
||||
"github_token": "", # needed only for private repos (read-only access to their releases is enough)
|
||||
"compat_tool": "SteamLinuxRuntime_4-arm64",
|
||||
"check_hours": 6,
|
||||
"self_update": True, # install new Frametanium releases by itself
|
||||
}
|
||||
CHUNK = 1024 * 1024
|
||||
RELEASES_TTL = 600
|
||||
@@ -1149,10 +1150,11 @@ def remove(pid, delete_files=True):
|
||||
|
||||
|
||||
def update_all(_pid=None):
|
||||
"""Updates every installed app that has a newer release, one after another."""
|
||||
"""Updates every installed app that has a newer release, one after another. Frametanium goes last: updating it
|
||||
restarts the service a few seconds later, which would stop any install after it."""
|
||||
jobs.set("_list", "installing", 0.0, "Updating everything")
|
||||
count = 0
|
||||
for row in app_state():
|
||||
for row in sorted(app_state(), key=lambda r: r["id"] == SELF_ID):
|
||||
if row["status"] == "update available":
|
||||
install(row["id"])
|
||||
count += 1
|
||||
@@ -1175,6 +1177,34 @@ def start(fn, pid, *args):
|
||||
_latest = {} # app id -> (tag, error)
|
||||
|
||||
|
||||
SELF_ID = "frametanium"
|
||||
|
||||
|
||||
def own_version():
|
||||
"""The release this service came from (VERSION, written by tools/bundle.sh), or None."""
|
||||
try:
|
||||
return (APP_DIR / "VERSION").read_text().strip() or None
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def self_update():
|
||||
"""Installs a newer Frametanium release once a check has found one, unless something else is installing (the next
|
||||
check tries again) or "self_update" is off in config.json."""
|
||||
if not config().get("self_update", True):
|
||||
return
|
||||
row = next((r for r in app_state() if r["id"] == SELF_ID), None)
|
||||
if row and row["status"] == "update available" and start(install, SELF_ID):
|
||||
print(f"Updating Frametanium {row['installed']} -> {row['latest']}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
def check(force=True):
|
||||
"""An update check (every few hours, when the headset app opens, or on request), then the self-update."""
|
||||
ok = refresh(force=force)
|
||||
self_update()
|
||||
return ok
|
||||
|
||||
|
||||
def refresh(force=False):
|
||||
"""Checks every app for releases. Returns False if any check failed."""
|
||||
ok = True
|
||||
@@ -1195,6 +1225,8 @@ def app_state():
|
||||
folder = target_dir(port, mode) if mode != "auto" else None
|
||||
present = bool(folder and folder.exists())
|
||||
tag = installed.get(pid, {}).get("tag") if present else None
|
||||
if pid == SELF_ID: # it's running, however it was installed (deploy.sh, its install.sh or an update)
|
||||
present, tag = True, own_version() or tag
|
||||
if present and not tag:
|
||||
tag = "unknown"
|
||||
if pid not in _latest:
|
||||
@@ -1412,8 +1444,13 @@ class Handler(BaseHTTPRequestHandler):
|
||||
arg = lambda k: q.get(k, [""])[0]
|
||||
pid = arg("id")
|
||||
if url.path == "/api/refresh":
|
||||
_releases_cache.clear()
|
||||
threading.Thread(target=refresh, kwargs={"force": True}, daemon=True).start()
|
||||
# The headset app asks with launch=1 each time it opens: releases checked in the last 10 minutes count.
|
||||
force = arg("launch") != "1"
|
||||
if force:
|
||||
_releases_cache.clear()
|
||||
elif jobs.state.get("_list", {}).get("message", "").startswith("Frametanium updated to"):
|
||||
jobs.state.pop("_list", None) # it's been reopened
|
||||
threading.Thread(target=check, kwargs={"force": force}, daemon=True).start()
|
||||
self.reply({"ok": True})
|
||||
elif url.path == "/api/update_all":
|
||||
ok = start(update_all, "_list")
|
||||
@@ -1487,7 +1524,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
def checker():
|
||||
retry = 30
|
||||
while True:
|
||||
if refresh(force=True):
|
||||
if check(force=True):
|
||||
retry = 30
|
||||
discover()
|
||||
time.sleep(max(1, float(config().get("check_hours", 6))) * 3600)
|
||||
@@ -1501,6 +1538,13 @@ def checker():
|
||||
def main():
|
||||
ensure_secrets()
|
||||
cfg = config()
|
||||
version = own_version()
|
||||
if version and version != cfg.get("last_version"):
|
||||
if cfg.get("last_version"):
|
||||
jobs.set("_list", "done", 1.0, f"Frametanium updated to {version}. Close this app and open it again "
|
||||
"to use the new version.")
|
||||
with files_lock:
|
||||
save_json(CONFIG_FILE, {**load_json(CONFIG_FILE, {}), "last_version": version}, private=True)
|
||||
threading.Thread(target=checker, daemon=True).start()
|
||||
server = ThreadingHTTPServer(("0.0.0.0", int(cfg["port"])), Handler)
|
||||
server.daemon_threads = True
|
||||
|
||||
@@ -8,5 +8,7 @@ APPDIR="$1" OUT="$2"
|
||||
rm -rf "$OUT" && mkdir -p "$OUT/tools"
|
||||
cp manager.py page.html catalog.json suggestions.json frametanium.service install.sh README.md "$OUT/"
|
||||
cp tools/find_appid.py "$OUT/tools/"
|
||||
# The release it is (releases pass VERSION; local builds count as the newest tag), so Frametanium knows when to update.
|
||||
echo "${VERSION:-$(git describe --tags --abbrev=0 2>/dev/null || echo unknown)}" > "$OUT/VERSION"
|
||||
cp -r art "$OUT/art"
|
||||
cp -r "$APPDIR" "$OUT/app"
|
||||
Reference in new issue
Block a user