1- name : Nightly Release
1+ name : Nightly Release (ORAS)
22
33on :
44 schedule :
99 CARGO_TERM_COLOR : always
1010
1111jobs :
12- # 0) Move/force the "nightly" tag to main and delete the prior GH release.
1312 prepare-nightly-release :
1413 name : Prepare nightly tag + delete prior release
1514 runs-on : ubuntu-latest
@@ -34,118 +33,185 @@ jobs:
3433 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
3534 run : gh release delete nightly --yes || true
3635
37- build-plugin :
38- name : Build and sign nightly plugin
39- environment : staging
36+ build-wasm :
37+ name : Build plugin.wasm
4038 needs : prepare-nightly-release
4139 runs-on : ubuntu-24.04
4240 permissions :
43- contents : write
44- packages : write
45- id-token : write # needed for keyless signing
46-
41+ contents : read
42+ outputs :
43+ sha : ${{ steps.meta.outputs.sha }}
4744 steps :
4845 - uses : actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
4946 with :
5047 ref : nightly
5148 fetch-depth : 0
5249 submodules : true
5350
54- - name : Show git version
51+ - name : Set up TinyGo
52+ uses : acifani/setup-tinygo@db56321a62b9a67922bb9ac8f9d085e218807bb3 # v2.0.1
53+ with :
54+ tinygo-version : " 0.40.1"
55+
56+ - name : Build wasm (auditable)
57+ run : GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm
58+
59+ - name : Ensure plugin.wasm exists
5560 shell : bash
5661 run : |
57- git describe --tags --always --dirty=-modified || echo "No git version available"
62+ set -euo pipefail
63+ ls -lh ./plugin.wasm
64+
65+ - name : Record commit sha
66+ id : meta
67+ shell : bash
68+ run : |
69+ set -euo pipefail
70+ echo "sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
71+
72+ - name : Upload wasm artifact
73+ uses : actions/upload-artifact@v4
74+ with :
75+ name : plugin-wasm
76+ path : plugin.wasm
77+ if-no-files-found : error
78+
79+ publish-oras :
80+ name : Publish ORAS artifact + sign
81+ needs : build-wasm
82+ runs-on : ubuntu-24.04
83+ permissions :
84+ contents : read
85+ packages : write
86+ id-token : write # keyless signing
87+ outputs :
88+ nightly_ref : ${{ steps.meta.outputs.nightly_ref }}
89+ nightly_digest : ${{ steps.digest.outputs.nightly_digest }}
90+ steps :
91+ - name : Download wasm artifact
92+ uses : actions/download-artifact@v4
93+ with :
94+ name : plugin-wasm
95+ path : .
5896
59- - name : Set up Docker Buildx
60- uses : docker /setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
97+ - name : Install ORAS
98+ uses : oras-project /setup-oras@v1
6199
62100 - name : Install cosign
63101 uses : sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
64102
65- - name : Log in to GitHub Container Registry
66- uses : docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
67- with :
68- registry : ghcr.io
69- username : ${{ github.actor }}
70- password : ${{ secrets.GITHUB_TOKEN }}
71-
72- # Build WASM for both platforms at once
73- # WASM is platform-independent, so buildx creates proper multi-arch manifest automatically
74- - name : Build and push multi-arch plugin image
75- id : build
76- uses : docker/build-push-action@v6
77- with :
78- context : .
79- push : true
80- platforms : linux/amd64,linux/arm64
81- provenance : false
82- tags : |
83- ghcr.io/${{ github.repository }}:nightly
84-
85- - name : Get manifest digest for signing
103+ - name : ORAS login to GHCR
86104 shell : bash
87105 run : |
88106 set -euo pipefail
107+ echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u "${{ github.actor }}" --password-stdin
89108
90- IMAGE="ghcr.io/${{ github.repository }}"
109+ - name : Compute ORAS ref
110+ id : meta
111+ shell : bash
112+ run : |
113+ set -euo pipefail
114+ REF="ghcr.io/${{ github.repository }}:nightly"
115+ echo "nightly_ref=$REF" >> "$GITHUB_OUTPUT"
116+
117+ - name : Push ORAS artifact (plugin.wasm)
118+ shell : bash
119+ run : |
120+ set -euo pipefail
121+ REF="${{ steps.meta.outputs.nightly_ref }}"
91122
92- # Get the manifest list digest for signing
93- MANIFEST_DIGEST=$(docker buildx imagetools inspect "${IMAGE}:nightly" --format '{{json .Manifest}}' | jq -r '.digest')
94- echo "IMAGE_DIGEST=${MANIFEST_DIGEST}" >> $GITHUB_ENV
123+ # Publish as an OCI artifact:
124+ # - artifact type: identifies "this is a hyper-mcp plugin artifact"
125+ # - layer: the wasm blob
126+ oras push "$REF" \
127+ --artifact-type application/vnd.hyper-mcp.plugin.v2 \
128+ ./plugin.wasm:application/wasm
95129
96- - name : Sign manifest list by digest
130+ - name : Resolve ORAS digest
131+ id : digest
97132 shell : bash
98133 run : |
99134 set -euo pipefail
135+ REF="${{ steps.meta.outputs.nightly_ref }}"
100136
101- MANIFEST_DIGEST="${{ env.IMAGE_DIGEST }}"
102- IMAGE="ghcr.io/${{ github.repository }}"
137+ # Get the descriptor JSON and extract the digest
138+ nightly_digest="$(
139+ oras manifest fetch "$REF" --descriptor \
140+ | python3 -c 'import json,sys; print(json.load(sys.stdin)["digest"])'
141+ )"
103142
104- echo "Signing multi-arch manifest list ${IMAGE}@${MANIFEST_DIGEST}"
105- cosign sign --yes "${IMAGE}@${MANIFEST_DIGEST}"
143+ if [[ ! "$nightly_digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then
144+ echo "ERROR: Invalid digest: '$nightly_digest'" >&2
145+ exit 1
146+ fi
106147
107- - name : Write release notes (with immutable digest)
108- id : notes
148+ echo "nightly_digest=$nightly_digest" >> "$GITHUB_OUTPUT"
149+ echo "Resolved nightly digest: $nightly_digest"
150+
151+ - name : Sign ORAS artifact by digest
109152 shell : bash
110153 run : |
111154 set -euo pipefail
155+ IMAGE="ghcr.io/${{ github.repository }}"
156+ cosign sign --yes "${IMAGE}@${{ steps.digest.outputs.nightly_digest }}"
112157
158+ publish-nightly-release :
159+ name : Publish nightly GitHub Release
160+ needs : publish-oras
161+ runs-on : ubuntu-latest
162+ permissions :
163+ contents : write
164+ steps :
165+ - name : Download plugin.wasm artifact
166+ uses : actions/download-artifact@v4
167+ with :
168+ name : plugin-wasm
169+ path : .
170+
171+ - name : Create release notes
172+ shell : bash
173+ run : |
174+ set -euo pipefail
113175 IMAGE="ghcr.io/${{ github.repository }}"
114- NIGHTLY_DIGEST ="${{ env.IMAGE_DIGEST }}"
115- SHA ="${GITHUB_SHA }"
176+ REF ="${{ needs.publish-oras.outputs.nightly_ref }}"
177+ DIGEST ="${{ needs.publish-oras.outputs.nightly_digest } }"
116178
117179 cat > release-body.md <<EOF
118180 Nightly build from \`main\`.
119181
120- Commit:
121- - \`${SHA}\`
122-
123- Container image (tag):
124- - \`${IMAGE}:nightly\`
182+ OCI artifact (tag):
183+ - \`${REF}\`
125184
126185 ✅ Immutable digest (recommended for pinning):
127- - \`${IMAGE}@${NIGHTLY_DIGEST }\`
186+ - \`${IMAGE}@${DIGEST }\`
128187
129- All container images are signed with Cosign. Verify the **immutable digest** with:
188+ Release asset:
189+ - \`plugin.wasm\`
190+
191+ Pull with ORAS:
192+ \`\`\`bash
193+ oras pull ${REF}
194+ \`\`\`
195+
196+ All artifacts are signed with Cosign. Verify the **immutable digest** with:
130197
131198 \`\`\`bash
132199 cosign verify \
133- --certificate-identity "https://github.com/${{ github.repository }}/.github/workflows/nightly.yml@refs/heads/main" \
134- --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
135- ${IMAGE}@${NIGHTLY_DIGEST }
200+ --certificate-identity-regexp "https://github.com/${{ github.repository }}/.github/workflows/nightly.yml@refs/heads/main" \
201+ --certificate-oidc-issuer-regexp "https://token.actions.githubusercontent.com" \
202+ ${IMAGE}@${DIGEST }
136203 \`\`\`
137204 EOF
138205
139- echo "body_path=release-body.md" >> "$GITHUB_OUTPUT"
140-
141206 - name : Create new nightly release
142- id : create_release
143207 uses : softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
144208 with :
145209 tag_name : nightly
146- name : Nightly build
210+ name : Nightly build (ORAS)
147211 draft : false
148212 prerelease : true
149213 generate_release_notes : true
150214 preserve_order : true
151- body_path : ${{ steps.notes.outputs.body_path }}
215+ body_path : release-body.md
216+ files : |
217+ plugin.wasm
0 commit comments