Public Access
CI / build (push) Successful in 11m26s
The workflow runs on the runner's host and builds in the project's Docker image through scripts/ci.sh, as on a developer's machine. A tag v*, or a run by hand for an older tag, builds that tag's sources, signs the Update File with the key held in the repository's secrets, checks the signature against the public key in the sources, and publishes a Gitea release with the .ota, the factory image, the ELF and checksums. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
66 lines
2.7 KiB
Python
Executable File
66 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Creates or completes a Gitea release from what scripts/release_build.sh made.
|
|
|
|
Usage: scripts/release_publish.py <folder>
|
|
Environment: GITEA_API (https://host/api/v1), GITEA_REPO (owner/name), GITEA_TOKEN.
|
|
Run again for the same version, it replaces the files and the text instead of failing.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
API = os.environ.get("GITEA_API", "").rstrip("/")
|
|
REPO = os.environ.get("GITEA_REPO", "")
|
|
TOKEN = os.environ.get("GITEA_TOKEN", "")
|
|
|
|
|
|
def call(method, path, body=None, raw=None, content_type="application/json"):
|
|
data = raw if raw is not None else (json.dumps(body).encode() if body is not None else None)
|
|
request = urllib.request.Request(f"{API}/repos/{REPO}{path}", data=data, method=method)
|
|
request.add_header("Authorization", f"token {TOKEN}")
|
|
if data is not None:
|
|
request.add_header("Content-Type", content_type)
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=300) as response:
|
|
text = response.read()
|
|
return response.status, json.loads(text) if text else None
|
|
except urllib.error.HTTPError as e:
|
|
return e.code, e.read().decode(errors="replace")[:300]
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) != 2 or not (API and REPO and TOKEN):
|
|
sys.exit(__doc__)
|
|
folder = sys.argv[1]
|
|
version = open(os.path.join(folder, "version")).read().strip()
|
|
notes = open(os.path.join(folder, "notes.md")).read()
|
|
files = sorted(f for f in os.listdir(folder) if f not in ("version", "notes.md"))
|
|
|
|
status, release = call("GET", f"/releases/tags/{urllib.parse.quote(version)}")
|
|
fields = {"tag_name": version, "name": f"roro9stack {version}", "body": notes, "draft": False, "prerelease": False}
|
|
if status == 200:
|
|
status, release = call("PATCH", f"/releases/{release['id']}", fields)
|
|
else:
|
|
status, release = call("POST", "/releases", fields)
|
|
if status not in (200, 201):
|
|
sys.exit(f"release: Gitea answered {status}: {release}")
|
|
|
|
for asset in release.get("assets") or []: # a second run replaces what the first uploaded
|
|
if asset["name"] in files:
|
|
call("DELETE", f"/releases/{release['id']}/assets/{asset['id']}")
|
|
for name in files:
|
|
with open(os.path.join(folder, name), "rb") as f:
|
|
status, answer = call("POST", f"/releases/{release['id']}/assets?name={urllib.parse.quote(name)}", raw=f.read(),
|
|
content_type="application/octet-stream")
|
|
if status != 201:
|
|
sys.exit(f"release: uploading {name}: Gitea answered {status}: {answer}")
|
|
print(f"uploaded {name} ({answer['size']} bytes)")
|
|
print(f"published {release['html_url']}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|