From 84cb1fe6f0fc22069119057a04a51d9f13530603 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Thu, 24 Sep 2026 10:10:20 +0000 Subject: [PATCH] Publish the Gentoo overlay, and stop winget pointing at a file that does not exist Gentoo turns out not to need anyone's review. An overlay is a git repo laid out the way portage expects, so it is ours to push like the tap and the bucket, not a pull request into someone else's tree. profullstack/gentoo-tronbrowser now exists with net-misc/tronbrowser-bin 3.15.1, and carries its own write deploy key (GENTOO_OVERLAY_SSH_KEY). It is a complete overlay: metadata/layout.conf and profiles/repo_name included, both of which gentoo-pairux is missing, so portage can actually consume this one. The ebuild is entirely ${PV}/${P}, so a release needs no edits to its contents -- only the filename carries the version. The templater renames it and drops the stale one, instead of printing a note and leaving tronbrowser-bin-0.1.1.ebuild in the tree forever. The Manifest is generated: portage wants BLAKE2B and SHA512 next to the byte count rather than the sha256 every other channel uses, and node's blake2b512 is byte-identical to b2sum. It names the tarball by the ${P}.tar.gz that SRC_URI renames it to, not by the release asset's filename. winget is a different story and the blocker is the artifact, not the manifest. tronbrowser-win-x64.zip contains no tronbrowser.exe at all: the Windows entry point is tronbrowser.cmd, a shim that hunts for Python 3.9+ and then drives a copy of Ungoogled Chromium the user must install separately. A portable winget package around that would install for somebody and then fail to start, so it would be rejected on review or, worse, accepted and broken. The manifest pointed RelativeFilePath at that non-existent exe; corrected to the file that does exist, with the real blocker written down. PairUX ships a nullsoft .exe and is in winget-pkgs today, which is the shape to copy when someone does the Windows build. Verified against the live overlay: running the submitter for real reports "already current, nothing to push", which is only true because the generated Manifest matches the one computed by hand with b2sum and sha512sum byte for byte. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/submit-packages.yml | 1 + ...1.ebuild => tronbrowser-bin-3.15.1.ebuild} | 0 .../Profullstack.TronBrowser.installer.yaml | 18 ++- scripts/submit-packages.mjs | 153 +++++++++++++++--- 4 files changed, 147 insertions(+), 25 deletions(-) rename distribution/gentoo/{tronbrowser-bin-0.1.1.ebuild => tronbrowser-bin-3.15.1.ebuild} (100%) diff --git a/.github/workflows/submit-packages.yml b/.github/workflows/submit-packages.yml index b3f710b5..7ef267c8 100644 --- a/.github/workflows/submit-packages.yml +++ b/.github/workflows/submit-packages.yml @@ -88,6 +88,7 @@ jobs: # from that repo. HOMEBREW_TAP_SSH_KEY: ${{ secrets.HOMEBREW_TAP_SSH_KEY }} SCOOP_BUCKET_SSH_KEY: ${{ secrets.SCOOP_BUCKET_SSH_KEY }} + GENTOO_OVERLAY_SSH_KEY: ${{ secrets.GENTOO_OVERLAY_SSH_KEY }} SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} run: | ARGS="-v ${{ steps.v.outputs.version }}" diff --git a/distribution/gentoo/tronbrowser-bin-0.1.1.ebuild b/distribution/gentoo/tronbrowser-bin-3.15.1.ebuild similarity index 100% rename from distribution/gentoo/tronbrowser-bin-0.1.1.ebuild rename to distribution/gentoo/tronbrowser-bin-3.15.1.ebuild diff --git a/distribution/winget/Profullstack.TronBrowser.installer.yaml b/distribution/winget/Profullstack.TronBrowser.installer.yaml index 56f6feb2..74758ac6 100644 --- a/distribution/winget/Profullstack.TronBrowser.installer.yaml +++ b/distribution/winget/Profullstack.TronBrowser.installer.yaml @@ -1,6 +1,18 @@ # winget manifest (installer). Submit to microsoft/winget-pkgs as -# manifests/p/Profullstack/TronBrowser//. Needs a Windows installer -# (.exe/.msi) or a portable zip artifact — pending the Windows build. +# manifests/p/Profullstack/TronBrowser//. +# +# NOT SUBMITTABLE YET, and the blocker is the artifact rather than the manifest. +# tronbrowser-win-x64.zip contains no tronbrowser.exe: the Windows entry point is +# tronbrowser.cmd, a shim that searches for Python 3.9+ and then drives a copy of +# Ungoogled Chromium the user has to install separately. A winget portable +# package shimming that would install for someone and then fail to start, so it +# would be rejected on review or, worse, accepted and broken. +# +# RelativeFilePath below named tronbrowser.exe, which has never existed in the +# zip; corrected to the file that does. Submitting still needs a real Windows +# build — a self-contained .exe/.msi, or a zip whose entry point runs without +# the user installing an interpreter and a browser first. PairUX ships a nullsoft +# .exe and is in winget-pkgs today, which is the shape to copy. PackageIdentifier: Profullstack.TronBrowser PackageVersion: 3.15.1 InstallerType: zip @@ -10,7 +22,7 @@ Installers: InstallerSha256: 2AA36BB55E6E45EB1ACE91435B1B696CB6BFAD929B876B85702F3AD51F51ADDE NestedInstallerType: portable NestedInstallerFiles: - - RelativeFilePath: tronbrowser\tronbrowser.exe + - RelativeFilePath: tronbrowser\tronbrowser.cmd PortableCommandAlias: tron ManifestType: installer ManifestVersion: 1.6.0 diff --git a/scripts/submit-packages.mjs b/scripts/submit-packages.mjs index ff1071f0..9ecc883f 100644 --- a/scripts/submit-packages.mjs +++ b/scripts/submit-packages.mjs @@ -18,7 +18,14 @@ // ones that are a pull request into somebody else's monorepo (winget, flathub, // nixpkgs, gentoo, freebsd) still cannot be automated end to end and say so // rather than reporting success. -import { readFileSync, writeFileSync, existsSync, mkdtempSync } from "node:fs"; +import { + readFileSync, + writeFileSync, + existsSync, + mkdtempSync, + readdirSync, + rmSync, +} from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { createHash } from "node:crypto"; @@ -72,6 +79,24 @@ async function sha256(asset) { return createHash("sha256").update(buf).digest("hex"); } +/** + * Size plus the digests a Gentoo Manifest names. + * + * Portage wants BLAKE2B and SHA512 alongside the byte count, not the sha256 + * everything else uses. Node's blake2b512 is byte-identical to b2sum. + */ +async function gentooDigests(asset) { + const url = `https://github.com/${REPO}/releases/download/v${version}/${asset}`; + const res = await fetch(url); + if (!res.ok) return null; + const buf = Buffer.from(await res.arrayBuffer()); + return { + size: buf.length, + blake2b: createHash("blake2b512").update(buf).digest("hex"), + sha512: createHash("sha512").update(buf).digest("hex"), + }; +} + function patch(file, replacers) { const path = join(ROOT, file); if (!existsSync(path)) return console.log(` skip (missing): ${file}`); @@ -135,11 +160,36 @@ for (const pm of targets) { : []), ]); break; - case "gentoo": - console.log( - ` rename distribution/gentoo/tronbrowser-bin-${version}.ebuild and bump SRC_URI (uses \${PV})`, + case "gentoo": { + // The ebuild is entirely ${PV}/${P}, so a release needs no edits to it — + // only the filename carries the version. Keep exactly one ebuild in the + // tree, renamed to this release, instead of printing a note and leaving + // the 0.1.1 file there forever. + const existing = readdirSync(join(ROOT, "distribution/gentoo")).filter((f) => + f.endsWith(".ebuild"), ); + const want = `tronbrowser-bin-${version}.ebuild`; + if (existing.includes(want) && existing.length === 1) { + console.log(` distribution/gentoo/${want} already current`); + break; + } + const source = existing[0]; + if (!source) { + console.log(" skip (missing): no ebuild in distribution/gentoo"); + break; + } + if (dryRun) { + console.log(` [dry-run] would rename ${source} -> ${want}`); + break; + } + const body = readFileSync(join(ROOT, "distribution/gentoo", source), "utf8"); + writeFileSync(join(ROOT, "distribution/gentoo", want), body); + for (const f of existing) { + if (f !== want) rmSync(join(ROOT, "distribution/gentoo", f)); + } + console.log(` renamed ${source} -> ${want}`); break; + } case "scoop": patch("distribution/scoop/tronbrowser.json", [ [/"version": "[^"]+"/, `"version": "${version}"`], @@ -241,6 +291,8 @@ for (const pm of targets) { const TAP_REPO = process.env.HOMEBREW_TAP_REPO || "profullstack/homebrew-tap"; const SCOOP_REPO = process.env.SCOOP_BUCKET_REPO || "profullstack/scoop-bucket"; +const GENTOO_REPO = + process.env.GENTOO_OVERLAY_REPO || "profullstack/gentoo-tronbrowser"; function run(cmd, args, opts = {}) { return execFileSync(cmd, args, { @@ -280,7 +332,22 @@ function hasCommand(cmd) { * created through the API, and is revoked by deleting it from that repo. */ function pushFileToRepo({ repo, sshKey, file, dest, message }) { - const dir = tmp(dest.replace(/[^a-z0-9]+/gi, "-")); + return pushFilesToRepo({ + repo, + sshKey, + message, + files: [{ from: file, dest }], + }); +} + +/** + * Write several files into a repo we own and push once. + * + * `files` are copied from the working tree, `write` are generated in place (the + * Gentoo Manifest has no on-disk source). + */ +function pushFilesToRepo({ repo, sshKey, files = [], write = [], message }) { + const dir = tmp(repo.replace(/[^a-z0-9]+/gi, "-")); const keyfile = join(dir, "..", `key-${repo.replace(/\W+/g, "-")}`); writeFileSync(keyfile, sshKey.endsWith("\n") ? sshKey : `${sshKey}\n`, { mode: 0o600, @@ -294,8 +361,14 @@ function pushFileToRepo({ repo, sshKey, file, dest, message }) { }); // The tap holds Casks/ and Formula/ side by side and the bucket holds bucket/; // a first push into a directory that does not exist yet has to create it. - run("mkdir", ["-p", dirname(join(dir, dest))]); - run("cp", [join(ROOT, file), join(dir, dest)], { cwd: ROOT }); + for (const f of files) { + run("mkdir", ["-p", dirname(join(dir, f.dest))]); + run("cp", [join(ROOT, f.from), join(dir, f.dest)], { cwd: ROOT }); + } + for (const w of write) { + run("mkdir", ["-p", dirname(join(dir, w.dest))]); + writeFileSync(join(dir, w.dest), w.content); + } run("git", ["config", "user.name", "github-actions[bot]"], { cwd: dir }); run("git", [ "config", @@ -310,10 +383,11 @@ function pushFileToRepo({ repo, sshKey, file, dest, message }) { console.log(` ${repo} already current, nothing to push`); return; } - run("git", ["add", dest], { cwd: dir }); + run("git", ["add", "-A"], { cwd: dir }); run("git", ["commit", "-m", message], { cwd: dir }); run("git", ["push"], { cwd: dir, env }); - console.log(` pushed ${dest} to ${repo}`); + const names = [...files, ...write].map((f) => f.dest).join(", "); + console.log(` pushed ${names} to ${repo}`); } /** Channels that are a PR into a third party's monorepo. */ @@ -429,12 +503,18 @@ const SUBMITTERS = { console.log(" uploaded to the Snap Store"); }, - winget: () => - upstreamPr( - "winget", - "microsoft/winget-pkgs", - "https://github.com/microsoft/winget-pkgs/blob/master/CONTRIBUTING.md", - ), + // Blocked on the artifact, not on credentials or review: the Windows zip has + // no exe, only a .cmd that needs the user to already have Python and Ungoogled + // Chromium. A portable package around that installs and then fails to start. + winget: () => { + console.log( + " winget: the Windows zip has no self-contained executable, so a portable", + ); + console.log( + " package would install and fail to start. Needs a real .exe/.msi build", + ); + console.log(" before submitting to microsoft/winget-pkgs."); + }, flatpak: () => upstreamPr( "flatpak", @@ -447,12 +527,39 @@ const SUBMITTERS = { "NixOS/nixpkgs", "https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md", ), - gentoo: () => - upstreamPr( - "gentoo", - "an ebuild overlay", - "https://wiki.gentoo.org/wiki/Ebuild_repository", - ), + // Gentoo has no central submission: an overlay is just a git repo laid out the + // way portage expects, and ours is profullstack/gentoo-tronbrowser. So unlike + // winget or flathub this one is entirely ours to push. + gentoo: async () => { + const sshKey = process.env.GENTOO_OVERLAY_SSH_KEY; + if (!sshKey) return skip("gentoo", "GENTOO_OVERLAY_SSH_KEY"); + const src = `distribution/gentoo/tronbrowser-bin-${version}.ebuild`; + if (!existsSync(join(ROOT, src))) { + console.log(` gentoo: ${src} is missing, nothing to push`); + return; + } + const d = await gentooDigests(ASSET.linux); + if (!d) { + console.log(" gentoo: could not read the release tarball, skipping"); + return; + } + // SRC_URI renames the tarball to ${P}.tar.gz, and the Manifest must name it + // by that renamed filename, not the release asset's. + const manifest = + `DIST tronbrowser-bin-${version}.tar.gz ${d.size}` + + ` BLAKE2B ${d.blake2b} SHA512 ${d.sha512}\n`; + pushFilesToRepo({ + repo: GENTOO_REPO, + sshKey, + message: `net-misc/tronbrowser-bin: ${version}`, + files: [ + { from: src, dest: `net-misc/tronbrowser-bin/tronbrowser-bin-${version}.ebuild` }, + ], + write: [ + { dest: "net-misc/tronbrowser-bin/Manifest", content: manifest }, + ], + }); + }, freebsd: () => upstreamPr( "freebsd", @@ -506,7 +613,9 @@ if (!dryRun) { const submit = SUBMITTERS[pm]; if (!submit) continue; console.log(`\n== submit ${pm} ==`); - submit(); + // gentoo fetches the tarball to build its Manifest, so submitters may be + // async; awaiting a sync one is harmless. + await submit(); } }