Compare commits
38 commits
v0.7-blueb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c961eba88 | |||
|
|
8c70030d80 | ||
|
|
89c7df0ecc | ||
|
|
c2b4df8ef9 | ||
|
|
b9df392fbc | ||
|
|
84fa325e46 | ||
|
|
1e4ca2b56b | ||
|
|
446c602683 | ||
|
|
ac5c29df42 | ||
|
|
6f4842a75c | ||
|
|
4b90e7e00b | ||
|
|
7a0c665cf0 | ||
|
|
d38fce4cb8 | ||
| bc738c1c7b | |||
| a3f6c1a1a6 | |||
| 356013e1ca | |||
| 417acb5585 | |||
| df574e00f5 | |||
| 3e660534a1 | |||
| 749bcef5b4 | |||
| 77ed91ed8e | |||
| ad059ec73e | |||
| 05d37f6419 | |||
| eafb8b7aa1 | |||
| d0738970e0 | |||
|
|
7974ed7a6e | ||
|
|
f06ee5cc1c | ||
|
|
130f0432dd | ||
|
|
08f16bb2ee | ||
|
|
25b8d30f35 | ||
|
|
aa731f9daa | ||
|
|
441f7d057f | ||
|
|
816fc0ee68 | ||
|
|
44f0c787a7 | ||
|
|
900f5465b3 | ||
|
|
63c5e199d9 | ||
|
|
abb67841f1 | ||
|
|
b86b4f9ec3 |
23 changed files with 99 additions and 2094 deletions
268
.github/workflows/build-bluebuild.yml
vendored
268
.github/workflows/build-bluebuild.yml
vendored
|
|
@ -1,268 +0,0 @@
|
||||||
name: Build veilor-os OCI (BlueBuild)
|
|
||||||
|
|
||||||
# v0.7 spike — builds the bootable OCI image used by the bootstrap
|
|
||||||
# kickstart's `ostreecontainer` directive. Runs on the Forgejo
|
|
||||||
# self-hosted runner (label `nullstone`); GitHub-side cosign/SBOM/
|
|
||||||
# attest steps are gated off because Forgejo has no Sigstore Fulcio-
|
|
||||||
# trusted OIDC issuer (see docs/PROOF-OF-WORK.md, build-iso.yml fix).
|
|
||||||
#
|
|
||||||
# Reference: https://blue-build.org/how-to/setup-build-action/
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [v0.7-bluebuild-spike]
|
|
||||||
paths:
|
|
||||||
- 'bluebuild/**'
|
|
||||||
- 'overlay/**'
|
|
||||||
- 'assets/**'
|
|
||||||
- 'scripts/**'
|
|
||||||
- '.github/workflows/build-bluebuild.yml'
|
|
||||||
pull_request:
|
|
||||||
branches: [main, v0.7-bluebuild-spike]
|
|
||||||
schedule:
|
|
||||||
# Rebuild weekly so we pick up upstream secureblue + Fedora updates.
|
|
||||||
- cron: '0 6 * * 1'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build + push OCI
|
|
||||||
# nullstone label resolves to veilor-build:43 (fedora43 + nodejs)
|
|
||||||
# via runner config. Privileged + userns=host + sock pass-through
|
|
||||||
# already wired in the runner config (see infra/forgejo/).
|
|
||||||
runs-on: nullstone
|
|
||||||
timeout-minutes: 360
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
id-token: write # for GH-only cosign keyless (skipped on Forgejo)
|
|
||||||
attestations: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
# Forgejo container registry path. PAT in FORGEJO_REGISTRY_TOKEN
|
|
||||||
# secret has package:write on veilor-org.
|
|
||||||
FORGEJO_REGISTRY: git.s8n.ru
|
|
||||||
FORGEJO_IMAGE: git.s8n.ru/veilor-org/veilor-os
|
|
||||||
OCI_TAG: "43"
|
|
||||||
# GH parallel target — only used when run on github.com.
|
|
||||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/veilor-os
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
# Pinned to last v4 tag confirmed to ship on node20.
|
|
||||||
uses: actions/checkout@v4.1.7
|
|
||||||
|
|
||||||
- name: Fix sudo perms (userns=host artefact)
|
|
||||||
run: |
|
|
||||||
# Daemon has userns-remap=default; the act job container is
|
|
||||||
# launched with --userns=host. The image was pulled under
|
|
||||||
# remap so /etc/sudo.conf + /etc/sudoers ship as uid 100000.
|
|
||||||
# sudo refuses to read either unless owned by uid 0. Restore.
|
|
||||||
chown -R 0:0 /etc/sudo.conf /etc/sudoers /etc/sudoers.d 2>/dev/null || true
|
|
||||||
ls -la /etc/sudo.conf /etc/sudoers 2>&1 | head -5
|
|
||||||
|
|
||||||
- name: Install build tooling (Fedora)
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
dnf -y upgrade --refresh
|
|
||||||
# veilor-build:43 already ships git, curl, tar, sudo, nodejs.
|
|
||||||
# cosign is not packaged in Fedora 43; we install it from the
|
|
||||||
# upstream release tarball below in a separate step.
|
|
||||||
dnf -y install --skip-unavailable \
|
|
||||||
podman \
|
|
||||||
buildah \
|
|
||||||
skopeo \
|
|
||||||
jq
|
|
||||||
# blue-build/github-action shells out to `docker`; Fedora ships
|
|
||||||
# podman. Symlink so the action finds the CLI.
|
|
||||||
if ! command -v docker >/dev/null; then
|
|
||||||
ln -sf "$(command -v podman)" /usr/local/bin/docker
|
|
||||||
docker --version
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Install cosign binary (upstream release)
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
# Fedora 43 has no cosign rpm. Pull static x86_64 binary
|
|
||||||
# from sigstore/cosign GitHub releases. Pinned to v2.4.1.
|
|
||||||
COSIGN_VERSION="2.4.1"
|
|
||||||
curl -fsSL \
|
|
||||||
"https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64" \
|
|
||||||
-o /usr/local/bin/cosign
|
|
||||||
chmod +x /usr/local/bin/cosign
|
|
||||||
cosign version
|
|
||||||
|
|
||||||
- name: Pre-pull secureblue base image
|
|
||||||
env:
|
|
||||||
GHCR_PULL_TOKEN: ${{ secrets.GHCR_PULL_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
# GHCR rate-limits anonymous CI pulls (403 on bearer-token).
|
|
||||||
# Login with a read-only PAT (forgejo secret GHCR_PULL_TOKEN)
|
|
||||||
# so bluebuild's buildah inside the CLI container also sees a
|
|
||||||
# valid auth.json via shared storage bind-mount below.
|
|
||||||
if [ -n "${GHCR_PULL_TOKEN:-}" ]; then
|
|
||||||
echo "$GHCR_PULL_TOKEN" | podman login \
|
|
||||||
--username s8n-ru \
|
|
||||||
--password-stdin ghcr.io
|
|
||||||
else
|
|
||||||
echo "[WARN] GHCR_PULL_TOKEN secret empty; trying anonymous pull"
|
|
||||||
fi
|
|
||||||
podman pull ghcr.io/secureblue/kinoite-main-hardened:latest
|
|
||||||
|
|
||||||
- name: Stage cosign private key for signing module
|
|
||||||
env:
|
|
||||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if [ -z "${COSIGN_PRIVATE_KEY:-}" ]; then
|
|
||||||
echo "[ERR] COSIGN_PRIVATE_KEY secret missing"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# bluebuild signing module reads from this env var when
|
|
||||||
# building the cosign.key bind stage. Also write to bluebuild/
|
|
||||||
# so it sits next to cosign.pub for local reproducible runs.
|
|
||||||
mkdir -p bluebuild
|
|
||||||
printf '%s' "$COSIGN_PRIVATE_KEY" > bluebuild/cosign.key
|
|
||||||
chmod 600 bluebuild/cosign.key
|
|
||||||
# bluebuild's generated Containerfile uses `FROM scratch as
|
|
||||||
# stage-keys; COPY cosign.pub /keys/`. Buildah's build context
|
|
||||||
# is the cwd ($PWD) — symlink the keys to repo root so COPY
|
|
||||||
# finds them there too.
|
|
||||||
ln -sf bluebuild/cosign.pub cosign.pub
|
|
||||||
ln -sf bluebuild/cosign.key cosign.key
|
|
||||||
ls -la cosign.pub cosign.key 2>&1 | head -4
|
|
||||||
|
|
||||||
- name: Build OCI image with BlueBuild CLI container
|
|
||||||
id: bluebuild
|
|
||||||
# blue-build/github-action requires docker buildx which podman
|
|
||||||
# doesn't ship. Run the official BlueBuild CLI container with
|
|
||||||
# buildah driver instead — works against rootless or rootful
|
|
||||||
# podman, no docker dependency.
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
# Pull cli image; pinned to v0.9.x at action time.
|
|
||||||
podman pull ghcr.io/blue-build/cli:latest
|
|
||||||
# Mount the repo + podman socket; build with buildah driver.
|
|
||||||
# Bind host /var/lib/containers/storage into the bluebuild
|
|
||||||
# CLI container so buildah inside it can see the pre-pulled
|
|
||||||
# secureblue base layer (avoids GHCR auth round-trip during
|
|
||||||
# templating).
|
|
||||||
# podman login writes to $XDG_RUNTIME_DIR/containers/auth.json
|
|
||||||
# by default, which is volatile. Find it + copy to a stable
|
|
||||||
# path that we then bind into the bluebuild container.
|
|
||||||
AUTH_SRC=""
|
|
||||||
for cand in \
|
|
||||||
"${XDG_RUNTIME_DIR:-/run/user/0}/containers/auth.json" \
|
|
||||||
"/run/containers/0/auth.json" \
|
|
||||||
"/root/.config/containers/auth.json" \
|
|
||||||
"/root/.docker/config.json"; do
|
|
||||||
if [ -f "$cand" ]; then AUTH_SRC="$cand"; break; fi
|
|
||||||
done
|
|
||||||
if [ -z "$AUTH_SRC" ]; then
|
|
||||||
echo "[ERR] no podman/docker auth.json found post-login"
|
|
||||||
find / -name auth.json -o -name 'config.json' 2>/dev/null | head -10
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
mkdir -p /root/.config/containers
|
|
||||||
cp "$AUTH_SRC" /root/.config/containers/auth.json
|
|
||||||
ls -la /root/.config/containers/auth.json
|
|
||||||
|
|
||||||
# Diagnostic: confirm the keypair landed where bluebuild expects.
|
|
||||||
ls -la bluebuild/
|
|
||||||
head -1 bluebuild/cosign.pub
|
|
||||||
head -1 bluebuild/cosign.key | cut -c1-30
|
|
||||||
|
|
||||||
podman run --rm \
|
|
||||||
--privileged \
|
|
||||||
--security-opt label=disable \
|
|
||||||
--security-opt seccomp=unconfined \
|
|
||||||
--entrypoint /usr/bin/bluebuild \
|
|
||||||
-v "$PWD:/work" \
|
|
||||||
-v /var/lib/containers/storage:/var/lib/containers/storage \
|
|
||||||
-v /root/.config/containers/auth.json:/root/.config/containers/auth.json:ro \
|
|
||||||
-w /work \
|
|
||||||
-e BB_BUILD_DRIVER=buildah \
|
|
||||||
ghcr.io/blue-build/cli:latest \
|
|
||||||
build \
|
|
||||||
--build-driver buildah \
|
|
||||||
-vv \
|
|
||||||
bluebuild/recipe.yml
|
|
||||||
# bluebuild CLI tags as <recipe-name>:<tag> in local podman
|
|
||||||
# storage. List + verify, then re-tag for the registries.
|
|
||||||
podman images
|
|
||||||
podman tag localhost/veilor-os:latest "${FORGEJO_IMAGE}:${OCI_TAG}" || true
|
|
||||||
podman tag localhost/veilor-os:latest "${FORGEJO_IMAGE}:latest" || true
|
|
||||||
|
|
||||||
- name: Push to Forgejo registry (primary)
|
|
||||||
if: success() && github.event_name != 'pull_request' && github.server_url != 'https://github.com'
|
|
||||||
env:
|
|
||||||
FORGEJO_REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
|
||||||
FORGEJO_REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if [ -z "${FORGEJO_REGISTRY_TOKEN:-}" ]; then
|
|
||||||
echo "[WARN] FORGEJO_REGISTRY_TOKEN secret is empty; skipping push"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "$FORGEJO_REGISTRY_TOKEN" | podman login \
|
|
||||||
--username "${FORGEJO_REGISTRY_USER:-veilor-org}" \
|
|
||||||
--password-stdin "$FORGEJO_REGISTRY"
|
|
||||||
podman push "${FORGEJO_IMAGE}:${OCI_TAG}"
|
|
||||||
podman push "${FORGEJO_IMAGE}:latest"
|
|
||||||
echo "[OK] pushed ${FORGEJO_IMAGE}:{${OCI_TAG},latest}"
|
|
||||||
|
|
||||||
- name: Push to GHCR (mirror, GitHub-only)
|
|
||||||
if: success() && github.event_name != 'pull_request' && github.server_url == 'https://github.com'
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
podman tag localhost/veilor-os:latest "${GHCR_IMAGE}:${OCI_TAG}"
|
|
||||||
podman tag localhost/veilor-os:latest "${GHCR_IMAGE}:latest"
|
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | podman login \
|
|
||||||
--username "${{ github.repository_owner }}" \
|
|
||||||
--password-stdin ghcr.io
|
|
||||||
podman push "${GHCR_IMAGE}:${OCI_TAG}"
|
|
||||||
podman push "${GHCR_IMAGE}:latest"
|
|
||||||
|
|
||||||
- name: Smoke-test OCI image
|
|
||||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
podman run --rm "localhost/veilor-os:latest" /bin/bash -c '
|
|
||||||
set -e
|
|
||||||
echo "-- os-release"
|
|
||||||
head -5 /etc/os-release
|
|
||||||
echo "-- sudo present"; which sudo
|
|
||||||
echo "-- mullvad-browser path"; rpm -q mullvad-browser || echo "not installed"
|
|
||||||
echo "-- yggdrasil"; rpm -q yggdrasil || echo "not installed"
|
|
||||||
echo "-- tailscale"; rpm -q tailscale || echo "not installed"
|
|
||||||
echo "-- veilor-firstboot unit"; ls -la /etc/systemd/system/veilor-firstboot.service 2>&1 || true
|
|
||||||
echo "-- brand-leak scan (text files only, bounded paths)"
|
|
||||||
HITS=$(find /etc/veilor* /etc/tuned/profiles/veilor-* /usr/share/veilor-os /usr/local/bin/veilor-* -type f \( -name "*.sh" -o -name "*.conf" -o -name "*.service" -o -name "*.timer" -o -name "*.txt" -o -name "*.md" -o -name "*.json" -o -name "*.yml" -o -name "*.yaml" -o -name "os-release" \) -exec grep -liE "onyx|192\.168\.0\.|fedora\.local|xynki\.dev" {} + 2>/dev/null || true)
|
|
||||||
if [ -n "$HITS" ]; then echo "[ERR] brand leak detected:"; echo "$HITS"; exit 1; fi
|
|
||||||
'
|
|
||||||
|
|
||||||
# ── GitHub-only signing/SBOM/attest ────────────────────────────
|
|
||||||
# cosign keyless needs Sigstore Fulcio-trusted OIDC. Forgejo
|
|
||||||
# has none, so these are GH-only. v0.7+ TODO: cosign key-pair
|
|
||||||
# signing for Forgejo using a stored secret.
|
|
||||||
|
|
||||||
- name: SBOM (SPDX, GitHub-only)
|
|
||||||
if: github.event_name == 'push' && github.server_url == 'https://github.com'
|
|
||||||
# Pinned to last v0.17 release that ships node20.
|
|
||||||
uses: anchore/sbom-action@v0.17.2
|
|
||||||
with:
|
|
||||||
image: ${{ env.GHCR_IMAGE }}:${{ env.OCI_TAG }}
|
|
||||||
format: spdx-json
|
|
||||||
output-file: veilor-os-oci.spdx.json
|
|
||||||
|
|
||||||
- name: Build provenance attestation (GitHub-only)
|
|
||||||
if: github.event_name == 'push' && github.server_url == 'https://github.com'
|
|
||||||
# Pinned to last v2.2 release that ships node20.
|
|
||||||
uses: actions/attest-build-provenance@v2.2.3
|
|
||||||
with:
|
|
||||||
subject-name: ${{ env.GHCR_IMAGE }}
|
|
||||||
subject-digest: ${{ steps.bluebuild.outputs.digest }}
|
|
||||||
208
.github/workflows/build-installer-iso.yml
vendored
208
.github/workflows/build-installer-iso.yml
vendored
|
|
@ -1,208 +0,0 @@
|
||||||
name: Build veilor-os Installer ISO
|
|
||||||
|
|
||||||
# v0.7+ — produces a small Anaconda installer ISO that consumes
|
|
||||||
# kickstart/install-ostreecontainer-installer.ks. The ISO boots
|
|
||||||
# Anaconda, asks for LUKS pw + admin pw interactively, then
|
|
||||||
# `ostreecontainer` populates / from the v0.7 OCI image at
|
|
||||||
# ghcr.io/veilor-org/veilor-os:43.
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [v0.7-bluebuild-spike]
|
|
||||||
paths:
|
|
||||||
- 'kickstart/install-ostreecontainer.ks'
|
|
||||||
- 'kickstart/install-ostreecontainer-installer.ks'
|
|
||||||
- 'bluebuild/recipe.yml'
|
|
||||||
- '.github/workflows/build-installer-iso.yml'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
releasever:
|
|
||||||
description: 'Fedora release version'
|
|
||||||
required: false
|
|
||||||
default: '43'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write # needed to create+update installer-latest release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build installer ISO
|
|
||||||
runs-on: nullstone
|
|
||||||
timeout-minutes: 120
|
|
||||||
|
|
||||||
env:
|
|
||||||
RELEASEVER: ${{ github.event.inputs.releasever || '43' }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4.1.7
|
|
||||||
|
|
||||||
- name: Install build tooling (Fedora)
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
dnf -y upgrade --refresh
|
|
||||||
dnf -y install --skip-unavailable podman jq
|
|
||||||
|
|
||||||
- name: Login to Forgejo registry (pull veilor-os OCI)
|
|
||||||
env:
|
|
||||||
FORGEJO_REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
|
||||||
FORGEJO_REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if [ -n "${FORGEJO_REGISTRY_TOKEN:-}" ]; then
|
|
||||||
echo "$FORGEJO_REGISTRY_TOKEN" | podman login \
|
|
||||||
--username "${FORGEJO_REGISTRY_USER:-veilor-org}" \
|
|
||||||
--password-stdin git.s8n.ru
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build installer ISO with bootc-image-builder
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
# livemedia-creator does NOT support ostreecontainer (only
|
|
||||||
# ostreesetup / url / nfs install methods). bootc-image-builder
|
|
||||||
# is the canonical tool for ostreecontainer-based installer
|
|
||||||
# ISOs; consumes our OCI image directly.
|
|
||||||
OUT="/tmp/bib-out-$$"
|
|
||||||
rm -rf "$OUT"
|
|
||||||
mkdir -p "$OUT"
|
|
||||||
# Pull the veilor-os OCI we built; bootc-image-builder needs
|
|
||||||
# it locally to compose the installer ISO.
|
|
||||||
podman pull ghcr.io/veilor-org/veilor-os:43 || \
|
|
||||||
podman pull git.s8n.ru/veilor-org/veilor-os:43
|
|
||||||
# Generate config.toml for bootc-image-builder.
|
|
||||||
#
|
|
||||||
# We use [customizations.installer.kickstart] (NOT
|
|
||||||
# [customizations.user]) because we need our own %post --nochroot
|
|
||||||
# block to persist install logs back to the boot USB. Per upstream
|
|
||||||
# docs, [customizations.user] and [customizations.installer.kickstart]
|
|
||||||
# are mutually exclusive (see osbuild/bootc-image-builder#528) — so
|
|
||||||
# the admin user is now created by a kickstart `user` directive
|
|
||||||
# below, locked + chage 0 so first SDDM login forces a real pw.
|
|
||||||
#
|
|
||||||
# bootc-image-builder auto-appends `ostreecontainer ...` to the
|
|
||||||
# contents we provide; we MUST NOT include that line ourselves
|
|
||||||
# (we strip it from the source kickstart with sed).
|
|
||||||
#
|
|
||||||
# NOTE on kernel cmdline default: ideally we'd set
|
|
||||||
# `veilor.install_logs=on` as an installer-kernel default, but
|
|
||||||
# `[customizations.kernel].append` targets the INSTALLED system's
|
|
||||||
# kargs.d, not the live ISO's grub.cfg (osbuild/bootc-image-builder
|
|
||||||
# #899 still open). The persist-install-logs.sh helper defaults to
|
|
||||||
# ON when the toggle is absent, so the desired default is achieved
|
|
||||||
# without needing installer-cmdline injection. Operators flip to
|
|
||||||
# off at boot via GRUB edit: append `veilor.install_logs=off`.
|
|
||||||
KS_SRC="kickstart/install-ostreecontainer-installer.ks"
|
|
||||||
KS_FILTERED="$(grep -v '^ostreecontainer' "$KS_SRC")"
|
|
||||||
# Insert a locked admin user directive under the rootpw block —
|
|
||||||
# Anaconda's interactive Users spoke is unavailable in unattended
|
|
||||||
# bib mode, so we pre-create admin and let chage -d 0 force a pw
|
|
||||||
# change at first login.
|
|
||||||
USER_LINE='user --name=admin --groups=wheel --plaintext --password="" --lock'
|
|
||||||
KS_FILTERED="$(printf '%s\n' "$KS_FILTERED" | awk -v ul="$USER_LINE" '/^rootpw --lock$/ { print; print ul; next } { print }')"
|
|
||||||
{
|
|
||||||
echo '[customizations.installer.kickstart]'
|
|
||||||
echo 'contents = """'
|
|
||||||
printf '%s\n' "$KS_FILTERED"
|
|
||||||
echo '"""'
|
|
||||||
} > /tmp/bib-config.toml
|
|
||||||
podman run --rm \
|
|
||||||
--privileged \
|
|
||||||
--pull=newer \
|
|
||||||
--security-opt label=type:unconfined_t \
|
|
||||||
-v "$OUT:/output" \
|
|
||||||
-v /tmp/bib-config.toml:/config.toml:ro \
|
|
||||||
-v /var/lib/containers/storage:/var/lib/containers/storage \
|
|
||||||
quay.io/centos-bootc/bootc-image-builder:latest \
|
|
||||||
--type anaconda-iso \
|
|
||||||
--config /config.toml \
|
|
||||||
--rootfs btrfs \
|
|
||||||
ghcr.io/veilor-org/veilor-os:43
|
|
||||||
mkdir -p build/out
|
|
||||||
find "$OUT" -name '*.iso' -exec cp {} build/out/ \;
|
|
||||||
ls -lh build/out/
|
|
||||||
|
|
||||||
- name: Rename ISO + sha256
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
ISO_FILE=$(ls build/out/*.iso 2>/dev/null | head -1)
|
|
||||||
[ -n "$ISO_FILE" ] || { echo "[ERR] no ISO produced"; exit 1; }
|
|
||||||
ISO_NAME="veilor-os-installer-${RELEASEVER}-$(date +%Y%m%d-%H%M%S).iso"
|
|
||||||
mv "$ISO_FILE" "build/out/$ISO_NAME"
|
|
||||||
cd build/out
|
|
||||||
sha256sum "$ISO_NAME" > "$ISO_NAME.sha256"
|
|
||||||
ls -lh "$ISO_NAME"
|
|
||||||
|
|
||||||
- name: Split ISO into 1900M chunks
|
|
||||||
if: success() && github.ref == 'refs/heads/v0.7-bluebuild-spike'
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
cd build/out
|
|
||||||
ISO=$(ls *.iso | head -1)
|
|
||||||
[ -n "$ISO" ] || { echo "[ERR] no ISO"; exit 1; }
|
|
||||||
split -b 1900M -d --suffix-length=2 "$ISO" "${ISO}.part-"
|
|
||||||
rm -f "$ISO"
|
|
||||||
sha256sum *.part-* > "${ISO}.parts.sha256"
|
|
||||||
ls "${ISO}".part-*
|
|
||||||
|
|
||||||
- name: Publish to installer-latest rolling prerelease (Forgejo)
|
|
||||||
if: success() && github.ref == 'refs/heads/v0.7-bluebuild-spike' && github.server_url != 'https://github.com'
|
|
||||||
env:
|
|
||||||
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
FORGEJO_API: ${{ github.server_url }}/api/v1
|
|
||||||
REPO: ${{ github.repository }}
|
|
||||||
GIT_SHA: ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
TAG="installer-latest"
|
|
||||||
REL_JSON=$(curl -fsSL -H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
"${FORGEJO_API}/repos/${REPO}/releases/tags/${TAG}" 2>/dev/null || echo "")
|
|
||||||
if [ -n "$REL_JSON" ]; then
|
|
||||||
REL_ID=$(echo "$REL_JSON" | grep -oE '"id":\s*[0-9]+' | head -1 | grep -oE '[0-9]+')
|
|
||||||
if [ -n "$REL_ID" ]; then
|
|
||||||
curl -fsSL -X DELETE -H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
"${FORGEJO_API}/repos/${REPO}/releases/${REL_ID}" || true
|
|
||||||
curl -fsSL -X DELETE -H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
"${FORGEJO_API}/repos/${REPO}/git/refs/tags/${TAG}" || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
BODY="Rolling auto-build from v0.7-bluebuild-spike. Latest commit: ${GIT_SHA}.
|
|
||||||
|
|
||||||
Installer ISO — boots Anaconda, prompts for LUKS pw + admin pw,
|
|
||||||
then ostreecontainer-pulls / from ghcr.io/veilor-org/veilor-os:43.
|
|
||||||
|
|
||||||
Reassemble:
|
|
||||||
cat veilor-os-installer-*.iso.part-* > veilor-os-installer.iso
|
|
||||||
sha256sum -c veilor-os-installer-*.iso.parts.sha256
|
|
||||||
|
|
||||||
Not a stable release — for testing only."
|
|
||||||
PAYLOAD=$(BODY="$BODY" TAG="$TAG" python3 -c "
|
|
||||||
import json,os
|
|
||||||
print(json.dumps({
|
|
||||||
'tag_name': os.environ['TAG'],
|
|
||||||
'target_commitish': 'v0.7-bluebuild-spike',
|
|
||||||
'name': 'installer-latest (auto)',
|
|
||||||
'body': os.environ['BODY'],
|
|
||||||
'prerelease': True,
|
|
||||||
'draft': False,
|
|
||||||
}))")
|
|
||||||
REL_ID=$(curl -fsSL -X POST -H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$PAYLOAD" \
|
|
||||||
"${FORGEJO_API}/repos/${REPO}/releases" | \
|
|
||||||
grep -oE '"id":\s*[0-9]+' | head -1 | grep -oE '[0-9]+')
|
|
||||||
[ -n "$REL_ID" ] || { echo "[ERR] failed to create release"; exit 1; }
|
|
||||||
cd build/out
|
|
||||||
for f in *.iso.part-* *.sha256; do
|
|
||||||
[ -f "$f" ] || continue
|
|
||||||
curl -fsSL -X POST -H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
-F "attachment=@${f}" \
|
|
||||||
"${FORGEJO_API}/repos/${REPO}/releases/${REL_ID}/assets?name=${f}"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Print build log on failure
|
|
||||||
if: failure()
|
|
||||||
run: |
|
|
||||||
echo "─── build/out/build.log ───"
|
|
||||||
tail -200 build/out/build.log 2>/dev/null || echo "(no build.log)"
|
|
||||||
find build/out -name 'program.log' -exec tail -100 {} \; 2>/dev/null || true
|
|
||||||
find /var/lmc -name '*.log' -exec tail -50 {} \; 2>/dev/null || true
|
|
||||||
122
.github/workflows/smoke-test-oci.yml
vendored
122
.github/workflows/smoke-test-oci.yml
vendored
|
|
@ -1,122 +0,0 @@
|
||||||
name: Smoke-test veilor-os OCI
|
|
||||||
|
|
||||||
# Pulls git.s8n.ru/veilor-org/veilor-os:43 and asserts that the image
|
|
||||||
# contains the veilor brand + the v0.5.x hardening overlay + the v0.7
|
|
||||||
# CLI tools, and that cosign verifies it against bluebuild/cosign.pub.
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["Build veilor-os OCI (BlueBuild)"]
|
|
||||||
types: [completed]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
smoke:
|
|
||||||
name: OCI smoke test
|
|
||||||
runs-on: nullstone
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'workflow_dispatch' ||
|
|
||||||
(github.event_name == 'workflow_run' &&
|
|
||||||
github.event.workflow_run.conclusion == 'success')
|
|
||||||
timeout-minutes: 20
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE: git.s8n.ru/veilor-org/veilor-os:43
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4.1.7
|
|
||||||
|
|
||||||
- name: Fix sudo perms
|
|
||||||
run: chown -R 0:0 /etc/sudo.conf /etc/sudoers /etc/sudoers.d 2>/dev/null || true
|
|
||||||
|
|
||||||
- name: Install podman + cosign
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
command -v podman >/dev/null || dnf -y install --skip-unavailable podman
|
|
||||||
if ! command -v cosign >/dev/null 2>&1; then
|
|
||||||
curl -fsSL "https://github.com/sigstore/cosign/releases/download/v2.4.1/cosign-linux-amd64" \
|
|
||||||
-o /usr/local/bin/cosign
|
|
||||||
chmod +x /usr/local/bin/cosign
|
|
||||||
fi
|
|
||||||
podman --version
|
|
||||||
cosign version
|
|
||||||
|
|
||||||
- name: Login + pull OCI image
|
|
||||||
env:
|
|
||||||
FORGEJO_REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
|
||||||
FORGEJO_REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
if [ -n "${FORGEJO_REGISTRY_TOKEN:-}" ]; then
|
|
||||||
echo "$FORGEJO_REGISTRY_TOKEN" | podman login \
|
|
||||||
--username "${FORGEJO_REGISTRY_USER:-veilor-org}" \
|
|
||||||
--password-stdin git.s8n.ru
|
|
||||||
fi
|
|
||||||
podman pull "${IMAGE}"
|
|
||||||
|
|
||||||
- name: Verify cosign signature
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
[ -f bluebuild/cosign.pub ] || { echo "[ERR] bluebuild/cosign.pub missing"; exit 1; }
|
|
||||||
cosign verify --key bluebuild/cosign.pub "${IMAGE}" 2>&1 | tail -10
|
|
||||||
|
|
||||||
- name: Run OCI assertions
|
|
||||||
run: |
|
|
||||||
set -uo pipefail
|
|
||||||
PASS=0; FAIL=0; ERRORS=""
|
|
||||||
pass() { echo "[PASS] $1"; PASS=$((PASS+1)); }
|
|
||||||
fail() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); ERRORS="${ERRORS} - $1\n"; }
|
|
||||||
img() { podman run --rm "${IMAGE}" /bin/bash -c "$1" 2>/dev/null; }
|
|
||||||
|
|
||||||
OS=$(img 'cat /etc/os-release 2>/dev/null')
|
|
||||||
echo "$OS" | grep -q 'ID=veilor' && pass "ID=veilor" || fail "ID=veilor missing"
|
|
||||||
echo "$OS" | grep -q 'NAME="veilor-os"' && pass 'NAME="veilor-os"' || fail 'NAME="veilor-os" missing'
|
|
||||||
|
|
||||||
img 'which sudo' >/dev/null && pass "sudo present" || fail "sudo missing"
|
|
||||||
img 'rpm -q mullvad-browser' >/dev/null && pass "mullvad-browser present" || fail "mullvad-browser missing"
|
|
||||||
img 'rpm -q tailscale' >/dev/null && pass "tailscale present" || fail "tailscale missing"
|
|
||||||
img 'rpm -q yggdrasil' >/dev/null && pass "yggdrasil present" || fail "yggdrasil missing"
|
|
||||||
|
|
||||||
if img 'grep -qi "^SELINUX=enforcing" /etc/selinux/config'; then
|
|
||||||
pass "SELinux config = enforcing"
|
|
||||||
else
|
|
||||||
fail "SELinux not enforcing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
img 'test -e /etc/systemd/system/multi-user.target.wants/veilor-firstboot.service \
|
|
||||||
-o -e /etc/systemd/system/graphical.target.wants/veilor-firstboot.service' >/dev/null \
|
|
||||||
&& pass "veilor-firstboot enabled" || fail "veilor-firstboot not enabled"
|
|
||||||
img 'test -e /etc/systemd/system/multi-user.target.wants/veilor-postinstall.service \
|
|
||||||
-o -e /etc/systemd/system/graphical.target.wants/veilor-postinstall.service' >/dev/null \
|
|
||||||
&& pass "veilor-postinstall enabled" || fail "veilor-postinstall not enabled"
|
|
||||||
|
|
||||||
for b in veilor-power veilor-update veilor-doctor veilor-postinstall; do
|
|
||||||
img "test -x /usr/local/bin/${b}" >/dev/null \
|
|
||||||
&& pass "${b} executable" || fail "${b} missing"
|
|
||||||
done
|
|
||||||
|
|
||||||
if img 'ls /usr/share/veilor-os/scripts/ 2>/dev/null' | grep -qE '(10-harden|20-harden|30-apply)'; then
|
|
||||||
pass "/usr/share/veilor-os/scripts populated"
|
|
||||||
else
|
|
||||||
fail "/usr/share/veilor-os/scripts missing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
LEAKS=$(img "grep -rIni 'onyx\|192\.168\.0\.\|fedora\.local\|xynki\.dev' /etc/veilor* /usr/share/veilor-os 2>/dev/null")
|
|
||||||
if [ -z "$LEAKS" ]; then
|
|
||||||
pass "no brand leaks"
|
|
||||||
else
|
|
||||||
fail "brand leaks found"
|
|
||||||
echo "$LEAKS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "═══ ${PASS} passed, ${FAIL} failed ═══"
|
|
||||||
if [ "$FAIL" -gt 0 ]; then
|
|
||||||
printf "%b" "$ERRORS"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✓ veilor-os:43 smoke test passed"
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,4 +16,3 @@ test/veilor-vm.nvram*
|
||||||
test/auto-install-vm.qcow2
|
test/auto-install-vm.qcow2
|
||||||
test/auto-install-vm.nvram*
|
test/auto-install-vm.nvram*
|
||||||
.claude/worktrees/
|
.claude/worktrees/
|
||||||
**/cosign.key
|
|
||||||
|
|
|
||||||
129
CHANGELOG.md
129
CHANGELOG.md
|
|
@ -11,75 +11,7 @@ future maintainers can see why a change exists, not just what it changes.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### v0.7 BlueBuild OCI spike (active — `v0.7-bluebuild-spike`)
|
### Planned
|
||||||
|
|
||||||
CI plumbing landed (~13 fixes) to unblock the first green BlueBuild
|
|
||||||
run on the self-hosted Forgejo runner. **Build still red** as of
|
|
||||||
2026-05-08; OCI artifact + installer ISO pending green run.
|
|
||||||
|
|
||||||
#### Forgejo runner + build-image plumbing
|
|
||||||
|
|
||||||
- Forgejo runner upgraded to **v6.4.0** with `userns-remap=default`.
|
|
||||||
Buildah needs `--userns=host` to undo the remap inside the job; added
|
|
||||||
to every `bluebuild build` invocation.
|
|
||||||
- Custom build image **`veilor-build:43`** (fedora:43 + nodejs +
|
|
||||||
buildah deps). Replaces the upstream BlueBuild image, which lacked
|
|
||||||
Forgejo-runner-friendly tooling.
|
|
||||||
- Workflow now **`runs-on: nullstone`** (single self-hosted runner,
|
|
||||||
no nested docker).
|
|
||||||
- Build timeout bumped **60 min → 360 min** to absorb first-time
|
|
||||||
secureblue base pulls on a cold runner.
|
|
||||||
|
|
||||||
#### Signing + registry auth
|
|
||||||
|
|
||||||
- **cosign v2.4.1** installed from upstream binary (no Fedora RPM yet
|
|
||||||
for v2.4.x).
|
|
||||||
- **GHCR PAT login** added so the BlueBuild step can pull
|
|
||||||
`ghcr.io/secureblue/kinoite-main-hardened` (rate-limited anonymous).
|
|
||||||
- **cosign keypair signing** — keyless OIDC fails on Forgejo (no
|
|
||||||
Sigstore Fulcio integration), so we ship a static keypair under
|
|
||||||
the repo and sign with `cosign sign --key`. Public key checked in
|
|
||||||
for verification.
|
|
||||||
|
|
||||||
#### BlueBuild recipe pivots
|
|
||||||
|
|
||||||
- Base image switched to **`ghcr.io/secureblue/kinoite-main-hardened`**
|
|
||||||
(the actual published image). Prior reference to
|
|
||||||
`securecore-kinoite-hardened-userns` was a planning-phase guess and
|
|
||||||
did not exist.
|
|
||||||
- Module type pivots driven by buildah-privileged + bind-mounted helper
|
|
||||||
scripts hitting chmod-permitted blockers:
|
|
||||||
- `type: files` → **`type: copy`** (files module's chmod step
|
|
||||||
failed under bind-mount).
|
|
||||||
- `type: script` + `type: systemd` → **`type: containerfile` RUN**
|
|
||||||
(single layer, no helper-script bind-mount).
|
|
||||||
|
|
||||||
#### Installer ISO — pivoted
|
|
||||||
|
|
||||||
- **livemedia-creator → bootc-image-builder.** livemedia-creator does
|
|
||||||
not support the `ostreecontainer` install method (only
|
|
||||||
`ostreesetup`/`url`/`nfs`), so the v0.7 path required the swap.
|
|
||||||
Build pending OCI artifact.
|
|
||||||
|
|
||||||
#### Docs
|
|
||||||
|
|
||||||
- This CHANGELOG entry.
|
|
||||||
- ROADMAP refresh — v0.5.0 marked done, v0.7 OCI marked in-flight,
|
|
||||||
installer-iso pivot recorded, USB install-log persistence default-on
|
|
||||||
promise documented, v1.0 ship criteria carried over.
|
|
||||||
|
|
||||||
### Infra (out-of-tree, recorded for traceability)
|
|
||||||
|
|
||||||
- **2026-05-08** — Headscale OIDC 403 fixed by adding
|
|
||||||
`172.20.0.0/24` (docker proxy bridge gateway) to the
|
|
||||||
`no-guest@file` Traefik middleware allowlist on nullstone.
|
|
||||||
Unblocks `tag:guest` provisioning for veilor-os clients.
|
|
||||||
- **All GitHub remotes removed** from veilor-os local clones, six
|
|
||||||
worktrees, and sibling projects (auth-limbo, minecraft-launcher,
|
|
||||||
minecraft-server, infra). GH push-mirrors disabled. Forgejo-only
|
|
||||||
since 2026-05-05.
|
|
||||||
|
|
||||||
### Planned (deferred / parking)
|
|
||||||
|
|
||||||
- v0.3 polish — Plymouth black theme, SDDM theme, Konsole profile,
|
- v0.3 polish — Plymouth black theme, SDDM theme, Konsole profile,
|
||||||
wallpaper SVG. Re-enable `init_on_alloc=1 init_on_free=1` post-install
|
wallpaper SVG. Re-enable `init_on_alloc=1 init_on_free=1` post-install
|
||||||
|
|
@ -90,65 +22,6 @@ run on the self-hosted Forgejo runner. **Build still red** as of
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [0.5.0] — 2026-05-06
|
|
||||||
|
|
||||||
**Tag:** `v0.5.0` — **final kickstart-path release**.
|
|
||||||
|
|
||||||
The hardened-Fedora-43 kickstart line ships. Future work moves to
|
|
||||||
the v0.7 BlueBuild OCI spike; the kickstart retires at v1.0.
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- First green Forgejo-CI ISO build (~2.7 GB live ISO, EFI + BIOS
|
|
||||||
bootable). Released as `ci-latest` artifact at
|
|
||||||
`git.s8n.ru/veilor-org/veilor-os/releases/tag/ci-latest`.
|
|
||||||
- **gum TUI installer** wrapping Anaconda — single LUKS prompt,
|
|
||||||
locale locked to `en_US.UTF-8`, admin-password first-boot flow.
|
|
||||||
- **LUKS2 argon2id + btrfs subvols** install via Anaconda, written
|
|
||||||
through `/etc/kernel/cmdline` so BLS entries carry the cmdline
|
|
||||||
veilor needs.
|
|
||||||
- **3-mode `veilor-power` CLI** (`save | mid | perf`) with AC/battery
|
|
||||||
udev auto-switching, lifted into the overlay.
|
|
||||||
- **KDE black theme** + Fira Code system font, branded
|
|
||||||
`/etc/os-release`, GRUB rebrand, plymouth detail-text boot.
|
|
||||||
- Hardening: SELinux enforcing, USBGuard default-block, fail2ban +
|
|
||||||
auditd, firewalld drop zone, NTS chrony, DNS-over-TLS, locked
|
|
||||||
root.
|
|
||||||
- Self-hosted **Forgejo CI** on nullstone replaces the GitHub
|
|
||||||
Actions build pipeline.
|
|
||||||
|
|
||||||
### Fixed (delta from v0.2.5 → v0.5.0 — 35+ failure classes)
|
|
||||||
|
|
||||||
The full v0.5.x grind is documented per-release in commit messages
|
|
||||||
(v0.5.21–v0.5.32). Headline fixes:
|
|
||||||
|
|
||||||
- **`--location=none` skipped `CollectKernelArgumentsTask`.** Anaconda
|
|
||||||
shipped BLS entries with empty cmdline. Fix: write
|
|
||||||
`/etc/kernel/cmdline` directly + `/etc/default/grub` + grubby +
|
|
||||||
explicit `kernel-install add`. (v0.5.31)
|
|
||||||
- **`transaction_progress.py` install scroll** masked real failures
|
|
||||||
when patched too broadly. Narrowed the patch to only suppress
|
|
||||||
`Configuring xxx.x86_64`. (v0.5.28 → v0.5.29)
|
|
||||||
- **Locale dialog raced anaconda startup.** Lock to en_US.UTF-8,
|
|
||||||
defer locale choice to `veilor-postinstall` (v0.7 scope). (v0.5.28)
|
|
||||||
- **`fbcon=nodefer`** + GRUB rebrand + ASCII gum cursor make the
|
|
||||||
install flow legible on linux fbcon. (v0.5.27)
|
|
||||||
- **`rd.luks.uuid`** injected via `grubby --update-kernel=ALL` in
|
|
||||||
chroot `%post` — earlier releases relied on Anaconda which silently
|
|
||||||
dropped it. (v0.5.23, v0.5.27)
|
|
||||||
- **9-agent research wave** identified the v0.5.32 blocker map; 7
|
|
||||||
blockers shipped in one bundle.
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
|
|
||||||
- Treat v0.5.0 as the **portfolio anchor** for the kickstart path.
|
|
||||||
v0.5.32-rc was the last test-run; v0.5.0 was tagged on
|
|
||||||
2026-05-06 as the freeze point.
|
|
||||||
- v0.6 was **cancelled** the same day (folded into v0.7). See
|
|
||||||
`docs/ROADMAP.md` strategy-pivot section.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.2.5] — 2026-05-01
|
## [0.2.5] — 2026-05-01
|
||||||
|
|
||||||
**Commit:** `8515bdb`
|
**Commit:** `8515bdb`
|
||||||
|
|
|
||||||
42
README.md
42
README.md
|
|
@ -48,46 +48,26 @@ spike at v0.7**, **bootc-only at v1.0**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick install — v0.7+ (recommended, atomic / OCI)
|
## Quick install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Download the bootstrap installer ISO from Forgejo.
|
# 1. Download the ISO from the latest Forgejo release.
|
||||||
# https://git.s8n.ru/veilor-org/veilor-os/releases/tag/ci-latest
|
# https://git.s8n.ru/veilor-org/veilor-os/releases/tag/ci-latest
|
||||||
|
# (rolling tag; replaced on each successful build-iso.yml run)
|
||||||
sha256sum -c veilor-os-43-*.iso.sha256
|
sha256sum -c veilor-os-43-*.iso.sha256
|
||||||
|
|
||||||
# 2. Flash to USB. Replace /dev/sdX — triple-check.
|
# 2. Flash to USB. Replace /dev/sdX with your USB device — triple-check.
|
||||||
sudo dd if=veilor-os-43-*.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
sudo dd if=veilor-os-43-*.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
||||||
sync
|
sync
|
||||||
|
|
||||||
# 3. Boot from USB. Anaconda asks for LUKS passphrase + admin password.
|
# 3. Boot from USB, pick "Install veilor-os" from the menu.
|
||||||
# Anaconda then runs `ostreecontainer --url=git.s8n.ru/veilor-org/veilor-os:43`
|
# 4. Set a strong LUKS passphrase — the only prompt during install.
|
||||||
# which populates / from the signed BlueBuild OCI image.
|
# 5. Reboot, remove USB.
|
||||||
|
# 6. On first boot: TTY prompts for an admin password (≥14 chars, mixed case,
|
||||||
# 4. Reboot. Log in as `admin`. The first-login TUI (veilor-postinstall)
|
# digit, symbol). Once accepted, SDDM starts. Log in as `admin`.
|
||||||
# asks for the small set of decisions we defer from install:
|
|
||||||
# keyboard, locale, hostname, GPU drivers, package presets,
|
|
||||||
# bluetooth, USBGuard policy snapshot. Each step skippable.
|
|
||||||
|
|
||||||
# 5. Day-to-day: `sudo veilor-update` (atomic, A/B, instant rollback).
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Full v0.7 walkthrough: [docs/INSTALL-V07.md](docs/INSTALL-V07.md).
|
Full install + first-boot walkthrough: [docs/INSTALL.md](docs/INSTALL.md).
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Legacy v0.5.0 install (kickstart-flat path)
|
|
||||||
|
|
||||||
The kickstart-installed v0.5.0 ISO ships as a frozen proof-of-work
|
|
||||||
release. Same hardening, no bootc/rpm-ostree atomic layer. Updates
|
|
||||||
go through `dnf upgrade` instead of `bootc upgrade`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Same flash + boot, then pick "Install veilor-os".
|
|
||||||
# Single LUKS passphrase prompt during install; admin password set
|
|
||||||
# on first boot via TTY.
|
|
||||||
```
|
|
||||||
|
|
||||||
Walkthrough: [docs/INSTALL.md](docs/INSTALL.md).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -167,7 +147,7 @@ clean, locked down, with no manual post-install hardening required.
|
||||||
[secureblue](https://github.com/secureblue/secureblue) is an upstream
|
[secureblue](https://github.com/secureblue/secureblue) is an upstream
|
||||||
hardened atomic Fedora project we benchmark against and plan to **build
|
hardened atomic Fedora project we benchmark against and plan to **build
|
||||||
on top of** at v0.7. The v0.7 BlueBuild spike uses their
|
on top of** at v0.7. The v0.7 BlueBuild spike uses their
|
||||||
`kinoite-main-hardened` OCI image as its base — we don't
|
`securecore-kinoite-hardened-userns` OCI image as its base — we don't
|
||||||
ship their source code in this repo, we layer veilor branding,
|
ship their source code in this repo, we layer veilor branding,
|
||||||
theming, the gum installer, and the kickstart bootstrap on top of
|
theming, the gum installer, and the kickstart bootstrap on top of
|
||||||
their already-signed image.
|
their already-signed image.
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
# bluebuild/ — v0.7 spike
|
|
||||||
|
|
||||||
This directory contains the BlueBuild recipe + supporting config that
|
|
||||||
builds the veilor-os bootable OCI image. **Active on the
|
|
||||||
`v0.7-bluebuild-spike` branch only.** Does NOT land in v0.5.x main
|
|
||||||
until the spike passes its success criteria (see
|
|
||||||
`docs/STRATEGY.md`).
|
|
||||||
|
|
||||||
## What's here
|
|
||||||
|
|
||||||
```
|
|
||||||
bluebuild/
|
|
||||||
├── recipe.yml # primary BlueBuild recipe
|
|
||||||
├── config/
|
|
||||||
│ └── just/
|
|
||||||
│ └── 60-veilor.just # ujust recipes for opt-in components
|
|
||||||
└── README.md # this file
|
|
||||||
```
|
|
||||||
|
|
||||||
The recipe extends
|
|
||||||
`ghcr.io/secureblue/kinoite-main-hardened:latest`. We
|
|
||||||
inherit secureblue's hardening (sysctl + kargs + custom SELinux
|
|
||||||
policy + USBGuard + hardened-malloc + Unbound DoT + chronyd NTS +
|
|
||||||
Trivalent browser + cosign-signed image chain). On top, we layer:
|
|
||||||
|
|
||||||
- veilor branding (overlay/, theme, plymouth, sddm, os-release)
|
|
||||||
- mullvad-browser (anti-fingerprint companion to Trivalent)
|
|
||||||
- xorg-x11-server-Xwayland (re-enable; secureblue disables it)
|
|
||||||
- sudo (re-enable; secureblue replaces with run0)
|
|
||||||
- tailscale + yggdrasil (mesh stack layer 1 + 2)
|
|
||||||
- ujust recipes for Reticulum (mesh layer 3) + Thorium (opt-in browser)
|
|
||||||
|
|
||||||
Trivalent stays as the default browser (correcting an earlier draft).
|
|
||||||
|
|
||||||
## Build locally
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Requires bluebuild CLI:
|
|
||||||
# curl -fsSL https://raw.githubusercontent.com/blue-build/cli/main/install.sh | sh
|
|
||||||
cd bluebuild
|
|
||||||
bluebuild build recipe.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Output: `localhost/veilor-os:43` in podman storage. Push to GHCR
|
|
||||||
via the workflow.
|
|
||||||
|
|
||||||
## Test the OCI image
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Smoke-test (boots into the rootfs; no kernel, no init):
|
|
||||||
podman run --rm -it ghcr.io/veilor-org/veilor-os:43 /bin/bash
|
|
||||||
|
|
||||||
# Inside, sanity:
|
|
||||||
cat /etc/os-release # PRETTY_NAME=veilor-os
|
|
||||||
which sudo # /usr/bin/sudo (re-enabled)
|
|
||||||
which trivalent # secureblue's COPR (default browser)
|
|
||||||
which mullvad-browser # /usr/bin/mullvad-browser
|
|
||||||
systemctl is-enabled yggdrasil # enabled (idle)
|
|
||||||
systemctl is-enabled tailscaled # disabled (awaits ujust veilor-mesh-join)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test the installer ISO
|
|
||||||
|
|
||||||
The installer ISO is built separately by livecd-creator (current path)
|
|
||||||
or bootc-image-builder (v1.0+). Its kickstart's `%packages` block is
|
|
||||||
replaced with:
|
|
||||||
|
|
||||||
```
|
|
||||||
ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry
|
|
||||||
```
|
|
||||||
|
|
||||||
That populates the target's `/` directly from this OCI image during
|
|
||||||
the install pass. No first-boot rebase. No transition window.
|
|
||||||
|
|
||||||
## Spike success criteria (1 day)
|
|
||||||
|
|
||||||
- [ ] `bluebuild build recipe.yml` exits 0
|
|
||||||
- [ ] `bootc container lint` exits 0 on the resulting image
|
|
||||||
- [ ] `podman run` smoke-test (commands above) all pass
|
|
||||||
- [ ] `.github/workflows/build-bluebuild.yml` builds + cosign-signs +
|
|
||||||
pushes to `ghcr.io/veilor-org/veilor-os:43`
|
|
||||||
- [ ] An installer ISO using `ostreecontainer` against this OCI
|
|
||||||
reaches SDDM with admin login on first boot
|
|
||||||
|
|
||||||
If all five land, merge `v0.7-bluebuild-spike` → `main` as v0.7.0.
|
|
||||||
If any fail in ways that aren't trivially fixable, file each as a GH
|
|
||||||
issue + return to v0.5.x kickstart path.
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- `docs/STRATEGY.md` — the strategic decision + override list
|
|
||||||
- `docs/ROADMAP.md` v0.7 — full schedule
|
|
||||||
- `docs/THREAT-MODEL.md` — what we publish before launch
|
|
||||||
- secureblue: <https://github.com/secureblue/secureblue>
|
|
||||||
- BlueBuild: <https://blue-build.org>
|
|
||||||
- bootc / ostreecontainer: <https://docs.fedoraproject.org/en-US/bootc/>
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
# veilor-os ujust recipes — opt-in components
|
|
||||||
# Loaded into /usr/share/ublue-os/just/ at image build time;
|
|
||||||
# `ujust install-X` discovers + dispatches.
|
|
||||||
|
|
||||||
# install Reticulum / RetiNet AGPL fork + Sideband (mesh layer 3)
|
|
||||||
install-reticulum:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
echo "═══ Reticulum (RetiNet AGPL fork) install ═══"
|
|
||||||
echo
|
|
||||||
echo "Installs RetiNet (AGPL fork — NOT upstream RNS due to anti-AI"
|
|
||||||
echo "license) plus Sideband messenger. Default config: AutoInterface"
|
|
||||||
echo "(LAN multicast) + 1-2 TCP backbone peers. RNode hardware (LoRa"
|
|
||||||
echo "transceiver) is a separate install."
|
|
||||||
echo
|
|
||||||
read -p "Proceed? [y/N]: " confirm
|
|
||||||
if [[ "$confirm" != "y" ]]; then echo "Cancelled."; exit 0; fi
|
|
||||||
rpm-ostree install python3-pip
|
|
||||||
pip install --user retinet sideband-cli
|
|
||||||
echo
|
|
||||||
echo "Done. To attach an RNode (LoRa transceiver), run:"
|
|
||||||
echo " ujust install-reticulum-rnode"
|
|
||||||
|
|
||||||
# install Reticulum RNode hardware support (LoRa transceiver)
|
|
||||||
install-reticulum-rnode:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
echo "═══ RNode (LoRa transceiver) hardware install ═══"
|
|
||||||
echo
|
|
||||||
echo "Adds RNode firmware-update tooling + udev rules for the LoRa"
|
|
||||||
echo "USB hardware. Required only if you have an RNode device."
|
|
||||||
echo
|
|
||||||
read -p "Proceed? [y/N]: " confirm
|
|
||||||
if [[ "$confirm" != "y" ]]; then echo "Cancelled."; exit 0; fi
|
|
||||||
pip install --user rnodeconf
|
|
||||||
echo "Done. Plug in your RNode via USB; it will appear as a serial device."
|
|
||||||
|
|
||||||
# install Thorium browser (OPT-IN, with explicit CVE-lag warning)
|
|
||||||
install-thorium:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
echo "═══ Thorium browser install ═══"
|
|
||||||
echo
|
|
||||||
echo "WARNING: Thorium is a perf/media-focused fork of Chromium that"
|
|
||||||
echo "uses LTS Chromium as its base. As of 2026-05 it lags upstream"
|
|
||||||
echo "stable by ~9 milestones (months of CVE backlog)."
|
|
||||||
echo
|
|
||||||
echo "veilor-os ships Trivalent (secureblue's hardened Chromium fork,"
|
|
||||||
echo "tracking upstream M147+ within hours) as the default browser."
|
|
||||||
echo "Thorium is provided as an OPT-IN profile for users who"
|
|
||||||
echo "explicitly need its perf characteristics (e.g. WebGL games,"
|
|
||||||
echo "media decode profiles)."
|
|
||||||
echo
|
|
||||||
echo "DO NOT use Thorium as your daily-driver browser. Use Trivalent"
|
|
||||||
echo "or Mullvad Browser for that."
|
|
||||||
echo
|
|
||||||
read -p "Acknowledge CVE-lag risk and continue? [y/N]: " confirm
|
|
||||||
if [[ "$confirm" != "y" ]]; then echo "Cancelled."; exit 0; fi
|
|
||||||
flatpak install --user -y org.thorium.Thorium 2>/dev/null || \
|
|
||||||
rpm-ostree install thorium-browser
|
|
||||||
echo "Done. Launch via Plasma menu or `flatpak run org.thorium.Thorium`."
|
|
||||||
|
|
||||||
# join the veilor mesh (Tailscale via Headscale)
|
|
||||||
veilor-mesh-join:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
echo "═══ Join veilor mesh (Tailscale via Headscale) ═══"
|
|
||||||
echo
|
|
||||||
echo "Pre-auth keys are minted by the Misskey signup page at"
|
|
||||||
echo "x.veilor (TTL 24h, single-use). You can paste the hex key"
|
|
||||||
echo "directly OR scan the QR code shown after signup."
|
|
||||||
echo
|
|
||||||
read -p "Hex key (paste): " preauth
|
|
||||||
if [[ -z "$preauth" ]]; then echo "Empty key. Cancelled."; exit 0; fi
|
|
||||||
sudo systemctl enable --now tailscaled
|
|
||||||
sudo tailscale up --login-server=https://hs.s8n.ru --auth-key="$preauth"
|
|
||||||
echo "Done. Status: $(sudo tailscale status | head -1)"
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5xQcyP7FHNSiG7+VLsN2ViWlvvIB
|
|
||||||
FYmu2XmPah7/VBlmuQ88H0ZbqCqqnS2u9x5+P1OMaMK+//k89V0Blrx65Q==
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
# veilor-os — BlueBuild recipe (v0.7 spike, 1-day target)
|
|
||||||
#
|
|
||||||
# Extends secureblue's hardened Kinoite OCI image with veilor branding,
|
|
||||||
# threat-model-driven UX choices, and the three-layer mesh stack
|
|
||||||
# (Tailscale + Yggdrasil + opt-in Reticulum). This is the OCI image
|
|
||||||
# that the v0.7+ kickstart's `ostreecontainer` directive pulls into
|
|
||||||
# the target root during the install pass.
|
|
||||||
#
|
|
||||||
# Build: bluebuild build recipe.yml
|
|
||||||
# Test: podman run --rm -it ghcr.io/veilor-org/veilor-os:43 /bin/bash
|
|
||||||
# CI: .github/workflows/build-bluebuild.yml signs + pushes to GHCR.
|
|
||||||
#
|
|
||||||
# Reference: https://blue-build.org/reference/recipe/
|
|
||||||
#
|
|
||||||
# ── Module collapse history ──────────────────────────────────────
|
|
||||||
# Run 183 (2026-05-08) ate 3h10min before runner timeout: each RUN/COPY
|
|
||||||
# layer COMMIT under fuse-overlayfs over secureblue's 130-layer hardened
|
|
||||||
# base costs ~40min wallclock (STEP 10..13 each 38–43min). Ergo: every
|
|
||||||
# saved module = ~40min saved. Collapsed (A1b):
|
|
||||||
# - 5× rpm-ostree → 1× (-4 layers)
|
|
||||||
# - 2× containerfile (brand sed + systemctl enable) → 1× (-1 layer)
|
|
||||||
# - 4× copy left as-is — BlueBuild copy module is one src/dest per
|
|
||||||
# entry per https://blue-build.org/reference/modules/copy/
|
|
||||||
# Net: 12 → 7 modules, ~5×40min ≈ 3h20min off wallclock budget.
|
|
||||||
#
|
|
||||||
# Run 189 + 191 (2026-05-08) — surviving rpm-ostree module hit the same
|
|
||||||
# `chmod: Operation not permitted` bug we already worked around for
|
|
||||||
# type:files / type:script / type:systemd: BlueBuild's helper scripts
|
|
||||||
# (here `/tmp/modules/rpm-ostree/rpm-ostree.sh`) try to chmod themselves
|
|
||||||
# inside their own buildah bind-mount under userns=host and fail.
|
|
||||||
#
|
|
||||||
# A1c fix: drop type:rpm-ostree, fold its install list into the existing
|
|
||||||
# containerfile module as a raw RUN. Per BB containerfile docs each
|
|
||||||
# `snippets:` entry = its own layer, so we MERGE pkg-install + brand +
|
|
||||||
# systemctl into ONE snippet (= one RUN, one layer). Ordering: install
|
|
||||||
# packages first (yggdrasil/tailscale/etc must exist before systemctl
|
|
||||||
# enable/disable touches their units), then brand sed, then unit toggles.
|
|
||||||
# `ostree container commit` ends the snippet because BB's rpm-ostree
|
|
||||||
# module wraps it implicitly; raw RUN must do it manually for parity.
|
|
||||||
# Mullvad + Tailscale repo files curl'd in same RUN — secureblue base
|
|
||||||
# does not ship either repo, and the previous type:rpm-ostree must have
|
|
||||||
# silently failed earlier (build never got that far in 189/191).
|
|
||||||
# Net: 7 → 6 modules, one more layer commit avoided.
|
|
||||||
---
|
|
||||||
name: veilor-os
|
|
||||||
description: Hardened security-branded Fedora KDE on top of secureblue.
|
|
||||||
|
|
||||||
# Base image: secureblue's hardened Kinoite variant with userns sandboxing.
|
|
||||||
# That brings in: sysctl + kargs + custom SELinux policy + USBGuard +
|
|
||||||
# hardened-malloc + Unbound DoT + chronyd NTS + Trivalent browser.
|
|
||||||
base-image: ghcr.io/secureblue/kinoite-main-hardened
|
|
||||||
image-version: latest
|
|
||||||
|
|
||||||
modules:
|
|
||||||
# ── 1. veilor branding overlay ──────────────────────────────────
|
|
||||||
# `type: copy` is a low-level direct COPY (no chmod, no script).
|
|
||||||
# `type: files` was failing with `chmod: Operation not permitted` on
|
|
||||||
# the BlueBuild-shipped /tmp/modules/files/files.sh under buildah +
|
|
||||||
# podman privileged in our runner — the script tries to make itself
|
|
||||||
# executable inside its own bind-mounted layer.
|
|
||||||
#
|
|
||||||
# NOTE: Each copy module = one COPY layer (~40min commit on our
|
|
||||||
# runner). BlueBuild's copy module accepts a single src/dest pair
|
|
||||||
# only, so these four entries are the floor unless we move to a
|
|
||||||
# hand-rolled Containerfile.
|
|
||||||
- type: copy
|
|
||||||
source: ../overlay
|
|
||||||
destination: /
|
|
||||||
|
|
||||||
- type: copy
|
|
||||||
source: ../assets
|
|
||||||
destination: /usr/share/veilor-os/assets
|
|
||||||
|
|
||||||
- type: copy
|
|
||||||
source: ../scripts
|
|
||||||
destination: /usr/share/veilor-os/scripts
|
|
||||||
|
|
||||||
- type: copy
|
|
||||||
source: config/just
|
|
||||||
destination: /usr/share/ublue-os/just
|
|
||||||
|
|
||||||
# ── 2. Packages + branding + unit toggles in ONE RUN snippet ────
|
|
||||||
# secureblue removes sudo + replaces with run0 (too disruptive for
|
|
||||||
# daily-driver) — restore. Xwayland was disabled for attack-surface
|
|
||||||
# reduction — restore for Element/Slack/Qt5 apps. Mullvad Browser
|
|
||||||
# layered alongside Trivalent (Trivalent default per STRATEGY.md;
|
|
||||||
# Mullvad for pseudonymous browsing). Mesh stack: Tailscale (Layer
|
|
||||||
# 1, daily driver, pre-disabled), Yggdrasil-go (Layer 2, idle warm-
|
|
||||||
# fallback). Reticulum/RetiNet stays opt-in via ujust. Memory
|
|
||||||
# hygiene + ergonomic deps for veilor-postinstall + veilor-doctor.
|
|
||||||
#
|
|
||||||
# Repos: secureblue base ships neither mullvad nor tailscale repos.
|
|
||||||
# curl them into /etc/yum.repos.d/ inside the same RUN, before the
|
|
||||||
# rpm-ostree install. Both pinned to upstream stable for Fedora.
|
|
||||||
#
|
|
||||||
# Branding + unit toggles run in the same RUN (= same layer) AFTER
|
|
||||||
# rpm-ostree install so systemctl enable yggdrasil / disable tailscaled
|
|
||||||
# see their unit files.
|
|
||||||
#
|
|
||||||
# Helper-script avoidance: BlueBuild's `type: rpm-ostree` /
|
|
||||||
# `type: files` / `type: script` / `type: systemd` modules all hit
|
|
||||||
# `chmod: Operation not permitted` on their own bind-mounted helper
|
|
||||||
# script under buildah userns=host (run 189 + 191, last-frame error:
|
|
||||||
# `chmod: changing permissions of '/tmp/modules/rpm-ostree/rpm-ostree.sh':
|
|
||||||
# Operation not permitted`). Raw `type: containerfile` RUN bypasses
|
|
||||||
# the whole helper-script layer.
|
|
||||||
#
|
|
||||||
# ostree container commit at the end mirrors what BB's wrapped
|
|
||||||
# rpm-ostree module does implicitly — finalizes the layer for the
|
|
||||||
# secureblue / Universal Blue base expectation.
|
|
||||||
#
|
|
||||||
# brand-leak grep moved to CI smoke-test in build-bluebuild.yml
|
|
||||||
# (STEP 14 hung under buildah overlayfs, run 171 2026-05-07).
|
|
||||||
- type: containerfile
|
|
||||||
snippets:
|
|
||||||
- |
|
|
||||||
RUN set -euo pipefail ; \
|
|
||||||
curl -fsSL https://repository.mullvad.net/rpm/stable/mullvad.repo \
|
|
||||||
-o /etc/yum.repos.d/mullvad.repo ; \
|
|
||||||
curl -fsSL https://pkgs.tailscale.com/stable/fedora/tailscale.repo \
|
|
||||||
-o /etc/yum.repos.d/tailscale.repo ; \
|
|
||||||
rpm-ostree install \
|
|
||||||
sudo \
|
|
||||||
xorg-x11-server-Xwayland \
|
|
||||||
mullvad-browser \
|
|
||||||
tailscale \
|
|
||||||
yggdrasil \
|
|
||||||
zram-generator \
|
|
||||||
jq \
|
|
||||||
vim-enhanced \
|
|
||||||
tmux \
|
|
||||||
htop ; \
|
|
||||||
{ sed -i -e 's|^GRUB_DISTRIBUTOR=.*|GRUB_DISTRIBUTOR="veilor-os"|' /etc/default/grub 2>/dev/null || true ; \
|
|
||||||
bash /usr/share/veilor-os/scripts/kde-theme-apply.sh 2>/dev/null || true ; \
|
|
||||||
bash /usr/share/veilor-os/scripts/30-apply-v03-theme.sh 2>/dev/null || true ; \
|
|
||||||
plymouth-set-default-theme details 2>/dev/null || true ; \
|
|
||||||
chmod +x /usr/share/veilor-os/scripts/*.sh \
|
|
||||||
/usr/share/veilor-os/scripts/selinux/*.sh \
|
|
||||||
/usr/local/bin/veilor-* 2>/dev/null || true ; \
|
|
||||||
fc-cache -f 2>/dev/null || true ; \
|
|
||||||
if [ -f /etc/os-release ]; then \
|
|
||||||
sed -i \
|
|
||||||
-e 's|^NAME=.*|NAME="veilor-os"|' \
|
|
||||||
-e 's|^PRETTY_NAME=.*|PRETTY_NAME="veilor-os 0.7 (atomic)"|' \
|
|
||||||
-e 's|^ID=.*|ID=veilor|' \
|
|
||||||
-e 's|^ID_LIKE=.*|ID_LIKE="fedora kinoite"|' \
|
|
||||||
/etc/os-release || true ; \
|
|
||||||
fi ; \
|
|
||||||
systemctl enable yggdrasil.service 2>/dev/null || true ; \
|
|
||||||
systemctl disable tailscaled.service 2>/dev/null || true ; \
|
|
||||||
systemctl enable veilor-firstboot.service 2>/dev/null || true ; \
|
|
||||||
systemctl enable veilor-modules-lock.service 2>/dev/null || true ; \
|
|
||||||
systemctl enable veilor-postinstall.service 2>/dev/null || true ; \
|
|
||||||
systemctl enable veilor-doctor.timer 2>/dev/null || true ; \
|
|
||||||
} ; \
|
|
||||||
rpm-ostree cleanup -m ; \
|
|
||||||
ostree container commit
|
|
||||||
|
|
||||||
# ── 4. signing config ───────────────────────────────────────────
|
|
||||||
# cosign.pub committed alongside this recipe; cosign.key kept off
|
|
||||||
# repo and provided to CI as Forgejo secret COSIGN_PRIVATE_KEY.
|
|
||||||
# The action exports it to /tmp at build time.
|
|
||||||
- type: signing
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
| Project | Role in veilor-os |
|
| Project | Role in veilor-os |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Fedora 43 KDE | Base OS for v0.5.x kickstart-installed flat builds |
|
| 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 `kinoite-main-hardened` |
|
| [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) |
|
| 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 |
|
| Bluefin / Bazzite (uBlue) | Reference for BlueBuild recipe shape and OCI publishing pattern |
|
||||||
| Tails | Reference for live-only install model — explicitly **not** veilor's path |
|
| Tails | Reference for live-only install model — explicitly **not** veilor's path |
|
||||||
|
|
@ -194,7 +194,7 @@ The repo carries more than just an ISO recipe:
|
||||||
| `scripts/selinux/veilor-systemd.te` | Custom SELinux module (targeted policy gap fixes) |
|
| `scripts/selinux/veilor-systemd.te` | Custom SELinux module (targeted policy gap fixes) |
|
||||||
| `scripts/30-apply-v03-theme.sh` | Plymouth + SDDM + Konsole + wallpaper apply |
|
| `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) |
|
| `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 kinoite-main-hardened) |
|
| `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` |
|
| `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/installer/{banner.txt,colors.gum}` | Pure-block VEILOR OS wordmark + branded gum colour palette |
|
||||||
| `assets/branding/` | Logo, wallpapers, plymouth theme assets |
|
| `assets/branding/` | Logo, wallpapers, plymouth theme assets |
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
# Installing veilor-os (v0.7+)
|
|
||||||
|
|
||||||
> v0.7 is the first OCI / atomic release. The kickstart-installed
|
|
||||||
> v0.5.x path still ships as legacy — if you want that flow, see
|
|
||||||
> [INSTALL.md](INSTALL.md). Both paths produce a hardened veilor-os
|
|
||||||
> system; the v0.7 path is what we recommend going forward.
|
|
||||||
|
|
||||||
## What's different from v0.5
|
|
||||||
|
|
||||||
| Topic | v0.5.x (kickstart) | v0.7+ (BlueBuild OCI) |
|
|
||||||
|---|---|---|
|
|
||||||
| Root filesystem | mutable, `/usr` writable | atomic / immutable, layered via `rpm-ostree` |
|
|
||||||
| Updates | `sudo dnf upgrade` | `sudo bootc upgrade` (atomic A/B, instant rollback) |
|
|
||||||
| Adding a package | `sudo dnf install foo` | `sudo rpm-ostree install foo` (layered into next deployment) |
|
|
||||||
| Base hardening | re-derived in our `%post` scripts | inherited from secureblue OCI image |
|
|
||||||
| Build artefact | `~2.7 GB` live ISO | small bootstrap ISO + signed OCI image at registry |
|
|
||||||
|
|
||||||
## Step-by-step
|
|
||||||
|
|
||||||
### 1. Download the bootstrap installer ISO
|
|
||||||
|
|
||||||
The bootstrap ISO is a tiny Anaconda-driven installer. It does
|
|
||||||
nothing more than collect a LUKS passphrase + admin password and
|
|
||||||
then call `ostreecontainer --url=...:43 --transport=registry` to
|
|
||||||
populate `/` from the pre-built signed OCI image.
|
|
||||||
|
|
||||||
Download from the Forgejo release:
|
|
||||||
|
|
||||||
<https://git.s8n.ru/veilor-org/veilor-os/releases/tag/ci-latest>
|
|
||||||
|
|
||||||
Reassemble the chunked ISO if needed (legacy artefact format):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cat veilor-os-*.iso.part-* > veilor-os.iso
|
|
||||||
sha256sum -c veilor-os-*.iso.parts.sha256
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Verify the OCI image signature (optional, recommended)
|
|
||||||
|
|
||||||
The OCI image is cosign-signed at build time. If you have `cosign`
|
|
||||||
installed:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cosign verify --key cosign.pub git.s8n.ru/veilor-org/veilor-os:43
|
|
||||||
```
|
|
||||||
|
|
||||||
The public key `cosign.pub` ships with the bootstrap ISO and is also
|
|
||||||
on the Forgejo release page.
|
|
||||||
|
|
||||||
### 3. Flash to USB
|
|
||||||
|
|
||||||
Replace `/dev/sdX` with your USB device — triple-check the path.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo dd if=veilor-os.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
|
||||||
sync
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Boot from USB
|
|
||||||
|
|
||||||
Pick **Install veilor-os** from the boot menu. Anaconda starts and
|
|
||||||
asks two things, no more:
|
|
||||||
|
|
||||||
- **LUKS passphrase** for the encrypted root
|
|
||||||
- **admin password** (≥14 chars, mixed case, digit, symbol)
|
|
||||||
|
|
||||||
Anaconda then runs the `ostreecontainer` directive — pulls the
|
|
||||||
signed OCI image, writes it to disk, configures bootloader.
|
|
||||||
|
|
||||||
### 5. Reboot, remove USB
|
|
||||||
|
|
||||||
The first boot lands on SDDM with `admin` pre-filled. Log in.
|
|
||||||
|
|
||||||
### 6. First-login TUI
|
|
||||||
|
|
||||||
`veilor-postinstall` runs once, asks for the small set of things we
|
|
||||||
defer from install time:
|
|
||||||
|
|
||||||
- Keyboard / locale (defaults are fine for most operators)
|
|
||||||
- Hostname (default `veilor`)
|
|
||||||
- GPU drivers (NVIDIA layered via `rpm-ostree install`; mesa = no-op)
|
|
||||||
- Package presets (`dev` / `media` / `homelab`, all opt-in)
|
|
||||||
- Bluetooth (opt-in)
|
|
||||||
- USBGuard snapshot (plug in trusted devices first)
|
|
||||||
- `veilor-doctor` first run
|
|
||||||
|
|
||||||
Each step is skippable. The TUI writes a marker file and disables
|
|
||||||
itself; it never runs again.
|
|
||||||
|
|
||||||
If you need to re-run it: `sudo veilor-postinstall --force`.
|
|
||||||
|
|
||||||
### 7. Day-to-day
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# update (atomic, A/B, instant rollback)
|
|
||||||
sudo veilor-update
|
|
||||||
|
|
||||||
# layer a package (takes effect after reboot)
|
|
||||||
sudo rpm-ostree install foo
|
|
||||||
|
|
||||||
# remove a layered package
|
|
||||||
sudo rpm-ostree uninstall foo
|
|
||||||
|
|
||||||
# health check + drift report
|
|
||||||
veilor-doctor
|
|
||||||
|
|
||||||
# rollback to previous deployment
|
|
||||||
sudo bootc rollback
|
|
||||||
|
|
||||||
# inspect current and staged deployments
|
|
||||||
bootc status
|
|
||||||
```
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
| Symptom | Try |
|
|
||||||
|---|---|
|
|
||||||
| `veilor-update` says "no rollback target" | First boot — bootc only has rollback after the first successful upgrade. Normal. |
|
|
||||||
| Network down inside Anaconda | Bootstrap ISO uses NetworkManager defaults; plug in ethernet for the first install. WiFi support post-first-boot. |
|
|
||||||
| `rpm-ostree install foo` fails | Run `bootc status` — if a staged deployment exists, reboot first, then re-try. rpm-ostree won't layer onto a staged tree. |
|
|
||||||
| First-login TUI didn't appear | Marker check: `ls /var/lib/veilor/postinstall-complete`. If present, run `sudo veilor-postinstall --force`. |
|
|
||||||
| GPU is black after NVIDIA layer + reboot | `bootc rollback` and try mesa first; check `journalctl -b -1 -u sddm` from the previous boot. |
|
|
||||||
|
|
||||||
### Where the OCI image comes from
|
|
||||||
|
|
||||||
The image is built by `.github/workflows/build-bluebuild.yml` on the
|
|
||||||
self-hosted Forgejo runner (label `nullstone`). Build inputs:
|
|
||||||
|
|
||||||
- Base: `ghcr.io/secureblue/kinoite-main-hardened`
|
|
||||||
- Recipe: [`bluebuild/recipe.yml`](../bluebuild/recipe.yml)
|
|
||||||
- Veilor overlay: stamped via BlueBuild `type: files` modules
|
|
||||||
- Layered RPMs: `sudo`, `xorg-x11-server-Xwayland`, `mullvad-browser`,
|
|
||||||
`tailscale`, `yggdrasil`
|
|
||||||
- Output: `git.s8n.ru/veilor-org/veilor-os:{43,latest}`
|
|
||||||
|
|
||||||
The build is cosign-signed (key-pair on Forgejo, keyless on GitHub
|
|
||||||
parallel mirror). See [`bluebuild/README.md`](../bluebuild/README.md)
|
|
||||||
for the recipe walk-through.
|
|
||||||
|
|
@ -9,22 +9,6 @@ For the historical record of what landed in each release, see
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Status snapshot — 2026-05-08
|
|
||||||
|
|
||||||
| Milestone | State | Notes |
|
|
||||||
|-----------|-------|-------|
|
|
||||||
| v0.2.x — green ISO + base hardening | DONE | shipped 2026-05-01 (`v0.2.5`) |
|
|
||||||
| v0.3 — UX polish (Plymouth/SDDM/Konsole) | parked | rolls into v0.7 overlay |
|
|
||||||
| v0.4 — distribution + signing | not started | cosign keypair already in v0.7 CI |
|
|
||||||
| v0.5 — hardening tier 2 | DONE (kickstart line) | tagged `v0.5.0` 2026-05-06 — final kickstart-path release |
|
|
||||||
| v0.6 — ergonomics | CANCELLED 2026-05-06 | folded into v0.7 |
|
|
||||||
| v0.7 — BlueBuild OCI mainline | IN FLIGHT — blocked on green CI run | ~13 CI plumbing fixes landed; OCI artifact + installer ISO pending first green build |
|
|
||||||
| v0.7 — installer-ISO tooling pivot | DONE (tooling) | livemedia-creator → bootc-image-builder; build pending OCI |
|
|
||||||
| v0.7 — USB install-log persistence | TODO | default ON until v1.0; see "Installer logs" item below |
|
|
||||||
| v1.0 — production | not started | multi-arch, LTS, recovery ISO, TPM2 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚡ STRATEGY PIVOT — 2026-05-06
|
## ⚡ STRATEGY PIVOT — 2026-05-06
|
||||||
|
|
||||||
**Decision: skip v0.6 kickstart polish. Pivot directly to v0.7
|
**Decision: skip v0.6 kickstart polish. Pivot directly to v0.7
|
||||||
|
|
@ -43,12 +27,10 @@ Reasons:
|
||||||
`veilor-update`) translate cleanly to v0.7: `bootc upgrade` replaces
|
`veilor-update`) translate cleanly to v0.7: `bootc upgrade` replaces
|
||||||
`dnf upgrade`. Move them into v0.7 scope.
|
`dnf upgrade`. Move them into v0.7 scope.
|
||||||
|
|
||||||
**v0.5.0 is the final kickstart-path release.** Tagged on 2026-05-06,
|
**v0.5.0 is the final kickstart-path release.** Tag, freeze, ship as
|
||||||
shipped as proof-of-work / portfolio anchor. **v0.6 cancelled as a
|
proof-of-work / portfolio anchor. **v0.6 cancelled as a milestone.**
|
||||||
milestone.**
|
|
||||||
|
|
||||||
Active focus: `v0.7-bluebuild-spike` branch — first green CI run is
|
Active focus: `v0.7-bluebuild-spike` branch.
|
||||||
the gating blocker for everything downstream.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -118,31 +100,20 @@ failures before greening.
|
||||||
(`/etc/kernel/cmdline` + `/etc/default/grub` + grubby) plus explicit
|
(`/etc/kernel/cmdline` + `/etc/default/grub` + grubby) plus explicit
|
||||||
`kernel-install add`.
|
`kernel-install add`.
|
||||||
|
|
||||||
## v0.5.0 — final kickstart release (DONE 2026-05-06)
|
## v0.5.32 — next ship (active)
|
||||||
|
|
||||||
Tagged `v0.5.0` on 2026-05-06 as the final kickstart-path release.
|
Outstanding from the grind, immediate priority for the next tag:
|
||||||
The v0.5.27→v0.5.31 install grind closed out via v0.5.32-rc, and the
|
|
||||||
9-agent verification wave bundle landed before the freeze.
|
|
||||||
|
|
||||||
Shipped:
|
- **End-to-end VM green run** — v0.5.31 lands the kernel-cmdline fix
|
||||||
- ~2.7 GB live ISO via Forgejo CI on nullstone (EFI + BIOS bootable)
|
but no full hybrid-VM pass has signed it off. Run the procedure in
|
||||||
- `ci-latest` artifact at
|
`test/TESTING.md` to install + reboot + login, file the report in
|
||||||
`git.s8n.ru/veilor-org/veilor-os/releases/tag/ci-latest`
|
`test/test-runs/`, then tag.
|
||||||
- gum TUI installer wrapping Anaconda; LUKS2 argon2id + btrfs
|
- **Real-hardware run on the spare laptop** — VM is necessary not
|
||||||
- Full hardening overlay: SELinux enforcing, USBGuard default-block,
|
sufficient. Friend's laptop is mate's-test, spare is ours. KMS,
|
||||||
fail2ban + auditd, firewalld drop, NTS chrony, DoT
|
fbcon, USB controller, real-firmware Secure Boot only show up here.
|
||||||
- 3-mode `veilor-power`, KDE black theme, Fira Code, branded
|
|
||||||
os-release / GRUB / plymouth
|
|
||||||
|
|
||||||
Carry-overs into v0.7 (NOT shipped in v0.5.0):
|
|
||||||
|
|
||||||
- **Real-hardware run on the spare laptop** — VM-only signoff. KMS,
|
|
||||||
fbcon, USB controller, real-firmware Secure Boot still need
|
|
||||||
validation on the spare or the friend's laptop.
|
|
||||||
- **gum input render glitch** — duplicate "Install", stray T in
|
- **gum input render glitch** — duplicate "Install", stray T in
|
||||||
password fields on linux fbcon. Replace `gum input --password` with
|
password fields on linux fbcon. Replace `gum input --password` with
|
||||||
bash `read -srp`; cosmetic only but visible on every install.
|
bash `read -srp`; cosmetic only but visible on every install.
|
||||||
Carries to v0.7 installer ISO, which inherits the gum TUI.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -273,35 +244,15 @@ distro from a kickstart.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.7 — BlueBuild OCI mainline (IN FLIGHT — blocked on green CI run, 2026-05-08)
|
## v0.7 — BlueBuild OCI mainline (ACTIVE — primary focus 2026-05-06+)
|
||||||
|
|
||||||
This was originally planned as "public flex + bootc spike". Post-pivot,
|
This was originally planned as "public flex + bootc spike". Post-pivot,
|
||||||
v0.7 is now the **primary active milestone** — it absorbs all v0.6
|
v0.7 is now the **primary active milestone** — it absorbs all v0.6
|
||||||
ergonomic work and becomes the next ship target.
|
ergonomic work and becomes the next ship target.
|
||||||
|
|
||||||
### Status as of 2026-05-08
|
|
||||||
|
|
||||||
- **CI plumbing:** ~13 fixes landed on `v0.7-bluebuild-spike` to make
|
|
||||||
the BlueBuild build run on the self-hosted Forgejo runner. See
|
|
||||||
`CHANGELOG.md` `[Unreleased]` for the full breakdown.
|
|
||||||
- **First green build:** **NOT YET.** Blocking everything downstream
|
|
||||||
(OCI artifact publish, installer ISO build, real-hardware install
|
|
||||||
test, public flex items).
|
|
||||||
- **Installer ISO tooling pivot:** **DONE** — livemedia-creator does
|
|
||||||
not support `ostreecontainer`; switched to `bootc-image-builder`.
|
|
||||||
Build itself is pending the first green OCI artifact.
|
|
||||||
- **Build host:** workflow runs on `nullstone` (single self-hosted
|
|
||||||
Forgejo runner v6.4.0, `userns-remap=default`, buildah needs
|
|
||||||
`--userns=host`).
|
|
||||||
- **Base image:** `ghcr.io/secureblue/kinoite-main-hardened` (locked
|
|
||||||
2026-05-08; corrected from earlier draft naming).
|
|
||||||
- **Signing:** cosign keypair (keyless OIDC fails on Forgejo — no
|
|
||||||
Sigstore Fulcio).
|
|
||||||
- **Build timeout:** 60 min → 360 min (cold-runner first pulls).
|
|
||||||
|
|
||||||
Scope:
|
Scope:
|
||||||
- BlueBuild recipe (`bluebuild/recipe.yml`) layering on
|
- BlueBuild recipe (`bluebuild/recipe.yml`) layering on
|
||||||
`ghcr.io/secureblue/kinoite-main-hardened`
|
`ghcr.io/secureblue/securecore-kinoite-hardened-userns`
|
||||||
- `kickstart/install-ostreecontainer.ks` — 10-line kickstart that calls
|
- `kickstart/install-ostreecontainer.ks` — 10-line kickstart that calls
|
||||||
`ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry`
|
`ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry`
|
||||||
and lets Anaconda's LUKS UX drive the install
|
and lets Anaconda's LUKS UX drive the install
|
||||||
|
|
@ -313,21 +264,6 @@ Scope:
|
||||||
- `veilor-update` rewritten on `bootc upgrade` (was `dnf upgrade`)
|
- `veilor-update` rewritten on `bootc upgrade` (was `dnf upgrade`)
|
||||||
- Forgejo registry as primary OCI publish target; GHCR mirror optional
|
- Forgejo registry as primary OCI publish target; GHCR mirror optional
|
||||||
- cosign key-pair signing of OCI image (replaces broken keyless flow)
|
- cosign key-pair signing of OCI image (replaces broken keyless flow)
|
||||||
- **Installer logs persisted to USB stick by default** (debug mode —
|
|
||||||
TODO, in-flight in a separate agent thread): the bootstrap ISO
|
|
||||||
writes `/var/log/anaconda/*` + the resolved kickstart +
|
|
||||||
ostreecontainer pull log + dmesg back onto the USB install medium
|
|
||||||
(mounted rw at `/run/install/repo` during install) into a
|
|
||||||
`veilor-install-logs/<timestamp>/` folder. Toggleable via kernel
|
|
||||||
cmdline `veilor.install_logs=on|off`; **default ON through v0.7,
|
|
||||||
v0.8, v0.9; flips OFF for v1.0 final release**. Why: any failed
|
|
||||||
install, the operator boots back to a working OS, plugs the USB,
|
|
||||||
reads the logs offline — no need to take screenshots of dracut on a
|
|
||||||
bricked machine. Implementation: `%post --nochroot` block in
|
|
||||||
`kickstart/install-ostreecontainer.ks` that detects the install
|
|
||||||
medium via `/run/install/repo` rw remount, copies the log set,
|
|
||||||
syncs, then unmounts. If the medium is read-only (DVD), skip
|
|
||||||
silently with a `journalctl` warning.
|
|
||||||
|
|
||||||
Public-flex items kept from original v0.7 entry:
|
Public-flex items kept from original v0.7 entry:
|
||||||
|
|
||||||
|
|
@ -356,7 +292,7 @@ spike on `quay.io/fedora/fedora-bootc:43`. Research on 2026-05-05
|
||||||
`docs/research/2026-05-05-agent-wave/`), then a parent-operator
|
`docs/research/2026-05-05-agent-wave/`), then a parent-operator
|
||||||
refinement same day, locked the path: **layer veilor's branding +
|
refinement same day, locked the path: **layer veilor's branding +
|
||||||
threat model + UX on top of secureblue's already-shipping
|
threat model + UX on top of secureblue's already-shipping
|
||||||
`kinoite-main-hardened` OCI image** via a BlueBuild
|
`securecore-kinoite-hardened-userns` OCI image** via a BlueBuild
|
||||||
recipe, and install it directly during the Anaconda pass via the
|
recipe, and install it directly during the Anaconda pass via the
|
||||||
`ostreecontainer` kickstart directive (no first-boot rebase).
|
`ostreecontainer` kickstart directive (no first-boot rebase).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Locked at: **v0.5.31 → v0.7 spike → v1.0**
|
||||||
works).
|
works).
|
||||||
- Anaconda's `ostreecontainer` directive populates the root filesystem
|
- Anaconda's `ostreecontainer` directive populates the root filesystem
|
||||||
directly from a **veilor-os OCI image** (built via BlueBuild on top
|
directly from a **veilor-os OCI image** (built via BlueBuild on top
|
||||||
of secureblue's `kinoite-main-hardened`) **during the
|
of secureblue's `securecore-kinoite-hardened-userns`) **during the
|
||||||
install pass — no first-boot rebase, no mutable→atomic transition**.
|
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.
|
||||||
|
|
@ -236,7 +236,7 @@ distro: **honest, scoped, public threat model**.
|
||||||
The Containerfile-from-scratch spike plan (Agent 3 of 2026-05-05
|
The Containerfile-from-scratch spike plan (Agent 3 of 2026-05-05
|
||||||
wave) is **superseded** by this hybrid: don't build a Containerfile
|
wave) is **superseded** by this hybrid: don't build a Containerfile
|
||||||
from scratch on `fedora-bootc:43`. Instead, write a BlueBuild recipe
|
from scratch on `fedora-bootc:43`. Instead, write a BlueBuild recipe
|
||||||
on `kinoite-main-hardened`. With `ostreecontainer`
|
on `securecore-kinoite-hardened-userns`. With `ostreecontainer`
|
||||||
swap, spike compresses 1 week → 1 day.
|
swap, spike compresses 1 week → 1 day.
|
||||||
|
|
||||||
## Next concrete steps
|
## Next concrete steps
|
||||||
|
|
@ -254,7 +254,7 @@ in the v0.7 spike branch only.
|
||||||
### v0.7-spike (1 day, separate branch)
|
### 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/kinoite-main-hardened: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).
|
||||||
|
|
@ -334,29 +334,3 @@ dir.
|
||||||
- Yggdrasil: <https://github.com/yggdrasil-network/yggdrasil-go>
|
- Yggdrasil: <https://github.com/yggdrasil-network/yggdrasil-go>
|
||||||
- Reticulum manual: <https://reticulum.network/manual/>
|
- Reticulum manual: <https://reticulum.network/manual/>
|
||||||
- Iroh blobs design: <https://github.com/n0-computer/iroh-blobs/blob/main/DESIGN.md>
|
- Iroh blobs design: <https://github.com/n0-computer/iroh-blobs/blob/main/DESIGN.md>
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PIVOT EXECUTION — 2026-05-06
|
|
||||||
|
|
||||||
The hybrid strategy locked at v0.5 is now in execution.
|
|
||||||
|
|
||||||
- **v0.5.0 shipped** as the proof-of-work / portfolio release of the
|
|
||||||
kickstart-flat path. Self-hosted Forgejo CI green-built a 2.7 GB
|
|
||||||
ISO; tag pushed; download lives at the ci-latest release.
|
|
||||||
- **v0.6 milestone cancelled.** Continuing to debug
|
|
||||||
`livecd-creator + anaconda` quirks for v0.6 polish would be sunk-
|
|
||||||
cost work on tooling we retire at v1.0. Original v0.6 plan kept in
|
|
||||||
ROADMAP.md as historical reference.
|
|
||||||
- **v0.7 BlueBuild OCI is the active mainline.** The
|
|
||||||
`v0.7-bluebuild-spike` branch carries the BlueBuild recipe layered
|
|
||||||
on `ghcr.io/secureblue/kinoite-main-hardened`, the
|
|
||||||
`ostreecontainer` kickstart bootstrap, and the new `bootc upgrade`-
|
|
||||||
driven update channel.
|
|
||||||
- **v0.6 ergonomic CLIs ported, not rewritten.** `veilor-update`
|
|
||||||
rewrites onto `bootc upgrade`; `veilor-postinstall` becomes the
|
|
||||||
first-login TUI on the atomic system; `veilor-doctor` learns
|
|
||||||
`bootc status --json` while keeping the legacy dnf path for v0.5.x.
|
|
||||||
- **v1.0 retires the kickstart entirely.** Only `kickstart/install-
|
|
||||||
ostreecontainer.ks` (10 lines) ships forward — bootstrap installer
|
|
||||||
for ostreecontainer pulls.
|
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
# veilor-os installer kickstart — v0.7 CI build variant
|
|
||||||
#
|
|
||||||
# Derived from kickstart/install-ostreecontainer.ks by stripping all
|
|
||||||
# __PLACEHOLDER__ tokens that the runtime gum TUI substitutes at install
|
|
||||||
# time. Anaconda's interactive TUI handles disk selection, LUKS passphrase,
|
|
||||||
# and user account creation in their place.
|
|
||||||
#
|
|
||||||
# Consumed by livemedia-creator --make-iso to produce
|
|
||||||
# veilor-os-installer-43-*.iso. Do NOT add __PLACEHOLDER__ tokens here —
|
|
||||||
# they cannot be filled at build time. See install-ostreecontainer.ks
|
|
||||||
# for the runtime template the gum TUI fills in.
|
|
||||||
|
|
||||||
# ── Locale / keyboard / time ──
|
|
||||||
keyboard --xlayouts='us'
|
|
||||||
lang en_US.UTF-8
|
|
||||||
timezone Europe/London --utc
|
|
||||||
|
|
||||||
# ── Install mode ──
|
|
||||||
text
|
|
||||||
firstboot --disable
|
|
||||||
eula --agreed
|
|
||||||
selinux --enforcing
|
|
||||||
|
|
||||||
# ── Network ──
|
|
||||||
network --bootproto=dhcp --device=link --activate --hostname=veilor-install
|
|
||||||
firewall --enabled --service=ssh
|
|
||||||
|
|
||||||
# ── Identity ──
|
|
||||||
# rootpw --lock only. No user directive — Anaconda's user spoke handles
|
|
||||||
# admin account creation interactively. Runtime ks substitutes
|
|
||||||
# --password=__ADMIN_PW__ for unattended installs.
|
|
||||||
rootpw --lock
|
|
||||||
|
|
||||||
# ── Disk / partitioning ──
|
|
||||||
# Intentionally absent. Anaconda's disk spoke presents interactive
|
|
||||||
# disk + LUKS + btrfs selection. Runtime ks (gum TUI) provides the
|
|
||||||
# full partition spec at real-install time.
|
|
||||||
|
|
||||||
# ── Packages for the LIVE BOOT ENVIRONMENT ──
|
|
||||||
# These are NOT installed on the target system. They populate the
|
|
||||||
# squashfs that boots Anaconda. The target is populated by
|
|
||||||
# `ostreecontainer` below from the OCI image.
|
|
||||||
%packages
|
|
||||||
@^minimal-environment
|
|
||||||
@core
|
|
||||||
@anaconda-tools
|
|
||||||
anaconda-live
|
|
||||||
anaconda-tui
|
|
||||||
livesys-scripts
|
|
||||||
dracut-live
|
|
||||||
dracut-config-generic
|
|
||||||
kernel
|
|
||||||
kernel-modules
|
|
||||||
kernel-modules-extra
|
|
||||||
glibc-all-langpacks
|
|
||||||
ostree
|
|
||||||
rpm-ostree
|
|
||||||
bootupd
|
|
||||||
grub2-efi-x64
|
|
||||||
grub2-efi-x64-modules
|
|
||||||
grub2-pc
|
|
||||||
grub2-pc-modules
|
|
||||||
grub2-tools
|
|
||||||
grub2-tools-extra
|
|
||||||
shim-x64
|
|
||||||
efibootmgr
|
|
||||||
syslinux
|
|
||||||
isomd5sum
|
|
||||||
xorriso
|
|
||||||
%end
|
|
||||||
|
|
||||||
# ── ostreecontainer: populate / from veilor-os OCI ──
|
|
||||||
ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry
|
|
||||||
|
|
||||||
# ── %post (chroot) ──
|
|
||||||
%post
|
|
||||||
set -uo pipefail
|
|
||||||
echo veilor-install > /etc/hostname
|
|
||||||
chage -d 0 admin 2>/dev/null || true
|
|
||||||
%end
|
|
||||||
|
|
||||||
# ── %post --nochroot — persist install logs to USB (toggle: veilor.install_logs=on|off) ──
|
|
||||||
#
|
|
||||||
# Runs OUTSIDE the target chroot so /tmp/anaconda.log etc. on the live
|
|
||||||
# ramdisk are accessible alongside /mnt/sysroot. Calls the helper that
|
|
||||||
# ships in the veilor-os OCI image overlay; if the helper is missing
|
|
||||||
# (corrupt overlay, stripped image, etc.) we fall back to a minimal
|
|
||||||
# inline copy. NEVER fail the install over log persistence.
|
|
||||||
#
|
|
||||||
# Default: ON until v1.0 final. Disable per-boot:
|
|
||||||
# edit GRUB / press 'e', append: veilor.install_logs=off
|
|
||||||
%post --nochroot --erroronfail=no
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
VEILOR_HELPER="/mnt/sysroot/usr/share/veilor-os/scripts/persist-install-logs.sh"
|
|
||||||
[ -x "$VEILOR_HELPER" ] || VEILOR_HELPER="/mnt/sysimage/usr/share/veilor-os/scripts/persist-install-logs.sh"
|
|
||||||
|
|
||||||
if [ -x "$VEILOR_HELPER" ]; then
|
|
||||||
"$VEILOR_HELPER" || true
|
|
||||||
else
|
|
||||||
# Inline fallback — toggle-aware, backup-only (no USB write attempt).
|
|
||||||
TS="$(date -u +%Y-%m-%dT%H-%M-%SZ)"
|
|
||||||
SR=/mnt/sysroot; [ -d "$SR" ] || SR=/mnt/sysimage
|
|
||||||
DST="${SR}/var/log/veilor-install-logs/${TS}"
|
|
||||||
TOGGLE=on
|
|
||||||
for tok in $(cat /proc/cmdline 2>/dev/null); do
|
|
||||||
case "$tok" in veilor.install_logs=off|veilor.install_logs=0|veilor.install_logs=false|veilor.install_logs=no) TOGGLE=off ;; esac
|
|
||||||
done
|
|
||||||
if [ "$TOGGLE" = "on" ]; then
|
|
||||||
mkdir -p "$DST" 2>/dev/null || true
|
|
||||||
for f in /tmp/anaconda.log /tmp/program.log /tmp/storage.log \
|
|
||||||
/tmp/packaging.log /tmp/syslog /tmp/dnf.log \
|
|
||||||
/tmp/ks.cfg /run/veilor-installer.log; do
|
|
||||||
[ -e "$f" ] && cp -a "$f" "$DST/" 2>/dev/null || true
|
|
||||||
done
|
|
||||||
dmesg > "$DST/dmesg.txt" 2>/dev/null || true
|
|
||||||
journalctl --no-pager -b > "$DST/journalctl-b.txt" 2>/dev/null || true
|
|
||||||
echo "[veilor] inline fallback used — helper missing at $VEILOR_HELPER" \
|
|
||||||
> "$DST/manifest.txt"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
%end
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
# veilor-os install kickstart — v0.7 spike (ostreecontainer path)
|
|
||||||
#
|
|
||||||
# This is the install-time kickstart for the v0.7 hybrid path. The live
|
|
||||||
# ISO boots; the gum TUI collects user answers (disk, LUKS pw, admin pw);
|
|
||||||
# this template gets the answers substituted in and is fed to anaconda.
|
|
||||||
#
|
|
||||||
# Anaconda partitions the disk + creates LUKS + btrfs subvols + mounts
|
|
||||||
# /boot/efi + /boot, then `ostreecontainer` populates `/` directly from
|
|
||||||
# the cosign-signed veilor-os OCI image at `ghcr.io/veilor-org/veilor-os:43`.
|
|
||||||
#
|
|
||||||
# No `%packages` block. No first-boot rebase. No
|
|
||||||
# `veilor-firstboot-rebase.service`. The ostreecontainer install pass is
|
|
||||||
# the entire transition from "Fedora live ISO" to "veilor-os on disk".
|
|
||||||
#
|
|
||||||
# Reference: pykickstart docs ostreecontainer command;
|
|
||||||
# https://docs.fedoraproject.org/en-US/bootc/anaconda-install/
|
|
||||||
|
|
||||||
# ── Locale / keyboard / time ──
|
|
||||||
keyboard --xlayouts='us'
|
|
||||||
lang en_US.UTF-8
|
|
||||||
timezone Europe/London --utc
|
|
||||||
|
|
||||||
# ── Install mode / behaviour ──
|
|
||||||
firstboot --disable
|
|
||||||
eula --agreed
|
|
||||||
# SELinux state inherited from the OCI image; --enforcing is implicit
|
|
||||||
# since secureblue's image ships /etc/selinux/config = enforcing.
|
|
||||||
selinux --enforcing
|
|
||||||
|
|
||||||
# ── Network / hostname ──
|
|
||||||
network --bootproto=dhcp --device=link --activate --hostname=__HOSTNAME__
|
|
||||||
firewall --enabled --service=ssh
|
|
||||||
|
|
||||||
# ── Identity (single LUKS prompt asked at install via gum TUI) ──
|
|
||||||
rootpw --lock
|
|
||||||
user --name=admin --groups=wheel --gecos="veilor admin" --password=__ADMIN_PW__ --plaintext
|
|
||||||
|
|
||||||
# ── Bootloader ──
|
|
||||||
# fbcon=nodefer for laptop KMS handoff (real-hardware audit, agent 9 of
|
|
||||||
# 2026-05-05 wave). rd.luks.options=tries=5,timeout=0 for UX.
|
|
||||||
# rd.luks.uuid is auto-injected by anaconda based on the encrypted
|
|
||||||
# part directive below.
|
|
||||||
#
|
|
||||||
# All other hardening kargs (lockdown=integrity, slab_nomerge, etc.)
|
|
||||||
# come from /usr/lib/bootc/kargs.d/ inside the OCI image — bootc
|
|
||||||
# applies them at install time. We only add what the OCI image can't
|
|
||||||
# know (laptop-specific KMS flag).
|
|
||||||
bootloader --append="fbcon=nodefer"
|
|
||||||
|
|
||||||
# ── Disk: LUKS2 (argon2id) + btrfs subvols ──
|
|
||||||
zerombr
|
|
||||||
clearpart --all --initlabel --drives=__DISK_BASENAME__
|
|
||||||
part /boot/efi --fstype=efi --size=600
|
|
||||||
part /boot --fstype=ext4 --size=1024
|
|
||||||
part btrfs.veilor --grow --encrypted --luks-version=luks2 --pbkdf=argon2id --passphrase=__LUKS_PW__
|
|
||||||
btrfs none --label=veilor btrfs.veilor
|
|
||||||
btrfs / --subvol --name=root LABEL=veilor
|
|
||||||
btrfs /home --subvol --name=home LABEL=veilor
|
|
||||||
|
|
||||||
# ── ostreecontainer: populate / from the veilor-os OCI image ──
|
|
||||||
# `--transport=registry` pulls from ghcr.io directly. Authentication
|
|
||||||
# token can be supplied via /etc/ostree/auth.json baked into the live
|
|
||||||
# rootfs OR via a kickstart `--remote-token` if the registry is private.
|
|
||||||
# At v0.7 spike the OCI image is public, so no auth needed.
|
|
||||||
#
|
|
||||||
# DO NOT migrate to the new `bootc` kickstart command until v1.0 — it
|
|
||||||
# blocks multi-disk and authenticated registries (per parent-operator
|
|
||||||
# handoff 2026-05-05).
|
|
||||||
ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry
|
|
||||||
|
|
||||||
# ── %post (chroot) — minimal; OCI image already has everything ──
|
|
||||||
# What we keep:
|
|
||||||
# - chage -d 0 admin so first SDDM login forces password change
|
|
||||||
# - hostname write (anaconda's --hostname doesn't always survive)
|
|
||||||
# - veilor-firstboot.service is enabled in the OCI image already
|
|
||||||
%post
|
|
||||||
set -uo pipefail
|
|
||||||
echo veilor > /etc/hostname
|
|
||||||
chage -d 0 admin || true
|
|
||||||
%end
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=veilor-doctor — system health + drift check
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/local/bin/veilor-doctor --quiet
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=veilor-doctor weekly drift check
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnCalendar=weekly
|
|
||||||
Persistent=true
|
|
||||||
RandomizedDelaySec=30m
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=veilor-os one-time post-install TUI (first login)
|
|
||||||
After=graphical.target
|
|
||||||
ConditionPathExists=!/var/lib/veilor/postinstall-complete
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/local/bin/veilor-postinstall
|
|
||||||
StandardInput=tty
|
|
||||||
StandardOutput=tty
|
|
||||||
StandardError=journal
|
|
||||||
TTYPath=/dev/tty1
|
|
||||||
TTYReset=yes
|
|
||||||
TTYVHangup=yes
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=graphical.target multi-user.target
|
|
||||||
|
|
@ -147,33 +147,15 @@ PUBLIC_IP=$(curl -s --max-time 3 ifconfig.me 2>/dev/null || echo "")
|
||||||
|| check Network public_ip fail "lookup timed out"
|
|| check Network public_ip fail "lookup timed out"
|
||||||
|
|
||||||
# ── 5. Updates ──────────────────────────────────────────────────────
|
# ── 5. Updates ──────────────────────────────────────────────────────
|
||||||
# v0.7+ atomic — bootc replaces dnf as the update channel. Parse
|
LAST_DNF=$(sudo -n dnf history list 2>/dev/null \
|
||||||
# `bootc status --json` for the booted deployment + staged/cached image
|
|
||||||
# age. Fall back to dnf history if bootc not present (legacy v0.5.x).
|
|
||||||
if have bootc; then
|
|
||||||
BOOTC_JSON=$(sudo -n bootc status --json 2>/dev/null || echo "")
|
|
||||||
if [[ -n $BOOTC_JSON ]] && have jq; then
|
|
||||||
BOOTED_IMG=$(jq -r '.status.booted.image.image.image // "unknown"' <<<"$BOOTC_JSON")
|
|
||||||
BOOTED_DIGEST=$(jq -r '.status.booted.image.imageDigest // ""' <<<"$BOOTC_JSON")
|
|
||||||
check Updates booted_image pass "${BOOTED_IMG}@${BOOTED_DIGEST:0:12}"
|
|
||||||
STAGED=$(jq -r '.status.staged.image.image.image // ""' <<<"$BOOTC_JSON")
|
|
||||||
if [[ -n $STAGED ]]; then
|
|
||||||
check Updates staged_image fail "staged: $STAGED — reboot to apply"
|
|
||||||
else
|
|
||||||
check Updates staged_image pass "no staged update"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
check Updates bootc_state pass "bootc present (jq missing — install for richer detail)"
|
|
||||||
fi
|
|
||||||
elif have dnf; then
|
|
||||||
# Legacy v0.5.x kickstart-installed system.
|
|
||||||
LAST_DNF=$(sudo -n dnf history list 2>/dev/null \
|
|
||||||
| awk 'NR==4 {for(i=4;i<NF;i++)printf "%s ", $i; print $NF; exit}')
|
| awk 'NR==4 {for(i=4;i<NF;i++)printf "%s ", $i; print $NF; exit}')
|
||||||
[[ -n $LAST_DNF ]] && check Updates last_dnf pass "$LAST_DNF" \
|
[[ -n $LAST_DNF ]] && check Updates last_dnf pass "$LAST_DNF" \
|
||||||
|| check Updates last_dnf pass "(unknown — try \`sudo dnf history\`)"
|
|| check Updates last_dnf pass "(unknown — try \`sudo dnf history\`)"
|
||||||
sudo -n dnf check-update -q >/dev/null 2>&1
|
|
||||||
RC=$?
|
# `dnf check-update` exits 100 if updates available, 0 if not.
|
||||||
case $RC in
|
sudo -n dnf check-update -q >/dev/null 2>&1
|
||||||
|
RC=$?
|
||||||
|
case $RC in
|
||||||
0) check Updates pending pass "system up-to-date" ;;
|
0) check Updates pending pass "system up-to-date" ;;
|
||||||
100)
|
100)
|
||||||
AVAIL=$(sudo -n dnf check-update -q 2>/dev/null \
|
AVAIL=$(sudo -n dnf check-update -q 2>/dev/null \
|
||||||
|
|
@ -181,10 +163,7 @@ elif have dnf; then
|
||||||
check Updates pending fail "${AVAIL} update(s) available — run \`veilor-update\`"
|
check Updates pending fail "${AVAIL} update(s) available — run \`veilor-update\`"
|
||||||
;;
|
;;
|
||||||
*) check Updates pending fail "dnf check-update returned $RC (need sudo?)" ;;
|
*) check Updates pending fail "dnf check-update returned $RC (need sudo?)" ;;
|
||||||
esac
|
esac
|
||||||
else
|
|
||||||
check Updates channel fail "neither bootc nor dnf available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 6. veilor services ──────────────────────────────────────────────
|
# ── 6. veilor services ──────────────────────────────────────────────
|
||||||
for unit in veilor-firstboot.service veilor-modules-lock.service; do
|
for unit in veilor-firstboot.service veilor-modules-lock.service; do
|
||||||
|
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
#!/usr/bin/bash
|
|
||||||
# veilor-postinstall — first-login TUI on v0.7+ atomic systems.
|
|
||||||
#
|
|
||||||
# Runs ONCE on first SDDM login via the user-mode systemd unit
|
|
||||||
# `veilor-postinstall.service`. Asks the operator for the small set
|
|
||||||
# of decisions we deliberately defer from install time:
|
|
||||||
# - keyboard / locale
|
|
||||||
# - hostname override
|
|
||||||
# - GPU drivers (NVIDIA layered via rpm-ostree, mesa = no-op)
|
|
||||||
# - package preset (dev / media / homelab — additive, opt-out)
|
|
||||||
# - bluetooth opt-in
|
|
||||||
# - USBGuard policy snapshot
|
|
||||||
# - veilor-doctor first run
|
|
||||||
# Writes /var/lib/veilor/postinstall-complete on success and disables
|
|
||||||
# its own autostart unit. Idempotent: safe to re-run.
|
|
||||||
#
|
|
||||||
# Style: gum if present, plain bash read fallback. No decorative ASCII.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
export TERM="${TERM:-linux}"
|
|
||||||
|
|
||||||
STATE_DIR=/var/lib/veilor
|
|
||||||
DONE_MARKER="$STATE_DIR/postinstall-complete"
|
|
||||||
LOG=/var/log/veilor-postinstall.log
|
|
||||||
|
|
||||||
have() { command -v "$1" >/dev/null 2>&1; }
|
|
||||||
GUM=$(have gum && echo gum || echo "")
|
|
||||||
|
|
||||||
# Always log + tee to stdout for live progress.
|
|
||||||
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
||||||
exec > >(tee -a "$LOG") 2>&1
|
|
||||||
|
|
||||||
if [[ -e $DONE_MARKER && ${1:-} != "--force" ]]; then
|
|
||||||
echo "veilor-postinstall already ran (marker: $DONE_MARKER). Pass --force to re-run."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Wrappers ────────────────────────────────────────────────────────
|
|
||||||
choose() {
|
|
||||||
local header=$1; shift
|
|
||||||
if [[ -n $GUM ]]; then
|
|
||||||
gum choose --header "$header" "$@"
|
|
||||||
else
|
|
||||||
echo
|
|
||||||
echo "$header"
|
|
||||||
local i=1
|
|
||||||
for opt in "$@"; do printf ' %d) %s\n' "$i" "$opt"; ((i++)); done
|
|
||||||
local n
|
|
||||||
read -rp " choice (1-$#): " n
|
|
||||||
[[ $n -ge 1 && $n -le $# ]] || return 1
|
|
||||||
eval "echo \${$n}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ask() {
|
|
||||||
local prompt=$1 default=${2:-}
|
|
||||||
if [[ -n $GUM ]]; then
|
|
||||||
gum input --header "$prompt" --value "$default"
|
|
||||||
else
|
|
||||||
local v
|
|
||||||
read -rp "$prompt [$default] " v
|
|
||||||
echo "${v:-$default}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
confirm() {
|
|
||||||
local prompt=$1
|
|
||||||
if [[ -n $GUM ]]; then
|
|
||||||
gum confirm "$prompt" && return 0 || return 1
|
|
||||||
else
|
|
||||||
read -rp "$prompt [y/N] " y
|
|
||||||
[[ ${y,,} == y* ]]
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
say() {
|
|
||||||
if [[ -n $GUM ]]; then
|
|
||||||
gum style --foreground 212 --bold "$1"
|
|
||||||
else
|
|
||||||
printf '\n=== %s ===\n' "$1"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Need root for several actions; re-exec under sudo if not root.
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
|
||||||
say "veilor-postinstall: sudo required"
|
|
||||||
exec sudo -E bash "$0" "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
say "veilor-postinstall — one-time setup"
|
|
||||||
echo " This runs once. Each step is skippable. Defaults are sane."
|
|
||||||
echo
|
|
||||||
|
|
||||||
# ── 1. Keyboard layout ──────────────────────────────────────────────
|
|
||||||
KB=$(choose "Keyboard layout" us gb de fr es ru "skip") || KB=skip
|
|
||||||
if [[ $KB != skip ]]; then
|
|
||||||
localectl set-keymap "$KB" 2>/dev/null || true
|
|
||||||
echo " [OK] keymap = $KB"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 2. Locale ───────────────────────────────────────────────────────
|
|
||||||
LOC=$(choose "Locale" en_US.UTF-8 en_GB.UTF-8 de_DE.UTF-8 fr_FR.UTF-8 "skip") || LOC=skip
|
|
||||||
if [[ $LOC != skip ]]; then
|
|
||||||
localectl set-locale LANG="$LOC" 2>/dev/null || true
|
|
||||||
echo " [OK] locale = $LOC"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 3. Hostname ─────────────────────────────────────────────────────
|
|
||||||
HN=$(ask "Hostname" "veilor")
|
|
||||||
if [[ -n $HN && $HN != $(hostnamectl --static 2>/dev/null) ]]; then
|
|
||||||
hostnamectl set-hostname "$HN"
|
|
||||||
echo " [OK] hostname = $HN"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 4. GPU drivers ──────────────────────────────────────────────────
|
|
||||||
GPU=$(choose "GPU drivers" "Skip (use mesa defaults)" "NVIDIA proprietary (akmod-nvidia)" "Intel/AMD mesa (no-op)") || GPU=skip
|
|
||||||
case "$GPU" in
|
|
||||||
*NVIDIA*)
|
|
||||||
say "Layering NVIDIA driver — this takes a few minutes"
|
|
||||||
rpm-ostree install --idempotent akmod-nvidia xorg-x11-drv-nvidia-cuda \
|
|
||||||
&& echo " [OK] NVIDIA driver layered (reboot to use)" \
|
|
||||||
|| echo " [WARN] NVIDIA layer failed; check rpm-ostree status"
|
|
||||||
;;
|
|
||||||
*) echo " (skipped GPU layering)" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# ── 5. Package presets (multi-select) ───────────────────────────────
|
|
||||||
say "Package presets — pick any combination (skip = none)"
|
|
||||||
PRESET_DEV="git tmux vim-enhanced htop podman skopeo"
|
|
||||||
PRESET_MEDIA="vlc obs-studio"
|
|
||||||
PRESET_HOMELAB="wireguard-tools jq yq tmux"
|
|
||||||
|
|
||||||
PICKED=()
|
|
||||||
confirm "Install dev preset? ($PRESET_DEV)" && PICKED+=($PRESET_DEV) || true
|
|
||||||
confirm "Install media preset? ($PRESET_MEDIA)" && PICKED+=($PRESET_MEDIA) || true
|
|
||||||
confirm "Install homelab preset? ($PRESET_HOMELAB)" && PICKED+=($PRESET_HOMELAB) || true
|
|
||||||
if (( ${#PICKED[@]} > 0 )); then
|
|
||||||
# de-dupe
|
|
||||||
UNIQ=$(printf '%s\n' "${PICKED[@]}" | sort -u | tr '\n' ' ')
|
|
||||||
say "Layering: $UNIQ"
|
|
||||||
rpm-ostree install --idempotent $UNIQ \
|
|
||||||
&& echo " [OK] preset packages layered (reboot to use)" \
|
|
||||||
|| echo " [WARN] preset layer failed; check rpm-ostree status"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 6. Bluetooth ────────────────────────────────────────────────────
|
|
||||||
if confirm "Enable Bluetooth?"; then
|
|
||||||
systemctl enable --now bluetooth.service 2>/dev/null || true
|
|
||||||
echo " [OK] bluetooth enabled"
|
|
||||||
else
|
|
||||||
echo " (skipped bluetooth)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 7. USBGuard snapshot ────────────────────────────────────────────
|
|
||||||
say "USBGuard policy snapshot"
|
|
||||||
echo " Plug in EVERY USB device you trust right now (keyboard,"
|
|
||||||
echo " mouse, dock, yubikey, etc.) before continuing."
|
|
||||||
if confirm "Snapshot current USB devices into the allowlist?"; then
|
|
||||||
usbguard generate-policy > /etc/usbguard/rules.conf \
|
|
||||||
&& echo " [OK] policy written to /etc/usbguard/rules.conf" \
|
|
||||||
|| echo " [WARN] generate-policy failed"
|
|
||||||
systemctl restart usbguard 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 8. veilor-doctor ────────────────────────────────────────────────
|
|
||||||
if confirm "Run veilor-doctor now?"; then
|
|
||||||
veilor-doctor || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Done ────────────────────────────────────────────────────────────
|
|
||||||
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$DONE_MARKER"
|
|
||||||
say "veilor-postinstall complete"
|
|
||||||
echo " Marker written: $DONE_MARKER"
|
|
||||||
echo " Disabling autostart unit so this never runs again."
|
|
||||||
systemctl --user --global disable veilor-postinstall.service 2>/dev/null || true
|
|
||||||
systemctl disable veilor-postinstall.service 2>/dev/null || true
|
|
||||||
echo
|
|
||||||
echo " If you layered any packages or drivers, reboot to activate."
|
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
# veilor-update — atomic update wrapper for v0.7+ (bootc + rpm-ostree).
|
# veilor-update — system update wrapper.
|
||||||
#
|
# Wraps `dnf upgrade --refresh` + `flatpak update` behind a single command.
|
||||||
# Wraps `bootc upgrade` + flatpak update behind a single command.
|
# User-facing CLI shipped in /usr/local/bin/. v0.6 ergonomic tooling.
|
||||||
# Pre-checks rollback availability, pauses auditd while staging the
|
|
||||||
# new image, prints a clear post-state summary, and offers reboot.
|
|
||||||
#
|
#
|
||||||
# Exit codes:
|
# Exit codes:
|
||||||
# 0 success (with or without pending reboot)
|
# 0 success
|
||||||
# 1 bootc upgrade failed
|
# 1 dnf failed
|
||||||
# 2 flatpak failed (bootc still ran successfully)
|
# 2 flatpak failed (dnf still ran successfully)
|
||||||
# 3 no network
|
# 3 no network
|
||||||
|
#
|
||||||
|
# Uses `gum` for spinner output if present, falls back to plain stdout.
|
||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
|
# ── Helpers ─────────────────────────────────────────────────────────
|
||||||
have() { command -v "$1" >/dev/null 2>&1; }
|
have() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
GUM=$(have gum && echo gum || echo "")
|
GUM=$(have gum && echo gum || echo "")
|
||||||
|
|
||||||
say() {
|
say() {
|
||||||
|
# Print a status line. Coloured if gum present, else plain.
|
||||||
if [[ -n $GUM ]]; then
|
if [[ -n $GUM ]]; then
|
||||||
gum style --foreground 212 --bold "$1"
|
gum style --foreground 212 --bold "$1"
|
||||||
else
|
else
|
||||||
|
|
@ -24,50 +27,46 @@ say() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
confirm() {
|
run_with_spinner() {
|
||||||
local prompt=$1
|
local title=$1; shift
|
||||||
if [[ -n $GUM ]]; then
|
if [[ -n $GUM ]]; then
|
||||||
gum confirm "$prompt"
|
gum spin --spinner dot --title "$title" -- "$@"
|
||||||
else
|
else
|
||||||
read -r -p "$prompt [y/N] " yn
|
echo "[+] $title"
|
||||||
[[ ${yn,,} == y* ]]
|
"$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Pre-flight: network ─────────────────────────────────────────────
|
# ── Pre-flight: network check ───────────────────────────────────────
|
||||||
say "veilor-update: checking network"
|
say "veilor-update: checking network"
|
||||||
if ! ping -c 1 -W 2 1.1.1.1 >/dev/null 2>&1; then
|
if ! ping -c 1 -W 2 mirrors.fedoraproject.org >/dev/null 2>&1; then
|
||||||
echo " No network. Connect and re-run \`veilor-update\`."
|
echo
|
||||||
|
echo " No route to mirrors.fedoraproject.org."
|
||||||
|
echo " Connect to a network and re-run \`veilor-update\`."
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Pre-flight: rollback target available ───────────────────────────
|
# ── Snapshot kernel before upgrade so we can warn about reboot need ─
|
||||||
# bootc has two deployments by design (booted + rollback). If
|
KERNEL_BEFORE=$(uname -r)
|
||||||
# something's wrong we want the user to see it before staging more.
|
|
||||||
if have bootc; then
|
# ── DNF upgrade ─────────────────────────────────────────────────────
|
||||||
say "veilor-update: bootc status"
|
say "veilor-update: refreshing DNF metadata + applying updates"
|
||||||
bootc status || true
|
# Capture upgrade output so we can count packages afterwards. Tee to
|
||||||
else
|
# stdout for live progress; swallow into a tempfile for the count.
|
||||||
echo " bootc not present — this CLI targets v0.7+ atomic systems."
|
LOG=$(mktemp -t veilor-update.XXXXXX)
|
||||||
|
trap 'rm -f "$LOG"' EXIT
|
||||||
|
|
||||||
|
if ! sudo dnf upgrade --refresh -y 2>&1 | tee "$LOG"; then
|
||||||
|
echo
|
||||||
|
echo " dnf upgrade failed. See output above."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Pause auditd while staging ──────────────────────────────────────
|
# ── Count packages updated ──────────────────────────────────────────
|
||||||
# Reduces audit log noise during the heavy fs writes; resume after.
|
# DNF prints "Upgraded: N", "Installed: N", "Removed: N" at end.
|
||||||
AUDIT_PAUSED=0
|
# Sum the upgrade/install lines for the user-visible total.
|
||||||
if systemctl is-active auditd >/dev/null 2>&1; then
|
UPDATED=$(grep -E '^(Upgraded|Installed)\b' "$LOG" 2>/dev/null \
|
||||||
if sudo systemctl stop auditd 2>/dev/null; then
|
| awk -F: '{ gsub(/[^0-9]/,"",$2); s+=$2 } END { print s+0 }')
|
||||||
AUDIT_PAUSED=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
trap '[[ $AUDIT_PAUSED == 1 ]] && sudo systemctl start auditd 2>/dev/null || true' EXIT
|
|
||||||
|
|
||||||
# ── bootc upgrade ───────────────────────────────────────────────────
|
|
||||||
say "veilor-update: bootc upgrade"
|
|
||||||
if ! sudo bootc upgrade; then
|
|
||||||
echo " bootc upgrade failed. See output above."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Flatpak (best-effort) ───────────────────────────────────────────
|
# ── Flatpak (best-effort) ───────────────────────────────────────────
|
||||||
FLATPAK_RC=0
|
FLATPAK_RC=0
|
||||||
|
|
@ -75,20 +74,21 @@ if have flatpak; then
|
||||||
say "veilor-update: updating flatpaks"
|
say "veilor-update: updating flatpaks"
|
||||||
if ! flatpak update -y; then
|
if ! flatpak update -y; then
|
||||||
FLATPAK_RC=2
|
FLATPAK_RC=2
|
||||||
echo " flatpak update failed; continuing."
|
echo " flatpak update failed; continuing anyway."
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo " (flatpak not installed — skipping)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Post-update summary ─────────────────────────────────────────────
|
# ── Post-update: reboot hint if kernel changed ──────────────────────
|
||||||
say "veilor-update: complete"
|
KERNEL_AFTER_LATEST=$(rpm -q kernel --last 2>/dev/null \
|
||||||
bootc status 2>/dev/null | head -20 || true
|
| awk 'NR==1 { sub(/^kernel-/,"",$1); print $1 }')
|
||||||
|
|
||||||
# ── Reboot prompt ───────────────────────────────────────────────────
|
say "veilor-update: complete"
|
||||||
# bootc always writes the new image into the staged deployment; reboot
|
printf ' Packages updated : %s\n' "${UPDATED:-0}"
|
||||||
# is required for it to become the running root.
|
printf ' Running kernel : %s\n' "$KERNEL_BEFORE"
|
||||||
if confirm " Reboot now to activate the new image?"; then
|
if [[ -n ${KERNEL_AFTER_LATEST:-} && $KERNEL_AFTER_LATEST != "$KERNEL_BEFORE" ]]; then
|
||||||
say "veilor-update: rebooting"
|
printf ' Newest kernel : %s (reboot suggested)\n' "$KERNEL_AFTER_LATEST"
|
||||||
sudo systemctl reboot
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit $FLATPAK_RC
|
exit $FLATPAK_RC
|
||||||
|
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# persist-install-logs.sh — copy Anaconda install logs back to the boot USB
|
|
||||||
#
|
|
||||||
# Runs from %post --nochroot near the end of the Anaconda install. At that
|
|
||||||
# point /tmp/*.log on the live ramdisk has the full evidence trail
|
|
||||||
# (anaconda.log, program.log, storage.log, packaging.log, dnf.log,
|
|
||||||
# syslog, etc.) — and is about to be lost forever when the user reboots
|
|
||||||
# into the freshly installed system.
|
|
||||||
#
|
|
||||||
# We:
|
|
||||||
# 1. Honour the kernel cmdline toggle veilor.install_logs=on|off
|
|
||||||
# (default: on, until v1.0 final flips the default to off).
|
|
||||||
# 2. Detect the boot USB device (BOOT=, BOOT_IMAGE=, /run/install/repo,
|
|
||||||
# then /sys/block/*/removable=1 fallback).
|
|
||||||
# 3. Try to remount it rw and copy logs into
|
|
||||||
# /veilor-install-logs/<UTC-ISO8601>/ on the USB.
|
|
||||||
# 4. ALSO copy a backup into /mnt/sysroot/var/log/veilor-install-logs/
|
|
||||||
# so logs survive in the installed system even if the USB is RO,
|
|
||||||
# missing, or write-failed.
|
|
||||||
# 5. NEVER fail the install over this. Every error is logged + ignored.
|
|
||||||
#
|
|
||||||
# Disable at boot: edit GRUB / press 'e', append: veilor.install_logs=off
|
|
||||||
# Disable in kickstart: comment out the call in install-ostreecontainer-installer.ks
|
|
||||||
#
|
|
||||||
# Author: veilor-os agent A2 (2026-05-08)
|
|
||||||
# License: AGPLv3 — same as veilor-os
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
# ── trace ── everything to stderr; Anaconda captures stderr to program.log
|
|
||||||
log() { printf '[persist-install-logs] %s\n' "$*" >&2; }
|
|
||||||
trap 'log "WARN: line $LINENO failed (rc=$?) — continuing"' ERR
|
|
||||||
|
|
||||||
TS="$(date -u +%Y-%m-%dT%H-%M-%SZ)"
|
|
||||||
SYSROOT="${VEILOR_SYSROOT:-/mnt/sysroot}"
|
|
||||||
[ -d "$SYSROOT" ] || SYSROOT="/mnt/sysimage" # legacy Anaconda path
|
|
||||||
|
|
||||||
BACKUP_DIR="${SYSROOT}/var/log/veilor-install-logs/${TS}"
|
|
||||||
mkdir -p "$BACKUP_DIR" 2>/dev/null || true
|
|
||||||
|
|
||||||
# ── 1. toggle ──────────────────────────────────────────────────────────────
|
|
||||||
parse_toggle() {
|
|
||||||
# default ON until v1.0 final
|
|
||||||
local cmdline val
|
|
||||||
cmdline="$(cat /proc/cmdline 2>/dev/null || true)"
|
|
||||||
for tok in $cmdline; do
|
|
||||||
case "$tok" in
|
|
||||||
veilor.install_logs=*) val="${tok#veilor.install_logs=}" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
val="${val:-on}"
|
|
||||||
case "$val" in
|
|
||||||
on|true|1|yes) echo on ;;
|
|
||||||
off|false|0|no) echo off ;;
|
|
||||||
*) log "unknown veilor.install_logs=$val — defaulting to on"; echo on ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
TOGGLE="$(parse_toggle)"
|
|
||||||
if [ "$TOGGLE" = "off" ]; then
|
|
||||||
log "veilor.install_logs=off — log persistence skipped"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
log "veilor.install_logs=on — persisting install logs (ts=${TS})"
|
|
||||||
|
|
||||||
# ── 2. collect log payload into staging dir ───────────────────────────────
|
|
||||||
STAGE="$(mktemp -d -t veilor-install-logs.XXXXXX 2>/dev/null || echo /tmp/veilor-install-logs-stage)"
|
|
||||||
mkdir -p "$STAGE"
|
|
||||||
|
|
||||||
collect() {
|
|
||||||
local src="$1" dst="$2"
|
|
||||||
if [ -e "$src" ]; then
|
|
||||||
cp -a "$src" "$STAGE/$dst" 2>/dev/null || \
|
|
||||||
log "could not copy $src"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Anaconda /tmp logs (live env)
|
|
||||||
for f in anaconda.log program.log storage.log packaging.log syslog \
|
|
||||||
dnf.log dnf.librepo.log dnf.rpm.log dnf.hawkey.log \
|
|
||||||
X.log ifcfg.log lvm.log yum.log; do
|
|
||||||
collect "/tmp/$f" "$f"
|
|
||||||
done
|
|
||||||
# Kickstart-related
|
|
||||||
collect /tmp/ks.cfg ks.cfg
|
|
||||||
collect /tmp/ks-script.log ks-script.log
|
|
||||||
collect /tmp/kickstart_pre.log kickstart_pre.log
|
|
||||||
collect /tmp/kickstart_post.log kickstart_post.log
|
|
||||||
# veilor TUI installer log (live ISO writes this to /run)
|
|
||||||
collect /run/veilor-installer.log veilor-installer.log
|
|
||||||
|
|
||||||
# Runtime evidence
|
|
||||||
{
|
|
||||||
echo "── /proc/cmdline ──"
|
|
||||||
cat /proc/cmdline 2>/dev/null
|
|
||||||
echo
|
|
||||||
echo "── /proc/version ──"
|
|
||||||
cat /proc/version 2>/dev/null
|
|
||||||
echo
|
|
||||||
echo "── /etc/os-release ──"
|
|
||||||
cat /etc/os-release 2>/dev/null
|
|
||||||
echo
|
|
||||||
echo "── timestamp (UTC) ──"
|
|
||||||
date -u
|
|
||||||
} > "$STAGE/system-info.txt" 2>/dev/null || true
|
|
||||||
|
|
||||||
dmesg --ctime > "$STAGE/dmesg.txt" 2>/dev/null || \
|
|
||||||
dmesg > "$STAGE/dmesg.txt" 2>/dev/null || true
|
|
||||||
|
|
||||||
journalctl --no-pager -b > "$STAGE/journalctl-b.txt" 2>/dev/null || true
|
|
||||||
|
|
||||||
lsblk -fJ > "$STAGE/lsblk.json" 2>/dev/null || true
|
|
||||||
blkid > "$STAGE/blkid.txt" 2>/dev/null || true
|
|
||||||
mount > "$STAGE/mount.txt" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Manifest
|
|
||||||
{
|
|
||||||
echo "veilor-os install log bundle"
|
|
||||||
echo "timestamp_utc=${TS}"
|
|
||||||
echo "host_uname=$(uname -a 2>/dev/null)"
|
|
||||||
echo "files:"
|
|
||||||
(cd "$STAGE" && ls -la 2>/dev/null)
|
|
||||||
} > "$STAGE/manifest.txt"
|
|
||||||
|
|
||||||
# Backup copy regardless of USB success
|
|
||||||
cp -a "$STAGE/." "$BACKUP_DIR/" 2>/dev/null && \
|
|
||||||
log "backup written to ${BACKUP_DIR}" || \
|
|
||||||
log "WARN: could not write backup to ${BACKUP_DIR}"
|
|
||||||
|
|
||||||
# ── 3. detect boot USB ─────────────────────────────────────────────────────
|
|
||||||
detect_usb_dev() {
|
|
||||||
local cmdline tok val dev
|
|
||||||
cmdline="$(cat /proc/cmdline 2>/dev/null || true)"
|
|
||||||
|
|
||||||
# 3a) BOOT=LABEL=... or BOOT=UUID=... explicit
|
|
||||||
for tok in $cmdline; do
|
|
||||||
case "$tok" in
|
|
||||||
BOOT=*)
|
|
||||||
val="${tok#BOOT=}"
|
|
||||||
dev="$(findfs "$val" 2>/dev/null || true)"
|
|
||||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# 3b) Anaconda mounts the install medium at /run/install/repo
|
|
||||||
if mountpoint -q /run/install/repo 2>/dev/null; then
|
|
||||||
dev="$(findmnt -no SOURCE /run/install/repo 2>/dev/null || true)"
|
|
||||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
|
||||||
fi
|
|
||||||
if mountpoint -q /run/install/sources/mount-0000-iso 2>/dev/null; then
|
|
||||||
dev="$(findmnt -no SOURCE /run/install/sources/mount-0000-iso 2>/dev/null || true)"
|
|
||||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3c) BOOT_IMAGE=(hdX,Y)/path — extract base device from kernel arg via
|
|
||||||
# /run/initramfs/livedev (dracut-live writes this)
|
|
||||||
if [ -r /run/initramfs/livedev ]; then
|
|
||||||
dev="$(cat /run/initramfs/livedev 2>/dev/null)"
|
|
||||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3d) /sys/block walk for first removable device with mounted partition
|
|
||||||
local d part
|
|
||||||
for d in /sys/block/*/removable; do
|
|
||||||
[ "$(cat "$d" 2>/dev/null)" = "1" ] || continue
|
|
||||||
local base
|
|
||||||
base="$(basename "$(dirname "$d")")"
|
|
||||||
for part in /sys/block/"$base"/"$base"*; do
|
|
||||||
[ -d "$part" ] || continue
|
|
||||||
local pname="/dev/$(basename "$part")"
|
|
||||||
[ -b "$pname" ] && { echo "$pname"; return 0; }
|
|
||||||
done
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
USB_DEV="$(detect_usb_dev || true)"
|
|
||||||
if [ -z "${USB_DEV:-}" ]; then
|
|
||||||
log "could not detect boot USB device — backup-only mode (see ${BACKUP_DIR})"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
log "detected boot USB partition: ${USB_DEV}"
|
|
||||||
|
|
||||||
# Walk to parent disk if we got a partition — we want the data partition not
|
|
||||||
# the ESP. For an Anaconda-spun installer USB the ISO is hybrid: the ISO9660
|
|
||||||
# partition holds the squashfs (RO), and there's usually an ESP. Strategy:
|
|
||||||
# try mounting the partition we got first; if it's RO we accept that and
|
|
||||||
# attempt remount; if remount fails we give up gracefully.
|
|
||||||
|
|
||||||
# ── 4. mount USB and write logs ────────────────────────────────────────────
|
|
||||||
MOUNT_POINT="/run/veilor-install-logs-mount"
|
|
||||||
mkdir -p "$MOUNT_POINT"
|
|
||||||
|
|
||||||
mount_rw() {
|
|
||||||
local dev="$1"
|
|
||||||
if mount "$dev" "$MOUNT_POINT" 2>/dev/null; then
|
|
||||||
# check if rw
|
|
||||||
if touch "$MOUNT_POINT/.veilor-write-test" 2>/dev/null; then
|
|
||||||
rm -f "$MOUNT_POINT/.veilor-write-test"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
# try remount rw
|
|
||||||
if mount -o remount,rw "$MOUNT_POINT" 2>/dev/null && \
|
|
||||||
touch "$MOUNT_POINT/.veilor-write-test" 2>/dev/null; then
|
|
||||||
rm -f "$MOUNT_POINT/.veilor-write-test"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
log "USB mounted RO and remount-rw failed: ${dev}"
|
|
||||||
umount "$MOUNT_POINT" 2>/dev/null || true
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount_rw "$USB_DEV"; then
|
|
||||||
DEST="${MOUNT_POINT}/veilor-install-logs/${TS}"
|
|
||||||
if mkdir -p "$DEST" 2>/dev/null && cp -a "$STAGE/." "$DEST/" 2>/dev/null; then
|
|
||||||
sync
|
|
||||||
log "logs persisted to USB: ${USB_DEV}:/veilor-install-logs/${TS}"
|
|
||||||
else
|
|
||||||
log "WARN: USB mounted rw but write failed — keeping backup at ${BACKUP_DIR}"
|
|
||||||
fi
|
|
||||||
umount "$MOUNT_POINT" 2>/dev/null || true
|
|
||||||
else
|
|
||||||
# Try the parent disk's other partitions (some installer USBs have a
|
|
||||||
# writable data partition separate from the ISO9660 squashfs partition).
|
|
||||||
parent="$(echo "$USB_DEV" | sed -E 's/[0-9]+$//; s/p$//')"
|
|
||||||
if [ -b "$parent" ]; then
|
|
||||||
for cand in "$parent"*[0-9]; do
|
|
||||||
[ -b "$cand" ] || continue
|
|
||||||
[ "$cand" = "$USB_DEV" ] && continue
|
|
||||||
if mount_rw "$cand"; then
|
|
||||||
DEST="${MOUNT_POINT}/veilor-install-logs/${TS}"
|
|
||||||
if mkdir -p "$DEST" 2>/dev/null && cp -a "$STAGE/." "$DEST/" 2>/dev/null; then
|
|
||||||
sync
|
|
||||||
log "logs persisted to USB partition: ${cand}:/veilor-install-logs/${TS}"
|
|
||||||
fi
|
|
||||||
umount "$MOUNT_POINT" 2>/dev/null || true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
log "USB write path unavailable — relying on backup at ${BACKUP_DIR}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rmdir "$MOUNT_POINT" 2>/dev/null || true
|
|
||||||
rm -rf "$STAGE" 2>/dev/null || true
|
|
||||||
exit 0
|
|
||||||
Loading…
Reference in a new issue