chore: add release supply-chain checks
This commit is contained in:
@@ -14,8 +14,14 @@ jobs:
|
|||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with: { node-version: 22, cache: npm }
|
with: { node-version: 22, cache: npm }
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
- run: npm audit --omit=dev --audit-level=high
|
||||||
- run: npx tsc --noEmit
|
- run: npx tsc --noEmit
|
||||||
- run: npm test
|
- 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 }} .
|
- 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.
|
# 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 != ''
|
- if: startsWith(gitea.ref, 'refs/tags/v') && secrets.DEPLOY_WEBHOOK_URL != ''
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
- Added a repeatable FTS search benchmark and documented PostgreSQL migration decision criteria.
|
- 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 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 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
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ Web 接收使用者操作和 webhook,將同步需求寫入 SQLite 的 `sync_jo
|
|||||||
- 登入在 15 分鐘內最多嘗試 8 次;webhook 與登入限流資料存於 SQLite,同一份資料庫的多個 Web 容器會共用計數。
|
- 登入在 15 分鐘內最多嘗試 8 次;webhook 與登入限流資料存於 SQLite,同一份資料庫的多個 Web 容器會共用計數。
|
||||||
- 所有會改變帳號或內容的瀏覽器 POST 都檢查 `Origin`,Webhook 則使用密鑰 URL 驗證,不適用此規則。
|
- 所有會改變帳號或內容的瀏覽器 POST 都檢查 `Origin`,Webhook 則使用密鑰 URL 驗證,不適用此規則。
|
||||||
- Gitea Actions 工作流程會在推送/標籤時執行型別檢查、測試與 Docker 建置;若設定 `DEPLOY_WEBHOOK_URL` secret,建立 `v*` tag 時會通知部署端。
|
- Gitea Actions 工作流程會在推送/標籤時執行型別檢查、測試與 Docker 建置;若設定 `DEPLOY_WEBHOOK_URL` secret,建立 `v*` tag 時會通知部署端。
|
||||||
|
- 工作流程也會對 production dependencies 執行高風險漏洞檢查,並產生 SPDX SBOM artifact;發布、升級與日後映像簽章的流程見 [發布文件](docs/RELEASING.md)。
|
||||||
|
|
||||||
## Webhook 設定與驗證
|
## Webhook 設定與驗證
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -8,7 +8,8 @@
|
|||||||
"build": "HUB_BUILD=1 next build",
|
"build": "HUB_BUILD=1 next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"worker": "tsx worker/index.ts",
|
"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": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -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}`);
|
||||||
Reference in New Issue
Block a user