cook: two default dishes (phyllomeos, phyllomeos-headless) + guest/experimental tiers; CI builds raw images
build-image / validate (push) Successful in 8s
ci / cook (push) Successful in 10s
build-image / build-image (phyllomeos, 16384) (push) Failing after 1m13s
build-image / build-image (phyllomeos-headless, 8192) (push) Failing after 11m10s

Manifest groups get a tier; only `default` is generated (make all TIER=...,
--tier). Default: phyllomeos (GNOME + virt-manager) and phyllomeos-headless,
Fedora 44, systemd-boot, CPU-agnostic hypervisor ingredient (hypervisor_type:
any). guest tier: guest-server, guest-desktop. experimental: biosboot/grub,
encrypted, rawhide. Generator clears stale recipes/dishes before writing.

build-image.sh: --dish and --tier; deploy.sh: --tier. build-iso.yaml replaced
by build-image.yaml: raw images for both editions on the fedora:host runner
(TMPDIR=/var/tmp, max-parallel 1), attached with the flattened kickstarts to
tagged releases. Live edition no longer built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lukas Greve
2026-09-20 11:35:28 +02:00
co-authored by Claude Sonnet 5
parent 35d8879bcc
commit 67316d9852
11 changed files with 433 additions and 141 deletions
+110
View File
@@ -0,0 +1,110 @@
name: build-image
on:
push:
branches: [main]
tags: ["v*.*.*"]
# Builds raw disk images of the two default-tier editions (see
# cook/recipes_manifest.yaml) with build-image.sh, i.e.
# `livemedia-creator --no-virt --make-disk`. Other tiers (guest, experimental)
# are built on demand with `./build-image.sh --tier <tier>`.
#
# The build job runs on the registered `fedora:host` runner (a Fedora VM, jobs
# run as root), not in a container: --make-disk needs real loop devices and a
# live systemd-udevd. The runner needs lorax-lmc-novirt, qemu-img, make,
# python3-pip, pykickstart and xz installed.
jobs:
validate:
runs-on: fedora
defaults:
run:
shell: bash
container:
image: git.phyllo.me/devops/fedora-runner-image:latest
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Install Python deps
run: pip install -r cook/requirements.txt
- name: Generate, lint, and validate dishes
run: |
cd cook
make all
build-image:
needs: validate
runs-on: fedora
strategy:
fail-fast: false
# One image at a time: each build takes loop devices, RAM and tens of GiB
max-parallel: 1
matrix:
include:
- edition: phyllomeos
root_size: 16384
- edition: phyllomeos-headless
root_size: 8192
defaults:
run:
shell: bash
env:
# /tmp is a small tmpfs on the runner; livemedia-creator writes the whole
# disk image under $TMPDIR
TMPDIR: /var/tmp
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Check runner prerequisites
run: |
missing=0
for tool in livemedia-creator qemu-img make pip xz sudo; do
command -v "$tool" >/dev/null || { echo "missing on runner: $tool"; missing=1; }
done
[ -e /dev/loop-control ] || { echo "missing on runner: /dev/loop-control"; missing=1; }
[ "$missing" = 0 ] || exit 1
- name: Install Python deps
run: pip install -r cook/requirements.txt
- name: Build raw image
run: |
# Editions are named "<edition>_<values...>.cfg"; the trailing "_" keeps
# "phyllomeos" from matching "phyllomeos-headless".
(cd cook && make all)
dish="$(basename "$(ls cook/dishes/${{ matrix.edition }}_*.cfg)" .cfg)"
echo "DISH=$dish" >> "$GITHUB_ENV"
./build-image.sh --dish "$dish" --root-size ${{ matrix.root_size }}
xz -T0 "build/$dish.img"
- name: Upload image and kickstart as artifact
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.edition }}
path: |
build/${{ env.DISH }}.img.xz
cook/dishes/${{ env.DISH }}.cfg
if-no-files-found: error
- name: Publish image and kickstart as release
if: startsWith(github.ref, 'refs/tags/')
uses: https://git.phyllo.me/devops/action-gh-release@v2
with:
files: |
build/${{ env.DISH }}.img.xz
cook/dishes/${{ env.DISH }}.cfg
draft: false
prerelease: false
- name: Clean up build products
if: always()
run: rm -rf build /var/tmp/lmc-result.*
-82
View File
@@ -1,82 +0,0 @@
name: build-iso
on:
push:
branches: [main]
tags: ["v*.*.*"]
env:
FEDORA_VERSION: "44"
KICKSTART_FILE: live-desktop_44_livefs_grub_gnome_guest-agents_live
jobs:
validate:
runs-on: fedora
defaults:
run:
shell: bash
container:
image: git.phyllo.me/devops/fedora-runner-image:latest
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Install Python deps
run: pip install -r cook/requirements.txt
- name: Generate, lint, and validate dishes
run: |
cd cook
make all
build-iso:
needs: validate
runs-on: fedora
defaults:
run:
shell: bash
container:
image: git.phyllo.me/devops/fedora-runner-image:latest
privileged: true
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Install Python deps
run: pip install -r cook/requirements.txt
- name: Install build tools
run: dnf -y install lorax-lmc-novirt pykickstart livecd-tools
- name: Generate dishes
run: |
cd cook
make all
- name: Build ISO with livemedia-creator
run: |
for i in $(seq 0 63); do mknod -m 0660 /dev/loop$i b 7 $i 2>/dev/null || true; done
mknod -m 0660 /dev/loop-control b 7 0 2>/dev/null || true
ks="$(mktemp --suffix=.cfg)"
grep -v -E '^(text|cmdline|graphical)([[:space:]]|$)' "cook/dishes/${{ env.KICKSTART_FILE }}.cfg" > "$ks"
livemedia-creator --ks "$ks" --no-virt --resultdir build --project phyllomeos --make-iso --volid phyllomeos --iso-only --iso-name ${{ env.KICKSTART_FILE }}-${{ env.FEDORA_VERSION }}.iso --releasever ${{ env.FEDORA_VERSION }} --macboot
- name: Upload ISO as artifact
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v3
with:
name: ${{ env.KICKSTART_FILE }}-${{ env.FEDORA_VERSION }}.iso
path: build/${{ env.KICKSTART_FILE }}-${{ env.FEDORA_VERSION }}.iso
if-no-files-found: error
- name: Publish ISO as release
if: startsWith(github.ref, 'refs/tags/')
uses: https://git.phyllo.me/devops/action-gh-release@v2
with:
files: build/${{ env.KICKSTART_FILE }}-${{ env.FEDORA_VERSION }}.iso
draft: false
prerelease: false
+28 -8
View File
@@ -10,7 +10,7 @@ Provided that some dependencies are met (`libvirt` is running on your computer,
chmod +x deploy.sh chmod +x deploy.sh
``` ```
- Execute it and pick a dish, e.g. `desktop_44_standard_grub_gnome_guest-agents`, when prompted: - Execute it and pick a dish when prompted (by default the two Phyllome OS editions; see [Editions and tiers](#editions-and-tiers)). The example below is from an earlier, larger matrix:
``` ```
./deploy.sh ./deploy.sh
@@ -35,9 +35,28 @@ Allocating 'desktop_44_standard_grub_gnome_guest-agents.img'
Creating domain... Creating domain...
``` ```
`deploy.sh` regenerates the `recipes` and `dishes` (they are build products) before deploying. `deploy.sh` regenerates the `recipes` and `dishes` (they are build products) before deploying. Pass `--tier guest` to deploy a plain Fedora server or desktop VM on a Phyllome OS host instead.
After a successfull installation, the virtual machine will shutdown and be ready to use when powered on again. After a successfull installation, the virtual machine will shutdown and be ready to use when powered on again.
## Editions and tiers
The repo ships a recipe to deploy Phyllome OS on a target host, not a general-purpose kickstart collection. Only two dishes are generated by default (Fedora 44, UEFI, systemd-boot, both with hardware support and guest agents so they also run as VMs):
| Dish (prefix) | What it is |
|---|---|
| `phyllomeos` | Phyllome OS with a GUI (GNOME + virt-manager) |
| `phyllomeos-headless` | Headless Phyllome OS |
Every group in `cook/recipes_manifest.yaml` belongs to a *tier*, and only the requested tier is generated:
| Tier | Contents | Build with |
|---|---|---|
| `default` | the two editions above | `make all` |
| `guest` | `guest-server`, `guest-desktop`: Fedora VMs to deploy on a Phyllome OS host | `make all TIER=guest` |
| `experimental` | single hand-written variants to try new dishes (BIOS/GRUB, encrypted root, rawhide) | `make all TIER=experimental` |
`TIER=all` (or `TIER="default guest"`) combines tiers; `deploy.sh` and `build-image.sh` take `--tier`. Lint always covers every tier, so inactive tiers cannot rot. To test a new dish, add one variant (no list-valued matrices) to an `experimental` group.
## Building a disk image (no libvirt/virt-install) ## Building a disk image (no libvirt/virt-install)
`deploy.sh` drives a live `virt-install`/libvirt session. If you just want a `deploy.sh` drives a live `virt-install`/libvirt session. If you just want a
@@ -48,11 +67,11 @@ use `build-image.sh` instead. It runs `livemedia-creator --no-virt
bootable ISO: bootable ISO:
``` ```
./build-image.sh [--as qcow2] [--root-size MiB] [--extra-ks FILE] ./build-image.sh [--dish NAME] [--tier TIER] [--as qcow2] [--root-size MiB] [--extra-ks FILE]
``` ```
It picks a dish the same way `deploy.sh` does and writes the result under It picks a dish the same way `deploy.sh` does (or takes `--dish NAME`) and writes the result under
`./build/`. Requires `lorax-lmc-novirt` (heavier than `deploy.sh`'s plain `./build/`. The `build-image` CI workflow runs it for both default editions on every push to `main`, and on `v*.*.*` tags it attaches the compressed raw images (`.img.xz`) and the flattened kickstart files to the release. Requires `lorax-lmc-novirt` (heavier than `deploy.sh`'s plain
QEMU/libvirt prerequisites, not installed by QEMU/libvirt prerequisites, not installed by
`deploy/install-prerequisites-on-linux.sh`) and root — the `--no-virt` disk `deploy/install-prerequisites-on-linux.sh`) and root — the `--no-virt` disk
install needs real loop-device access (`/dev/loop-control`), which a install needs real loop-device access (`/dev/loop-control`), which a
@@ -123,7 +142,7 @@ cd cook && make all
Alternatively, for a bespoke dish not part of the matrix, append the ingredient directly to an existing recipe and re-flatten it: Alternatively, for a bespoke dish not part of the matrix, append the ingredient directly to an existing recipe and re-flatten it:
``` ```
echo "%include ../ingredients/extra-luanti.ks # Sandbox video game engine" >> cook/recipes/desktop_44_standard_grub_gnome.cfg echo "%include ../ingredients/extra-luanti.ks # Sandbox video game engine" >> cook/recipes/<recipe>.cfg
cd cook && make generate cd cook && make generate
``` ```
@@ -135,12 +154,13 @@ cd cook && make generate
cd cook && make inventory cd cook && make inventory
``` ```
- Define a new edition (a group named e.g. `server`) in `cook/recipes_manifest.yaml` with the desired variants; the cartesian product of its list values generates one dish per combination. - Define a new group in `cook/recipes_manifest.yaml` with a `tier` (use `experimental` for anything that should not be built by default); the cartesian product of its list values generates one dish per combination, so keep it to a single variant.
### Useful targets (in `cook/`) ### Useful targets (in `cook/`)
``` ```
make all # generate recipes + dishes, lint, and validate (default) make all # generate the default tier's dishes, lint, and validate
make all TIER=guest # ... or the guest / experimental / all tiers
make lint # check the manifest/templates without writing files make lint # check the manifest/templates without writing files
make validate # validate existing dishes make validate # validate existing dishes
make inventory # print the ingredient catalog derived from the templates make inventory # print the ingredient catalog derived from the templates
+30 -7
View File
@@ -6,7 +6,12 @@
# (e.g. `virtpull local ./build/<dish>.img --wonder-vm NAME`) instead of a # (e.g. `virtpull local ./build/<dish>.img --wonder-vm NAME`) instead of a
# bootable ISO. # bootable ISO.
# #
# Usage: ./build-image.sh [--as qcow2] [--root-size MiB] [--extra-ks FILE] # Usage: ./build-image.sh [--dish NAME] [--tier TIER] [--as qcow2]
# [--root-size MiB] [--extra-ks FILE]
#
# --dish skips the interactive picker (used by CI); --tier selects which
# manifest tier of dishes to generate (default: default; see
# cook/recipes_manifest.yaml).
# #
# Requires: lorax-lmc-novirt (livemedia-creator), qemu-img, and root (the # Requires: lorax-lmc-novirt (livemedia-creator), qemu-img, and root (the
# --no-virt disk install needs real loop-device access via # --no-virt disk install needs real loop-device access via
@@ -31,10 +36,20 @@
set -euo pipefail set -euo pipefail
AS_FORMAT="" AS_FORMAT=""
DISH=""
TIER="${TIER:-default}"
ROOT_SIZE=8192 ROOT_SIZE=8192
EXTRA_KS="" EXTRA_KS=""
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--dish)
DISH="$2"
shift 2
;;
--tier)
TIER="$2"
shift 2
;;
--as) --as)
AS_FORMAT="$2" AS_FORMAT="$2"
shift 2 shift 2
@@ -49,7 +64,7 @@ while [[ $# -gt 0 ]]; do
;; ;;
*) *)
echo "Unknown argument: $1" echo "Unknown argument: $1"
echo "Usage: $0 [--as qcow2] [--root-size MiB] [--extra-ks FILE]" echo "Usage: $0 [--dish NAME] [--tier TIER] [--as qcow2] [--root-size MiB] [--extra-ks FILE]"
exit 1 exit 1
;; ;;
esac esac
@@ -81,15 +96,23 @@ if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
fi fi
# Regenerate recipes and dishes before building (they are build products) # Regenerate recipes and dishes before building (they are build products)
if ! (cd cook && make all); then if ! (cd cook && make all TIER="$TIER"); then
echo "Failed to generate dishes in cook/ (see 'make all' in cook/)" echo "Failed to generate dishes in cook/ (see 'make all' in cook/)"
exit 1 exit 1
fi fi
# Pick a dish (shared with deploy/deploy-distro.sh) # Pick a dish: --dish, or interactively (shared with deploy/deploy-distro.sh)
# shellcheck source=./deploy/select-dish.sh if [[ -n "$DISH" ]]; then
source "$(dirname "$0")/deploy/select-dish.sh" dish="${DISH%.cfg}"
dish="$selected_dish" if [[ ! -f "cook/dishes/${dish}.cfg" ]]; then
echo "Dish not found: cook/dishes/${dish}.cfg (is --tier '$TIER' right?)"
exit 1
fi
else
# shellcheck source=./deploy/select-dish.sh
source "$(dirname "$0")/deploy/select-dish.sh"
dish="$selected_dish"
fi
# Derive the target release, same convention as # Derive the target release, same convention as
# deploy/deploy-distro.sh's find_fedora_iso() (used for --releasever, which # deploy/deploy-distro.sh's find_fedora_iso() (used for --releasever, which
+6 -2
View File
@@ -1,6 +1,9 @@
.PHONY: help generate lint validate inventory test install-deps clean clean-recipes clean-dishes all .PHONY: help generate lint validate inventory test install-deps clean clean-recipes clean-dishes all
PYTHON ?= python3 PYTHON ?= python3
# Manifest tier(s) to generate: default, guest, experimental, all (space-separated)
TIER ?= default
TIER_ARGS = $(foreach t,$(TIER),--tier $(t))
default: all default: all
@@ -9,6 +12,7 @@ help:
@echo "" @echo ""
@echo "Available targets:" @echo "Available targets:"
@echo " all - Generate recipes and dishes, lint, and validate (default)" @echo " all - Generate recipes and dishes, lint, and validate (default)"
@echo " TIER=default|guest|experimental|all picks the manifest tier(s)"
@echo " generate - Same as 'all'" @echo " generate - Same as 'all'"
@echo " lint - Lint manifest and templates without writing files" @echo " lint - Lint manifest and templates without writing files"
@echo " validate - Validate existing dishes without regenerating" @echo " validate - Validate existing dishes without regenerating"
@@ -22,13 +26,13 @@ help:
all: generate all: generate
generate: generate:
@$(PYTHON) generate_recipe.py @$(PYTHON) generate_recipe.py $(TIER_ARGS)
lint: lint:
@$(PYTHON) generate_recipe.py --no-generate --no-validate @$(PYTHON) generate_recipe.py --no-generate --no-validate
validate: validate:
@$(PYTHON) generate_recipe.py --no-generate --no-lint @$(PYTHON) generate_recipe.py $(TIER_ARGS) --no-generate --no-lint
inventory: inventory:
@$(PYTHON) generate_recipe.py --inventory @$(PYTHON) generate_recipe.py --inventory
+43 -4
View File
@@ -204,9 +204,42 @@ def validate_dish(dish_path):
return False, str(exc) return False, str(exc)
DEFAULT_TIER = 'default'
def group_tier(group):
"""Return the tier of a manifest group (groups default to 'default')."""
return group.get('tier', DEFAULT_TIER)
def select_groups(manifest, tiers):
"""Return the manifest groups belonging to the requested tiers.
tiers is a collection of tier names; 'all' selects every group.
"""
groups = manifest.get('recipes', [])
if 'all' in tiers:
return groups
return [g for g in groups if group_tier(g) in tiers]
def clean_output(directory):
"""Remove previously generated .cfg files so stale dishes never linger."""
if not os.path.isdir(directory):
return
for entry in os.listdir(directory):
if entry.endswith('.cfg'):
os.remove(os.path.join(directory, entry))
def generate(manifest_path, templates_path, recipes_dir, dishes_dir, def generate(manifest_path, templates_path, recipes_dir, dishes_dir,
ingredients_dir, do_generate=True, do_lint=True, do_validate=True): ingredients_dir, do_generate=True, do_lint=True, do_validate=True,
"""Full cooking pipeline. Returns an exit code (0 on success).""" tiers=(DEFAULT_TIER,)):
"""Full cooking pipeline. Returns an exit code (0 on success).
Only groups in the requested tiers are written; lint always covers the
whole manifest so inactive tiers cannot rot.
"""
manifest = load_yaml(manifest_path) manifest = load_yaml(manifest_path)
templates = load_yaml(templates_path) templates = load_yaml(templates_path)
ingredients_root = os.path.join(os.path.dirname(os.path.abspath(manifest_path)), ingredients_root = os.path.join(os.path.dirname(os.path.abspath(manifest_path)),
@@ -225,8 +258,10 @@ def generate(manifest_path, templates_path, recipes_dir, dishes_dir,
if do_generate: if do_generate:
os.makedirs(recipe_dir, exist_ok=True) os.makedirs(recipe_dir, exist_ok=True)
os.makedirs(dish_dir, exist_ok=True) os.makedirs(dish_dir, exist_ok=True)
clean_output(recipe_dir)
clean_output(dish_dir)
for group in manifest.get('recipes', []): for group in select_groups(manifest, tiers):
group_name = group.get('name', 'unknown') group_name = group.get('name', 'unknown')
for config in group.get('variants', []): for config in group.get('variants', []):
for variant in expand_variants(config): for variant in expand_variants(config):
@@ -315,6 +350,9 @@ def main(argv=None):
help='output directory for flattened dishes (default: dishes)') help='output directory for flattened dishes (default: dishes)')
parser.add_argument('--ingredients-dir', default='ingredients', parser.add_argument('--ingredients-dir', default='ingredients',
help='ingredient fragments directory (default: ingredients)') help='ingredient fragments directory (default: ingredients)')
parser.add_argument('--tier', action='append', metavar='TIER',
help="manifest tier to generate: default, guest, experimental "
"or all; repeatable (default: default)")
parser.add_argument('--no-generate', action='store_true', parser.add_argument('--no-generate', action='store_true',
help='do not write recipes or dishes (lint/validate only)') help='do not write recipes or dishes (lint/validate only)')
parser.add_argument('--no-lint', action='store_true', parser.add_argument('--no-lint', action='store_true',
@@ -342,7 +380,8 @@ def main(argv=None):
args.ingredients_dir, args.ingredients_dir,
do_generate=not args.no_generate, do_generate=not args.no_generate,
do_lint=not args.no_lint, do_lint=not args.no_lint,
do_validate=not args.no_validate) do_validate=not args.no_validate,
tiers=tuple(args.tier or (DEFAULT_TIER,)))
if __name__ == '__main__': if __name__ == '__main__':
@@ -0,0 +1,33 @@
# CPU-agnostic optimization for hypervisor (Intel and AMD)
#
# One ingredient for both CPU vendors, so a single dish can be installed on either:
# * intel_iommu=on is ignored by AMD kernels (amd_iommu is enabled by default).
# * "options kvm_intel"/"options kvm_amd" only apply to the module that actually loads.
%post --nochroot --log=/mnt/sysimage/root/hypervisor-cpu-post.log # Beginning of %post section. Those commands are executed outside the chroot environment. Logging is enabled to help with post-installation troubleshooting
# Kernel arguments cannot be attached to a single canonical location at %post time:
# anaconda always creates /etc/kernel/cmdline (kernel-install source for both
# systemd-boot and GRUB+BLS layouts), but GRUB+BLS boot entries have already been
# generated from it by the kernel package scriptlets, and non-BLS flows still read
# /etc/default/grub. Update every surface idempotently, guarded by file existence.
IOMMU_ARGS="intel_iommu=on iommu=pt rd.driver.pre=vfio-pci"
if [ -f /mnt/sysimage/etc/kernel/cmdline ]; then # kernel-install source (systemd-boot now, GRUB+BLS for future kernels)
grep -q "iommu=pt" /mnt/sysimage/etc/kernel/cmdline || echo " $IOMMU_ARGS" >> /mnt/sysimage/etc/kernel/cmdline # Append IOMMU arguments once
fi
if [ -f /mnt/sysimage/etc/default/grub ]; then # GRUB: arguments for grub.cfg-based boot flows
grep -q "iommu=pt" /mnt/sysimage/etc/default/grub || sed -i "s/\(quiet\)/\1 $IOMMU_ARGS/i" /mnt/sysimage/etc/default/grub # Append IOMMU arguments once
fi
for bls_entry in /mnt/sysimage/boot/loader/entries/*.conf; do # Already-generated boot entries share one "options" line shape
[ -e "$bls_entry" ] || continue # Nothing matched: the glob stays literal
grep -q "iommu=pt" "$bls_entry" || sed -i "s/^\(options .*\)/\1 $IOMMU_ARGS/" "$bls_entry" # Patch the options line of each entry once
done
grep -q "kvm_intel" /mnt/sysimage/etc/modprobe.d/kvm.conf 2>/dev/null || echo "options kvm_intel nested=1" >> /mnt/sysimage/etc/modprobe.d/kvm.conf # Add support for nested virtualization on Intel CPUs, exactly once
grep -q "kvm_amd" /mnt/sysimage/etc/modprobe.d/kvm.conf 2>/dev/null || echo "options kvm_amd nested=1" >> /mnt/sysimage/etc/modprobe.d/kvm.conf # Add support for nested virtualization on AMD CPUs, exactly once
%end # End of the %post section
+1
View File
@@ -68,6 +68,7 @@ features:
- packages/virtual-machine-manager/packages.ks - packages/virtual-machine-manager/packages.ks
- packages/virtual-machine-manager/post-scripts.ks - packages/virtual-machine-manager/post-scripts.ks
hypervisor_type: hypervisor_type:
any: hypervisor/cpu-agnostic.ks
amdcpu: hypervisor/amdcpu.ks amdcpu: hypervisor/amdcpu.ks
intelcpu: hypervisor/intelcpu.ks intelcpu: hypervisor/intelcpu.ks
intelgpu: hypervisor/intelgpu.ks intelgpu: hypervisor/intelgpu.ks
+89 -36
View File
@@ -2,52 +2,87 @@
# Define all recipe variants using ingredients from recipe_templates.yaml. # Define all recipe variants using ingredients from recipe_templates.yaml.
# Edit this file to add new variants, then run: make all # Edit this file to add new variants, then run: make all
# #
# Tiers: every group belongs to a tier (default: "default"). Only the requested
# tier is generated, so the default build stays tiny:
# default -- the two shipping editions (phyllomeos, phyllomeos-headless)
# guest -- Fedora VM guests to deploy on a Phyllome OS host
# experimental -- one-off variants for testing new dishes
# Build a tier with: make all TIER=guest (or: TIER="default guest", TIER=all)
# Lint always covers every tier.
#
# Syntax: # Syntax:
# - Single values: repository: "43" # - Single values: repository: "44"
# - List values: repository: ["43", "44", "rawhide"] (expands to multiple variants) # - List values: repository: ["43", "44"] (expands to multiple variants)
# - Booleans: hardware-support: true / false (feature flags) # - Booleans: hardware-support: true / false (feature flags)
# #
# NOTE: List values create a cartesian product. # NOTE: List values create a cartesian product. Keep it out of the default and
# Example: repository: ["43", "44", "rawhide"] + storage: ["standard", "encrypted"] # guest tiers; prefer one hand-written variant per experimental dish.
# creates 6 variants: (43,standard), (43,encrypted), (44,standard), (44,encrypted),
# (rawhide,standard), (rawhide,encrypted)
# #
# NOTE: The 'name' field prefixes every generated dish/recipe filename, e.g. # NOTE: The 'name' field prefixes every generated dish/recipe filename, e.g.
# the group below yields dishes like desktop_43_gnome_standard_grub_guest-agents.cfg. # the "phyllomeos" group yields phyllomeos_44_standard_systemd-boot_...cfg.
recipes: recipes:
# Desktop variants # --- default tier: the two shipping editions -------------------------------
- name: desktop
variants:
- repository: ["43", "44", "rawhide"]
desktop: gnome
storage: ["standard", "encrypted"]
bootloader: ["grub", "systemd-boot"]
hardware-support: [true, false]
guest-agents: [true, false]
# Server variants # Phyllome OS with a GUI (GNOME + virt-manager)
- name: server - name: phyllomeos
tier: default
variants: variants:
- repository: ["44"] - repository: "44"
desktop: gnome
storage: standard storage: standard
bootloader: systemd-boot bootloader: systemd-boot
security: enabled
initial-setup: gnome
hardware-support: true
guest-agents: true
hypervisor: desktop
hypervisor_type: any
# Headless Phyllome OS
- name: phyllomeos-headless
tier: default
variants:
- repository: "44"
storage: standard
bootloader: systemd-boot
security: enabled
initial-setup: server
hardware-support: true
guest-agents: true
hypervisor: base
hypervisor_type: any
# --- guest tier: VMs to deploy on a Phyllome OS host -------------------------
- name: guest-server
tier: guest
variants:
- repository: "44"
storage: standard
bootloader: systemd-boot
security: enabled
initial-setup: server
hardware-support: false hardware-support: false
guest-agents: true guest-agents: true
initial-setup: server
# Hypervisor variants - name: guest-desktop
- name: hypervisor tier: guest
variants: variants:
- repository: ["44"] - repository: "44"
desktop: gnome
storage: standard storage: standard
bootloader: systemd-boot bootloader: systemd-boot
hardware-support: true security: enabled
guest-agents: false initial-setup: gnome
hypervisor: base hardware-support: false
hypervisor_type: intelcpu guest-agents: true
# Desktop-hypervisor variants
- name: desktop-hypervisor # --- experimental tier: single variants, no matrices -------------------------
# BIOS (legacy firmware) laptop: GPT + biosboot partition, GRUB
- name: exp-bios-desktop-hypervisor
tier: experimental
variants: variants:
- repository: "44" - repository: "44"
desktop: gnome desktop: gnome
@@ -58,14 +93,32 @@ recipes:
hardware-support: true hardware-support: true
guest-agents: false guest-agents: false
hypervisor: desktop hypervisor: desktop
hypervisor_type: intelcpu hypervisor_type: any
# Live desktop ISO variants (built with livemedia-creator --make-iso) # Encrypted root
- name: live-desktop - name: exp-encrypted-headless
tier: experimental
variants: variants:
- repository: "44" - repository: "44"
desktop: gnome storage: encrypted
storage: livefs bootloader: systemd-boot
bootloader: grub security: enabled
initial-setup: server
hardware-support: true
guest-agents: true guest-agents: true
live: true hypervisor: base
hypervisor_type: any
# Rawhide
- name: exp-rawhide-headless
tier: experimental
variants:
- repository: rawhide
storage: standard
bootloader: systemd-boot
security: enabled
initial-setup: server
hardware-support: true
guest-agents: true
hypervisor: base
hypervisor_type: any
+73
View File
@@ -279,3 +279,76 @@ def test_generate_writes_recipes_and_dishes(tmp_path, templates):
assert len(list(dishes.glob("*.cfg"))) == 1 assert len(list(dishes.glob("*.cfg"))) == 1
dish = (dishes / "desktop_43_standard_grub_gnome.cfg").read_text() dish = (dishes / "desktop_43_standard_grub_gnome.cfg").read_text()
assert "%include" not in dish assert "%include" not in dish
# ---------------------------------------------------------------------------
# tiers and the real manifest
# ---------------------------------------------------------------------------
def real_manifest():
return gen.load_yaml(os.path.join(COOK_DIR, "recipes_manifest.yaml"))
def dish_names(manifest, templates, tiers):
return sorted(
gen.render_filename(templates, group["name"], variant)
for group in gen.select_groups(manifest, tiers)
for config in group["variants"]
for variant in gen.expand_variants(config))
def test_select_groups_defaults_untiered_groups_to_default():
manifest = {"recipes": [{"name": "a"}, {"name": "b", "tier": "guest"}]}
assert [g["name"] for g in gen.select_groups(manifest, ("default",))] == ["a"]
assert [g["name"] for g in gen.select_groups(manifest, ("guest",))] == ["b"]
assert [g["name"] for g in gen.select_groups(manifest, ("all",))] == ["a", "b"]
def test_real_manifest_lints_clean_across_all_tiers(templates):
problems = gen.lint_manifest(real_manifest(), templates,
os.path.join(COOK_DIR, "ingredients"))
assert problems == []
def test_default_tier_is_the_two_editions(templates):
names = dish_names(real_manifest(), templates, ("default",))
assert len(names) == 2
assert sorted(n.split("_")[0] for n in names) == ["phyllomeos", "phyllomeos-headless"]
def test_guest_tier_is_server_and_desktop(templates):
names = dish_names(real_manifest(), templates, ("guest",))
assert sorted(n.split("_")[0] for n in names) == ["guest-desktop", "guest-server"]
assert all("guest-agents" in n for n in names)
def test_shipping_dishes_contents(templates):
manifest = real_manifest()
for group in gen.select_groups(manifest, ("default",)):
variant = gen.expand_variants(group["variants"][0])[0]
fragments, problems = gen.collect_fragments(templates, variant)
assert problems == []
assert "hypervisor/cpu-agnostic.ks" in fragments
assert "bootloader/systemd-boot.ks" in fragments
assert "storage/standard.ks" in fragments
assert ("desktop/gnome/config.ks" in fragments) == (group["name"] == "phyllomeos")
def test_generate_only_writes_requested_tier_and_clears_stale(tmp_path):
(tmp_path / "ingredients").symlink_to(os.path.join(COOK_DIR, "ingredients"),
target_is_directory=True)
recipes = tmp_path / "recipes"
dishes = tmp_path / "dishes"
recipes.mkdir()
dishes.mkdir()
(dishes / "stale.cfg").write_text("stale")
(recipes / "stale.cfg").write_text("stale")
templates_path = os.path.join(COOK_DIR, "recipe_templates.yaml")
manifest_path = os.path.join(COOK_DIR, "recipes_manifest.yaml")
code = gen.generate(manifest_path, templates_path, str(recipes), str(dishes),
"ingredients", tiers=("guest",))
assert code == 0
names = sorted(p.name.split("_")[0] for p in dishes.glob("*.cfg"))
assert names == ["guest-desktop", "guest-server"]
assert not (dishes / "stale.cfg").exists()
assert not (recipes / "stale.cfg").exists()
+19 -1
View File
@@ -11,8 +11,26 @@ execute_script() {
return 0 # Indicate success return 0 # Indicate success
} }
# Usage: ./deploy.sh [--tier default|guest|experimental|all]
# Tier of dishes to generate and offer (default: default). Use "guest" to
# deploy a plain Fedora server/desktop VM on a Phyllome OS host.
TIER="${TIER:-default}"
while [[ $# -gt 0 ]]; do
case "$1" in
--tier)
TIER="$2"
shift 2
;;
*)
echo "Unknown argument: $1"
echo "Usage: $0 [--tier default|guest|experimental|all]"
exit 1
;;
esac
done
# Generate recipes and dishes before deployment (they are build products) # Generate recipes and dishes before deployment (they are build products)
if ! (cd cook && make all); then if ! (cd cook && make all TIER="$TIER"); then
echo "Failed to generate dishes in cook/ (see 'make all' in cook/)" echo "Failed to generate dishes in cook/ (see 'make all' in cook/)"
exit 1 exit 1
fi fi