feat: include winres metadata in Windows binaries (#16706)

Adds information like product/file version, description, product name
and copyright to compiled Windows binaries in dogfood and release
builds. Also adds an icon to the executable.

This is necessary for Coder Desktop to be able to check the version on
binaries.

### Before:

![image](https://github.com/user-attachments/assets/82351b63-6b23-4ef8-ab89-7f9e6dafeabd)

![image](https://github.com/user-attachments/assets/d17d8098-e330-4ac0-b104-31163f84279f)

### After:

![image](https://github.com/user-attachments/assets/0ba50afa-ad53-4ad2-b5e2-557358cda037)

![image](https://github.com/user-attachments/assets/d305cc27-e3f3-41a8-9098-498b71344faa)

![image](https://github.com/user-attachments/assets/42f74ace-bda1-414f-b514-68d4d928c958)

Closes https://github.com/coder/coder/issues/16693
This commit is contained in:
Dean Sheather
2025-02-28 16:03:08 +11:00
committed by GitHub
parent 3997eeee26
commit 64fec8bf0b
7 changed files with 185 additions and 21 deletions
+50 -3
View File
@@ -1021,7 +1021,10 @@ jobs:
if: github.ref == 'refs/heads/main' && needs.changes.outputs.docs-only == 'false' && !github.event.pull_request.head.repo.fork
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-22.04' }}
permissions:
packages: write # Needed to push images to ghcr.io
# Necessary to push docker images to ghcr.io.
packages: write
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
id-token: write
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
outputs:
@@ -1050,12 +1053,44 @@ jobs:
- name: Setup Go
uses: ./.github/actions/setup-go
# Necessary for signing Windows binaries.
- name: Setup Java
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
distribution: "zulu"
java-version: "11.0"
- name: Install go-winres
run: go install github.com/tc-hib/go-winres@d743268d7ea168077ddd443c4240562d4f5e8c3e # v0.3.3
- name: Install nfpm
run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.35.1
- name: Install zstd
run: sudo apt-get install -y zstd
- name: Setup Windows EV Signing Certificate
run: |
set -euo pipefail
touch /tmp/ev_cert.pem
chmod 600 /tmp/ev_cert.pem
echo "$EV_SIGNING_CERT" > /tmp/ev_cert.pem
wget https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar -O /tmp/jsign-6.0.jar
env:
EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }}
# Setup GCloud for signing Windows binaries.
- name: Authenticate to Google Cloud
id: gcloud_auth
uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8
with:
workload_identity_provider: ${{ secrets.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }}
service_account: ${{ secrets.GCP_CODE_SIGNING_SERVICE_ACCOUNT }}
token_format: "access_token"
- name: Setup GCloud SDK
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
- name: Download dylibs
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
@@ -1082,6 +1117,18 @@ jobs:
build/coder_linux_{amd64,arm64,armv7} \
build/coder_"$version"_windows_amd64.zip \
build/coder_"$version"_linux_amd64.{tar.gz,deb}
env:
# The Windows slim binary must be signed for Coder Desktop to accept
# it. The darwin executables don't need to be signed, but the dylibs
# do (see above).
CODER_SIGN_WINDOWS: "1"
CODER_WINDOWS_RESOURCES: "1"
EV_KEY: ${{ secrets.EV_KEY }}
EV_KEYSTORE: ${{ secrets.EV_KEYSTORE }}
EV_TSA_URL: ${{ secrets.EV_TSA_URL }}
EV_CERTIFICATE_PATH: /tmp/ev_cert.pem
GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }}
JSIGN_PATH: /tmp/jsign-6.0.jar
- name: Build Linux Docker images
id: build-docker
@@ -1183,10 +1230,10 @@ jobs:
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
- name: Set up Flux CLI
uses: fluxcd/flux2/action@af67405ee43a6cd66e0b73f4b3802e8583f9d961 # v2.5.0
uses: fluxcd/flux2/action@8d5f40dca5aa5d3c0fc3414457dda15a0ac92fa4 # v2.5.1
with:
# Keep this and the github action up to date with the version of flux installed in dogfood cluster
version: "2.2.1"
version: "2.5.1"
- name: Get Cluster Credentials
uses: google-github-actions/get-gke-credentials@7a108e64ed8546fe38316b4086e91da13f4785e1 # v2.3.1
+16 -12
View File
@@ -223,21 +223,12 @@ jobs:
distribution: "zulu"
java-version: "11.0"
- name: Install go-winres
run: go install github.com/tc-hib/go-winres@d743268d7ea168077ddd443c4240562d4f5e8c3e # v0.3.3
- name: Install nsis and zstd
run: sudo apt-get install -y nsis zstd
- name: Download dylibs
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dylibs
path: ./build
- name: Insert dylibs
run: |
mv ./build/*amd64.dylib ./site/out/bin/coder-vpn-darwin-amd64.dylib
mv ./build/*arm64.dylib ./site/out/bin/coder-vpn-darwin-arm64.dylib
mv ./build/*arm64.h ./site/out/bin/coder-vpn-darwin-dylib.h
- name: Install nfpm
run: |
set -euo pipefail
@@ -294,6 +285,18 @@ jobs:
- name: Setup GCloud SDK
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
- name: Download dylibs
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dylibs
path: ./build
- name: Insert dylibs
run: |
mv ./build/*amd64.dylib ./site/out/bin/coder-vpn-darwin-amd64.dylib
mv ./build/*arm64.dylib ./site/out/bin/coder-vpn-darwin-arm64.dylib
mv ./build/*arm64.h ./site/out/bin/coder-vpn-darwin-dylib.h
- name: Build binaries
run: |
set -euo pipefail
@@ -310,6 +313,7 @@ jobs:
env:
CODER_SIGN_WINDOWS: "1"
CODER_SIGN_DARWIN: "1"
CODER_WINDOWS_RESOURCES: "1"
AC_CERTIFICATE_FILE: /tmp/apple_cert.p12
AC_CERTIFICATE_PASSWORD_FILE: /tmp/apple_cert_password.txt
AC_APIKEY_ISSUER_ID: ${{ secrets.AC_APIKEY_ISSUER_ID }}
+1
View File
@@ -0,0 +1 @@
*.syso
+8
View File
@@ -0,0 +1,8 @@
// This package is used for embedding .syso resource files into the binary
// during build and does not contain any code. During build, .syso files will be
// dropped in this directory and then removed after the build completes.
//
// This package must be imported by all binaries for this to work.
//
// See build_go.sh for more details.
package resources
+1
View File
@@ -8,6 +8,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/coder/coder/v2/agent/agentexec"
_ "github.com/coder/coder/v2/buildinfo/resources"
"github.com/coder/coder/v2/cli"
)
+1
View File
@@ -8,6 +8,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/coder/coder/v2/agent/agentexec"
_ "github.com/coder/coder/v2/buildinfo/resources"
entcli "github.com/coder/coder/v2/enterprise/cli"
)
+108 -6
View File
@@ -36,17 +36,19 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
version=""
os="${GOOS:-linux}"
arch="${GOARCH:-amd64}"
output_path=""
slim="${CODER_SLIM_BUILD:-0}"
agpl="${CODER_BUILD_AGPL:-0}"
sign_darwin="${CODER_SIGN_DARWIN:-0}"
sign_windows="${CODER_SIGN_WINDOWS:-0}"
bin_ident="com.coder.cli"
output_path=""
agpl="${CODER_BUILD_AGPL:-0}"
boringcrypto=${CODER_BUILD_BORINGCRYPTO:-0}
debug=0
dylib=0
windows_resources="${CODER_WINDOWS_RESOURCES:-0}"
debug=0
args="$(getopt -o "" -l version:,os:,arch:,output:,slim,agpl,sign-darwin,boringcrypto,dylib,debug -- "$@")"
bin_ident="com.coder.cli"
args="$(getopt -o "" -l version:,os:,arch:,output:,slim,agpl,sign-darwin,sign-windows,boringcrypto,dylib,windows-resources,debug -- "$@")"
eval set -- "$args"
while true; do
case "$1" in
@@ -79,6 +81,10 @@ while true; do
sign_darwin=1
shift
;;
--sign-windows)
sign_windows=1
shift
;;
--boringcrypto)
boringcrypto=1
shift
@@ -87,6 +93,10 @@ while true; do
dylib=1
shift
;;
--windows-resources)
windows_resources=1
shift
;;
--debug)
debug=1
shift
@@ -115,11 +125,13 @@ if [[ "$sign_darwin" == 1 ]]; then
dependencies rcodesign
requiredenvs AC_CERTIFICATE_FILE AC_CERTIFICATE_PASSWORD_FILE
fi
if [[ "$sign_windows" == 1 ]]; then
dependencies java
requiredenvs JSIGN_PATH EV_KEYSTORE EV_KEY EV_CERTIFICATE_PATH EV_TSA_URL GCLOUD_ACCESS_TOKEN
fi
if [[ "$windows_resources" == 1 ]]; then
dependencies go-winres
fi
ldflags=(
-X "'github.com/coder/coder/v2/buildinfo.tag=$version'"
@@ -204,10 +216,100 @@ if [[ "$boringcrypto" == 1 ]]; then
goexp="boringcrypto"
fi
# On Windows, we use go-winres to embed the resources into the binary.
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
# Convert the version to a format that Windows understands.
# Remove any trailing data after a "+" or "-".
version_windows=$version
version_windows="${version_windows%+*}"
version_windows="${version_windows%-*}"
# If there wasn't any extra data, add a .0 to the version. Otherwise, add
# a .1 to the version to signify that this is not a release build so it can
# be distinguished from a release build.
non_release_build=0
if [[ "$version_windows" == "$version" ]]; then
version_windows+=".0"
else
version_windows+=".1"
non_release_build=1
fi
if [[ ! "$version_windows" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-1]$ ]]; then
error "Computed invalid windows version format: $version_windows"
fi
# File description changes based on slimness, AGPL status, and architecture.
file_description="Coder"
if [[ "$agpl" == 1 ]]; then
file_description+=" AGPL"
fi
if [[ "$slim" == 1 ]]; then
file_description+=" CLI"
fi
if [[ "$non_release_build" == 1 ]]; then
file_description+=" (development build)"
fi
# Because this writes to a file with the OS and arch in the filename, we
# don't support concurrent builds for the same OS and arch (irregardless of
# slimness or AGPL status).
#
# This is fine since we only embed resources during dogfood and release
# builds, which use make (which will build all slim targets in parallel,
# then all non-slim targets in parallel).
expected_rsrc_file="./buildinfo/resources/resources_windows_${arch}.syso"
if [[ -f "$expected_rsrc_file" ]]; then
rm "$expected_rsrc_file"
fi
touch "$expected_rsrc_file"
pushd ./buildinfo/resources
GOARCH="$arch" go-winres simply \
--arch "$arch" \
--out "resources" \
--product-version "$version_windows" \
--file-version "$version_windows" \
--manifest "cli" \
--file-description "$file_description" \
--product-name "Coder" \
--copyright "Copyright $(date +%Y) Coder Technologies Inc." \
--original-filename "coder.exe" \
--icon ../../scripts/win-installer/coder.ico
popd
if [[ ! -f "$expected_rsrc_file" ]]; then
error "Failed to generate $expected_rsrc_file"
fi
fi
set +e
GOEXPERIMENT="$goexp" CGO_ENABLED="$cgo" GOOS="$os" GOARCH="$arch" GOARM="$arm_version" \
go build \
"${build_args[@]}" \
"$cmd_path" 1>&2
exit_code=$?
set -e
# Clean up the resources file if it was generated.
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
rm "$expected_rsrc_file"
fi
if [[ "$exit_code" != 0 ]]; then
exit "$exit_code"
fi
# If we did embed resources, verify that they were included.
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
winres_dir=$(mktemp -d)
if ! go-winres extract --dir "$winres_dir" "$output_path" 1>&2; then
rm -rf "$winres_dir"
error "Compiled binary does not contain embedded resources"
fi
# If go-winres didn't return an error, it means it did find embedded
# resources.
rm -rf "$winres_dir"
fi
if [[ "$sign_darwin" == 1 ]] && [[ "$os" == "darwin" ]]; then
execrelative ./sign_darwin.sh "$output_path" "$bin_ident" 1>&2