Skip to content

Commit b1e333a

Browse files
committed
자산: Buildroot 독립 재현 빌드를 게이트화
1 parent c4a4f6b commit b1e333a

2 files changed

Lines changed: 83 additions & 11 deletions

File tree

.github/workflows/buildroot-guest.yml

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,96 @@ on:
66
push:
77
branches: [main]
88
paths:
9-
- scripts/buildroot/**
10-
- scripts/assetCatalog.json
11-
- .github/workflows/buildroot-guest.yml
9+
- "scripts/buildroot/**"
10+
- ".github/workflows/buildroot-guest.yml"
1211

1312
permissions:
1413
contents: read
1514

1615
jobs:
1716
reproduce:
17+
name: reproduce-${{ matrix.slot }}
1818
runs-on: ubuntu-24.04
1919
timeout-minutes: 120
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
slot: [a, b]
24+
env:
25+
PYPROC_BUILDROOT_WORKSPACE: ${{ runner.temp }}/pyproc-buildroot-${{ matrix.slot }}
2026
steps:
21-
- uses: actions/checkout@v4
22-
- uses: actions/setup-node@v4
27+
- uses: actions/checkout@v5
28+
- uses: actions/setup-node@v5
2329
with:
2430
node-version: 22
2531
cache: npm
2632
- run: npm ci
2733
- name: Build pinned Buildroot guest and legal material
2834
run: npm run assets:buildroot
29-
- name: Upload reproducible guest bundle
35+
- name: Upload independent build evidence
3036
uses: actions/upload-artifact@v4
3137
with:
32-
name: pyproc-buildroot-guest
33-
path: |
34-
.cache/buildrootGuest/dist/
35-
.cache/buildrootGuest/output/legal-info/
38+
name: pyproc-buildroot-guest-${{ matrix.slot }}
39+
path: ${{ env.PYPROC_BUILDROOT_WORKSPACE }}/dist/
3640
if-no-files-found: error
3741
retention-days: 30
42+
- name: Upload complete legal material
43+
if: matrix.slot == 'a'
44+
uses: actions/upload-artifact@v4
45+
with:
46+
name: pyproc-buildroot-legal-info
47+
path: ${{ env.PYPROC_BUILDROOT_WORKSPACE }}/output/legal-info/
48+
if-no-files-found: error
49+
compression-level: 0
50+
retention-days: 30
51+
52+
verify:
53+
name: verify-byte-identical-builds
54+
needs: reproduce
55+
runs-on: ubuntu-24.04
56+
timeout-minutes: 15
57+
steps:
58+
- uses: actions/download-artifact@v4
59+
with:
60+
name: pyproc-buildroot-guest-a
61+
path: .cache/repro/a
62+
- uses: actions/download-artifact@v4
63+
with:
64+
name: pyproc-buildroot-guest-b
65+
path: .cache/repro/b
66+
- name: Verify image, manifest, SBOM, and legal gate
67+
run: |
68+
set -euo pipefail
69+
cmp .cache/repro/a/buildroot-pyproc-i686.bin .cache/repro/b/buildroot-pyproc-i686.bin
70+
cmp .cache/repro/a/build-manifest.json .cache/repro/b/build-manifest.json
71+
cmp .cache/repro/a/buildroot.cyclonedx.json .cache/repro/b/buildroot.cyclonedx.json
72+
mkdir -p .cache/repro/verified
73+
cp .cache/repro/a/* .cache/repro/verified/
74+
node --input-type=module <<'NODE'
75+
import { createHash } from "node:crypto";
76+
import { readFile, writeFile } from "node:fs/promises";
77+
const base = ".cache/repro/verified";
78+
const manifest = JSON.parse(await readFile(`${base}/build-manifest.json`, "utf8"));
79+
const image = await readFile(`${base}/buildroot-pyproc-i686.bin`);
80+
const actualSha256 = createHash("sha256").update(image).digest("hex");
81+
if (actualSha256 !== manifest.output.sha256) throw new Error("verified image SHA-256 mismatch");
82+
if (image.byteLength !== manifest.output.byteLength) throw new Error("verified image byteLength mismatch");
83+
if (manifest.evidence.legalWarnings.length) throw new Error("verified legal-info warnings are not empty");
84+
const receipt = {
85+
schemaVersion: 1,
86+
recipe: manifest.recipe,
87+
githubRunId: process.env.GITHUB_RUN_ID,
88+
headSha: process.env.GITHUB_SHA,
89+
independentBuilds: ["a", "b"],
90+
byteIdentical: true,
91+
output: manifest.output,
92+
};
93+
await writeFile(`${base}/reproducibility-manifest.json`, `${JSON.stringify(receipt, null, 2)}\n`);
94+
NODE
95+
- name: Upload verified reproducible guest
96+
uses: actions/upload-artifact@v4
97+
with:
98+
name: pyproc-buildroot-guest-verified
99+
path: .cache/repro/verified/
100+
if-no-files-found: error
101+
retention-days: 90

scripts/buildroot/buildGuest.mjs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ await mkdir(outputDir, { recursive: true });
7373
await mkdir(distDir, { recursive: true });
7474
await copyFile(configPath, join(outputDir, ".config"));
7575
const sourceDateEpoch = String(BUILDROOT.sourceDateEpoch);
76-
const env = { ...process.env, SOURCE_DATE_EPOCH: sourceDateEpoch, TZ: "UTC", LC_ALL: "C" };
76+
const env = {
77+
...process.env,
78+
SOURCE_DATE_EPOCH: sourceDateEpoch,
79+
TZ: "UTC",
80+
LC_ALL: "C",
81+
// sourceDir가 저장소 안의 .cache에 있더라도 상위 pyproc Git SHA가
82+
// Buildroot BR2_VERSION_FULL과 guest 바이트에 유입되지 않게 한다.
83+
GIT_CEILING_DIRECTORIES: root,
84+
};
7785
run("make", [`O=${outputDir}`, "olddefconfig"], { cwd: sourceDir, env });
7886
run("make", [`O=${outputDir}`], { cwd: sourceDir, env });
7987
run("make", [`O=${outputDir}`, "legal-info"], { cwd: sourceDir, env });

0 commit comments

Comments
 (0)