Splux Linux

source-based Linux using SPS

splux-site · log · files · refs · tags · releases · people · languages · GitHub · commit

206213d


Add a public Splux Git page and the matching Forgejo theme.

Date
Commit206213d6711d79ae5dca0071f26675a8a31b27a7
GitHubmirror

Parents

Stat

 README.md                                        |  12 +
 forgejo/README.md                                |  34 +++
 forgejo/app.ini.example                          |  80 ++++++
 forgejo/install.sh                               | 153 ++++++++++++
 forgejo/templates/custom/extra_links.tmpl        |   4 +
 forgejo/templates/custom/extra_links_footer.tmpl |   3 +
 forgejo/theme-splux.css                          | 306 +++++++++++++++++++++++
 site/404.html                                    |   2 +-
 site/assets/git.js                               |  80 ++++++
 site/docs/index.html                             |   3 +-
 site/git/index.html                              |  93 +++++++
 site/index.html                                  |  10 +-
 site/packages/index.html                         |   2 +-
 site/source/index.html                           |   4 +-
 tools/build-site.sh                              |  12 +-
 tools/subst.awk                                  |   3 +
 16 files changed, 789 insertions(+), 12 deletions(-)

Diff

diff --git a/README.md b/README.md
index 76673c8..2f233b2 100644
--- a/README.md
+++ b/README.md
@@ -73,3 +73,15 @@ 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
 repository there. `SITE_PREFIX` stays empty so links are rooted at `/`.
+
+## Git
+
+`/git/` is the public forge page. Canonical clone, accounts, and pull
+requests belong on Splux Git (`git.splux.robertflexx.dev` once that name
+has DNS and TLS). GitHub stays a mirror of git history. The Forgejo
+theme and installer live in `forgejo/`.
+
+```sh
+sh forgejo/install.sh
+```
+
diff --git a/forgejo/README.md b/forgejo/README.md
new file mode 100644
index 0000000..c9e074e
--- /dev/null
+++ b/forgejo/README.md
@@ -0,0 +1,34 @@
+# Splux Git
+
+Forgejo themed to match the Splux handbook. The live process lives in
+`/home/robert/splux-forge`. This directory is the theme, example config,
+and installer that the static site ships so the forge and the handbook
+stay in the same style.
+
+```sh
+sh forgejo/install.sh
+```
+
+That copies the theme, writes `app.ini`, and starts port 3000. Create
+the admin account yourself (the installer does not set a password):
+
+```sh
+/home/robert/splux-forge/bin/forgejo -w /home/robert/splux-forge/data \
+  admin user create --username RobertFlexx --email ohf9ck@gmail.com \
+  --admin --password 'your-password'
+```
+
+Then in the web UI, migrate SPS, sps-core, sps-extra, and splux-site
+from GitHub.
+
+The public handbook page is `/git/`. Clone URLs on the internet still
+use GitHub until `git.splux.robertflexx.dev` has DNS and TLS in front of
+this process. LAN:
+
+```sh
+git clone http://10.0.0.139:3000/RobertFlexx/SPS.git
+git clone ssh://robert@10.0.0.139:2222/RobertFlexx/SPS.git
+```
+
+Push mirrors to GitHub are a per-repo setting in the forge UI. They
+need a GitHub PAT. Issues and pull requests stay on Splux Git.
diff --git a/forgejo/app.ini.example b/forgejo/app.ini.example
new file mode 100644
index 0000000..1b1e694
--- /dev/null
+++ b/forgejo/app.ini.example
@@ -0,0 +1,80 @@
+; Splux Git. Copy to $FORGEJO_WORK_DIR/custom/conf/app.ini
+; forgejo/install.sh writes the live file with secrets filled in.
+
+APP_NAME = Splux Git
+APP_SLOGAN = official repositories for Splux and SPS
+RUN_USER = robert
+WORK_PATH = /home/robert/splux-forge/data
+
+[server]
+APP_DATA_PATH = /home/robert/splux-forge/data
+PROTOCOL = http
+DOMAIN = 10.0.0.139
+HTTP_ADDR = 0.0.0.0
+HTTP_PORT = 3000
+; Change ROOT_URL to https://git.splux.robertflexx.dev/ after DNS and TLS exist.
+ROOT_URL = http://10.0.0.139:3000/
+DISABLE_SSH = false
+START_SSH_SERVER = true
+SSH_DOMAIN = 10.0.0.139
+SSH_PORT = 2222
+SSH_LISTEN_PORT = 2222
+LANDING_PAGE = explore
+OFFLINE_MODE = false
+
+[database]
+DB_TYPE = sqlite3
+PATH = /home/robert/splux-forge/data/forgejo.db
+
+[security]
+INSTALL_LOCK = true
+; SECRET_KEY and INTERNAL_TOKEN are generated by install.sh
+
+[oauth2]
+; JWT_SECRET is generated by install.sh
+
+[service]
+DISABLE_REGISTRATION = true
+REQUIRE_SIGNIN_VIEW = false
+ENABLE_NOTIFY_MAIL = false
+DEFAULT_KEEP_EMAIL_PRIVATE = true
+NO_REPLY_ADDRESS = noreply.splux.robertflexx.dev
+
+[ui]
+DEFAULT_THEME = splux
+THEMES = splux,forgejo-dark,forgejo-auto,forgejo-light
+SHOW_USER_EMAIL = false
+DEFAULT_SHOW_FULL_NAME = false
+
+[repository]
+DEFAULT_BRANCH = main
+DEFAULT_PRIVATE = last
+ENABLE_PUSH_CREATE_USER = false
+ENABLE_PUSH_CREATE_ORG = false
+
+[picture]
+DISABLE_GRAVATAR = true
+ENABLE_FEDERATED_AVATAR = false
+
+[cors]
+ENABLED = true
+SCHEME = https
+ALLOW_DOMAIN = splux.robertflexx.dev
+ALLOW_SUBDOMAIN = false
+METHODS = GET,HEAD,OPTIONS
+HEADERS = Content-Type,Authorization
+MAX_AGE = 10m
+ALLOW_CREDENTIALS = false
+
+[other]
+SHOW_FOOTER_VERSION = false
+SHOW_FOOTER_TEMPLATE_LOAD_TIME = false
+SHOW_FOOTER_POWERED_BY = false
+
+[session]
+PROVIDER = file
+
+[log]
+MODE = file
+LEVEL = info
+ROOT_PATH = /home/robert/splux-forge/log
diff --git a/forgejo/install.sh b/forgejo/install.sh
new file mode 100755
index 0000000..0dae969
--- /dev/null
+++ b/forgejo/install.sh
@@ -0,0 +1,153 @@
+#!/bin/sh
+# Install Splux Git (Forgejo) on this host and copy the Splux theme.
+# Does not create an admin user or access token.
+# Usage: sh forgejo/install.sh
+set -eu
+
+SITE=$(CDPATH= cd -P "$(dirname "$0")/.." && pwd) || exit 1
+FORGE=${FORGE:-/home/robert/splux-forge}
+THEME=$SITE/forgejo
+DATA=$FORGE/data
+CONF=$DATA/custom/conf/app.ini
+BIN=$FORGE/bin/forgejo
+FJ() {
+	"$BIN" -w "$DATA" -c "$CONF" "$@"
+}
+
+[ -x "$BIN" ] || {
+	printf '%s\n' "missing $BIN" >&2
+	exit 1
+}
+
+mkdir -p "$DATA/custom/conf" "$DATA/custom/public/assets/css" \
+	"$DATA/custom/public/assets/img" "$DATA/custom/templates/custom" \
+	"$FORGE/log"
+
+install_theme() {
+	cp "$THEME/theme-splux.css" "$DATA/custom/public/assets/css/theme-splux.css"
+	cp "$THEME/templates/custom/extra_links.tmpl" \
+		"$DATA/custom/templates/custom/extra_links.tmpl"
+	cp "$THEME/templates/custom/extra_links_footer.tmpl" \
+		"$DATA/custom/templates/custom/extra_links_footer.tmpl"
+	if [ -f "$SITE/site/assets/sps.png" ]; then
+		cp "$SITE/site/assets/sps.png" "$DATA/custom/public/assets/img/logo.png"
+		cp "$SITE/site/assets/sps.png" \
+			"$DATA/custom/public/assets/img/apple-touch-icon.png"
+	fi
+	if [ -f "$SITE/site/assets/favicon.png" ]; then
+		cp "$SITE/site/assets/favicon.png" "$DATA/custom/public/assets/img/favicon.png"
+	fi
+}
+
+write_app_ini() {
+	sk=$("$BIN" generate secret SECRET_KEY)
+	it=$("$BIN" generate secret INTERNAL_TOKEN)
+	jw=$("$BIN" generate secret JWT_SECRET)
+	umask 077
+	cat >"$CONF" <<EOF
+APP_NAME = Splux Git
+APP_SLOGAN = official repositories for Splux and SPS
+RUN_USER = robert
+WORK_PATH = $DATA
+
+[server]
+APP_DATA_PATH = $DATA
+PROTOCOL = http
+DOMAIN = 10.0.0.139
+HTTP_ADDR = 0.0.0.0
+HTTP_PORT = 3000
+ROOT_URL = http://10.0.0.139:3000/
+DISABLE_SSH = false
+START_SSH_SERVER = true
+SSH_DOMAIN = 10.0.0.139
+SSH_PORT = 2222
+SSH_LISTEN_PORT = 2222
+LANDING_PAGE = explore
+OFFLINE_MODE = false
+
+[database]
+DB_TYPE = sqlite3
+PATH = $DATA/forgejo.db
+
+[security]
+INSTALL_LOCK = true
+SECRET_KEY = $sk
+INTERNAL_TOKEN = $it
+
+[oauth2]
+JWT_SECRET = $jw
+
+[service]
+DISABLE_REGISTRATION = true
+REQUIRE_SIGNIN_VIEW = false
+ENABLE_NOTIFY_MAIL = false
+DEFAULT_KEEP_EMAIL_PRIVATE = true
+NO_REPLY_ADDRESS = noreply.splux.robertflexx.dev
+
+[ui]
+DEFAULT_THEME = splux
+THEMES = splux,forgejo-dark,forgejo-auto,forgejo-light
+SHOW_USER_EMAIL = false
+
+[repository]
+DEFAULT_BRANCH = main
+DEFAULT_PRIVATE = last
+ENABLE_PUSH_CREATE_USER = false
+ENABLE_PUSH_CREATE_ORG = false
+
+[picture]
+DISABLE_GRAVATAR = true
+ENABLE_FEDERATED_AVATAR = false
+
+[cors]
+ENABLED = true
+SCHEME = https
+ALLOW_DOMAIN = splux.robertflexx.dev
+METHODS = GET,HEAD,OPTIONS
+HEADERS = Content-Type,Authorization
+MAX_AGE = 10m
+ALLOW_CREDENTIALS = false
+
+[other]
+SHOW_FOOTER_VERSION = false
+SHOW_FOOTER_TEMPLATE_LOAD_TIME = false
+SHOW_FOOTER_POWERED_BY = false
+
+[session]
+PROVIDER = file
+
+[log]
+MODE = file
+LEVEL = info
+ROOT_PATH = $FORGE/log
+EOF
+}
+
+install_theme
+
+if [ ! -f "$CONF" ] || ! grep -q '^INSTALL_LOCK = true' "$CONF" 2>/dev/null
+then
+	write_app_ini
+fi
+
+FJ migrate
+sh "$FORGE/start.sh"
+
+i=0
+while [ "$i" -lt 40 ]
+do
+	if curl -fsS http://127.0.0.1:3000/api/v1/version >/dev/null 2>&1
+	then
+		break
+	fi
+	i=$((i + 1))
+	sleep 1
+done
+curl -fsS http://127.0.0.1:3000/api/v1/version >/dev/null || {
+	printf '%s\n' "forgejo did not start on :3000" >&2
+	exit 1
+}
+
+printf '%s\n' "Splux Git: http://10.0.0.139:3000/" >&2
+printf '%s\n' "Create the admin account yourself, then migrate the four GitHub trees from the web UI:" >&2
+printf '%s\n' "  $BIN -w $DATA -c $CONF admin user create --username RobertFlexx --email ohf9ck@gmail.com --admin --password 'your-password'" >&2
diff --git a/forgejo/templates/custom/extra_links.tmpl b/forgejo/templates/custom/extra_links.tmpl
new file mode 100644
index 0000000..37ed272
--- /dev/null
+++ b/forgejo/templates/custom/extra_links.tmpl
@@ -0,0 +1,4 @@
+<a class="item" href="https://splux.robertflexx.dev/">Handbook</a>
+<a class="item" href="https://splux.robertflexx.dev/git/">Git</a>
+<a class="item" href="https://splux.robertflexx.dev/packages/">Packages</a>
+<a class="item" href="https://splux.robertflexx.dev/download/">Download</a>
diff --git a/forgejo/templates/custom/extra_links_footer.tmpl b/forgejo/templates/custom/extra_links_footer.tmpl
new file mode 100644
index 0000000..004d307
--- /dev/null
+++ b/forgejo/templates/custom/extra_links_footer.tmpl
@@ -0,0 +1,3 @@
+<a href="https://splux.robertflexx.dev/">splux.robertflexx.dev</a>
+·
+<a href="https://github.com/RobertFlexx">GitHub mirror</a>
diff --git a/forgejo/theme-splux.css b/forgejo/theme-splux.css
new file mode 100644
index 0000000..12e6bc0
--- /dev/null
+++ b/forgejo/theme-splux.css
@@ -0,0 +1,306 @@
+/*
+ * Splux Git. Same black, gray, yellow, monospace as splux.robertflexx.dev.
+ * Drop at $FORGEJO_CUSTOM/public/assets/css/theme-splux.css
+ */
+
+@import url("./theme-forgejo-dark.css");
+
+:root {
+	color-scheme: dark;
+
+	--fonts-proportional: ui-monospace, "SFMono-Regular", "Cascadia Mono",
+		"Liberation Mono", "DejaVu Sans Mono", "Adwaita Mono", monospace !important;
+	--fonts-monospace: ui-monospace, "SFMono-Regular", "Cascadia Mono",
+		"Liberation Mono", "DejaVu Sans Mono", "Adwaita Mono", monospace !important;
+
+	--color-primary: #ffff00 !important;
+	--color-primary-hover: #ffff66 !important;
+	--color-primary-active: #cccc00 !important;
+	--color-primary-light-1: #ffff33 !important;
+	--color-primary-light-2: #ffff66 !important;
+	--color-primary-light-3: #ffff99 !important;
+	--color-primary-dark-1: #cccc00 !important;
+	--color-primary-dark-2: #999900 !important;
+	--color-primary-alpha-10: rgba(255, 255, 0, 0.10) !important;
+	--color-primary-alpha-20: rgba(255, 255, 0, 0.20) !important;
+	--color-primary-alpha-40: rgba(255, 255, 0, 0.40) !important;
+	--color-accent: #ffff00 !important;
+	--color-secondary-button: #8a8a8a !important;
+	--color-secondary-button-hover: #d6d6d6 !important;
+
+	--color-body: #0a0a0a !important;
+	--color-box-body: #111111 !important;
+	--color-box-body-highlight: #1a1a1a !important;
+	--color-secondary: #2a2a2a !important;
+	--color-secondary-dark-1: #1a1a1a !important;
+	--color-secondary-dark-2: #111111 !important;
+	--color-secondary-dark-3: #0a0a0a !important;
+	--color-secondary-dark-4: #000000 !important;
+	--color-secondary-dark-5: #000000 !important;
+	--color-secondary-dark-6: #000000 !important;
+	--color-secondary-dark-7: #000000 !important;
+	--color-secondary-dark-8: #000000 !important;
+	--color-secondary-dark-9: #000000 !important;
+	--color-secondary-dark-10: #000000 !important;
+	--color-secondary-dark-11: #000000 !important;
+	--color-secondary-dark-12: #000000 !important;
+	--color-secondary-dark-13: #000000 !important;
+	--color-secondary-light-1: #333333 !important;
+	--color-secondary-alpha-20: rgba(255, 255, 255, 0.04) !important;
+	--color-secondary-alpha-40: rgba(255, 255, 255, 0.07) !important;
+	--color-secondary-alpha-60: rgba(255, 255, 255, 0.10) !important;
+	--color-secondary-alpha-80: rgba(255, 255, 255, 0.14) !important;
+
+	--color-border: #2a2a2a !important;
+	--color-light-border: #2a2a2a !important;
+
+	--color-text: #d6d6d6 !important;
+	--color-text-dark: #d6d6d6 !important;
+	--color-text-light: #8a8a8a !important;
+	--color-text-light-1: #8a8a8a !important;
+	--color-text-light-2: #6e6e6e !important;
+	--color-text-light-3: #555555 !important;
+	--color-text-light-4: #444444 !important;
+	--color-text-light-5: #333333 !important;
+	--color-text-light-6: #2a2a2a !important;
+
+	--color-nav-bg: #0a0a0a !important;
+	--color-nav-hover-bg: #1a1a1a !important;
+	--color-nav-text: #d6d6d6 !important;
+	--color-header-wrapper: #0a0a0a !important;
+	--color-header-bar: #0a0a0a !important;
+	--color-footer: #0a0a0a !important;
+	--color-menu: #111111 !important;
+
+	--color-markup-code-block: #111111 !important;
+	--color-code-bg: #111111 !important;
+	--color-code-sidebar-bg: #0a0a0a !important;
+	--color-input-background: #111111 !important;
+	--color-input-border: #2a2a2a !important;
+	--color-input-text: #d6d6d6 !important;
+	--color-hover: #1a1a1a !important;
+	--color-active: #1a1a1a !important;
+	--color-active-line: rgba(255, 255, 0, 0.08) !important;
+
+	--color-button: #111111 !important;
+	--color-button-hover: #1a1a1a !important;
+
+	--color-label-text: #d6d6d6 !important;
+	--color-label-hover-bg: #1a1a1a !important;
+	--color-label-active-bg: rgba(255, 255, 0, 0.12) !important;
+	--color-label-active-text: #ffff00 !important;
+
+	--color-red: #ff6666 !important;
+	--color-orange: #ffcc00 !important;
+	--color-yellow: #ffff00 !important;
+	--color-olive: #cccc00 !important;
+	--color-green: #88cc44 !important;
+	--color-teal: #88aaaa !important;
+	--color-blue: #88aacc !important;
+	--color-violet: #aaaaee !important;
+	--color-purple: #cc88cc !important;
+	--color-pink: #eeaaaa !important;
+	--color-brown: #aa8866 !important;
+	--color-grey: #8a8a8a !important;
+	--color-black: #0a0a0a !important;
+	--color-gold: #ffff00 !important;
+	--color-white: #d6d6d6 !important;
+
+	--color-diff-removed-word-bg: rgba(255, 102, 102, 0.28) !important;
+	--color-diff-added-word-bg: rgba(255, 255, 0, 0.22) !important;
+	--color-diff-removed-row-bg: rgba(255, 102, 102, 0.08) !important;
+	--color-diff-added-row-bg: rgba(255, 255, 0, 0.07) !important;
+	--color-diff-removed-row-border: rgba(255, 102, 102, 0.25) !important;
+	--color-diff-added-row-border: rgba(255, 255, 0, 0.25) !important;
+	--color-diff-inactive: #111111 !important;
+
+	--color-console-fg: #d6d6d6 !important;
+	--color-console-bg: #0a0a0a !important;
+	--color-console-border: #2a2a2a !important;
+
+	--shadow-xs: none !important;
+	--shadow-sm: none !important;
+	--shadow-md: none !important;
+	--shadow-lg: none !important;
+	--shadow-xl: none !important;
+
+	--border-radius: 0 !important;
+	--border-radius-circle: 0 !important;
+}
+
+html,
+body {
+	background: #0a0a0a !important;
+	color: #d6d6d6 !important;
+	font-family: ui-monospace, "SFMono-Regular", "Cascadia Mono",
+		"Liberation Mono", "DejaVu Sans Mono", monospace !important;
+	font-size: 15px !important;
+	line-height: 1.5 !important;
+}
+
+*,
+.ui.segment,
+.ui.card,
+.ui.menu,
+.ui.dropdown .menu,
+.ui.modal,
+.ui.popup {
+	box-shadow: none !important;
+	border-radius: 0 !important;
+}
+
+.ui.segment,
+.ui.attached.segment,
+.repository .header-wrapper,
+.page-content {
+	border-color: #2a2a2a !important;
+	background: #0a0a0a !important;
+}
+
+.ui.card,
+.ui.cards > .card {
+	border: 1px solid #2a2a2a !important;
+	background: #111111 !important;
+}
+
+.flex-item {
+	border-bottom: 1px solid #1a1a1a !important;
+}
+
+a:not(.button):not(.ui.label) {
+	color: #ffff00 !important;
+	text-decoration: none !important;
+}
+
+a:not(.button):hover {
+	text-decoration: underline !important;
+	color: #ffff66 !important;
+}
+
+.ui.button,
+.ui.basic.button {
+	border-radius: 0 !important;
+	font-family: inherit !important;
+	text-transform: none !important;
+	font-weight: 400 !important;
+}
+
+.ui.primary.button,
+a.button.primary {
+	background: #ffff00 !important;
+	color: #0a0a0a !important;
+	border: 1px solid #ffff00 !important;
+	font-weight: 700 !important;
+}
+
+.ui.primary.button:hover,
+a.button.primary:hover {
+	background: #0a0a0a !important;
+	color: #ffff00 !important;
+	border: 1px solid #ffff00 !important;
+}
+
+.ui.basic.button {
+	background: transparent !important;
+	color: #8a8a8a !important;
+	border: 1px solid #2a2a2a !important;
+}
+
+.ui.basic.button:hover {
+	color: #ffff00 !important;
+	border-color: #ffff00 !important;
+	background: transparent !important;
+}
+
+.ui.tabular.menu {
+	border-bottom: 1px solid #2a2a2a !important;
+}
+
+.ui.tabular.menu .item {
+	border-radius: 0 !important;
+	color: #8a8a8a !important;
+}
+
+.ui.tabular.menu .active.item {
+	background: #111111 !important;
+	color: #ffff00 !important;
+	border-color: #2a2a2a !important;
+	border-bottom-color: #0a0a0a !important;
+}
+
+.ui.secondary.menu .active.item {
+	color: #ffff00 !important;
+}
+
+#navbar,
+.ui.secondary.menu {
+	background: #0a0a0a !important;
+	border-bottom: 1px solid #2a2a2a !important;
+}
+
+.page-header,
+.repo-header {
+	border-bottom: 1px solid #2a2a2a !important;
+}
+
+.ui.dropdown .menu {
+	background: #111111 !important;
+	border: 1px solid #2a2a2a !important;
+}
+
+.ui.dropdown .menu > .item {
+	color: #d6d6d6 !important;
+}
+
+.ui.dropdown .menu > .item:hover {
+	background: #1a1a1a !important;
+	color: #ffff00 !important;
+}
+
+input,
+textarea,
+.ui.form input,
+.ui.input input,
+.ui.selection.dropdown {
+	background: #111111 !important;
+	color: #d6d6d6 !important;
+	border: 1px solid #2a2a2a !important;
+	border-radius: 0 !important;
+	font-family: inherit !important;
+}
+
+footer {
+	background: #0a0a0a !important;
+	border-top: 1px solid #2a2a2a !important;
+	color: #8a8a8a !important;
+}
+
+.code-view .lines-code,
+.code-view .lines-num,
+.chroma,
+pre,
+code {
+	font-family: inherit !important;
+}
+
+::-webkit-scrollbar {
+	width: 8px;
+	height: 8px;
+}
+
+::-webkit-scrollbar-track {
+	background: #0a0a0a;
+}
+
+::-webkit-scrollbar-thumb {
+	background: #2a2a2a;
+}
+
+::-webkit-scrollbar-thumb:hover {
+	background: #ffff00;
+}
+
+::selection {
+	background: rgba(255, 255, 0, 0.28);
+	color: #0a0a0a;
+}
diff --git a/site/404.html b/site/404.html
index eb7a9c1..f237d31 100644
--- a/site/404.html
+++ b/site/404.html
@@ -14,7 +14,7 @@
 <h2>404</h2>
 <hr class="rule">
 <p>The requested page does not exist.</p>
-<p><a href="../../../../">home</a> · <a href="../../../../docs/">handbook</a> · <a href="../../../../install/">install</a></p>
+<p><a href="../../../../">home</a> · <a href="../../../../docs/">handbook</a> · <a href="../../../../git/">git</a> · <a href="../../../../install/">install</a></p>
 </main>
 @@FOOTER@@
 </div>
diff --git a/site/assets/git.js b/site/assets/git.js
new file mode 100644
index 0000000..19d65d2
--- /dev/null
+++ b/site/assets/git.js
@@ -0,0 +1,80 @@
+// Fill /git/ with GitHub metadata and whether Splux Git answers.
+(function () {
+	var API = "https://api.github.com/repos/RobertFlexx/";
+	var ACCEPT = { Accept: "application/vnd.github+json" };
+	var thisScript = document.currentScript ||
+		document.querySelector("script[src*=\"git.js\"]");
+	var gitHost = (thisScript && thisScript.getAttribute("data-git")) ||
+		"https://git.splux.robertflexx.dev";
+	gitHost = String(gitHost).replace(/\/+$/, "");
+
+	function esc(s) {
+		return String(s)
+			.replace(/&/g, "&amp;")
+			.replace(/</g, "&lt;")
+			.replace(/>/g, "&gt;")
+			.replace(/"/g, "&quot;");
+	}
+
+	function json(url) {
+		return fetch(url, { headers: ACCEPT }).then(function (r) {
+			if (!r.ok)
+				throw new Error(String(r.status));
+			return r.json();
+		});
+	}
+
+	function when(iso) {
+		if (!iso)
+			return "";
+		var d = new Date(iso);
+		if (isNaN(d.getTime()))
+			return iso;
+		try {
+			return d.toLocaleString(undefined, {
+				year: "numeric",
+				month: "short",
+				day: "numeric"
+			});
+		} catch (e) {
+			return iso.slice(0, 10);
+		}
+	}
+
+	var status = document.getElementById("git-status");
+	if (status) {
+		fetch(gitHost + "/api/v1/version", { mode: "cors" }).then(function (r) {
+			if (!r.ok)
+				throw new Error(String(r.status));
+			return r.json();
+		}).then(function (v) {
+			var ver = (v && (v.version || v.forgejo)) || "up";
+			status.innerHTML = "reachable (" + esc(String(ver)) + ")";
+			var rows = document.querySelectorAll("#git-repos tr[data-repo]");
+			for (var j = 0; j < rows.length; j++) {
+				var name = rows[j].getAttribute("data-repo");
+				var a = rows[j].querySelector("a.name");
+				if (a && name)
+					a.setAttribute("href", gitHost + "/RobertFlexx/" + name);
+			}
+		}).catch(function () {
+			status.textContent =
+				"not reachable from this browser yet; clone GitHub until DNS and TLS exist";
+		});
+	}
+
+	var rows = document.querySelectorAll("#git-repos tr[data-repo]");
+	for (var i = 0; i < rows.length; i++) {
+		(function (row) {
+			var name = row.getAttribute("data-repo");
+			json(API + encodeURIComponent(name)).then(function (repo) {
+				var lang = row.querySelector(".lang");
+				var pushed = row.querySelector(".pushed");
+				if (lang && repo.language)
+					lang.textContent = repo.language;
+				if (pushed && repo.pushed_at)
+					pushed.textContent = when(repo.pushed_at);
+			}).catch(function () {});
+		})(rows[i]);
+	}
+})();
diff --git a/site/docs/index.html b/site/docs/index.html
index 8bf2a3a..d8c940b 100644
--- a/site/docs/index.html
+++ b/site/docs/index.html
@@ -17,7 +17,7 @@
 <main id="main">
 <h2>Handbook</h2>
 <hr class="rule">
-<p>This is the Splux Linux handbook. Splux is a source-based distribution. Packages are built on the machine that installs them, from recipes in <a href="https://github.com/RobertFlexx/sps-core">sps-core</a> and <a href="https://github.com/RobertFlexx/sps-extra">sps-extra</a>, using SPS.</p>
+<p>This is the Splux Linux handbook. Splux is a source-based distribution. Packages are built on the machine that installs them, from recipes in <a href="../../../../git/">sps-core and sps-extra</a>, using SPS.</p>
 <p>Live images are development snapshots, not a numbered stable release. Read the chapter that matches the ISO you booted.</p>
 
 <h3>Start here</h3>
@@ -61,6 +61,7 @@
 <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-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="../../../../git/">Splux Git</a></li>
 <li><a href="../../../../source/">Source repositories</a></li>
 </ul>
 
diff --git a/site/git/index.html b/site/git/index.html
new file mode 100644
index 0000000..93934b5
--- /dev/null
+++ b/site/git/index.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Git - Splux Linux</title>
+<meta name="description" content="Official Splux Git repositories. Splux Git is the forge. GitHub is a mirror of git history.">
+<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>Git</h2>
+<hr class="rule">
+<p>Splux Git is the official forge: clone, commit history, file trees, accounts, and pull requests. GitHub holds a read-only mirror of the same git history. Live ISO files still come from GitHub Releases.</p>
+<div class="info">
+<div class="dl-row"><span class="muted">Forge</span><span><a href="https://splux.robertflexx.dev/git">https://splux.robertflexx.dev/git</a></span></div>
+<div class="dl-row"><span class="muted">Status</span><span id="git-status">checking</span></div>
+<div class="dl-row"><span class="muted">Mirror</span><span><a href="https://github.com/RobertFlexx">github.com/RobertFlexx</a></span></div>
+</div>
+
+<h2>Repositories</h2>
+<hr class="rule">
+<table class="pkgs" id="git-repos">
+<thead>
+<tr><th>name</th><th>role</th><th>language</th><th>updated</th></tr>
+</thead>
+<tbody>
+<tr data-repo="SPS">
+<td><a class="name" href="https://github.com/RobertFlexx/SPS">SPS</a></td>
+<td>package system, installer, live ISO builder</td>
+<td class="lang muted">Shell</td>
+<td class="pushed muted"></td>
+</tr>
+<tr data-repo="sps-core">
+<td><a class="name" href="https://github.com/RobertFlexx/sps-core">sps-core</a></td>
+<td>official base recipes</td>
+<td class="lang muted">Shell</td>
+<td class="pushed muted"></td>
+</tr>
+<tr data-repo="sps-extra">
+<td><a class="name" href="https://github.com/RobertFlexx/sps-extra">sps-extra</a></td>
+<td>official extra recipes</td>
+<td class="lang muted">Shell</td>
+<td class="pushed muted"></td>
+</tr>
+<tr data-repo="splux-site">
+<td><a class="name" href="https://github.com/RobertFlexx/splux-site">splux-site</a></td>
+<td>this website</td>
+<td class="lang muted">HTML</td>
+<td class="pushed muted"></td>
+</tr>
+</tbody>
+</table>
+<p class="note">If the forge link does not answer yet, use the GitHub clone URLs below. The trees are the same commits.</p>
+
+<h2>Clone (GitHub mirror)</h2>
+<hr class="rule">
+<p>These URLs work from anywhere today.</p>
+<pre class="block">git clone https://github.com/RobertFlexx/SPS.git
+git clone https://github.com/RobertFlexx/sps-core.git
+git clone https://github.com/RobertFlexx/sps-extra.git
+git clone https://github.com/RobertFlexx/splux-site.git</pre>
+
+<h2>Clone (Splux Git)</h2>
+<hr class="rule">
+<p>Use these when <code>git.splux.robertflexx.dev</code> has DNS and TLS, or from the LAN that runs the forge.</p>
+<pre class="block">git clone https://splux.robertflexx.dev/git/RobertFlexx/SPS.git
+git clone https://splux.robertflexx.dev/git/RobertFlexx/sps-core.git
+git clone https://splux.robertflexx.dev/git/RobertFlexx/sps-extra.git
+git clone https://splux.robertflexx.dev/git/RobertFlexx/splux-site.git</pre>
+
+<h2>Pull requests</h2>
+<hr class="rule">
+<p>Open them on Splux Git. GitHub does not receive pull requests, issues, or accounts. A push mirror copies commits, branches, and tags only.</p>
+<p>Registration on the forge is closed until the host is on a public name. Clone and browse do not need an account.</p>
+
+<h2>What stays on GitHub</h2>
+<hr class="rule">
+<ul class="plain">
+<li>Live ISO files and SHA256SUMS</li>
+<li>This handbook (GitHub Pages)</li>
+<li>The news feed GitHub API calls</li>
+</ul>
+</main>
+@@FOOTER@@
+</div>
+<script src="../../../../assets/git.js?v=4c4f12c-20261019130111" data-root="../../../../" data-git="https://splux.robertflexx.dev/git"></script>
+</body>
+</html>
diff --git a/site/index.html b/site/index.html
index df23376..3784f59 100644
--- a/site/index.html
+++ b/site/index.html
@@ -69,9 +69,9 @@ pkin: installed curl 8.21.0-2</pre>
 
 <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>
+<p><a href="../../../../git/">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>
+<p><a href="../../../../git/">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>
 
@@ -86,8 +86,10 @@ Desktop software, graphics, multimedia, development tools, and other packages. @
 <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="../../../../git/">Splux Git</a>
+canonical forge<br>
+<a href="https://github.com/RobertFlexx/SPS">SPS</a>
+the package system (GitHub mirror)<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>
diff --git a/site/packages/index.html b/site/packages/index.html
index eb2cf21..a0aa6aa 100644
--- a/site/packages/index.html
+++ b/site/packages/index.html
@@ -15,7 +15,7 @@
 <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>
+<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. Browse them from <a href="../../../../git/">Splux Git</a>.</p>
 <form id="pkg-search" role="search">
 <label class="search" for="q">Search
 <input id="q" name="q" type="search" autocomplete="off" spellcheck="false">
diff --git a/site/source/index.html b/site/source/index.html
index 9466a9e..e97de3e 100644
--- a/site/source/index.html
+++ b/site/source/index.html
@@ -15,7 +15,8 @@
 <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>
+<p>Canonical trees belong on <a href="../../../../git/">Splux Git</a>. Until that host has a public name, clone the GitHub mirrors. License for SPS and the package collections is ISC. See each repository.</p>
+<p><a href="https://github.com/RobertFlexx/SPS">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>
@@ -23,7 +24,6 @@ Official base recipe collection.</p>
 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>
diff --git a/tools/build-site.sh b/tools/build-site.sh
index ea15d4c..e9c12a3 100755
--- a/tools/build-site.sh
+++ b/tools/build-site.sh
@@ -17,6 +17,7 @@ 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}
 SITE_URL=${SITE_URL:-https://splux.robertflexx.dev}
+GIT_HOST=${GIT_HOST:-https://git.splux.robertflexx.dev}
 
 clone_tree() {
 	url=$1
@@ -45,7 +46,7 @@ fi
 rm -rf "$OUT"
 mkdir -p "$OUT/assets" "$OUT/data" "$OUT/download" "$OUT/packages" \
 	"$OUT/install" "$OUT/docs" "$OUT/docs/images" "$OUT/docs/sps" \
-	"$OUT/docs/first-boot" "$OUT/source" "$OUT/news"
+	"$OUT/docs/first-boot" "$OUT/source" "$OUT/news" "$OUT/git"
 
 cp -a site/assets/. "$OUT/assets/"
 if [ -f CNAME ]; then
@@ -68,6 +69,7 @@ write_header() {
 <a href="${root}packages/">packages</a>
 <a href="${root}install/">install</a>
 <a href="${root}docs/">docs</a>
+<a href="${root}git/">git</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>
@@ -80,7 +82,7 @@ write_footer() {
 	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><a href="${root}">home</a> · <a href="${root}packages/">packages</a> · <a href="${root}git/">git</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
@@ -97,7 +99,8 @@ write_docs_nav() {
 		images "${root}docs/images/" "Live images" \
 		install "${root}install/" "Install" \
 		sps "${root}docs/sps/" "SPS tools" \
-		firstboot "${root}docs/first-boot/" "After first boot"
+		firstboot "${root}docs/first-boot/" "After first boot" \
+		git "${root}git/" "Git"
 	while [ "$#" -ge 3 ]
 	do
 		if [ "$cur" = "$1" ]; then
@@ -314,6 +317,7 @@ subst() {
 		-v generated="$generated" \
 		-v livesig="$livesig" \
 		-v curlver="$curlver" \
+		-v githost="$GIT_HOST" \
 		"$src" >"$dest"
 }
 
@@ -327,6 +331,7 @@ subst site/docs/images/index.html "$OUT/docs/images/index.html" "../../" images
 subst site/docs/sps/index.html "$OUT/docs/sps/index.html" "../../" sps
 subst site/docs/first-boot/index.html "$OUT/docs/first-boot/index.html" "../../" firstboot
 subst site/source/index.html "$OUT/source/index.html" "../"
+subst site/git/index.html "$OUT/git/index.html" "../"
 
 awk -f tools/news.awk -v mode=html -v page=1 -v per="$news_per" \
 	"$tmp/news.tsv" >"$tmp/news.html"
@@ -398,6 +403,7 @@ do
 		-v generated="$generated" \
 		-v livesig="$livesig" \
 		-v curlver="$curlver" \
+		-v githost="$GIT_HOST" \
 		"$page" >"$tmp/page.html"
 	mv "$tmp/page.html" "$page"
 done
diff --git a/tools/subst.awk b/tools/subst.awk
index 9ab025f..eb37dc2 100644
--- a/tools/subst.awk
+++ b/tools/subst.awk
@@ -20,6 +20,8 @@ BEGIN {
 			footer = footer line "\n"
 		close(footerfile)
 	}
+	if (githost == "")
+		githost = "https://git.splux.robertflexx.dev"
 }
 
 $0 == "@@HEADER@@" {
@@ -79,5 +81,6 @@ $0 == "@@DOCSNAV@@" {
 	gsub(/4c4f12c-20261019130111/, livesig)
 	gsub(/8.21.0-2/, curlver)
 	gsub(//, pagetitle)
+	gsub(/https://splux.robertflexx.dev/git/, githost)
 	print
 }