Repository navigation
1070 lines (960 loc) · 49.9 KB
/
Copy pathrelease.yml
File metadata and controls
1070 lines (960 loc) · 49.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
name: Release
# Run "Cut a release" (cut-release.yml) under Actions, or push a tag such as v0.2.0 as the repository owner, to publish
# the NuGet packages (library, ASP.NET Core integration and the `semantic-router` dotnet tool), the container image
# (ghcr.io/<owner>/semantic-router) and the desktop app (self-contained downloads for Windows, macOS and Linux, and the
# installers with their update feeds, attached to the GitHub release). Only the repository owner can release.
#
# Every release can be verified (docs/releases.md): the image is signed with keyless cosign and carries provenance and
# SBOM attestations; every release asset, the NuGet packages included, has a build provenance attestation and a line in
# SHA256SUMS; CycloneDX SBOMs (the packages, the image, and each platform's archive and installer, each naming what it
# describes and every package's licence) and THIRD-PARTY-NOTICES.txt are attached. Signatures and attestations use
# short-lived Sigstore certificates issued to this workflow (GitHub OIDC), so no signing key is stored anywhere. The
# jobs that hold the OIDC token build nothing; they sign and attest what the build jobs produced, after any code
# signing.
#
# Nothing is public until everything is ready (docs/releases.md#the-order-of-a-release). Everything is built, signed,
# checked and attached to a draft GitHub release first, which nobody but the maintainers sees and which the desktop
# app's updater does not read. SHA256SUMS and the provenance attestation are added to the draft too. Only then, one
# after another, the image is pushed and signed, the NuGet packages are pushed, and the draft is published. So a
# failure in a build, installer or signing job leaves a draft (and, after checksums, its public attestation) and nothing
# else: no package on nuget.org, no new image tag and no public release. deploy/release-order-check.sh, which CI runs,
# keeps this order.
#
# A tag with a suffix (v1.0.0-rc.1) is a prerelease: the GitHub release is marked as one, so the desktop app's updater
# (which skips prereleases) never offers it, and the image gets its version tag but not :latest. NuGet treats the
# suffix as a prerelease by itself. "Cut a release" makes stable releases only; push such a tag by hand for a candidate.
#
# The Windows and macOS installers must be code-signed: a release fails at its first job when a platform's signing
# secrets are missing, unless the variable ALLOW_UNSIGNED_RELEASE is `true` (docs/releases.md#code-signing). "Cut a
# release" also asks for its "Allow unsigned installers" box to be ticked before it tags such a release.
on:
push:
tags: [ 'v*' ]
# Read-only by default; each job that publishes, signs or attests asks for the extra permissions it needs (id-token only
# in nuget, image-sign, installer-windows and checksums, and attestations only in image-sign and checksums, all jobs that
# build nothing). Each secret, the workflow's token included, goes to the steps that use it rather than to a whole job,
# and no job that builds holds one (deploy/workflow-secrets-check.sh, which CI runs). Every publishing job runs in the
# `release` environment, so the repository owner can require an approval under Settings > Environments (and restrict it
# to `main` and v* tags: cut-release.yml runs there from main). Publishing to nuget.org needs the `NUGET_USER` variable
# there (see the nuget job). installer-windows runs in an environment of its own, `windows-signing`, because the OIDC
# token of a job there is what Azure accepts for signing: the other id-token jobs, in `release`, cannot sign
# (docs/desktop.md#signing-in-to-azure).
permissions:
contents: read
jobs:
packages:
runs-on: ubuntu-latest
environment: release
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
outputs:
# signed or unsigned, per platform; the installer jobs sign and check signatures by these.
windows-signing: ${{ steps.signing.outputs.windows }}
macos-signing: ${{ steps.signing.outputs.macos }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# Only the repository owner releases: a v* tag pushed by anyone else publishes nothing, since every other job needs
# this one. The "Cut a release" workflow (cut-release.yml) creates the tag with the owner's token, so its tag is the
# owner's too.
- name: The repository owner released it
env:
ACTOR: ${{ github.actor }}
TRIGGERING_ACTOR: ${{ github.triggering_actor }}
OWNER: ${{ github.repository_owner }}
run: |
if [ "$ACTOR" != "$OWNER" ] || [ "$TRIGGERING_ACTOR" != "$OWNER" ]; then
echo "::error::Only $OWNER can release: $GITHUB_REF_NAME was pushed by $ACTOR (triggered by $TRIGGERING_ACTOR)."
exit 1
fi
# Releases are built from reviewed code only: a tag on a commit that is not on main publishes nothing, since every
# other job needs this one.
- name: The tag is on main
run: |
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
echo "::error::$GITHUB_REF_NAME points at $GITHUB_SHA, which is not on main. Tag a commit on main."
exit 1
fi
# Code signing is required, and checked here so that a release without it publishes nothing (the script says how
# each platform's state is decided; cut-release.yml runs the same check before it tags). Only whether each secret
# is set reaches this job, as true or false, never its value.
- name: Code signing
id: signing
env:
ALLOW_UNSIGNED_RELEASE: ${{ vars.ALLOW_UNSIGNED_RELEASE }}
WINDOWS: >-
AZURE_TENANT_ID=${{ secrets.AZURE_TENANT_ID != '' }}
AZURE_CLIENT_ID=${{ secrets.AZURE_CLIENT_ID != '' }}
AZURE_SIGNING_ENDPOINT=${{ vars.AZURE_SIGNING_ENDPOINT != '' }}
AZURE_SIGNING_ACCOUNT=${{ vars.AZURE_SIGNING_ACCOUNT != '' }}
AZURE_SIGNING_PROFILE=${{ vars.AZURE_SIGNING_PROFILE != '' }}
MACOS: >-
APPLE_BUILD_CERTIFICATE_BASE64=${{ secrets.APPLE_BUILD_CERTIFICATE_BASE64 != '' }}
APPLE_INSTALLER_CERTIFICATE_BASE64=${{ secrets.APPLE_INSTALLER_CERTIFICATE_BASE64 != '' }}
APPLE_P12_PASSWORD=${{ secrets.APPLE_P12_PASSWORD != '' }}
APPLE_ID=${{ secrets.APPLE_ID != '' }}
APPLE_APP_PASSWORD=${{ secrets.APPLE_APP_PASSWORD != '' }}
APPLE_TEAM_ID=${{ secrets.APPLE_TEAM_ID != '' }}
APPLE_SIGN_APP_IDENTITY=${{ vars.APPLE_SIGN_APP_IDENTITY != '' }}
APPLE_SIGN_INSTALL_IDENTITY=${{ vars.APPLE_SIGN_INSTALL_IDENTITY != '' }}
run: deploy/release-signing-check.sh
# The installed apps check each update against its attestation from the checksums job below, with the Sigstore
# trust root they carry: this release must attest its update packages that way, and carry Sigstore's current
# trust root so that it can check the releases after it (docs/releases.md#update-attestations).
- name: Update attestation
run: deploy/update-attestation-check.sh --online
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Version from the tag
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
# Exactly the packages in the projects' packages.lock.json files; the restores in test and pack below then find
# nothing to change.
- name: Restore
run: dotnet restore --locked-mode
- name: Test
run: dotnet test --configuration Release
# The vpk the installer jobs install is the Velopack package's version, whose native binaries and Rust crates the
# desktop notices list.
- name: Velopack versions
run: deploy/velopack-version-check.sh
# The tool package carries the licences and notices of the packages it ships, and of the fonts.
- name: Third-party notices
run: |
dotnet run --file deploy/third-party-notices.cs -- notices/THIRD-PARTY-NOTICES.txt src/SemanticRouter.Host/bin/Release/net10.0
deploy/third-party-notices-check.sh notices/THIRD-PARTY-NOTICES.txt "Inter (font)" "Fira Sans (font)"
- name: Pack
run: |
dotnet pack --configuration Release --output artifacts -p:Version="$VERSION" \
-p:ThirdPartyNoticesFile="$PWD/notices/THIRD-PARTY-NOTICES.txt"
# Each package has its readme, icon and copyright for nuget.org, and each library its symbol package (.snupkg),
# whose PDB SourceLink maps to this commit's sources.
- name: Package contents
run: deploy/package-contents-check.sh artifacts
# A CycloneDX SBOM of the NuGet packages, read from their contents: the tool's deps.json and each package's
# assemblies. deploy/sbom-complete.cs then names the release as its subject, gives every package its licence (from
# the nuspecs restore put in the NuGet cache) and adds the fonts, which Syft cannot see (docs/releases.md).
- name: SBOM input
run: |
for package in artifacts/*.nupkg; do
name="$(basename "$package" .nupkg)"
mkdir -p "sbom/$name" && unzip -q "$package" -d "sbom/$name"
done
- uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
with:
path: sbom
format: cyclonedx-json
output-file: SemanticRouter-${{ env.VERSION }}-packages.cdx.json
upload-artifact: false
upload-release-assets: false
- name: Complete the SBOM
run: |
sbom="SemanticRouter-$VERSION-packages.cdx.json"
dotnet run --file deploy/sbom-complete.cs -- "$sbom" --type library --name SemanticRouter.NET --version "$VERSION" \
--purl "pkg:github/${GITHUB_REPOSITORY,,}@v$VERSION" \
--description "The SemanticRouter NuGet packages and the semantic-router tool" --contents sbom
deploy/sbom-check.sh "$sbom" SemanticRouter.NET "$VERSION" "Inter (font)" "Fira Sans (font)"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: packages
path: |
artifacts/*.nupkg
artifacts/*.snupkg
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: sbom-packages
path: SemanticRouter-${{ env.VERSION }}-packages.cdx.json
# NuGet Trusted Publishing: no NuGet key is stored anywhere. This job exchanges its GitHub OIDC token for an API key
# that nuget.org issues for one hour, under the policy on the nuget.org account (repository, release.yml, environment
# `release`). It runs nothing but the push, so restore, build and test code in the job above never sees the token.
# A release fails here rather than skipping the push: the README tells people to install from nuget.org. A version on
# nuget.org can never be used again, so the push comes after everything else is signed, attached and checked and the
# image is out; only publishing the draft release comes after it.
nuget:
needs: [ packages, image-sign ]
runs-on: ubuntu-latest
environment: release
permissions:
contents: read
id-token: write
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
NUGET_USER: ${{ vars.NUGET_USER }}
steps:
- name: Check the nuget.org account is set
run: |
if [ -z "$NUGET_USER" ]; then
echo "::error::Set the NUGET_USER variable (the nuget.org profile name that owns the Trusted Publishing policy) under Settings > Environments > release."
exit 1
fi
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: packages
path: artifacts
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 10.0.x
- name: NuGet login
id: login
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
with:
user: ${{ vars.NUGET_USER }}
# Pushing a .nupkg also pushes the .snupkg of the same name beside it, to nuget.org's symbol server.
- name: Push to NuGet
env:
NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY }}
run: dotnet nuget push "artifacts/*.nupkg" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate
# The image is built, scanned and pushed in three jobs, so that neither the build nor the scanners (Trivy and Syft,
# which download their binaries and Trivy's vulnerability database at run time) run next to a token that can push or
# next to the docker daemon the push uses. The build only reads the repository, the scan holds no permission at all,
# and the push runs no tool but docker: it loads the build's tarball, checks that it is the image the build made, and
# checks that the registry holds that same image afterwards. So what is signed and attested is what was built and
# scanned.
image-build:
needs: packages
runs-on: ubuntu-latest
outputs:
image: ${{ steps.build.outputs.image }}
version: ${{ steps.build.outputs.version }}
id: ${{ steps.save.outputs.id }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# The tag's version and the commit go into the assemblies (what the router reports) and the OCI labels.
- name: Build
id: build
run: |
image="ghcr.io/$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')/semantic-router"
version="${GITHUB_REF_NAME#v}"
echo "IMAGE=$image" >> "$GITHUB_ENV"
echo "VERSION=$version" >> "$GITHUB_ENV"
echo "image=$image" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
docker build -f src/SemanticRouter.Host/Dockerfile \
--build-arg VERSION="$version" --build-arg REVISION="$GITHUB_SHA" \
-t "$image:$version" -t "$image:latest" .
# Nothing is published that reports another version than its tag: the router, the dashboard and the labels.
- name: Check the version
run: deploy/image-version-check.sh "$IMAGE:latest" "$VERSION" "$GITHUB_SHA"
- name: Save
id: save
run: |
echo "id=$(docker image inspect "$IMAGE:$VERSION" --format '{{.Id}}')" >> "$GITHUB_OUTPUT"
docker save "$IMAGE:$VERSION" "$IMAGE:latest" -o image.tar
# Kept for a week, like the installers' full packages below: the push waits for the installers, and a failed
# release is finished by re-running its failed jobs, which reuse these (docs/releases.md#when-a-release-fails).
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: image
path: image.tar
if-no-files-found: error
retention-days: 7
# No permission, so the job's token can do nothing, and no cache, so a cache entry written elsewhere cannot change
# the scanner or its database.
image-scan:
needs: image-build
runs-on: ubuntu-latest
permissions: {}
env:
IMAGE: ${{ needs.image-build.outputs.image }}
VERSION: ${{ needs.image-build.outputs.version }}
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: image
- name: Load
run: docker load -i image.tar
# Nothing with a fixable high or critical vulnerability is published.
- name: Scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ env.IMAGE }}:latest
scanners: vuln
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: '1'
cache: 'false'
# The image's CycloneDX SBOM: completed in image-sbom, then attached to the release, and to the image as an
# attestation (image-sign).
- uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
with:
image: ${{ env.IMAGE }}:latest
format: cyclonedx-json
output-file: SemanticRouter-${{ env.VERSION }}-image.cdx.json
upload-artifact: false
upload-release-assets: false
# Scanners and inventories match the router by the version its SBOM names.
- name: Check the SBOM's version
run: |
sbom="SemanticRouter-$VERSION-image.cdx.json"
versions="$(jq -r '[.components[]? | select(.name == "semantic-router") | .version] | unique | join(" ")' "$sbom")"
if [ "$versions" != "$VERSION" ]; then
echo "::error::$sbom names semantic-router '${versions:-nothing}', expected '$VERSION'."
exit 1
fi
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: image-syft
path: SemanticRouter-${{ env.VERSION }}-image.cdx.json
retention-days: 7
# The image's SBOM gets its subject (the image and its version), its NuGet packages' licences from a restore of the
# tool's packages, and the fonts, as the tool package's deps.json says (the image is the tool's build;
# deploy/sbom-complete.cs). Here rather than in image-scan, which holds no permission and checks nothing out; before
# the push, so no image is published without its completed SBOM.
image-sbom:
needs: [ image-build, image-scan ]
runs-on: ubuntu-latest
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
IMAGE: ${{ needs.image-build.outputs.image }}
VERSION: ${{ needs.image-build.outputs.version }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: image-syft
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: packages
path: packages
- name: Complete the SBOM
run: |
dotnet restore src/SemanticRouter.Host/SemanticRouter.Host.csproj --locked-mode
mkdir -p tool && unzip -q packages/SemanticRouter.Host.*.nupkg -d tool
sbom="SemanticRouter-$VERSION-image.cdx.json"
dotnet run --file deploy/sbom-complete.cs -- "$sbom" --type container --name "$IMAGE" --version "$VERSION" \
--purl "pkg:docker/${IMAGE#ghcr.io/}@$VERSION?repository_url=ghcr.io" --description "The semantic-router container image" \
--contents tool
deploy/sbom-check.sh "$sbom" "$IMAGE" "$VERSION" "Inter (font)" "Fira Sans (font)"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: sbom-image
path: SemanticRouter-${{ env.VERSION }}-image.cdx.json
# The image is pushed, as :VERSION and :latest, only once the draft release is complete: every installer signed and
# checked, SHA256SUMS and the attestation attached (checksums). A failed installer or signing job leaves no new tag.
image:
needs: [ image-build, image-scan, image-sbom, checksums ]
runs-on: ubuntu-latest
environment: release
permissions:
packages: write
env:
IMAGE: ${{ needs.image-build.outputs.image }}
VERSION: ${{ needs.image-build.outputs.version }}
BUILT: ${{ needs.image-build.outputs.id }}
outputs:
image: ${{ steps.push.outputs.image }}
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: image
- name: Load
run: |
docker load -i image.tar
for tag in "$VERSION" latest; do
id="$(docker image inspect "$IMAGE:$tag" --format '{{.Id}}')"
if [ "$id" != "$BUILT" ]; then
echo "::error::$IMAGE:$tag is $id, but the build made $BUILT."
exit 1
fi
done
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# The digest is read back from the registry: what the version tag points to is what is signed and attested, and
# it must be the image the build made.
- name: Push
id: push
run: |
docker push "$IMAGE:$VERSION"
# A prerelease (a version with a suffix, 1.0.0-rc.1) is not what :latest users should get.
if [[ "$VERSION" != *-* ]]; then
docker push "$IMAGE:latest"
fi
digest="$(docker buildx imagetools inspect "$IMAGE:$VERSION" --format '{{json .Manifest}}' | jq -r .digest)"
if [[ "$digest" != sha256:* ]]; then
echo "::error::No digest for $IMAGE:$VERSION in the registry."
exit 1
fi
docker pull -q "$IMAGE@$digest" >/dev/null
id="$(docker image inspect "$IMAGE@$digest" --format '{{.Id}}')"
if [ "$id" != "$BUILT" ]; then
echo "::error::The registry's $IMAGE@$digest is $id, but the build made $BUILT."
exit 1
fi
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
echo "digest=$digest" >> "$GITHUB_OUTPUT"
# Keyless signing of the pushed image, then its provenance and SBOM attestations, stored in the registry next to it.
# cosign and actions/attest get a short-lived Sigstore certificate for this workflow from the job's GitHub OIDC token.
# This job builds and runs nothing from the repository; the image jobs never hold the OIDC token.
image-sign:
needs: image
runs-on: ubuntu-latest
environment: release
permissions:
contents: read
packages: write
id-token: write
attestations: write
env:
IMAGE: ${{ needs.image.outputs.image }}
DIGEST: ${{ needs.image.outputs.digest }}
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: sbom-image
path: sbom
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Sign
run: cosign sign --yes "$IMAGE@$DIGEST"
- name: Version from the tag
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
# Storage records need an organization account and artifact-metadata: write; the attestations do not.
- name: Provenance
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ env.DIGEST }}
push-to-registry: true
create-storage-record: false
- name: SBOM attestation
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ env.DIGEST }}
sbom-path: sbom/SemanticRouter-${{ env.VERSION }}-image.cdx.json
push-to-registry: true
create-storage-record: false
# The desktop app is built in jobs that hold no secret and only read the repository; the jobs that sign, publish or
# write to the release download what they built and run no build. So no third-party build code (NuGet packages'
# MSBuild tasks included) ever runs next to a signing credential or a token that can change the release.
desktop-build:
needs: packages
runs-on: ubuntu-latest
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Version from the tag
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
# Every platform cross-publishes from Linux: the app is self-contained and its native parts come from NuGet.
- name: Package
run: |
for rid in win-x64 win-arm64 osx-arm64 osx-x64 linux-x64 linux-arm64; do
deploy/package-desktop.sh "$rid" "$VERSION" artifacts
done
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: packages
path: packages
# The release's own THIRD-PARTY-NOTICES.txt covers all it ships: the desktop app for every platform and the tool
# (whose packages are also the image's). Each archive, installer, the tool package and the image carry their own
# copy. The SBOMs are read from the archives' contents.
- name: Notices and SBOM input
run: |
for archive in artifacts/*.zip artifacts/*.tar.gz; do
name="$(basename "$archive")" && name="${name%.zip}" && name="${name%.tar.gz}"
mkdir -p "sbom/$name"
case "$archive" in
*.zip) unzip -q "$archive" -d "sbom/$name" ;;
*) tar -C "sbom/$name" -xzf "$archive" ;;
esac
done
mkdir -p tool && unzip -q packages/SemanticRouter.Host.*.nupkg -d tool
dotnet restore src/SemanticRouter.Host/SemanticRouter.Host.csproj --locked-mode
dotnet run --file deploy/third-party-notices.cs -- artifacts/THIRD-PARTY-NOTICES.txt sbom tool
deploy/third-party-notices-check.sh artifacts/THIRD-PARTY-NOTICES.txt "Inter (font)" "Fira Sans (font)" \
"Velopack native binaries" "Rust standard library" "AppImage type2-runtime"
- uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
id: syft
# One SBOM per platform, SemanticRouter-<version>-<rid>.cdx.json, named after the archive it describes and
# completed with its packages' licences and the fonts (deploy/sbom-complete.cs). The installers' are made later,
# from what vpk built (installer-sbom).
- name: SBOMs
env:
SYFT: ${{ steps.syft.outputs.cmd }}
run: |
for archive in artifacts/*.zip artifacts/*.tar.gz; do
file="$(basename "$archive")" && name="${file%.zip}" && name="${name%.tar.gz}" && rid="${name#"SemanticRouter-$VERSION-"}"
sbom="artifacts/$name.cdx.json"
"$SYFT" scan "dir:sbom/$name" -q -o "cyclonedx-json=$sbom"
dotnet run --file deploy/sbom-complete.cs -- "$sbom" --type application --name "SemanticRouter-$rid" --version "$VERSION" \
--purl "pkg:generic/SemanticRouter-$rid@$VERSION?download_url=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/download/$GITHUB_REF_NAME/$file" \
--description "The desktop app for $rid, as in $file" --contents "sbom/$name"
deploy/sbom-check.sh "$sbom" "SemanticRouter-$rid" "$VERSION" "Inter (font)" "Fira Sans (font)"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: desktop
path: artifacts/*
desktop:
needs: desktop-build
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: desktop
path: artifacts
# The release notes are the version's section of CHANGELOG.md at the tag (or its Unreleased section, when that was
# not renamed before the release), followed by GitHub's list of merged pull requests.
- name: Release notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version="${GITHUB_REF_NAME#v}"
gh api -H "Accept: application/vnd.github.raw" "repos/$GITHUB_REPOSITORY/contents/CHANGELOG.md?ref=$GITHUB_REF_NAME" > CHANGELOG.md \
|| : > CHANGELOG.md
section() {
awk -v heading="## [$1]" 'index($0, heading) == 1 { found = 1; next } found && /^## \[/ { exit } found' CHANGELOG.md
}
notes="$(section "$version")"
if [ -z "$(tr -d '[:space:]' <<<"$notes")" ]; then
notes="$(section Unreleased)"
fi
printf '%s\n' "$notes" > notes.md
# The release is created as a draft, which only the publish job makes public, once everything else is done. A
# version with a suffix (1.0.0-rc.1) is created as a prerelease: the desktop app's updater skips those. On a
# re-run, gh finds the draft by its tag and the archives replace the ones there.
- name: Attach to the draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
prerelease=()
if [[ "$GITHUB_REF_NAME" == *-* ]]; then
prerelease=(--prerelease)
fi
gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 \
|| gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" \
--notes-file notes.md --generate-notes --verify-tag --draft "${prerelease[@]}"
gh release upload "$GITHUB_REF_NAME" artifacts/* --repo "$GITHUB_REPOSITORY" --clobber
# The app each installer packages, published on the installer's own OS (as before the split), without secrets. Tar
# keeps the executable bits that an artifact would lose.
installer-build:
needs: packages
strategy:
matrix:
include:
- { rid: linux-x64, os: ubuntu-latest }
- { rid: win-x64, os: windows-latest }
- { rid: osx-arm64, os: macos-latest }
- { rid: osx-x64, os: macos-latest }
runs-on: ${{ matrix.os }}
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
RID: ${{ matrix.rid }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Publish
shell: bash
run: |
deploy/publish-desktop.sh "$RID" "${GITHUB_REF_NAME#v}" "publish/$RID"
tar -C "publish/$RID" -czf "publish-$RID.tar.gz" .
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: publish-${{ matrix.rid }}
path: publish-${{ matrix.rid }}.tar.gz
# The installers (Velopack): each platform is a channel with its own update feed; the previous release's package is
# fetched for a delta update. These jobs only pack, sign and upload what installer-build published (PUBLISH_DIR);
# vpk, the packaging and signing tool itself, is the one tool installed. The Windows and macOS jobs sign when the
# packages job found their platform's signing set up, and then check the signature before anything is uploaded
# (docs/desktop.md#signing). vpk uploads into the draft release (--merge) and leaves it a draft: without --publish,
# the update feeds are public only when the publish job publishes the release. Each secret goes to the steps that use
# it, not to the job (deploy/workflow-secrets-check.sh): the token to the steps that fetch the previous release and
# upload, the signing credentials to the signing setup. vpk is installed before either, so its restore never runs
# next to them. Windows signs with an Azure login by OIDC, so no Azure secret is stored; but id-token: write is
# job-wide, so every step of installer-windows, the vpk install included, could ask for the token that signs. That
# job therefore runs only vpk, pinned actions and this repository's scripts, in the windows-signing environment that
# no other job uses, and logs out of Azure as soon as it has signed.
installers:
needs: [ desktop, installer-build ]
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
VPK_PREVIOUS_FROM: ${{ github.server_url }}/${{ github.repository }}
# A version with a suffix (1.0.0-rc.1) goes into a prerelease, which the desktop app's updater does not take.
# Not VPK_PRE: vpk reads VPK_<option> variables as its own options, and an empty VPK_PRE is not a boolean.
RELEASE_PRE: ${{ contains(github.ref_name, '-') && '--pre' || '' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: publish-linux-x64
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Install vpk and the AppImage tools
run: |
sudo apt-get install -y --no-install-recommends squashfs-tools zstd
dotnet tool install -g vpk --version 1.2.161
# The token fetches the previous release's package (for the delta) and uploads.
- name: Package and attach
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p published && tar -C published -xzf publish-linux-x64.tar.gz
PUBLISH_DIR=published deploy/package-installer.sh linux-x64 "${GITHUB_REF_NAME#v}" installers/linux-x64
vpk upload github --outputDir installers/linux-x64 --channel linux-x64 \
--repoUrl "$VPK_PREVIOUS_FROM" --token "$GITHUB_TOKEN" --tag "$GITHUB_REF_NAME" --merge $RELEASE_PRE
# For installer-sbom (with the previous release's full package, when one was downloaded for the delta).
# Kept 7 days, so a failed installer-sbom can be re-run after the day the release was cut.
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: installer-linux-x64
path: installers/linux-x64/*-full.nupkg
if-no-files-found: error
retention-days: 7
installer-windows:
needs: [ packages, desktop, installer-build ]
# Not `release`: the federated credential trusts this environment's OIDC token only (docs/desktop.md#signing-in-to-azure).
environment: windows-signing
runs-on: windows-latest
permissions:
contents: write
# For the Azure login: the job's GitHub OIDC token, which Azure exchanges for a short-lived access token under the
# app registration's federated credential (docs/desktop.md#signing). Every step of the job can ask for it.
id-token: write
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
VPK_PREVIOUS_FROM: ${{ github.server_url }}/${{ github.repository }}
# A version with a suffix (1.0.0-rc.1) goes into a prerelease, which the desktop app's updater does not take.
# Not VPK_PRE: vpk reads VPK_<option> variables as its own options, and an empty VPK_PRE is not a boolean.
RELEASE_PRE: ${{ contains(github.ref_name, '-') && '--pre' || '' }}
SIGNING: ${{ needs.packages.outputs.windows-signing }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: publish-win-x64
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Install vpk
shell: bash
run: |
dotnet tool install -g vpk --version 1.2.161
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
# The signing setup and the signature check are scripts that the signing dry run (signing-dry-run.yml) runs too.
# Azure Trusted Signing: the app registration that may sign (its ids are secrets) and the account's endpoint, name
# and certificate profile (variables). The setup names any that are missing.
- name: Signing metadata
if: env.SIGNING == 'signed'
shell: bash
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
SIGNING_ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
SIGNING_ACCOUNT: ${{ vars.AZURE_SIGNING_ACCOUNT }}
SIGNING_PROFILE: ${{ vars.AZURE_SIGNING_PROFILE }}
run: deploy/signing-setup.sh windows
# OIDC: no client secret is stored anywhere. vpk's signing (DefaultAzureCredential) uses this Azure CLI login, and
# the action logs out again when the job ends.
- name: Azure login
if: env.SIGNING == 'signed'
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
allow-no-subscriptions: true
# The token fetches the previous release's package, for the delta.
- name: Package
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p published && tar -C published -xzf publish-win-x64.tar.gz
PUBLISH_DIR=published deploy/package-installer.sh win-x64 "${GITHUB_REF_NAME#v}" installers/win-x64
# Signed: nothing after this needs the Azure login, so it ends here, not at the end of the job.
- name: Azure logout
if: always() && env.SIGNING == 'signed'
shell: bash
run: az logout || az account clear
# A release meant to be signed uploads nothing whose signature does not check out.
- name: Check the signature
shell: pwsh
run: ./deploy/check-signature-windows.ps1 installers/win-x64 -Required:($env:SIGNING -eq 'signed')
# A release meant to be signed uploads nothing whose signature does not check out: the installer, and the
# programs in the update package it installs (deploy/installer-signature-check.ps1).
- name: Check the signatures
shell: pwsh
run: deploy/installer-signature-check.ps1 -Installers installers/win-x64 -Version ($env:GITHUB_REF_NAME -replace '^v', '')
- name: Attach
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
vpk upload github --outputDir installers/win-x64 --channel win-x64 \
--repoUrl "$VPK_PREVIOUS_FROM" --token "$GITHUB_TOKEN" --tag "$GITHUB_REF_NAME" --merge $RELEASE_PRE
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: installer-win-x64
path: installers/win-x64/*-full.nupkg
if-no-files-found: error
retention-days: 7
installer-macos:
needs: [ packages, desktop, installer-build ]
runs-on: macos-latest
environment: release
permissions:
contents: write
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
VPK_PREVIOUS_FROM: ${{ github.server_url }}/${{ github.repository }}
# A version with a suffix (1.0.0-rc.1) goes into a prerelease, which the desktop app's updater does not take.
# Not VPK_PRE: vpk reads VPK_<option> variables as its own options, and an empty VPK_PRE is not a boolean.
RELEASE_PRE: ${{ contains(github.ref_name, '-') && '--pre' || '' }}
SIGNING: ${{ needs.packages.outputs.macos-signing }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: publish-osx-*
merge-multiple: true
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Install vpk
run: dotnet tool install -g vpk --version 1.2.161
# A throw-away keychain with both certificates, and the notarytool profile vpk uses. The signing setup and the
# signature check are scripts that the signing dry run (signing-dry-run.yml) runs too. Signing and notarization
# (an Apple Developer account): the two Developer ID certificates as base64 .p12 files, their password, the
# identities, and the Apple ID that notarizes. Only this step sees them; later steps use the keychain.
- name: Signing keychain
if: env.SIGNING == 'signed'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.APPLE_BUILD_CERTIFICATE_BASE64 }}
INSTALLER_CERTIFICATE_BASE64: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.APPLE_P12_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM: ${{ secrets.APPLE_TEAM_ID }}
APP_IDENTITY: ${{ vars.APPLE_SIGN_APP_IDENTITY }}
INSTALL_IDENTITY: ${{ vars.APPLE_SIGN_INSTALL_IDENTITY }}
run: deploy/signing-setup.sh macos
# The token fetches the previous release's packages, for the deltas.
- name: Package
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for rid in osx-arm64 osx-x64; do
mkdir -p "published/$rid" && tar -C "published/$rid" -xzf "publish-$rid.tar.gz"
PUBLISH_DIR="published/$rid" deploy/package-installer.sh "$rid" "${GITHUB_REF_NAME#v}" "installers/$rid"
done
# A release meant to be signed uploads nothing whose signature or notarization ticket does not check out: the
# installer, the app in it, and the app in the update package, each signed by this team
# (deploy/installer-signature-check.sh).
- name: Check the signatures
env:
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
required=()
if [ "$SIGNING" = signed ]; then
required=(--required)
fi
deploy/check-signature-macos.sh ${required[@]+"${required[@]}"} installers/osx-arm64 installers/osx-x64
for rid in osx-arm64 osx-x64; do
deploy/installer-signature-check.sh "installers/$rid" "${GITHUB_REF_NAME#v}" "$rid"
done
- name: Attach
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for rid in osx-arm64 osx-x64; do
vpk upload github --outputDir "installers/$rid" --channel "$rid" \
--repoUrl "$VPK_PREVIOUS_FROM" --token "$GITHUB_TOKEN" --tag "$GITHUB_REF_NAME" --merge $RELEASE_PRE
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: installer-osx
path: installers/osx-*/*-full.nupkg
if-no-files-found: error
retention-days: 7
# The installers' and update packages' SBOMs, one per platform, SemanticRouter-<version>-<rid>-installer.cdx.json,
# read from the full update package each installer job built (deploy/installer-sbom.sh): the app as installed, plus
# what vpk adds, Velopack's native binaries and, on Linux, the AppImage runtime. In a job of its own, so that Syft and
# the restore never run next to the signing secrets or a token that can write.
installer-sbom:
needs: [ installers, installer-windows, installer-macos ]
runs-on: ubuntu-latest
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: installer-*
path: installers
- uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
id: syft
- name: SBOMs
env:
SYFT: ${{ steps.syft.outputs.cmd }}
run: |
version="${GITHUB_REF_NAME#v}"
dotnet restore src/SemanticRouter.Desktop/SemanticRouter.Desktop.csproj --locked-mode
mkdir -p sboms
for rid in linux-x64 win-x64 osx-arm64 osx-x64; do
file="SemanticRouter-$version-$rid-full.nupkg"
package="$(find installers -name "$file" | head -n 1)"
if [ -z "$package" ]; then
echo "::error::No $file from the installer jobs."
exit 1
fi
deploy/installer-sbom.sh "$rid" "$version" "$package" "sboms/SemanticRouter-$version-$rid-installer.cdx.json" \
"pkg:generic/SemanticRouter-$rid-installer@$version?download_url=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/download/$GITHUB_REF_NAME/$file"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: sbom-installers
path: sboms/*.cdx.json
# Once every asset is signed and attached to the draft release: SHA256SUMS over all its assets; the NuGet and symbol
# packages (as nuget.org will get them, before nuget.org adds its repository signature) and the packages', image's
# and installers' SBOMs attached (the archives' came with them); and one build provenance attestation covering every
# asset and SHA256SUMS, for `gh attestation verify`. The job downloads, hashes and uploads; it builds and runs nothing
# from the repository. It first checks that the draft has every channel's update feed and the notices. Nothing has
# been pushed or published yet (the image, NuGet and publish jobs come after this one), but the attestation is
# public: it goes to the public Sigstore log and the repository's attestations, and stays there if the version is
# given up.
checksums:
needs: [ packages, image-sbom, desktop, installers, installer-windows, installer-macos, installer-sbom ]
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
id-token: write
attestations: write
env:
LC_ALL: C
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: packages
path: attach
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: sbom-*
merge-multiple: true
path: attach
- name: Checksums
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release download "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --dir release --clobber
cp attach/* release/
rm -f release/SHA256SUMS
(cd release && sha256sum -- * > SHA256SUMS)