docs: refine strategy — ostreecontainer install + mesh stack + browser stack

Refines docs/STRATEGY.md per parent-operator handoff (2026-05-05).
Locks in five things the original draft didn't cover, and corrects
one mistake.

## Refinement: ostreecontainer install path

The original draft proposed a two-step install: Anaconda partitions
+ kickstart, then on first boot a `veilor-firstboot-rebase.service`
runs `bootc rebase ghcr.io/veilor/veilor-os:43`. This commit drops
that step.

Anaconda's `ostreecontainer --url=... --transport=registry`
directive populates the root filesystem directly from the OCI image
during the install pass. No first-boot rebase, no transition
window, no second reboot. Same end state, simpler path.

Stay on `ostreecontainer` through v0.8. Do NOT migrate to the new
`bootc` kickstart command until v1.0 — it blocks multi-disk and
authenticated registries. Do NOT use `bootc-image-builder
anaconda-iso` output — deprecated in image-builder v44+. Produce
the OCI image and the bootstrap ISO as separate artifacts.

This compresses the v0.7 BlueBuild spike from 2 days → 1 day.

## Correction: keep Trivalent as default

The original strategy.md treated Trivalent (secureblue's hardened
Chromium) as an override-and-remove. That was wrong: Trivalent's
COPR tracks upstream M147+ within hours, ships hardened_malloc +
JIT-less + Drumbrake WASM. Default browser pick.

Mullvad Browser layered alongside for anti-fingerprint. Thorium
remains opt-in via `ujust install-thorium` only — its CVE lag is
months and contradicts the threat model. Never default.

## Mesh stack baked in

Three-layer warm-stack documented in STRATEGY.md:
- L3a Tailscale + Headscale (Day 1, daily driver)
- L3b Yggdrasil-go (Day 1, idle warm-fallback, AllowedPublicKeys mode)
- L3c Reticulum/RetiNet AGPL fork (opt-in via ujust install-reticulum)

Threat floor table: ISP-DNS-block (i, Day 1), ISP-Tailscale-block
(ii, Phase 2 promote Yggdrasil), internet-down (iii, opt-in RetiNet
+ RNode).

Tier model: tag:admin / tag:infra / tag:guest with failsafe pre-auth
key on yubikey + paper + Authentik OIDC group.

## Onboarding

Token paste / QR (user picks). Misskey signup mints reusable
24h-TTL pre-auth key. NOT auto-OIDC at first boot.

## Iroh seeding daemon stub (v0.8 / Phase 2)

`veilor-seed.service` documented but NOT implemented until Iroh hits
1.0 (current 0.96–0.98 RC, Q1 2026 target slipped). BLAKE3 +
iroh-gossip per-service topic. Static media only — DEFER DB
replication forever.

## External dependency tracked

nullstone Traefik `no-guest@file` ACL is currently 0.0.0.0/0
allow-all (XFF chain breakage 2026-05-03). Must be fixed before
veilor-os first-public-ISO ships, otherwise tag:guest provisioning
leaks the full vhost surface to every veilor user. Parent operator
owns the fix; explicitly out of veilor-os scope.

## Files

- docs/STRATEGY.md — full refinement
- docs/ROADMAP.md — v0.7 spike entry now reflects ostreecontainer
  + mesh stack + 1-day spike target
- README.md — drops the "v0.2.5 pre-release" badge + status box
  (out of date), adds bootc/atomic trajectory paragraph

## What did NOT change

- v0.5.x main branch is untouched. The ostreecontainer swap belongs
  in the v0.7 spike branch, NOT v0.5.32.
- nullstone Traefik config is untouched. Out of scope.
- The kickstart and overlay code is untouched.
This commit is contained in:
veilor-org 2026-05-05 15:15:52 +01:00
parent 8c409d323b
commit 21c0bbd120
3 changed files with 283 additions and 63 deletions

View file

@ -4,31 +4,42 @@
[![Build veilor-os ISO](https://github.com/veilor-org/veilor-os/actions/workflows/build-iso.yml/badge.svg)](https://github.com/veilor-org/veilor-os/actions/workflows/build-iso.yml) [![Build veilor-os ISO](https://github.com/veilor-org/veilor-os/actions/workflows/build-iso.yml/badge.svg)](https://github.com/veilor-org/veilor-os/actions/workflows/build-iso.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Status: pre-release](https://img.shields.io/badge/status-pre--release_v0.2.5-orange)](CHANGELOG.md)
veilor-os is a Fedora 43 KDE Plasma remix for operators who want a clean, veilor-os is a Fedora 43 KDE Plasma remix for operators who want a clean,
fast, opinionated desktop with serious hardening already wired in. Boot the fast, opinionated desktop with serious hardening already wired in. Boot the
ISO, set an admin password, work. No installer wizard. No initial-setup ISO, set an admin password, work. No installer wizard. No initial-setup
screen. No telemetry. No "would you like to enable X" prompts. screen. No telemetry. No "would you like to enable X" prompts.
The current install path is an Anaconda kickstart with a custom gum TUI
on top. v0.7+ ships a hybrid path: the kickstart ISO becomes the bootstrap
installer (Anaconda's LUKS UX is mature), but the root filesystem is
populated directly from a cosign-signed bootc OCI image built via BlueBuild
on top of [secureblue](https://github.com/secureblue/secureblue)'s
hardened Kinoite variant. Updates from there flow through `bootc upgrade`
— atomic A/B, instant rollback. v1.0 is bootc-only.
See [docs/STRATEGY.md](docs/STRATEGY.md) for the full trajectory.
--- ---
## Status ## Status
**Pre-release `v0.2.5`** — first feature-complete ISO that actually applies Active development on the install path. Three bug classes have been
the veilor-os overlay to the installed system. The build pipeline is green worked through (LUKS unlock cmdline, anaconda RPM-6.0 cmdline-mode
on CI; the live ISO boots to KDE on KVM and bare metal. See brittleness, bootloader install via `gen_grub_cfgstub`); current focus
[CHANGELOG.md](CHANGELOG.md) for the full v0.2.0 → v0.2.5 story (it is is the v0.5.32 blocker list from the
worth reading — five real bugs caught and documented). [2026-05-05 9-agent research wave](docs/research/2026-05-05-agent-wave/README.md).
What is **done**: hardening (SELinux, sysctl, USBGuard, fail2ban, What is **shipping**: hardening (SELinux, sysctl, USBGuard, fail2ban,
firewalld), KDE black theme, Fira Code system font, 3-mode power firewalld), KDE black theme, Fira Code system font, 3-mode power
management, single-prompt LUKS install, first-boot admin password flow, management, single-prompt LUKS install, first-boot admin password flow,
reproducible CI build, EFI+BIOS bootable live ISO. reproducible CI build, EFI+BIOS bootable live ISO.
What is **planned** (see [docs/ROADMAP.md](docs/ROADMAP.md)): Plymouth What is **planned** (see [docs/ROADMAP.md](docs/ROADMAP.md)): Plymouth
black theme, SDDM theme, signed ISOs (own MOK + GPG), AppArmor + nftables, + SDDM polish, signed ISOs (own MOK + GPG, sigstore/cosign on OCI),
veilor-update / veilor-doctor helpers, public docs site. AppArmor + nftables stack, `veilor-update` / `veilor-doctor` /
`veilor-postinstall` helpers, public docs site, **bootc OCI hybrid
spike at v0.7**, **bootc-only at v1.0**.
--- ---

View file

@ -223,15 +223,17 @@ public, benchmarks come after.
After threat model, not before. After threat model, not before.
5. **Press kit** — wallpapers, logo, screenshots, feature one-liner. 5. **Press kit** — wallpapers, logo, screenshots, feature one-liner.
### Hybrid bootc spike — layer on secureblue (REVISED 2026-05-05) ### Hybrid bootc spike — layer on secureblue, install via `ostreecontainer` (REVISED 2026-05-05)
The original v0.7 entry called for a Containerfile-from-scratch The original v0.7 entry called for a Containerfile-from-scratch
spike on `quay.io/fedora/fedora-bootc:43`. Research on 2026-05-05 spike on `quay.io/fedora/fedora-bootc:43`. Research on 2026-05-05
(see `docs/STRATEGY.md` and (see `docs/STRATEGY.md` and
`docs/research/2026-05-05-agent-wave/`) found a faster path: `docs/research/2026-05-05-agent-wave/`), then a parent-operator
**layer veilor's branding + threat model + UX on top of refinement same day, locked the path: **layer veilor's branding +
secureblue's already-shipping `securecore-kinoite-hardened-userns` threat model + UX on top of secureblue's already-shipping
OCI image** via a BlueBuild recipe. `securecore-kinoite-hardened-userns` OCI image** via a BlueBuild
recipe, and install it directly during the Anaconda pass via the
`ostreecontainer` kickstart directive (no first-boot rebase).
Reasoning: Reasoning:
@ -240,27 +242,55 @@ Reasoning:
surface we'd need to build alone (sysctl + kargs + SELinux surface we'd need to build alone (sysctl + kargs + SELinux
custom policy + USBGuard + hardened-malloc + Unbound DoT + custom policy + USBGuard + hardened-malloc + Unbound DoT +
cosign-signed OCI build pipeline). cosign-signed OCI build pipeline).
- Containerfile-from-scratch spike: 1 week to first ISO. - Containerfile-from-scratch spike: 1 week to first ISO. BlueBuild
BlueBuild recipe extending secureblue: ~2 days, ~200 lines recipe extending secureblue: ~2 days. With the `ostreecontainer`
YAML. The hardening review is inherited. swap (no `veilor-firstboot-rebase.service`, no transition window):
**~1 day**.
- secureblue does NOT publish a threat model. Athena OS does - secureblue does NOT publish a threat model. Athena OS does
(their main differentiator, only public threat model in (their main differentiator, only public threat model in
hardened-Linux 2026). Our `docs/THREAT-MODEL.md` (drafted) gets hardened-Linux 2026). Our `docs/THREAT-MODEL.md` (drafted) gets
us ahead of both on the one axis that matters most for a us ahead of both on the one axis that matters most for a
security-branded distro. security-branded distro.
Hybrid path locked: kickstart ISO stays as the **bootstrap Hybrid path locked:
installer** (Anaconda's LUKS UX is mature). On first boot, a
one-shot `veilor-firstboot-rebase` service runs `bootc rebase
ghcr.io/veilor/veilor-os:43`. From then on, `bootc upgrade` is
the update channel. v1.0 deprecates the kickstart entirely.
Overrides we apply over secureblue: replace Trivalent (their - Kickstart ISO stays as the **bootstrap installer** (Anaconda's
single-maintainer browser fork) with Brave or Mullvad-Browser; LUKS UX is mature).
keep sudo (revert `run0`-only); re-enable Xwayland. - `%packages` is replaced with `ostreecontainer
--url=ghcr.io/veilor/veilor-os:43 --transport=registry` so the
install pass populates `/` directly from the OCI image — no
first-boot rebase, no second reboot.
- From boot one onward, `bootc upgrade` is the update channel.
- v1.0 deprecates the kickstart entirely.
Stay on `ostreecontainer` through v0.8. **Do NOT migrate to the new
`bootc` kickstart command until v1.0** — it blocks multi-disk and
authenticated registries (likely needed eventually). **Do NOT use**
`bootc-image-builder anaconda-iso` output — deprecated in
image-builder v44+. Produce OCI image and bootstrap ISO as
**separate artifacts**.
Overrides over secureblue: keep Trivalent as default (their COPR
tracks upstream M147+ within hours; reverses earlier draft that
treated it as override-and-remove); add Mullvad Browser alongside;
gate Thorium behind `ujust install-thorium` with CVE-lag warning;
restore sudo (revert `run0`-only); re-enable Xwayland.
Mesh stack baked in: Tailscale (Day 1, daily driver), Yggdrasil-go
(Day 1, idle warm-fallback), Reticulum/RetiNet AGPL fork (opt-in
via `ujust install-reticulum`). See `docs/STRATEGY.md` mesh stack
section for the layer breakdown and threat-floor table.
Full plan: `docs/STRATEGY.md`. Spike will land in Full plan: `docs/STRATEGY.md`. Spike will land in
`bluebuild/recipe.yml` plus `.github/workflows/build-bluebuild.yml`. `bluebuild/recipe.yml` plus `.github/workflows/build-bluebuild.yml`,
on a separate branch — does NOT land in v0.5.x main.
External dependency tracked: Traefik `no-guest@file` ACL on
nullstone is currently an `0.0.0.0/0` allow-all stub. Must be
fixed before veilor-os first-public-ISO ships, otherwise
`tag:guest` provisioning leaks the full vhost surface to every
veilor user. **Parent operator owns the fix; not in veilor-os
scope.**
--- ---

View file

@ -1,6 +1,8 @@
# veilor-os Strategy — Hybrid kickstart bootstrap + bootc OCI # veilor-os Strategy — Hybrid kickstart bootstrap + bootc OCI
Decision date: **2026-05-05** Decision date: **2026-05-05** (refined same day from parent-operator
handoff, locks the `ostreecontainer` install path, mesh stack
bake-in, browser stack, Iroh seeding roadmap, and threat floor table).
Locked at: **v0.5.31 → v0.7 spike → v1.0** Locked at: **v0.5.31 → v0.7 spike → v1.0**
## TL;DR ## TL;DR
@ -8,9 +10,10 @@ Locked at: **v0.5.31 → v0.7 spike → v1.0**
- Keep the Anaconda-driven kickstart ISO as the **bootstrap installer** - Keep the Anaconda-driven kickstart ISO as the **bootstrap installer**
(LUKS UX is mature, single passphrase prompt, custom partitioning (LUKS UX is mature, single passphrase prompt, custom partitioning
works). works).
- On first boot, the installed system is automatically rebased to a - Anaconda's `ostreecontainer` directive populates the root filesystem
**veilor-os OCI image** built via BlueBuild on top of secureblue's directly from a **veilor-os OCI image** (built via BlueBuild on top
`securecore-kinoite-hardened-userns`. of secureblue's `securecore-kinoite-hardened-userns`) **during the
install pass — no first-boot rebase, no mutable→atomic transition**.
- All future updates flow through `bootc upgrade` — atomic A/B, - All future updates flow through `bootc upgrade` — atomic A/B,
instant rollback, cosign-signed. instant rollback, cosign-signed.
- The kickstart-driven mutable-root path is deprecated at v1.0; kept - The kickstart-driven mutable-root path is deprecated at v1.0; kept
@ -20,20 +23,53 @@ Locked at: **v0.5.31 → v0.7 spike → v1.0**
Pure pivot to bootc-from-scratch (Agent 3's spike plan) was **1 week Pure pivot to bootc-from-scratch (Agent 3's spike plan) was **1 week
to first ISO**. Pure pivot to layering on secureblue is **2 days to to first ISO**. Pure pivot to layering on secureblue is **2 days to
first ISO** because the hardening work is already done. But both first ISO** because the hardening work is already done. The
require throwing away the partitioning UX we already have working in `ostreecontainer` refinement compresses that to **1 day** by
Anaconda. eliminating the first-boot rebase choreography (no
`veilor-firstboot-rebase.service`, no second reboot, no transition
window where the system is half-mutable, half-atomic).
Both pure-pivot paths require throwing away the partitioning UX we
already have working in Anaconda. Hybrid keeps it.
Hybrid: Hybrid:
- **Day-zero install:** Anaconda kickstart + custom partitioning + - **Day-zero install:** Anaconda kickstart + custom partitioning +
LUKS prompt (what we have today). User experience = unchanged. LUKS prompt (what we have today). User experience = unchanged.
- **First boot, post-LUKS-unlock:** `bootc rebase - **End of install pass:** `ostreecontainer
ghcr.io/veilor/veilor-os:43` runs once; pulls the OCI image; next --url=ghcr.io/veilor/veilor-os:43 --transport=registry` populates
reboot lands in the veilor OCI tree. `/` from the OCI image. Transition is invisible.
- **First boot:** veilor OCI tree, no rebase, no special service.
- **Day-2:** `bootc upgrade` cadence for everything from then on. - **Day-2:** `bootc upgrade` cadence for everything from then on.
We keep what works, pivot the part that doesn't. We keep what works, pivot the part that doesn't.
## ostreecontainer directive (refinement, locked)
Replace the `%packages` block in the install kickstart with:
```
ostreecontainer --url=ghcr.io/veilor/veilor-os:43 --transport=registry
```
Keep the existing `part`/LUKS encryption block verbatim — Anaconda
partitions before `ostreecontainer` populates root.
**Stay on `ostreecontainer` through v0.8.** Do NOT migrate to the new
`bootc` kickstart command until v1.0 — `bootc` blocks multi-disk and
authenticated registries, both of which we'll likely need.
**Do NOT use** `bootc-image-builder anaconda-iso` output —
deprecated in image-builder v44+. Produce the OCI image and the
bootstrap ISO as **separate artifacts**:
- OCI image: BlueBuild recipe → cosign-signed image at
`ghcr.io/veilor/veilor-os:43`
- Bootstrap ISO: Anaconda kickstart with `ostreecontainer` directive
pointing at the OCI image
Reference: <https://docs.fedoraproject.org/en-US/bootc/>; pykickstart
docs for `ostreecontainer`.
## Why secureblue underneath ## Why secureblue underneath
| Question | Answer | | Question | Answer |
@ -47,14 +83,135 @@ We keep what works, pivot the part that doesn't.
What we override in our recipe: What we override in our recipe:
- **Browser**: Trivalent (their fork) → Brave / Mullvad-Browser.
Single-maintainer browser fork is unacceptable risk for daily-driver
audience.
- **`run0` instead of sudo**: revert. Breaks too many workflows. - **`run0` instead of sudo**: revert. Breaks too many workflows.
- **Xwayland disabled**: revert. Some apps still need it. - **Xwayland disabled**: revert. Some apps still need it.
- **veilor branding**: theme, KDE color scheme, Plymouth, SDDM, font, - **Veilor branding**: theme, KDE color scheme, Plymouth, SDDM, font,
os-release. All `overlay/*` ports verbatim from current repo. os-release. All `overlay/*` ports verbatim from current repo.
(Browser stack is its own section below — Trivalent is now a *kept*
default, not an override.)
## Browser stack
| Role | Pick | Source |
|---|---|---|
| **Default browser** | **Trivalent** (secureblue's hardened Chromium) | Fedora COPR `secureblue/trivalent` — tracks upstream M147+ within hours, ships hardened_malloc + JIT-less + Drumbrake WASM |
| **Anti-fingerprint companion** | **Mullvad Browser** | Clearnet, no Tor, layered alongside Trivalent for pseudonymous browsing |
| **Optional opt-in** | **Thorium** | `ujust install-thorium` only — WARN users of months-long CVE lag (LTS Chromium base, ~9 milestones behind upstream stable as of 2026-05) |
**DO NOT default to Thorium under any circumstances** — contradicts
the threat model. Trivalent's COPR keeps us inside one-hour-of-upstream
patch latency; Thorium is multi-month-stale and is a perf/media
profile choice, not a security choice.
The earlier draft of this doc treated Trivalent as an override-and-
remove. That was wrong: Trivalent is exactly the level of hardening
we want for a default browser. Keep it. Add Mullvad alongside.
Move Thorium behind an explicit opt-in.
## Mesh stack — three-layer warm-stack
Day 1 ships layers 1 (Tailscale) and 2 (Yggdrasil idle). Layer 3
(Reticulum) is opt-in via `ujust`.
### Layer 1 — Tailscale + Headscale (daily driver)
- Already running on `nullstone`, `hs.s8n.ru`. OIDC via Authentik.
- Veilor OS ships `tailscale-1.94.2+` from official Fedora repo.
- Service unit **pre-disabled** at install time.
- First-boot prompt: "join Veilor mesh? [paste / QR]". On accept:
`tailscale up --login-server=https://hs.s8n.ru` with the user's
pre-auth key.
### Layer 2 — Yggdrasil-go (warm fallback, idle by default)
- `yggdrasil-go` 0.5.13+ from COPR / dnf.
- Decentralized IPv6 in `200::/7`.
- systemd unit **enabled** but config = empty `Listen[]`, one
`Public peer` (e.g. `vpn.itrus.su` or another EU peer),
`AllowedPublicKeys` allowlist mode (no allow-all).
- WSS:443 transport for ISP DPI evasion.
- Generates ECC keypair on first boot via systemd-tmpfiles or
firstboot script.
- Survives ISP-level Tailscale block (threat floor (ii)).
### Layer 3 — Reticulum (opt-in)
- **RetiNet AGPL fork** (NOT upstream RNS — upstream has an anti-AI
license clause incompatible with our governance). Sourced from the
Codeberg AGPL fork.
- Sideband (Android/desktop messenger built on RNS).
- Install via `ujust install-reticulum`. NOT auto-started until
RetiNet stabilizes.
- Default config when enabled: `AutoInterface` (LAN multicast) +
12 TCP backbone peers.
- RNode hardware (LoRa transceiver) bundle as separate
`ujust install-reticulum-rnode`.
- Survives total internet outage (threat floor (iii)) when paired
with RNode.
## Onboarding model
Token-based (paste OR QR, user picks). Misskey signup page mints a
**reusable pre-auth key** (TTL=24h, single-use, regenerated per
signup). First boot of Veilor ISO accepts hex paste OR QR scan of
the same key.
**NOT auto-OIDC at first boot** — too much Authentik exposure for
day-zero users.
## Tier model — three-tier
- `tag:admin` — onyx + failsafe. Full mesh, `*:*`.
- `tag:infra` — nullstone, office. Mesh among themselves; admin
inbound only.
- `tag:guest` — Veilor OS users + friend. ONLY `x.veilor:443`
reachable + future seeded service hostnames whitelisted.
- **Failsafe** — pre-baked admin pre-auth key on yubikey + printed
paper + Authentik OIDC group `tailnet-admin` as second auth path.
## Threat floor table
| Floor | Attack | Day 1 (v0.7 ship) | Phase 2 (v0.8) |
|-------|--------|---|---|
| (i) | ISP blocks `s8n.ru` DNS | Tailscale dies, Yggdrasil survives | YES (documented failover) |
| (ii) | ISP blocks Tailscale protocol | Yggdrasil-WSS:443 survives | YES |
| (iii) | Internet unreachable | RNS over LoRa survives | OPT-IN (RetiNet + RNode) |
Day 1 must hold floor (i). Floors (ii) and (iii) become P2 once
Yggdrasil is promoted from idle to documented failover.
## Iroh seeding daemon (Phase 2 / v0.8)
- `veilor-seed.service` systemd unit, runs as `_veilor-seed` user.
- Watches `/var/lib/<service>/files/` blob store directories.
- BLAKE3-hashes new blobs, registers with local iroh node.
- Publishes tickets on per-service `iroh-gossip` topic.
- LRU local cache, default 10 GB.
- Sidecar mirrors service blob stores: Misskey `/files/`, Matrix
media, `dl.veilor` downloads.
- Other Veilor nodes pull lazily on cache miss.
- **DEFER DB replication forever.** Static media only.
DOCUMENT but DO NOT IMPLEMENT until **Iroh hits 1.0** (currently
0.960.98 RC season; 1.0 target Q1 2026 slipped, watching).
Reference: <https://github.com/n0-computer/iroh-blobs/blob/main/DESIGN.md>.
## External dependency — Phase 0 (NOT veilor-os scope)
Real ACL gap on nullstone Traefik right now: friend on `tag:guest`
can reach `nullstone:443` → SNI-routes to ALL Traefik vhosts
(`sys.s8n.ru`, `pihole.s8n.ru`, `hs.s8n.ru`, `auth.s8n.ru`, n8n, rc,
mx, …). Only per-vhost auth blocks them. The `no-guest@file` Traefik
middleware that should fix this is currently an `0.0.0.0/0`
allow-all stub (neutralized 2026-05-03 from XFF chain breakage).
**veilor-os does NOT fix this.** Tracked here as an external
dependency: ACL fix on nullstone Traefik **required before veilor-os
first-public-ISO ships**, otherwise `tag:guest` provisioning leaks
the full vhost surface to every veilor user. Parent operator owns it.
## Strategic credibility win ## Strategic credibility win
secureblue does NOT publish a threat model. Athena OS does, and it's secureblue does NOT publish a threat model. Athena OS does, and it's
@ -71,14 +228,16 @@ distro: **honest, scoped, public threat model**.
| v0.5.31 | shipped | Anaconda kickstart, mutable root | | v0.5.31 | shipped | Anaconda kickstart, mutable root |
| v0.5.32 | active — top blockers from 9-agent wave | Anaconda kickstart | | v0.5.32 | active — top blockers from 9-agent wave | Anaconda kickstart |
| v0.5.x → v0.6 | maintenance | Anaconda kickstart, ergonomics + UX polish | | v0.5.x → v0.6 | maintenance | Anaconda kickstart, ergonomics + UX polish |
| **v0.7 spike** | **2-day BlueBuild prototype** | First veilor OCI image extending secureblue-kinoite-hardened | | **v0.7 spike** | **1-day BlueBuild prototype** (was 2 days; `ostreecontainer` removes first-boot-rebase work) | First veilor OCI image extending secureblue-kinoite-hardened |
| v0.7 ship | ISO bootstraps install, first boot rebases to OCI | Hybrid path live | | v0.7 ship | ISO bootstraps install, `ostreecontainer` populates from OCI in-pass | Hybrid path live |
| **v1.0** | **bootc-only**, kickstart deprecated | `bootc upgrade` for all updates | | v0.8 | Iroh seeding (P2P static media), Yggdrasil promoted from idle to documented failover, RetiNet stabilization watch | bootc-only direction |
| **v1.0** | **bootc-only**, kickstart deprecated, possibly migrate `ostreecontainer` → new `bootc` kickstart command if multi-disk + auth-registry blockers resolved upstream | `bootc upgrade` for all updates |
The `bootc-image-builder` spike plan (Agent 3) is **superseded** by The Containerfile-from-scratch spike plan (Agent 3 of 2026-05-05
this hybrid: don't build a Containerfile from scratch on wave) is **superseded** by this hybrid: don't build a Containerfile
`fedora-bootc:43`. Instead, write a BlueBuild recipe on from scratch on `fedora-bootc:43`. Instead, write a BlueBuild recipe
`securecore-kinoite-hardened-userns`. Spike compresses 1 week → 2 days. on `securecore-kinoite-hardened-userns`. With `ostreecontainer`
swap, spike compresses 1 week → 1 day.
## Next concrete steps ## Next concrete steps
@ -89,39 +248,55 @@ suspend/resume wifi fix, firstboot WantedBy, USBGuard id-rules,
firewalld tailscale0 zone, KMS modeset, /etc/skel branding, virtio-9p firewalld tailscale0 zone, KMS modeset, /etc/skel branding, virtio-9p
log capture. log capture.
### v0.7-spike (2 days) `ostreecontainer` swap **does NOT land in v0.5.32 main.** It belongs
in the v0.7 spike branch only.
### v0.7-spike (1 day, separate branch)
1. New repo dir: `bluebuild/recipe.yml`. 1. New repo dir: `bluebuild/recipe.yml`.
2. `from`: `ghcr.io/secureblue/securecore-kinoite-hardened-userns:latest`. 2. `from`: `ghcr.io/secureblue/securecore-kinoite-hardened-userns:latest`.
3. Override modules: 3. Override modules:
- `type: files` — stamp our `overlay/*` tree (branding, themes, - `type: files` — stamp our `overlay/*` tree (branding, themes,
veilor scripts, sddm theme, plymouth theme). veilor scripts, sddm theme, plymouth theme).
- `type: rpm-ostree` — install Brave + restore Xwayland. - `type: rpm-ostree` — install Mullvad Browser + restore Xwayland +
- `type: rpm-ostree` — remove Trivalent. re-enable sudo (revert run0).
- **Keep Trivalent** as default (was wrongly marked for removal in
the first draft of this doc).
- `type: brand` — PRETTY_NAME, GRUB_DISTRIBUTOR, distributor URL. - `type: brand` — PRETTY_NAME, GRUB_DISTRIBUTOR, distributor URL.
- `type: files` — pre-disabled `tailscale.service`, idle
`yggdrasil.service`, `ujust install-reticulum` and
`ujust install-thorium` recipes.
4. `.github/workflows/build-bluebuild.yml` — pull BlueBuild action, 4. `.github/workflows/build-bluebuild.yml` — pull BlueBuild action,
build + cosign sign + push to GHCR. build + cosign sign + push to GHCR.
5. `kickstart/install.ks` — add a one-shot `veilor-firstboot-rebase` 5. `kickstart/install.ks` — replace `%packages` block with
service that runs `rpm-ostree rebase ghcr.io/veilor/veilor-os:43` `ostreecontainer --url=ghcr.io/veilor/veilor-os:43
then disables itself. User reboots once and is on the OCI image. --transport=registry`. Keep existing partitioning + LUKS block
verbatim. **Drop** all planned `veilor-firstboot-rebase.service`
work — no longer needed.
### v1.0 — bootc-only ### v1.0 — bootc-only
- Drop `kickstart/veilor-os.ks`, drop `livecd-creator` workflow. - Drop `kickstart/veilor-os.ks`, drop `livecd-creator` workflow.
- Keep `installer-iso.toml` for the bootstrap ISO (built via - Bootstrap ISO is built as a **separate artifact** (NOT via
bootc-image-builder); the OCI image is the source of truth. `bootc-image-builder anaconda-iso`, which was deprecated in
image-builder v44).
- The OCI image is the source of truth.
- `veilor-update` becomes thin `bootc upgrade --apply` wrapper. - `veilor-update` becomes thin `bootc upgrade --apply` wrapper.
- Migrate `ostreecontainer` directive → new `bootc` kickstart
command IF multi-disk + authenticated-registry support has landed
upstream by then.
## Open questions ## Open questions
- Does secureblue accept upstream contributions? If yes, send our - Does secureblue accept upstream contributions? If yes, send our
USBGuard id-based-rules fix and our threat model framework. USBGuard id-based-rules fix and our threat-model framework.
- Brave vs Mullvad-Browser: Brave has telemetry concerns out of box; - Recovery flow when `ostreecontainer` install pass fails — Anaconda
Mullvad-Browser is Tor-Browser-derived but not designed for should abort cleanly; verify in spike that no half-installed
daily-driver. Test both in spike. state is bootable.
- Recovery flow when bootc rebase fails on first boot — need fallback - Iroh 1.0 timing — currently 0.960.98 RC; Q1 2026 target slipped.
to keep the kickstart-installed system bootable. Likely already Re-evaluate Phase 2 schedule when 1.0 lands.
handled by bootc's A/B; verify in spike. - RetiNet upstream stabilization — track Codeberg fork for releases.
If it stalls > 6 months we re-evaluate Layer 3.
- Fedora 44 transition: secureblue tracks Fedora releases (current - Fedora 44 transition: secureblue tracks Fedora releases (current
`v4.9` on F44). If we follow, we get F44 for free at the same time `v4.9` on F44). If we follow, we get F44 for free at the same time
upstream does. upstream does.
@ -129,9 +304,13 @@ log capture.
## See also ## See also
- `docs/THREAT-MODEL.md` — drafted, needs publish for v0.7 - `docs/THREAT-MODEL.md` — drafted, needs publish for v0.7
- `docs/ROADMAP.md` to be updated to reflect this strategy - `docs/ROADMAP.md` — updated to reflect this strategy
- `docs/research/2026-05-05-agent-wave/03-bootc-spike-plan.md` - `docs/research/2026-05-05-agent-wave/03-bootc-spike-plan.md`
superseded by this hybrid (kept as reference for the superseded by this hybrid (kept as reference for the
Containerfile-from-scratch alternative) Containerfile-from-scratch alternative)
- secureblue: <https://github.com/secureblue/secureblue> - secureblue: <https://github.com/secureblue/secureblue>
- BlueBuild: <https://blue-build.org> - BlueBuild: <https://blue-build.org>
- bootc / ostreecontainer docs: <https://docs.fedoraproject.org/en-US/bootc/>
- Yggdrasil: <https://github.com/yggdrasil-network/yggdrasil-go>
- Reticulum manual: <https://reticulum.network/manual/>
- Iroh blobs design: <https://github.com/n0-computer/iroh-blobs/blob/main/DESIGN.md>