2 Commits

Author SHA1 Message Date
Flowseal 5bc5001c4d SHA256 compare fix 2026-06-18 15:22:30 +03:00
Flowseal 2afd80825b docs update 2026-06-17 11:33:20 +03:00
5 changed files with 86 additions and 38 deletions
+1 -1
View File
@@ -49,7 +49,7 @@
- [Fake TLS + upstream в Nginx](./FakeTlsNginx.md) - [Fake TLS + upstream в Nginx](./FakeTlsNginx.md)
- [Файлы конфигурации Tray-приложения](./TrayConfig.md) - [Файлы конфигурации Tray-приложения](./TrayConfig.md)
- [Установка из исходников](./BuildFromSource.md) - [Установка из исходников](./BuildFromSource.md)
- [Руководство для контрибьюторов](../CONTRIBUTING.md) - [Руководство для контрибьюторов](./CONTRIBUTING.md)
## Windows: быстрый вход ## Windows: быстрый вход
+4
View File
@@ -43,6 +43,10 @@
- **Порт:** `1443` (или переопределенный вами) - **Порт:** `1443` (или переопределенный вами)
- **Secret:** из настроек или логов - **Secret:** из настроек или логов
## Портативный режим
Портативный режим автоматически включается, если рядом с исполняемым файлом есть папка с названием `TgWsProxy_data`.
Либо можно принудительно включить портативный режим (который сам создаст папку), запустив исполняемый файл с параметром `--portable`.
## Установка из исходников ## Установка из исходников
Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md) Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md)
+80 -36
View File
@@ -1,5 +1,5 @@
""" """
Минимальная проверка новой версии через GitHub Releases API (без сторонних зависимостей). Проверка новой версии через GitHub Releases API
Ограничение частоты запросов: не чаще одного раза в час на машину (кэш в каталоге Ограничение частоты запросов: не чаще одного раза в час на машину (кэш в каталоге
данных приложения). Поддерживается If-None-Match (ETag) для ответа 304. данных приложения). Поддерживается If-None-Match (ETag) для ответа 304.
@@ -18,6 +18,7 @@ from proxy.utils import build_github_opener
REPO = "Flowseal/tg-ws-proxy" REPO = "Flowseal/tg-ws-proxy"
RELEASES_LATEST_API = f"https://api.github.com/repos/{REPO}/releases/latest" RELEASES_LATEST_API = f"https://api.github.com/repos/{REPO}/releases/latest"
RELEASES_BY_TAG_API = f"https://api.github.com/repos/{REPO}/releases/tags/{{tag}}?t={{timestamp}}"
RELEASES_PAGE_URL = f"https://github.com/{REPO}/releases/latest" RELEASES_PAGE_URL = f"https://github.com/{REPO}/releases/latest"
# Не чаще одного полного запроса к API в час (без учёта 304 с тем же ETag). # Не чаще одного полного запроса к API в час (без учёта 304 с тем же ETag).
@@ -223,58 +224,101 @@ def run_check(current_version: str) -> None:
_state["html_url"] = RELEASES_PAGE_URL _state["html_url"] = RELEASES_PAGE_URL
def fetch_release_by_tag(
tag: str, timeout: float = 12.0,
) -> Tuple[Optional[dict], int]:
if not tag:
return None, 0
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "tg-ws-proxy-update-check",
}
req = Request(
RELEASES_BY_TAG_API.format(tag=tag, timestamp=int(time.time())),
headers=headers,
method="GET",
)
try:
with build_github_opener().open(req, timeout=timeout) as resp:
code = getattr(resp, "status", None) or resp.getcode()
raw = resp.read().decode("utf-8", errors="replace")
return json.loads(raw), int(code)
except HTTPError as e:
if e.code in [304, 404]:
return None, e.code
raise
def _extract_assets(data: Optional[dict]) -> list:
if not data:
return []
return [
{"name": a.get("name", ""), "url": a.get("browser_download_url", ""), "digest": a.get("digest", "")}
for a in (data.get("assets") or [])
if a.get("name") and a.get("browser_download_url")
]
def get_status() -> Dict[str, Any]: def get_status() -> Dict[str, Any]:
"""Снимок состояния после run_check (для подписей в настройках).""" """Снимок состояния после run_check (для подписей в настройках)."""
return dict(_state) return dict(_state)
def get_update_asset(exe_path: Path) -> Optional[Tuple[str, str]]: def get_update_asset(exe_path: Path, current_version: str) -> Optional[Tuple[str, str]]:
assets = _state.get("assets") or [] new_assets = _state.get("assets") or []
if not assets: if not new_assets:
return None return None
# Try SHA256 match against release asset digests target_name = None
# SHA256 match
try: try:
import hashlib import hashlib
h = hashlib.sha256() data, code = fetch_release_by_tag(f"v{current_version}")
with open(exe_path, "rb") as f: if code == 200 and data:
while True: cur_assets = _extract_assets(data)
chunk = f.read(65536) if cur_assets:
if not chunk: h = hashlib.sha256()
break with open(exe_path, "rb") as f:
h.update(chunk) while True:
exe_sha = h.hexdigest().lower() chunk = f.read(65536)
for a in assets: if not chunk:
d = (a.get("digest") or "").lower() break
if d.startswith("sha256:") and d[7:] == exe_sha: h.update(chunk)
return a["url"], a["name"] exe_sha = h.hexdigest().lower()
for a in cur_assets:
d = (a.get("digest") or "").lower()
if d.startswith("sha256:") and d[7:] == exe_sha:
target_name = a["name"]
break
except Exception: except Exception:
pass pass
# Fallback # Fallback
import platform if not target_name or target_name not in [a.get("name") for a in new_assets]:
import struct import platform
import struct
is_64 = struct.calcsize("P") * 8 == 64 is_64 = struct.calcsize("P") * 8 == 64
machine = platform.machine().lower() machine = platform.machine().lower()
is_arm64 = machine in ("arm64", "aarch64") is_arm64 = machine in ("arm64", "aarch64")
try: try:
is_modern = sys.getwindowsversion().major >= 10 is_modern = sys.getwindowsversion().major >= 10
except Exception: except Exception:
is_modern = True is_modern = True
if is_arm64: if is_arm64:
name = "TgWsProxy_windows_arm64.exe" target_name = "TgWsProxy_windows_arm64.exe"
elif is_modern: elif is_modern:
name = "TgWsProxy_windows.exe" target_name = "TgWsProxy_windows.exe"
elif is_64: elif is_64:
name = "TgWsProxy_windows_7_64bit.exe" target_name = "TgWsProxy_windows_7_64bit.exe"
else: else:
name = "TgWsProxy_windows_7_32bit.exe" target_name = "TgWsProxy_windows_7_32bit.exe"
for a in assets: for a in new_assets:
if a.get("name") == name: if a.get("name") == target_name:
return a["url"], a["name"] return a["url"], a["name"]
return None return None
+1 -1
View File
@@ -333,7 +333,7 @@ def _maybe_do_update(cfg: dict, is_exiting) -> None:
return return
url = (st.get("html_url") or "").strip() or RELEASES_PAGE_URL url = (st.get("html_url") or "").strip() or RELEASES_PAGE_URL
ver = st.get("latest") or "?" ver = st.get("latest") or "?"
asset = get_update_asset(Path(sys.executable)) if IS_FROZEN else None asset = get_update_asset(Path(sys.executable), __version__) if IS_FROZEN else None
choice = update_ctk_form( choice = update_ctk_form(
f"Доступна новая версия: {ver}", f"Доступна новая версия: {ver}",
download_url=asset[0] if asset else None, download_url=asset[0] if asset else None,