From ea8a4d8846d38497cf8e4bb83a91c78ccbc65169 Mon Sep 17 00:00:00 2001 From: cabbage Date: Fri, 11 Sep 2026 09:24:32 +0000 Subject: [PATCH] build: embed Windows release resources --- deploy/Dockerfile.toolchain | 6 +++ scripts/release | 5 +++ tools/releaseicon/main.go | 89 +++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 tools/releaseicon/main.go diff --git a/deploy/Dockerfile.toolchain b/deploy/Dockerfile.toolchain index 4d69753..9d6b887 100644 --- a/deploy/Dockerfile.toolchain +++ b/deploy/Dockerfile.toolchain @@ -8,6 +8,7 @@ ARG PROTOC_AMD64_SHA256=a45cda0989c17dd950db55f6fbe1e5814c50fda08e87aa422980ac1f ARG PROTOC_ARM64_SHA256=36b518ac14d90351cc6598228ed2bbe5afe4e357b1af470b07e0ec1609875de2 ARG PROTOC_GEN_GO_VERSION=1.36.12 ARG PROTOC_GEN_GO_GRPC_VERSION=1.6.2 +ARG GO_WINRES_VERSION=0.3.3 ARG TARGETARCH ENV GOCACHE=/cache/go-build \ @@ -34,6 +35,11 @@ RUN apt-get update \ RUN GOBIN=/usr/local/bin go install "google.golang.org/protobuf/cmd/protoc-gen-go@v${PROTOC_GEN_GO_VERSION}" \ && GOBIN=/usr/local/bin go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@v${PROTOC_GEN_GO_GRPC_VERSION}" +# Release packaging generates a temporary, architecture-suffixed Windows +# resource object with this pinned pure-Go tool. It is intentionally part of +# the toolchain image rather than a host prerequisite. +RUN GOBIN=/usr/local/bin go install "github.com/tc-hib/go-winres@v${GO_WINRES_VERSION}" + RUN mkdir -p /cache/go-build /cache/go/pkg/mod /cache/xdg /workspace \ && chown -R 1001:1001 /cache /workspace diff --git a/scripts/release b/scripts/release index 30b1243..8722fd2 100755 --- a/scripts/release +++ b/scripts/release @@ -93,6 +93,11 @@ docker compose -f "$compose_file" run --rm toolchain sh -ec ' version=$1 out=$2 flags="-s -w -X main.buildVersion=$version" + 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 diff --git a/tools/releaseicon/main.go b/tools/releaseicon/main.go new file mode 100644 index 0000000..b936171 --- /dev/null +++ b/tools/releaseicon/main.go @@ -0,0 +1,89 @@ +// Command releaseicon creates the small deterministic PNG used only by the +// Windows release resource generator. Keeping the source rather than a binary +// asset makes the icon reproducible in the pinned toolchain. +package main + +import ( + "flag" + "image" + "image/color" + "image/png" + "os" + "path/filepath" +) + +const canvasSize = 256 + +func main() { + output := flag.String("out", "", "absolute PNG output path") + flag.Parse() + if *output == "" || !filepath.IsAbs(*output) { + panic("--out must be an absolute path") + } + if err := os.MkdirAll(filepath.Dir(*output), 0o700); err != nil { + panic(err) + } + file, err := os.OpenFile(*output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + panic(err) + } + defer file.Close() + + image := image.NewNRGBA(image.Rect(0, 0, canvasSize, canvasSize)) + fill(image, color.NRGBA{R: 15, G: 28, B: 48, A: 255}) + fillRoundedRect(image, 18, 18, 238, 238, 42, color.NRGBA{R: 28, G: 56, B: 92, A: 255}) + // A compact outlined box and terminal prompt remain recognizable at the + // 16-pixel size Windows Explorer generates from this source image. + strokeRect(image, 57, 62, 199, 194, 12, color.NRGBA{R: 54, G: 211, B: 189, A: 255}) + fillRect(image, 79, 105, 96, 121, color.NRGBA{R: 240, G: 249, B: 255, A: 255}) + fillRect(image, 95, 121, 112, 137, color.NRGBA{R: 240, G: 249, B: 255, A: 255}) + fillRect(image, 79, 137, 96, 153, color.NRGBA{R: 240, G: 249, B: 255, A: 255}) + fillRect(image, 126, 150, 176, 166, color.NRGBA{R: 240, G: 249, B: 255, A: 255}) + if err := png.Encode(file, image); err != nil { + panic(err) + } +} + +func fill(image *image.NRGBA, value color.NRGBA) { + for y := 0; y < canvasSize; y++ { + for x := 0; x < canvasSize; x++ { + image.SetNRGBA(x, y, value) + } + } +} + +func fillRoundedRect(image *image.NRGBA, left, top, right, bottom, radius int, value color.NRGBA) { + for y := top; y < bottom; y++ { + for x := left; x < right; x++ { + dx, dy := 0, 0 + if x < left+radius { + dx = left + radius - x + } else if x >= right-radius { + dx = x - (right - radius - 1) + } + if y < top+radius { + dy = top + radius - y + } else if y >= bottom-radius { + dy = y - (bottom - radius - 1) + } + if dx*dx+dy*dy <= radius*radius { + image.SetNRGBA(x, y, value) + } + } + } +} + +func strokeRect(image *image.NRGBA, left, top, right, bottom, width int, value color.NRGBA) { + fillRect(image, left, top, right, top+width, value) + fillRect(image, left, bottom-width, right, bottom, value) + fillRect(image, left, top, left+width, bottom, value) + fillRect(image, right-width, top, right, bottom, value) +} + +func fillRect(image *image.NRGBA, left, top, right, bottom int, value color.NRGBA) { + for y := top; y < bottom; y++ { + for x := left; x < right; x++ { + image.SetNRGBA(x, y, value) + } + } +}