chore: add release supply-chain checks

This commit is contained in:
2026-07-19 13:03:28 +08:00
parent 21395c19e9
commit 71b6402bd8
6 changed files with 43 additions and 1 deletions
+6
View File
@@ -14,8 +14,14 @@ jobs:
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npm audit --omit=dev --audit-level=high
- run: npx tsc --noEmit
- run: npm test
- run: npm run sbom -- artifacts/mebbling.spdx.json
- uses: actions/upload-artifact@v4
with:
name: mebbling-sbom-${{ gitea.sha }}
path: artifacts/mebbling.spdx.json
- run: docker build --build-arg APP_VERSION=${{ gitea.ref_name }} -t mebbling:${{ gitea.sha }} .
# Optional: configure DEPLOY_WEBHOOK_URL as a Gitea Actions secret to notify your host on a v* tag.
- if: startsWith(gitea.ref, 'refs/tags/v') && secrets.DEPLOY_WEBHOOK_URL != ''
+1
View File
@@ -13,6 +13,7 @@
- Added a repeatable FTS search benchmark and documented PostgreSQL migration decision criteria.
- Added persistent alert delivery with HTTPS validation, exponential-backoff retries, and Prometheus delivery-state metrics.
- Added verified backup reports plus optional age-encrypted rclone offsite copies.
- Added production dependency auditing, reproducible SPDX SBOM generation, and a documented release/upgrade/signing path.
### Fixed
+1
View File
@@ -94,6 +94,7 @@ Web 接收使用者操作和 webhook,將同步需求寫入 SQLite 的 `sync_jo
- 登入在 15 分鐘內最多嘗試 8 次;webhook 與登入限流資料存於 SQLite,同一份資料庫的多個 Web 容器會共用計數。
- 所有會改變帳號或內容的瀏覽器 POST 都檢查 `Origin`Webhook 則使用密鑰 URL 驗證,不適用此規則。
- Gitea Actions 工作流程會在推送/標籤時執行型別檢查、測試與 Docker 建置;若設定 `DEPLOY_WEBHOOK_URL` secret,建立 `v*` tag 時會通知部署端。
- 工作流程也會對 production dependencies 執行高風險漏洞檢查,並產生 SPDX SBOM artifact;發布、升級與日後映像簽章的流程見 [發布文件](docs/RELEASING.md)。
## Webhook 設定與驗證
+17
View File
@@ -0,0 +1,17 @@
# 發布流程
## 每次發布前
1. 確認 `CHANGELOG.md` 的 Unreleased 內容與目標版本一致。
2. 在本機執行 `npm test``npx tsc --noEmit``npm run build``./scripts/backup.sh`
3. 提交並推送 `main`,確認 Gitea Actions 的 verify workflow 成功;它會執行 production dependency audit、測試、Docker build,並產生 SPDX SBOM artifact。
4. 建立 annotated tag,例如:`git tag -a v0.8.0 -m "Mebbling v0.8.0"`,再執行 `git push origin v0.8.0`
5. 在 Gitea 的 Releases 以同一個 tag 建立 release;若版本尚供測試,勾選 Pre-release。
## 映像簽章
目前 workflow 只建置本機 Docker image,尚未指定容器 registry,因此不會產生無法驗證的假簽章。要啟用 cosign,先決定可推送的 OCI registry 與 image 名稱,並在 Gitea Actions 設定 `COSIGN_PRIVATE_KEY``COSIGN_PASSWORD` 和 registry 登入 secret;之後將 push、`cosign sign``cosign verify` 加入 tag 工作。部署端應只接受已驗證的 tag digest。
## 升級
升級前先備份。將 `.env``MEBBLING_VERSION` 改為新 tag,執行 `docker compose pull`(若使用 registry)或重新建置後 `docker compose up -d --no-build`。確認 `/api/health?probe=ready``/api/metrics`,並觀察 worker log。資料庫 migration 會在 Web/Worker 啟動時自動執行,請勿在升級後直接回退到舊映像。
+2 -1
View File
@@ -8,7 +8,8 @@
"build": "HUB_BUILD=1 next build",
"start": "next start",
"worker": "tsx worker/index.ts",
"test": "TMPDIR=/tmp tsx --test tests/**/*.test.ts"
"test": "TMPDIR=/tmp tsx --test tests/**/*.test.ts",
"sbom": "node scripts/generate-sbom.mjs"
},
"keywords": [],
"author": "",
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env node
import { readFile, mkdir, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
const output = resolve(process.argv[2] || "artifacts/mebbling.spdx.json");
const lock = JSON.parse(await readFile("package-lock.json", "utf8"));
const packages = Object.entries(lock.packages)
.filter(([path, value]) => path.startsWith("node_modules/") && value.version)
.map(([path, value]) => {
const name = path.slice("node_modules/".length);
return { SPDXID: `SPDXRef-Package-${name.replace(/[^A-Za-z0-9.-]/g, "-")}`, name, versionInfo: value.version, downloadLocation: value.resolved || "NOASSERTION", licenseConcluded: "NOASSERTION", licenseDeclared: value.license || "NOASSERTION", checksums: value.integrity ? [{ algorithm: "SHA512", checksumValue: value.integrity.replace(/^sha512-/, "") }] : [], externalRefs: [{ referenceCategory: "PACKAGE-MANAGER", referenceType: "purl", referenceLocator: `pkg:npm/${encodeURIComponent(name).replace("%40", "@")}@${value.version}` }] };
});
const sbom = { spdxVersion: "SPDX-2.3", dataLicense: "CC0-1.0", SPDXID: "SPDXRef-DOCUMENT", name: "mebbling", documentNamespace: `https://gitea.fishking.studio/tangsongdayo/Mebbling/sbom/${lock.version}`, creationInfo: { created: new Date().toISOString(), creators: ["Tool: Mebbling package-lock SBOM generator"] }, packages: [{ SPDXID: "SPDXRef-Mebbling", name: lock.name, versionInfo: lock.version, downloadLocation: "NOASSERTION", licenseConcluded: "NOASSERTION", licenseDeclared: lock.packages[""].license || "NOASSERTION" }, ...packages], relationships: packages.map((item) => ({ spdxElementId: "SPDXRef-Mebbling", relationshipType: "DEPENDS_ON", relatedSpdxElement: item.SPDXID })) };
await mkdir(dirname(output), { recursive: true });
await writeFile(output, `${JSON.stringify(sbom, null, 2)}\n`);
console.log(`Generated SPDX SBOM with ${packages.length} packages: ${output}`);