Update README.md #107
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |