release build workflow

This commit is contained in:
abrown38 committed 2026-09-30 19:57:43 +00:00
1 parent 998d9f603d
commit 8084c39e37
4 files changed
+140 -1

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# Builds the .deb when a version tag is pushed and publishes it as a release,
# with that version's CHANGELOG.md section as the release notes. To release,
# set VERSION and CHANGELOG.md, commit, then:
# git tag v$(cat VERSION) && git push origin v$(cat VERSION)
name: release
on:
push:
tags: ['v*']
jobs:
deb:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check that the tag matches VERSION
run: |
if [ "$GITHUB_REF_NAME" != "v$(cat VERSION)" ]; then
echo "tag $GITHUB_REF_NAME doesn't match VERSION ($(cat VERSION))"
exit 1
fi
- name: Install build tools
env:
DEBIAN_FRONTEND: noninteractive
run: |
SUDO=$(command -v sudo || true)
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq make python3 dpkg shellcheck python3-pyflakes
- name: Build (make check runs first)
run: make deb
- name: Publish the release
env:
GITEA_URL: ${{ gitea.server_url }}
GITEA_REPO: ${{ gitea.repository }}
# The job's own token; set a RELEASE_TOKEN secret instead if it isn't allowed to write releases
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITEA_TOKEN }}
run: python3 packaging/gitea-release.py "$GITHUB_REF_NAME" build/*.deb
+1
View File
@@ -1,2 +1,3 @@
build/ build/
*.deb *.deb
__pycache__/
+7 -1
View File
@@ -69,7 +69,13 @@ make check # bash -n on all scripts, plus shellcheck if installed
make deb # build/terraria-server-manager_<version>_all.deb make deb # build/terraria-server-manager_<version>_all.deb
``` ```
To release: update `VERSION` and `CHANGELOG.md`, run `make deb`, and attach the `.deb` to the release. To release, update `VERSION` and `CHANGELOG.md`, commit, and push a matching tag:
```bash
git tag v$(cat VERSION) && git push origin v$(cat VERSION)
```
The `release` workflow (`.gitea/workflows/release.yaml`, Gitea Actions) then builds the `.deb` and publishes it as a release, with that version's changelog section as the notes. It needs a runner with the `ubuntu-latest` label. To build a package by hand instead, run `make deb`.
## Disclaimer ## Disclaimer
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
# Creates or updates the Gitea release for a tag and attaches files to it. Its
# notes are that version's section of CHANGELOG.md. Run again for the same tag,
# it updates the notes and replaces files of the same name.
#
# gitea-release.py <tag> <file>...
#
# Needs GITEA_URL (https://gitea.example.com), GITEA_REPO (owner/name) and
# GITEA_TOKEN. Used by .gitea/workflows/release.yaml.
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
import uuid
def api(method, path, body=None, content_type="application/json"):
url = f"{os.environ['GITEA_URL'].rstrip('/')}/api/v1/repos/{os.environ['GITEA_REPO']}{path}"
if isinstance(body, dict):
body = json.dumps(body).encode()
headers = {"Authorization": f"token {os.environ['GITEA_TOKEN']}", "Accept": "application/json"}
if body is not None:
headers["Content-Type"] = content_type
request = urllib.request.Request(url, data=body, method=method, headers=headers)
try:
with urllib.request.urlopen(request, timeout=300) as response:
data = response.read()
return json.loads(data) if data else None
except urllib.error.HTTPError as e:
if e.code == 404 and method == "GET":
return None
sys.exit(f"{method} {url}: HTTP {e.code}: {e.read().decode(errors='replace')}")
def changelog_section(version):
"""The lines under '## <version>' in CHANGELOG.md, up to the next '## '."""
lines, found = [], False
with open("CHANGELOG.md") as f:
for line in f:
if line.startswith("## "):
if found:
break
found = line[3:].split()[0] == version
continue
if found:
lines.append(line)
if not found:
sys.exit(f"CHANGELOG.md has no '## {version}' section")
return "".join(lines).strip() + "\n"
def upload(release_id, path):
name = os.path.basename(path)
boundary = uuid.uuid4().hex
with open(path, "rb") as f:
content = f.read()
body = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"{name}\"\r\n"
f"Content-Type: application/octet-stream\r\n\r\n").encode() + content + f"\r\n--{boundary}--\r\n".encode()
api("POST", f"/releases/{release_id}/assets?name={urllib.parse.quote(name)}", body,
f"multipart/form-data; boundary={boundary}")
print(f"attached {name}")
def main(tag, files):
notes = changelog_section(tag[1:] if tag.startswith("v") else tag)
release = api("GET", f"/releases/tags/{urllib.parse.quote(tag)}")
if release is None:
release = api("POST", "/releases", {"tag_name": tag, "name": tag, "body": notes})
print(f"created release {tag}")
else:
release = api("PATCH", f"/releases/{release['id']}", {"body": notes})
print(f"updated release {tag}")
names = {os.path.basename(p) for p in files}
for asset in release.get("assets") or []:
if asset["name"] in names:
api("DELETE", f"/releases/{release['id']}/assets/{asset['id']}")
for path in files:
upload(release["id"], path)
print(release.get("html_url", ""))
if __name__ == "__main__":
if len(sys.argv) < 3:
sys.exit("usage: gitea-release.py <tag> <file>...")
for var in ("GITEA_URL", "GITEA_REPO", "GITEA_TOKEN"):
if not os.environ.get(var):
sys.exit(f"{var} is not set")
main(sys.argv[1], sys.argv[2:])