#!/bin/sh # Build an inspectable, reproducible RVBox Linux-server/Windows-client bundle. # Publishing and certificate custody remain outside this repository; an optional # local signing hook can sign the Windows executable before checksums are made. set -eu repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) compose_file=$repo_root/deploy/compose.yaml usage() { cat <<'EOF' usage: scripts/release build --version VERSION --bootstrap-server-url WSS_URL [--output DIR] [--sign-windows-hook FILE] Builds a fresh immutable bundle containing: rvbox-server-linux-amd64 rvbox-server-linux-arm64 rvc-linux-amd64 rvc-linux-arm64 rvbox-windows-amd64.exe SHA256SUMS manifest.json The default output is dist/rvbox-VERSION. --output must remain below this repository's ignored dist/ tree. VERSION must be a safe release label ([0-9A-Za-z][0-9A-Za-z._+-]{0,63}); an existing final output is never replaced. WSS_URL is the deliberate first-install endpoint embedded only in the Windows installer; it must be an absolute `wss://` URL and is never inferred from a test fixture. Artifacts are built inside the pinned Docker toolchain with that exact version embedded in `--version`. The optional signing hook is a regular executable run on the host as: HOOK WINDOWS_EXE VERSION. It receives RVBOX_ARTIFACT and RVBOX_VERSION as environment variables and must sign the supplied executable in place. SHA256SUMS and manifest.json are generated only after it succeeds. No signing hook means the manifest explicitly marks the Windows artifact as unsigned. This is deliberate for CI/test builds; do not publish it as signed. EOF } fail() { printf '%s\n' "release: $*" >&2; exit 2; } command=${1:-} [ "$#" -gt 0 ] && shift [ "$command" = build ] || { usage >&2; fail "expected build"; } version= output= sign_hook= bootstrap_server_url= while [ "$#" -gt 0 ]; do case $1 in --version) [ "$#" -ge 2 ] || fail "--version needs a value"; version=$2; shift 2 ;; --bootstrap-server-url) [ "$#" -ge 2 ] || fail "--bootstrap-server-url needs a value"; bootstrap_server_url=$2; shift 2 ;; --output) [ "$#" -ge 2 ] || fail "--output needs a directory"; output=$2; shift 2 ;; --sign-windows-hook) [ "$#" -ge 2 ] || fail "--sign-windows-hook needs an executable file"; sign_hook=$2; shift 2 ;; --help|-h) usage; exit 0 ;; *) fail "unknown argument $1" ;; esac done case $version in ''|[!0-9A-Za-z]*|*[!0-9A-Za-z._+-]*|?????????????????????????????????????????????????????????????????*) fail "--version must match [0-9A-Za-z][0-9A-Za-z._+-]{0,63}" ;; esac case $bootstrap_server_url in wss://*) ;; *) fail "--bootstrap-server-url must be an absolute wss:// URL" ;; esac case $bootstrap_server_url in *[!A-Za-z0-9:/._-]*) fail "--bootstrap-server-url contains unsupported characters" ;; esac if [ -z "$output" ]; then output=$repo_root/dist/rvbox-$version; fi case $output in /*) ;; *) output=$repo_root/$output ;; esac case $output in "$repo_root"/dist/*) ;; *) fail "--output must be below $repo_root/dist" ;; esac parent=$(dirname -- "$output") [ ! -e "$output" ] || fail "refusing to replace existing output $output" [ -d "$parent" ] || mkdir -p "$parent" [ ! -L "$parent" ] || fail "refusing symlink output parent $parent" if [ -n "$sign_hook" ]; then [ -f "$sign_hook" ] && [ ! -L "$sign_hook" ] && [ -x "$sign_hook" ] || fail "signing hook must be an executable regular file" fi docker version >/dev/null 2>&1 || fail "Docker is unavailable" docker compose -f "$compose_file" version >/dev/null 2>&1 || fail "Docker Compose is unavailable" partial=$parent/.rvbox-$version.partial-$$ mkdir "$partial" || fail "could not create private release staging directory" cleanup() { rm -rf -- "$partial"; } trap cleanup EXIT HUP INT TERM container_partial=/workspace/${partial#"$repo_root"/} commit=$(git -C "$repo_root" rev-parse HEAD) dirty=$(git -C "$repo_root" status --porcelain) [ -z "$dirty" ] || fail "refusing release build from a dirty worktree" docker compose -f "$compose_file" run --rm toolchain sh -ec ' set -eu version=$1 out=$2 flags="-s -w -X main.buildVersion=$version" windows_flags="$flags -X main.bootstrapServerURL=$3" resource=/workspace/cmd/rvbox/rvbox-release_windows_amd64.syso icon="$out/.rvbox-release-icon.png" trap "rm -f -- \"$resource\" \"$icon\"" EXIT HUP INT TERM go run ./tools/releaseicon --out "$icon" go-winres simply --arch amd64 --out /workspace/cmd/rvbox/rvbox-release --icon "$icon" --manifest gui --file-version "$version" --product-version "$version" --file-description "RVBox Windows client" --product-name "RVBox" --original-filename "rvbox.exe" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$flags" -o "$out/rvbox-server-linux-amd64" ./cmd/rvbox-server CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$flags" -o "$out/rvbox-server-linux-arm64" ./cmd/rvbox-server CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$flags" -o "$out/rvc-linux-amd64" ./cmd/rvc CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$flags" -o "$out/rvc-linux-arm64" ./cmd/rvc CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "-H=windowsgui $windows_flags" -o "$out/rvbox-windows-amd64.exe" ./cmd/rvbox ' sh "$version" "$container_partial" "$bootstrap_server_url" signed=false if [ -n "$sign_hook" ]; then RVBOX_ARTIFACT=$partial/rvbox-windows-amd64.exe RVBOX_VERSION=$version "$sign_hook" "$partial/rvbox-windows-amd64.exe" "$version" signed=true fi for artifact in rvbox-server-linux-amd64 rvbox-server-linux-arm64 rvc-linux-amd64 rvc-linux-arm64 rvbox-windows-amd64.exe; do [ -f "$partial/$artifact" ] && [ ! -L "$partial/$artifact" ] || fail "builder did not create regular $artifact" done (cd "$partial" && sha256sum rvbox-server-linux-amd64 rvbox-server-linux-arm64 rvc-linux-amd64 rvc-linux-arm64 rvbox-windows-amd64.exe) >"$partial/SHA256SUMS" { printf '{\n' printf ' "schema": 1,\n' printf ' "version": "%s",\n' "$version" printf ' "git_commit": "%s",\n' "$commit" printf ' "windows_signed": %s,\n' "$signed" printf ' "artifacts": "SHA256SUMS"\n' printf '}\n' } >"$partial/manifest.json" chmod 0755 "$partial/rvbox-server-linux-amd64" "$partial/rvbox-server-linux-arm64" "$partial/rvc-linux-amd64" "$partial/rvc-linux-arm64" "$partial/rvbox-windows-amd64.exe" chmod 0644 "$partial/SHA256SUMS" "$partial/manifest.json" mv -- "$partial" "$output" trap - EXIT HUP INT TERM printf 'release_bundle=%s\n' "$output"