#!/usr/bin/env python3 """Публикует новую версию кода как релиз в Gitea. В релиз кладётся только папка app - несколько десятков килобайт. Целевая машина скачивает этот архив при следующем запуске и обновляется, не трогая Python, библиотеки, ffmpeg и модели. uv run --with requests python build/release.py 0.2.0 -m "что изменилось" """ import argparse import hashlib import io import os import re import subprocess import sys import zipfile from pathlib import Path import requests ROOT = Path(__file__).resolve().parent.parent SERVER = "https://git.netranking.ru" REPO = "bryzgalov/talkscore-asr" def get_token() -> str: token = os.environ.get("GITEA_RELEASE_TOKEN") if token: return token # Секреты лежат зашифрованными, достаём их штатной утилитой. try: out = subprocess.run(["secrets", "get", "GITEA_RELEASE_TOKEN"], capture_output=True, text=True, check=True) return out.stdout.strip() except (subprocess.SubprocessError, FileNotFoundError): sys.exit("не найден GITEA_RELEASE_TOKEN") def bump_version(version: str) -> None: """Записывает версию в app/version.py, чтобы она уехала внутрь архива.""" path = ROOT / "app" / "version.py" path.write_text(f'__version__ = "{version}"\n', encoding="utf-8") def pack_app() -> bytes: buf = io.BytesIO() with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z: for file in sorted((ROOT / "app").rglob("*.py")): if "__pycache__" in file.parts: continue z.write(file, Path("app") / file.relative_to(ROOT / "app")) return buf.getvalue() def api(method: str, path: str, token: str, **kwargs): response = requests.request( method, f"{SERVER}/api/v1/{path}", headers={"Authorization": f"token {token}"}, timeout=60, **kwargs) response.raise_for_status() return response def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("version", help="номер версии, например 0.2.0") ap.add_argument("-m", "--message", default="", help="описание изменений") args = ap.parse_args() if not re.fullmatch(r"\d+\.\d+\.\d+", args.version): sys.exit("версия должна быть вида 1.2.3") token = get_token() bump_version(args.version) payload = pack_app() digest = hashlib.sha256(payload).hexdigest() name = f"app-{args.version}.zip" print(f"Архив {name}: {len(payload) / 1024:.0f} КБ") tag = f"v{args.version}" release = api("POST", f"repos/{REPO}/releases", token, json={ "tag_name": tag, "name": tag, "body": args.message or f"Версия {args.version}", "draft": False, "prerelease": False, }).json() api("POST", f"repos/{REPO}/releases/{release['id']}/assets?name={name}", token, files={"attachment": (name, payload, "application/zip")}) api("POST", f"repos/{REPO}/releases/{release['id']}/assets?name={name}.sha256", token, files={"attachment": (f"{name}.sha256", f"{digest} {name}\n".encode(), "text/plain")}) print(f"Опубликовано: {SERVER}/{REPO}/releases/tag/{tag}") print("Целевая машина подхватит обновление при следующем запуске start.bat") return 0 if __name__ == "__main__": sys.exit(main())