feat(phase4): self-hosted Homebrew tap + release pipeline
e884a4fabbd06fcefac6b3b708518d2e15ab7b44
humdrum-tiv <45084903+humdrum-tiv@users.noreply.github.com> · 2026-06-17 21:18
parent 7f869008
feat(phase4): self-hosted Homebrew tap + release pipeline Repoint phase 4 from 'brew custard' to using the forge as a self-hosted brew origin for CLI repos (sportsball, etc.) — no GitHub. - scripts/brew-release.sh: archives a repo's tag from the server (as the repo owner), publishes the source tarball to /dl, generates a source-build formula (depends_on go), and pushes it to the homebrew-tap repo - homebrew-tap repo created + seeded on the server (public) - README: tap usage + release/install instructions Archive mechanic validated against sportsball HEAD; first real release awaits a version tag on sportsball. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed
README.md +24 −0
@@ -91,4 +91,28 @@ │ └─▶ soft-serve.db (visibility, optional)
└─/git/*─▶ Soft Serve HTTP (clone, optional)
```
+## Releasing CLIs via a self-hosted Homebrew tap
+
+The same server can host a [Homebrew](https://brew.sh) tap + release tarballs, so your
+command-line repos install with **no GitHub**. custard serves the tarballs (`/dl`) and the tap
+repo (`/git`); `scripts/brew-release.sh` does the rest.
+
+```bash
+# one-time: a `homebrew-tap` repo exists on the server (public)
+# release a tagged repo (archives the tag from the server, publishes the tarball,
+# writes a source-build formula, pushes the tap):
+scripts/brew-release.sh <repo> <version> [go-package]
+# e.g. scripts/brew-release.sh sportsball v0.1.0
+# scripts/brew-release.sh sportsball v0.1.0 ./cmd/sportsball # if main isn't at root
+```
+
+Users then:
+
+```bash
+brew tap you/tap https://your-host/git/homebrew-tap.git
+brew install <repo>
+```
+
+Formulas build from source (`depends_on "go"`) — no per-arch bottles, no GitHub.
+
See `PLAN.md` for the phased build plan.
scripts/brew-release.sh +77 −0
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+#
+# brew-release.sh <repo> <version> [go-package]
+#
+# Release one of your Soft Serve repos to your self-hosted Homebrew tap — no
+# GitHub. It archives the tag straight from the server's bare repo into a source
+# tarball under /dl, then writes/updates a source-build formula in the
+# `homebrew-tap` repo and pushes it.
+#
+# scripts/brew-release.sh sportsball v0.1.0 # main package at repo root
+# scripts/brew-release.sh sportsball v0.1.0 ./cmd/sportsball
+#
+# Prereqs: the tag exists in the repo on the server; deploy/deploy.env is filled
+# (REMOTE, DOMAIN, REPOS_PATH); the `homebrew-tap` repo exists on the server.
+# Users then: brew tap you/tap https://DOMAIN/git/homebrew-tap.git && brew install <repo>
+#
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+REPO="${1:?usage: brew-release.sh <repo> <version> [go-package]}"
+VERSION="${2:?usage: brew-release.sh <repo> <version> [go-package]}"
+PKG="${3:-.}"
+VER="${VERSION#v}" # strip leading v for tarball/version string
+
+[ -f deploy/deploy.env ] || { echo "missing deploy/deploy.env" >&2; exit 1; }
+set -a; . deploy/deploy.env; set +a
+: "${REMOTE:?}"; : "${DOMAIN:?}"; : "${REPOS_PATH:?}"; : "${RUN_USER:?}"
+
+TARBALL="${REPO}-${VER}.tar.gz"
+URL="https://${DOMAIN}/dl/${TARBALL}"
+HOMEPAGE="https://${DOMAIN}/r/${REPO}"
+CLASS="$(echo "$REPO" | perl -pe 's/(^|[-_])(\w)/\U$2/g')" # sportsball → Sportsball, foo-bar → FooBar
+
+echo "==> archiving ${REPO}@${VERSION} from the server"
+# Run as the repo owner so git doesn't reject "dubious ownership".
+ssh "$REMOTE" "sudo -u ${RUN_USER} git -C '${REPOS_PATH}/${REPO}.git' archive --format=tar.gz --prefix='${REPO}-${VER}/' '${VERSION}'" > "/tmp/${TARBALL}"
+SHA="$(shasum -a 256 "/tmp/${TARBALL}" | awk '{print $1}')"
+echo " ${TARBALL} sha256=${SHA}"
+
+echo "==> publishing tarball to /dl"
+ssh "$REMOTE" "install -d -o caddy -g caddy /var/lib/custard/dl"
+scp -q "/tmp/${TARBALL}" "$REMOTE:/var/lib/custard/dl/${TARBALL}"
+
+echo "==> updating the tap formula"
+TAP="$(mktemp -d)"
+git clone -q "soft:homebrew-tap" "$TAP"
+mkdir -p "$TAP/Formula"
+cat > "$TAP/Formula/${REPO}.rb" <<EOF
+# Generated by custard brew-release.sh — do not edit by hand.
+class ${CLASS} < Formula
+ desc "${REPO}"
+ homepage "${HOMEPAGE}"
+ url "${URL}"
+ sha256 "${SHA}"
+ version "${VER}"
+ license "AGPL-3.0-or-later"
+
+ depends_on "go" => :build
+
+ def install
+ system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}"), "${PKG}"
+ end
+
+ test do
+ assert_path_exists bin/"${REPO}"
+ end
+end
+EOF
+git -C "$TAP" add "Formula/${REPO}.rb"
+git -C "$TAP" -c user.name="custard" -c user.email="custard@${DOMAIN}" \
+ commit -q -m "${REPO} ${VERSION}"
+git -C "$TAP" push -q origin HEAD
+rm -rf "$TAP"
+
+echo "==> done"
+echo " brew tap you/tap https://${DOMAIN}/git/homebrew-tap.git"
+echo " brew install ${REPO}"