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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
#!/bin/sh
# ship -- the whole release, one command:
#
# contrib/ship 0.2.0 # tag, build, publish, everywhere
# contrib/ship 0.2.0 "What changed."
#
# contrib/release cuts the tag and deliberately stops there: pushing is
# publishing, and publishing should be the thing you do on purpose. This is
# that thing, done on purpose -- every channel a release must reach, in order,
# so that none is forgotten (v0.1.2 shipped with the arch repo still serving
# v0.1.1, which is the mistake this script exists to prevent):
#
# 1. contrib/release bump ./VERSION, commit, tag (skipped if done)
# 2. git push master + tag -> cgit and GitHub
# 3. make release-* linux tarballs, windows zips (cross-built here)
# 4. make macos-dist signed, notarized zip (the builder mac)
# 5. gh release the five artifacts + SHA256SUMS on GitHub
# 6. brew-release pin the formula, push the tap
# 7. arch bump the PKGBUILD, rebuild + sign + rsync the repo
# 8. paperwork commit the formula + PKGBUILD, push
# 9. website rebuild + publish slosh.foo, demo included, so the
# in-browser slosh is the released one
#
# Every step is skipped when its result already exists, so a run that died at
# notarization resumes where it stopped instead of rebuilding artifacts whose
# checksums are already published -- a rebuilt tarball has new bytes (mtimes),
# and changing SHA256SUMS under people is worse than a slow retry.
#
# The macOS build uses the working-tree sync mode of macos-remote-build (the
# tree *is* the tag checkout, and clean, so the version stamps honestly).
# --ref mode would be tidier but needs the builder to fetch from origin, and
# the builder holds no git credentials on purpose.
set -eu
GH_REPO="${SLOSH_GH_REPO:-sl0p-foo/slosh}"
TAP_DIR="${SLOSH_TAP_DIR:-../homebrew-slosh}"
TAP_URL="${SLOSH_TAP_URL:-git@github.com:sl0p-foo/homebrew-slosh.git}"
ARCH_DEST="${SLOSH_ARCH_DEST:-root@sl0p.foo:/opt/slosh/arch}"
ARCH_URL="${SLOSH_ARCH_URL:-https://slosh.foo/arch}"
GPG_KEY="${SLOSH_GPG_KEY:-0F67C909938CFFA8A76B59FD8D69B55A0E9056B9}"
root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
cd "$root"
fail() { printf 'ship: %s\n' "$*" >&2; exit 1; }
say() { printf ' %s\n' "$*"; }
step() { printf '\n== %s\n' "$*"; }
[ $# -ge 1 ] || { sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'; exit 1; }
v="${1#v}"; notes="${2:-}"
printf '%s' "$v" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$' \
|| fail "'$v' does not look like X.Y.Z"
tag="v$v"
# ── preflight: every credential and machine, before anything is cut ─────────
# The point of one overarching script is that it cannot half-publish; so it
# refuses to start unless every channel it will touch is reachable now.
step "preflight"
git diff --quiet && git diff --cached --quiet || fail "the tree is not clean"
[ "$(git symbolic-ref --short -q HEAD)" = master ] || fail "not on master"
command -v gh >/dev/null || fail "gh not installed"
gh auth status >/dev/null 2>&1 || fail "gh is not authenticated (gh auth login)"
command -v makepkg >/dev/null && command -v repo-add >/dev/null \
|| fail "makepkg/repo-add missing -- the arch leg runs here"
gpg --list-secret-keys "$GPG_KEY" >/dev/null 2>&1 \
|| fail "arch signing key $GPG_KEY not in this keyring"
[ -f macos-release.mk ] || fail "no macos-release.mk -- the macOS leg needs it"
builder="$(sed -n 's/^BUILDER[[:space:]]*[?:]*=[[:space:]]*//p' macos-release.mk | head -1)"
[ -n "$builder" ] || fail "macos-release.mk names no BUILDER"
ssh -o BatchMode=yes -o ConnectTimeout=15 "$builder" true \
|| fail "cannot ssh to the builder '$builder'"
arch_host="${ARCH_DEST%%:*}"
ssh -o BatchMode=yes -o ConnectTimeout=15 "$arch_host" true \
|| fail "cannot ssh to '$arch_host' (publishes the arch repo)"
if [ ! -d "$TAP_DIR/.git" ]; then
say "no tap checkout at $TAP_DIR -- cloning"
git clone -q "$TAP_URL" "$TAP_DIR" || fail "could not clone the tap"
fi
say "all channels reachable"
# ── 1. the tag ──────────────────────────────────────────────────────────────
step "tag $tag"
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
[ "$(cat VERSION)" = "$v" ] || fail "$tag exists but VERSION says $(cat VERSION)"
say "already cut (resuming)"
else
contrib/release "$v" ${notes:+"$notes"}
fi
# ── 2. push ─────────────────────────────────────────────────────────────────
# origin fans out to cgit and GitHub (two push urls); a no-op when resuming.
step "push master + $tag"
git push origin master "$tag"
# ── 3+4. artifacts, from the tag checkout ───────────────────────────────────
# Only the legs whose artifact is missing are built: artifacts are named by
# version and a tag is immutable, so an existing file *is* the build. (This is
# what makes a resumed run safe -- see the header.)
linux_x=dist/slosh-$v-linux-x86_64.tar.gz
linux_a=dist/slosh-$v-linux-aarch64.tar.gz
win_x=dist/slosh-$v-windows-x86_64.zip
win_a=dist/slosh-$v-windows-aarch64.zip
mac=dist/slosh-$v-macos-arm64.zip
missing=""
for f in "$linux_x" "$linux_a" "$win_x" "$win_a" "$mac"; do
[ -f "$f" ] || missing="$missing $f"
done
if [ -n "$missing" ]; then
step "build:$missing"
git checkout -q "$tag"
trap 'git checkout -q master' EXIT INT TERM
make release-check
{ [ -f "$linux_x" ] && [ -f "$linux_a" ]; } || make release-linux
{ [ -f "$win_x" ] && [ -f "$win_a" ]; } || make release-windows
[ -f "$mac" ] || make macos-dist
git checkout -q master
trap - EXIT INT TERM
else
step "build"
say "all five artifacts already in dist/ (resuming)"
fi
for f in "$linux_x" "$linux_a" "$win_x" "$win_a" "$mac"; do
[ -f "$f" ] || fail "$f was not produced"
done
( cd dist && sha256sum "slosh-$v"-* > SHA256SUMS )
# ── 5. the GitHub release ───────────────────────────────────────────────────
step "github release"
if gh release view "$tag" --repo "$GH_REPO" >/dev/null 2>&1; then
have="$(gh release view "$tag" --repo "$GH_REPO" --json assets -q '.assets[].name')"
for f in "$linux_x" "$linux_a" "$win_x" "$win_a" "$mac"; do
printf '%s\n' "$have" | grep -qx "$(basename "$f")" \
|| gh release upload "$tag" --repo "$GH_REPO" "$f"
done
gh release upload "$tag" --repo "$GH_REPO" --clobber dist/SHA256SUMS
say "release existed -- missing assets uploaded, SHA256SUMS refreshed"
else
prev="$(git tag -l 'v[0-9]*' | sort -V | grep -B1 -x "$tag" | head -1)"
[ "$prev" = "$tag" ] && prev=""
relnotes="$(git log --oneline ${prev:+$prev..}"$tag" --no-decorate \
| grep -v ' release: v' | sed 's/^[a-f0-9]* /- /')"
gh release create "$tag" --repo "$GH_REPO" --title "slosh $v" \
--notes "$relnotes" \
"$linux_x" "$linux_a" "$win_x" "$win_a" "$mac" dist/SHA256SUMS
fi
# ── 6. homebrew ─────────────────────────────────────────────────────────────
# Rerun even when already pinned: it re-downloads and re-hashes the tarball,
# so a drifted GitHub archive would be caught here and not by a user.
step "homebrew formula + tap"
contrib/brew-release --tap "$TAP_DIR" "$tag"
( cd "$TAP_DIR" \
&& { git diff --quiet Formula/slosh.rb 2>/dev/null \
&& git ls-files --error-unmatch Formula/slosh.rb >/dev/null 2>&1; } \
|| { git add Formula/slosh.rb && git commit -q -m "slosh $v" && git push -q; } )
# ── 7. arch ─────────────────────────────────────────────────────────────────
# The db on the server is the truth about what pacman serves; if it already
# lists this version for every architecture, the leg is done. Both packages
# are built here -- zig cross-compiles the aarch64 one (see contrib/arch-repo).
step "arch repo"
arches="${SLOSH_ARCHES:-x86_64 aarch64}"
served=yes
for carch in $arches; do
curl -fsSL "$ARCH_URL/$carch/slosh.db.tar.gz" 2>/dev/null | tar tzf - 2>/dev/null \
| grep -q "^slosh-$v-" || served=""
done
if [ -n "$served" ]; then
say "$ARCH_URL already serves $v for: $arches (resuming)"
else
sha="$(curl -fsSL "https://github.com/$GH_REPO/archive/refs/tags/$tag.tar.gz" \
| sha256sum | cut -d' ' -f1)"
sed -i -e "s/^pkgver=.*/pkgver=$v/" -e "s/^pkgrel=.*/pkgrel=1/" \
-e "s/^sha256sums=.*/sha256sums=('$sha')/" contrib/arch/PKGBUILD
contrib/arch-repo
for carch in $arches; do
rsync -a --delete --chown=root:root "dist/arch/$carch/" "$ARCH_DEST/$carch/"
done
rsync -a --chown=root:root dist/arch/slosh.asc "$ARCH_DEST/slosh.asc"
say "published to $ARCH_DEST for: $arches"
fi
# ── 8. the paperwork ────────────────────────────────────────────────────────
# The formula and the PKGBUILD pin the tag's tarball, so they can only change
# *after* the tag exists -- they land on master behind it. Committed before
# the website builds: steps 6 and 7 dirtied these files, and a site built
# from a tag checkout with uncommitted pins stamps its demo -dirty (which
# is how v0.1.4's demo first said 0.1.4-dirty+webdemo).
step "paperwork"
if git diff --quiet contrib/homebrew/slosh.rb contrib/arch/PKGBUILD; then
say "nothing to commit"
else
git add contrib/homebrew/slosh.rb contrib/arch/PKGBUILD
git commit -q -m "release: $tag paperwork -- formula and PKGBUILD pinned"
git push origin master
say "committed and pushed"
fi
# ── 9. the website, so the in-browser demo runs what was just released ─────
# The demo's slosh is cross-compiled from the tree the site is built in, and
# its version comes from contrib/version -- built from the (now clean)
# tag checkout, the demo says exactly what the release says (v0.1.2 shipped while the demo
# still announced 0.1.0, which is the drift this step removes). Old tags
# predate web/publish; for those the site is built from master instead,
# which is newer than the tag, not older.
step "website + demo"
if git show "$tag:web/publish" >/dev/null 2>&1; then
git checkout -q "$tag"
trap 'git checkout -q master' EXIT INT TERM
web/publish
git checkout -q master
trap - EXIT INT TERM
else
say "$tag has no web/publish -- building the site from master"
web/publish
fi
printf '\nshipped %s:\n' "$tag"
say "github https://github.com/$GH_REPO/releases/tag/$tag"
say "brew brew tap sl0p-foo/slosh && brew install slosh"
say "arch $ARCH_URL (x86_64 + aarch64)"
say "reminder: brew install --build-from-source contrib/homebrew/slosh.rb on a"
say " machine with brew, to prove the formula before announcing"
say " contrib/vendor-check: is the vendored terminal core falling behind?"
|