From 68202ce58d80d16e8c07869d5c7fd6a8d8cb93e3 Mon Sep 17 00:00:00 2001 From: "Maciek \"mab122\" Bator" Date: Thu, 16 Jul 2026 13:49:13 +0200 Subject: [PATCH] build: tag rad --version with a +lfs-ipfs suffix, fix newline bug Appends a semver build-metadata suffix to RADICLE_VERSION so `rad --version` (and radicle-node/git-remote-rad, which share this build script via symlink) unambiguously identify a binary as this fork rather than upstream heartwood -- useful for checking what a deploy target is actually running. While wiring this up, found and fixed a real pre-existing bug: none of the three `git` subprocess outputs captured here (GIT_HEAD, RADICLE_VERSION, SOURCE_DATE_EPOCH) were trimmed, so each carried a trailing newline into its `cargo::rustc-env=KEY=VALUE` directive. Harmless on its own since nothing followed it, but appending the +lfs-ipfs suffix landed after that embedded newline, splitting the directive across two lines -- cargo silently discards the second line as unrecognized, so the suffix never actually took effect until this trim was added. Also adds scripts/publish-package.sh: builds all four release binaries (rad, radicle-node, git-remote-rad, rad-lfs-transfer) and publishes them as a Forgejo generic package, so a deploy target without a Rust toolchain can download instead of rebuilding from source (see NOTES-lfs-store-note-write-bug.md's VPS deployment story for why that matters). --- build.rs | 15 ++++- scripts/publish-package.sh | 124 +++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 3 deletions(-) create mode 100755 scripts/publish-package.sh diff --git a/build.rs b/build.rs index 4cdd62b4..92c80f76 100644 --- a/build.rs +++ b/build.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { .ok() .and_then(|output| { if output.status.success() { - String::from_utf8(output.stdout).ok() + String::from_utf8(output.stdout).ok().map(|s| s.trim().to_string()) } else { None } @@ -44,7 +44,7 @@ fn main() -> Result<(), Box> { .ok() .and_then(|output| { if output.status.success() { - String::from_utf8(output.stdout).ok() + String::from_utf8(output.stdout).ok().map(|s| s.trim().to_string()) } else { None } @@ -61,6 +61,15 @@ fn main() -> Result<(), Box> { version }; + // This is the `rad-lfs-ipfs` fork (Git LFS support backed by IPFS, see + // README.md/LFS-IPFS.md) -- append a semver build-metadata suffix (the + // `+...` part a `git describe`-derived version doesn't otherwise have + // room for) so `--version` unambiguously identifies a binary as this + // fork rather than upstream heartwood, e.g. when checking what a + // deploy target is actually running. Applies to every binary in this + // workspace, since they all share this build script via symlink. + let version = format!("{version}+lfs-ipfs"); + // Set a build-time `SOURCE_DATE_EPOCH` env var which includes the commit time. let commit_time = env::var("SOURCE_DATE_EPOCH").unwrap_or_else(|_| { Command::new("git") @@ -72,7 +81,7 @@ fn main() -> Result<(), Box> { .ok() .and_then(|output| { if output.status.success() { - String::from_utf8(output.stdout).ok() + String::from_utf8(output.stdout).ok().map(|s| s.trim().to_string()) } else { None } diff --git a/scripts/publish-package.sh b/scripts/publish-package.sh new file mode 100755 index 00000000..0c52fb21 --- /dev/null +++ b/scripts/publish-package.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# +# Builds release binaries for the LFS-over-IPFS stack (rad, radicle-node, +# git-remote-rad, rad-lfs-transfer) and publishes them as a Forgejo generic +# package, so a deploy target can `curl` a tarball instead of needing a full +# Rust toolchain (see NOTES-lfs-store-note-write-bug.md's VPS deployment +# story for why that matters -- rebuilding in place isn't always possible). +# +# Usage: +# FORGEJO_TOKEN=... ./scripts/publish-package.sh [version] +# +# `version` defaults to `git describe --tags --always --dirty`, sanitized +# for use in a URL path. The token needs the `write:package` scope; it's +# read from $FORGEJO_TOKEN, falling back to ~/.config/forgejo/token if that +# env var isn't set (chmod 600 that file if you use it). +# +# Config (override via env): +# FORGEJO_HOST default: git.hswro.org +# FORGEJO_OWNER default: mab122 +# PACKAGE_NAME default: radicle-lfs-ipfs + +set -euo pipefail + +FORGEJO_HOST="${FORGEJO_HOST:-git.hswro.org}" +FORGEJO_OWNER="${FORGEJO_OWNER:-mab122}" +PACKAGE_NAME="${PACKAGE_NAME:-radicle-lfs-ipfs}" + +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" + +if [ -n "${FORGEJO_TOKEN:-}" ]; then + token="$FORGEJO_TOKEN" +elif [ -r "$HOME/.config/forgejo/token" ]; then + token="$(cat "$HOME/.config/forgejo/token")" +else + echo "error: no Forgejo API token found. Set \$FORGEJO_TOKEN, or put one" \ + "(scope: write:package) in ~/.config/forgejo/token (chmod 600)." >&2 + exit 1 +fi + +version_raw="${1:-$(git describe --tags --always --dirty)}" +# Forgejo generic package versions must be URL-path-safe; a `git describe` +# with a "-dirty" suffix or a leading "v" is otherwise fine, but sanitize +# defensively rather than let a stray character break the upload URL. +version="$(printf '%s' "$version_raw" | tr -c 'A-Za-z0-9._-' '-')" + +if [ ! -f "$repo_root/radicle-lfs-transfer/Cargo.toml" ]; then + echo "error: radicle-lfs-transfer/ submodule is empty -- run" \ + "'git submodule update --init' first." >&2 + exit 1 +fi + +echo "==> Building release binaries (version: $version)" +cargo build --release --locked -p radicle-cli --bin rad +cargo build --release --locked -p radicle-node +cargo build --release --locked -p radicle-remote-helper +cargo build --release --locked --manifest-path radicle-lfs-transfer/Cargo.toml + +target_triple="$(rustc -vV | sed -n 's/host: //p')" +staging="$(mktemp -d)" +trap 'rm -rf "$staging"' EXIT + +cp target/release/rad "$staging/" +cp target/release/radicle-node "$staging/" +cp target/release/git-remote-rad "$staging/" +cp radicle-lfs-transfer/target/release/rad-lfs-transfer "$staging/" + +# Recorded because a binary built on a newer glibc can fail to run on an +# older one (discovered live deploying this exact stack to a Debian VPS) -- +# anyone consuming this package needs to be able to check compatibility +# before copying the binaries over, not find out by them failing to start. +cat > "$staging/MANIFEST.txt" << EOF +package: $PACKAGE_NAME +version: $version +git commit: $(git rev-parse HEAD) +git branch: $(git rev-parse --abbrev-ref HEAD) +built: $(date -u +%Y-%m-%dT%H:%M:%SZ) +target triple: $target_triple +rustc: $(rustc --version) +glibc: $(ldd --version | head -1) +contents: + rad $(target/release/rad --version) + radicle-node $(target/release/radicle-node --version) + git-remote-rad $(target/release/git-remote-rad --version) + rad-lfs-transfer (no --version; speaks git-lfs custom-transfer protocol on stdin/stdout) +EOF + +archive_name="${PACKAGE_NAME}-${version}-${target_triple}.tar.gz" +archive_path="$staging/../${archive_name}" +tar -C "$staging" -czf "$archive_path" . +sha256sum "$archive_path" | awk '{print $1}' > "${archive_path}.sha256" + +echo "==> Built $(du -h "$archive_path" | cut -f1) archive: $archive_name" + +upload() { + local file="$1" name="$2" + local url="https://${FORGEJO_HOST}/api/packages/${FORGEJO_OWNER}/generic/${PACKAGE_NAME}/${version}/${name}" + local status + status="$(curl -sS -o /tmp/forgejo-upload-response.$$ -w '%{http_code}' \ + -H "Authorization: token ${token}" \ + -X PUT "$url" \ + -T "$file")" + if [ "$status" = "201" ]; then + echo " uploaded: $name" + elif [ "$status" = "409" ]; then + echo " error: $name already exists at version $version -- bump the version" \ + "or delete the existing package file in Forgejo first." >&2 + cat /tmp/forgejo-upload-response.$$ >&2 + rm -f /tmp/forgejo-upload-response.$$ + exit 1 + else + echo " error: upload of $name failed (HTTP $status)" >&2 + cat /tmp/forgejo-upload-response.$$ >&2 + rm -f /tmp/forgejo-upload-response.$$ + exit 1 + fi + rm -f /tmp/forgejo-upload-response.$$ +} + +echo "==> Uploading to https://${FORGEJO_HOST}/${FORGEJO_OWNER}/-/packages/generic/${PACKAGE_NAME}/${version}" +upload "$archive_path" "$archive_name" +upload "${archive_path}.sha256" "${archive_name}.sha256" + +echo "==> Done: https://${FORGEJO_HOST}/${FORGEJO_OWNER}/-/packages/generic/${PACKAGE_NAME}/${version}"