From 5771d631fdddfcc9065c44bdf968ca7ab38a85a6 Mon Sep 17 00:00:00 2001 From: luzadev Date: Tue, 9 Jun 2026 22:14:53 +0200 Subject: [PATCH] v1.7.12: codesign identifier coerente per TCC subprocess + skip quota su Annual Bug: codesign --deep ad-hoc dava a ffmpeg un identifier auto-generato (ffmpeg-) diverso da com.djluza.musictools del bundle, quindi macOS trattava ffmpeg come app distinta per TCC -> permesso microfono del main NON ereditato dal subprocess -> registrazione muta o errore. Fix: in adhoc_codesign() ora firmiamo OGNI Mach-O nested con --identifier com.djluza.musictools (stesso del bundle), poi firmiamo il bundle .app. Cosi' TCC vede un unico "team" coerente e il permesso microfono si propaga a ffmpeg/ffprobe/yt-dlp. Verificato in dev mode: registrazione BlackHole funziona quando ffmpeg eredita correttamente il TCC. Anche fix collaterale: bridge._gate() salta la chiamata consume_quota al server quando il piano corrente ha daily_limit=None (Annual). Per i piani unlimited non c'e' motivo di chiamare il server -> niente piu' errori online se il server e' temporaneamente irraggiungibile durante una registrazione su un piano Annual. --- api/bridge.py | 7 +++- build_macos.py | 90 +++++++++++++++++++++++++++++++++++--------------- core/config.py | 2 +- 3 files changed, 71 insertions(+), 28 deletions(-) diff --git a/api/bridge.py b/api/bridge.py index 3a32a15..27f0631 100644 --- a/api/bridge.py +++ b/api/bridge.py @@ -229,7 +229,12 @@ class Api: ), "plan": plan, } - # 2) Quota gate server-side + # 2) Quota gate server-side: skippato per i piani senza limite + # giornaliero (Annual) — evita una chiamata HTTP inutile e fa + # funzionare l'app anche offline per quei piani. + plan = license_mod.get_plan() + if plan.get("daily_limit") is None: + return None try: res = license_mod.consume_quota(feature) except license_mod.LicenseNetworkError as e: diff --git a/build_macos.py b/build_macos.py index 43df681..d565a76 100644 --- a/build_macos.py +++ b/build_macos.py @@ -293,40 +293,78 @@ def patch_info_plist(app_path): log("Info.plist aggiornato.") +_BUNDLE_ID = "com.djluza.musictools" + + def adhoc_codesign(app_path): - """Ad-hoc codesign dell'intero bundle .app. + """Ad-hoc codesign dell'intero bundle .app con identifier coerente. - Senza una firma coerente, macOS tratta ffmpeg (subprocess bundled) - come binario separato dal main MusicTools per TCC: ffmpeg apre il - dispositivo audio senza errore ma riceve solo silenzio perche' il - sistema non gli concede il permesso microfono ereditato. + macOS tratta ogni binario Mach-O firmato come una "app" distinta per + le decisioni TCC (microfono, camera, ecc.). Con `codesign --deep` + standard, i nested binary (ffmpeg, ffprobe, yt-dlp) ricevono ognuno + un identifier auto-generato del tipo `-`, diverso da + quello del bundle principale -> TCC NEGA al subprocess. - `codesign --force --deep --sign -` applica una firma ad-hoc (gratis, - senza Developer ID) a tutti i binari nested. Per macOS questo rende - l'app un singolo "team" coerente, e il permesso TCC concesso al - main si propaga anche a ffmpeg/ffprobe/yt-dlp. + Soluzione: firmiamo PRIMA ogni binario interno con `--identifier + com.djluza.musictools`, poi il bundle .app intero. Cosi' TCC vede + tutti i Mach-O come parte dello stesso "team" e propaga il permesso + del main ai subprocess. """ - log("Ad-hoc codesign del bundle (per TCC microfono ereditato)...") + log("Ad-hoc codesign del bundle (identifier coerente per TCC)...") try: - # Rimuovi vecchie firme che potrebbero confondere codesign --deep - subprocess.run( - ["xattr", "-cr", str(app_path)], - capture_output=True, - ) - subprocess.run( - ["codesign", "--remove-signature", "--deep", str(app_path)], - capture_output=True, - ) - result = subprocess.run( - ["codesign", "--force", "--deep", "--sign", "-", - "--timestamp=none", str(app_path)], + # 1) Pulisci attributi estesi (rimuove quarantine residui dal build) + subprocess.run(["xattr", "-cr", str(app_path)], capture_output=True) + + # 2) Trova tutti i Mach-O nested (binari ed eventuali .dylib). + # Li firmiamo uno a uno con identifier coerente, dal piu' profondo + # al piu' esterno (richiesta da codesign). + nested_machos = [] + for p in sorted(app_path.rglob("*"), key=lambda x: -len(x.parts)): + if not p.is_file(): + continue + # Skip risorse non eseguibili + if p.suffix in (".plist", ".nib", ".png", ".icns", ".svg", + ".html", ".css", ".js", ".json", ".md", ".txt"): + continue + try: + with open(p, "rb") as f: + magic = f.read(4) + except Exception: + continue + # Mach-O magic numbers (32/64-bit, big/little endian, fat) + if magic in (b"\xcf\xfa\xed\xfe", b"\xce\xfa\xed\xfe", + b"\xfe\xed\xfa\xce", b"\xfe\xed\xfa\xcf", + b"\xca\xfe\xba\xbe", b"\xbe\xba\xfe\xca"): + nested_machos.append(p) + + log(f"Trovati {len(nested_machos)} binari Mach-O da firmare.") + + for p in nested_machos: + r = subprocess.run( + ["codesign", "--force", "--sign", "-", + "--identifier", _BUNDLE_ID, + "--timestamp=none", + str(p)], + capture_output=True, text=True, + ) + if r.returncode != 0: + log(f" warn: firma {p.name} fallita: {r.stderr.strip()}") + + # 3) Firma del bundle .app principale (l'identifier corretto viene + # letto dall'Info.plist - CFBundleIdentifier che gia' settiamo). + r = subprocess.run( + ["codesign", "--force", "--sign", "-", + "--identifier", _BUNDLE_ID, + "--timestamp=none", + str(app_path)], capture_output=True, text=True, ) - if result.returncode != 0: - log(f"ATTENZIONE: codesign fallito: {result.stderr.strip()}") + if r.returncode != 0: + log(f"ATTENZIONE: firma bundle fallita: {r.stderr.strip()}") else: log("Codesign ad-hoc completato.") - # Verifica + + # 4) Verifica finale verify = subprocess.run( ["codesign", "--verify", "--deep", "--strict", str(app_path)], capture_output=True, text=True, @@ -334,7 +372,7 @@ def adhoc_codesign(app_path): if verify.returncode == 0: log("Verifica firma OK.") else: - log(f"NOTA: verifica firma con warning: {verify.stderr.strip()}") + log(f"NOTA verifica firma: {verify.stderr.strip()}") except FileNotFoundError: log("ATTENZIONE: 'codesign' non trovato nel PATH, skip ad-hoc signing") diff --git a/core/config.py b/core/config.py index e464d85..a60ab8e 100644 --- a/core/config.py +++ b/core/config.py @@ -5,7 +5,7 @@ import os import sys from pathlib import Path -VERSION = "v1.7.11" +VERSION = "v1.7.12" APP_NAME = "MusicTools"