Skip to content

Commit ce44541

Browse files
committed
Drop push-based downstream fan-out in favor of self-polling
1 parent 537a139 commit ce44541

1 file changed

Lines changed: 24 additions & 188 deletions

File tree

.github/workflows/release.yml

Lines changed: 24 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -191,193 +191,29 @@ jobs:
191191
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') }}
192192

193193
# ---------------------------------------------------------------------------
194-
# Downstream fan-out. After the GitHub Release (binaries + checksums) is
195-
# published, propagate THIS tag's version to every distribution channel:
196-
# PyPI, npm, Homebrew. Each leg is independent: one failing channel does not
197-
# block the others, but every failure is surfaced (the final "gate" step
198-
# fails the job if any leg failed). Skipped for -rc/-beta prereleases.
194+
# Downstream fan-out is NO LONGER driven from here.
199195
#
200-
# Version-locking: every channel publishes ${GITHUB_REF_NAME} (the daemon
201-
# tag), NOT the stale version sitting in each downstream repo's source.
196+
# Each distribution channel now SELF-UPDATES by polling this repo's public
197+
# latest release on a schedule, using only secrets already present in its
198+
# own repo. This removes the need for any cross-repo PAT
199+
# (RELEASE_DISPATCH_TOKEN, HOMEBREW_TAP_TOKEN) — a workflow's built-in
200+
# GITHUB_TOKEN can write to its own repo but not sibling repos, which is why
201+
# the old push-based fan-out needed PATs. The pull-based watchers below act
202+
# with each repo's own token instead:
202203
#
203-
# PyPI/npm are triggered via `gh workflow run publish.yml -f version=...`
204-
# against the SDK repos. This MUST use a cross-repo PAT — a release created
205-
# with GITHUB_TOKEN cannot trigger another repo's workflow, and GITHUB_TOKEN
206-
# has no write scope on sibling repos. See RELEASE_DISPATCH_TOKEN below.
207-
publish-downstream:
208-
name: Publish to PyPI / npm / Homebrew
209-
needs: release
210-
if: ${{ !(contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta')) }}
211-
runs-on: ubuntu-latest
212-
permissions:
213-
contents: read
214-
steps:
215-
- name: Compute version
216-
id: ver
217-
run: |
218-
TAG="${GITHUB_REF_NAME}"
219-
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
220-
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
221-
222-
# --- PyPI -------------------------------------------------------------
223-
- name: Dispatch sdk-python publish
224-
id: pypi
225-
continue-on-error: true
226-
env:
227-
GH_TOKEN: ${{ secrets.RELEASE_DISPATCH_TOKEN }}
228-
run: |
229-
if [ -z "$GH_TOKEN" ]; then
230-
echo "::error::RELEASE_DISPATCH_TOKEN is not set — cannot dispatch sdk-python"
231-
exit 1
232-
fi
233-
gh workflow run publish.yml \
234-
--repo pilot-protocol/sdk-python \
235-
--ref main \
236-
-f version="${{ steps.ver.outputs.version }}"
237-
echo "Dispatched sdk-python publish @ ${{ steps.ver.outputs.version }}"
238-
239-
# --- npm --------------------------------------------------------------
240-
- name: Dispatch sdk-node publish
241-
id: npm
242-
continue-on-error: true
243-
env:
244-
GH_TOKEN: ${{ secrets.RELEASE_DISPATCH_TOKEN }}
245-
run: |
246-
if [ -z "$GH_TOKEN" ]; then
247-
echo "::error::RELEASE_DISPATCH_TOKEN is not set — cannot dispatch sdk-node"
248-
exit 1
249-
fi
250-
gh workflow run publish.yml \
251-
--repo pilot-protocol/sdk-node \
252-
--ref main \
253-
-f version="${{ steps.ver.outputs.version }}"
254-
echo "Dispatched sdk-node publish @ ${{ steps.ver.outputs.version }}"
255-
256-
# --- Homebrew ---------------------------------------------------------
257-
# Regenerate the formula from this release's checksums + tag and push it
258-
# to the tap. HOMEBREW_TAP_TOKEN must be a PAT with write access to the
259-
# tap repo (the tap is in a different owner, so GITHUB_TOKEN can't push).
260-
- name: Update Homebrew formula
261-
id: brew
262-
continue-on-error: true
263-
env:
264-
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
265-
TAP_REPO: TeoSlayer/homebrew-pilot
266-
FORMULA_PATH: Formula/pilotprotocol.rb
267-
run: |
268-
set -euo pipefail
269-
if [ -z "$GH_TOKEN" ]; then
270-
echo "::error::HOMEBREW_TAP_TOKEN is not set — cannot update the tap"
271-
exit 1
272-
fi
273-
TAG="${{ steps.ver.outputs.tag }}"
274-
VERSION="${{ steps.ver.outputs.version }}"
275-
276-
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern checksums.txt --clobber
277-
cat checksums.txt
278-
279-
sum() { grep "pilot-$1.tar.gz" checksums.txt | awk '{print $1}'; }
280-
DA=$(sum darwin-arm64); DX=$(sum darwin-amd64)
281-
LA=$(sum linux-arm64); LX=$(sum linux-amd64)
282-
for v in "$DA" "$DX" "$LA" "$LX"; do
283-
[ -n "$v" ] || { echo "::error::missing checksum in checksums.txt"; exit 1; }
284-
done
285-
286-
base="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}"
287-
cat > pilotprotocol.rb <<RUBY
288-
class Pilotprotocol < Formula
289-
desc "The network stack for AI agents - addresses, ports, tunnels, encryption, trust"
290-
homepage "https://pilotprotocol.network"
291-
version "${VERSION}"
292-
license "AGPL-3.0"
293-
294-
on_macos do
295-
if Hardware::CPU.arm?
296-
url "${base}/pilot-darwin-arm64.tar.gz"
297-
sha256 "${DA}"
298-
else
299-
url "${base}/pilot-darwin-amd64.tar.gz"
300-
sha256 "${DX}"
301-
end
302-
end
303-
304-
on_linux do
305-
if Hardware::CPU.arm?
306-
url "${base}/pilot-linux-arm64.tar.gz"
307-
sha256 "${LA}"
308-
else
309-
url "${base}/pilot-linux-amd64.tar.gz"
310-
sha256 "${LX}"
311-
end
312-
end
313-
314-
def install
315-
bin.install "daemon" => "pilot-daemon"
316-
bin.install "pilotctl" => "pilotctl"
317-
bin.install "updater" => "pilot-updater"
318-
end
319-
320-
def post_install
321-
(var/"pilot").mkpath
322-
config_dir = Pathname.new(Dir.home)/".pilot"
323-
config_dir.mkpath
324-
(config_dir/"bin").mkpath
325-
(config_dir/"bin/.pilot-version").write "v#{version}\n"
326-
end
327-
328-
def caveats
329-
<<~EOS
330-
Get started:
331-
pilotctl daemon start --hostname my-agent --email you@example.com
332-
pilotctl info
333-
Docs: https://pilotprotocol.network/docs
334-
EOS
335-
end
336-
337-
service do
338-
run [opt_bin/"pilot-daemon", "-socket", "/tmp/pilot.sock", "-encrypt"]
339-
keep_alive crashed: true
340-
log_path var/"log/pilot-daemon.log"
341-
error_log_path var/"log/pilot-daemon.log"
342-
end
343-
344-
test do
345-
assert_match "pilotctl", shell_output("#{bin}/pilotctl --help 2>&1", 0)
346-
end
347-
end
348-
RUBY
349-
sed -i 's/^ //' pilotprotocol.rb
350-
cat pilotprotocol.rb
351-
352-
CONTENT=$(base64 -w 0 pilotprotocol.rb)
353-
SHA=$(gh api "repos/${TAP_REPO}/contents/${FORMULA_PATH}" --jq '.sha' 2>/dev/null || echo "")
354-
if [ -n "$SHA" ]; then
355-
gh api "repos/${TAP_REPO}/contents/${FORMULA_PATH}" -X PUT \
356-
-f message="pilotprotocol ${TAG}" -f content="$CONTENT" -f sha="$SHA"
357-
else
358-
gh api "repos/${TAP_REPO}/contents/${FORMULA_PATH}" -X PUT \
359-
-f message="pilotprotocol ${TAG}" -f content="$CONTENT"
360-
fi
361-
echo "Updated ${TAP_REPO}/${FORMULA_PATH} -> ${TAG}"
362-
363-
# --- Surface failures -------------------------------------------------
364-
- name: Fan-out gate
365-
if: always()
366-
run: |
367-
echo "PyPI dispatch : ${{ steps.pypi.outcome }}"
368-
echo "npm dispatch : ${{ steps.npm.outcome }}"
369-
echo "Homebrew : ${{ steps.brew.outcome }}"
370-
{
371-
echo "## Downstream fan-out (${{ steps.ver.outputs.tag }})"
372-
echo "| Channel | Result |"
373-
echo "|---------|--------|"
374-
echo "| PyPI (sdk-python dispatch) | ${{ steps.pypi.outcome }} |"
375-
echo "| npm (sdk-node dispatch) | ${{ steps.npm.outcome }} |"
376-
echo "| Homebrew (tap formula) | ${{ steps.brew.outcome }} |"
377-
} >> "$GITHUB_STEP_SUMMARY"
378-
if [ "${{ steps.pypi.outcome }}" != "success" ] || \
379-
[ "${{ steps.npm.outcome }}" != "success" ] || \
380-
[ "${{ steps.brew.outcome }}" != "success" ]; then
381-
echo "::error::one or more downstream channels failed to dispatch/update"
382-
exit 1
383-
fi
204+
# - Homebrew tap TeoSlayer/homebrew-pilot
205+
# .github/workflows/update-formula.yml (schedule + workflow_dispatch)
206+
# regenerates Formula/pilotprotocol.rb from this release's public
207+
# checksums.txt and commits with its own GITHUB_TOKEN.
208+
#
209+
# - npm SDK pilot-protocol/sdk-node
210+
# .github/workflows/release-watch.yml (schedule + workflow_dispatch)
211+
# dispatches its own publish.yml (NPM_TOKEN) at the new version.
212+
#
213+
# - PyPI SDK pilot-protocol/sdk-python
214+
# .github/workflows/release-watch.yml (schedule + workflow_dispatch)
215+
# dispatches its own publish.yml (PYPI_API_TOKEN) at the new version.
216+
#
217+
# All three poll every ~30 min (workflow_dispatch is the instant manual
218+
# path) and are idempotent. This release workflow's only job is to build
219+
# the binaries and publish the GitHub Release — the watchers do the rest.

0 commit comments

Comments
 (0)