Vectrum
Sign In

build.sh --release publishes the build on Vectrum

Tags the commit with the version in Cargo.toml, pushes the tag and
uploads the AppImage, the binary and SHA256SUMS, which publishes the
release. Checks the token, a clean tree and an unused version first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JjohannSocommitted 1 parent a3b190ecommitf298ee6

Showing 2 changed files with 83 additions and 0 deletions.

+10−0README.mdView File
@@ -273,6 +273,16 @@ paths set there for each agent's first instance. Threads and the project list li
273273 `~/.local/share/vectrum-code` (`threads/`, `state.json`); threads started without a project run
274274 in its `scratch` folder.
275275
276+ ## Build and release
277+
278+ `./build.sh` builds the release binary and an AppImage into `build/`. `./build.sh --release`
279+ (or `-r`) also publishes them on Vectrum: it tags the commit with the version in `Cargo.toml`,
280+ pushes the tag, and uploads the AppImage, the binary and `SHA256SUMS`, which publishes the
281+ release. It needs a clean working tree, a version not tagged yet (bump it in `Cargo.toml` for
282+ each release), and a read & write API token in `VECTRUM_TOKEN` or `~/.config/vectrum/token`.
283+ `VECTRUM_URL` (`https://vectrum.cc`) and `VECTRUM_REPO` (from the origin remote) pick where
284+ it goes.
285+
276286 ## Layout
277287
278288 - `src/claude.rs`: spawns and owns the agent process, `claude`, `pi` or `jcode api --stdio`
+73−0build.shView File
@@ -5,8 +5,26 @@
55 #
66 # appimagetool is used from $PATH if present, otherwise downloaded once into
77 # build/.tools. Set APPIMAGETOOL=/path/to/appimagetool to use a specific one.
8+ #
9+ # ./build.sh --release (or -r) then publishes them as a release on Vectrum:
10+ # it tags the commit with the version in Cargo.toml, pushes the tag, and
11+ # uploads the AppImage, the binary and SHA256SUMS (the first upload
12+ # publishes the release). Bump the version in Cargo.toml for each release.
13+ # VECTRUM_TOKEN a read & write API token (vec_…); otherwise read from
14+ # ~/.config/vectrum/token (or $VECTRUM_TOKEN_FILE)
15+ # VECTRUM_URL the server, https://vectrum.cc by default
16+ # VECTRUM_REPO owner/repo, taken from the origin remote by default
817 set -euo pipefail
918
19+ RELEASE=0
20+ for arg in "$@"; do
21+ case "$arg" in
22+ -r|--release) RELEASE=1 ;;
23+ -h|--help) sed -n '2,/^set -euo/p' "$0" | sed '$d; s/^# \{0,1\}//'; exit 0 ;;
24+ *) printf 'Unknown option: %s (try --help)\n' "$arg" >&2; exit 2 ;;
25+ esac
26+ done
27+
1028 ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1129 BUILD="$ROOT/build"
1230 TOOLS="$BUILD/.tools"
@@ -18,6 +36,32 @@ VERSION="$(sed -n 's/^version *= *"\(.*\)"/\1/p' "$ROOT/Cargo.toml" | head -n1)"
1836 ARCH="$(uname -m)"
1937
2038 log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
39+ die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; }
40+
41+ # ── 0. Release checks, before spending time on a build ───────────────────────
42+ if (( RELEASE )); then
43+ TAG="$VERSION"
44+ SERVER="${VECTRUM_URL:-https://vectrum.cc}"
45+ SERVER="${SERVER%/}"
46+ TOKEN_FILE="${VECTRUM_TOKEN_FILE:-$HOME/.config/vectrum/token}"
47+ TOKEN="${VECTRUM_TOKEN:-}"
48+ if [[ -z "$TOKEN" && -r "$TOKEN_FILE" ]]; then
49+ TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
50+ fi
51+ [[ -n "$TOKEN" ]] || die "No API token: set VECTRUM_TOKEN or save a read & write token to $TOKEN_FILE"
52+ # ssh://git@git.vectrum.cc:2222/vectrum/code.git, https://…/vectrum/code → vectrum/code
53+ REPO="${VECTRUM_REPO:-$(git -C "$ROOT" remote get-url origin | sed -E 's#\.git$##; s#^.*[:/]([^/:]+/[^/:]+)$#\1#')}"
54+ [[ "$REPO" == */* ]] || die "Couldn't tell the repository from the origin remote; set VECTRUM_REPO=owner/repo"
55+ [[ -z "$(git -C "$ROOT" status --porcelain)" ]] || die "Commit or stash your changes first: a release is built from a commit"
56+ HEAD="$(git -C "$ROOT" rev-parse HEAD)"
57+ REMOTE_TAG="$(git -C "$ROOT" ls-remote origin "refs/tags/$TAG^{}" "refs/tags/$TAG" | awk 'NR==1 || /\^\{\}$/ {sha=$1} END {print sha}')"
58+ LOCAL_TAG="$(git -C "$ROOT" rev-parse -q --verify "refs/tags/$TAG^{commit}" || true)"
59+ for tagged in "$REMOTE_TAG" "$LOCAL_TAG"; do
60+ [[ -z "$tagged" || "$tagged" == "$HEAD" ]] ||
61+ die "$TAG is already tagged at ${tagged:0:7}; bump the version in Cargo.toml for a new release"
62+ done
63+ log "Releasing $TAG of $REPO on $SERVER from ${HEAD:0:7}"
64+ fi
2165
2266 # ── 1. Binary ────────────────────────────────────────────────────────────────
2367 log "Building $NAME $VERSION (release, $ARCH)"
@@ -84,3 +128,32 @@ APPIMAGE_EXTRACT_AND_RUN=1 ARCH="$ARCH" "$APPIMAGETOOL" --no-appstream "$APPDIR"
84128 rm -rf "$APPDIR"
85129
86130 log "AppImage: ${OUT#"$ROOT"/}"
131+
132+ # ── 5. Release ───────────────────────────────────────────────────────────────
133+ if (( RELEASE )); then
134+ STAGE="$BUILD/release-$TAG"
135+ rm -rf "$STAGE"
136+ mkdir -p "$STAGE"
137+ cp "$OUT" "$STAGE/"
138+ cp "$BUILD/$NAME" "$STAGE/$NAME-$VERSION-$ARCH"
139+ (cd "$STAGE" && sha256sum -- * > SHA256SUMS)
140+
141+ if [[ -z "$LOCAL_TAG" ]]; then
142+ git -C "$ROOT" tag -a "$TAG" -m "$PRETTY $VERSION" "$HEAD"
143+ fi
144+ if [[ -z "$REMOTE_TAG" ]]; then
145+ log "Pushing tag $TAG"
146+ git -C "$ROOT" push -q origin "refs/tags/$TAG"
147+ fi
148+
149+ for file in "$STAGE"/*; do
150+ name="$(basename "$file")"
151+ log "Uploading $name"
152+ # The token goes in through a file descriptor, so it never shows in ps.
153+ if ! reply="$(curl -sS --fail-with-body -T "$file" -H @<(printf 'Authorization: Bearer %s\n' "$TOKEN") \
154+ "$SERVER/$REPO/releases/download/$TAG/$name")"; then
155+ die "Uploading $name failed: $reply"
156+ fi
157+ done
158+ log "Released: $SERVER/$REPO/releases/tag/$TAG"
159+ fi