Skip to content

Commit ae45cf7

Browse files
update workflow files, delegate windows signing to custom script
1 parent aa9b10d commit ae45cf7

File tree

4 files changed

+121
-37
lines changed

4 files changed

+121
-37
lines changed

.github/workflows/build.yml

+51-13
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,28 @@ env:
5555
- config:
5656
# Human identifier for the job.
5757
name: Windows
58-
runs-on: windows-2019
58+
runs-on: [self-hosted, windows-sign-pc]
5959
# The value is a string representing a JSON document.
6060
# Setting this to null causes the job to run directly in the runner machine instead of in a container.
6161
container: |
6262
null
6363
# Name of the secret that contains the certificate.
64-
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
64+
certificate-secret: INSTALLER_CERT_WINDOWS_CER
6565
# Name of the secret that contains the certificate password.
66-
certificate-password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
66+
certificate-password-secret: INSTALLER_CERT_WINDOWS_PASSWORD
6767
# File extension for the certificate.
6868
certificate-extension: pfx
69+
# Container for windows cert signing
70+
certificate-container: INSTALLER_CERT_WINDOWS_CONTAINER
6971
# Quoting on the value is required here to allow the same comparison expression syntax to be used for this
7072
# and the companion needs.select-targets.outputs.merge-channel-files property (output values always have string
7173
# type).
7274
mergeable-channel-file: 'false'
75+
# as this runs on a self hosted runner, we need to avoid building with the default working directory path,
76+
# otherwise paths in the build job will be too long for `light.exe`
77+
# we use the below as a Symbolic link (just changing the wd will break the checkout action)
78+
# this is a work around (see: https://github.com/actions/checkout/issues/197).
79+
working-directory: 'C:\a'
7380
artifacts:
7481
- path: '*Windows_64bit.exe'
7582
name: Windows_X86-64_interactive_installer
@@ -270,6 +277,14 @@ jobs:
270277
env:
271278
# Location of artifacts generated by build.
272279
BUILD_ARTIFACTS_PATH: electron-app/dist/build-artifacts
280+
# to skip passing signing credentials to electron-builder
281+
IS_WINDOWS_CONFIG: ${{ matrix.config.name == 'Windows' }}
282+
INSTALLER_CERT_WINDOWS_CER: "/tmp/cert.cer"
283+
# We are hardcoding the path for signtool because is not present on the windows PATH env var by default.
284+
# Keep in mind that this path could change when upgrading to a new runner version
285+
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0/x86/signtool.exe"
286+
WIN_CERT_PASSWORD: ${{ secrets.INSTALLER_CERT_WINDOWS_PASSWORD }}
287+
WIN_CERT_CONTAINER_NAME: ${{ secrets.INSTALLER_CERT_WINDOWS_CONTAINER }}
273288
strategy:
274289
matrix:
275290
config: ${{ fromJson(needs.select-targets.outputs.build-matrix) }}
@@ -283,6 +298,19 @@ jobs:
283298
timeout-minutes: 90
284299

285300
steps:
301+
- name: Pre Clean Up for self-hosted runner
302+
if: runner.os == 'Windows' && matrix.config.working-directory
303+
shell: cmd
304+
run: |
305+
if exist "${{ matrix.config.working-directory }}" rmdir /s /q "${{ matrix.config.working-directory }}"
306+
if exist "C:\actions-runner\_work\arduino-ide\arduino-ide" rmdir /s /q "C:\actions-runner\_work\arduino-ide\arduino-ide"
307+
308+
- name: Symlink custom working directory
309+
shell: cmd
310+
if: runner.os == 'Windows' && matrix.config.working-directory
311+
run: |
312+
mklink /d "${{ matrix.config.working-directory }}" "C:\actions-runner\_work\arduino-ide\arduino-ide"
313+
286314
- name: Checkout
287315
if: fromJSON(matrix.config.container) == null
288316
uses: actions/checkout@v4
@@ -293,42 +321,42 @@ jobs:
293321
uses: actions/checkout@v3
294322

295323
- name: Install Node.js
296-
if: fromJSON(matrix.config.container) == null
324+
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
297325
uses: actions/setup-node@v4
298326
with:
299327
node-version: ${{ env.NODE_VERSION }}
300328
registry-url: 'https://registry.npmjs.org'
301329
cache: 'yarn'
302330

303331
- name: Install Python 3.x
304-
if: fromJSON(matrix.config.container) == null
332+
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
305333
uses: actions/setup-python@v5
306334
with:
307335
python-version: '3.11.x'
308336

309337
- name: Install Go
310-
if: fromJSON(matrix.config.container) == null
338+
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
311339
uses: actions/setup-go@v5
312340
with:
313341
go-version: ${{ env.GO_VERSION }}
314342

315343
- name: Install Go
316344
# actions/setup-go@v5 has dependency on a higher version of glibc than available in the Linux container.
317-
if: fromJSON(matrix.config.container) != null
345+
if: fromJSON(matrix.config.container) != null && runner.os != 'Windows'
318346
uses: actions/setup-go@v4
319347
with:
320348
go-version: ${{ env.GO_VERSION }}
321349

322350
- name: Install Taskfile
323-
if: fromJSON(matrix.config.container) == null
351+
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
324352
uses: arduino/setup-task@v2
325353
with:
326354
repo-token: ${{ secrets.GITHUB_TOKEN }}
327355
version: 3.x
328356

329357
- name: Install Taskfile
330358
# actions/setup-task@v2 has dependency on a higher version of glibc than available in the Linux container.
331-
if: fromJSON(matrix.config.container) != null
359+
if: fromJSON(matrix.config.container) != null && runner.os != 'Windows'
332360
uses: arduino/setup-task@v1
333361
with:
334362
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -350,9 +378,10 @@ jobs:
350378
CREATE_USERNAME: ${{ secrets.CREATE_USERNAME }}
351379
CREATE_PASSWORD: ${{ secrets.CREATE_PASSWORD }}
352380
CREATE_CLIENT_SECRET: ${{ secrets.CREATE_CLIENT_SECRET }}
381+
working-directory: ${{ runner.os == 'Windows' && matrix.config.working-directory || './' }}
353382
run: |
354383
# See: https://www.electron.build/code-signing
355-
if [ $CAN_SIGN = false ]; then
384+
if [ $CAN_SIGN = false ] || [ $IS_WINDOWS_CONFIG = true ]; then
356385
echo "Skipping the app signing: certificate not provided."
357386
else
358387
export CSC_LINK="${{ runner.temp }}/signing_certificate.${{ matrix.config.certificate-extension }}"
@@ -372,13 +401,14 @@ jobs:
372401
yarn --cwd electron-app rebuild
373402
yarn --cwd electron-app build
374403
yarn --cwd electron-app package
375-
404+
376405
# Both macOS jobs generate a "channel update info file" with same path and name. The second job to complete would
377406
# overwrite the file generated by the first in the workflow artifact.
378407
- name: Stage channel file for merge
379408
if: >
380409
needs.select-targets.outputs.merge-channel-files == 'true' &&
381410
matrix.config.mergeable-channel-file == 'true'
411+
working-directory: ${{ runner.os == 'Windows' && matrix.config.working-directory || './' }}
382412
run: |
383413
staged_channel_files_path="${{ runner.temp }}/staged-channel-files"
384414
mkdir "$staged_channel_files_path"
@@ -398,13 +428,21 @@ jobs:
398428
with:
399429
if-no-files-found: error
400430
name: ${{ env.STAGED_CHANNEL_FILES_ARTIFACT }}
401-
path: ${{ env.STAGED_CHANNEL_FILES_PATH }}
431+
path: ${{ runner.os == 'Windows' && matrix.config.working-directory && format('{0}/{1}', matrix.config.working-directory, env.STAGED_CHANNEL_FILES_PATH) || env.STAGED_CHANNEL_FILES_PATH }}
432+
402433

403434
- name: Upload [GitHub Actions]
404435
uses: actions/upload-artifact@v3
405436
with:
406437
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
407-
path: ${{ env.BUILD_ARTIFACTS_PATH }}
438+
path: ${{ runner.os == 'Windows' && matrix.config.working-directory && format('{0}/{1}', matrix.config.working-directory, env.BUILD_ARTIFACTS_PATH) || env.BUILD_ARTIFACTS_PATH }}
439+
440+
- name: Manual Clean up for self-hosted runners
441+
if: runner.os == 'Windows' && matrix.config.working-directory
442+
shell: cmd
443+
run: |
444+
rmdir /s /q "${{ matrix.config.working-directory }}"
445+
rmdir /s /q "C:\actions-runner\_work\arduino-ide\arduino-ide"
408446
409447
merge-channel-files:
410448
needs:

.github/workflows/check-certificates.yml

+42-23
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ jobs:
7474
- identifier: macOS signing certificate # Text used to identify certificate in notifications.
7575
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12 # Name of the secret that contains the certificate.
7676
password-secret: KEYCHAIN_PASSWORD # Name of the secret that contains the certificate password.
77+
type: pkcs12
7778
- identifier: Windows signing certificate
78-
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
79-
password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
79+
certificate-secret: INSTALLER_CERT_WINDOWS_CER
80+
# The password for the Windows certificate is not needed, because its not a container, but a single certificate.
81+
type: x509
8082

8183
steps:
8284
- name: Set certificate path environment variable
@@ -95,7 +97,7 @@ jobs:
9597
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
9698
run: |
9799
(
98-
openssl pkcs12 \
100+
openssl ${{ matrix.certificate.type }} \
99101
-in "${{ env.CERTIFICATE_PATH }}" \
100102
-legacy \
101103
-noout \
@@ -122,26 +124,43 @@ jobs:
122124
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
123125
id: get-days-before-expiration
124126
run: |
125-
EXPIRATION_DATE="$(
126-
(
127-
openssl pkcs12 \
128-
-in "${{ env.CERTIFICATE_PATH }}" \
129-
-clcerts \
130-
-legacy \
131-
-nodes \
132-
-passin env:CERTIFICATE_PASSWORD
133-
) | (
134-
openssl x509 \
135-
-noout \
136-
-enddate
137-
) | (
138-
grep \
139-
--max-count=1 \
140-
--only-matching \
141-
--perl-regexp \
142-
'notAfter=(\K.*)'
143-
)
144-
)"
127+
if [[ ${{ matrix.certificate.type }} == "pkcs12" ]]; then
128+
EXPIRATION_DATE="$(
129+
(
130+
openssl pkcs12 \
131+
-in "${{ env.CERTIFICATE_PATH }}" \
132+
-clcerts \
133+
-legacy \
134+
-nodes \
135+
-passin env:CERTIFICATE_PASSWORD
136+
) | (
137+
openssl x509 \
138+
-noout \
139+
-enddate
140+
) | (
141+
grep \
142+
--max-count=1 \
143+
--only-matching \
144+
--perl-regexp \
145+
'notAfter=(\K.*)'
146+
)
147+
)"
148+
elif [[ ${{ matrix.certificate.type }} == "x509" ]]; then
149+
EXPIRATION_DATE="$(
150+
(
151+
openssl x509 \
152+
-in ${{ env.CERTIFICATE_PATH }} \
153+
-noout \
154+
-enddate
155+
) | (
156+
grep \
157+
--max-count=1 \
158+
--only-matching \
159+
--perl-regexp \
160+
'notAfter=(\K.*)'
161+
)
162+
)"
163+
fi
145164
146165
DAYS_BEFORE_EXPIRATION="$((($(date --utc --date="$EXPIRATION_DATE" +%s) - $(date --utc +%s)) / 60 / 60 / 24))"
147166

electron-app/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@
133133
"msi",
134134
"nsis",
135135
"zip"
136-
]
136+
],
137+
"sign": "./scripts/windowsCustomSign.js"
137138
},
138139
"mac": {
139140
"darkModeSupport": true,
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const childProcess = require('child_process');
2+
3+
exports.default = async function (configuration) {
4+
const SIGNTOOL_PATH = process.env.SIGNTOOL_PATH;
5+
const INSTALLER_CERT_WINDOWS_CER = process.env.INSTALLER_CERT_WINDOWS_CER;
6+
const CERT_PASSWORD = process.env.WIN_CERT_PASSWORD;
7+
const CONTAINER_NAME = process.env.WIN_CERT_CONTAINER_NAME;
8+
const filePath = configuration.path;
9+
10+
if (
11+
SIGNTOOL_PATH &&
12+
INSTALLER_CERT_WINDOWS_CER &&
13+
CERT_PASSWORD &&
14+
CONTAINER_NAME
15+
) {
16+
childProcess.execSync(
17+
`"${SIGNTOOL_PATH}" sign -d "Arduino IDE" -f "${INSTALLER_CERT_WINDOWS_CER}" -csp "eToken Base Cryptographic Provider" -k "[{{${CERT_PASSWORD}}}]=${CONTAINER_NAME}" -fd sha256 -tr http://timestamp.digicert.com -td SHA256 -v "${filePath}"`,
18+
{ stdio: 'inherit' }
19+
);
20+
} else {
21+
console.warn(
22+
`Custom windows signing was no performed one of the following variables was not provided: SIGNTOOL_PATH (${SIGNTOOL_PATH}), INSTALLER_CERT_WINDOWS_CERT (${INSTALLER_CERT_WINDOWS_CER}), CERT_PASSWORD (${CERT_PASSWORD}), CONTAINER_NAME (${CONTAINER_NAME})`
23+
);
24+
process.exit(1);
25+
}
26+
};

0 commit comments

Comments
 (0)