Skip to content

Commit 64fec8b

Browse files
authored
feat: include winres metadata in Windows binaries (coder#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 coder#16693
1 parent 3997eee commit 64fec8b

File tree

7 files changed

+185
-21
lines changed

7 files changed

+185
-21
lines changed

.github/workflows/ci.yaml

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,10 @@ jobs:
10211021
if: github.ref == 'refs/heads/main' && needs.changes.outputs.docs-only == 'false' && !github.event.pull_request.head.repo.fork
10221022
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-22.04' }}
10231023
permissions:
1024-
packages: write # Needed to push images to ghcr.io
1024+
# Necessary to push docker images to ghcr.io.
1025+
packages: write
1026+
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
1027+
id-token: write
10251028
env:
10261029
DOCKER_CLI_EXPERIMENTAL: "enabled"
10271030
outputs:
@@ -1050,12 +1053,44 @@ jobs:
10501053
- name: Setup Go
10511054
uses: ./.github/actions/setup-go
10521055

1056+
# Necessary for signing Windows binaries.
1057+
- name: Setup Java
1058+
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
1059+
with:
1060+
distribution: "zulu"
1061+
java-version: "11.0"
1062+
1063+
- name: Install go-winres
1064+
run: go install github.com/tc-hib/go-winres@d743268d7ea168077ddd443c4240562d4f5e8c3e # v0.3.3
1065+
10531066
- name: Install nfpm
10541067
run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.35.1
10551068

10561069
- name: Install zstd
10571070
run: sudo apt-get install -y zstd
10581071

1072+
- name: Setup Windows EV Signing Certificate
1073+
run: |
1074+
set -euo pipefail
1075+
touch /tmp/ev_cert.pem
1076+
chmod 600 /tmp/ev_cert.pem
1077+
echo "$EV_SIGNING_CERT" > /tmp/ev_cert.pem
1078+
wget https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar -O /tmp/jsign-6.0.jar
1079+
env:
1080+
EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }}
1081+
1082+
# Setup GCloud for signing Windows binaries.
1083+
- name: Authenticate to Google Cloud
1084+
id: gcloud_auth
1085+
uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8
1086+
with:
1087+
workload_identity_provider: ${{ secrets.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }}
1088+
service_account: ${{ secrets.GCP_CODE_SIGNING_SERVICE_ACCOUNT }}
1089+
token_format: "access_token"
1090+
1091+
- name: Setup GCloud SDK
1092+
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
1093+
10591094
- name: Download dylibs
10601095
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
10611096
with:
@@ -1082,6 +1117,18 @@ jobs:
10821117
build/coder_linux_{amd64,arm64,armv7} \
10831118
build/coder_"$version"_windows_amd64.zip \
10841119
build/coder_"$version"_linux_amd64.{tar.gz,deb}
1120+
env:
1121+
# The Windows slim binary must be signed for Coder Desktop to accept
1122+
# it. The darwin executables don't need to be signed, but the dylibs
1123+
# do (see above).
1124+
CODER_SIGN_WINDOWS: "1"
1125+
CODER_WINDOWS_RESOURCES: "1"
1126+
EV_KEY: ${{ secrets.EV_KEY }}
1127+
EV_KEYSTORE: ${{ secrets.EV_KEYSTORE }}
1128+
EV_TSA_URL: ${{ secrets.EV_TSA_URL }}
1129+
EV_CERTIFICATE_PATH: /tmp/ev_cert.pem
1130+
GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }}
1131+
JSIGN_PATH: /tmp/jsign-6.0.jar
10851132

10861133
- name: Build Linux Docker images
10871134
id: build-docker
@@ -1183,10 +1230,10 @@ jobs:
11831230
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
11841231

11851232
- name: Set up Flux CLI
1186-
uses: fluxcd/flux2/action@af67405ee43a6cd66e0b73f4b3802e8583f9d961 # v2.5.0
1233+
uses: fluxcd/flux2/action@8d5f40dca5aa5d3c0fc3414457dda15a0ac92fa4 # v2.5.1
11871234
with:
11881235
# Keep this and the github action up to date with the version of flux installed in dogfood cluster
1189-
version: "2.2.1"
1236+
version: "2.5.1"
11901237

11911238
- name: Get Cluster Credentials
11921239
uses: google-github-actions/get-gke-credentials@7a108e64ed8546fe38316b4086e91da13f4785e1 # v2.3.1

.github/workflows/release.yaml

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -223,21 +223,12 @@ jobs:
223223
distribution: "zulu"
224224
java-version: "11.0"
225225

226+
- name: Install go-winres
227+
run: go install github.com/tc-hib/go-winres@d743268d7ea168077ddd443c4240562d4f5e8c3e # v0.3.3
228+
226229
- name: Install nsis and zstd
227230
run: sudo apt-get install -y nsis zstd
228231

229-
- name: Download dylibs
230-
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
231-
with:
232-
name: dylibs
233-
path: ./build
234-
235-
- name: Insert dylibs
236-
run: |
237-
mv ./build/*amd64.dylib ./site/out/bin/coder-vpn-darwin-amd64.dylib
238-
mv ./build/*arm64.dylib ./site/out/bin/coder-vpn-darwin-arm64.dylib
239-
mv ./build/*arm64.h ./site/out/bin/coder-vpn-darwin-dylib.h
240-
241232
- name: Install nfpm
242233
run: |
243234
set -euo pipefail
@@ -294,6 +285,18 @@ jobs:
294285
- name: Setup GCloud SDK
295286
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
296287

288+
- name: Download dylibs
289+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
290+
with:
291+
name: dylibs
292+
path: ./build
293+
294+
- name: Insert dylibs
295+
run: |
296+
mv ./build/*amd64.dylib ./site/out/bin/coder-vpn-darwin-amd64.dylib
297+
mv ./build/*arm64.dylib ./site/out/bin/coder-vpn-darwin-arm64.dylib
298+
mv ./build/*arm64.h ./site/out/bin/coder-vpn-darwin-dylib.h
299+
297300
- name: Build binaries
298301
run: |
299302
set -euo pipefail
@@ -310,6 +313,7 @@ jobs:
310313
env:
311314
CODER_SIGN_WINDOWS: "1"
312315
CODER_SIGN_DARWIN: "1"
316+
CODER_WINDOWS_RESOURCES: "1"
313317
AC_CERTIFICATE_FILE: /tmp/apple_cert.p12
314318
AC_CERTIFICATE_PASSWORD_FILE: /tmp/apple_cert_password.txt
315319
AC_APIKEY_ISSUER_ID: ${{ secrets.AC_APIKEY_ISSUER_ID }}

buildinfo/resources/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.syso

buildinfo/resources/resources.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// This package is used for embedding .syso resource files into the binary
2+
// during build and does not contain any code. During build, .syso files will be
3+
// dropped in this directory and then removed after the build completes.
4+
//
5+
// This package must be imported by all binaries for this to work.
6+
//
7+
// See build_go.sh for more details.
8+
package resources

cmd/coder/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
tea "github.com/charmbracelet/bubbletea"
99

1010
"github.com/coder/coder/v2/agent/agentexec"
11+
_ "github.com/coder/coder/v2/buildinfo/resources"
1112
"github.com/coder/coder/v2/cli"
1213
)
1314

enterprise/cmd/coder/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
tea "github.com/charmbracelet/bubbletea"
99

1010
"github.com/coder/coder/v2/agent/agentexec"
11+
_ "github.com/coder/coder/v2/buildinfo/resources"
1112
entcli "github.com/coder/coder/v2/enterprise/cli"
1213
)
1314

scripts/build_go.sh

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,19 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
3636
version=""
3737
os="${GOOS:-linux}"
3838
arch="${GOARCH:-amd64}"
39+
output_path=""
3940
slim="${CODER_SLIM_BUILD:-0}"
41+
agpl="${CODER_BUILD_AGPL:-0}"
4042
sign_darwin="${CODER_SIGN_DARWIN:-0}"
4143
sign_windows="${CODER_SIGN_WINDOWS:-0}"
42-
bin_ident="com.coder.cli"
43-
output_path=""
44-
agpl="${CODER_BUILD_AGPL:-0}"
4544
boringcrypto=${CODER_BUILD_BORINGCRYPTO:-0}
46-
debug=0
4745
dylib=0
46+
windows_resources="${CODER_WINDOWS_RESOURCES:-0}"
47+
debug=0
48+
49+
bin_ident="com.coder.cli"
4850

49-
args="$(getopt -o "" -l version:,os:,arch:,output:,slim,agpl,sign-darwin,boringcrypto,dylib,debug -- "$@")"
51+
args="$(getopt -o "" -l version:,os:,arch:,output:,slim,agpl,sign-darwin,sign-windows,boringcrypto,dylib,windows-resources,debug -- "$@")"
5052
eval set -- "$args"
5153
while true; do
5254
case "$1" in
@@ -79,6 +81,10 @@ while true; do
7981
sign_darwin=1
8082
shift
8183
;;
84+
--sign-windows)
85+
sign_windows=1
86+
shift
87+
;;
8288
--boringcrypto)
8389
boringcrypto=1
8490
shift
@@ -87,6 +93,10 @@ while true; do
8793
dylib=1
8894
shift
8995
;;
96+
--windows-resources)
97+
windows_resources=1
98+
shift
99+
;;
90100
--debug)
91101
debug=1
92102
shift
@@ -115,11 +125,13 @@ if [[ "$sign_darwin" == 1 ]]; then
115125
dependencies rcodesign
116126
requiredenvs AC_CERTIFICATE_FILE AC_CERTIFICATE_PASSWORD_FILE
117127
fi
118-
119128
if [[ "$sign_windows" == 1 ]]; then
120129
dependencies java
121130
requiredenvs JSIGN_PATH EV_KEYSTORE EV_KEY EV_CERTIFICATE_PATH EV_TSA_URL GCLOUD_ACCESS_TOKEN
122131
fi
132+
if [[ "$windows_resources" == 1 ]]; then
133+
dependencies go-winres
134+
fi
123135

124136
ldflags=(
125137
-X "'github.com/coder/coder/v2/buildinfo.tag=$version'"
@@ -204,10 +216,100 @@ if [[ "$boringcrypto" == 1 ]]; then
204216
goexp="boringcrypto"
205217
fi
206218

219+
# On Windows, we use go-winres to embed the resources into the binary.
220+
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
221+
# Convert the version to a format that Windows understands.
222+
# Remove any trailing data after a "+" or "-".
223+
version_windows=$version
224+
version_windows="${version_windows%+*}"
225+
version_windows="${version_windows%-*}"
226+
# If there wasn't any extra data, add a .0 to the version. Otherwise, add
227+
# a .1 to the version to signify that this is not a release build so it can
228+
# be distinguished from a release build.
229+
non_release_build=0
230+
if [[ "$version_windows" == "$version" ]]; then
231+
version_windows+=".0"
232+
else
233+
version_windows+=".1"
234+
non_release_build=1
235+
fi
236+
237+
if [[ ! "$version_windows" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-1]$ ]]; then
238+
error "Computed invalid windows version format: $version_windows"
239+
fi
240+
241+
# File description changes based on slimness, AGPL status, and architecture.
242+
file_description="Coder"
243+
if [[ "$agpl" == 1 ]]; then
244+
file_description+=" AGPL"
245+
fi
246+
if [[ "$slim" == 1 ]]; then
247+
file_description+=" CLI"
248+
fi
249+
if [[ "$non_release_build" == 1 ]]; then
250+
file_description+=" (development build)"
251+
fi
252+
253+
# Because this writes to a file with the OS and arch in the filename, we
254+
# don't support concurrent builds for the same OS and arch (irregardless of
255+
# slimness or AGPL status).
256+
#
257+
# This is fine since we only embed resources during dogfood and release
258+
# builds, which use make (which will build all slim targets in parallel,
259+
# then all non-slim targets in parallel).
260+
expected_rsrc_file="./buildinfo/resources/resources_windows_${arch}.syso"
261+
if [[ -f "$expected_rsrc_file" ]]; then
262+
rm "$expected_rsrc_file"
263+
fi
264+
touch "$expected_rsrc_file"
265+
266+
pushd ./buildinfo/resources
267+
GOARCH="$arch" go-winres simply \
268+
--arch "$arch" \
269+
--out "resources" \
270+
--product-version "$version_windows" \
271+
--file-version "$version_windows" \
272+
--manifest "cli" \
273+
--file-description "$file_description" \
274+
--product-name "Coder" \
275+
--copyright "Copyright $(date +%Y) Coder Technologies Inc." \
276+
--original-filename "coder.exe" \
277+
--icon ../../scripts/win-installer/coder.ico
278+
popd
279+
280+
if [[ ! -f "$expected_rsrc_file" ]]; then
281+
error "Failed to generate $expected_rsrc_file"
282+
fi
283+
fi
284+
285+
set +e
207286
GOEXPERIMENT="$goexp" CGO_ENABLED="$cgo" GOOS="$os" GOARCH="$arch" GOARM="$arm_version" \
208287
go build \
209288
"${build_args[@]}" \
210289
"$cmd_path" 1>&2
290+
exit_code=$?
291+
set -e
292+
293+
# Clean up the resources file if it was generated.
294+
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
295+
rm "$expected_rsrc_file"
296+
fi
297+
298+
if [[ "$exit_code" != 0 ]]; then
299+
exit "$exit_code"
300+
fi
301+
302+
# If we did embed resources, verify that they were included.
303+
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
304+
winres_dir=$(mktemp -d)
305+
if ! go-winres extract --dir "$winres_dir" "$output_path" 1>&2; then
306+
rm -rf "$winres_dir"
307+
error "Compiled binary does not contain embedded resources"
308+
fi
309+
# If go-winres didn't return an error, it means it did find embedded
310+
# resources.
311+
rm -rf "$winres_dir"
312+
fi
211313

212314
if [[ "$sign_darwin" == 1 ]] && [[ "$os" == "darwin" ]]; then
213315
execrelative ./sign_darwin.sh "$output_path" "$bin_ident" 1>&2

0 commit comments

Comments
 (0)