Nachgelesen in ct/<app>.sh: AdGuard und PVE Scripts Local verweisen auf ihren eingebauten Updater, Gitea/NetBird/NPMplus aktualisieren wirklich, PBS ueber die Pakete. /root/.proxmoxve-local stand noch auf 0.5.8, die App selbst schon auf der neuesten Veroeffentlichung (v1.2.1, untagged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 lines
1.8 KiB
Python
46 lines
1.8 KiB
Python
"""GitHub-Abfragen mit Zwischenspeicher — für beide Rollen (Box: Motor und llama-swap, Homelab: die Apps).
|
|
|
|
GitHub erlaubt ohne Anmeldung 60 Anfragen pro Stunde. Jeder Blick auf die Update-Details fragte früher
|
|
neu; jetzt gilt eine Antwort 15 Minuten. Fehler (auch das Anfragelimit) werden nicht gemerkt.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import threading
|
|
import time
|
|
|
|
import httpx
|
|
|
|
GITHUB_CACHE_S = int(os.environ.get("MC_GITHUB_CACHE_S", "900"))
|
|
_cache: dict[str, tuple[float, object]] = {}
|
|
_lock = threading.Lock()
|
|
|
|
|
|
def github_json(pfad: str, timeout: float = 8) -> object:
|
|
"""GET https://api.github.com/<pfad>, 15 Minuten gemerkt. Wirft bei HTTP-Fehlern."""
|
|
jetzt = time.time()
|
|
with _lock:
|
|
if (eintrag := _cache.get(pfad)) and jetzt - eintrag[0] < GITHUB_CACHE_S:
|
|
return eintrag[1]
|
|
r = httpx.get(f"https://api.github.com/{pfad}", timeout=timeout, headers={"User-Agent": "MissionControl2"})
|
|
r.raise_for_status()
|
|
daten = r.json()
|
|
with _lock:
|
|
_cache[pfad] = (jetzt, daten)
|
|
return daten
|
|
|
|
|
|
_VERSION = re.compile(r"^v?(\d+(?:\.\d+)+|\d{4}-\d{2}-\d{2}(?:-r\d+)?)", re.IGNORECASE)
|
|
|
|
|
|
def neueste_version(repo: str) -> dict | None:
|
|
"""Neueste Veröffentlichung eines Repos: {"tag", "version", "datum"} oder None. Manche Projekte
|
|
veröffentlichen „untagged“ und tragen die Version nur im Namen (PVE Scripts Local, 09/2026)."""
|
|
daten = github_json(f"repos/{repo}/releases/latest")
|
|
if not isinstance(daten, dict) or not daten.get("tag_name"):
|
|
return None
|
|
tag, name = str(daten["tag_name"]), str(daten.get("name") or "")
|
|
roh = tag if _VERSION.match(tag) or not _VERSION.match(name) else name
|
|
return {"tag": roh, "tag_roh": tag, "version": roh.lstrip("vV"),
|
|
"datum": str(daten.get("published_at") or "")[:10] or None}
|