Skip to content

Update README.md

Update README.md #107

Workflow file for this run

# Workflow builds cross-platform binaries and publishes a GitHub release only when commits include "stable release".
# Keeping the matrix explicit makes future platform additions easy to reason about while the single rolling tag keeps URLs stable.
name: cross-platform-release
on:
push:
# Grant write access to release assets so publishing succeeds when the workflow
# tags a stable build. GitHub defaults to read-only tokens, which prevents
# creating releases even though the rest of the pipeline succeeds.
permissions:
contents: write
jobs:
release:
# Publish only when any pushed commit declares a stable release to keep download links predictable.
if: contains(join(github.event.commits.*.message, ' '), 'stable release')
runs-on: ubuntu-latest
steps:
- name: Checkout repository without external actions
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
git init .
git remote add origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
- name: Install Go toolchain without external actions
run: |
set -euo pipefail
curl -L "https://go.dev/dl/go1.21.10.linux-amd64.tar.gz" -o /tmp/go.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf /tmp/go.tar.gz
echo "/usr/local/go/bin" >> "$GITHUB_PATH"
- name: Compute incremental version
id: version
run: |
set -euo pipefail
echo "count=$(git rev-list --count HEAD)" >> "$GITHUB_OUTPUT"
- name: Build cross-platform binaries
env:
VERSION: ${{ steps.version.outputs.count }}
run: |
set -euo pipefail
mkdir -p upload
targets=(
"linux/amd64"
"linux/arm64"
"darwin/amd64"
"darwin/arm64"
"windows/amd64"
"windows/arm64"
"freebsd/amd64"
"freebsd/arm64"
"openbsd/amd64"
"openbsd/arm64"
)
for target in "${targets[@]}"; do
os="${target%/*}"
arch="${target#*/}"
ext=""
if [ "${os}" = "windows" ]; then ext=".exe"; fi
CGO_ENABLED=0 GOOS="${os}" GOARCH="${arch}" go build \
-tags netgo \
-ldflags "-X github.com/matveynator/chicha-ip-proxy/pkg/version.Number=${VERSION}" \
-o "upload/chicha-ip-proxy-${os}-${arch}${ext}"
done
- name: Publish release without external actions
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.count }}
run: |
set -euo pipefail
python - <<'PY'
import json
import os
import pathlib
import urllib.error
import urllib.request
token = os.environ["GITHUB_TOKEN"]
repo = os.environ["GITHUB_REPOSITORY"]
version = os.environ["VERSION"]
base = f"https://api.github.com/repos/{repo}"
headers = {"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}
tag = "stable-release"
def request(method: str, url: str, data: bytes | None = None, extra_headers: dict | None = None) -> tuple[int, bytes]:
merged = dict(headers)
if extra_headers:
merged.update(extra_headers)
req = urllib.request.Request(url, method=method, headers=merged, data=data)
try:
with urllib.request.urlopen(req) as resp:
return resp.status, resp.read()
except urllib.error.HTTPError as exc:
return exc.code, exc.read()
status, body = request("GET", f"{base}/releases/tags/{tag}")
if status == 200:
release = json.loads(body.decode())
release_id = release.get("id")
if not release_id:
raise SystemExit("Release ID missing.")
update_payload = json.dumps(
{
"tag_name": tag,
"name": "Stable release",
"target_commitish": os.environ.get("GITHUB_SHA", ""),
"make_latest": "true",
"prerelease": False,
}
).encode()
status, body = request(
"PATCH",
f"{base}/releases/{release_id}",
update_payload,
{"Content-Type": "application/json"},
)
if status not in (200, 201):
raise SystemExit(f"Failed to update release: {status} {body.decode()}")
release = json.loads(body.decode())
else:
payload = json.dumps(
{
"tag_name": tag,
"name": "Stable release",
"target_commitish": os.environ.get("GITHUB_SHA", ""),
"make_latest": "true",
"prerelease": False,
}
).encode()
status, body = request("POST", f"{base}/releases", payload, {"Content-Type": "application/json"})
if status not in (200, 201):
raise SystemExit(f"Failed to create release: {status} {body.decode()}")
release = json.loads(body.decode())
assets_url = release.get("assets_url", "")
if assets_url:
status, body = request("GET", assets_url)
if status == 200:
assets = json.loads(body.decode())
for asset in assets:
asset_id = asset.get("id")
if asset_id:
request("DELETE", f"{base}/releases/assets/{asset_id}")
upload_url = release.get("upload_url", "").split("{")[0]
if not upload_url:
raise SystemExit("Release upload URL missing.")
for asset in sorted(pathlib.Path("upload").glob("chicha-ip-proxy-*")):
data = asset.read_bytes()
asset_url = f"{upload_url}?name={asset.name}"
status, body = request(
"POST",
asset_url,
data,
{"Content-Type": "application/octet-stream"},
)
if status not in (200, 201):
raise SystemExit(f"Failed to upload {asset.name}: {status} {body.decode()}")
PY