diff --git a/macos.py b/macos.py index 92f01f2..b61745d 100644 --- a/macos.py +++ b/macos.py @@ -21,6 +21,7 @@ import rumps # ── proxy core is a sibling package ──────────────────────────────────────── sys.path.insert(0, str(Path(__file__).parent)) import proxy.tg_ws_proxy as tg_ws_proxy +import updater # ── paths ─────────────────────────────────────────────────────────────────── APP_NAME = "TgWsProxy" @@ -363,7 +364,31 @@ def main(): log.info("Config: %s", _config) log.info("Log file: %s", LOG_FILE) - start_proxy(_config) + # ── Auto-update proxy core at startup ──────────────────────────────── + def _do_update(): + updated = updater.check_and_update() + if updated: + log.info("Proxy core updated — reloading and restarting proxy") + import importlib + global tg_ws_proxy + try: + import proxy.tg_ws_proxy as _fresh + importlib.reload(_fresh) + tg_ws_proxy = _fresh + except Exception as exc: + log.error("Failed to reload proxy core after update: %s", exc) + restart_proxy() + rumps.notification( + APP_NAME, + "Обновление установлено", + "Proxy core обновлён и перезапущен.", + sound=False, + ) + else: + start_proxy(_config) + + threading.Thread(target=_do_update, daemon=True).start() + # ───────────────────────────────────────────────────────────────────── if not FIRST_RUN_MARKER.exists(): threading.Thread(target=show_first_run, daemon=True).start() diff --git a/macos.spec b/macos.spec index 3a16b94..64c5767 100644 --- a/macos.spec +++ b/macos.spec @@ -14,6 +14,7 @@ a = Analysis( datas=[], hiddenimports=[ 'proxy.tg_ws_proxy', + 'updater', 'cryptography', 'cryptography.hazmat.primitives.ciphers', 'cryptography.hazmat.primitives.ciphers.algorithms', diff --git a/updater.py b/updater.py new file mode 100644 index 0000000..0eda599 --- /dev/null +++ b/updater.py @@ -0,0 +1,100 @@ +""" +updater.py — автообновление proxy/tg_ws_proxy.py с GitHub main ветки. + +Логика: + 1. Скачивает актуальный файл с GitHub raw + 2. Сравнивает SHA-256 с локальной копией + 3. Если отличается — сохраняет новую версию, возвращает True + 4. Вся работа синхронная (вызывается из фонового потока) +""" +from __future__ import annotations + +import hashlib +import logging +import shutil +import sys +import urllib.request +from pathlib import Path +from typing import Optional + +log = logging.getLogger("tg-ws-updater") + +RAW_URL = ( + "https://raw.githubusercontent.com/" + "Flowseal/tg-ws-proxy/main/proxy/tg_ws_proxy.py" +) + +# Локальный путь к файлу ядра (рядом с этим скриптом) +_HERE = Path(__file__).parent +PROXY_CORE = _HERE / "proxy" / "tg_ws_proxy.py" + +TIMEOUT = 15 # секунд на скачивание + + +def _sha256(path: Path) -> Optional[str]: + """SHA-256 файла или None если файл не существует.""" + if not path.exists(): + return None + h = hashlib.sha256() + with open(path, "rb") as fh: + for chunk in iter(lambda: fh.read(65536), b""): + h.update(chunk) + return h.hexdigest() + + +def _fetch(url: str) -> bytes: + """Скачать URL, вернуть содержимое как bytes.""" + req = urllib.request.Request( + url, + headers={"User-Agent": "tg-ws-proxy-macos-updater/1.0"}, + ) + with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: + return resp.read() + + +def check_and_update() -> bool: + """ + Проверить обновление proxy core. + + Возвращает True если файл был обновлён, False если уже актуален + или произошла ошибка (ошибки логируются, не бросаются). + """ + log.info("Checking for proxy core update: %s", RAW_URL) + try: + new_content = _fetch(RAW_URL) + except Exception as exc: + log.warning("Update check failed (network): %s", exc) + return False + + new_hash = hashlib.sha256(new_content).hexdigest() + old_hash = _sha256(PROXY_CORE) + + if new_hash == old_hash: + log.info("Proxy core is up to date (sha256: %s…)", new_hash[:12]) + return False + + log.info( + "Proxy core update detected: %s… → %s…", + (old_hash or "none")[:12], + new_hash[:12], + ) + + # Атомарная замена: пишем во временный файл, потом переименовываем + tmp = PROXY_CORE.with_suffix(".py.tmp") + try: + PROXY_CORE.parent.mkdir(parents=True, exist_ok=True) + tmp.write_bytes(new_content) + shutil.move(str(tmp), str(PROXY_CORE)) + except Exception as exc: + log.error("Failed to write updated proxy core: %s", exc) + tmp.unlink(missing_ok=True) + return False + + # Выгрузить старый модуль из sys.modules, чтобы следующий import + # подхватил новый файл с диска + for key in list(sys.modules.keys()): + if "tg_ws_proxy" in key: + del sys.modules[key] + + log.info("Proxy core updated successfully") + return True