diff --git a/docs/server.mdx b/docs/server.mdx
index d228a0a40..ff8dd4b74 100644
--- a/docs/server.mdx
+++ b/docs/server.mdx
@@ -1,5 +1,6 @@
---
title: Bluefin Server
+hide_title: true
description: An image-based Linux server OS built like a container, composed from freedesktop-sdk.
slug: /server
custom_edit_url: https://github.com/projectbluefin/server/edit/main/README.md
@@ -13,9 +14,9 @@ import {
} from "@site/src/components/ImageProductSection";
import { PerImageUsage } from "@site/src/components/analytics/CountmeAnalyticsCharts";
-
-
-
+
+
+
## Downloads & Image Versions
diff --git a/docs/skills/variant-docs-pages.md b/docs/skills/variant-docs-pages.md
index 430b4955c..c3cb03abd 100644
--- a/docs/skills/variant-docs-pages.md
+++ b/docs/skills/variant-docs-pages.md
@@ -63,11 +63,8 @@ motivation prose — see _Never write in a maintainer's voice_ in `AGENTS.md`.
no second route and needs no sidebar or global Markdown configuration changes.
Its `mdx.format: md` frontmatter preserves ordinary Markdown and raw HTML;
the wrapper retains native headings, TOC, search, and the upstream edit link.
-Keep `title` in frontmatter for navigation and the single top-level `h1`.
-Do not set `hide_title: true`; `scripts/fetch-readmes.mjs` strips any leading
-`# Title` heading from the imported README when `product` is specified so
-Docusaurus emits exactly one page title.
-
+Keep `title` in frontmatter for navigation and metadata. When an image logo already contains the product wordmark, set `hide_title: true` so the visual page header avoids duplicate title text while `` preserves an accessible screen-reader `
`.
+`scripts/fetch-readmes.mjs` strips leading `# Title` headings and scale comparison silhouettes from imported product READMEs so the landing page retains a clean single-character identity and flow.
`scripts/fetch-readmes.mjs` rewrites parsed links/images, not code examples.
It reads its source list from `src/components/image-identities.json`.
Because `format: md` passes raw HTML through to the page and the seeds are
@@ -166,6 +163,7 @@ card entry from `IMAGES` in `ImageCards.tsx`.
explicit, and link build-time recipes to upstream repositories. When headings
are simplified, place explicit HTML anchors (``) so legacy links
do not 404.
+
## Common Rationalizations
| Rationalization | Reality |
diff --git a/scripts/fetch-readmes.mjs b/scripts/fetch-readmes.mjs
index 227dc57cf..14091e9f8 100644
--- a/scripts/fetch-readmes.mjs
+++ b/scripts/fetch-readmes.mjs
@@ -243,7 +243,9 @@ export async function prepareReadme(
const resolve = (value, image) => {
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(value)) return value;
if (product === "server" && !image) {
- const match = value.match(/^(?:docs\/skills\/)?([^/#?]+?\.md)(?:#(.+))?$/);
+ const match = value.match(
+ /^(?:docs\/skills\/)?([^/#?]+?\.md)(?:#(.+))?$/,
+ );
if (match && serverIndexedDocs.has(match[1])) {
const slug = match[1].slice(0, -3);
return match[2] ? `/server/${slug}/#${match[2]}` : `/server/${slug}/`;
@@ -266,6 +268,11 @@ export async function prepareReadme(
identity.logoLight,
identity.logoDark,
...identity.images.map((image) => image.src),
+ ...(product === "server"
+ ? [
+ "https://upload.wikimedia.org/wikipedia/commons/d/d8/Dicraeosauridae_Scale.svg",
+ ]
+ : []),
].filter(Boolean),
);
const matchesArtwork = (url) => artwork.has(resolve(url, true));
diff --git a/scripts/fetch-readmes.test.js b/scripts/fetch-readmes.test.js
index 6378b2710..4d3685371 100644
--- a/scripts/fetch-readmes.test.js
+++ b/scripts/fetch-readmes.test.js
@@ -63,6 +63,20 @@ test("server indexed docs resolve to local documentation routes", async () => {
/\[Architecture Roadmap\]\(https:\/\/github.com\/projectbluefin\/server\/blob\/main\/docs\/skills\/architecture-roadmap.md\)/,
);
});
+test("server product README strips scale comparison silhouette image", async () => {
+ const serverSource = {
+ product: "server",
+ repo: "projectbluefin/server",
+ ref: "main",
+ readmePath: "README.md",
+ };
+ const input =
+ "> An image-based Linux server OS built like a container\n\n" +
+ "\n\n" +
+ "## Release status\n";
+ const result = await prepareReadme(input, serverSource);
+ assert.doesNotMatch(result, /Dicraeosauridae_Scale\.svg/);
+});
test("a failed refresh preserves the offline seed rather than publishing an empty README", async () => {
const directory = await mkdtemp(join(tmpdir(), "readme-import-"));
diff --git a/src/components/ImageIdentity.module.css b/src/components/ImageIdentity.module.css
index 259391d07..f9c3b9933 100644
--- a/src/components/ImageIdentity.module.css
+++ b/src/components/ImageIdentity.module.css
@@ -2,6 +2,18 @@
margin-bottom: 2rem;
}
+.srOnly {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
.wordmark {
display: inline-block;
max-width: 100%;
@@ -121,3 +133,101 @@
.sources {
margin-bottom: 0;
}
+
+/* ── Hero top of page layout with wrapping text ───────────────────────── */
+
+.identityHero {
+ margin-bottom: 2.5rem;
+ padding-bottom: 1.5rem;
+ border-bottom: 1px solid var(--ifm-color-emphasis-200);
+}
+
+.heroTop {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ gap: 1rem 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.heroTop .wordmark {
+ flex-shrink: 0;
+}
+
+.heroTop .species {
+ margin: 0;
+ font-size: 0.95rem;
+ color: var(--ifm-color-emphasis-700);
+}
+
+/* Float character artwork right so sibling prose flows around it */
+.heroContent::after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.heroCharacter {
+ float: right;
+ width: 320px;
+ max-width: 42%;
+ margin: 0 0 1.5rem 2rem;
+ text-align: center;
+}
+
+.heroCharacter img {
+ display: block;
+ width: 100%;
+ height: auto;
+ max-height: 380px;
+ object-fit: contain;
+ filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15));
+}
+
+[data-theme="dark"] .heroCharacter img {
+ filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.45));
+}
+.heroCharacter figcaption {
+ font-size: 0.8rem;
+ color: var(--ifm-color-emphasis-600);
+ margin-top: 0.4rem;
+}
+
+.heroProse {
+ font-size: 1.05rem;
+ line-height: 1.65;
+ color: var(--ifm-font-color-base);
+}
+
+.heroProse blockquote {
+ margin: 0 0 1rem;
+ padding: 0.75rem 1rem;
+ font-size: 1.15rem;
+ font-weight: 500;
+ color: var(--ifm-color-primary-darker);
+ border-left: 4px solid var(--ifm-color-primary);
+ background: var(--ifm-color-emphasis-100);
+ border-radius: 0 var(--ifm-global-radius) var(--ifm-global-radius) 0;
+}
+
+[data-theme="dark"] .heroProse blockquote {
+ color: var(--ifm-color-primary-lightest);
+ background: var(--ifm-color-emphasis-200);
+}
+
+.heroProse p {
+ margin-bottom: 1rem;
+}
+
+@media (max-width: 768px) {
+ .heroTop {
+ gap: 0.5rem 1rem;
+ }
+
+ .heroCharacter {
+ float: none;
+ width: 100%;
+ max-width: 260px;
+ margin: 0 auto 1.5rem;
+ }
+}
diff --git a/src/components/ImageIdentity.tsx b/src/components/ImageIdentity.tsx
index 1eade0e31..600634af9 100644
--- a/src/components/ImageIdentity.tsx
+++ b/src/components/ImageIdentity.tsx
@@ -4,10 +4,84 @@ import styles from "./ImageIdentity.module.css";
export type ImageProduct = keyof typeof identities;
-export default function ImageIdentity({ product }: { product: ImageProduct }) {
+export default function ImageIdentity({
+ product,
+ children,
+}: {
+ product: ImageProduct;
+ children?: React.ReactNode;
+}) {
const identity = identities[product];
const readme = `https://github.com/${identity.repo}/blob/${identity.ref}/README.md`;
+ if (children) {
+ const primaryImage = identity.images[0];
+ return (
+
+
+
+ );
+ }
+
return (
{identity.logoLight && (
diff --git a/src/components/image-identities.json b/src/components/image-identities.json
index 67e321b75..026c75a27 100644
--- a/src/components/image-identities.json
+++ b/src/components/image-identities.json
@@ -18,13 +18,6 @@
"source": "/dinosaurs#kubernetes",
"attribution": "Jacob Schnurr · Project Bluefin artwork",
"diagram": false
- },
- {
- "src": "https://upload.wikimedia.org/wikipedia/commons/d/d8/Dicraeosauridae_Scale.svg",
- "alt": "Dicraeosauridae silhouette size comparison, including Amargasaurus and a human figure",
- "source": "https://commons.wikimedia.org/wiki/File:Dicraeosauridae_Scale.svg",
- "attribution": "Slate Weasel · Public domain",
- "diagram": true
}
]
},
diff --git a/src/content/server-readme.md b/src/content/server-readme.md
index 78705329b..ef4c7b159 100644
--- a/src/content/server-readme.md
+++ b/src/content/server-readme.md
@@ -3,7 +3,7 @@ mdx:
format: md
---
-**An image-based Linux server OS built like a container, composed from freedesktop-sdk.**
+> An image-based Linux server OS built like a container, composed from freedesktop-sdk.
Bluefin Server targets the same workload space as Flatcar Container Linux, Fedora CoreOS, and Talos. Built with [BuildStream 2](https://buildstream.build/), its userspace, kernel, and boot chain compose from [freedesktop-sdk](https://freedesktop-sdk.freedesktop.org/) (FSDK 26.08, systemd v261) components. No other distribution binaries ship in the base image; third-party userspace extensions (such as Flatcar System Extension Bakery images) require `systemd-sysext merge --force` per [systemd-sysext-extensions.md](/server/systemd-sysext-extensions/).
@@ -21,14 +21,35 @@ Bluefin Server is currently in **Alpha**:
## Architecture
-* **Diskless boot** — Netboot UKI pulls `bluefin-server_.raw` into RAM with `rd.systemd.pull` (`verify=signature`), mounts a dm-verity erofs `/usr`, and runs from tmpfs. Nodes update by rebooting into a newer image, flagging `/run/reboot-required` when an updated image is offered.
+* **Diskless boot** — Netboot UKI pulls `bluefin-server_.raw` into RAM with `rd.systemd.pull` (`verify=signature`), mounts a dm-verity erofs `/usr`, and runs from tmpfs. Nodes update by rebooting into a newer image, flagging `/run/reboot-required` when an updated image is offered. This is the recommended configuration for Kubernetes worker nodes.
* **Optional disk install with A/B rollback** — A running diskless node installs itself via [ddi-installer.md](/server/ddi-installer/) (`systemd-sysinstall` copies `/usr` into slot A). First disk boot creates slot B and an xfs root. `systemd-sysupdate` fills the inactive slot and reboots nightly (Kubernetes nodes defer reboots to kured); UKI boot counting automatically rolls back failed boots. Updates can be held via `/run/reboot-lock` or `/etc/reboot-lock`.
+ * This is the recommended mode for Kubernetes Control Planes, and Home Servers.
* **Secure Boot by default** — Signed boot chain with `lockdown=integrity`. [Booty](https://github.com/jeefy/booty) serves UEFI HTTP boot assets and per-node Ignition configs.
* **Declarative provisioning** — Pass an `ignition.config` credential (or `bluefin-node.ign` next to the UKI); Ignition runs in the initrd on every boot. Configs must be idempotent.
* **Opt-in sysexts** — k0s, kubeadm, homelab automation, OpenZFS, and NVIDIA drivers/CDI ship as separate `systemd-sysext` images outside the base `/usr`.
> **Remote diagnostics:** OpenSSH is installed for diagnostics but disabled by default via presets. Start manually with `systemctl start sshd` when access is required. See [`docs/skills/factory-integration.md`](https://github.com/projectbluefin/server/blob/main/docs/skills/factory-integration.md).
+## Installation Profiles
+
+Choose your profile during installation. Start small and build up or go full lazy mode ootb.
+
+### Core and Kubernetes Profiles
+
+| Boot Menu Option | Best For | Included Software | First Boot Login |
+| ------------------------------ | --------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
+| **Bluefin Server** *(Default)* | Minimal servers and automated deployments | Base OS only (read-only system root, systemd, SSH). No container engines or Kubernetes. | Prompts on the local console (tty1) to set the root password. |
+| **Homelab: Control Plane** | Primary node for a multi-machine Kubernetes cluster | Base OS + kubeadm, containerd, Argo Workflows, and a KubeStellar dashboard | Prompts on tty1 for a root password. Prints the cluster join passphrase on the screen. |
+| **Homelab: Node** | Worker nodes joining an existing Homelab cluster | Base OS + kubeadm, containerd, and auto-join service. | Prompts for the cluster join passphrase during install; prompts on tty1 for a root password on first boot. |
+
+### Home Server Profile
+
+This is lazy mode, which includes `brew` for that lazy Ubuntu Server vibe. This mode is great for the home to run everything, or if you just need a box/VM that needs an OS to run one thing. Having brew baked in makes this perfect for VMs/machines to hold your agents/tmux, etc.
+
+| Boot Menu Option | Best For | Included Software | First Boot Login |
+| ----------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
+| **Bluefin Home Server** | Standalone personal server running containers and tools | Base OS + Podman (default) + Homebrew inside an isolated nspawn system container. Root stays locked. | Prompts on the local console to create a regular user account and password (or accepts an SSH key via credential). |
+
## Downloads
Each push to `main` publishes an immutable [GitHub Release](https://github.com/projectbluefin/server/releases) (`v`) and an OCI artifact at `ghcr.io/projectbluefin/bluefin-server:`.
@@ -60,20 +81,20 @@ just dogfood-install # diskless boot, install to disk, verify rollback (QEMU)
Start here to explore architecture and operations guides in [`docs/skills/`](https://github.com/projectbluefin/server/blob/main/docs/skills/index.md):
-| Guide | Scope |
-| ------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
-| [`ddi-installer.md`](/server/ddi-installer/) | Boot, installation, and A/B sysupdate lifecycle |
-| [`usb-installer.md`](/server/usb-installer/) | Offline USB installer, interactive prompts, and unattended installs |
-| [`booty-integration.md`](/server/booty-integration/) | UEFI HTTP boot, iPXE chainloading, and network boot provisioning |
-| [`diskless-troubleshooting.md`](/server/diskless-troubleshooting/) | RAM sizing, initrd failure modes, and Ignition debugging |
-| [`systemd-sysext-extensions.md`](/server/systemd-sysext-extensions/) | System extensions, bakery compatibility, and version locking |
-| [`kubeadm-sysext.md`](/server/kubeadm-sysext/) | Kubeadm worker and single-node control-plane extensions |
-| [`k0s-sysext-ops.md`](/server/k0s-sysext-ops/) | K0s cluster operations, tokens, and troubleshooting |
-| [`homelab-profile.md`](/server/homelab-profile/) | Homelab Ignition templates, mDNS cluster join, and add-ons |
-| [`nvidia-sysext.md`](/server/nvidia-sysext/) | NVIDIA open kernel modules and Container Toolkit CDI extensions |
-| [`systemd-sysupdate-verification.md`](/server/systemd-sysupdate-verification/) | Release signatures, sysupdate policies, and SBOM attestations |
-| [`secure-boot-keys.md`](/server/secure-boot-keys/) | Key enrollment, signing workflows, and shim integration |
-| [`enabling-extensions.md`](/server/enabling-extensions/) | Enabling extensions (ZFS, NVIDIA, Kubernetes, Podman, Docker) |
+| Guide | Scope |
+| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
+| [`ddi-installer.md`](/server/ddi-installer/) | Boot, installation, and A/B sysupdate lifecycle |
+| [`usb-installer.md`](/server/usb-installer/) | Offline USB installer, interactive prompts, and unattended installs |
+| [`booty-integration.md`](/server/booty-integration/) | UEFI HTTP boot, iPXE chainloading, and network boot provisioning |
+| [`diskless-troubleshooting.md`](/server/diskless-troubleshooting/) | RAM sizing, initrd failure modes, and Ignition debugging |
+| [`systemd-sysext-extensions.md`](/server/systemd-sysext-extensions/) | System extensions, bakery compatibility, and version locking |
+| [`kubeadm-sysext.md`](/server/kubeadm-sysext/) | Kubeadm worker and single-node control-plane extensions |
+| [`k0s-sysext-ops.md`](/server/k0s-sysext-ops/) | K0s cluster operations, tokens, and troubleshooting |
+| [`homelab-profile.md`](/server/homelab-profile/) | Homelab Ignition templates, mDNS cluster join, and add-ons |
+| [`nvidia-sysext.md`](/server/nvidia-sysext/) | NVIDIA open kernel modules and Container Toolkit CDI extensions |
+| [`systemd-sysupdate-verification.md`](/server/systemd-sysupdate-verification/) | Release signatures, sysupdate policies, and SBOM attestations |
+| [`secure-boot-keys.md`](/server/secure-boot-keys/) | Key enrollment, signing workflows, and shim integration |
+| [`gap-analysis-distros.md`](https://github.com/projectbluefin/server/blob/main/docs/skills/gap-analysis-distros.md) | Comparison to Flatcar, Fedora CoreOS, Ubuntu, and Talos |
Complete manifest and developer commands are listed in [`AGENTS.md`](https://github.com/projectbluefin/server/blob/main/AGENTS.md) and [`CONTRIBUTING.md`](https://github.com/projectbluefin/server/blob/main/CONTRIBUTING.md).