▍ humdrum codex / custard
license AGPL-3.0

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}"