#!/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
  rvc-linux-amd64
  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=amd64 go build -trimpath -ldflags "$flags" -o "$out/rvc-linux-amd64" ./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 rvc-linux-amd64 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 rvc-linux-amd64 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/rvc-linux-amd64" "$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"
