Skip to content

server profile: fix desktop assumptions that abort headless/Cloud runs - #32

Open
edmondscommerce wants to merge 2 commits into
LongTermSupport:F44from
edmondscommerce:plan-00161-headless-server-support-fixes
Open

server profile: fix desktop assumptions that abort headless/Cloud runs#32
edmondscommerce wants to merge 2 commits into
LongTermSupport:F44from
edmondscommerce:plan-00161-headless-server-support-fixes

Conversation

@edmondscommerce

@edmondscommerce edmondscommerce commented Jul 21, 2026

Copy link
Copy Markdown

Server profile: fix desktop assumptions that abort headless/Cloud runs

Proving the provisioning_profile=server path on a minimal Fedora 44 Cloud Base VM
surfaced six plays that assume a desktop (hardware, firmware, GUI XDG dirs, a local
firewalld, or desktop-only tooling) and therefore hard-fail under any_errors_fatal: true
on a server/VM. The GNOME/GUI profile guard already works correctly (the ~35 desktop plays
skip); these are the remaining gaps.

Every change is a pure addition — the desktop path is unchanged, so there is zero
regression risk to existing desktop users. All proven end-to-end on a real Cloud VM (green
through podman + ccy + claude-code).

1. play-AB-dnf-upgrade.yml — kernel half-install cleanup (CRITICAL: would brick a VM)

The kernel half-install cleanup enumerates rpm -q kernel-modules. A minimal Cloud/Server
image legitimately ships kernel-core + kernel-modules-core and no full
kernel-modules — a valid minimal kernel, not a half-install. On a server this either
(a) hard-fails on rpm -q kernel-modules (rc 1) under any_errors_fatal, or (b) if
made tolerant, computes half_installed_kernels = every kernel and the removal dnf
task would remove kernel-core + kernel-modules-core for all of them → an unbootable
box.

Fix: default half_installed_kernels: [] first, then gate the enumerate/identify tasks
on provisioning_profile != 'server'.

2. play-basic-configs.yml — fwupd firmware updates

"Check and Apply Firmware Updates via fwupd" runs fwupdmgr, absent on a minimal Cloud/VM
image → rc 127 aborts the run.

Fix: a command -v fwupdmgr presence-check gate — bare-metal servers with fwupd still
get firmware updates while VMs/minimal images skip.

3. play-podman.yml — podman-compose needs pip3

"Install podman-compose" uses the pip module, which fails with "Unable to find any of pip3
to use" — a minimal Cloud image has no python3-pip.

Fix: add python3-pip to the play's "Install Podman" dnf list.

4. play-markless.yml — assumes ~/Downloads exists

The install shell does cd ~/Downloads, a desktop XDG dir absent on minimal Cloud → cd
aborts the run.

Fix: mkdir -p ~/Downloads before cd (no-op on desktop).

5. play-vpn.yml — OpenVPN firewalld rule needs python3-firewall

"Allow OpenVPN Through Firewall" uses the firewalld module, which needs python3-firewall
(absent on a minimal Cloud image). The play already keeps the VPN CLI packages and already
server-guards its GNOME applet task.

Fix: the same when: provisioning_profile != 'server' guard on the firewall-rule task.
A headless server is firewalled at the network edge and runs no local firewalld.

6. play-lxc-install-config.yml — desktop-only; server-skip it

This play is desktop/dev-workstation tooling: it hard-requires the container engine, uses
the firewalld module (python3-firewall, absent on minimal Cloud), and clones "LXC Bash"
over a GitHub SSH url. None of that applies to a headless server (podman is the runtime
there), and the firewalld import aborts the run.

Fix: classify it as desktop-only like firefox/vscode/browsers/comms/
terminal-emulators (all scope: gnome): set scope: gnome and add the standard two-task
scope guard so it cleanly end_plays on the server profile.

Proven end-to-end

With these fixes, ansible-playbook playbooks/playbook-main.yml -e provisioning_profile=server
runs green on a Fedora 44 Cloud VM through dnf-upgrade → basic-configs → GitHub CLI → RPM
Fusion → podman → python → claude-yolo (ccy) image build → claude-code → markless →
VPN → repo-cleanup, with the GNOME/GUI/desktop plays (LXC included) skipping. The ccy
container image builds and podman image inspect claude-yolo:latest verifies; the
claude-code CLI installs and runs.


🤖 Generated with Claude Code

Proving provisioning_profile=server on a minimal Fedora 44 Cloud Base VM surfaced
five scope:general plays that assume a desktop (hardware, firmware, a GUI XDG dir,
or a local firewalld) and hard-fail under any_errors_fatal on a server/VM. The
GNOME/GUI profile guard already works (the ~35 desktop plays skip); these are the
remaining gaps. Every change is a pure addition — the desktop path is unchanged,
so there is zero regression risk to existing desktop users. All proven end-to-end
on a real Cloud VM (green through ccy + claude-code + markless + VPN).

1. play-AB-dnf-upgrade.yml (CRITICAL): the kernel half-install cleanup enumerates
   `rpm -q kernel-modules`. A minimal Cloud/Server image legitimately ships
   kernel-core + kernel-modules-core and NO full kernel-modules. On server this
   either hard-fails on `rpm -q kernel-modules` (rc 1) or, if made tolerant, treats
   EVERY kernel as half-installed and dnf-removes kernel-core + kernel-modules-core
   -> unbootable. Fix: default half_installed_kernels to [] then gate the
   enumerate/identify tasks on provisioning_profile != 'server'.

2. play-basic-configs.yml: the fwupd firmware task runs fwupdmgr, absent on a
   minimal Cloud image (rc 127). Fix: a `command -v fwupdmgr` presence-check gate,
   so bare-metal servers with fwupd still get firmware updates while VMs skip.

3. play-podman.yml: podman-compose uses the pip module but a minimal Cloud image
   has no pip3. Fix: add python3-pip to the engine dnf install list.

4. play-markless.yml: the install shell does `cd ~/Downloads`, a desktop XDG dir
   absent on minimal Cloud. Fix: `mkdir -p ~/Downloads` before cd (no-op on desktop).

5. play-vpn.yml: "Allow OpenVPN Through Firewall" uses the firewalld module, which
   needs python3-firewall (absent on minimal Cloud). The play already keeps the VPN
   CLI packages and already server-guards its GNOME applet task. Fix: the same
   `when: provisioning_profile != 'server'` guard on the firewall-rule task.

play-lxc-install-config.yml (also firewalld, plus a hard Docker + GitHub-SSH
dependency) is a desktop/dev-workstation play and is intentionally left out —
LXC-on-server needs its own decision, not a forced-green here.
…(bug 3)

play-lxc-install-config.yml is desktop/dev-workstation tooling: it hard-requires
the container engine to be present, uses the firewalld module (python3-firewall,
absent on a minimal Cloud image), and clones "LXC Bash" over a GitHub SSH url.
None of that applies to a headless dev/prod server (podman is the runtime there),
and under any_errors_fatal the firewalld import aborts the whole run.

Classify it as desktop-only, exactly like firefox/vscode/browsers/comms/
terminal-emulators (all scope: gnome): set scope: gnome and add the standard
two-task scope guard so the play cleanly end_play's on provisioning_profile=server.
Proven: the full server run now skips LXC and continues to podman + ccy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant