splux-site · log · files · refs · tags · releases · people · languages · GitHub · commit
7e66877
Add the Splux website
Parents
Stat
.github/workflows/pages.yml | 51 ++++++++ .gitignore | 4 + LICENSE | 15 +++ README.md | 40 +++++++ site/404.html | 22 ++++ site/assets/favicon.png | Bin 0 -> 847 bytes site/assets/github.svg | 3 + site/assets/packages.js | 48 ++++++++ site/assets/sps.png | Bin 0 -> 2262 bytes site/assets/style.css | 281 ++++++++++++++++++++++++++++++++++++++++++++ site/docs/index.html | 35 ++++++ site/download/index.html | 41 +++++++ site/index.html | 105 +++++++++++++++++ site/install/index.html | 44 +++++++ site/news/index.html | 25 ++++ site/packages/index.html | 60 ++++++++++ site/source/index.html | 31 +++++ tools/build-site.sh | 257 ++++++++++++++++++++++++++++++++++++++++ tools/packages.awk | 203 ++++++++++++++++++++++++++++++++ tools/subst.awk | 48 ++++++++ 20 files changed, 1313 insertions(+)
Diff
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..8ad5bdb
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,51 @@
+name: pages
+
+on:
+ push:
+ branches: [main]
+ schedule:
+ - cron: "17 * * * *"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/checkout@v4
+ with:
+ repository: RobertFlexx/sps-core
+ path: vendor/core
+ - uses: actions/checkout@v4
+ with:
+ repository: RobertFlexx/sps-extra
+ path: vendor/extra
+ - name: Build site
+ env:
+ GH_TOKEN: ${{ github.token }}
+ CORE: vendor/core
+ EXTRA: vendor/extra
+ SITE_PREFIX: /splux-site
+ run: sh tools/build-site.sh
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: _site
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.page.outputs.page_url }}
+ steps:
+ - id: page
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a84bdd1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+_site/
+vendor/
+*.iso
+.DS_Store
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..aada0e7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2026 RobertFlexx
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6e1cfd0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,40 @@
+# Splux website
+
+Static site for Splux Linux and the official SPS package collections.
+
+This site was fully handmade by RobertFlexx.
+
+## Build
+
+```sh
+CORE=/tmp/sps-core EXTRA=/tmp/sps-extra ./tools/build-site.sh
+```
+
+If `CORE` and `EXTRA` are unset, the script clones
+`https://github.com/RobertFlexx/sps-core` and
+`https://github.com/RobertFlexx/sps-extra` into `vendor/`.
+
+Output is `_site/`. Preview:
+
+```sh
+python3 -m http.server -d _site
+```
+
+Python is only for local preview. The generator is POSIX shell and AWK.
+
+## Download links
+
+ISO names are stable:
+
+- https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-tty.iso
+- https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-tty-slim.iso
+- 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.
+
+## GitHub Pages
+
+The workflow rebuilds the package index on a schedule and on push to `main`.
+For a project site the Pages URL is `/splux-site/`. Set `SITE_PREFIX=/splux-site`
+in that workflow so the 404 page can still load CSS.
diff --git a/site/404.html b/site/404.html
new file mode 100644
index 0000000..addf7da
--- /dev/null
+++ b/site/404.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>404 - Splux Linux</title>
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+<h2>404</h2>
+<hr class="rule">
+<p>The requested page does not exist.</p>
+<p><a href="../../../../">home</a></p>
+</main>
+@@FOOTER@@
+</div>
+</body>
+</html>
diff --git a/site/assets/favicon.png b/site/assets/favicon.png
new file mode 100644
index 0000000..118edf0
Binary files /dev/null and b/site/assets/favicon.png differ
diff --git a/site/assets/github.svg b/site/assets/github.svg
new file mode 100644
index 0000000..7cf1f20
--- /dev/null
+++ b/site/assets/github.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="#ffff00" aria-hidden="true">
+ <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
+</svg>
diff --git a/site/assets/packages.js b/site/assets/packages.js
new file mode 100644
index 0000000..1ef7e54
--- /dev/null
+++ b/site/assets/packages.js
@@ -0,0 +1,48 @@
+// Package data is generated from the official repositories.
+(function () {
+ var form = document.getElementById("pkg-search");
+ var input = document.getElementById("q");
+ var table = document.getElementById("pkg-table");
+ var stats = document.getElementById("pkg-stats");
+ if (!form || !input || !table) return;
+
+ var rows = table.querySelectorAll("tbody tr");
+ var total = rows.length;
+
+ function currentRepo() {
+ var params = new URLSearchParams(window.location.search);
+ return params.get("repo") || "all";
+ }
+
+ function apply() {
+ var q = input.value.toLowerCase();
+ var repo = currentRepo();
+ var shown = 0;
+ for (var i = 0; i < rows.length; i++) {
+ var row = rows[i];
+ var text = row.getAttribute("data-search") || "";
+ var rowRepo = row.getAttribute("data-repo") || "";
+ var ok = text.indexOf(q) !== -1 &&
+ (repo === "all" || rowRepo === repo);
+ row.hidden = !ok;
+ if (ok) shown++;
+ }
+ if (stats) {
+ stats.textContent = shown + " of " + total + " packages";
+ }
+ }
+
+ var params = new URLSearchParams(window.location.search);
+ if (params.get("q")) input.value = params.get("q");
+
+ input.addEventListener("input", apply);
+ form.addEventListener("submit", function (e) {
+ e.preventDefault();
+ apply();
+ var next = new URL(window.location.href);
+ if (input.value) next.searchParams.set("q", input.value);
+ else next.searchParams.delete("q");
+ history.replaceState(null, "", next);
+ });
+ apply();
+})();
diff --git a/site/assets/sps.png b/site/assets/sps.png
new file mode 100644
index 0000000..cc2c4e7
Binary files /dev/null and b/site/assets/sps.png differ
diff --git a/site/assets/style.css b/site/assets/style.css
new file mode 100644
index 0000000..66e38ba
--- /dev/null
+++ b/site/assets/style.css
@@ -0,0 +1,281 @@
+/* Splux site. Black, gray, yellow. Monospace. No framework. */
+
+:root {
+ --bg: #0a0a0a;
+ --fg: #d6d6d6;
+ --muted: #8a8a8a;
+ --rule: #2a2a2a;
+ --accent: #ffff00;
+ --focus: #ffff00;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ background: var(--bg);
+ color: var(--fg);
+ font-family: ui-monospace, "SFMono-Regular", "Cascadia Mono",
+ "Liberation Mono", "DejaVu Sans Mono", "Adwaita Mono", monospace;
+ font-size: 15px;
+ line-height: 1.5;
+}
+
+body {
+ margin: 0;
+ padding: 1.5rem 1.25rem 3rem;
+}
+
+.wrap {
+ max-width: 920px;
+ margin: 0 auto;
+}
+
+a {
+ color: var(--accent);
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible {
+ outline: 1px solid var(--focus);
+ outline-offset: 2px;
+}
+
+.skip {
+ position: absolute;
+ left: -999px;
+}
+
+.skip:focus {
+ left: 1rem;
+ top: 1rem;
+ background: var(--bg);
+ padding: 0.25rem 0.5rem;
+}
+
+header.top {
+ display: flex;
+ align-items: flex-start;
+ gap: 1.25rem;
+ margin-bottom: 1rem;
+ border-bottom: 1px solid var(--rule);
+ padding-bottom: 1rem;
+}
+
+.logo {
+ margin: 0;
+ font-size: 11px;
+ line-height: 1.15;
+ letter-spacing: 0;
+ color: #9a9a9a;
+ white-space: pre;
+}
+
+.logo .p {
+ color: var(--accent);
+}
+
+.identity h1 {
+ margin: 0;
+ font-size: 1.15rem;
+ font-weight: 700;
+ color: var(--fg);
+}
+
+.identity p {
+ margin: 0.15rem 0 0;
+ color: var(--muted);
+ font-size: 0.95rem;
+}
+
+nav.row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem 1rem;
+ margin: 1rem 0 1.5rem;
+ color: var(--muted);
+}
+
+nav.row a {
+ color: var(--accent);
+}
+
+nav.row a[aria-current="page"] {
+ color: var(--fg);
+ text-decoration: underline;
+}
+
+nav.row .gh {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+nav.row .gh img {
+ display: block;
+ width: 14px;
+ height: 14px;
+}
+
+h2 {
+ margin: 1.75rem 0 0.35rem;
+ font-size: 1rem;
+ font-weight: 700;
+ color: var(--accent);
+}
+
+h2 .num {
+ color: var(--muted);
+ font-weight: 400;
+}
+
+hr.rule {
+ border: 0;
+ border-top: 1px solid var(--rule);
+ margin: 0.35rem 0 0.85rem;
+}
+
+p, li {
+ max-width: 78ch;
+}
+
+.muted, .note {
+ color: var(--muted);
+}
+
+pre, code {
+ font-family: inherit;
+}
+
+pre.block {
+ background: #111;
+ border: 1px solid var(--rule);
+ padding: 0.75rem 1rem;
+ overflow: auto;
+ color: var(--fg);
+}
+
+table.pkgs {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.92rem;
+}
+
+table.pkgs th {
+ text-align: left;
+ color: var(--muted);
+ font-weight: 400;
+ border-bottom: 1px solid var(--rule);
+ padding: 0.3rem 0.6rem 0.3rem 0;
+}
+
+table.pkgs td {
+ padding: 0.25rem 0.6rem 0.25rem 0;
+ border-bottom: 1px solid #1a1a1a;
+ vertical-align: top;
+}
+
+table.pkgs a.name {
+ color: var(--accent);
+}
+
+.filters {
+ margin: 0.75rem 0;
+ color: var(--muted);
+}
+
+.filters a {
+ margin-right: 0.75rem;
+}
+
+.filters a[aria-current="true"] {
+ color: var(--fg);
+ text-decoration: underline;
+}
+
+label.search {
+ display: block;
+ margin: 0.75rem 0;
+ color: var(--muted);
+}
+
+label.search input {
+ display: block;
+ width: 100%;
+ max-width: 28rem;
+ margin-top: 0.25rem;
+ background: #111;
+ color: var(--fg);
+ border: 1px solid var(--rule);
+ padding: 0.35rem 0.5rem;
+ font: inherit;
+}
+
+.stats {
+ color: var(--muted);
+ margin: 0.5rem 0 1rem;
+}
+
+dl.meta dt {
+ color: var(--muted);
+ float: left;
+ clear: left;
+ width: 11rem;
+}
+
+dl.meta dd {
+ margin: 0 0 0.25rem 11rem;
+}
+
+ul.plain {
+ list-style: none;
+ padding: 0;
+ margin: 0.5rem 0;
+}
+
+ul.plain li {
+ margin: 0.2rem 0;
+}
+
+footer {
+ margin-top: 2.5rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--rule);
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+footer p {
+ margin: 0.2rem 0;
+}
+
+.dl-row {
+ display: grid;
+ grid-template-columns: 12rem 1fr;
+ gap: 0.25rem 1rem;
+ margin: 0.2rem 0;
+}
+
+@media (max-width: 640px) {
+ .dl-row,
+ dl.meta dt,
+ dl.meta dd {
+ display: block;
+ width: auto;
+ float: none;
+ margin-left: 0;
+ }
+
+ table.pkgs {
+ display: block;
+ overflow-x: auto;
+ }
+}
diff --git a/site/docs/index.html b/site/docs/index.html
new file mode 100644
index 0000000..4dce92c
--- /dev/null
+++ b/site/docs/index.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Documentation - Splux Linux</title>
+<meta name="description" content="Splux and SPS documentation.">
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<a class="skip" href="#main">Skip to content</a>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+<h2>Documentation</h2>
+<hr class="rule">
+<ul class="plain">
+<li><a href="../../../../install/">Installation</a></li>
+<li><a href="../../../../packages/">Package browser</a></li>
+<li><a href="https://github.com/RobertFlexx/SPS/blob/main/README.md">SPS README</a></li>
+<li><a href="https://github.com/RobertFlexx/SPS/blob/main/DESIGN.md">SPS 1.0 formats and behavior</a></li>
+<li><a href="https://github.com/RobertFlexx/SPS/blob/main/PACKAGE-MAINTAINER.md">Package maintainer guide</a></li>
+<li><a href="https://github.com/RobertFlexx/SPS/blob/main/MANIFESTO.md">SPS manifesto</a></li>
+<li><a href="https://github.com/RobertFlexx/sps-core/blob/main/README.md">core collection</a></li>
+<li><a href="https://github.com/RobertFlexx/sps-extra/blob/main/README.md">extra collection</a></li>
+<li><a href="../../../../source/">Source repositories</a></li>
+</ul>
+<p>SPS stores package definitions in ordinary Git repositories. The core tree contains the base system and toolchain. Extra contains packages outside the base installation.</p>
+<p>Fastfetch is a native SPS package. On a Splux install it counts SPS packages in its Packages module and still reports Flatpak, Homebrew, Nix, and other managers when they are present. The system default is gray labels and a yellow P in the SPS logo. A user config in <code>~/.config/fastfetch/</code> overrides that default. Fastfetch is not started from shell profiles.</p>
+</main>
+@@FOOTER@@
+</div>
+</body>
+</html>
diff --git a/site/download/index.html b/site/download/index.html
new file mode 100644
index 0000000..533c400
--- /dev/null
+++ b/site/download/index.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Download - Splux Linux</title>
+<meta name="description" content="Download Splux Linux live images and checksums.">
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<a class="skip" href="#main">Skip to content</a>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+<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">Architecture</span><span>x86_64</span></div>
+<h2>Images</h2>
+<hr class="rule">
+<p><a href="https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-tty.iso">sps-live-tty.iso</a><br>
+Console live image with sps-core, sps-extra, and a seed compiler so <code>setup</code> can build packages.</p>
+<p><a href="https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-tty-slim.iso">sps-live-tty-slim.iso</a><br>
+Small console image without the seed compiler. <code>setup</code> needs the network to fetch recipes and a toolchain.</p>
+<p><a href="https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-plasma.iso">sps-live-plasma.iso</a><br>
+Plasma/Wayland live session plus the same installer path. Needs a KMS device (virtio-gpu or a real GPU).</p>
+<h2>Checksums</h2>
+<hr class="rule">
+<p><a href="https://github.com/RobertFlexx/SPS/releases/latest/download/SHA256SUMS">SHA256SUMS</a> is produced from the uploaded ISOs. Verify on the machine that downloads the image:</p>
+<pre class="block">sha256sum -c SHA256SUMS</pre>
+<p>Signatures are not published yet.</p>
+<p><a href="https://github.com/RobertFlexx/SPS/releases/latest">GitHub release notes</a></p>
+<p class="note">These snapshots track installer and live-session work. They are not a numbered stable Splux release.</p>
+</main>
+@@FOOTER@@
+</div>
+</body>
+</html>
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..11e27dd
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Splux Linux</title>
+<meta name="description" content="Splux is a source-based Linux distribution using SPS, the Source Package System.">
+<meta property="og:title" content="Splux Linux">
+<meta property="og:description" content="A source-based Linux distribution using SPS.">
+<meta property="og:image" content="../../../../assets/sps.png">
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<a class="skip" href="#main">Skip to content</a>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+
+<h2 id="about"><span class="num">[001]</span> About</h2>
+<hr class="rule">
+<p>Splux is an independent source-based Linux distribution using SPS, the Source Package System.</p>
+<p>SPS is built from small POSIX shell and AWK tools. Packages are described by text recipes in Git repositories and built locally. The system is intended for users who want to understand and maintain their installation directly.</p>
+<p>SPS the package system is not locked to Splux. The same tools can be used on other Unix-like trees. Splux is the distribution that uses SPS for its installer, live images, and official package collections.</p>
+
+<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">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>
+(console image with recipes and a compiler)<br>
+<a href="https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-tty-slim.iso">sps-live-tty-slim.iso</a>
+(small console image; setup needs the network)<br>
+<a href="https://github.com/RobertFlexx/SPS/releases/latest/download/sps-live-plasma.iso">sps-live-plasma.iso</a>
+(Plasma/Wayland live session)<br>
+<a href="https://github.com/RobertFlexx/SPS/releases/latest/download/SHA256SUMS">SHA256SUMS</a><br>
+<a href="https://github.com/RobertFlexx/SPS/releases/latest">release notes</a>
+</p>
+<p class="note">These are development live snapshots, not a numbered stable distro release. Installation still compiles packages from source. A Plasma install takes a long time. See <a href="../../../../install/">install</a>.</p>
+
+<h2 id="packages"><span class="num">[003]</span> Package system</h2>
+<hr class="rule">
+<p>SPS is split into small tools rather than one large front end:</p>
+<pre class="block">src repository handling
+sget dependency-aware package frontend
+mkpkg package builder
+pkin package installer
+pkdel package removal
+pkstat installed package queries
+pkcheck package verification
+pkmark package reason management
+setup Splux installer
+mkiso Splux live ISO builder</pre>
+<p>Example:</p>
+<pre class="block">$ src update
+$ sget install curl
+install curl 8.21.0-2
+
+mkpkg: curl 8.21.0-2
+mkpkg: running configure phase
+mkpkg: running build phase
+mkpkg: built curl-8.21.0-2-x86_64.pkg.tar.zst
+pkin: installed curl 8.21.0-2</pre>
+<p><a href="../../../../packages/">Search 1474 packages</a> in the official trees.</p>
+
+<h2 id="repos"><span class="num">[004]</span> Repositories</h2>
+<hr class="rule">
+<p><a href="https://github.com/RobertFlexx/sps-core">core</a><br>
+Base system, toolchain, libraries, boot, filesystems, and networking. 119 packages.</p>
+<p><a href="https://github.com/RobertFlexx/sps-extra">extra</a><br>
+Desktop software, graphics, multimedia, development tools, and other packages. 1355 packages.</p>
+<p>SPS also supports administrator-maintained local repositories in addition to the official trees.</p>
+
+<h2 id="docs"><span class="num">[005]</span> Documentation</h2>
+<hr class="rule">
+<p><a href="../../../../install/">Installation</a><br>
+<a href="../../../../docs/">Documentation index</a><br>
+<a href="https://github.com/RobertFlexx/SPS/blob/main/README.md">SPS README</a><br>
+<a href="https://github.com/RobertFlexx/SPS/blob/main/DESIGN.md">SPS 1.0 formats</a></p>
+
+<h2 id="source"><span class="num">[006]</span> Source</h2>
+<hr class="rule">
+<p>
+<a class="gh" href="https://github.com/RobertFlexx/SPS"><img src="../../../../assets/github.svg" width="14" height="14" alt=""> SPS</a>
+the package system<br>
+<a href="https://github.com/RobertFlexx/sps-core">sps-core</a>
+official base recipes<br>
+<a href="https://github.com/RobertFlexx/sps-extra">sps-extra</a>
+official extra recipes<br>
+<a href="https://github.com/RobertFlexx/splux-site">splux-site</a>
+this website
+</p>
+
+<h2 id="status"><span class="num">[007]</span> Project status</h2>
+<hr class="rule">
+<p>Splux is under active development. SPS 1.0 is usable and the official package repositories are being built out. Live images exist so the installer can be tested. Splux is not a stable release yet.</p>
+
+</main>
+@@FOOTER@@
+</div>
+</body>
+</html>
diff --git a/site/install/index.html b/site/install/index.html
new file mode 100644
index 0000000..7568113
--- /dev/null
+++ b/site/install/index.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Install - Splux Linux</title>
+<meta name="description" content="How to boot a Splux live image and run setup.">
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<a class="skip" href="#main">Skip to content</a>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+<h2>Install</h2>
+<hr class="rule">
+<p>The installation procedure is still being finalized. The path that exists today is: boot a live ISO, mount a target filesystem, and run <code>setup</code>.</p>
+<p><code>setup</code> is the Splux installer. It is not a generic SPS or LFS installer. It uses dialog screens, or the same choices from flags for scripts.</p>
+<h2>1. Download and verify</h2>
+<hr class="rule">
+<p>Get an image from <a href="../../../../download/">download</a>. For a source install use <code>sps-live-tty.iso</code>, not the slim image. Check it against <a href="https://github.com/RobertFlexx/SPS/releases/latest/download/SHA256SUMS">SHA256SUMS</a>.</p>
+<h2>2. Write the image</h2>
+<hr class="rule">
+<pre class="block">dd if=sps-live-tty.iso of=/dev/sdX bs=4M status=progress conv=fsync</pre>
+<p>Replace <code>/dev/sdX</code> with the USB device. That command erases the destination.</p>
+<h2>3. Boot and target disk</h2>
+<hr class="rule">
+<p>Boot the "SPS Linux live (Splux)" entry. The live system comes up on the console (or Plasma on the Plasma image). For QEMU, give the guest a writable second disk in addition to the CD. The live image never runs <code>grub-install</code> on its own.</p>
+<h2>4. Run setup</h2>
+<hr class="rule">
+<pre class="block">setup
+setup --plan --profile plasma-desktop --target /mnt --user alex
+setup --non-interactive --profile minimal --target /mnt --hostname darkstar</pre>
+<p>Profiles are <code>minimal</code>, <code>server</code>, <code>plasma-desktop</code>, and <code>plasma-full</code>. Init is a choice: systemd (default) or OpenRC. Those packages conflict. Guided partitioning is optional. <code>grub-install</code> still needs <code>--install-bootloader</code> and <code>--disk</code>.</p>
+<p>The installer writes hostname, timezone, keymap, and a first user, copies bundled recipe trees when present, runs <code>src update</code>, and <code>sget install</code>. Packages are built from source on the target. A Plasma install compiles Qt and KDE and takes a long time.</p>
+<h2>More</h2>
+<hr class="rule">
+<p><a href="https://github.com/RobertFlexx/SPS/blob/main/README.md">SPS README</a> and <a href="https://github.com/RobertFlexx/SPS/blob/main/man/setup.1">setup(1)</a> are the current command references.</p>
+</main>
+@@FOOTER@@
+</div>
+</body>
+</html>
diff --git a/site/news/index.html b/site/news/index.html
new file mode 100644
index 0000000..9d5356b
--- /dev/null
+++ b/site/news/index.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>News - Splux Linux</title>
+<meta name="description" content="Splux Linux news.">
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<a class="skip" href="#main">Skip to content</a>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+<h2>News</h2>
+<hr class="rule">
+<p>2026-08-31 Website brought online.</p>
+<p>2026-08-31 Live ISO snapshots published from <a href="https://github.com/RobertFlexx/SPS/releases">SPS releases</a>.</p>
+<p>Release notes stay on GitHub rather than being copied here.</p>
+</main>
+@@FOOTER@@
+</div>
+</body>
+</html>
diff --git a/site/packages/index.html b/site/packages/index.html
new file mode 100644
index 0000000..eb2cf21
--- /dev/null
+++ b/site/packages/index.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Packages - Splux Linux</title>
+<meta name="description" content="Official Splux package collections: core and extra.">
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<a class="skip" href="#main">Skip to content</a>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+<h2>Packages</h2>
+<hr class="rule">
+<p>Generated from the official <a href="https://github.com/RobertFlexx/sps-core">sps-core</a> and <a href="https://github.com/RobertFlexx/sps-extra">sps-extra</a> recipe trees. 1474 packages (119 core, 1355 extra). Updated 2026-10-19 13:01 UTC.</p>
+<form id="pkg-search" role="search">
+<label class="search" for="q">Search
+<input id="q" name="q" type="search" autocomplete="off" spellcheck="false">
+</label>
+</form>
+<p class="filters">
+<a href="?repo=all" data-repo="all">all</a>
+<a href="?repo=core" data-repo="core">core</a>
+<a href="?repo=extra" data-repo="extra">extra</a>
+</p>
+<p class="stats" id="pkg-stats">1474 packages</p>
+<div style="overflow-x:auto">
+<table class="pkgs" id="pkg-table">
+<thead>
+<tr>
+<th>name</th>
+<th>version</th>
+<th>repository</th>
+<th>category</th>
+</tr>
+</thead>
+<tbody>
+@@PKG_ROWS@@
+</tbody>
+</table>
+</div>
+</main>
+@@FOOTER@@
+</div>
+<script src="../../../../assets/packages.js"></script>
+<script>
+(function () {
+ var repo = new URLSearchParams(location.search).get("repo") || "all";
+ var links = document.querySelectorAll(".filters a");
+ for (var i = 0; i < links.length; i++) {
+ if (links[i].getAttribute("data-repo") === repo)
+ links[i].setAttribute("aria-current", "true");
+ }
+})();
+</script>
+</body>
+</html>
diff --git a/site/source/index.html b/site/source/index.html
new file mode 100644
index 0000000..9466a9e
--- /dev/null
+++ b/site/source/index.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Source - Splux Linux</title>
+<meta name="description" content="Source repositories for Splux and SPS.">
+<link rel="icon" href="../../../../assets/favicon.png" type="image/png">
+<link rel="stylesheet" href="../../../../assets/style.css">
+</head>
+<body>
+<a class="skip" href="#main">Skip to content</a>
+<div class="wrap">
+@@HEADER@@
+<main id="main">
+<h2>Source</h2>
+<hr class="rule">
+<p><a class="gh" href="https://github.com/RobertFlexx/SPS"><img src="../../../../assets/github.svg" width="14" height="14" alt=""> SPS</a><br>
+Source Package System. POSIX shell and AWK. Installer and live ISO builder live here too.</p>
+<p><a href="https://github.com/RobertFlexx/sps-core">sps-core</a><br>
+Official base recipe collection.</p>
+<p><a href="https://github.com/RobertFlexx/sps-extra">sps-extra</a><br>
+Official extra recipe collection.</p>
+<p><a href="https://github.com/RobertFlexx/splux-site">splux-site</a><br>
+This website. Static HTML generated from the recipe trees.</p>
+<p>License for SPS and the package collections is ISC. See each repository.</p>
+</main>
+@@FOOTER@@
+</div>
+</body>
+</html>
diff --git a/tools/build-site.sh b/tools/build-site.sh
new file mode 100755
index 0000000..ff2d8f4
--- /dev/null
+++ b/tools/build-site.sh
@@ -0,0 +1,257 @@
+#!/bin/sh
+# Build the static Splux website from official SPS recipe trees.
+# Usage: ./tools/build-site.sh
+# Optional: CORE EXTRA SITE_PREFIX OUT
+
+set -eu
+
+cd "$(CDPATH= cd -P "$(dirname "$0")/.." && pwd)" || exit 1
+
+CORE=${CORE:-vendor/core}
+EXTRA=${EXTRA:-vendor/extra}
+OUT=${OUT:-_site}
+SITE_PREFIX=${SITE_PREFIX-}
+CORE_GIT=${CORE_GIT:-https://github.com/RobertFlexx/sps-core}
+EXTRA_GIT=${EXTRA_GIT:-https://github.com/RobertFlexx/sps-extra}
+SPS_GIT=${SPS_GIT:-https://github.com/RobertFlexx/SPS}
+
+if [ ! -d "$CORE" ] || [ ! -d "$EXTRA" ]; then
+ mkdir -p vendor
+ if [ ! -d "$CORE" ]; then
+ printf '%s\n' "cloning sps-core" >&2
+ git clone --depth 1 "$CORE_GIT.git" vendor/core
+ CORE=vendor/core
+ fi
+ if [ ! -d "$EXTRA" ]; then
+ printf '%s\n' "cloning sps-extra" >&2
+ git clone --depth 1 "$EXTRA_GIT.git" vendor/extra
+ EXTRA=vendor/extra
+ fi
+fi
+
+rm -rf "$OUT"
+mkdir -p "$OUT/assets" "$OUT/data" "$OUT/download" "$OUT/packages" \
+ "$OUT/install" "$OUT/docs" "$OUT/source" "$OUT/news"
+
+cp -a site/assets/. "$OUT/assets/"
+
+# Official SPS logo, three letters. Colors: gray S, yellow P, gray S.
+logo_pre='<pre class="logo" aria-label="SPS"><span class="s"> _____ </span><span class="p">_____ </span><span class="s">_____</span>
+<span class="s"> / ___| </span><span class="p">| _ \ </span><span class="s">/ ___|</span>
+<span class="s"> \ `--. </span><span class="p">| |_| | </span><span class="s">\ `--.</span>
+<span class="s"> `--. \ </span><span class="p">| __/ </span><span class="s">`--. \</span>
+<span class="s"> /\__/ / </span><span class="p">| | </span><span class="s">/\__/ /</span>
+<span class="s"> \____/ </span><span class="p">|_| </span><span class="s">\____/</span></pre>'
+
+write_header() {
+ root=$1
+ cat <<EOF
+<header class="top">
+$logo_pre
+<div class="identity">
+<h1>Splux Linux</h1>
+<p>source-based Linux using SPS</p>
+</div>
+</header>
+<nav class="row" aria-label="Site">
+<a href="${root}">home</a>
+<a href="${root}download/">download</a>
+<a href="${root}packages/">packages</a>
+<a href="${root}install/">install</a>
+<a href="${root}docs/">docs</a>
+<a href="${root}source/">source</a>
+<a href="${root}news/">news</a>
+<a class="gh" href="${SPS_GIT}"><img src="${root}assets/github.svg" width="14" height="14" alt=""> SPS</a>
+</nav>
+EOF
+}
+
+write_footer() {
+ root=$1
+ cat <<EOF
+<footer>
+<p>Splux Linux</p>
+<p><a href="${root}">home</a> · <a href="${root}packages/">packages</a> · <a href="${root}docs/">documentation</a></p>
+<p>This site was fully handmade by <a href="https://github.com/RobertFlexx">RobertFlexx</a>.</p>
+</footer>
+EOF
+}
+
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/splux-site.XXXXXX") || exit 1
+trap 'rm -rf "$tmp"' 0 HUP INT TERM
+
+tsv=$tmp/packages.tsv
+: >"$tsv"
+nwarn=0
+
+scan_tree() {
+ repo=$1
+ rootdir=$2
+ github=$3
+ find "$rootdir" -name recipe -type f | LC_ALL=C sort | while IFS= read -r rec
+ do
+ rel=${rec#"$rootdir"/}
+ case $rel in
+ recipe) pkgpath=. ;;
+ */recipe) pkgpath=${rel%/recipe} ;;
+ *) continue ;;
+ esac
+ if ! awk -f tools/packages.awk \
+ -v repo="$repo" \
+ -v pkgpath="$pkgpath" \
+ -v github="$github" \
+ -v outdir="$OUT" \
+ "$rec" >>"$tsv"
+ then
+ printf '%s\n' "warning: skipped $rec" >&2
+ nwarn=$((nwarn + 1))
+ fi
+ done
+}
+
+printf '%s\n' "reading recipes" >&2
+scan_tree core "$CORE" "$CORE_GIT"
+scan_tree extra "$EXTRA" "$EXTRA_GIT"
+
+LC_ALL=C sort -t "$(printf '\t')" -k1,1 -k5,5 "$tsv" >"$tmp/sorted.tsv"
+mv "$tmp/sorted.tsv" "$tsv"
+
+npkgs=$(awk 'END { print NR + 0 }' "$tsv")
+ncore=$(awk -F '\t' '$5 == "core" { n++ } END { print n + 0 }' "$tsv")
+nextra=$(awk -F '\t' '$5 == "extra" { n++ } END { print n + 0 }' "$tsv")
+curlver=$(awk -F '\t' '$1 == "curl" { print $2 "-" $3; exit }' "$tsv")
+[ -n "$curlver" ] || curlver="8.21.0-1"
+
+awk -F '\t' '
+function esc(s) {
+ gsub(/&/, "\\&", s)
+ gsub(/</, "\\<", s)
+ gsub(/>/, "\\>", s)
+ gsub(/"/, "\\"", s)
+ return s
+}
+{
+ name=$1; ver=$2; rel=$3; repo=$5; cat=$6; desc=$7; path=$8
+ search = tolower(name " " desc " " cat " " repo)
+ href = "../../../../packages/" repo "/" path "/"
+ printf "<tr data-repo=\"%s\" data-search=\"%s\">", esc(repo), esc(search)
+ printf "<td><a class=\"name\" href=\"%s\">%s</a></td>", href, esc(name)
+ printf "<td>%s-%s</td>", esc(ver), esc(rel)
+ printf "<td>%s</td>", esc(repo)
+ printf "<td>%s</td></tr>\n", esc(cat)
+}
+' "$tsv" >"$tmp/rows.html"
+
+awk -F '\t' '
+function jesc(s) {
+ gsub(/\\/, "\\\\", s)
+ gsub(/"/, "\\\"", s)
+ return s
+}
+BEGIN { print "[" }
+{
+ if (NR > 1) print ","
+ printf "{\"name\":\"%s\",\"version\":\"%s\",\"release\":\"%s\",\"arch\":\"%s\",\"repo\":\"%s\",\"category\":\"%s\",\"description\":\"%s\",\"path\":\"%s\"}", \
+ jesc($1), jesc($2), jesc($3), jesc($4), jesc($5), jesc($6), jesc($7), jesc($8)
+}
+END { print "\n]" }
+' "$tsv" >"$OUT/data/packages.json"
+
+tag=unknown
+date=unknown
+if command -v gh >/dev/null 2>&1; then
+ tag=$(gh release view -R RobertFlexx/SPS --json tagName --jq .tagName 2>/dev/null) || tag=unknown
+ date=$(gh release view -R RobertFlexx/SPS --json publishedAt --jq .publishedAt 2>/dev/null) || date=unknown
+ date=${date%%T*}
+fi
+if [ "$tag" = unknown ] || [ -z "$tag" ]; then
+ tag=latest
+ date=see GitHub
+fi
+generated=$(date -u '+%Y-%m-%d %H:%M UTC' 2>/dev/null || date)
+
+printf '%s\n' "{\"tag\":\"$tag\",\"date\":\"$date\",\"iso_tty\":\"$SPS_GIT/releases/latest/download/sps-live-tty.iso\"}" \
+ >"$OUT/data/release.json"
+
+subst() {
+ src=$1
+ dest=$2
+ root=$3
+ write_header "$root" >"$tmp/header.html"
+ write_footer "$root" >"$tmp/footer.html"
+ mkdir -p "$(dirname "$dest")"
+ awk -f tools/subst.awk \
+ -v headerfile="$tmp/header.html" \
+ -v footerfile="$tmp/footer.html" \
+ -v rowsfile="$tmp/rows.html" \
+ -v root="$root" \
+ -v tag="$tag" \
+ -v date="$date" \
+ -v npkgs="$npkgs" \
+ -v ncore="$ncore" \
+ -v nextra="$nextra" \
+ -v generated="$generated" \
+ -v curlver="$curlver" \
+ "$src" >"$dest"
+}
+
+# Static pages
+subst site/index.html "$OUT/index.html" "./"
+subst site/download/index.html "$OUT/download/index.html" "../"
+subst site/packages/index.html "$OUT/packages/index.html" "../"
+subst site/install/index.html "$OUT/install/index.html" "../"
+subst site/docs/index.html "$OUT/docs/index.html" "../"
+subst site/source/index.html "$OUT/source/index.html" "../"
+subst site/news/index.html "$OUT/news/index.html" "../"
+
+# 404 must use an absolute prefix so nested missing URLs still load CSS.
+if [ -n "$SITE_PREFIX" ]; then
+ abs=${SITE_PREFIX%/}/
+else
+ abs=/
+fi
+subst site/404.html "$OUT/404.html" "$abs"
+
+# Detail pages already contain @@HEADER@@ / @@FOOTER@@ and baked asset paths.
+# Re-run subst with a per-directory root of "".
+# Their <head> links already use ../ prefixes from packages.awk.
+find "$OUT/packages" -name index.html | while IFS= read -r page
+do
+ case $page in
+ */packages/index.html) continue ;;
+ esac
+ # Count depth below _site for nav ROOT (packages/core/cat/pkg = 4)
+ rel=${page#"$OUT"/}
+ depth=0
+ rest=$rel
+ while [ "$rest" != "${rest#*/}" ]
+ do
+ depth=$((depth + 1))
+ rest=${rest#*/}
+ done
+ root=""
+ i=0
+ while [ "$i" -lt "$depth" ]
+ do
+ root="../$root"
+ i=$((i + 1))
+ done
+ write_header "$root" >"$tmp/h2.html"
+ write_footer "$root" >"$tmp/f2.html"
+ awk -f tools/subst.awk \
+ -v headerfile="$tmp/h2.html" \
+ -v footerfile="$tmp/f2.html" \
+ -v rowsfile="" \
+ -v root="$root" \
+ -v tag="$tag" \
+ -v date="$date" \
+ -v npkgs="$npkgs" \
+ -v ncore="$ncore" \
+ -v nextra="$nextra" \
+ -v generated="$generated" \
+ -v curlver="$curlver" \
+ "$page" >"$tmp/page.html"
+ mv "$tmp/page.html" "$page"
+done
+
+printf '%s\n' "built $OUT ($npkgs packages, $ncore core, $nextra extra, tag $tag)" >&2
diff --git a/tools/packages.awk b/tools/packages.awk
new file mode 100644
index 0000000..f5c4c6d
--- /dev/null
+++ b/tools/packages.awk
@@ -0,0 +1,203 @@
+# Read one SPS recipe. Print one TSV record. Optionally write a detail page.
+# Fields: name version release arch repo category description path
+# depend builddep optional conflict source
+
+function trim(s) {
+ sub(/^[ \t]+/, "", s)
+ sub(/[ \t]+$/, "", s)
+ return s
+}
+
+function html_esc(s) {
+ gsub(/&/, "\\&", s)
+ gsub(/</, "\\<", s)
+ gsub(/>/, "\\>", s)
+ gsub(/"/, "\\"", s)
+ gsub(/\t/, " ", s)
+ return s
+}
+
+function json_esc(s) {
+ gsub(/\\/, "\\\\", s)
+ gsub(/"/, "\\\"", s)
+ gsub(/\t/, "\\t", s)
+ gsub(/\n/, "\\n", s)
+ gsub(/\r/, "\\r", s)
+ return s
+}
+
+function append_dep(key, value, n, f, i, item) {
+ value = trim(value)
+ gsub(/[ \t,]+/, " ", value)
+ n = split(value, f, /[ ]+/)
+ for (i = 1; i <= n; i++) {
+ item = trim(f[i])
+ if (item == "") continue
+ if (val[key] != "") val[key] = val[key] ", "
+ val[key] = val[key] item
+ }
+}
+
+function expand(s, pass, prev) {
+ for (pass = 1; pass <= 8; pass++) {
+ prev = s
+ gsub(/\$\{name\}/, val["name"], s)
+ gsub(/\$\{version\}/, val["version"], s)
+ gsub(/\$\{release\}/, val["release"], s)
+ gsub(/\$\{arch\}/, val["arch"], s)
+ if (s == prev) break
+ }
+ return s
+}
+
+function warn(msg) {
+ printf "packages.awk: %s:%d: %s\n", FILENAME, FNR, msg > "/dev/stderr"
+}
+
+function process(line, key, value, sep) {
+ line = trim(line)
+ if (line == "" || line ~ /^#/) return
+ sep = match(line, /[ \t]/)
+ if (!sep) {
+ warn("expected key value")
+ failed = 1
+ return
+ }
+ key = substr(line, 1, RSTART - 1)
+ value = trim(substr(line, RSTART + RLENGTH))
+ if (value == "") {
+ warn("empty value for " key)
+ failed = 1
+ return
+ }
+ if (key == "name" || key == "version" || key == "release" ||
+ key == "arch" || key == "description") {
+ if (seen[key]) {
+ warn("duplicate " key)
+ failed = 1
+ return
+ }
+ seen[key] = 1
+ val[key] = value
+ } else if (key == "depend" || key == "builddep" || key == "optional" ||
+ key == "conflict") {
+ append_dep(key, value)
+ } else if (key == "source") {
+ if (source == "") source = value
+ } else if (key == "hash" || key == "install" || key == "prepare" ||
+ key == "configure" || key == "build") {
+ # indexing only
+ } else {
+ warn("unknown field " key)
+ }
+}
+
+function list_html(page, label, items, n, f, i) {
+ if (items == "") return
+ print "<p>" html_esc(label) "</p>" > page
+ print "<ul class=\"plain\">" > page
+ n = split(items, f, ", ")
+ for (i = 1; i <= n; i++) {
+ print "<li>" html_esc(f[i]) "</li>" > page
+ }
+ print "</ul>" > page
+}
+
+function write_page( dir, ncomp, i, prefix, parts, cat, page, gh_dir) {
+ ncomp = split(pkgpath, parts, "/")
+ prefix = ""
+ # packages/repo/ + path components
+ for (i = 0; i < ncomp + 2; i++) prefix = prefix "../"
+ cat = parts[1]
+ gh_dir = pkgpath
+ page = outdir "/packages/" repo "/" pkgpath "/index.html"
+ system("mkdir -p \"" outdir "/packages/" repo "/" pkgpath "\"")
+ print "<!DOCTYPE html>" > page
+ print "<html lang=\"en\">" > page
+ print "<head>" > page
+ print "<meta charset=\"utf-8\">" > page
+ print "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" > page
+ print "<title>" html_esc(val["name"]) " - Splux Linux</title>" > page
+ print "<link rel=\"icon\" href=\"" prefix "assets/favicon.png\" type=\"image/png\">" > page
+ print "<link rel=\"stylesheet\" href=\"" prefix "assets/style.css\">" > page
+ print "</head><body>" > page
+ print "<div class=\"wrap\">" > page
+ # header/footer filled later by build-site via markers
+ print "@@HEADER@@" > page
+ print "<main id=\"main\">" > page
+ print "<h2>" html_esc(val["name"]) " " html_esc(val["version"]) "-" html_esc(val["release"]) "</h2>" > page
+ print "<hr class=\"rule\">" > page
+ print "<dl class=\"meta\">" > page
+ print "<dt>description</dt><dd>" html_esc(val["description"]) "</dd>" > page
+ print "<dt>repository</dt><dd>" html_esc(repo) "</dd>" > page
+ print "<dt>category</dt><dd>" html_esc(cat) "</dd>" > page
+ print "<dt>architecture</dt><dd>" html_esc(val["arch"]) "</dd>" > page
+ if (source != "")
+ print "<dt>source</dt><dd><a href=\"" html_esc(expand(source)) "\">" html_esc(expand(source)) "</a></dd>" > page
+ print "</dl>" > page
+ list_html(page, "dependencies", val["depend"])
+ list_html(page, "build dependencies", val["builddep"])
+ list_html(page, "optional dependencies", val["optional"])
+ list_html(page, "conflicts", val["conflict"])
+ print "<p>recipe<br>" > page
+ print "<a href=\"" github "/blob/main/" gh_dir "/recipe\">view recipe</a><br>" > page
+ print "<a href=\"" github "/tree/main/" gh_dir "\">repository directory</a><br>" > page
+ print "<a href=\"" github "/commits/main/" gh_dir "\">history</a></p>" > page
+ print "</main>" > page
+ print "@@FOOTER@@" > page
+ print "</div></body></html>" > page
+ close(page)
+}
+
+BEGIN {
+ if (repo == "") repo = "unknown"
+ if (github == "") github = "https://github.com/RobertFlexx/sps-core"
+ if (pkgpath == "") pkgpath = "unknown"
+}
+
+{
+ sub(/\r$/, "", $0)
+ if (continuing)
+ logical = logical $0
+ else
+ logical = $0
+ if (logical ~ /\\[ \t]*$/) {
+ sub(/\\[ \t]*$/, "", logical)
+ logical = logical " "
+ continuing = 1
+ next
+ }
+ continuing = 0
+ process(logical)
+ logical = ""
+}
+
+END {
+ if (continuing) {
+ warn("unterminated continuation")
+ failed = 1
+ }
+ if (val["name"] == "" || val["version"] == "" || val["release"] == "") {
+ warn("missing name, version, or release")
+ exit 1
+ }
+ if (val["arch"] == "") val["arch"] = "x86_64"
+ val["name"] = expand(val["name"])
+ val["version"] = expand(val["version"])
+ val["release"] = expand(val["release"])
+ val["arch"] = expand(val["arch"])
+ val["description"] = expand(val["description"])
+ val["depend"] = expand(val["depend"])
+ val["builddep"] = expand(val["builddep"])
+ val["optional"] = expand(val["optional"])
+ val["conflict"] = expand(val["conflict"])
+ source = expand(source)
+ n = split(pkgpath, parts, "/")
+ category = parts[1]
+ if (outdir != "") write_page()
+ # TSV
+ print val["name"] "\t" val["version"] "\t" val["release"] "\t" \
+ val["arch"] "\t" repo "\t" category "\t" val["description"] "\t" \
+ pkgpath "\t" val["depend"] "\t" val["builddep"] "\t" \
+ val["optional"] "\t" val["conflict"] "\t" source
+}
diff --git a/tools/subst.awk b/tools/subst.awk
new file mode 100644
index 0000000..eab5188
--- /dev/null
+++ b/tools/subst.awk
@@ -0,0 +1,48 @@
+# Replace @@HEADER@@ / @@FOOTER@@ / ../../../../ and other tokens.
+# headerfile, footerfile, root, rowsfile, and token variables are -v flags.
+
+BEGIN {
+ if (headerfile != "") {
+ while ((getline line < headerfile) > 0)
+ header = header line "\n"
+ close(headerfile)
+ }
+ if (footerfile != "") {
+ while ((getline line < footerfile) > 0)
+ footer = footer line "\n"
+ close(footerfile)
+ }
+}
+
+$0 == "@@HEADER@@" {
+ printf "%s", header
+ next
+}
+
+$0 == "@@FOOTER@@" {
+ printf "%s", footer
+ next
+}
+
+$0 == "@@PKG_ROWS@@" {
+ if (rowsfile != "") {
+ while ((getline line < rowsfile) > 0) {
+ gsub(/../../../..//, root, line)
+ print line
+ }
+ close(rowsfile)
+ }
+ next
+}
+
+{
+ gsub(/../../../..//, root)
+ gsub(/live-2026-09-06-1/, tag)
+ gsub(/2026-09-06/, date)
+ gsub(/1474/, npkgs)
+ gsub(/119/, ncore)
+ gsub(/1355/, nextra)
+ gsub(/2026-10-19 13:01 UTC/, generated)
+ gsub(/8.21.0-2/, curlver)
+ print
+}