fix: replace inherited updater provider in Gitea packages

This commit is contained in:
2026-10-25 17:41:43 +03:00
parent 674d4bd8c6
commit 2da091d6e9
5 changed files with 34 additions and 11 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ npm run desktop:release:linux
The release script builds the standalone server, initializes providers offline, The release script builds the standalone server, initializes providers offline,
curates the payload, boots it under Electron's bundled Node, packages AppImage curates the payload, boots it under Electron's bundled Node, packages AppImage
and Debian installers, creates SHA-256 checksums, and verifies update metadata and Debian installers, checks the packaged Gitea updater configuration, creates SHA-256 checksums, and verifies update metadata
against the actual installer bytes. against the actual installer bytes.
Run the packaged-runtime gates too: Run the packaged-runtime gates too:
+5 -4
View File
@@ -1,7 +1,8 @@
# Official packages for this fork. Upstream packaging stays in electron-builder.yml. # Official packages for this fork. Upstream packaging stays in electron-builder.yml.
extends: ./electron-builder.yml extends: ./electron-builder.yml
publish: publish:
- provider: generic # Use an object: electron-builder concatenates inherited publish arrays.
# Gitea resolves the latest published, non-prerelease release here. provider: generic
# Drafts remain private and do not become an update before publication. # Gitea resolves the latest published, non-prerelease release here.
url: https://server2.sorokin-oleg-edu.xyz/replicated/learnharness/releases/download/latest # Drafts remain private and do not become an update before publication.
url: https://server2.sorokin-oleg-edu.xyz/replicated/learnharness/releases/download/latest
+1 -1
View File
@@ -19,7 +19,7 @@
"test": "NODE_ENV=test tsx --test tests/*.test.ts tests/*.test.tsx", "test": "NODE_ENV=test tsx --test tests/*.test.ts tests/*.test.tsx",
"lint": "eslint .", "lint": "eslint .",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"desktop:release:linux": "npm run build && node scripts/smoke-provider-runtime.mjs && node scripts/build-server.mjs && node scripts/smoke-server.mjs --electron && electron-builder --config electron-builder.gitea.yml --linux --x64 --publish never && node scripts/gitea-release.mjs checksums dist && node scripts/gitea-release.mjs verify dist", "desktop:release:linux": "rm -rf dist .next/standalone && npm run build && node scripts/smoke-provider-runtime.mjs && node scripts/build-server.mjs && node scripts/smoke-server.mjs --electron && electron-builder --config electron-builder.gitea.yml --linux --x64 --publish never && node scripts/gitea-release.mjs verify-package dist/linux-unpacked && node scripts/gitea-release.mjs checksums dist && node scripts/gitea-release.mjs verify dist",
"desktop:dev": "concurrently -k \"next dev\" \"LH_DEV_URL=http://localhost:3000 electron .\"", "desktop:dev": "concurrently -k \"next dev\" \"LH_DEV_URL=http://localhost:3000 electron .\"",
"dev:fresh": "bash scripts/dev-fresh.sh", "dev:fresh": "bash scripts/dev-fresh.sh",
"desktop:dist": "rm -rf dist .next/standalone && next build && node scripts/smoke-provider-runtime.mjs && node scripts/build-server.mjs && node scripts/smoke-server.mjs && DEVELOPER_DIR=${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer} APPLE_KEYCHAIN_PROFILE=LH_NOTARY APPLE_TEAM_ID=JS6XN22HY6 electron-builder --mac --arm64" "desktop:dist": "rm -rf dist .next/standalone && next build && node scripts/smoke-provider-runtime.mjs && node scripts/build-server.mjs && node scripts/smoke-server.mjs && DEVELOPER_DIR=${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer} APPLE_KEYCHAIN_PROFILE=LH_NOTARY APPLE_TEAM_ID=JS6XN22HY6 electron-builder --mac --arm64"
+9 -1
View File
@@ -35,6 +35,13 @@ export async function writeChecksums(directory, version) {
await fsp.writeFile(path.join(directory, "SHA256SUMS"), lines.join("\n") + "\n"); await fsp.writeFile(path.join(directory, "SHA256SUMS"), lines.join("\n") + "\n");
} }
export async function verifyPackagedUpdater(directory) {
const config = yaml.load(await fsp.readFile(path.join(directory, "resources", "app-update.yml"), "utf8"));
assert.equal(config.provider, "generic", "packaged updater must use the Gitea generic provider");
assert.equal(config.url, UPDATE_URL, "packaged updater must use the public Gitea release feed");
assert(!config.owner && !config.repo, "packaged updater must not retain an upstream GitHub destination");
}
export async function verifyLocal(directory, version) { export async function verifyLocal(directory, version) {
const names = expectedLinuxAssets(version); const names = expectedLinuxAssets(version);
const inventory = new Map(); const inventory = new Map();
@@ -145,6 +152,7 @@ async function main() {
const [command, directory = "dist"] = process.argv.slice(2); const [command, directory = "dist"] = process.argv.slice(2);
const { version } = JSON.parse(await fsp.readFile(path.join(ROOT, "package.json"), "utf8")); const { version } = JSON.parse(await fsp.readFile(path.join(ROOT, "package.json"), "utf8"));
if (command === "checksums") await writeChecksums(directory, version); if (command === "checksums") await writeChecksums(directory, version);
else if (command === "verify-package") await verifyPackagedUpdater(directory);
else if (command === "verify") console.log(JSON.stringify(await verifyLocal(directory, version), null, 2)); else if (command === "verify") console.log(JSON.stringify(await verifyLocal(directory, version), null, 2));
else if (command === "upload") await upload(directory, version); else if (command === "upload") await upload(directory, version);
else if (command === "verify-remote") { else if (command === "verify-remote") {
@@ -153,7 +161,7 @@ async function main() {
assert(release, "draft not found"); assert(release, "draft not found");
await verifyRemote(release, inventory, authorization()); await verifyRemote(release, inventory, authorization());
console.log(`Verified draft: ${release.html_url}`); console.log(`Verified draft: ${release.html_url}`);
} else throw new Error("Usage: node scripts/gitea-release.mjs checksums|verify|upload|verify-remote [dist-directory]"); } else throw new Error("Usage: node scripts/gitea-release.mjs checksums|verify|upload|verify-remote [dist-directory], or verify-package [unpacked-app-directory]");
} }
if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) { if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) {
main().catch((error) => { console.error(error.message); process.exitCode = 1; }); main().catch((error) => { console.error(error.message); process.exitCode = 1; });
+18 -4
View File
@@ -5,7 +5,7 @@ import os from "node:os";
import path from "node:path"; import path from "node:path";
import { after, test } from "node:test"; import { after, test } from "node:test";
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import { expectedLinuxAssets, writeChecksums, verifyLocal, verifyRemote, UPDATE_URL } from "../scripts/gitea-release.mjs"; import { expectedLinuxAssets, writeChecksums, verifyLocal, verifyRemote, verifyPackagedUpdater, UPDATE_URL } from "../scripts/gitea-release.mjs";
const root = await fs.mkdtemp(path.join(os.tmpdir(), "lh-gitea-gate-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "lh-gitea-gate-"));
after(() => fs.rm(root, { recursive: true, force: true })); after(() => fs.rm(root, { recursive: true, force: true }));
@@ -39,6 +39,7 @@ test("updater cannot point at traversal paths, other versions, or substituted in
["traversal", (feed: any) => { feed.files[0].url = "../outside.AppImage"; }, /reference exactly/], ["traversal", (feed: any) => { feed.files[0].url = "../outside.AppImage"; }, /reference exactly/],
["version", (feed: any) => { feed.version = "0.3.7"; }, /metadata version/], ["version", (feed: any) => { feed.version = "0.3.7"; }, /metadata version/],
["hash", (feed: any) => { feed.files[0].sha512 = "bad"; }, /update checksum/], ["hash", (feed: any) => { feed.files[0].sha512 = "bad"; }, /update checksum/],
["duplicate", (feed: any) => { feed.files.push({ ...feed.files[1] }); }, /reference exactly/],
] as const) { ] as const) {
const directory = await fixture(name); const directory = await fixture(name);
const file = path.join(directory, "latest-linux.yml"); const file = path.join(directory, "latest-linux.yml");
@@ -73,11 +74,24 @@ test("remote verification downloads and hashes every draft attachment, and refus
test("release configuration preserves packaging while replacing the upstream update origin", async () => { test("release configuration preserves packaging while replacing the upstream update origin", async () => {
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const config = yaml.load(await fs.readFile(new URL("../electron-builder.gitea.yml", import.meta.url), "utf8")); const { getConfig } = require("app-builder-lib/out/util/config/config.js");
assert.equal(config.extends, "./electron-builder.yml"); const config = await getConfig(path.resolve(import.meta.dirname, ".."), "electron-builder.gitea.yml", null);
assert.deepEqual(config.publish, [{ provider: "generic", url: UPDATE_URL }]); assert.deepEqual(config.publish, { provider: "generic", url: UPDATE_URL });
assert.equal(config.appId, "com.learnharness.LearnHarness");
assert.deepEqual(config.linux.target.map((target: any) => target.target), ["AppImage", "deb"]);
const workflow = yaml.load(await fs.readFile(new URL("../.gitea/workflows/release.yml", import.meta.url), "utf8")); const workflow = yaml.load(await fs.readFile(new URL("../.gitea/workflows/release.yml", import.meta.url), "utf8"));
assert(workflow.on.workflow_dispatch); assert(workflow.on.workflow_dispatch);
assert(!workflow.on.pull_request, "untrusted pull requests must not run release publishing"); assert(!workflow.on.pull_request, "untrusted pull requests must not run release publishing");
assert.equal(workflow.permissions.releases, "write"); assert.equal(workflow.permissions.releases, "write");
}); });
test("packaged updater verification refuses inherited GitHub or incorrect generic destinations", async () => {
const directory = path.join(root, "package");
await fs.mkdir(path.join(directory, "resources"), { recursive: true });
const file = path.join(directory, "resources", "app-update.yml");
await fs.writeFile(file, JSON.stringify({ provider: "github", owner: "amosblomqvist", repo: "learnharness" }));
await assert.rejects(verifyPackagedUpdater(directory), /Gitea generic provider/);
await fs.writeFile(file, JSON.stringify({ provider: "generic", url: "https://other.example" }));
await assert.rejects(verifyPackagedUpdater(directory), /public Gitea release feed/);
await fs.writeFile(file, JSON.stringify({ provider: "generic", url: UPDATE_URL }));
await verifyPackagedUpdater(directory);
});