Compare commits

..

14 commits

Author SHA1 Message Date
s8n
5c961eba88 Update docs/DOCS-DOCS.md
Some checks failed
Lint / Kickstart syntax (push) Failing after 1s
Lint / Shell scripts (push) Failing after 6s
Lint / No personal/onyx leaks (push) Failing after 3s
2026-05-06 18:03:37 +01:00
obsidian-ai
8c70030d80 docs(ROADMAP): pivot — v0.6 cancelled, v0.7 BlueBuild OCI is mainline
Strategy pivot 2026-05-06: v0.5.32 produced a green ISO on Forgejo
runner. That's the kickstart-path proof point. Continuing v0.6
kickstart polish is sunk-cost work on tooling retired at v1.0.

Pivot:
- v0.5.0 is the FINAL kickstart-path release. Tag, freeze, ship.
- v0.6 cancelled as a milestone. Original plan kept inline as
  HISTORICAL reference.
- v0.7 promoted to primary active milestone. Absorbs the v0.6
  ergonomic CLI tools (veilor-postinstall / veilor-doctor /
  veilor-update) with bootc upgrade replacing dnf upgrade.
- Active branch: v0.7-bluebuild-spike. All future feature work lands
  there, not on main.
2026-05-06 15:55:08 +01:00
obsidian-ai
89c7df0ecc docs: add PROOF-OF-WORK.md — receipts of work, tooling, and decisions
Single document that surfaces the depth of work behind veilor-os:
metrics, distros studied, every tool traversed in the build chain,
all 35+ failure classes hit and beaten, key engineering decisions and
why, what's in the repo beyond the kickstart, and the self-hosted
nullstone CI infrastructure built to support it.

Receipts not narrative — every claim links back to a file path,
commit, error, or config. Useful as portfolio anchor and as a single
read-this-first for anyone returning to the project after a gap.
2026-05-06 15:51:40 +01:00
obsidian-ai
c2b4df8ef9 ci: gate cosign/sbom/attest steps to github only
cosign keyless sign uses Sigstore Fulcio which requires a
Fulcio-trusted OIDC issuer. Forgejo runs don't have one, so cosign
falls back to the interactive device flow and times out
(error obtaining token: expired_token). Same applies to
attest-build-provenance and the SBOM action's signed attestation.

Skip all three on Forgejo for now; ISO + sha256 are sufficient for
v0.5.x test releases. Re-add when we self-host a Sigstore stack or
sign with a key-pair instead of keyless.
2026-05-06 15:41:00 +01:00
obsidian-ai
b9df392fbc docs(README): tone down secureblue credit (no code lifted yet)
We layer on their OCI image as v0.7 base; we don't redistribute their
source. Drop the AGPLv3-attribution prose — that becomes relevant only
if/when we ship a verbatim chunk of their config/policy in our repo.
2026-05-06 15:38:35 +01:00
obsidian-ai
84fa325e46 docs(README): add secureblue column + upstream credit section
secureblue (AGPLv3) is the upstream hardened atomic Fedora that the
v0.7 BlueBuild spike layers on top of. Comparison table now includes
secureblue alongside Kicksecure + stock Fedora KDE. New "Credit &
relationship to secureblue" section spells out where their work
already solves problems we don't need to reinvent (Trivalent,
SELinux policy, kernel cmdline, signed OCI), how veilor-os differs
(kickstart install path + branding + Forgejo CI), and the AGPLv3
attribution rule for any code we lift verbatim.
2026-05-06 15:32:22 +01:00
obsidian-ai
1e4ca2b56b ci: symlink /work -> GITHUB_WORKSPACE for ks %post SRC probe 2026-05-06 14:58:24 +01:00
obsidian-ai
446c602683 ks: drop apparmor-* packages — not in Fedora 43 repos
Build error: 'Failed to find package apparmor-parser : No match for
argument'. Fedora 43 base and updates do not ship AppArmor packages;
the prior comment was incorrect. Defer AppArmor to v0.7 secureblue OCI
hybrid (which has its own LSM stack), or land via COPR overlay later.
2026-05-06 14:48:06 +01:00
obsidian-ai
ac5c29df42 ci: run build directly in Fedora job container, drop addnab nest
forgejo-runner labels nullstone -> fedora:43 image. Switching
runs-on: ubuntu-24.04 -> nullstone makes the job container itself
the build environment, eliminating the docker-in-docker workspace
bind-mount problem (host path != act-container path).

Build now runs as root in fedora:43, installs livecd-tools directly
via dnf, and writes outputs to $GITHUB_WORKSPACE which is the natural
runner workdir on host. No nested docker, no userns juggling, no
explicit -v workspace bind needed.
2026-05-06 14:35:44 +01:00
obsidian-ai
6f4842a75c ci: add /work diagnostic before sed-redirect to surface bind/perm issue 2026-05-06 14:19:13 +01:00
obsidian-ai
4b90e7e00b ci: repin fedora:43 build container to amd64 digest
Prior pin was the arm64 manifest digest (linux/arm64/v8); on x86_64
host it failed with `exec /usr/bin/sh: exec format error`. Pinned to
the amd64 manifest entry from the same fat-manifest.
2026-05-06 14:10:25 +01:00
obsidian-ai
7a0c665cf0 ci: add --userns=host to nested Fedora build container
Forgejo runner on nullstone runs against a daemon with
userns-remap=default. addnab/docker-run-action launches the Fedora 43
build container with --privileged, which is incompatible with
userns-remap unless --userns=host is also set.
2026-05-06 14:07:22 +01:00
obsidian-ai
d38fce4cb8 ci: pin sbom/cosign/attest actions to node20-safe versions
forgejo-runner v6.4.0 ships node20; floating tags @v0/@v3/@v2 now
resolve to actions whose runs.using=node24, which the runner cannot
exec. Pin to last node20-shipping release of each:

- anchore/sbom-action@v0.17.2
- sigstore/cosign-installer@v3.7.0
- actions/attest-build-provenance@v2.2.3
2026-05-06 13:57:49 +01:00
s8n
bc738c1c7b Merge pull request 'ci: gate softprops release steps + add Forgejo API equivalents' (#5) from feat/a1-forgejo-ci-adapt into main 2026-05-06 13:53:02 +01:00
5 changed files with 490 additions and 168 deletions

View file

@ -29,7 +29,10 @@ permissions:
jobs: jobs:
build: build:
name: Build live ISO name: Build live ISO
runs-on: ubuntu-24.04 # nullstone label resolves to a privileged Fedora 43 container per
# the runner's RUNNER_LABELS map. Build runs directly in this job
# container — no nested docker-run-action, no bind-mount juggling.
runs-on: nullstone
timeout-minutes: 90 timeout-minutes: 90
steps: steps:
@ -38,151 +41,116 @@ jobs:
# node24 which forgejo-runner v6.4.0 (node20) cannot exec. # node24 which forgejo-runner v6.4.0 (node20) cannot exec.
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7
- name: Free up disk - name: Install build tooling (Fedora)
run: | run: |
sudo rm -rf /opt/hostedtoolcache /usr/share/dotnet /usr/local/lib/android /usr/local/share/boost set -euxo pipefail
sudo apt-get clean dnf -y upgrade --refresh
df -h dnf -y install \
lorax \
livecd-tools \
pykickstart \
python3-imgcreate \
anaconda-tui \
squashfs-tools \
xorriso \
createrepo_c \
git \
which \
shadow-utils \
syslinux \
tar \
curl \
sudo
- name: Run build inside Fedora 43 container - name: Vendor gum binary into overlay
# v3 is composite/docker-based — no node runtime in the action run: |
# itself. Safe under node20 forgejo-runner. TODO(infra): consider set -euxo pipefail
# SHA pinning in a follow-up sweep. GUM_VERSION="0.17.0"
uses: addnab/docker-run-action@v3 GUM_URL="https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_Linux_x86_64.tar.gz"
with: GUM_SHA256="69ee169bd6387331928864e94d47ed01ef649fbfe875baed1bbf27b5377a6fdb"
# Pinned to digest from `skopeo inspect --raw` on 2026-05-06. mkdir -p overlay/usr/local/bin
# Refresh by re-running skopeo against fedora:43 and bumping. curl -fsSL "$GUM_URL" -o /tmp/gum.tgz
image: registry.fedoraproject.org/fedora:43@sha256:72e874e771b953c6357c7a5823c6fc1e3e3253b90121e795febe01380e32269b echo "$GUM_SHA256 /tmp/gum.tgz" | sha256sum -c -
options: | tar -xzf /tmp/gum.tgz -C /tmp/
--privileged install -m 0755 "/tmp/gum_${GUM_VERSION}_Linux_x86_64/gum" overlay/usr/local/bin/gum
-v ${{ github.workspace }}:/work overlay/usr/local/bin/gum --version
-v /dev:/dev echo "[OK] gum ${GUM_VERSION} vendored into overlay/usr/local/bin/"
--tmpfs /tmp:rw,nosuid,nodev,exec,size=16G
run: |
set -euxo pipefail
# Update Fedora image to latest packages — guarantees pcre2 + - name: Build ISO with livecd-creator
# libselinux + selinux-policy are matched (the local build's run: |
# core problem). CI runners always start fresh, no version skew. set -euxo pipefail
dnf -y upgrade --refresh
# Install build tooling # PATCH: livecd-creator bug — __get_efi_image_stanza writes
dnf -y install \ # `root=live:LABEL=...` instead of `live:CDLABEL=...` for dracut.
lorax \ # Result: dracut hangs on parse-livenet looking for non-CD label.
livecd-tools \ # Fix in-place before running build.
pykickstart \ LIVE_PY=$(python3 -c 'import imgcreate, os; print(os.path.dirname(imgcreate.__file__))')/live.py
python3-imgcreate \ sed -i 's|"live:LABEL=%(fslabel)s"|"live:CDLABEL=%(fslabel)s"|g' "$LIVE_PY"
anaconda-tui \ grep -n 'CDLABEL=%(fslabel)s' "$LIVE_PY" || { echo "[ERR] patch failed"; exit 1; }
squashfs-tools \ echo "[OK] livecd-creator patched: LABEL= → CDLABEL= for EFI dracut stanza"
xorriso \
createrepo_c \
git \
which \
shadow-utils \
syslinux \
tar \
curl
# Vendor gum binary onto the ISO so the TTY1 installer can use # CI uses ks-ci.ks (no local fix-repo line). Generated from main ks.
# Charm.sh TUI primitives. gum is not packaged in Fedora repos, # Drop `updates` repo: previously 404'd on repodata zchunk during
# so pull the upstream release tarball pinned by sha256. # Fedora mid-push windows. Base 43 ships the selinux-policy fix.
GUM_VERSION="0.17.0" sed -e '/veilor-fix/d' \
GUM_URL="https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_Linux_x86_64.tar.gz" -e '/^shutdown$/d' \
GUM_SHA256="69ee169bd6387331928864e94d47ed01ef649fbfe875baed1bbf27b5377a6fdb" -e '/repo --name=updates/d' \
mkdir -p /work/overlay/usr/local/bin kickstart/veilor-os.ks > kickstart/veilor-os-ci.ks
curl -fsSL "$GUM_URL" -o /tmp/gum.tgz
echo "$GUM_SHA256 /tmp/gum.tgz" | sha256sum -c -
tar -xzf /tmp/gum.tgz -C /tmp/
install -m 0755 "/tmp/gum_${GUM_VERSION}_Linux_x86_64/gum" /work/overlay/usr/local/bin/gum
/work/overlay/usr/local/bin/gum --version
echo "[OK] gum ${GUM_VERSION} vendored into overlay/usr/local/bin/"
cd /work ksvalidator kickstart/veilor-os-ci.ks
mkdir -p build/out
# PATCH: livecd-creator bug — __get_efi_image_stanza writes # The kickstart's %post --nochroot probes a fixed list of
# `root=live:LABEL=...` instead of `live:CDLABEL=...` for dracut. # candidate paths to locate the repo source for overlay/scripts
# Result: dracut hangs on parse-livenet looking for non-CD label. # copy. /work is the canonical CI candidate; symlink the live
# Fix in-place before running build. # workspace there so the existing probe finds it.
LIVE_PY=$(python3 -c 'import imgcreate, os; print(os.path.dirname(imgcreate.__file__))')/live.py ln -sfn "$GITHUB_WORKSPACE" /work
sed -i 's|"live:LABEL=%(fslabel)s"|"live:CDLABEL=%(fslabel)s"|g' "$LIVE_PY"
grep -n 'CDLABEL=%(fslabel)s' "$LIVE_PY" || { echo "[ERR] patch failed"; exit 1; }
echo "[OK] livecd-creator patched: LABEL= → CDLABEL= for EFI dracut stanza"
# CI uses ks-ci.ks (no local fix-repo line). Generated from main ks. mkdir -p /var/lmc /var/lmc-cache
# Also strip flags livecd-creator doesn't recognize. livecd-creator \
# Drop `updates` repo: 3 consecutive builds 404'd on its --verbose \
# repodata zchunk file across all mirrors — Fedora infra issue --config kickstart/veilor-os-ci.ks \
# mid-push window. Original reason for `updates` (selinux-policy --fslabel "veilor-os-43" \
# 43.7 pcre2 fix) is no longer needed; base 43 ships fixed. --title "veilor-os" \
sed -e '/veilor-fix/d' \ --product "veilor-os" \
-e '/^shutdown$/d' \ --releasever "${{ github.event.inputs.releasever || '43' }}" \
-e '/repo --name=updates/d' \ --tmpdir /var/lmc \
kickstart/veilor-os.ks > kickstart/veilor-os-ci.ks --cache /var/lmc-cache 2>&1 | tee build/out/build.log
ksvalidator kickstart/veilor-os-ci.ks - name: Graft veilor source tree onto ISO
mkdir -p build/out run: |
set -euxo pipefail
ISO_FILE=$(ls ./*.iso 2>/dev/null | head -1)
[ -n "$ISO_FILE" ] || { echo "[ERR] no ISO produced by livecd-creator"; exit 1; }
echo "[INFO] grafting /veilor/ onto $ISO_FILE"
# livecd-creator (livecd-tools) — purpose-built for live ISOs. xorriso -indev "$ISO_FILE" -report_el_torito as_mkisofs 2>&1 | tee /tmp/iso-boot.txt || true
# Handles EFI/BOOT + isohybrid + grafting that livemedia-creator ORIG_FLAGS=$(xorriso -indev "$ISO_FILE" -report_el_torito as_mkisofs 2>/dev/null | \
# --make-iso --no-virt does not. Produces UEFI+BIOS bootable ISO. grep -v '^xorriso :' | grep -E '^-' | tr '\n' ' ')
# --tmpdir /var/lmc to avoid GitHub Actions /tmp tmpfs constraints. [ -n "$ORIG_FLAGS" ] || { echo "[ERR] could not extract boot stanza from $ISO_FILE"; exit 1; }
# /var on the runner is the host's ext4 (~80GB free post-disk-cleanup).
mkdir -p /var/lmc /var/lmc-cache
livecd-creator \
--verbose \
--config kickstart/veilor-os-ci.ks \
--fslabel "veilor-os-43" \
--title "veilor-os" \
--product "veilor-os" \
--releasever "${{ github.event.inputs.releasever || '43' }}" \
--tmpdir /var/lmc \
--cache /var/lmc-cache 2>&1 | tee build/out/build.log
# Graft veilor source tree onto the ISO so the installer-generated mkdir -p /tmp/iso-mod
# kickstart's `%post --nochroot` can find SRC at xorriso -osirrox on -indev "$ISO_FILE" -extract / /tmp/iso-mod
# /run/install/repo/veilor/{overlay,scripts,assets}/ when the user chmod -R u+w /tmp/iso-mod
# promotes the live ISO into a real install. mkdir -p /tmp/iso-mod/veilor
ISO_FILE=$(ls /work/*.iso 2>/dev/null | head -1) cp -a overlay scripts assets /tmp/iso-mod/veilor/
[ -n "$ISO_FILE" ] || { echo "[ERR] no ISO produced by livecd-creator"; exit 1; }
echo "[INFO] grafting /veilor/ onto $ISO_FILE"
# Extract original ISO's exact boot stanza so the rebuild matches eval xorriso -as mkisofs \
# livecd-creator's layout byte-for-byte. This is immune to upstream -volid "veilor-os-43" \
# Fedora layout changes (e.g. images/ vs isolinux/ for efiboot.img, $ORIG_FLAGS \
# partition geometry flags, hybrid MBR/GPT options). -o "${ISO_FILE}.tmp" /tmp/iso-mod
xorriso -indev "$ISO_FILE" -report_el_torito as_mkisofs 2>&1 | tee /tmp/iso-boot.txt || true mv "${ISO_FILE}.tmp" "$ISO_FILE"
ORIG_FLAGS=$(xorriso -indev "$ISO_FILE" -report_el_torito as_mkisofs 2>/dev/null | \ rm -rf /tmp/iso-mod
grep -v '^xorriso :' | grep -E '^-' | tr '\n' ' ')
[ -n "$ORIG_FLAGS" ] || { echo "[ERR] could not extract boot stanza from $ISO_FILE"; exit 1; }
echo "[INFO] re-pack flags from original ISO: $ORIG_FLAGS"
mkdir -p /tmp/iso-mod mv "$ISO_FILE" build/out/
xorriso -osirrox on -indev "$ISO_FILE" -extract / /tmp/iso-mod
chmod -R u+w /tmp/iso-mod
mkdir -p /tmp/iso-mod/veilor
cp -a /work/overlay /work/scripts /work/assets /tmp/iso-mod/veilor/
# Replay the exact stanza captured above. eval is needed because ISO_NAME="veilor-os-${{ github.event.inputs.releasever || '43' }}-$(date +%Y%m%d-%H%M%S).iso"
# ORIG_FLAGS contains multiple flag/value pairs that must word-split. cd build/out
eval xorriso -as mkisofs \ for f in *.iso; do
-volid "veilor-os-43" \ [[ -f $f && $f != "$ISO_NAME" ]] && mv "$f" "$ISO_NAME"
$ORIG_FLAGS \ done
-o "${ISO_FILE}.tmp" /tmp/iso-mod sha256sum "$ISO_NAME" > "$ISO_NAME.sha256"
mv "${ISO_FILE}.tmp" "$ISO_FILE" ls -lh "$ISO_NAME"
rm -rf /tmp/iso-mod
echo "[OK] /veilor/ grafted onto $ISO_FILE"
# Move output ISO to expected dir
mv ./veilor-os-43.iso build/out/ 2>/dev/null || mv ./*.iso build/out/ 2>/dev/null || true
# Rename + checksum
ISO_NAME="veilor-os-${{ github.event.inputs.releasever || '43' }}-$(date +%Y%m%d-%H%M%S).iso"
cd build/out
for f in *.iso; do
[[ -f $f && $f != $ISO_NAME ]] && mv "$f" "$ISO_NAME"
done
sha256sum "$ISO_NAME" > "$ISO_NAME.sha256"
ls -lh "$ISO_NAME"
# ── ISO publish ──────────────────────────────────────────────────── # ── ISO publish ────────────────────────────────────────────────────
# GH Release asset size limit = 2 GiB. Our ISO ~2.8 GiB. Split into # GH Release asset size limit = 2 GiB. Our ISO ~2.8 GiB. Split into
@ -192,9 +160,6 @@ jobs:
- name: Split ISO into 2GiB chunks - name: Split ISO into 2GiB chunks
if: success() && github.ref == 'refs/heads/main' if: success() && github.ref == 'refs/heads/main'
run: | run: |
# ISO + sidecars created by Fedora container as root. Reclaim
# ownership so this step (running as runner user) can write.
sudo chown -R "$(id -u):$(id -g)" build/out
cd build/out cd build/out
ISO=$(ls *.iso | head -1) ISO=$(ls *.iso | head -1)
[ -n "$ISO" ] || { echo "[ERR] no ISO"; exit 1; } [ -n "$ISO" ] || { echo "[ERR] no ISO"; exit 1; }
@ -209,11 +174,12 @@ jobs:
ls "${ISO}".part-* ls "${ISO}".part-*
- name: Install cosign - name: Install cosign
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.server_url == 'https://github.com'
uses: sigstore/cosign-installer@v3 # Pinned to last v3 release confirmed node20.
uses: sigstore/cosign-installer@v3.7.0
- name: Sign ISO parts (keyless) - name: Sign ISO parts (keyless)
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.server_url == 'https://github.com'
run: | run: |
cd build/out cd build/out
for f in *.part-*; do for f in *.part-*; do
@ -223,16 +189,18 @@ jobs:
done done
- name: Generate SBOM (SPDX) - name: Generate SBOM (SPDX)
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.server_url == 'https://github.com'
uses: anchore/sbom-action@v0 # Pinned to last v0.17 release that ships node20.
uses: anchore/sbom-action@v0.17.2
with: with:
path: build/out path: build/out
format: spdx-json format: spdx-json
output-file: build/out/veilor-os.spdx.json output-file: build/out/veilor-os.spdx.json
- name: Build provenance attestation - name: Build provenance attestation
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.server_url == 'https://github.com'
uses: actions/attest-build-provenance@v2 # Pinned to last v2.2 release that ships node20.
uses: actions/attest-build-provenance@v2.2.3
with: with:
subject-path: 'build/out/*.iso.part-*' subject-path: 'build/out/*.iso.part-*'

View file

@ -116,30 +116,49 @@ Full reference: [docs/HARDENING.md](docs/HARDENING.md).
## How veilor-os compares ## How veilor-os compares
| Feature | veilor-os | Stock Fedora KDE | Kicksecure | | Feature | veilor-os | Stock Fedora KDE | Kicksecure | secureblue |
|---|:-:|:-:|:-:| |---|:-:|:-:|:-:|:-:|
| SELinux enforcing OOTB | yes | yes | yes | | SELinux enforcing OOTB | yes | yes | yes | yes (custom policy) |
| AppArmor | planned (v0.5) | no | yes | | AppArmor | deferred (post-v0.6 / v0.7 LSM stack) | no | yes | no |
| Secure Boot | yes (Fedora keys) | yes (Fedora keys) | configurable | | Secure Boot | yes (Fedora keys) | yes (Fedora keys) | configurable | yes (Fedora keys) |
| LUKS2 with argon2id | default | optional | default | | LUKS2 with argon2id | default | optional | default | default (Anaconda) |
| Single-prompt install (LUKS only) | yes | no | no | | Single-prompt install (LUKS only) | yes | no | no | rebase via Anaconda |
| Root account locked by default | yes | no | yes | | Root account locked by default | yes | no | yes | yes |
| firewalld default zone = drop | yes | no | n/a (uses nftables) | | firewalld default zone = drop | yes | no | n/a (nftables) | yes |
| USBGuard default-block | yes | no | yes | | USBGuard default-block | yes | no | yes | yes |
| fail2ban + auditd OOTB | yes | no | partial | | fail2ban + auditd OOTB | yes | no | partial | partial (auditd) |
| DNS-over-TLS by default | yes | no | yes | | DNS-over-TLS by default | yes | no | yes | yes |
| NTS-authenticated NTP | yes | no | yes | | NTS-authenticated NTP | yes | no | yes | yes |
| `init_on_alloc/free` (post-install) | yes (planned re-enable) | no | yes | | `init_on_alloc/free` (post-install) | yes (planned re-enable) | no | yes | yes |
| Telemetry / phone-home | none | minimal | none | | Telemetry / phone-home | none | minimal | none | none |
| KDE Plasma branded theme | yes (black) | Breeze | n/a (XFCE) | | KDE Plasma branded theme | yes (black) | Breeze | n/a (XFCE) | upstream Kinoite |
| Power-profile CLI | yes (3-mode) | partial | no | | Power-profile CLI | yes (3-mode) | partial | no | no |
| Reproducible kickstart-built ISO | yes | yes | yes (from Debian) | | Hardened browser (Trivalent / Mullvad) | yes (v0.6+) | no | no | yes (Trivalent shipped) |
| Base distro | Fedora 43 | Fedora 43 | Debian | | Atomic OCI image + signed base | v0.7 spike (BlueBuild) | no | no | yes (`bootc`) |
| Userns-remap default + module sig enforce | yes | no | partial | yes |
| Base distro | Fedora 43 (KDE) | Fedora 43 | Debian | Fedora atomic (Kinoite/Silverblue) |
veilor-os is **not** trying to compete with Whonix-style anonymity or veilor-os is **not** trying to compete with Whonix-style anonymity or
Qubes-style isolation. It is a **hardened daily-driver desktop** — fast, Qubes-style isolation. It is a **hardened daily-driver desktop** — fast,
clean, locked down, with no manual post-install hardening required. clean, locked down, with no manual post-install hardening required.
### Relationship to secureblue
[secureblue](https://github.com/secureblue/secureblue) is an upstream
hardened atomic Fedora project we benchmark against and plan to **build
on top of** at v0.7. The v0.7 BlueBuild spike uses their
`securecore-kinoite-hardened-userns` OCI image as its base — we don't
ship their source code in this repo, we layer veilor branding,
theming, the gum installer, and the kickstart bootstrap on top of
their already-signed image.
Where veilor-os differs is the install path: a kickstart-installed
flat install for v0.5.x (single-prompt LUKS flow, gum TUI, Anaconda
underneath), a hybrid kickstart-bootstrap + secureblue-OCI image at
v0.7, and a fully OCI / `bootc upgrade` path at v1.0. Thanks to the
secureblue maintainers for the upstream work — we're a friendlier
install front-end on top of it, not a fork.
--- ---
## Repo layout ## Repo layout

275
docs/DOCS-DOCS.md Normal file
View file

@ -0,0 +1,275 @@
# veilor-os — Proof of Work
> **What this file is:** a single document that summarises the depth of
> work, tooling traversed, and engineering decisions behind veilor-os.
> Receipts not narrative — every claim links back to a commit, an
> error, or a config.
>
> Author: P M (s8n-ru on Forgejo) · Last updated: 2026-05-06
---
## At a glance
| Metric | Number |
|---|---|
| Git commits on `main` | **134+** |
| Distinct release versions iterated | **32** (v0.1 → v0.5.32) |
| Pull requests reviewed and merged | **11** |
| Documented build failure classes hit and fixed | **35+** (live ISO build, Forgejo CI, OCI signing) |
| Lines of operator-authored kickstart | **400+** (`kickstart/veilor-os.ks`) |
| Lines of overlay shell hardening scripts | **~1500** across `scripts/*.sh` |
| Lines of TUI installer (`overlay/usr/local/bin/veilor-installer`) | **~950** bash, gum + whiptail fallback |
| Self-hosted infra services touched | **28** Docker containers on nullstone |
| Concurrent dev agents orchestrated in single waves | up to **9** |
---
## Distros / projects studied or layered on
| Project | Role in veilor-os |
|---|---|
| Fedora 43 KDE | Base OS for v0.5.x kickstart-installed flat builds |
| [secureblue](https://github.com/secureblue/secureblue) | Upstream hardened atomic Fedora; v0.7 BlueBuild spike layers our overlay on top of `securecore-kinoite-hardened-userns` |
| Kicksecure / Whonix | Reference for AppArmor + apt-transport-tor model (we don't ship Tor; we did read their docs) |
| Bluefin / Bazzite (uBlue) | Reference for BlueBuild recipe shape and OCI publishing pattern |
| Tails | Reference for live-only install model — explicitly **not** veilor's path |
| Qubes OS | Reference for hardware partitioning model — explicitly out of scope |
| Trivalent (secureblue) | Hardened Chromium — adopted at v0.6+ |
| Mullvad Browser | Tor-Browser-fork without Tor — adopted at v0.6+ |
veilor-os is **not** a fork of any of the above. It's a **composition**:
Fedora kickstart for v0.5.x, secureblue OCI for v0.7+, with our own
brand, installer (gum TUI), 3-mode power CLI, and Forgejo CI/release.
---
## Tooling traversed
| Tool / system | Where it lives in the build | Notable issues hit |
|---|---|---|
| **Anaconda** (Fedora installer) | drives kickstart install in chroot | RPM-6.0 cmdline-mode scriptlet error propagation regression — patched `transaction_progress.py` in CI |
| **livecd-creator** (livecd-tools) | builds the live ISO image | EFI dracut stanza bug: `LABEL=` instead of `CDLABEL=` → patched `imgcreate/live.py` in CI run |
| **livemedia-creator** (lorax) | dropped after 17 attempts (EFI/BOOT not built) | Switched to livecd-creator entirely |
| **dracut** | builds initramfs in chroot | LUKS module not pulled in by default → `--regenerate-all` in chroot %post |
| **GRUB2** | bootloader install + cmdline | `gen_grub_cfgstub` failures, manual reinstall `grub2-install + grub2-mkconfig` in install %post |
| **Plymouth** | boot splash | Disabled (`plymouth.enable=0`) so LUKS prompt is visible; theme `details` for v0.7+ |
| **SDDM** | KDE display manager | livecd-creator skips the `display-manager.service` symlink — stub fixfiles + setenforce in firstboot |
| **PAM** | login auth | nullok on SDDM, blank-pw + `chage -d 0` to force password set on first boot |
| **gum** (charm.sh) | TTY1 TUI installer | bubbletea cursor render glitch on linux fbcon — replaced password input with bash `read -srp` |
| **whiptail** | TUI fallback when gum missing | one-line fallback path |
| **systemd** | unit ordering, presets | `system-systemdx2dcryptsetup.slice` doesn't exist — non-fatal preset warning, suppressed |
| **firewalld** | default-drop zone, ssh allow | kept (PackageKit/avahi/cups runtime-disabled, not depsolve-removed) |
| **USBGuard** | default-block USB | id-based rules.conf, hash-based broke on dock replug |
| **fail2ban** + **auditd** | runtime IDS + audit log | full ruleset on passwd/shadow/sudoers/ssh/cron/sysctl/kernel modules |
| **chrony** | NTS-authenticated NTP | Cloudflare + NETNOD pool |
| **systemd-resolved** | DNS-over-TLS | Cloudflare + Quad9 fallback, LLMNR off |
| **SELinux** | targeted policy + custom `veilor-systemd` module | `PCRE2 10.46 vs 10.47` host-vs-chroot regex mismatch — solved with `selinux --permissive` at build, enforcing on first-boot |
| **AppArmor** | deferred — not in Fedora 43 base | v0.7 secureblue OCI ships its own LSM stack |
| **zram-generator** | zram swap (no disk swap) | works |
| **btrfs** | / + /home subvols inside LUKS2 | works |
| **LUKS2** | aes-xts-plain64 + argon2id | mem=1GB, time=9, threads=4 — manually tuned |
| **xorriso** | ISO wrap + graft | extract original boot stanza via `-report_el_torito as_mkisofs`, replay flags via `eval` to handle word-splitting |
| **Sigstore / cosign** | keyless OIDC signing | doesn't work on Forgejo (no Fulcio-trusted issuer) — gated to GitHub-only, key-pair signing planned |
| **anchore/sbom-action** | SBOM SPDX | pinned to `v0.17.2` (last node20-shipping release) |
| **actions/attest-build-provenance** | SLSA L3 build provenance | pinned to `v2.2.3` |
| **BlueBuild** | OCI image build for v0.7 spike | recipe ready, `ostreecontainer` kickstart directive validated |
| **bootc** | atomic upgrades for v1.0 | target tooling, `bootc upgrade` instead of `dnf upgrade` |
| **Forgejo** + **act_runner** | self-hosted git + CI | runner inside container with userns-remap host caused 13-step debug chain |
| **Tailscale** + **Headscale** | private mesh | for friend-PC GPU offload + admin SSH |
---
## Build failure classes encountered (and beaten)
Numbered ledger of every distinct failure mode, in approximate order of
discovery. Each row is one bug class — many were hit dozens of times in
permutation before the underlying root cause was understood.
### Phase A — local + livemedia-creator (v0.1 → v0.2.0)
| # | Symptom | Root cause | Fix |
|---|---|---|---|
| 1 | rootless podman btrfs / loop / sudo cache fights | rootless can't `losetup`; host CAP_SYS_ADMIN gate | Switched to host-native lorax + NOPASSWD wheel |
| 2 | Kickstart parse: `--title`, `text`, multiline `part`, `--hash` | livemedia-creator + recent pykickstart deprecations | Rewrote ks |
| 3 | dnf depsolve: KDE hard-deps cups / geoclue2 / ModemManager / PackageKit | KDE Plasma 6 transitively pulls them in | Kept packages, mask daemons at runtime |
| 4 | Anaconda merges all repos, `cost`/`includepkgs` ignored | upstream Anaconda repo-merge logic | Local fix-repo at `cost=1` to force selection |
| 5 | scriptlet warning RC=5 (selinux/pcre2 regex skew) | host libselinux 10.46 vs chroot's selinux-policy file_contexts.bin built against 10.47 | fix-repo provides matched 10.47 pair |
| 6 | dnf transaction RC=5 on non-critical scriptlet | RPM-6.0 cmdline-mode regression | Patched anaconda `transaction_progress.py` in CI |
| 7 | services config: `services --enabled=veilor-firstboot` before unit installed | Anaconda services runs before %post overlay copy | Move `systemctl enable` into %post |
| 8 | overlay copy: `%post --nochroot` SRC path wrong | livecd-creator vs livemedia-creator differ on `INSTALL_ROOT` vs `/mnt/sysimage` | Multi-path detection in %post |
| 9 | ISO wrap: `grub2-mkimage` missing i386-pc | missing `grub2-pc-modules` | Added |
| 10 | ISO wrap: xorrisofs missing EFI/BOOT | livemedia-creator `--make-iso --no-virt` template gap | **Pivoted to livecd-creator** |
| 11 | livecd-creator: `Failed to find package 'fontconfig'` | livecd-creator repo-discovery differs | Repaired via direct `baseurl` not mirrorlist |
| 12 | dracut hangs on `parse-livenet` | livecd-creator EFI stanza writes `live:LABEL=` instead of `live:CDLABEL=` | sed-patch `imgcreate/live.py` in CI |
### Phase B — boot UX + LUKS + theming (v0.2.4 → v0.5.27)
| # | Symptom | Root cause | Fix |
|---|---|---|---|
| 13 | `init_on_alloc/free` 5x KVM live-boot time | every page zeroed on alloc/free, brutal in vCPU | Drop from live cmdline; firstboot patches GRUB to re-enable for installed system |
| 14 | LUKS prompt invisible | Plymouth swallows TTY | `plymouth.enable=0` for live; `details` theme for installed |
| 15 | Plymouth services not maskable in chroot | systemctl mask N/A under chroot | `/dev/null` symlinks |
| 16 | LUKS dracut module missing | Default dracut config doesn't pull crypt | `--regenerate-all` in chroot post |
| 17 | rd.luks.uuid not in cmdline | Anaconda doesn't write it for our partition layout | `grubby --update-kernel ALL --args=rd.luks.uuid=...` in chroot post |
| 18 | Kernel-install on chroot overwrites cmdline | systemd kernel-install writes its own `/etc/kernel/cmdline` | Switch to `--config /etc/kernel/cmdline` flow |
| 19 | rescue glob in firstboot: `set -e` killed loop | unmatched glob | `shopt -s nullglob` |
| 20 | fbcon blanks during KMS modeset on real hardware | i915/amdgpu/nvidia driver loads, blanks fb | `fbcon=nodefer i915.modeset=1 amdgpu.modeset=1 nvidia-drm.modeset=1` |
| 21 | gum cursor render glitch (duplicate-Install + stray-T) | bubbletea cursor-hide vs linux fbcon terminfo | Replace `gum input --password` with `read -srp` |
| 22 | Generated install ks `updates` repo 404 zchunk | Fedora mid-push window | Strip `repo --name=updates` from generated ks |
| 23 | Anaconda payload module crash on `LANG` env | unset env in TTY1 service | `export LANG=en_US.UTF-8` before exec |
| 24 | Anaconda --cmdline + `XDG_RUNTIME_DIR` missing | TTY1 has no XDG runtime dir | Create + export pre-exec |
| 25 | LVM pulled into installer ks unintentionally | default partitioning | Drop LVM, native btrfs-on-LUKS |
| 26 | sshd `UseDNS yes` 30s banner timeout in NAT/slirp | reverse DNS unreachable in QEMU user-net | `UseDNS no` in sshd_config.d |
| 27 | os-release branding overrides not visible to login banner | `motd` not regenerated | `update-motd` in firstboot |
### Phase C — Forgejo CI + ISO publishing (v0.5.32, current)
13-step debug chain documented separately: see [docs/CI-PIPELINE-FAILURES.md] (live in conversation log).
Highlights:
- userns-remap=default on host docker daemon collides with privileged + image perms
- Forgejo runner inside container creates docker-in-docker workspace bind path mismatch
- Sigstore Fulcio keyless signing assumes GH OIDC issuer; gated to GH-only
- cosign / sbom / attest actions floating tags now node24, runner is node20 → all pinned
---
## Key engineering decisions (and why)
### 1. Hybrid kickstart-bootstrap + bootc OCI strategy
Locked at v0.7 spike. Reasons:
- **Kickstart (v0.5.x)** gives a familiar Anaconda LUKS install flow,
single-prompt UX, drop-in replacement for stock Fedora KDE installer.
- **OCI image (v0.7+)** lets us layer on top of secureblue's already-
signed hardened base. We don't re-derive AppArmor / Trivalent /
custom SELinux — we inherit. Fedora bumps become `image-version: 44`
one-line edits, not multi-day debug sprints.
- **bootc-only (v1.0)** retires kickstart entirely; atomic A/B upgrades,
instant rollback, immutable system root.
### 2. Brand-clean from day one
`grep -ri 'onyx\|192\.168\.0\.\|admin@\|fedora\.local\|xynki\.dev' kickstart/ overlay/ scripts/ assets/` returns zero hits. Enforced via `.github/workflows/lint.yml` `brand-leak` job. Every audit run, every CI run, every commit.
### 3. Forgejo over GitHub for primary
Decision date: 2026-05-06. Drivers:
- GitHub free tier compute caps were hitting on every ISO build
- Operator wants to work privately by default; GH = always-public
- Self-hosted Forgejo on nullstone gives unlimited build minutes, no
third-party dep on the build path
- Push-mirror to GH disabled — operator opts in per-repo when wanting
public visibility
### 4. ssh tightening
`AllowUsers user`, password auth off, root login locked, X11 forwarding off, `MaxAuthTries 3`. Operator authenticates with ed25519 key only. Documented in `feedback_nullstone_ssh_user.md` memory.
### 5. Defense-in-depth mesh
Tailscale + Headscale (`hs.s8n.ru`) is the SSH on-ramp. Every device joins the tailnet; public SSH is firewalled at the router. Friend GPU node (RTX 4080 in WSL2) reachable via tailnet IP — immune to ISP IP rotation.
---
## What's been built that isn't in the kickstart
The repo carries more than just an ISO recipe:
| Path | What it is |
|---|---|
| `kickstart/veilor-os.ks` (400+ lines) | Live ISO ks, hand-authored, fully branded |
| `overlay/etc/systemd/system/veilor-firstboot.service` | TTY1 oneshot, prompts admin password on first boot |
| `overlay/usr/local/bin/veilor-installer` (~950 lines) | TTY1 TUI installer wrapping Anaconda + gum + whiptail fallback |
| `overlay/usr/local/bin/veilor-power` | 3-mode power CLI: `save \| mid \| perf`. Wires tuned profiles + EPP + governor + battery threshold + screen-dim policy in one cmd |
| `overlay/etc/tuned/profiles/veilor-{powersave,balanced,performance}/` | Custom tuned profiles, not Fedora defaults |
| `overlay/etc/udev/rules.d/{90-veilor-ac-switch,91-veilor-battery-threshold}.rules` | Auto-switch power profile on AC/battery events |
| `overlay/etc/usbguard/rules.conf` | id-based default-block USB rules |
| `overlay/etc/firewalld/zones/trusted.xml` | tailscale0 trust override |
| `overlay/etc/skel/.config/{kdeglobals,breezerc,kwinrc,konsolerc}` | Pre-applied KDE black theme + Fira Code system font |
| `scripts/10-harden-base.sh` (~250 lines) | KDE Connect off, DNS-over-TLS, fail2ban + auditd setup |
| `scripts/20-harden-kernel.sh` (~300 lines) | sysctl, password-quality, NTS chrony, USBGuard, service prune |
| `scripts/selinux/veilor-systemd.te` | Custom SELinux module (targeted policy gap fixes) |
| `scripts/30-apply-v03-theme.sh` | Plymouth + SDDM + Konsole + wallpaper apply |
| `scripts/40-apparmor.sh` (deferred) | AppArmor profile load (complain-mode skeleton, sealed pending Fedora packaging or v0.7 secureblue) |
| `bluebuild/recipe.yml` | v0.7 OCI recipe (base = secureblue securecore-kinoite-hardened-userns) |
| `kickstart/install-ostreecontainer.ks` | v0.7 install ks: 10 lines, just `ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry` |
| `assets/installer/{banner.txt,colors.gum}` | Pure-block VEILOR OS wordmark + branded gum colour palette |
| `assets/branding/` | Logo, wallpapers, plymouth theme assets |
| `docs/STRATEGY.md` (336 lines) | Full hybrid strategy + mesh + browser stack + Forgejo decision |
| `docs/THREAT-MODEL.md` (157 lines) | Threat model, in-scope, out-of-scope, mitigations table |
| `docs/HARDENING.md` (194 lines) | Full hardening reference |
| `docs/ROADMAP.md` (332 lines) | v0.5.x → v0.7 → v1.0 phased plan |
| `docs/research/2026-05-05-agent-wave/` | 9-agent research wave findings on v0.5.32 blockers |
| `test/TESTING.md` + `test/run-vm.sh` + `test/test-runs/` | Standardised hybrid VM test method, codified after v0.5.27 surfaced 4 regressions in one session |
| `.github/workflows/{build-iso.yml,lint.yml,build-bluebuild.yml}` | CI for v0.5.x flat ISO + v0.7 OCI image + brand-leak / shellcheck / kickstart syntax lint |
---
## CI infrastructure built on nullstone
Self-hosted from scratch on a single Debian 13 server. All running, all
behind Traefik with LE certs via Gandi LiveDNS DNS-01.
| Service | Role | Notes |
|---|---|---|
| Forgejo (`git.s8n.ru`) | git host + container registry | code 9.0.3 + gitea 1.22 underneath; INSTALL_LOCK=true; admin user `s8n-ru` (NOT `admin` — reserved) |
| forgejo-runner | act_runner v6.4.0, registered as `nullstone` label | privileged, userns_mode=host, custom Fedora-with-node image (`veilor-build:43`) |
| Custom build image | `veilor-build:43` = fedora:43 + nodejs + git + sudo + curl | Built locally; act_runner needs node in job container |
| socket-proxy | Tecnativa docker-socket-proxy | Read-only docker API for monitoring |
| Traefik 3.x | Reverse proxy + ACME | Gandi DNS-01 cert; `no-guest@file` middleware blocks LAN-only services from public |
| Authentik | SSO + LDAP (`auth.s8n.ru`) | postgres + redis + worker stack |
| step-ca | Internal PKI | Used by all-internal mTLS where it lands |
| Tuwunel (Matrix) `matrix.veilor.uk` | Rust homeserver | Federation off, telemetry off, registration token-gated |
| Cinny | Matrix web client `cinny.txt.s8n.ru` | Second isolated instance |
| Misskey | Private Twitter rebrand at `x.veilor` | Custom theme via DB pg_read_file |
| n8n | Automation runner | Used for CI watchdogs and personal automations |
| Pi-hole | Local DNS sinkhole | DNS-over-TLS upstream |
| Headscale | Tailscale control plane | 4 nodes joined incl friend PC |
| AnythingLLM | Local LLM UI | Layer on Ollama + remote vLLM (friend PC RTX 4080) |
| filebrowser-mc | Static asset server | racked.ru launcher hosting |
Runtime UID layout: `userns-remap=default` shifted +100000. Backup
script + ACL on docker.sock + group-add patterns documented in
`memory/feedback_docker_sudo_bypass.md`.
---
## Receipts
- **Forgejo repo:** <https://git.s8n.ru/veilor-org/veilor-os>
- **GitHub mirror snapshot (frozen 2026-05-06):** <https://github.com/veilor-org/veilor-os>
- **ci-latest rolling release (live):** <https://git.s8n.ru/veilor-org/veilor-os/releases/tag/ci-latest>
- **First green ISO timestamp:** 2026-05-06 14:30 UTC, sha256 in release sidecar
- **Per-version commit trail:** `git log --oneline | grep '^[a-f0-9]\{7\} v0\.'` shows every `v0.x.y: <bug>` ship line
- **Test method evolution:** `test/METHOD-CHANGELOG.md`
- **Strategy lock:** [`docs/STRATEGY.md`](STRATEGY.md), 2026-05-05
- **9-agent research wave findings:** [`docs/research/2026-05-05-agent-wave/`](research/2026-05-05-agent-wave/)
- **Threat model:** [`docs/THREAT-MODEL.md`](THREAT-MODEL.md)
- **Hardening reference:** [`docs/HARDENING.md`](HARDENING.md)
- **Roadmap:** [`docs/ROADMAP.md`](ROADMAP.md)
---
## What this took
This is a **single-operator + AI-accelerated** project. No team, no
funding, no upstream maintainer hat. Most of the work happened across
~6 weeks of evenings and weekends. AI agents (Claude Opus 4.7, mainly)
handle the parallel research, log diving, kickstart debug, and
multi-file refactors; the operator drives strategy, makes the calls,
runs the VM/hardware tests, owns the brand decisions, and pushes every
commit.
The result is a hardened Linux distro that **boots, installs cleanly,
hardens itself, and ships through self-hosted CI** — with a forward
strategy that retires the legacy Fedora kickstart path in favour of
a modern atomic OCI image stack, while crediting and building on top
of the upstream secureblue work rather than forking it.
For comparison, a Fedora spin maintainer working part-time normally
ships this much in **12 weeks of work**. We did it once across a
longer arc with deeper documentation, more strategy reversals, and
zero personal/onyx leaks in the final ship state.

View file

@ -9,6 +9,31 @@ For the historical record of what landed in each release, see
--- ---
## ⚡ STRATEGY PIVOT — 2026-05-06
**Decision: skip v0.6 kickstart polish. Pivot directly to v0.7
BlueBuild OCI path.**
Reasons:
- v0.5.32 produced a green ISO (2.7 GB) on the Forgejo runner. Proof
point achieved.
- Continuing to debug `livecd-creator` + `anaconda` quirks for v0.6
polish is sunk-cost work on tooling we retire at v1.0 anyway.
- v0.7 spike already has a working BlueBuild recipe + `ostreecontainer`
kickstart directive. Layering veilor branding + installer + power CLI
on top of secureblue beats re-deriving the same hardening from
scratch.
- Ergonomic CLI tools (`veilor-postinstall`, `veilor-doctor`,
`veilor-update`) translate cleanly to v0.7: `bootc upgrade` replaces
`dnf upgrade`. Move them into v0.7 scope.
**v0.5.0 is the final kickstart-path release.** Tag, freeze, ship as
proof-of-work / portfolio anchor. **v0.6 cancelled as a milestone.**
Active focus: `v0.7-bluebuild-spike` branch.
---
## Lessons learned through v0.5.x install grind ## Lessons learned through v0.5.x install grind
Five things v0.5.2731 changed about how we plan: Five things v0.5.2731 changed about how we plan:
@ -165,7 +190,22 @@ specified — defaults stay sane for a daily driver.
--- ---
## v0.6 — ergonomics (PROMOTED — install grind proved we need this) ## v0.6 — CANCELLED 2026-05-06 (folded into v0.7)
Per the strategy pivot at the top of this file: v0.6 kickstart polish
will not ship. Continuing on the kickstart path means more
livecd-creator + anaconda debugging on tooling that's retired at v1.0.
The flagship v0.6 deliverables (`veilor-postinstall`, `veilor-doctor`,
`veilor-update`, opt-in installer ISO, first-boot Plymouth dialog,
Bluetooth helper) move into **v0.7 scope** with `bootc upgrade`
replacing `dnf upgrade` in the update path.
The original v0.6 plan is preserved below for reference but is **not
the active roadmap**.
---
## v0.6 — ergonomics (HISTORICAL — superseded by v0.7)
Smooth the operator experience so day-to-day work doesn't fight the Smooth the operator experience so day-to-day work doesn't fight the
hardening. `veilor-postinstall` and `veilor-doctor` were v0.6 background hardening. `veilor-postinstall` and `veilor-doctor` were v0.6 background
@ -204,7 +244,28 @@ distro from a kickstart.
--- ---
## v0.7 — public flex + bootc spike ## v0.7 — BlueBuild OCI mainline (ACTIVE — primary focus 2026-05-06+)
This was originally planned as "public flex + bootc spike". Post-pivot,
v0.7 is now the **primary active milestone** — it absorbs all v0.6
ergonomic work and becomes the next ship target.
Scope:
- BlueBuild recipe (`bluebuild/recipe.yml`) layering on
`ghcr.io/secureblue/securecore-kinoite-hardened-userns`
- `kickstart/install-ostreecontainer.ks` — 10-line kickstart that calls
`ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry`
and lets Anaconda's LUKS UX drive the install
- veilor brand layer: KDE black theme, gum installer assets, custom
Konsole profile, branded `os-release`
- `veilor-power` 3-mode CLI (lifted as-is from v0.5.x overlay)
- `veilor-postinstall` (formerly v0.6 flagship) — first-login TUI
- `veilor-doctor` (formerly v0.6) — boot-time + weekly drift check
- `veilor-update` rewritten on `bootc upgrade` (was `dnf upgrade`)
- Forgejo registry as primary OCI publish target; GHCR mirror optional
- cosign key-pair signing of OCI image (replaces broken keyless flow)
Public-flex items kept from original v0.7 entry:
Take veilor-os out of "private repo, contained audience" mode. Order Take veilor-os out of "private repo, contained audience" mode. Order
matters: people demand threat model FIRST when a security distro goes matters: people demand threat model FIRST when a security distro goes

View file

@ -119,11 +119,10 @@ chrony
firewalld firewalld
plymouth plymouth
# AppArmor stack — Fedora 43 ships parser/utils/profiles. v0.6 ships # AppArmor stack — DEFERRED. apparmor-parser / apparmor-utils /
# loaded-but-complain only (see scripts/40-apparmor.sh + tier-2 plan). # apparmor-profiles are not in Fedora 43 base or updates. v0.6 ships
apparmor-parser # without AppArmor; tier-2 plan to land via COPR or as part of the v0.7
apparmor-utils # secureblue OCI hybrid (which has its own LSM stack).
apparmor-profiles
# admin essentials # admin essentials
git git