127 lines
5.2 KiB
Bash
Executable File
127 lines
5.2 KiB
Bash
Executable File
#!/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 [--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.
|
|
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=
|
|
while [ "$#" -gt 0 ]; do
|
|
case $1 in
|
|
--version) [ "$#" -ge 2 ] || fail "--version needs a value"; version=$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
|
|
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"
|
|
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 $flags" -o "$out/rvbox-windows-amd64.exe" ./cmd/rvbox
|
|
' sh "$version" "$container_partial"
|
|
|
|
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"
|