Skip to content

Commit 36d05ca

Browse files
author
Codex
committed
build: add runtime release pipeline
1 parent cdf9d70 commit 36d05ca

3 files changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: runtime-build-windows
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
notes:
7+
description: "Release notes stored in latest.json"
8+
required: false
9+
default: ""
10+
push:
11+
tags:
12+
- "v*"
13+
14+
jobs:
15+
build:
16+
runs-on: [self-hosted, Windows]
17+
permissions:
18+
contents: read
19+
env:
20+
RELEASE_NOTES: ${{ github.event.inputs.notes || '' }}
21+
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
22+
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
23+
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
24+
R2_BUCKET: ${{ secrets.R2_BUCKET }}
25+
R2_PUBLIC_BASE_URL: ${{ secrets.R2_PUBLIC_BASE_URL }}
26+
TARGET_TRIPLE: x86_64-pc-windows-msvc
27+
PROTOCOL_VERSION: "1"
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v4
32+
33+
- name: Setup Node.js
34+
uses: actions/setup-node@v4
35+
with:
36+
node-version: 20
37+
38+
- name: Setup Rust toolchain
39+
uses: dtolnay/rust-toolchain@stable
40+
41+
- name: Resolve release version
42+
id: release
43+
shell: powershell
44+
run: |
45+
$version = node .\scripts\resolve-release-version.mjs
46+
if (-not $version) {
47+
throw "Failed to resolve release version"
48+
}
49+
"version=$version" >> $env:GITHUB_OUTPUT
50+
51+
- name: Build runtime binary
52+
run: cargo build --release --bin simprint-runtime
53+
54+
- name: Generate latest.json
55+
shell: powershell
56+
env:
57+
BINARY_PATH: target/release/simprint-runtime.exe
58+
RELEASE_VERSION: ${{ steps.release.outputs.version }}
59+
OUTPUT_PATH: latest.json
60+
run: node .\scripts\generate-latest-json.mjs
61+
62+
- name: Upload runtime binary to R2 storage
63+
shell: powershell
64+
run: |
65+
$ErrorActionPreference = "Stop"
66+
67+
if (-not (Get-Command aws -ErrorAction SilentlyContinue)) {
68+
Write-Error "aws CLI not found. Please install AWS CLI v2 and ensure 'aws' is in PATH."
69+
}
70+
71+
$version = "${{ steps.release.outputs.version }}"
72+
$accountId = "${{ env.R2_ACCOUNT_ID }}"
73+
$bucket = "${{ env.R2_BUCKET }}"
74+
$endpoint = "https://${accountId}.r2.cloudflarestorage.com"
75+
$destination = "s3://${bucket}/simprint-runtime/${version}/simprint-runtime.exe"
76+
77+
$env:AWS_ACCESS_KEY_ID = "${{ env.R2_ACCESS_KEY_ID }}"
78+
$env:AWS_SECRET_ACCESS_KEY = "${{ env.R2_SECRET_ACCESS_KEY }}"
79+
$env:AWS_EC2_METADATA_DISABLED = "true"
80+
81+
Write-Host "Uploading runtime binary to R2: $destination"
82+
aws s3 cp "target/release/simprint-runtime.exe" $destination --endpoint-url $endpoint --region auto
83+
84+
- name: Upload latest.json to R2 storage
85+
shell: powershell
86+
run: |
87+
$ErrorActionPreference = "Stop"
88+
89+
if (-not (Get-Command aws -ErrorAction SilentlyContinue)) {
90+
Write-Error "aws CLI not found. Please install AWS CLI v2 and ensure 'aws' is in PATH."
91+
}
92+
93+
$accountId = "${{ env.R2_ACCOUNT_ID }}"
94+
$bucket = "${{ env.R2_BUCKET }}"
95+
$endpoint = "https://${accountId}.r2.cloudflarestorage.com"
96+
$destination = "s3://${bucket}/simprint-runtime/latest.json"
97+
98+
$env:AWS_ACCESS_KEY_ID = "${{ env.R2_ACCESS_KEY_ID }}"
99+
$env:AWS_SECRET_ACCESS_KEY = "${{ env.R2_SECRET_ACCESS_KEY }}"
100+
$env:AWS_EC2_METADATA_DISABLED = "true"
101+
102+
Write-Host "Uploading latest.json to R2: $destination"
103+
aws s3 cp "latest.json" $destination --endpoint-url $endpoint --region auto
104+
105+
- name: Upload latest.json artifact
106+
if: github.event_name == 'workflow_dispatch'
107+
uses: actions/upload-artifact@v4
108+
with:
109+
name: latest-json
110+
path: latest.json

scripts/generate-latest-json.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import fs from 'node:fs/promises';
2+
import path from 'node:path';
3+
import crypto from 'node:crypto';
4+
5+
const binaryPath = requiredEnv('BINARY_PATH');
6+
const releaseVersion = requiredEnv('RELEASE_VERSION');
7+
const r2PublicBaseUrl = requiredEnv('R2_PUBLIC_BASE_URL').replace(/\/+$/, '');
8+
const targetTriple = requiredEnv('TARGET_TRIPLE');
9+
const protocolVersion = Number(requiredEnv('PROTOCOL_VERSION'));
10+
const outputPath = process.env.OUTPUT_PATH || 'latest.json';
11+
const notes = process.env.RELEASE_NOTES || '';
12+
13+
if (!Number.isInteger(protocolVersion) || protocolVersion <= 0) {
14+
throw new Error(`Invalid PROTOCOL_VERSION: ${process.env.PROTOCOL_VERSION}`);
15+
}
16+
17+
const resolvedBinaryPath = path.resolve(binaryPath);
18+
const fileBuffer = await fs.readFile(resolvedBinaryPath);
19+
const stats = await fs.stat(resolvedBinaryPath);
20+
21+
const fullSha256 = sha256Hex(fileBuffer);
22+
const headSha256 = sha256Hex(fileBuffer.subarray(0, Math.min(fileBuffer.length, 10 * 1024 * 1024)));
23+
24+
const document = {
25+
version: releaseVersion,
26+
protocol_version: protocolVersion,
27+
notes,
28+
pub_date: new Date().toISOString(),
29+
platforms: {
30+
[targetTriple]: {
31+
r2_url: `${r2PublicBaseUrl}/simprint-runtime/${releaseVersion}/simprint-runtime.exe`,
32+
size: stats.size,
33+
sha256: fullSha256,
34+
head_sha256_10mb: headSha256,
35+
},
36+
},
37+
};
38+
39+
const resolvedOutputPath = path.resolve(outputPath);
40+
await fs.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
41+
await fs.writeFile(resolvedOutputPath, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
42+
43+
function requiredEnv(name) {
44+
const value = process.env[name];
45+
if (!value) {
46+
throw new Error(`${name} is not set`);
47+
}
48+
return value;
49+
}
50+
51+
function sha256Hex(buffer) {
52+
return crypto.createHash('sha256').update(buffer).digest('hex');
53+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import fs from 'node:fs/promises';
2+
3+
const cargoToml = await fs.readFile(new URL('../Cargo.toml', import.meta.url), 'utf8');
4+
const match = cargoToml.match(/^version\s*=\s*"([^"]+)"/m);
5+
6+
if (!match) {
7+
throw new Error('Failed to resolve package.version from Cargo.toml');
8+
}
9+
10+
const cargoVersion = match[1];
11+
const eventName = process.env.GITHUB_EVENT_NAME || '';
12+
const refName = process.env.GITHUB_REF_NAME || '';
13+
14+
if (eventName === 'push') {
15+
const tagVersion = refName.replace(/^v/, '');
16+
if (tagVersion !== cargoVersion) {
17+
throw new Error(
18+
`Tag version "${tagVersion}" does not match Cargo.toml version "${cargoVersion}"`
19+
);
20+
}
21+
}
22+
23+
process.stdout.write(cargoVersion);

0 commit comments

Comments
 (0)