splux-site · log · files · refs · tags · releases · people · languages · GitHub · commit
4511a2d
Rebuild news every five minutes and refresh it from GitHub in the browser.
Parents
Stat
.github/workflows/pages.yml | 10 +- README.md | 24 +++-- site/assets/live.js | 237 ++++++++++++++++++++++++++++++++++++++++++++ site/download/index.html | 5 +- site/index.html | 9 +- site/news/index.html | 7 +- tools/build-site.sh | 17 ++-- tools/collect-news.sh | 68 ++++++++++--- tools/news.awk | 4 +- 9 files changed, 333 insertions(+), 48 deletions(-)
Diff
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 82557d3..0c57e41 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -4,7 +4,7 @@ on:
push:
branches: [main]
schedule:
- - cron: "7,27,47 * * * *"
+ - cron: "*/5 * * * *"
workflow_dispatch:
repository_dispatch:
types: [rebuild]
@@ -24,22 +24,22 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
- fetch-depth: 25
+ fetch-depth: 0
- uses: actions/checkout@v4
with:
repository: RobertFlexx/sps-core
path: vendor/core
- fetch-depth: 25
+ fetch-depth: 0
- uses: actions/checkout@v4
with:
repository: RobertFlexx/sps-extra
path: vendor/extra
- fetch-depth: 25
+ fetch-depth: 0
- uses: actions/checkout@v4
with:
repository: RobertFlexx/SPS
path: vendor/sps
- fetch-depth: 25
+ fetch-depth: 0
- name: Build site
env:
GH_TOKEN: ${{ github.token }}
diff --git a/README.md b/README.md
index afb21a6..d326e8a 100644
--- a/README.md
+++ b/README.md
@@ -30,28 +30,32 @@ ISO names are stable:
- https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-plasma.iso
- https://github.com/RobertFlexx/SPS/releases/latest/download/SHA256SUMS
-The visible tag name is filled at build time from the GitHub Releases API.
+The visible tag name is filled at build time from the GitHub Releases API
+and refreshed from GitHub when you open the page.
## News
`news/` is generated at build time from GitHub, not edited by hand:
-- SPS live ISO releases (`RobertFlexx/SPS` GitHub Releases)
-- recent commits on SPS, sps-core, sps-extra, and this repository
+- every published SPS live ISO release (`RobertFlexx/SPS` GitHub Releases)
+- every commit on SPS, sps-core, sps-extra, and this repository that
+ Git or the GitHub API can see
-The Pages workflow rebuilds about every twenty minutes, and also on
-push and `workflow_dispatch`. A new ISO tag shows up here without
-committing to splux-site. Atom feed: `/news/atom.xml`.
+The Pages workflow rebuilds every five minutes, and also on push,
+`workflow_dispatch`, and a `rebuild` repository dispatch. The news and
+download pages also ask GitHub for anything newer when they load, so a
+missed cron job cannot hide a new ISO. Atom feed: `/news/atom.xml`.
To rebuild immediately from another repository, send a
`repository_dispatch` of type `rebuild` to `RobertFlexx/splux-site`.
-SPS does that after a live ISO release when the `SPLUX_SITE_TOKEN`
-secret is set (a PAT that can dispatch workflows on this repo).
-Without that secret, the schedule still picks releases up.
+SPS does that after a live ISO release and on push to `main` when the
+`SPLUX_SITE_TOKEN` secret is set (a PAT that can dispatch workflows on
+this repo). Without that secret, the five-minute schedule and the
+in-page GitHub refresh still pick releases up.
## GitHub Pages
-The workflow rebuilds the package index and news feed on a schedule
+The workflow rebuilds the package index and news feed every five minutes
and on push to `main`.
The custom domain is `https://splux.robertflexx.dev`. Point a DNS CNAME
for `splux` at `robertflexx.github.io`, then GitHub Pages serves this
diff --git a/site/assets/live.js b/site/assets/live.js
new file mode 100644
index 0000000..82cfe4b
--- /dev/null
+++ b/site/assets/live.js
@@ -0,0 +1,237 @@
+// Refresh ISO tags and news from GitHub so a delayed Pages build
+// cannot hide a new release or commit. Baked HTML stays if this fails.
+(function () {
+ var API = "https://api.github.com/repos/RobertFlexx/";
+ var ACCEPT = { Accept: "application/vnd.github+json" };
+ var REPOS = [
+ { path: "SPS", label: "SPS" },
+ { path: "sps-core", label: "sps-core" },
+ { path: "sps-extra", label: "sps-extra" },
+ { path: "splux-site", label: "splux-site" }
+ ];
+
+ var feed = document.getElementById("live-feed");
+ var brief = document.getElementById("live-brief");
+ var isoEl = document.getElementById("live-iso");
+ var tagEl = document.getElementById("live-tag");
+ var dateEl = document.getElementById("live-date");
+ if (!feed && !brief && !isoEl && !tagEl)
+ return;
+
+ function esc(s) {
+ return String(s)
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, """);
+ }
+
+ function dayOf(iso) {
+ var d = String(iso || "");
+ var t = d.indexOf("T");
+ return t === -1 ? d : d.slice(0, t);
+ }
+
+ function json(url) {
+ return fetch(url, { headers: ACCEPT, cache: "no-store" }).then(function (r) {
+ if (!r.ok)
+ throw new Error(String(r.status));
+ return r.json();
+ });
+ }
+
+ function firstLine(msg) {
+ msg = String(msg || "");
+ var n = msg.indexOf("\n");
+ return n === -1 ? msg : msg.slice(0, n);
+ }
+
+ function readItems(root) {
+ var items = [];
+ if (!root)
+ return items;
+ var articles = root.querySelectorAll("article.item");
+ for (var i = 0; i < articles.length; i++) {
+ var a = articles[i];
+ var time = a.querySelector("time");
+ var kind = a.querySelector(".kind");
+ var link = a.querySelector("h3 a");
+ var summary = a.querySelector(".summary");
+ var meta = a.querySelector(".meta");
+ if (!time || !link)
+ continue;
+ var repo = "";
+ if (meta) {
+ repo = meta.textContent.replace(time.textContent, "");
+ if (kind)
+ repo = repo.replace(kind.textContent, "");
+ repo = repo.replace(/\s+/g, " ").trim();
+ }
+ items.push({
+ iso: time.getAttribute("datetime") || "",
+ kind: kind ? kind.textContent : "commit",
+ repo: repo,
+ url: link.getAttribute("href") || "",
+ title: link.textContent || "",
+ summary: summary ? summary.textContent : ""
+ });
+ }
+ return items;
+ }
+
+ function renderItem(it, hideSummary) {
+ var html = "<article class=\"item\">";
+ html += "<p class=\"meta\"><time datetime=\"" + esc(it.iso) + "\">" +
+ esc(dayOf(it.iso)) + "</time> <span class=\"kind\">" +
+ esc(it.kind) + "</span> " + esc(it.repo) + "</p>";
+ html += "<h3><a href=\"" + esc(it.url) + "\">" + esc(it.title) + "</a></h3>";
+ if (!hideSummary && it.summary)
+ html += "<p class=\"summary\">" + esc(it.summary) + "</p>";
+ html += "</article>";
+ return html;
+ }
+
+ function merge(base, extra) {
+ var seen = {};
+ var out = [];
+ function add(it) {
+ if (!it || !it.url || seen[it.url])
+ return;
+ seen[it.url] = true;
+ out.push(it);
+ }
+ var i;
+ for (i = 0; i < extra.length; i++)
+ add(extra[i]);
+ for (i = 0; i < base.length; i++)
+ add(base[i]);
+ out.sort(function (a, b) {
+ if (a.iso < b.iso)
+ return 1;
+ if (a.iso > b.iso)
+ return -1;
+ return 0;
+ });
+ return out;
+ }
+
+ function paint(root, items, limit, hideSummary) {
+ if (!root)
+ return;
+ var n = items.length;
+ if (limit && n > limit)
+ n = limit;
+ var html = "<div class=\"feed\">";
+ for (var i = 0; i < n; i++)
+ html += renderItem(items[i], hideSummary);
+ html += "</div>";
+ root.innerHTML = html;
+ }
+
+ function shortSha(sha) {
+ sha = String(sha || "");
+ return sha.length > 7 ? sha.slice(0, 7) : sha;
+ }
+
+ function setSha(id, repoPath, sha) {
+ var el = document.getElementById(id);
+ if (!el || !sha)
+ return;
+ el.innerHTML = "<a href=\"https://github.com/RobertFlexx/" +
+ esc(repoPath) + "/commit/" + esc(sha) + "\">" +
+ esc(shortSha(sha)) + "</a>";
+ }
+
+ function setIso(rel) {
+ if (!rel)
+ return;
+ var tag = rel.tag_name || rel.name || "";
+ var when = dayOf(rel.published_at || "");
+ if (isoEl && tag) {
+ isoEl.innerHTML = "<a href=\"https://github.com/RobertFlexx/SPS/releases/latest\">" +
+ esc(tag) + "</a> (" + esc(when) + ")";
+ }
+ if (tagEl && tag)
+ tagEl.textContent = tag;
+ if (dateEl && when)
+ dateEl.textContent = when;
+ }
+
+ function fromReleases(list) {
+ var items = [];
+ for (var i = 0; i < list.length; i++) {
+ var r = list[i];
+ if (!r || r.draft)
+ continue;
+ var body = r.body ? String(r.body).replace(/\s+/g, " ").slice(0, 900) : "";
+ items.push({
+ iso: r.published_at || "",
+ kind: "release",
+ repo: "SPS",
+ url: r.html_url || "",
+ title: r.name || r.tag_name || "",
+ summary: body
+ });
+ }
+ return items;
+ }
+
+ function fromCommits(list, label) {
+ var items = [];
+ for (var i = 0; i < list.length; i++) {
+ var c = list[i];
+ if (!c)
+ continue;
+ var commit = c.commit || {};
+ var when = (commit.committer && commit.committer.date) ||
+ (commit.author && commit.author.date) || "";
+ items.push({
+ iso: when,
+ kind: "commit",
+ repo: label,
+ url: c.html_url || "",
+ title: firstLine(commit.message),
+ summary: ""
+ });
+ }
+ return items;
+ }
+
+ var req = [json(API + "SPS/releases?per_page=100")];
+ for (var r = 0; r < REPOS.length; r++)
+ req.push(json(API + REPOS[r].path + "/commits?per_page=100"));
+
+ Promise.all(req).then(function (rows) {
+ var releases = rows[0] || [];
+ var extra = fromReleases(releases);
+ for (var i = 0; i < REPOS.length; i++)
+ extra = extra.concat(fromCommits(rows[i + 1] || [], REPOS[i].label));
+
+ var baked = readItems(feed).concat(readItems(brief));
+ var items = merge(baked, extra);
+ paint(feed, items, 0, false);
+ paint(brief, items, 8, true);
+
+ var latest = null;
+ for (var j = 0; j < releases.length; j++) {
+ if (releases[j] && !releases[j].draft) {
+ latest = releases[j];
+ break;
+ }
+ }
+ setIso(latest);
+
+ for (var k = 0; k < REPOS.length; k++) {
+ var commits = rows[k + 1] || [];
+ var sha = commits[0] && commits[0].sha;
+ if (REPOS[k].path === "SPS")
+ setSha("live-sps", "SPS", sha);
+ else if (REPOS[k].path === "sps-core")
+ setSha("live-core", "sps-core", sha);
+ else if (REPOS[k].path === "sps-extra")
+ setSha("live-extra", "sps-extra", sha);
+ }
+ }).catch(function () {
+ /* keep the HTML from the last site build */
+ });
+})();
diff --git a/site/download/index.html b/site/download/index.html
index 533c400..6efee0f 100644
--- a/site/download/index.html
+++ b/site/download/index.html
@@ -16,8 +16,8 @@
<h2>Download</h2>
<hr class="rule">
<p>Images are hybrid ISOs from <a href="https://github.com/RobertFlexx/SPS">RobertFlexx/SPS</a>. The filenames below are stable. GitHub's <code>releases/latest/download</code> URLs always resolve to the newest published files of those names.</p>
-<div class="dl-row"><span class="muted">Latest tag</span><span>live-2026-09-06-1</span></div>
-<div class="dl-row"><span class="muted">Released</span><span>2026-09-06</span></div>
+<div class="dl-row"><span class="muted">Latest tag</span><span id="live-tag">live-2026-09-06-1</span></div>
+<div class="dl-row"><span class="muted">Released</span><span id="live-date">2026-09-06</span></div>
<div class="dl-row"><span class="muted">Architecture</span><span>x86_64</span></div>
<h2>Images</h2>
<hr class="rule">
@@ -37,5 +37,6 @@ Plasma/Wayland live session plus the same installer path. Needs a KMS device (vi
</main>
@@FOOTER@@
</div>
+<script src="../../../../assets/live.js"></script>
</body>
</html>
diff --git a/site/index.html b/site/index.html
index dbb4a4b..979c9d4 100644
--- a/site/index.html
+++ b/site/index.html
@@ -27,8 +27,8 @@
<h2 id="download"><span class="num">[002]</span> Download</h2>
<hr class="rule">
<p>Live images are published from the SPS repository. Asset names stay the same from release to release, so these links always point at the newest published files.</p>
-<div class="dl-row"><span class="muted">Latest tag</span><span>live-2026-09-06-1</span></div>
-<div class="dl-row"><span class="muted">Released</span><span>2026-09-06</span></div>
+<div class="dl-row"><span class="muted">Latest tag</span><span id="live-tag">live-2026-09-06-1</span></div>
+<div class="dl-row"><span class="muted">Released</span><span id="live-date">2026-09-06</span></div>
<div class="dl-row"><span class="muted">Architecture</span><span>x86_64</span></div>
<p>
<a href="https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-tty.iso">sps-live-tty.iso</a>
@@ -102,12 +102,15 @@ this website
<h2 id="news"><span class="num">[008]</span> News</h2>
<hr class="rule">
@@NEWS_INFO@@
+<div id="live-brief">
@@NEWS_BRIEF@@
+</div>
<p><a href="../../../../news/">Full news feed</a> · <a href="../../../../news/atom.xml">Atom</a></p>
-<p class="note">The feed is generated from GitHub when this site builds. New ISO releases and git commits appear here without a manual site edit.</p>
+<p class="note">The feed is filled from GitHub when this site builds, and this page also asks GitHub for anything newer when you open it.</p>
</main>
@@FOOTER@@
</div>
+<script src="../../../../assets/live.js"></script>
</body>
</html>
diff --git a/site/news/index.html b/site/news/index.html
index bbea957..ce1f8cc 100644
--- a/site/news/index.html
+++ b/site/news/index.html
@@ -16,14 +16,17 @@
<main id="main">
<h2>News</h2>
<hr class="rule">
-<p>This page is filled from GitHub when the site builds: SPS live ISO releases, plus recent commits on SPS, sps-core, sps-extra, and this website. Nobody has to edit it for a new snapshot or commit to show up.</p>
-<p class="note">The Pages workflow rebuilds on a schedule and on push. <a href="../../../../news/atom.xml">Atom feed</a>.</p>
+<p>This page is filled from GitHub: every published SPS live ISO release, plus commits on SPS, sps-core, sps-extra, and this website. Nobody has to edit it for a new snapshot or commit to show up.</p>
+<p class="note">The Pages workflow rebuilds every five minutes, on push, and when SPS asks it to. This page also reads GitHub when you open it, so a delayed rebuild cannot hide a new ISO. <a href="../../../../news/atom.xml">Atom feed</a>.</p>
@@NEWS_INFO@@
<h2>Feed</h2>
<hr class="rule">
+<div id="live-feed">
@@NEWS@@
+</div>
</main>
@@FOOTER@@
</div>
+<script src="../../../../assets/live.js"></script>
</body>
</html>
diff --git a/tools/build-site.sh b/tools/build-site.sh
index a64ddab..caae463 100755
--- a/tools/build-site.sh
+++ b/tools/build-site.sh
@@ -21,25 +21,24 @@ SITE_URL=${SITE_URL:-https://splux.robertflexx.dev}
clone_tree() {
url=$1
dest=$2
- depth=${3:-25}
if [ -d "$dest" ]; then
return 0
fi
mkdir -p vendor
printf '%s\n' "cloning $url" >&2
- git clone --depth "$depth" "$url" "$dest"
+ git clone "$url" "$dest"
}
if [ ! -d "$CORE" ]; then
- clone_tree "$CORE_GIT.git" vendor/core 25
+ clone_tree "$CORE_GIT.git" vendor/core
CORE=vendor/core
fi
if [ ! -d "$EXTRA" ]; then
- clone_tree "$EXTRA_GIT.git" vendor/extra 25
+ clone_tree "$EXTRA_GIT.git" vendor/extra
EXTRA=vendor/extra
fi
if [ ! -d "$SPS" ]; then
- clone_tree "$SPS_GIT.git" vendor/sps 25
+ clone_tree "$SPS_GIT.git" vendor/sps
SPS=vendor/sps
fi
@@ -207,10 +206,10 @@ site_sha=$(short_sha "$SITE")
cat >"$tmp/news-info.html" <<EOF
<div class="info">
-<div class="dl-row"><span class="muted">Latest ISO</span><span><a href="$SPS_GIT/releases/latest">$tag</a> ($date)</span></div>
-<div class="dl-row"><span class="muted">SPS</span><span><a href="$SPS_GIT/commit/$sps_sha">$sps_sha</a></span></div>
-<div class="dl-row"><span class="muted">sps-core</span><span><a href="$CORE_GIT/commit/$core_sha">$core_sha</a></span></div>
-<div class="dl-row"><span class="muted">sps-extra</span><span><a href="$EXTRA_GIT/commit/$extra_sha">$extra_sha</a></span></div>
+<div class="dl-row"><span class="muted">Latest ISO</span><span id="live-iso"><a href="$SPS_GIT/releases/latest">$tag</a> ($date)</span></div>
+<div class="dl-row"><span class="muted">SPS</span><span id="live-sps"><a href="$SPS_GIT/commit/$sps_sha">$sps_sha</a></span></div>
+<div class="dl-row"><span class="muted">sps-core</span><span id="live-core"><a href="$CORE_GIT/commit/$core_sha">$core_sha</a></span></div>
+<div class="dl-row"><span class="muted">sps-extra</span><span id="live-extra"><a href="$EXTRA_GIT/commit/$extra_sha">$extra_sha</a></span></div>
<div class="dl-row"><span class="muted">Packages</span><span>$npkgs ($ncore core, $nextra extra)</span></div>
<div class="dl-row"><span class="muted">Site build</span><span>$generated ($site_sha)</span></div>
</div>
diff --git a/tools/collect-news.sh b/tools/collect-news.sh
old mode 100644
new mode 100755
index f911828..6038e1c
--- a/tools/collect-news.sh
+++ b/tools/collect-news.sh
@@ -2,6 +2,8 @@
# Collect Splux releases and git history into TSV for the news page.
# Fields: epoch iso kind repo url title summary
# Env: CORE EXTRA SPS SITE (git work trees). GH_TOKEN optional via gh.
+# Pulls every non-draft SPS release and every commit available from the
+# local clones and from the GitHub API, then drops duplicate URLs.
set -eu
@@ -11,9 +13,10 @@ SPS=${SPS:-vendor/sps}
SITE=${SITE:-.}
tab=$(printf '\t')
-out=$(mktemp "${TMPDIR:-/tmp}/splux-news.XXXXXX") || exit 1
-trap 'rm -f "$out" "$rel"' 0 HUP INT TERM
-rel=
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/splux-news.XXXXXX") || exit 1
+out=$tmp/out
+trap 'rm -rf "$tmp"' 0 HUP INT TERM
+: >"$out"
emit() {
epoch=$1
@@ -47,9 +50,8 @@ git_commits() {
dir=$1
repo=$2
baseurl=$3
- n=${4:-12}
git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
- git -C "$dir" log -n "$n" --format='%ct%x09%cI%x09%H%x09%s' 2>/dev/null |
+ git -C "$dir" log --format='%ct%x09%cI%x09%H%x09%s' 2>/dev/null |
while IFS=$tab read -r epoch iso hash subject || [ -n "${epoch-}" ]
do
[ -n "${epoch-}" ] || continue
@@ -60,20 +62,32 @@ git_commits() {
done
}
-git_commits "$SPS" SPS https://github.com/RobertFlexx/SPS 15
-git_commits "$CORE" sps-core https://github.com/RobertFlexx/sps-core 15
-git_commits "$EXTRA" sps-extra https://github.com/RobertFlexx/sps-extra 15
-git_commits "$SITE" splux-site https://github.com/RobertFlexx/splux-site 8
+read_commit_tsv() {
+ repo=$1
+ file=$2
+ while IFS=$tab read -r published url title || [ -n "${published-}" ]
+ do
+ [ -n "${published-}" ] || continue
+ [ -n "${url-}" ] || continue
+ [ -n "${title-}" ] || title=$url
+ epoch=$(epoch_of "$published")
+ emit "$epoch" "$published" commit "$repo" "$url" "$title" ""
+ done <"$file"
+}
+
+git_commits "$SPS" SPS https://github.com/RobertFlexx/SPS
+git_commits "$CORE" sps-core https://github.com/RobertFlexx/sps-core
+git_commits "$EXTRA" sps-extra https://github.com/RobertFlexx/sps-extra
+git_commits "$SITE" splux-site https://github.com/RobertFlexx/splux-site
if command -v gh >/dev/null 2>&1; then
- rel=$(mktemp "${TMPDIR:-/tmp}/splux-rel.XXXXXX") || exit 1
- if gh api "repos/RobertFlexx/SPS/releases?per_page=25" --jq \
+ if gh api --paginate "repos/RobertFlexx/SPS/releases?per_page=100" --jq \
'.[] | select(.draft == false) | [
.published_at,
.html_url,
- (.name // .tag_name),
+ ((.name // .tag_name) | gsub("[\r\t]"; " ")),
((.body // "") | gsub("[\r\t]"; " ") | gsub("\n+"; " ") | .[0:900])
- ] | @tsv' >"$rel" 2>/dev/null
+ ] | @tsv' >"$tmp/rel"
then
while IFS=$tab read -r published url name body || [ -n "${published-}" ]
do
@@ -81,8 +95,32 @@ if command -v gh >/dev/null 2>&1; then
[ -n "${url-}" ] || continue
epoch=$(epoch_of "$published")
emit "$epoch" "$published" release SPS "$url" "$name" "$body"
- done <"$rel"
+ done <"$tmp/rel"
+ else
+ printf '%s\n' "collect-news: GitHub releases request failed" >&2
fi
+
+ api_commits() {
+ path=$1
+ label=$2
+ if gh api --paginate "repos/${path}/commits?per_page=100" --jq \
+ '.[] | [
+ .commit.committer.date,
+ .html_url,
+ ((.commit.message // "") | split("\n")[0] | gsub("[\r\t]"; " "))
+ ] | @tsv' >"$tmp/c"
+ then
+ read_commit_tsv "$label" "$tmp/c"
+ else
+ printf '%s\n' "collect-news: GitHub commits $path failed" >&2
+ fi
+ }
+
+ api_commits RobertFlexx/SPS SPS
+ api_commits RobertFlexx/sps-core sps-core
+ api_commits RobertFlexx/sps-extra sps-extra
+ api_commits RobertFlexx/splux-site splux-site
fi
-LC_ALL=C sort -t "$tab" -k1,1nr "$out"
+LC_ALL=C sort -t "$tab" -k1,1nr "$out" |
+awk -F '\t' 'NF >= 5 && $5 != "" && !seen[$5]++'
diff --git a/tools/news.awk b/tools/news.awk
index fcbff6f..cae80f8 100644
--- a/tools/news.awk
+++ b/tools/news.awk
@@ -8,8 +8,8 @@ BEGIN {
mode = "html"
if (siteurl == "")
siteurl = "https://splux.robertflexx.dev"
- html_limit = (mode == "brief") ? 8 : 60
- atom_limit = 40
+ html_limit = (mode == "brief") ? 8 : 10000
+ atom_limit = 200
}
function esc(s) {