From 8a391b8dfa482763c0fe2ea45c0c1d2317fa05b0 Mon Sep 17 00:00:00 2001 From: TheMockTv Date: Sun, 6 Sep 2026 20:43:15 +0200 Subject: [PATCH] Release-Anhaenge: der Name entscheidet - und ein Pruefstand dafuer Der Fehler Das Release 1.7.0 war angelegt, die EXE hing dran - und der Updater haette trotzdem nie etwas gefunden. Der Anhang hiess "Rechnungstool-1.7.0.exe", gesucht wird aber "Rechnungstool.exe" (version.EXE_NAME). Die Nummer kommt erst beim Ablegen dazu. Das Tueckische: nichts schlaegt fehl. Der Updater meldet freundlich "Aktuell. Kein Update noetig." und startet die alte Fassung. Ein Update, das stumm nie ankommt, ist schlimmer als eines, das laut scheitert. Zweiter Fund derselben Runde: die Pruefsummen standen nur im Release-TEXT. Der Updater liest sie aus einer angehaengten Datei .sha256. Ohne die prueft er nur die Groesse - und eine verfaelschte Datei hat genau die richtige Groesse. bauen.py legt jetzt release/ an Genau die vier Dateien, die ans Release gehoeren, unter genau den Namen, unter denen der Updater sie sucht. Damit haengt niemand mehr etwas Falsches an. pruef_release.py Prueft gegen das echte Repository: gibt es zur Nummer aus version.py ein Release, heisst der Anhang exakt wie EXE_NAME, haengt die Pruefsumme dran, ist es die der zuletzt gebauten Datei - und findet passendes_release() es auch. Ohne Netz wird uebersprungen statt rot gemeldet. muster/ ist raus Der Gast darin ist erfunden, der Betrieb nicht: IBAN, Steuernummer und die private Rufnummer des Auftraggebers stehen im Briefkopf. In ein oeffentliches Repository gehoert das nicht. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax --- bauen.py | 33 ++++++++++++---- pruef_release.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 pruef_release.py diff --git a/bauen.py b/bauen.py index 2220498..5ef69f5 100644 --- a/bauen.py +++ b/bauen.py @@ -79,7 +79,7 @@ def main(): # Bauordner uebernimmt sonst Reste, die es in der Quelle nicht mehr gibt - # und in setup/ laege noch die Datei der VORIGEN Nummer herum. Beim # Ausliefern greift man dann leicht zur falschen. - for ordner in ("build", "dist", "setup"): + for ordner in ("build", "dist", "setup", "release"): shutil.rmtree(os.path.join(HIER, ordner), ignore_errors=True) lauf([sys.executable, "-m", "PyInstaller", "--noconfirm", f"{stamm}.spec"], @@ -94,14 +94,33 @@ def main(): lauf([iscc(), f"/DVersion={version.VERSION}", f"/DExeProgramm={mit_nummer}", "Installer.iss"], "Setup") + # ---- release/ : genau das, was ins Release gehoert --------------------- + # Der Anhang, den der Updater sucht, heisst OHNE Nummer - so steht es in + # version.EXE_NAME, und danach sucht passendes_release(). Haengt man ihn + # mit Nummer an, findet der Updater nie ein Update und meldet stumm + # "aktuell". Die Nummer kommt erst beim Ablegen dazu (updater.dateiname). + # + # Daneben die Pruefsumme als eigene Datei: ohne sie prueft der Updater nur + # die GROESSE. Eine Datei, die unterwegs verfaelscht wurde, hat aber genau + # die richtige Groesse. + freigabe = os.path.join(HIER, "release") + os.makedirs(freigabe, exist_ok=True) + shutil.copy2(ziel, os.path.join(freigabe, version.EXE_NAME)) + with open(os.path.join(freigabe, version.EXE_NAME + ".sha256"), + "w", encoding="ascii") as f: + f.write(summe(ziel) + chr(10)) + shutil.copy2(os.path.join(HIER, "dist", "Updater.exe"), + os.path.join(freigabe, "Updater.exe")) + for name in os.listdir(os.path.join(HIER, "setup")): + shutil.copy2(os.path.join(HIER, "setup", name), + os.path.join(freigabe, name)) + print() print(f"FERTIG | {stamm} {version.VERSION}") - for name in sorted(os.listdir(os.path.join(HIER, "dist"))): - p = os.path.join(HIER, "dist", name) - print(f" {name:44s} {os.path.getsize(p):>10,d} B {summe(p)}") - setup = os.path.join(HIER, "setup") - for name in sorted(os.listdir(setup)) if os.path.isdir(setup) else []: - p = os.path.join(setup, name) + print() + print(" release/ - genau diese Dateien gehoeren ans Release:") + for name in sorted(os.listdir(freigabe)): + p = os.path.join(freigabe, name) print(f" {name:44s} {os.path.getsize(p):>10,d} B {summe(p)}") diff --git a/pruef_release.py b/pruef_release.py new file mode 100644 index 0000000..0616fa8 --- /dev/null +++ b/pruef_release.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +"""Pruefstand: passt das VEROEFFENTLICHTE Release zu dem, was der Updater sucht? + +DER FALL, DER DAZU GEFUEHRT HAT (06.09.2026) +-------------------------------------------- +Das Release war angelegt, die EXE hing dran - und der Updater haette trotzdem +nie etwas gefunden. Der Anhang hiess `Rechnungstool-1.7.0.exe`; gesucht wird +aber `Rechnungstool.exe` (so steht es in version.EXE_NAME, und danach sucht +passendes_release). Die Nummer kommt erst beim ABLEGEN dazu. + +Das Tueckische daran: nichts schlaegt fehl. Der Updater meldet freundlich +"Aktuell. Kein Update noetig." und startet die alte Fassung. Ein Update, das +stumm nie ankommt, ist schlimmer als eines, das laut scheitert. + +Zweiter Fund derselben Runde: die Pruefsummen standen nur im Release-TEXT. Der +Updater liest sie aus einer angehaengten Datei `.sha256`. Ohne die prueft +er nur die Groesse - und eine unterwegs verfaelschte Datei hat genau die +richtige Groesse. + +WAS HIER GEPRUEFT WIRD +---------------------- +Gegen das echte Repository, nicht gegen einen Nachbau: + + 1. Zu der Nummer in version.py gibt es ein Release. + 2. Es haengt eine Datei mit EXAKT dem Namen aus version.EXE_NAME an. + 3. Es haengt die Pruefsumme `.sha256` an. + 4. Die Summe darin ist die der angehaengten Datei - und die stimmt mit dem + ueberein, was hier zuletzt gebaut wurde (release/). + 5. passendes_release() findet dieses Release auch wirklich. + +Ohne Netz wird uebersprungen statt rot gemeldet: ein fehlender Anschluss ist +kein Fehler des Programms. +""" +import os +import sys + +HIER = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HIER) + +import updater # noqa: E402 +import version # noqa: E402 + +fehler = [] + + +def pruefe(bedingung, text): + zeile = (" OK | " if bedingung else "FEHLER | ") + text + kod = sys.stdout.encoding or "utf-8" + print(zeile.encode(kod, "replace").decode(kod)) + if not bedingung: + fehler.append(text) + + +try: + releases = updater.releases_holen(version.REPOSITORY) +except Exception as e: # noqa: BLE001 - ohne Netz ist hier nichts zu pruefen + print(f"UEBERSPRUNGEN | Repository nicht erreichbar ({e})") + sys.exit(0) + +marke = "v" + version.VERSION +dieses = next((r for r in releases if (r.get("tag_name") or "") == marke), None) +pruefe(dieses is not None, f"zu Fassung {version.VERSION} gibt es ein Release ({marke})") +if dieses is None: + print("ROT | ohne Release ist der Rest nicht pruefbar") + sys.exit(1) + +anhaenge = {a.get("name"): a for a in dieses.get("assets") or []} +print(f" (Anhaenge: {', '.join(sorted(anhaenge)) or 'keine'})") + +pruefe(version.EXE_NAME in anhaenge, + f"der Anhang heisst genau {version.EXE_NAME} - danach sucht der Updater") +pruefe(version.EXE_NAME + ".sha256" in anhaenge, + f"die Pruefsumme haengt als {version.EXE_NAME}.sha256 dran") + +# ---- Die Summe muss zur Datei passen, und zwar zu der gebauten ------------ +gebaut = os.path.join(HIER, "release", version.EXE_NAME) +url_summe = updater.summe_aus_release(dieses, version.EXE_NAME) +if url_summe: + veroeffentlicht = updater.text_holen(url_summe).strip().split()[0] + if os.path.exists(gebaut): + pruefe(veroeffentlicht == updater.sha256(gebaut), + "die veroeffentlichte Summe ist die der zuletzt gebauten Datei") + else: + print(f" (release/{version.EXE_NAME} fehlt - Summe nicht " + f"gegenpruefbar, vorher: python bauen.py)") + pruefe(len(veroeffentlicht) == 64 and all(c in "0123456789abcdef" for c in veroeffentlicht), + f"und sie sieht aus wie eine SHA256 ({veroeffentlicht[:16]}...)") + +# ---- Und findet der Updater das Release ueberhaupt? ----------------------- +treffer = updater.passendes_release(releases, version.EXE_NAME) +pruefe(treffer is not None, "passendes_release findet ueberhaupt ein Release") +if treffer: + pruefe((treffer[0].get("tag_name") or "") == marke, + f"und zwar dieses hier ({treffer[0].get('tag_name')})") + +if fehler: + print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt") + sys.exit(1) +print("GRUEN | Das Release ist so, wie der Updater es erwartet")