116 lines
3.9 KiB
Python
116 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
||
"""Создаёт неизменяемый релиз Forgejo и загружает собранные файлы."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import mimetypes
|
||
import os
|
||
import sys
|
||
import urllib.error
|
||
import urllib.parse
|
||
import urllib.request
|
||
from pathlib import Path
|
||
|
||
|
||
def required_env(name: str) -> str:
|
||
value = os.environ.get(name, "").strip()
|
||
if not value:
|
||
raise RuntimeError(f"не задана переменная окружения {name}")
|
||
return value
|
||
|
||
|
||
def request_json(
|
||
url: str,
|
||
token: str,
|
||
*,
|
||
method: str = "GET",
|
||
payload: dict[str, object] | None = None,
|
||
) -> dict[str, object]:
|
||
data = None if payload is None else json.dumps(payload).encode("utf-8")
|
||
headers = {
|
||
"Accept": "application/json",
|
||
"Authorization": f"token {token}",
|
||
"User-Agent": "zapret2-forgejo-release",
|
||
}
|
||
if data is not None:
|
||
headers["Content-Type"] = "application/json"
|
||
request = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||
with urllib.request.urlopen(request, timeout=60) as response:
|
||
return json.load(response)
|
||
|
||
|
||
def upload_asset(url: str, token: str, path: Path) -> None:
|
||
content_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
|
||
request = urllib.request.Request(
|
||
f"{url}?{urllib.parse.urlencode({'name': path.name})}",
|
||
data=path.read_bytes(),
|
||
headers={
|
||
"Accept": "application/json",
|
||
"Authorization": f"token {token}",
|
||
"Content-Type": content_type,
|
||
"User-Agent": "zapret2-forgejo-release",
|
||
},
|
||
method="POST",
|
||
)
|
||
with urllib.request.urlopen(request, timeout=300) as response:
|
||
result = json.load(response)
|
||
print(f"Загружен {path.name}: {result.get('browser_download_url', '')}")
|
||
|
||
|
||
def main() -> int:
|
||
if len(sys.argv) < 2:
|
||
print(f"Использование: {sys.argv[0]} ФАЙЛ...", file=sys.stderr)
|
||
return 2
|
||
|
||
token = required_env("FORGEJO_TOKEN")
|
||
api_url = required_env("FORGEJO_API_URL").rstrip("/")
|
||
repository = required_env("FORGEJO_REPOSITORY")
|
||
tag = required_env("RELEASE_TAG")
|
||
assets = [Path(argument).resolve() for argument in sys.argv[1:]]
|
||
|
||
for asset in assets:
|
||
if not asset.is_file():
|
||
raise RuntimeError(f"файл релиза не найден: {asset}")
|
||
|
||
release_url = f"{api_url}/repos/{repository}/releases"
|
||
prerelease = "-" in tag
|
||
release = request_json(
|
||
release_url,
|
||
token,
|
||
method="POST",
|
||
payload={
|
||
"body": (
|
||
"Восстановленная автономная сборка Zapret2 для YouTube и Discord.\n\n"
|
||
"В релиз входят архив 7z, самораспаковывающийся EXE и SHA256SUMS.txt "
|
||
"для проверки целостности."
|
||
),
|
||
"draft": False,
|
||
"name": f"Zapret2 YouTube Discord {tag}",
|
||
"prerelease": prerelease,
|
||
"tag_name": tag,
|
||
"target_commitish": tag,
|
||
},
|
||
)
|
||
release_id = release.get("id")
|
||
if not isinstance(release_id, int):
|
||
raise RuntimeError("Forgejo не вернул числовой идентификатор релиза")
|
||
|
||
asset_url = f"{release_url}/{release_id}/assets"
|
||
for asset in assets:
|
||
upload_asset(asset_url, token, asset)
|
||
|
||
print(f"Релиз опубликован: {release.get('html_url', '')}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
sys.exit(main())
|
||
except urllib.error.HTTPError as exc:
|
||
body = exc.read().decode("utf-8", errors="replace")
|
||
print(f"Forgejo API вернул HTTP {exc.code}: {body}", file=sys.stderr)
|
||
sys.exit(1)
|
||
except (OSError, RuntimeError, ValueError) as exc:
|
||
print(f"Ошибка публикации: {exc}", file=sys.stderr)
|
||
sys.exit(1)
|