Files
talkscore-asr/build/release.py
T
Vladimir BryzgalovandClaude Opus 5 9dc67bda5c talkscore-asr 0.1.0: сервис транскрибации и диаризации
Локальный FastAPI-сервис поверх GigaAM v3 и sherpa-onnx: приём аудио,
очередь задач, разделение по говорящим, постобработка терминов.

Доставка на Windows - ZIP со встроенным Python, без установки чего-либо.
Обновление кода при запуске тянется из релизов Gitea: меняется только
папка app, десятки килобайт вместо всего пакета.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 21:41:04 +05:00

101 lines
3.6 KiB
Python

#!/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())