1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
name: CI
on:
push:
branches:
pull_request:
# Recovery path: re-run packaging (binaries + Homebrew + NUR + GitHub release
# assets) for an already-tagged release, e.g. when goreleaser failed after
# release-plz already published to crates.io. Runs only `publish-release`
# against the given tag.
workflow_dispatch:
inputs:
tag:
description: "Existing tag to (re)package, e.g. v0.2.1"
required: true
permissions:
contents: write
pull-requests: write
# Newer push or PR update on the same ref supersedes the older in-flight run.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
BAZEL_REMOTE_AUTH: ${{ secrets.BAZEL_REMOTE_AUTH }}
jobs:
build-and-test:
name: build-and-test
# Skip on the recovery dispatch - it only re-runs publish-release for a tag.
if: github.event_name != 'workflow_dispatch'
runs-on:
steps:
# clean: false persists target/ + caches on the self-hosted disk between
# runs (incremental rebuilds). moon, cargo/rustc, protoc, and kache (the
# global RUSTC_WRAPPER, with its own S3 daemon) are preinstalled on the
# ci-runner user - no setup actions needed.
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
clean: false
- run: moon run pond:format
- run: moon run pond:lint
- run: moon run pond:test
release-plz:
name: release-plz
needs: build-and-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
releases_created: ${{ steps.release-plz.outputs.releases_created }}
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0 # release-plz needs full git history for changelog
- id: release-plz
uses: release-plz/action@v0.5
# No `dry_run` input on purpose: the action adds `--dry-run` whenever the
# input is non-empty (it tests `-n`, so even `dry_run: false` enables it).
# Omitting it is the only way to actually publish. Default command runs
# both `release-pr` (opens the release PR) and `release` (cuts the tag +
# crates.io publish + GitHub release once a release-plz-* branch merges).
env:
GITHUB_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
publish-release:
name: publish-release
needs: release-plz
# Runs after release-plz cuts a release, OR on a manual recovery dispatch.
# always()+!cancelled() lets it run on workflow_dispatch even though
# release-plz is skipped there (its `if` is push-only).
if: ${{ always() && !cancelled() && (needs.release-plz.outputs.releases_created == 'true' || github.event_name == 'workflow_dispatch') }}
# Self-hosted: provides the allowlisted network path to the cluster API.
# Cluster *config* (kubeconfig, buildx builder, S3 creds) is bootstrapped
# from secrets below, so the runner needs no pre-provisioned pond setup -
# only docker+buildx, kubectl, and moon installed.
runs-on:
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
clean: false
# On recovery dispatch, check out the tag so goreleaser releases it;
# empty ref (push flow) checks out the triggering commit.
ref: ${{ inputs.tag }}
# nix-hash, required by goreleaser's `nix` block to compute the NUR sha256.
- uses: cachix/install-nix-action@v31
- name: Bootstrap cluster access + buildx builder
env:
KUBECONFIG_DATA: ${{ secrets.KUBECONFIG_CASCADE }}
run: |
# Keep the kubeconfig OUT of the repo checkout: goreleaser aborts on a
# dirty tree, and a kubeconfig in the workspace is an untracked file.
# ($RUNNER_TEMP is only available in steps, not at job-level env.)
# export (not just assign) so subprocesses in this step - e.g.
# `docker buildx create --driver kubernetes` - inherit it; GITHUB_ENV
# carries it to later steps (build-dist's kubectl).
export KUBECONFIG="$RUNNER_TEMP/.kube-cascade"
echo "KUBECONFIG=$KUBECONFIG" >> "$GITHUB_ENV"
# Remove any kubeconfig a prior run left in the workspace (clean:false
# preserves untracked files) so it can't dirty the tree.
rm -f "${{ github.workspace }}/.kube-cascade"
printf '%s' "$KUBECONFIG_DATA" | base64 -d > "$KUBECONFIG"
chmod 600 "$KUBECONFIG"
# buildx builder state is client-side; on a fresh runner re-register it.
# Drop any stale in-cluster deployment first so create is idempotent.
docker buildx rm pond-hetzner 2>/dev/null || true
docker buildx create --name pond-hetzner --driver kubernetes \
--driver-opt namespace=pond-buildkit,replicas=1,nodeselector=workload=buildkit,timeout=10m \
--driver-opt requests.cpu=12,requests.memory=24Gi,limits.memory=48Gi \
--platform linux/amd64
# Cross-compiles all 4 targets on the Hetzner buildkit pool (kache S3 +
# bazel-remote), exactly as local; drops binaries in target/dist/.
- name: Build dist binaries
run: moon run pond:build-dist
env:
KACHE_S3_ACCESS_KEY: ${{ secrets.KACHE_S3_ACCESS_KEY }}
KACHE_S3_SECRET_KEY: ${{ secrets.KACHE_S3_SECRET_KEY }}
KACHE_S3_REGION: nbg1
# Bucket-virtual-host form; build-dist strips the bucket subdomain to
# derive the buildx S3 cache endpoint.
KACHE_S3_ENDPOINT: https://ttq.nbg1.your-objectstorage.com
# builder: prebuilt - packages target/dist/ into archives + brew/nix/release.
- uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser-pro # `builder: prebuilt` is a Pro feature
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }}
GH_RELEASE_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}