Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccd1450a01 | ||
|
|
dad1642705 | ||
|
|
f7b6623b25 | ||
|
|
6d521930f4 | ||
|
|
c611bbf2c2 | ||
|
|
6931039809 | ||
|
|
41eb18cc70 | ||
|
|
b8cd2966f4 | ||
|
|
67316d9852 | ||
|
|
35d8879bcc | ||
|
|
bb23fed54a | ||
|
|
d3189bee94 | ||
|
|
435ce41c02 | ||
|
|
7197ed9be9 | ||
|
|
da5bcf6b04 | ||
|
|
f246dec765 | ||
|
|
bddd05f3a8 | ||
|
|
c491aeb635 | ||
|
|
dceb82f8f6 | ||
|
|
a8228df780 | ||
|
|
0a553429ba | ||
|
|
cd4302cb09 | ||
|
|
fff901ef1a |
@@ -0,0 +1,147 @@
|
|||||||
|
name: build-image
|
||||||
|
|
||||||
|
# A full build takes ~25 min on the runner and executes the checked-out code
|
||||||
|
# as root there, so it never runs on a plain push. Recipes are still linted
|
||||||
|
# and validated on every push and PR by ci.yml. Images are built:
|
||||||
|
# - on a release tag (v*.*.*): build, smoke test, publish as a release;
|
||||||
|
# - on a PR into main carrying the `build-image` label, i.e. once a
|
||||||
|
# maintainer has reviewed it (Gitea does not let authors approve their
|
||||||
|
# own PRs, so approval cannot be the gate). Re-runs on every new push to
|
||||||
|
# the PR while the label stays. Only for changes that affect images;
|
||||||
|
# - weekly, to catch breakage from upstream Fedora package changes;
|
||||||
|
# - by hand (workflow_dispatch) on any branch.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*.*.*"]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
types: [labeled, synchronize, reopened]
|
||||||
|
paths:
|
||||||
|
- "cook/**"
|
||||||
|
- "build-image.sh"
|
||||||
|
- "smoke-test.sh"
|
||||||
|
- ".gitea/workflows/build-image.yaml"
|
||||||
|
schedule:
|
||||||
|
- cron: "0 3 * * 1" # Mondays 03:00
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# 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, plus qemu-system-x86_64,
|
||||||
|
# /dev/kvm and edk2-ovmf for smoke-test.sh. It must run with SELinux enabled:
|
||||||
|
# anaconda inherits the runner's own selinux=0, if any (smoke-test.sh fails
|
||||||
|
# such images).
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
# On PRs, only once the `build-image` label is set (see `on:` above), and
|
||||||
|
# not again when some other label is added to an already-labeled PR.
|
||||||
|
if: >-
|
||||||
|
github.event_name != 'pull_request' ||
|
||||||
|
(contains(github.event.pull_request.labels.*.name, 'build-image') &&
|
||||||
|
(github.event.action != 'labeled' || github.event.label.name == 'build-image'))
|
||||||
|
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
|
||||||
|
# NB: Gitea runs every matrix entry as its own task, so `max-parallel`
|
||||||
|
# does not serialize them; build-image.sh takes a host-wide flock instead.
|
||||||
|
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 qemu-system-x86_64 debugfs; 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; }
|
||||||
|
[ -e /dev/kvm ] || { echo "missing on runner: /dev/kvm"; missing=1; }
|
||||||
|
[ -e /usr/share/edk2/ovmf/OVMF_CODE.fd ] || { echo "missing on runner: edk2-ovmf"; 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 }}
|
||||||
|
|
||||||
|
- name: Smoke test (static checks + boot)
|
||||||
|
run: ./smoke-test.sh "build/$DISH.img"
|
||||||
|
|
||||||
|
- name: Compress image
|
||||||
|
run: xz -T0 "build/$DISH.img"
|
||||||
|
|
||||||
|
- name: Upload image and kickstart as artifact
|
||||||
|
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||||
|
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
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- 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,74 +0,0 @@
|
|||||||
name: build-iso
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate:
|
|
||||||
runs-on: fedora
|
|
||||||
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 dependencies
|
|
||||||
run: |
|
|
||||||
pip install PyYAML pykickstart
|
|
||||||
|
|
||||||
- name: Generate, lint, and validate dishes
|
|
||||||
run: |
|
|
||||||
cd cook
|
|
||||||
make all
|
|
||||||
|
|
||||||
build-iso:
|
|
||||||
needs: validate
|
|
||||||
runs-on: fedora-cloud-42
|
|
||||||
container:
|
|
||||||
image: git.phyllo.me/devops/fedora-runner-image:latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: https://git.phyllo.me/devops/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Initialize mock
|
|
||||||
run: |
|
|
||||||
mock -r fedora-44-x86_64 --init
|
|
||||||
|
|
||||||
- name: Install required packages
|
|
||||||
run: |
|
|
||||||
mock -r fedora-44-x86_64 --install lorax-lmc-novirt vim-minimal pykickstart livecd-tools
|
|
||||||
|
|
||||||
- name: Generate dishes
|
|
||||||
run: |
|
|
||||||
cd cook
|
|
||||||
make all
|
|
||||||
|
|
||||||
- name: Copy configuration file to mock
|
|
||||||
run: |
|
|
||||||
mock -r fedora-44-x86_64 --copyin dishes/desktop_44_standard_grub_gnome_guest-agents.cfg /builddir
|
|
||||||
|
|
||||||
- name: Build ISO with livemedia-creator
|
|
||||||
run: |
|
|
||||||
mock -r fedora-44-x86_64 --shell --enable-network --isolation=simple << 'EOF'
|
|
||||||
cd /builddir
|
|
||||||
livemedia-creator --ks desktop_44_standard_grub_gnome_guest-agents.cfg --no-virt --resultdir /var/lmc --project phyllomeos --make-iso --volid phyllomeos --iso-only --iso-name desktop-44.iso --releasever 44 --macboot
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Upload ISO as artifact
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: desktop-44.iso
|
|
||||||
path: /var/lmc/desktop-44.iso
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Cleanup mock environment
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
mock -r fedora-44-x86_64 --clean
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
name: release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
|
|
||||||
env:
|
|
||||||
FEDORA_VERSION: 44
|
|
||||||
KICKSTART_FILE: desktop_44_standard_grub_gnome_guest-agents
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
checkout:
|
|
||||||
runs-on: fedora
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: https://git.phyllo.me/devops/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Initialize mock
|
|
||||||
run: |
|
|
||||||
mock -r fedora-${{ env.FEDORA_VERSION }}-x86_64 --init
|
|
||||||
|
|
||||||
- name: Install required packages
|
|
||||||
run: |
|
|
||||||
mock -r fedora-${{ env.FEDORA_VERSION }}-x86_64 --install lorax-lmc-novirt vim-minimal pykickstart livecd-tools
|
|
||||||
|
|
||||||
- name: Generate dishes
|
|
||||||
run: |
|
|
||||||
cd cook
|
|
||||||
make all
|
|
||||||
|
|
||||||
- name: Copy configuration file to mock
|
|
||||||
run: |
|
|
||||||
mock -r fedora-${{ env.FEDORA_VERSION }}-x86_64 --copyin dishes/${{ env.KICKSTART_FILE }} /builddir
|
|
||||||
|
|
||||||
- name: Build ISO with livemedia-creator
|
|
||||||
run: |
|
|
||||||
mock -r fedora-${{ env.FEDORA_VERSION }}-x86_64 --shell --enable-network --isolation=simple << 'EOF'
|
|
||||||
cd /builddir
|
|
||||||
livemedia-creator --ks ${{ env.KICKSTART_FILE }}.cfg --no-virt --resultdir /var/lmc --project ${{ env.KICKSTART_FILE }} --make-iso --volid ${{ env.KICKSTART_FILE }} --iso-only --iso-name ${{ env.KICKSTART_FILE }}-${{ env.FEDORA_VERSION }}.iso --releasever ${{ env.FEDORA_VERSION }} --macboot
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: https://git.phyllo.me/devops/action-gh-release@v2
|
|
||||||
if: github.ref_type == 'tag'
|
|
||||||
with:
|
|
||||||
files: /var/lib/mock/fedora-${{ env.FEDORA_VERSION }}-x86_64/root/var/lmc/${{ env.KICKSTART_FILE }}-${{ env.FEDORA_VERSION }}.iso
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
- name: Cleanup mock environment
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
mock -r fedora-${{ env.FEDORA_VERSION }}-x86_64 --clean
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Bump when the recipe language changes; both jobs share the runner.
|
||||||
|
cook:
|
||||||
|
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: Lint manifest + templates
|
||||||
|
run: make -C cook lint
|
||||||
|
- name: Generate recipes + dishes, validate every dish
|
||||||
|
run: make -C cook all
|
||||||
|
- name: Run the pytest suite
|
||||||
|
run: make -C cook test
|
||||||
@@ -2,7 +2,14 @@
|
|||||||
cook/recipes/*.cfg
|
cook/recipes/*.cfg
|
||||||
cook/dishes/*.cfg
|
cook/dishes/*.cfg
|
||||||
|
|
||||||
|
# Generated build products (created by build-image.sh)
|
||||||
|
build/
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
# Build artifacts (livemedia-creator side effects)
|
||||||
|
anaconda/
|
||||||
|
livemedia.log
|
||||||
|
program.log
|
||||||
|
|||||||
@@ -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,65 @@ 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)
|
||||||
|
|
||||||
|
`deploy.sh` drives a live `virt-install`/libvirt session. If you just want a
|
||||||
|
raw or qcow2 disk image — e.g. to hand to another VM manager such as
|
||||||
|
wonder-vm via virtpull's `virtpull local ... --wonder-vm NAME` —
|
||||||
|
use `build-image.sh` instead. It runs `livemedia-creator --no-virt
|
||||||
|
--make-disk` directly on the host, producing a disk image instead of a
|
||||||
|
bootable ISO:
|
||||||
|
|
||||||
|
```
|
||||||
|
./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 (or takes `--dish NAME`) and writes the result under
|
||||||
|
`./build/`. `smoke-test.sh IMAGE` then checks the result: read-only static checks (no `selinux=0` on any kernel command line and labeled files when the image declares SELinux; builds on a host with SELinux disabled fail this), then a KVM boot with `-snapshot` that waits for qemu-guest-agent to answer.
|
||||||
|
|
||||||
|
The `build-image` CI workflow builds, smoke-tests and compresses both default editions. It does not run on plain pushes (a build takes ~25 min and runs as root on the runner); `ci.yml` lints and validates every push instead. It runs on:
|
||||||
|
|
||||||
|
* `v*.*.*` tags: attaches the compressed raw images (`.img.xz`) and the flattened kickstart files to the release;
|
||||||
|
* PRs into `main` labeled `build-image` (set it once the PR is reviewed; it re-runs on every new push while the label stays), for changes under `cook/`, to `build-image.sh`, `smoke-test.sh` or the workflow itself;
|
||||||
|
* a weekly schedule (Mondays 03:00), to catch breakage from upstream Fedora packages;
|
||||||
|
* manual dispatch from the Actions tab, on any branch.
|
||||||
|
|
||||||
|
Non-tag runs keep the images as 7-day workflow artifacts. 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
|
||||||
|
`mock` chroot cannot provide (no live `systemd-udevd` inside it, which
|
||||||
|
breaks blivet's device scan) and neither can a rootless container.
|
||||||
|
`--root-size` (default 8192 MiB) sizes the root partition, needed because
|
||||||
|
livemedia-creator computes the image size up front, unlike virt-install
|
||||||
|
which pre-creates the disk at a known size. `--extra-ks FILE` appends
|
||||||
|
local content (e.g. a bespoke `user`/`rootpw` override) to the dish before
|
||||||
|
building, without touching the tracked ingredients — see the "bespoke
|
||||||
|
dish" pattern below; never commit a file used here if it carries real
|
||||||
|
credentials.
|
||||||
|
|
||||||
## Repository structure
|
## Repository structure
|
||||||
|
|
||||||
This repository contains such files broken down as:
|
This repository contains such files broken down as:
|
||||||
@@ -95,7 +151,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
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -107,12 +163,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
|
||||||
|
|||||||
Executable
+233
@@ -0,0 +1,233 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build a raw (or qcow2) disk image from a dish, without libvirt/virt-install.
|
||||||
|
#
|
||||||
|
# Runs `livemedia-creator --no-virt --make-disk` directly on the host, so
|
||||||
|
# the result is a disk image you can hand to another VM manager directly
|
||||||
|
# (e.g. `virtpull local ./build/<dish>.img --wonder-vm NAME`) instead of a
|
||||||
|
# bootable ISO.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# /dev/loop-control; individual commands below run under sudo — passwordless
|
||||||
|
# sudo, or run this whole script as root).
|
||||||
|
#
|
||||||
|
# Earlier iteration wrapped this in `mock` (mirroring
|
||||||
|
# .gitea/workflows/build-iso.yaml's ISO build) to pin the target release's
|
||||||
|
# toolchain. That does not work for --make-disk: mock's chroot has no live
|
||||||
|
# systemd-udevd, so udev never populates properties for the loop device
|
||||||
|
# livemedia-creator creates for the image, and blivet's device scan
|
||||||
|
# crashes (blivet/udev.py device_get_name: "TypeError: argument of type
|
||||||
|
# 'NoneType' is not a container or iterable") — reproduced identically on
|
||||||
|
# Fedora 44 and 43 mock chroots, and confirmed absent when run directly on
|
||||||
|
# the host instead. --make-iso never hits this: ISO building doesn't touch
|
||||||
|
# block devices at all, only --make-disk (and other disk/appliance modes)
|
||||||
|
# does. The consequence: this script now builds using whatever
|
||||||
|
# anaconda/lorax the host has installed — the kickstart's own repo/url
|
||||||
|
# lines still pin the actual package set to the target Fedora release
|
||||||
|
# regardless, same as any other direct (non-mock) livemedia-creator use.
|
||||||
|
|
||||||
|
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
|
||||||
|
;;
|
||||||
|
--root-size)
|
||||||
|
ROOT_SIZE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--extra-ks)
|
||||||
|
EXTRA_KS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $1"
|
||||||
|
echo "Usage: $0 [--dish NAME] [--tier TIER] [--as qcow2] [--root-size MiB] [--extra-ks FILE]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [[ -n "$AS_FORMAT" && "$AS_FORMAT" != "qcow2" ]]; then
|
||||||
|
echo "--as only supports 'qcow2' (the build itself always produces raw)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! [[ "$ROOT_SIZE" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "--root-size must be a whole number of MiB, got '$ROOT_SIZE'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -n "$EXTRA_KS" && ! -f "$EXTRA_KS" ]]; then
|
||||||
|
echo "--extra-ks file not found: $EXTRA_KS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for tool in livemedia-creator qemu-img; do
|
||||||
|
if ! command -v "$tool" &> /dev/null; then
|
||||||
|
echo "Missing required tool: $tool"
|
||||||
|
echo "Fedora: sudo dnf install lorax-lmc-novirt qemu-img"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
|
||||||
|
echo "This needs root (loop-device access for the --no-virt disk install)."
|
||||||
|
echo "Re-run as root, or ensure passwordless sudo is available."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Regenerate recipes and dishes before building (they are build products)
|
||||||
|
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: --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
|
||||||
|
# substitutes @VERSION@ in bootloader/template text — the kickstart's own
|
||||||
|
# repo/url lines are what actually pin the package set).
|
||||||
|
if [[ "$dish" == *"rawhide"* ]]; then
|
||||||
|
releasever="rawhide"
|
||||||
|
elif [[ "$dish" =~ _([0-9]+) ]]; then
|
||||||
|
releasever="${BASH_REMATCH[1]}"
|
||||||
|
else
|
||||||
|
echo "Could not determine a Fedora release version from dish name '$dish'."
|
||||||
|
echo "Expected a '_<NN>' segment (e.g. desktop_44_standard...) or 'rawhide'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
out_dir="./build"
|
||||||
|
mkdir -p "$out_dir"
|
||||||
|
out_dir="$(cd "$out_dir" && pwd)"
|
||||||
|
|
||||||
|
# Adjustments needed only for this --no-virt disk-image path, applied to a
|
||||||
|
# scratch copy rather than the shared dish:
|
||||||
|
#
|
||||||
|
# 1. cook/ingredients/core/base.ks sets `text` for deploy/deploy-distro.sh's
|
||||||
|
# virt-install netinstall console; livemedia-creator manages the display
|
||||||
|
# mode itself and refuses a kickstart that also sets one ("The kickstart
|
||||||
|
# must not set a display mode").
|
||||||
|
#
|
||||||
|
# 2. cook/ingredients' storage layout uses `part / --grow` with no explicit
|
||||||
|
# --size: fine for virt-install, which pre-creates the disk at a known
|
||||||
|
# size for anaconda to grow into. livemedia-creator instead computes the
|
||||||
|
# total image size up front by summing partition sizes, and crashes
|
||||||
|
# (TypeError: unsupported operand type(s) for +: 'int' and 'NoneType')
|
||||||
|
# on a --grow partition with no size to add. Give it one via --root-size
|
||||||
|
# (default 8192 MiB; the desktop dishes likely need more).
|
||||||
|
#
|
||||||
|
# 3. --extra-ks appends arbitrary local content (e.g. a bespoke `user`/
|
||||||
|
# `rootpw`/`security` override) after the above. Kickstart's singular
|
||||||
|
# commands take the last occurrence, so this can override anything the
|
||||||
|
# dish already set without touching the shared ingredients — see the
|
||||||
|
# README's "bespoke dish not part of the matrix" pattern. Never commit
|
||||||
|
# a file used here if it carries real credentials.
|
||||||
|
disk_ks="$(mktemp "${TMPDIR:-/tmp}/${dish}.disk-build.XXXXXX.cfg")"
|
||||||
|
trap 'rm -f "$disk_ks"' EXIT
|
||||||
|
grep -v -E '^(text|cmdline|graphical)([[:space:]]|$)' "cook/dishes/${dish}.cfg" \
|
||||||
|
| awk -v size="$ROOT_SIZE" '
|
||||||
|
/^part / && /--grow/ && !/--size=/ { $0 = $0 " --size=" size }
|
||||||
|
{ print }
|
||||||
|
' > "$disk_ks"
|
||||||
|
if [[ -n "$EXTRA_KS" ]]; then
|
||||||
|
{
|
||||||
|
echo
|
||||||
|
cat "$EXTRA_KS"
|
||||||
|
} >> "$disk_ks"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v ksvalidator &> /dev/null; then
|
||||||
|
echo "Validating kickstart (informational; a build failure is the real test)..."
|
||||||
|
ksvalidator "$disk_ks" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
resultdir="$(mktemp -u "${TMPDIR:-/tmp}/lmc-result.XXXXXX")"
|
||||||
|
|
||||||
|
# Only one livemedia-creator run per host at a time. It and anaconda use
|
||||||
|
# host-global resources (/mnt/sysroot, /mnt/sysimage, loop and device-mapper
|
||||||
|
# devices), so concurrent builds corrupt each other -- e.g. bootctl failing with
|
||||||
|
# "Couldn't find EFI system partition" when the CI matrix started both editions
|
||||||
|
# at once on a shared runner (Gitea expands each matrix entry into its own
|
||||||
|
# task, so `max-parallel` does not serialize them). A host-wide flock does.
|
||||||
|
# Under the lock, first clear mounts/devices a previously failed build left
|
||||||
|
# behind, which would otherwise poison this one.
|
||||||
|
LOCK=/run/lock/phyllomeos-build-image.lock
|
||||||
|
echo "Building disk image for '$dish' (target release $releasever)..."
|
||||||
|
echo "Waiting for the host build lock ($LOCK) if another build is running..."
|
||||||
|
sudo flock "$LOCK" env DISH="$dish" bash -c '
|
||||||
|
for mnt in /mnt/sysimage /mnt/sysroot; do
|
||||||
|
if findmnt -rn "$mnt" >/dev/null 2>&1; then
|
||||||
|
echo "Cleaning stale mount $mnt from a failed build"
|
||||||
|
umount -R "$mnt" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for dm in $(dmsetup ls 2>/dev/null | awk -v d="$DISH" "index(\$1, d) == 1 {print \$1}" | sort -r); do
|
||||||
|
echo "Removing stale device-mapper device $dm"
|
||||||
|
dmsetup remove --retry "$dm" || true
|
||||||
|
done
|
||||||
|
for lo in $(losetup -a | awk -F: "/lmc-result\\./ {print \$1}"); do
|
||||||
|
echo "Detaching stale loop device $lo"
|
||||||
|
losetup -d "$lo" || true
|
||||||
|
done
|
||||||
|
exec livemedia-creator "$@"
|
||||||
|
' _ --ks "$disk_ks" --no-virt --resultdir "$resultdir" \
|
||||||
|
--make-disk --image-name "${dish}.img" --releasever "$releasever"
|
||||||
|
|
||||||
|
# livemedia-creator's exact output filename depends on the installed lorax
|
||||||
|
# version; don't assume --image-name is honored verbatim, take whatever
|
||||||
|
# single disk image landed in the resultdir.
|
||||||
|
produced=$(sudo find "$resultdir" -maxdepth 1 -type f \( -iname '*.img' -o -iname '*disk*' \) | head -n 1)
|
||||||
|
if [[ -z "$produced" ]]; then
|
||||||
|
echo "livemedia-creator did not produce a recognizable disk image under $resultdir"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
raw_out="${out_dir}/${dish}.img"
|
||||||
|
sudo cp --reflink=auto "$produced" "$raw_out"
|
||||||
|
sudo chown "$(id -u):$(id -g)" "$raw_out"
|
||||||
|
sudo rm -rf "$resultdir"
|
||||||
|
echo "Built: $raw_out"
|
||||||
|
|
||||||
|
if [[ "$AS_FORMAT" == "qcow2" ]]; then
|
||||||
|
qcow2_out="${out_dir}/${dish}.qcow2"
|
||||||
|
qemu-img convert -O qcow2 "$raw_out" "$qcow2_out"
|
||||||
|
echo "Converted: $qcow2_out"
|
||||||
|
final="$qcow2_out"
|
||||||
|
else
|
||||||
|
final="$raw_out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Next: virtpull local $final --wonder-vm <NAME>"
|
||||||
+6
-2
@@ -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
@@ -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__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Keyboard, language, and timezone configuration
|
# Keyboard, language, and timezone configuration
|
||||||
|
|
||||||
keyboard --xlayouts='ch (fr)' # Set keyboard layouts for Romandy
|
keyboard --vckeymap=ch-fr --xlayouts='ch (fr)' # Set keyboard layouts for Romandy: --vckeymap for the text console (tty login, headless), --xlayouts for graphical sessions
|
||||||
lang en_US.UTF-8 # Set system language to American English. More languages could be supported: --addsupport=cs_CZ,de_DE,en_UK
|
lang en_US.UTF-8 # Set system language to American English. More languages could be supported: --addsupport=cs_CZ,de_DE,en_UK
|
||||||
timezone Europe/Zurich --utc # Set system timezone to Zurich
|
timezone Europe/Zurich --utc # Set system timezone to Zurich
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Network configuration
|
# Network configuration
|
||||||
|
|
||||||
network --onboot=yes --bootproto=dhcp --device=link --activate --hostname=phyllome-alpha # Configure network devices, enable them at boot time device and sets a particular hostname. "link" selects the first device reaching an up state
|
network --onboot=yes --bootproto=dhcp --device=link --activate --hostname=phyllomeos # Configure network devices, enable them at boot time device and sets a generic hostname (rename per machine after install). "link" selects the first device reaching an up state
|
||||||
|
|||||||
@@ -2,8 +2,27 @@
|
|||||||
|
|
||||||
%post --nochroot --log=/mnt/sysimage/root/hypervisor-amdcpu-post.log # Beginning of %post section. Those commands are executed outside the chroot environment. Logging is enabled to help with post-installation troubleshooting
|
%post --nochroot --log=/mnt/sysimage/root/hypervisor-amdcpu-post.log # Beginning of %post section. Those commands are executed outside the chroot environment. Logging is enabled to help with post-installation troubleshooting
|
||||||
|
|
||||||
sed -i 's/\(quiet\)/\1 iommu=pt rd.driver.pre=vfio-pci/i' /mnt/sysimage/etc/default/grub # Load kernel modules in GRUB.
|
# 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.
|
||||||
|
|
||||||
echo "options kvm_amd nested=1" >> /mnt/sysimage/etc/modprobe.d/kvm.conf # Add support for nested virtualization
|
IOMMU_ARGS="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 "nested=1" /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
|
%end # End of the %post section
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -2,12 +2,27 @@
|
|||||||
|
|
||||||
%post --nochroot --log=/mnt/sysimage/root/hypervisor-intelcpu-post.log # Beginning of %post section. Those commands are executed outside the chroot environment. Logging is enabled to help with post-installation troubleshooting
|
%post --nochroot --log=/mnt/sysimage/root/hypervisor-intelcpu-post.log # Beginning of %post section. Those commands are executed outside the chroot environment. Logging is enabled to help with post-installation troubleshooting
|
||||||
|
|
||||||
if [ -f /mnt/sysimage/etc/kernel/cmdline ]; then # systemd-boot: kernel arguments live in /etc/kernel/cmdline
|
# Kernel arguments cannot be attached to a single canonical location at %post time:
|
||||||
grep -q "intel_iommu=on" /mnt/sysimage/etc/kernel/cmdline || sed -i 's/$/ intel_iommu=on iommu=pt rd.driver.pre=vfio-pci/' /mnt/sysimage/etc/kernel/cmdline # Append IOMMU arguments once
|
# anaconda always creates /etc/kernel/cmdline (kernel-install source for both
|
||||||
else # GRUB: kernel arguments live in /etc/default/grub
|
# systemd-boot and GRUB+BLS layouts), but GRUB+BLS boot entries have already been
|
||||||
sed -i 's/\(quiet\)/\1 intel_iommu=on iommu=pt rd.driver.pre=vfio-pci/i' /mnt/sysimage/etc/default/grub # Load kernel modules in GRUB.
|
# 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 "intel_iommu=on" /mnt/sysimage/etc/kernel/cmdline || echo " $IOMMU_ARGS" >> /mnt/sysimage/etc/kernel/cmdline # Append IOMMU arguments once
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "options kvm_intel nested=1" >> /mnt/sysimage/etc/modprobe.d/kvm.conf # Add support for nested virtualization on Intel CPUs
|
if [ -f /mnt/sysimage/etc/default/grub ]; then # GRUB: arguments for grub.cfg-based boot flows
|
||||||
|
grep -q "intel_iommu=on" /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 "intel_iommu=on" "$bls_entry" || sed -i "s/^\(options .*\)/\1 $IOMMU_ARGS/" "$bls_entry" # Patch the options line of each entry once
|
||||||
|
done
|
||||||
|
|
||||||
|
grep -q "nested=1" /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
|
||||||
|
|
||||||
%end # End of the %post section
|
%end # End of the %post section
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ gnome-initial-setup # Add GNOME initial setup too to let user create local accou
|
|||||||
|
|
||||||
%end # End of the packages section
|
%end # End of the packages section
|
||||||
|
|
||||||
# %post --nochroot --log=/mnt/sysimage/root/initial-setup-gnome.log # Beginning of %post section. Those commands are executed outside the chroot environment. Add logging.
|
%post --nochroot --log=/mnt/sysimage/root/initial-setup-gnome.log # Beginning of %post section. Those commands are executed outside the chroot environment. Add logging.
|
||||||
#
|
|
||||||
# truncate -s 0 /mnt/sysimage/usr/share/gnome-initial-setup/vendor.conf # remove content of vendor.conf so that all options are made available
|
truncate -s 0 /mnt/sysimage/usr/share/gnome-initial-setup/vendor.conf # remove content of vendor.conf so that all options are made available
|
||||||
#
|
|
||||||
# ## Append lines to existing vendor.conf file, so that options are skipped upon reboot
|
## Append lines to existing vendor.conf file, so that options are skipped upon reboot
|
||||||
# cat >> /mnt/sysimage/usr/share/gnome-initial-setup/vendor.conf<< EOF
|
cat >> /mnt/sysimage/usr/share/gnome-initial-setup/vendor.conf<< EOF
|
||||||
# [pages]
|
[pages]
|
||||||
# skip=privacy
|
skip=privacy
|
||||||
# [goa]
|
[goa]
|
||||||
# providers=local-first!
|
providers=local-first!
|
||||||
# EOF
|
EOF
|
||||||
#
|
|
||||||
# %end # End of the %post section
|
%end # End of the %post section
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# BIOS boot storage configuration (GPT disk label with a BIOS boot partition)
|
||||||
|
|
||||||
|
zerombr # Destroy all the contents of disks with invalid partition tables or other formatting unrecognizable to the installer
|
||||||
|
clearpart --all --initlabel --disklabel=gpt # Erase all partitions, initialize a GPT disk label, and initialize the disk label to the default for the target architecture
|
||||||
|
|
||||||
|
part biosboot --fstype="biosboot" --size=2 --label=biosboot # Creates a 2 MiB BIOS boot partition, required to install GRUB on a GPT-labelled disk under legacy BIOS firmware
|
||||||
|
part /boot --fstype="ext4" --size=2048 --label=boot # Creates a 2048 MiB ext4 boot partition
|
||||||
|
part / --fstype="ext4" --grow --label=root --mkfsoptions="-O encrypt,fast_commit" # Create a single root partition with the remaining space
|
||||||
@@ -6,3 +6,70 @@ clearpart --all --initlabel # Erase all partitions and Initializes the disk labe
|
|||||||
part /boot/efi --fstype="efi" --size=512 --fsoptions="umask=0077,shortname=winnt" --label=efi # Creates a 512 MiB EFI system partition
|
part /boot/efi --fstype="efi" --size=512 --fsoptions="umask=0077,shortname=winnt" --label=efi # Creates a 512 MiB EFI system partition
|
||||||
part /boot --fstype="ext4" --size=2048 --label=boot # Creates a 2048 MiB ext4 boot partition
|
part /boot --fstype="ext4" --size=2048 --label=boot # Creates a 2048 MiB ext4 boot partition
|
||||||
part / --fstype="ext4" --grow --label=root --mkfsoptions="-O encrypt,fast_commit" --encrypted --passphrase= # Create a single encrypted root partition with the remaining space.
|
part / --fstype="ext4" --grow --label=root --mkfsoptions="-O encrypt,fast_commit" --encrypted --passphrase= # Create a single encrypted root partition with the remaining space.
|
||||||
|
|
||||||
|
# Tools for LUKS management and TPM2-based auto-unlock
|
||||||
|
%packages --exclude-weakdeps
|
||||||
|
|
||||||
|
cryptsetup # Userspace tool for dm-crypt, required to open and manage the LUKS root device
|
||||||
|
tpm2-tools # TPM2 tools, provides the dracut tpm2-tss module needed to rebuild the initrd for TPM2 auto-unlock
|
||||||
|
tpm2-tss # TPM2 TSS libraries, runtime dependency of tpm2-tools and systemd-cryptenroll
|
||||||
|
|
||||||
|
%end # End of the packages section
|
||||||
|
|
||||||
|
# Stash a preseeded LUKS passphrase so the %post below can enroll TPM2-based
|
||||||
|
# auto-unlock. Only acts when `--passphrase=` was preseeded on the encrypted
|
||||||
|
# `part /` line; with the interactive default (empty passphrase) nothing is
|
||||||
|
# stashed and the %post enrollment is skipped cleanly.
|
||||||
|
%pre --logfile=/tmp/luks-tpm-pre.log
|
||||||
|
|
||||||
|
KS_FILE=""
|
||||||
|
for candidate in /run/install/ks.cfg /tmp/ks.cfg; do
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
KS_FILE="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$KS_FILE" ]; then
|
||||||
|
LINE=$(grep -E '^[[:space:]]*part[[:space:]]+/[[:space:]].*--encrypted' "$KS_FILE" | grep -- '--passphrase=' | head -n 1)
|
||||||
|
if [ -n "$LINE" ]; then
|
||||||
|
PW=${LINE#*--passphrase=}
|
||||||
|
case "$PW" in
|
||||||
|
\"*) PW=${PW#\"}; PW=${PW%%\"*} ;;
|
||||||
|
*) PW=${PW%%[[:space:]]*} ;;
|
||||||
|
esac
|
||||||
|
if [ -n "$PW" ]; then
|
||||||
|
printf '%s' "$PW" > /tmp/.luks-passphrase
|
||||||
|
chmod 600 /tmp/.luks-passphrase
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
%end
|
||||||
|
|
||||||
|
# Enroll TPM2-based LUKS auto-unlock (PCR 7-bound) when both guards hold: a
|
||||||
|
# TPM2 device is available at install time and a passphrase was preseeded in
|
||||||
|
# the kickstart (stashed by the %pre above). Otherwise the system keeps the
|
||||||
|
# regular passphrase prompt at boot. On any failure the LUKS configuration is
|
||||||
|
# left untouched, so the worst case is a passphrase prompt on every boot.
|
||||||
|
%post --logfile=/root/luks-tpm-post.log
|
||||||
|
|
||||||
|
if [ -e /dev/tpmrm0 ] && [ -s /tmp/.luks-passphrase ]; then
|
||||||
|
set -x
|
||||||
|
LUKSUUID=$(awk '$2 ~ /^UUID=/ {sub(/^UUID=/, "", $2); print $2}' /etc/crypttab)
|
||||||
|
LUKSDEV="/dev/disk/by-uuid/$LUKSUUID"
|
||||||
|
if systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 --unlock-key-file=/tmp/.luks-passphrase "$LUKSDEV"; then
|
||||||
|
sed -i 's| none discard,x-initrd.attach| tpm2-device=auto,discard,x-initrd.attach|' /etc/crypttab
|
||||||
|
if ! grep -q 'tpm2-device=auto' /etc/crypttab; then
|
||||||
|
sed -i -E 's|[[:space:]]none([[:space:]]|$)| tpm2-device=auto,discard\1|' /etc/crypttab
|
||||||
|
fi
|
||||||
|
if grep -q 'tpm2-device=auto' /etc/crypttab; then
|
||||||
|
MID=$(cat /etc/machine-id)
|
||||||
|
KVER=$(ls /usr/lib/modules | head -n 1)
|
||||||
|
dracut -f --kver "$KVER" "/boot/efi/$MID/$KVER/initrd"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
shred -u /tmp/.luks-passphrase 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
%end
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ choices:
|
|||||||
storage:
|
storage:
|
||||||
standard: storage/standard.ks
|
standard: storage/standard.ks
|
||||||
encrypted: storage/encrypted.ks
|
encrypted: storage/encrypted.ks
|
||||||
|
biosboot: storage/biosboot.ks
|
||||||
|
livefs: live/core/storage.ks
|
||||||
bootloader:
|
bootloader:
|
||||||
grub: bootloader/grub.ks
|
grub: bootloader/grub.ks
|
||||||
systemd-boot: bootloader/systemd-boot.ks
|
systemd-boot: bootloader/systemd-boot.ks
|
||||||
@@ -60,11 +62,21 @@ features:
|
|||||||
- hypervisor/base/services.ks
|
- hypervisor/base/services.ks
|
||||||
- hypervisor/base/post-scripts.ks
|
- hypervisor/base/post-scripts.ks
|
||||||
desktop:
|
desktop:
|
||||||
|
- hypervisor/base/packages.ks
|
||||||
|
- hypervisor/base/services.ks
|
||||||
|
- hypervisor/base/post-scripts.ks
|
||||||
- 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
|
||||||
hardware-support: packages/hardware-support.ks
|
hardware-support: packages/hardware-support.ks
|
||||||
guest-agents: guest-agents/base.ks
|
guest-agents: guest-agents/base.ks
|
||||||
|
live:
|
||||||
|
true:
|
||||||
|
- live/core/base.ks
|
||||||
|
- live/core/packages.ks
|
||||||
|
- live/post/base.ks
|
||||||
|
- live/post/session.ks
|
||||||
+100
-36
@@ -2,59 +2,123 @@
|
|||||||
# 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
|
||||||
|
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
|
||||||
|
storage: biosboot
|
||||||
|
bootloader: grub
|
||||||
|
security: enabled
|
||||||
|
initial-setup: gnome
|
||||||
hardware-support: true
|
hardware-support: true
|
||||||
guest-agents: false
|
guest-agents: false
|
||||||
|
hypervisor: desktop
|
||||||
|
hypervisor_type: any
|
||||||
|
|
||||||
|
# Encrypted root
|
||||||
|
- name: exp-encrypted-headless
|
||||||
|
tier: experimental
|
||||||
|
variants:
|
||||||
|
- repository: "44"
|
||||||
|
storage: encrypted
|
||||||
|
bootloader: systemd-boot
|
||||||
|
security: enabled
|
||||||
|
initial-setup: server
|
||||||
|
hardware-support: true
|
||||||
|
guest-agents: true
|
||||||
hypervisor: base
|
hypervisor: base
|
||||||
hypervisor_type: intelcpu
|
hypervisor_type: any
|
||||||
#
|
|
||||||
# # Desktop-hypervisor variants
|
# Rawhide
|
||||||
# - name: desktop-hypervisor
|
- name: exp-rawhide-headless
|
||||||
# variants:
|
tier: experimental
|
||||||
# - repository: 43
|
variants:
|
||||||
# desktop: gnome
|
- repository: rawhide
|
||||||
# storage: standard
|
storage: standard
|
||||||
# bootloader: grub
|
bootloader: systemd-boot
|
||||||
# hardware-support: false
|
security: enabled
|
||||||
# guest-agents: true
|
initial-setup: server
|
||||||
# hypervisor: desktop
|
hardware-support: true
|
||||||
# hypervisor_type: ["amdcpu", "intelcpu"]
|
guest-agents: true
|
||||||
|
hypervisor: base
|
||||||
|
hypervisor_type: any
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
PyYAML>=6.0
|
PyYAML>=6.0
|
||||||
pykickstart>=1.99
|
pykickstart>=1.99
|
||||||
|
pytest>=7.0
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ BASE_FRAGMENTS = [
|
|||||||
"core/locale.ks",
|
"core/locale.ks",
|
||||||
"core/network.ks",
|
"core/network.ks",
|
||||||
"core/services.ks",
|
"core/services.ks",
|
||||||
"packages/core.ks",
|
"packages/core-explicit.ks",
|
||||||
"packages/fedora-remix.ks",
|
"packages/fedora-remix.ks",
|
||||||
"packages/hand-picked.ks",
|
"packages/hand-picked.ks",
|
||||||
]
|
]
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+4
-29
@@ -141,35 +141,10 @@ fi
|
|||||||
# Display the selected option (optional)
|
# Display the selected option (optional)
|
||||||
echo "You selected: $uri"
|
echo "You selected: $uri"
|
||||||
|
|
||||||
# Get a list of files in "dishes" directory
|
# Pick a dish (shared with build-image.sh)
|
||||||
mapfile -t dish_name < <(find "cook/dishes/" -maxdepth 1 -type f \( -name "*guest-agents*" \) -printf "%f\n" | sed 's/\.[^.]*$//')
|
# shellcheck source=./select-dish.sh
|
||||||
|
source "$(dirname "$0")/select-dish.sh"
|
||||||
# Check if there are any files
|
vm_name="$selected_dish"
|
||||||
if [ ${#dish_name[@]} -eq 0 ]; then
|
|
||||||
echo "No files found in the directory ../dishes."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Display the files with numbered options
|
|
||||||
echo "Available files:"
|
|
||||||
for i in "${!dish_name[@]}"; do
|
|
||||||
echo "$((i + 1)). ${dish_name[$i]}"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Prompt the user to select a file
|
|
||||||
read -r -p "Enter the number of the file you want to select: " choice
|
|
||||||
|
|
||||||
# Validate the user's choice
|
|
||||||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 )) || (( choice > ${#dish_name[@]} )); then
|
|
||||||
echo "Invalid choice. Please enter a number from 1 to ${#dish_name[@]}."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get the selected filename
|
|
||||||
vm_name="${dish_name[$((choice - 1))]}"
|
|
||||||
|
|
||||||
# Output the selected filename
|
|
||||||
echo "You selected: $vm_name"
|
|
||||||
|
|
||||||
# Find Fedora ISO based on the dish name
|
# Find Fedora ISO based on the dish name
|
||||||
fedora_iso=$(find_fedora_iso "$vm_name")
|
fedora_iso=$(find_fedora_iso "$vm_name")
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Lists cook/dishes/*guest-agents*.cfg and prompts the user to pick one.
|
||||||
|
# Meant to be sourced (not executed) from repo root, after `cook && make all`
|
||||||
|
# has regenerated dishes. Sets $selected_dish to the chosen dish's basename
|
||||||
|
# (no extension) on success, or exits non-zero.
|
||||||
|
|
||||||
|
mapfile -t dish_name < <(find "cook/dishes/" -maxdepth 1 -type f \( -name "*guest-agents*" \) -printf "%f\n" | sed 's/\.[^.]*$//')
|
||||||
|
|
||||||
|
if [ ${#dish_name[@]} -eq 0 ]; then
|
||||||
|
echo "No files found in cook/dishes/."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Available dishes:"
|
||||||
|
for i in "${!dish_name[@]}"; do
|
||||||
|
echo "$((i + 1)). ${dish_name[$i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
read -r -p "Enter the number of the dish you want to select: " choice
|
||||||
|
|
||||||
|
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 )) || (( choice > ${#dish_name[@]} )); then
|
||||||
|
echo "Invalid choice. Please enter a number from 1 to ${#dish_name[@]}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
selected_dish="${dish_name[$((choice - 1))]}"
|
||||||
|
echo "You selected: $selected_dish"
|
||||||
Executable
+206
@@ -0,0 +1,206 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Smoke-test a disk image produced by build-image.sh.
|
||||||
|
#
|
||||||
|
# Usage: ./smoke-test.sh [--timeout SECONDS] IMAGE
|
||||||
|
#
|
||||||
|
# 1. Static checks, read-only, without booting: if the image declares
|
||||||
|
# SELinux (SELINUX= in /etc/selinux/config is not "disabled"), no boot
|
||||||
|
# entry and not /etc/kernel/cmdline may carry selinux=0, and files must be
|
||||||
|
# labeled. Catches builds on a host with SELinux disabled: anaconda then
|
||||||
|
# inherits the host's selinux=0 and overrides the kickstart's
|
||||||
|
# `selinux --enforcing` (see devices/runner.md in inventory-of-devices).
|
||||||
|
# 2. Boot test: boots the image under KVM with -snapshot (the image is never
|
||||||
|
# written) and no network, and waits for qemu-guest-agent to answer
|
||||||
|
# guest-sync. The agent only starts once userspace is up, so this proves
|
||||||
|
# firmware, bootloader, kernel, initramfs, root mount and systemd all work,
|
||||||
|
# without depending on a serial console being configured in the image.
|
||||||
|
#
|
||||||
|
# Requires: root (loop devices, mount), qemu-system-x86_64 with /dev/kvm,
|
||||||
|
# edk2-ovmf (UEFI images), e2fsprogs (debugfs), python3. The image must
|
||||||
|
# include qemu-guest-agent (the guest-agents ingredient).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TIMEOUT=300
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--timeout)
|
||||||
|
TIMEOUT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: $0 [--timeout SECONDS] IMAGE"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
IMAGE="${1:?Usage: $0 [--timeout SECONDS] IMAGE}"
|
||||||
|
[ -f "$IMAGE" ] || { echo "no such image: $IMAGE"; exit 1; }
|
||||||
|
|
||||||
|
OVMF_CODE=/usr/share/edk2/ovmf/OVMF_CODE.fd
|
||||||
|
OVMF_VARS=/usr/share/edk2/ovmf/OVMF_VARS.fd
|
||||||
|
|
||||||
|
WORK="$(mktemp -d "${TMPDIR:-/tmp}/smoke-test.XXXXXX")"
|
||||||
|
LOOP=""
|
||||||
|
QEMU_PID=""
|
||||||
|
cleanup() {
|
||||||
|
[ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true
|
||||||
|
mountpoint -q "$WORK/esp" && umount "$WORK/esp" || true
|
||||||
|
[ -n "$LOOP" ] && losetup -d "$LOOP" || true
|
||||||
|
rm -rf "$WORK"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
fail() { echo "FAIL: $*"; exit 1; }
|
||||||
|
|
||||||
|
# --- 1. static checks ---------------------------------------------------------
|
||||||
|
|
||||||
|
LOOP="$(losetup --read-only --partscan --find --show "$IMAGE")"
|
||||||
|
udevadm settle
|
||||||
|
# Filesystem labels come from the storage ingredients (root, boot); the ESP is
|
||||||
|
# matched by partition type instead, as its FAT label ends up upper-cased.
|
||||||
|
part_by_label() { lsblk -rno PATH,LABEL "$LOOP" | awk -v l="$1" '$2 == l { print $1; exit }'; }
|
||||||
|
ROOT_PART="$(part_by_label root)"
|
||||||
|
BOOT_PART="$(part_by_label boot)"
|
||||||
|
ESP_PART="$(lsblk -rno PATH,PARTTYPE "$LOOP" | awk '$2 == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" { print $1; exit }')"
|
||||||
|
[ -n "$ROOT_PART" ] || fail "no partition labeled 'root' in $IMAGE"
|
||||||
|
|
||||||
|
rootcat() { debugfs -R "cat $1" "$ROOT_PART" 2>/dev/null; }
|
||||||
|
selinux_mode="$(rootcat /etc/selinux/config | sed -n 's/^SELINUX=//p')"
|
||||||
|
echo "SELINUX=${selinux_mode:-<unset>} in /etc/selinux/config"
|
||||||
|
|
||||||
|
# Collect every kernel command line the image boots with.
|
||||||
|
cmdlines="$WORK/cmdlines"
|
||||||
|
: > "$cmdlines"
|
||||||
|
{ echo "== /etc/kernel/cmdline"; rootcat /etc/kernel/cmdline; } >> "$cmdlines"
|
||||||
|
if [ -n "$ESP_PART" ]; then
|
||||||
|
mkdir -p "$WORK/esp"
|
||||||
|
mount -o ro "$ESP_PART" "$WORK/esp"
|
||||||
|
for f in "$WORK"/esp/loader/entries/*.conf; do
|
||||||
|
[ -e "$f" ] && { echo "== ESP ${f#"$WORK"/esp/}"; grep '^options' "$f"; } >> "$cmdlines"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ -n "$BOOT_PART" ]; then
|
||||||
|
for f in $(debugfs -R "ls /loader/entries" "$BOOT_PART" 2>/dev/null | grep -o '[^ ]*\.conf'); do
|
||||||
|
{ echo "== /boot/loader/entries/$f"; debugfs -R "cat /loader/entries/$f" "$BOOT_PART" 2>/dev/null | grep '^options'; } >> "$cmdlines"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
cat "$cmdlines"
|
||||||
|
|
||||||
|
if [ -n "$selinux_mode" ] && [ "$selinux_mode" != disabled ]; then
|
||||||
|
if grep -qw 'selinux=0' "$cmdlines"; then
|
||||||
|
fail "image declares SELINUX=$selinux_mode but boots with selinux=0 (built on a host with SELinux disabled?)"
|
||||||
|
fi
|
||||||
|
for f in /etc/shadow /usr/bin/bash; do
|
||||||
|
label="$(debugfs -R "ea_get $f security.selinux" "$ROOT_PART" 2>/dev/null | sed -n 's/.*= "\(.*\)\\000"$/\1/p')"
|
||||||
|
echo "label $f: ${label:-<none>}"
|
||||||
|
[ -n "$label" ] || fail "$f has no SELinux label (built on a host with SELinux disabled?)"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo "static checks: OK"
|
||||||
|
|
||||||
|
mountpoint -q "$WORK/esp" && umount "$WORK/esp"
|
||||||
|
losetup -d "$LOOP"
|
||||||
|
LOOP=""
|
||||||
|
|
||||||
|
# --- 2. boot test -------------------------------------------------------------
|
||||||
|
|
||||||
|
firmware=()
|
||||||
|
if [ -n "$ESP_PART" ]; then
|
||||||
|
cp "$OVMF_VARS" "$WORK/vars.fd"
|
||||||
|
firmware=(-drive "if=pflash,format=raw,readonly=on,file=$OVMF_CODE"
|
||||||
|
-drive "if=pflash,format=raw,file=$WORK/vars.fd")
|
||||||
|
fi
|
||||||
|
|
||||||
|
qemu-system-x86_64 \
|
||||||
|
-name smoke-test -machine q35,accel=kvm -cpu host -smp 2 -m 2048 \
|
||||||
|
"${firmware[@]}" \
|
||||||
|
-drive "file=$IMAGE,format=raw,if=virtio,snapshot=on" \
|
||||||
|
-nic none -display none -monitor none \
|
||||||
|
-serial "file:$WORK/serial.log" \
|
||||||
|
-chardev "socket,id=qga0,path=$WORK/qga.sock,server=on,wait=off" \
|
||||||
|
-device virtio-serial \
|
||||||
|
-device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 &
|
||||||
|
QEMU_PID=$!
|
||||||
|
|
||||||
|
echo "booting (timeout ${TIMEOUT}s), waiting for qemu-guest-agent..."
|
||||||
|
if python3 - "$WORK/qga.sock" "$TIMEOUT" "$QEMU_PID" <<'EOF'
|
||||||
|
import json, os, socket, sys, time
|
||||||
|
|
||||||
|
path, timeout, qemu_pid = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
|
||||||
|
def qemu_alive():
|
||||||
|
try:
|
||||||
|
os.kill(qemu_pid, 0)
|
||||||
|
return True
|
||||||
|
except ProcessLookupError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
while not os.path.exists(path):
|
||||||
|
if not qemu_alive():
|
||||||
|
sys.exit("qemu exited before creating the guest agent socket")
|
||||||
|
if time.monotonic() > deadline:
|
||||||
|
sys.exit("qemu did not create the guest agent socket within %ds" % timeout)
|
||||||
|
time.sleep(0.5)
|
||||||
|
sock = socket.socket(socket.AF_UNIX)
|
||||||
|
sock.connect(path)
|
||||||
|
buf = b""
|
||||||
|
sync_id = 0
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if not qemu_alive():
|
||||||
|
sys.exit("qemu exited before the guest agent answered")
|
||||||
|
# guest-sync with a fresh id flushes anything stale in the channel; the
|
||||||
|
# agent echoes the id back once it runs.
|
||||||
|
sync_id += 1
|
||||||
|
sock.sendall(json.dumps({"execute": "guest-sync", "arguments": {"id": sync_id}}).encode() + b"\n")
|
||||||
|
sock.settimeout(5)
|
||||||
|
try:
|
||||||
|
while b"\n" not in buf:
|
||||||
|
chunk = sock.recv(4096)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
buf += chunk
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
line, _, buf = buf.partition(b"\n")
|
||||||
|
try:
|
||||||
|
reply = json.loads(line)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
# Replies can lag behind: syncs sent while the agent was still starting
|
||||||
|
# are answered in order, so accept any id we have sent.
|
||||||
|
ret = reply.get("return") if isinstance(reply, dict) else None
|
||||||
|
if isinstance(ret, int) and 1 <= ret <= sync_id:
|
||||||
|
# Drain whatever else is queued before asking for osinfo.
|
||||||
|
sock.settimeout(1)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
chunk = sock.recv(4096)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
except socket.timeout:
|
||||||
|
pass
|
||||||
|
buf = b""
|
||||||
|
sock.sendall(b'{"execute": "guest-get-osinfo"}\n')
|
||||||
|
sock.settimeout(10)
|
||||||
|
while b"\n" not in buf:
|
||||||
|
buf += sock.recv(4096)
|
||||||
|
info = json.loads(buf.partition(b"\n")[0]).get("return", {})
|
||||||
|
print("guest agent answered after %ds: %s, kernel %s"
|
||||||
|
% (timeout - (deadline - time.monotonic()), info.get("pretty-name"), info.get("kernel-release")))
|
||||||
|
sys.exit(0)
|
||||||
|
sys.exit("guest agent did not answer within %ds" % timeout)
|
||||||
|
EOF
|
||||||
|
then
|
||||||
|
echo "boot test: OK"
|
||||||
|
else
|
||||||
|
echo "--- qemu-guest-agent on the serial console:"
|
||||||
|
grep -a 'qemu-guest-agent\|Guest Agent' "$WORK/serial.log" 2>/dev/null || echo "(no mention)"
|
||||||
|
echo "--- last 40 lines of the serial console:"
|
||||||
|
tail -40 "$WORK/serial.log" 2>/dev/null || true
|
||||||
|
fail "boot test"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user