cook: two default dishes (phyllomeos, phyllomeos-headless) + guest/experimental tiers; CI builds raw images
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:
co-authored by
Claude Sonnet 5
parent
35d8879bcc
commit
67316d9852
@@ -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.*
|
||||
@@ -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
|
||||
@@ -10,7 +10,7 @@ Provided that some dependencies are met (`libvirt` is running on your computer,
|
||||
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
|
||||
@@ -35,9 +35,28 @@ Allocating 'desktop_44_standard_grub_gnome_guest-agents.img'
|
||||
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.
|
||||
|
||||
## 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)
|
||||
|
||||
`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:
|
||||
|
||||
```
|
||||
./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
|
||||
`./build/`. Requires `lorax-lmc-novirt` (heavier than `deploy.sh`'s plain
|
||||
It picks a dish the same way `deploy.sh` does (or takes `--dish NAME`) and writes the result under
|
||||
`./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
|
||||
`deploy/install-prerequisites-on-linux.sh`) and root — the `--no-virt` disk
|
||||
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:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
@@ -135,12 +154,13 @@ cd cook && make generate
|
||||
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/`)
|
||||
|
||||
```
|
||||
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 validate # validate existing dishes
|
||||
make inventory # print the ingredient catalog derived from the templates
|
||||
|
||||
+30
-7
@@ -6,7 +6,12 @@
|
||||
# (e.g. `virtpull local ./build/<dish>.img --wonder-vm NAME`) instead of a
|
||||
# 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
|
||||
# --no-virt disk install needs real loop-device access via
|
||||
@@ -31,10 +36,20 @@
|
||||
set -euo pipefail
|
||||
|
||||
AS_FORMAT=""
|
||||
DISH=""
|
||||
TIER="${TIER:-default}"
|
||||
ROOT_SIZE=8192
|
||||
EXTRA_KS=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dish)
|
||||
DISH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--tier)
|
||||
TIER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--as)
|
||||
AS_FORMAT="$2"
|
||||
shift 2
|
||||
@@ -49,7 +64,7 @@ while [[ $# -gt 0 ]]; do
|
||||
;;
|
||||
*)
|
||||
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
|
||||
;;
|
||||
esac
|
||||
@@ -81,15 +96,23 @@ if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
|
||||
fi
|
||||
|
||||
# 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/)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Pick a dish (shared with deploy/deploy-distro.sh)
|
||||
# shellcheck source=./deploy/select-dish.sh
|
||||
source "$(dirname "$0")/deploy/select-dish.sh"
|
||||
dish="$selected_dish"
|
||||
# Pick a dish: --dish, or interactively (shared with deploy/deploy-distro.sh)
|
||||
if [[ -n "$DISH" ]]; then
|
||||
dish="${DISH%.cfg}"
|
||||
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
|
||||
# deploy/deploy-distro.sh's find_fedora_iso() (used for --releasever, which
|
||||
|
||||
+6
-2
@@ -1,6 +1,9 @@
|
||||
.PHONY: help generate lint validate inventory test install-deps clean clean-recipes clean-dishes all
|
||||
|
||||
PYTHON ?= python3
|
||||
# Manifest tier(s) to generate: default, guest, experimental, all (space-separated)
|
||||
TIER ?= default
|
||||
TIER_ARGS = $(foreach t,$(TIER),--tier $(t))
|
||||
|
||||
default: all
|
||||
|
||||
@@ -9,6 +12,7 @@ help:
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@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 " lint - Lint manifest and templates without writing files"
|
||||
@echo " validate - Validate existing dishes without regenerating"
|
||||
@@ -22,13 +26,13 @@ help:
|
||||
all: generate
|
||||
|
||||
generate:
|
||||
@$(PYTHON) generate_recipe.py
|
||||
@$(PYTHON) generate_recipe.py $(TIER_ARGS)
|
||||
|
||||
lint:
|
||||
@$(PYTHON) generate_recipe.py --no-generate --no-validate
|
||||
|
||||
validate:
|
||||
@$(PYTHON) generate_recipe.py --no-generate --no-lint
|
||||
@$(PYTHON) generate_recipe.py $(TIER_ARGS) --no-generate --no-lint
|
||||
|
||||
inventory:
|
||||
@$(PYTHON) generate_recipe.py --inventory
|
||||
|
||||
+43
-4
@@ -204,9 +204,42 @@ def validate_dish(dish_path):
|
||||
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,
|
||||
ingredients_dir, do_generate=True, do_lint=True, do_validate=True):
|
||||
"""Full cooking pipeline. Returns an exit code (0 on success)."""
|
||||
ingredients_dir, do_generate=True, do_lint=True, do_validate=True,
|
||||
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)
|
||||
templates = load_yaml(templates_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:
|
||||
os.makedirs(recipe_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')
|
||||
for config in group.get('variants', []):
|
||||
for variant in expand_variants(config):
|
||||
@@ -315,6 +350,9 @@ def main(argv=None):
|
||||
help='output directory for flattened dishes (default: dishes)')
|
||||
parser.add_argument('--ingredients-dir', 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',
|
||||
help='do not write recipes or dishes (lint/validate only)')
|
||||
parser.add_argument('--no-lint', action='store_true',
|
||||
@@ -342,7 +380,8 @@ def main(argv=None):
|
||||
args.ingredients_dir,
|
||||
do_generate=not args.no_generate,
|
||||
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__':
|
||||
|
||||
@@ -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
|
||||
@@ -68,6 +68,7 @@ features:
|
||||
- packages/virtual-machine-manager/packages.ks
|
||||
- packages/virtual-machine-manager/post-scripts.ks
|
||||
hypervisor_type:
|
||||
any: hypervisor/cpu-agnostic.ks
|
||||
amdcpu: hypervisor/amdcpu.ks
|
||||
intelcpu: hypervisor/intelcpu.ks
|
||||
intelgpu: hypervisor/intelgpu.ks
|
||||
|
||||
+89
-36
@@ -2,52 +2,87 @@
|
||||
# Define all recipe variants using ingredients from recipe_templates.yaml.
|
||||
# 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:
|
||||
# - Single values: repository: "43"
|
||||
# - List values: repository: ["43", "44", "rawhide"] (expands to multiple variants)
|
||||
# - Single values: repository: "44"
|
||||
# - List values: repository: ["43", "44"] (expands to multiple variants)
|
||||
# - Booleans: hardware-support: true / false (feature flags)
|
||||
#
|
||||
# NOTE: List values create a cartesian product.
|
||||
# Example: repository: ["43", "44", "rawhide"] + storage: ["standard", "encrypted"]
|
||||
# creates 6 variants: (43,standard), (43,encrypted), (44,standard), (44,encrypted),
|
||||
# (rawhide,standard), (rawhide,encrypted)
|
||||
# NOTE: List values create a cartesian product. Keep it out of the default and
|
||||
# guest tiers; prefer one hand-written variant per experimental dish.
|
||||
#
|
||||
# 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:
|
||||
# Desktop variants
|
||||
- name: desktop
|
||||
variants:
|
||||
- repository: ["43", "44", "rawhide"]
|
||||
desktop: gnome
|
||||
storage: ["standard", "encrypted"]
|
||||
bootloader: ["grub", "systemd-boot"]
|
||||
hardware-support: [true, false]
|
||||
guest-agents: [true, false]
|
||||
# --- default tier: the two shipping editions -------------------------------
|
||||
|
||||
# Server variants
|
||||
- name: server
|
||||
# Phyllome OS with a GUI (GNOME + virt-manager)
|
||||
- name: phyllomeos
|
||||
tier: default
|
||||
variants:
|
||||
- repository: ["44"]
|
||||
- repository: "44"
|
||||
desktop: gnome
|
||||
storage: standard
|
||||
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
|
||||
guest-agents: true
|
||||
initial-setup: server
|
||||
|
||||
# Hypervisor variants
|
||||
- name: hypervisor
|
||||
- name: guest-desktop
|
||||
tier: guest
|
||||
variants:
|
||||
- repository: ["44"]
|
||||
- repository: "44"
|
||||
desktop: gnome
|
||||
storage: standard
|
||||
bootloader: systemd-boot
|
||||
hardware-support: true
|
||||
guest-agents: false
|
||||
hypervisor: base
|
||||
hypervisor_type: intelcpu
|
||||
# Desktop-hypervisor variants
|
||||
- name: desktop-hypervisor
|
||||
security: enabled
|
||||
initial-setup: gnome
|
||||
hardware-support: false
|
||||
guest-agents: true
|
||||
|
||||
# --- experimental tier: single variants, no matrices -------------------------
|
||||
|
||||
# BIOS (legacy firmware) laptop: GPT + biosboot partition, GRUB
|
||||
- name: exp-bios-desktop-hypervisor
|
||||
tier: experimental
|
||||
variants:
|
||||
- repository: "44"
|
||||
desktop: gnome
|
||||
@@ -58,14 +93,32 @@ recipes:
|
||||
hardware-support: true
|
||||
guest-agents: false
|
||||
hypervisor: desktop
|
||||
hypervisor_type: intelcpu
|
||||
hypervisor_type: any
|
||||
|
||||
# Live desktop ISO variants (built with livemedia-creator --make-iso)
|
||||
- name: live-desktop
|
||||
# Encrypted root
|
||||
- name: exp-encrypted-headless
|
||||
tier: experimental
|
||||
variants:
|
||||
- repository: "44"
|
||||
desktop: gnome
|
||||
storage: livefs
|
||||
bootloader: grub
|
||||
storage: encrypted
|
||||
bootloader: systemd-boot
|
||||
security: enabled
|
||||
initial-setup: server
|
||||
hardware-support: 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
|
||||
|
||||
@@ -279,3 +279,76 @@ def test_generate_writes_recipes_and_dishes(tmp_path, templates):
|
||||
assert len(list(dishes.glob("*.cfg"))) == 1
|
||||
dish = (dishes / "desktop_43_standard_grub_gnome.cfg").read_text()
|
||||
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()
|
||||
|
||||
@@ -11,8 +11,26 @@ execute_script() {
|
||||
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)
|
||||
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/)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user