fix: replace inherited updater provider in Gitea packages

This commit is contained in:
2026-10-25 17:41:43 +03:00
parent 674d4bd8c6
commit b7f41a6b76
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,
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.
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.
extends: ./electron-builder.yml
publish:
- provider: generic
# Gitea resolves the latest published, non-prerelease release here.
# Drafts remain private and do not become an update before publication.
url: https://server2.sorokin-oleg-edu.xyz/replicated/learnharness/releases/download/latest
# Use an object: electron-builder concatenates inherited publish arrays.
provider: generic
# Gitea resolves the latest published, non-prerelease release here.
# 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",
"lint": "eslint .",
"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 .\"",
"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"
+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");
}
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) {
const names = expectedLinuxAssets(version);
const inventory = new Map();
@@ -145,6 +152,7 @@ async function main() {
const [command, directory = "dist"] = process.argv.slice(2);
const { version } = JSON.parse(await fsp.readFile(path.join(ROOT, "package.json"), "utf8"));
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 === "upload") await upload(directory, version);
else if (command === "verify-remote") {
@@ -153,7 +161,7 @@ async function main() {
assert(release, "draft not found");
await verifyRemote(release, inventory, authorization());
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) {
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 { after, test } from "node:test";
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-"));
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/],
["version", (feed: any) => { feed.version = "0.3.7"; }, /metadata version/],
["hash", (feed: any) => { feed.files[0].sha512 = "bad"; }, /update checksum/],
["duplicate", (feed: any) => { feed.files.push({ ...feed.files[1] }); }, /reference exactly/],
] as const) {
const directory = await fixture(name);
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 () => {
const require = createRequire(import.meta.url);
const yaml = require("js-yaml");
const config = yaml.load(await fs.readFile(new URL("../electron-builder.gitea.yml", import.meta.url), "utf8"));
assert.equal(config.extends, "./electron-builder.yml");
assert.deepEqual(config.publish, [{ provider: "generic", url: UPDATE_URL }]);
const { getConfig } = require("app-builder-lib/out/util/config/config.js");
const config = await getConfig(path.resolve(import.meta.dirname, ".."), "electron-builder.gitea.yml", null);
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"));
assert(workflow.on.workflow_dispatch);
assert(!workflow.on.pull_request, "untrusted pull requests must not run release publishing");
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);
});