mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-09-11 11:45:27 +02:00
* Sign and notarize macOS builds jpackage cannot produce a distributable macOS bundle on its own. It ad-hoc signs the embedded runtime and then re-runs codesign on the same files without --force, which codesign rejects; and "--type dmg --app-image" re-signs the app it is handed, replacing a Developer ID signature with an ad-hoc one. So the build now creates an unsigned app-image, signs it from the inside out, and wraps it with hdiutil. Apple's notary service also unpacks JARs and checks the native libraries inside them, which sqlite-jdbc ships for both architectures. Those are signed before the bundle is sealed, since rewriting a JAR afterwards would invalidate the seal. A preflight check verifies Apple's two criteria locally, so a missed binary costs seconds rather than a round trip to the notary service. Two long-standing defects surfaced while testing and are fixed here: the bundle identifier defaulted to the main class's package name (kst4contest.view instead of de.x08.KST4Contest), and every release reported version 1.0 in Finder because --app-version was never passed. Neither affects existing users: the app keeps its settings in ~/.praktiKST, independent of the bundle ID. Both workflows call the same script the local Mac uses, so the two cannot drift apart. Signing needs a keychain that can answer a UI prompt, which a runner cannot, so ci-import-cert.sh creates a throwaway keychain whose password is generated per job and discarded with it. Notarization goes through an App Store Connect API key and needs no keychain at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Sign bundle contents serially Signing the app image's Mach-O files with "xargs -P 8" passed locally but failed on a runner: codesign reported "replacing existing signature" and then "No such file or directory" for that same path. The two libjli.dylib copies are separate inodes, so this is not hard links being signed twice -- concurrent codesign runs over one bundle are simply not reliable. Serially costs about a minute, since each call waits on Apple's timestamp server. Also stop the matrix from cancelling the other architecture on a failure; that throws away half the diagnostic information from a failed run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Document macOS signing from 1.42 The installation guides described the right-click workaround as the normal first launch. That stays, but as the path for 1.41.1 and older; from 1.42 a double-click works. Both guides also show how to verify a download with spctl, so the claim is checkable rather than something to take on faith. The per-channel download notes distinguish where the channels actually stand: Nightly is built from main and is signed as of now, while Stable still points at 1.41.1, so those notes name the version instead of claiming it outright. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
192 lines
5.7 KiB
JavaScript
192 lines
5.7 KiB
JavaScript
const REPO = "praktimarc/kst4contest";
|
|
const API = `https://api.github.com/repos/${REPO}`;
|
|
const WORKFLOW_FILE = "nightly-artifacts.yml";
|
|
const WORKFLOW_BASENAME = "nightly-artifacts";
|
|
const BRANCH = "main";
|
|
const WORKFLOW_RUNS_URL = `https://github.com/${REPO}/actions/workflows/${WORKFLOW_FILE}`;
|
|
|
|
/**
|
|
* nightly.link (https://nightly.link) mirrors the most recent successful
|
|
* GitHub Actions artifact for a given workflow/branch/artifact-name as a
|
|
* plain public download, with no GitHub account required. It always follows
|
|
* the latest successful run on its own, so these URLs stay correct without
|
|
* this site having to resolve a run ID at build time.
|
|
*/
|
|
const NIGHTLY_LINK_BASE = `https://nightly.link/${REPO}/workflows/${WORKFLOW_BASENAME}/${BRANCH}`;
|
|
|
|
/**
|
|
* Static metadata for the artifacts produced by
|
|
* .github/workflows/nightly-artifacts.yml, keyed by the upload-artifact name
|
|
* used in that workflow. Anything not listed here (e.g. the raw
|
|
* flatpak-ostree-repo folder) is skipped on the nightly page.
|
|
*/
|
|
const ARTIFACT_INFO = {
|
|
"windows-zip": {
|
|
os: "Windows",
|
|
format: "ZIP x64",
|
|
icon: "🪟",
|
|
note: "Extract the archive, then start praktiKST.exe. No separate Java installation is required.",
|
|
order: 1
|
|
},
|
|
"linux-appimage": {
|
|
os: "Linux",
|
|
format: "AppImage x86_64",
|
|
icon: "🐧",
|
|
note: "Portable build. Make the downloaded file executable before the first launch.",
|
|
order: 2
|
|
},
|
|
flatpakref: {
|
|
os: "Linux",
|
|
format: "Flatpak (.flatpakref, nightly branch)",
|
|
icon: "🐧",
|
|
note: "Adds the nightly branch of the KST4Contest Flatpak repo. See the installation guide for the flatpak CLI alternative.",
|
|
order: 3
|
|
},
|
|
"linux-debian": {
|
|
os: "Debian / Ubuntu",
|
|
format: "DEB amd64",
|
|
icon: "📦",
|
|
note: "Native package for Debian, Ubuntu and distributions based on them.",
|
|
order: 4
|
|
},
|
|
"linux-fedora": {
|
|
os: "Fedora",
|
|
format: "RPM x86_64",
|
|
icon: "📦",
|
|
note: "Native package built for Fedora and compatible RPM-based systems.",
|
|
order: 5
|
|
},
|
|
"linux-arch": {
|
|
os: "Arch Linux",
|
|
format: "pkg.tar.zst",
|
|
icon: "📦",
|
|
note: "Package for direct installation with pacman.",
|
|
order: 6
|
|
},
|
|
"macos-dmg-macos-latest": {
|
|
os: "macOS Apple Silicon",
|
|
format: "DMG arm64",
|
|
icon: "🍎",
|
|
note: "Best-effort build for Apple Silicon Macs. Signed and notarized by Apple.",
|
|
order: 7
|
|
},
|
|
"macos-dmg-macos-15-intel": {
|
|
os: "macOS Intel",
|
|
format: "DMG x86_64",
|
|
icon: "🍎",
|
|
note: "Best-effort build for Intel Macs. Signed and notarized by Apple.",
|
|
order: 8
|
|
}
|
|
};
|
|
|
|
function buildItems() {
|
|
return Object.entries(ARTIFACT_INFO)
|
|
.map(([name, info]) => ({
|
|
...info,
|
|
name,
|
|
url: `${NIGHTLY_LINK_BASE}/${name}.zip`
|
|
}))
|
|
.sort((a, b) => a.order - b.order);
|
|
}
|
|
|
|
function authHeaders() {
|
|
const headers = { Accept: "application/vnd.github+json" };
|
|
|
|
if (process.env.GITHUB_TOKEN) {
|
|
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
function formatSize(bytes) {
|
|
if (bytes < 1024 * 1024) {
|
|
return `${(bytes / 1024).toFixed(0)} KB`;
|
|
}
|
|
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
/**
|
|
* Best-effort lookup of the run behind the current nightly.link downloads,
|
|
* purely to show a commit/date on the page. The download links themselves
|
|
* (buildItems) do not depend on this succeeding.
|
|
*/
|
|
async function fetchLatestRunMeta(headers) {
|
|
const runsRes = await fetch(
|
|
`${API}/actions/workflows/${WORKFLOW_FILE}/runs?branch=${BRANCH}&status=success&per_page=1`,
|
|
{ headers }
|
|
);
|
|
|
|
if (!runsRes.ok) {
|
|
throw new Error(`workflow runs request failed: ${runsRes.status}`);
|
|
}
|
|
|
|
const runsData = await runsRes.json();
|
|
const run = runsData.workflow_runs && runsData.workflow_runs[0];
|
|
|
|
if (!run) {
|
|
throw new Error("no successful nightly run found");
|
|
}
|
|
|
|
const meta = {
|
|
runUrl: run.html_url,
|
|
commitUrl: `https://github.com/${REPO}/commit/${run.head_sha}`,
|
|
shortSha: run.head_sha.slice(0, 7),
|
|
createdAt: run.created_at
|
|
};
|
|
|
|
try {
|
|
const artifactsRes = await fetch(
|
|
`${API}/actions/runs/${run.id}/artifacts?per_page=100`,
|
|
{ headers }
|
|
);
|
|
|
|
if (artifactsRes.ok) {
|
|
const artifactsData = await artifactsRes.json();
|
|
const sizeByName = {};
|
|
|
|
for (const artifact of artifactsData.artifacts || []) {
|
|
if (!artifact.expired) {
|
|
sizeByName[artifact.name] = formatSize(artifact.size_in_bytes);
|
|
}
|
|
}
|
|
|
|
meta.sizeByName = sizeByName;
|
|
}
|
|
} catch {
|
|
// Sizes are a nice-to-have; the run metadata above is still useful without them.
|
|
}
|
|
|
|
return meta;
|
|
}
|
|
|
|
module.exports = async function () {
|
|
const items = buildItems();
|
|
|
|
let meta = null;
|
|
|
|
try {
|
|
meta = await fetchLatestRunMeta(authHeaders());
|
|
} catch (err) {
|
|
console.warn(
|
|
`[nightly] Could not load latest nightly run metadata (commit/date will be omitted). ` +
|
|
`Download links are unaffected since they are served by nightly.link: ${err.message}`
|
|
);
|
|
}
|
|
|
|
if (meta && meta.sizeByName) {
|
|
for (const item of items) {
|
|
if (meta.sizeByName[item.name]) {
|
|
item.size = meta.sizeByName[item.name];
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
runsUrl: WORKFLOW_RUNS_URL,
|
|
meta,
|
|
items
|
|
};
|
|
};
|