diff --git a/cookbooks/base/Makefile b/cookbooks/base/Makefile index 7fb20ef..e3ded57 100644 --- a/cookbooks/base/Makefile +++ b/cookbooks/base/Makefile @@ -1,6 +1,4 @@ -SYSTEMD_MAIN_UNIT_NAMES := var-lib-virtiofs-data.mount -SYSTEMD_MAIN_UNIT_NAMES += rpm-ostree-install-qemu-guest-agent.service SYSTEMD_MAIN_UNIT_NAMES += install-tools.service # .path units are not picked up by common.mk's wildcard (only .service/.target/ # .timer/.mount), so install and enable/start it explicitly. It triggers the @@ -16,15 +14,6 @@ include ../../scripts/common.mk $(TARGET_CHROOT)/etc/quadlets/base/install-tools.d: install -d -m 0755 -o root -g root -D $@ -pre-requisites:: - @set -Eeuo pipefail; \ - for tool in rpm-ostree; do \ - if ! which $$tool &>/dev/null ; then \ - echo "$$tool is not installed. Please install it first." >&2; \ - exit 1; \ - fi ; \ - done - install-actions-pre:: @set -Eeuo pipefail; \ run() { echo $$*; "$$@"; }; \ diff --git a/cookbooks/base/README.md b/cookbooks/base/README.md index 11b49aa..0df11e7 100644 --- a/cookbooks/base/README.md +++ b/cookbooks/base/README.md @@ -2,20 +2,24 @@ ## Overview -The base cookbook provides foundational configuration for Fedora CoreOS systems. It includes: +The base cookbook provides the foundational configuration shared by all cookbooks, whatever the underlying operating system. It includes: -- **fastfetch**: A system information tool displayed on login, with color-coded output (red for root, blue for regular users). -- **tmpfiles configuration**: Sets up required directories such as `/var/lib/virtiofs/data`. -- **QEMU guest agent**: Optional installation for better VM integration. -- **SSH key persistence**: Backs up and restores SSH host keys across reboots. -- **resolv.conf resync**: Keeps `Network=host` containers in sync with the host's DNS. +- **Tools installation**: the **install-tools.service** oneshot runs every script found in `/etc/quadlets/base/install-tools.d/`. + Versions are pinned in `/etc/quadlets/base/install-tools.env`. + The service is skipped when this file is absent. +- **fastfetch**: a system information tool installed by **install-tools.service** and displayed on login, with color-coded output (red for root, blue for regular users). +- **resolv.conf resync**: keeps `Network=host` containers in sync with the host's DNS. With `Network=host`, Podman snapshots `/etc/resolv.conf` at container creation and bind-mounts the copy, so a later DNS change (e.g. a DHCP renewal) never reaches running containers. A `systemd.path` unit watches `/etc/resolv.conf` and a oneshot service rewrites each host-network container's snapshot **in place** (same inode, no restart); bridged containers are left untouched. See [issue #1](https://github.com/nmasse-itix/podman-quadlet-cookbook/issues/1). +- **Container teardown drop-in** (example): a `container.d` drop-in that makes `systemctl stop` wait until the container has actually been removed. +- **Base images**: `fedora.image` and `centos-stream10.image` pull the base images used by other cookbooks to build their own images. +- **Users and groups**: creates the `itix-svc` service account (uid/gid 10000) and, in the Butane spec, the `itix` group (gid 1000) and removes the default `core` user. -This cookbook is used as an implicit dependency for other cookbooks that run on Fedora CoreOS. +This cookbook is an implicit dependency of every other cookbook, except [coreos](../coreos/). +It depends itself implicitly on the [coreos](../coreos/) cookbook, which holds everything specific to Fedora CoreOS. ## Usage @@ -31,7 +35,7 @@ Install the base configuration. sudo make clean install ``` -You should see the **install-fastfetch.service** downloading and installing fastfetch. +You should see the **install-tools.service** downloading and installing fastfetch. On next login, fastfetch will display system information. To verify the installation: diff --git a/cookbooks/base/overlay.bu b/cookbooks/base/overlay.bu index 2413f41..04e0fdb 100644 --- a/cookbooks/base/overlay.bu +++ b/cookbooks/base/overlay.bu @@ -1,31 +1,5 @@ variant: fcos version: 1.4.0 -storage: - directories: - - path: /var/lib/private/sshd - mode: 0700 - files: - - path: /etc/resolv.conf - overwrite: true - contents: - inline: "" # will be updated by NetworkManager - - path: /etc/NetworkManager/conf.d/itix.conf - contents: - inline: | - [main] - # test - rc-manager=file -systemd: - units: - - name: systemd-resolved.service - enabled: false - mask: true - - name: sshd-keygen@.service - dropins: - - name: backup-restore.conf - contents: | - [Service] - ExecStartPost=/bin/bash -c 'if [ -f /var/lib/private/sshd/ssh_host_%i_key ]; then cp -Z /var/lib/private/sshd/ssh_host_%i_key{,.pub} /etc/ssh/; elif [ -f /etc/ssh/ssh_host_%i_key ]; then cp -a /etc/ssh/ssh_host_%i_key{,.pub} /var/lib/private/sshd/; fi' passwd: users: - name: core diff --git a/cookbooks/coreos/Makefile b/cookbooks/coreos/Makefile new file mode 100644 index 0000000..f5ba099 --- /dev/null +++ b/cookbooks/coreos/Makefile @@ -0,0 +1,15 @@ + +SYSTEMD_MAIN_UNIT_NAMES := var-lib-virtiofs-data.mount +SYSTEMD_MAIN_UNIT_NAMES += rpm-ostree-install-qemu-guest-agent.service + +# Include common Makefile +include ../../scripts/common.mk + +pre-requisites:: + @set -Eeuo pipefail; \ + for tool in rpm-ostree; do \ + if ! which $$tool &>/dev/null ; then \ + echo "$$tool is not installed. Please install it first." >&2; \ + exit 1; \ + fi ; \ + done diff --git a/cookbooks/coreos/README.md b/cookbooks/coreos/README.md new file mode 100644 index 0000000..db02db9 --- /dev/null +++ b/cookbooks/coreos/README.md @@ -0,0 +1,46 @@ +# Podman Quadlet: CoreOS + +## Overview + +The coreos cookbook provides the configuration specific to Fedora CoreOS systems. It includes: + +- **virtiofs data mount**: the **discover-virtiofs-tags.service** lists the virtiofs tags exposed by the hypervisor. + When a `data` tag is present, **var-lib-virtiofs-data.mount** mounts it on `/var/lib/virtiofs/data` with the `container_file_t` SELinux context, so containers can use it. +- **tmpfiles configuration**: creates the `/var/lib/virtiofs` directory. +- **QEMU guest agent**: when running on KVM, **rpm-ostree-install-qemu-guest-agent.service** layers `qemu-guest-agent` with `rpm-ostree`, applies it live and starts it. +- **SSH key persistence** (Butane spec only): backs up the SSH host keys in `/var/lib/private/sshd` and restores them on reinstall, so the host keeps its identity. +- **DNS configuration** (Butane spec only): masks `systemd-resolved` and lets NetworkManager write `/etc/resolv.conf` directly (`rc-manager=file`). + +This cookbook is an implicit dependency of every other cookbook, including [base](../base/). +It has no dependencies itself. + +## Usage + +This cookbook requires `rpm-ostree`, so it can only be installed on Fedora CoreOS. + +In a separate terminal, follow the logs. + +```sh +sudo make tail-logs +``` + +Install the CoreOS configuration. + +```sh +sudo make clean install +``` + +On a KVM virtual machine, you should see the **rpm-ostree-install-qemu-guest-agent.service** installing the QEMU guest agent. + +To verify the installation: + +```sh +systemctl status qemu-guest-agent.service +findmnt /var/lib/virtiofs/data +``` + +Finally, remove the configuration and its data. + +```sh +sudo make uninstall clean +``` diff --git a/cookbooks/base/discover-virtiofs-tags.service b/cookbooks/coreos/discover-virtiofs-tags.service similarity index 78% rename from cookbooks/base/discover-virtiofs-tags.service rename to cookbooks/coreos/discover-virtiofs-tags.service index 6e089be..eb15333 100644 --- a/cookbooks/base/discover-virtiofs-tags.service +++ b/cookbooks/coreos/discover-virtiofs-tags.service @@ -12,5 +12,5 @@ DefaultDependencies=no [Service] Type=oneshot -ExecStart=/bin/bash -Eeuo pipefail -c 'shopt -s nullglob; mkdir -p /run/quadlets/base/virtiofs; for tagfile in /sys/fs/virtiofs/*/tag; do tag=$(<"$tagfile"); echo -n > "/run/quadlets/base/virtiofs/$tag"; done' +ExecStart=/bin/bash -Eeuo pipefail -c 'shopt -s nullglob; mkdir -p /run/quadlets/coreos/virtiofs; for tagfile in /sys/fs/virtiofs/*/tag; do tag=$(<"$tagfile"); echo -n > "/run/quadlets/coreos/virtiofs/$tag"; done' RemainAfterExit=yes diff --git a/cookbooks/coreos/overlay.bu b/cookbooks/coreos/overlay.bu new file mode 100644 index 0000000..9a71239 --- /dev/null +++ b/cookbooks/coreos/overlay.bu @@ -0,0 +1,28 @@ +variant: fcos +version: 1.4.0 +storage: + directories: + - path: /var/lib/private/sshd + mode: 0700 + files: + - path: /etc/resolv.conf + overwrite: true + contents: + inline: "" # will be updated by NetworkManager + - path: /etc/NetworkManager/conf.d/itix.conf + contents: + inline: | + [main] + # test + rc-manager=file +systemd: + units: + - name: systemd-resolved.service + enabled: false + mask: true + - name: sshd-keygen@.service + dropins: + - name: backup-restore.conf + contents: | + [Service] + ExecStartPost=/bin/bash -c 'if [ -f /var/lib/private/sshd/ssh_host_%i_key ]; then cp -Z /var/lib/private/sshd/ssh_host_%i_key{,.pub} /etc/ssh/; elif [ -f /etc/ssh/ssh_host_%i_key ]; then cp -a /etc/ssh/ssh_host_%i_key{,.pub} /var/lib/private/sshd/; fi' diff --git a/cookbooks/base/rpm-ostree-install-qemu-guest-agent.service b/cookbooks/coreos/rpm-ostree-install-qemu-guest-agent.service similarity index 100% rename from cookbooks/base/rpm-ostree-install-qemu-guest-agent.service rename to cookbooks/coreos/rpm-ostree-install-qemu-guest-agent.service diff --git a/cookbooks/base/tmpfiles.d/base.conf b/cookbooks/coreos/tmpfiles.d/coreos.conf similarity index 100% rename from cookbooks/base/tmpfiles.d/base.conf rename to cookbooks/coreos/tmpfiles.d/coreos.conf diff --git a/cookbooks/base/var-lib-virtiofs-data.mount b/cookbooks/coreos/var-lib-virtiofs-data.mount similarity index 87% rename from cookbooks/base/var-lib-virtiofs-data.mount rename to cookbooks/coreos/var-lib-virtiofs-data.mount index ff5459e..7613b1b 100644 --- a/cookbooks/base/var-lib-virtiofs-data.mount +++ b/cookbooks/coreos/var-lib-virtiofs-data.mount @@ -4,7 +4,7 @@ Requires=discover-virtiofs-tags.service After=discover-virtiofs-tags.service # Only run if the "data" tag is present (as discovered by discover-virtiofs-tags.service) -ConditionPathExists=/run/quadlets/base/virtiofs/data +ConditionPathExists=/run/quadlets/coreos/virtiofs/data [Mount] What=data diff --git a/cookbooks/seedbox/var-lib-virtiofs-hdd.mount b/cookbooks/seedbox/var-lib-virtiofs-hdd.mount index 4a22f5a..614f052 100644 --- a/cookbooks/seedbox/var-lib-virtiofs-hdd.mount +++ b/cookbooks/seedbox/var-lib-virtiofs-hdd.mount @@ -4,7 +4,7 @@ Requires=discover-virtiofs-tags.service After=discover-virtiofs-tags.service # Only run if the "hdd" tag is present (as discovered by discover-virtiofs-tags.service) -ConditionPathExists=/run/quadlets/base/virtiofs/hdd +ConditionPathExists=/run/quadlets/coreos/virtiofs/hdd [Mount] What=hdd diff --git a/cookbooks/seedbox/var-lib-virtiofs-ssd.mount b/cookbooks/seedbox/var-lib-virtiofs-ssd.mount index 41bbb41..dad9955 100644 --- a/cookbooks/seedbox/var-lib-virtiofs-ssd.mount +++ b/cookbooks/seedbox/var-lib-virtiofs-ssd.mount @@ -4,7 +4,7 @@ Requires=discover-virtiofs-tags.service After=discover-virtiofs-tags.service # Only run if the "ssd" tag is present (as discovered by discover-virtiofs-tags.service) -ConditionPathExists=/run/quadlets/base/virtiofs/ssd +ConditionPathExists=/run/quadlets/coreos/virtiofs/ssd [Mount] What=ssd diff --git a/scripts/common.mk b/scripts/common.mk index bd2235a..18e1813 100644 --- a/scripts/common.mk +++ b/scripts/common.mk @@ -11,6 +11,7 @@ help: @echo " Persistent Storage directory: /var/lib/quadlets/$(PROJECT_NAME)" @echo " UID & GID for project files: $(PROJECT_UID):$(PROJECT_GID)" @echo " Dependencies: $(if $(DEPENDENCIES),$(DEPENDENCIES),none)" + @echo " Implicit dependencies: $(if $(IMPLICIT_DEPENDENCIES),$(IMPLICIT_DEPENDENCIES),none)" @echo @echo "Available targets:" @echo @@ -132,15 +133,26 @@ TARGET_EXAMPLE_FILES += $(TARGET_EXAMPLES_CONFIG_FILES) $(TARGET_EXAMPLES_TMPFIL # List here the names of other projects (directories at the top-level) that this project depends on. DEPENDENCIES ?= +# Implicit dependencies, injected in every project's butane specs. +# "coreos" holds everything related to Fedora CoreOS and "base" everything else. +# "base" depends on "coreos", so the order matters and neither one depends on itself. +ifeq ($(PROJECT_NAME),coreos) +IMPLICIT_DEPENDENCIES := +else ifeq ($(PROJECT_NAME),base) +IMPLICIT_DEPENDENCIES := coreos +else +IMPLICIT_DEPENDENCIES := coreos base +endif + # Set this variable to "yes" to skip the confirmation prompt when running "make clean". I_KNOW_WHAT_I_AM_DOING ?= # List of all ignition files corresponding to the dependencies -# Here, we inject the "base" project as a dependency. It can therefore be assumed to always be embeddable in project's butane specs. -DEPENDENCIES_IGNITION_FILES := $(shell for dep in $$(if [ "$(PROJECT_NAME)" != "base" ]; then echo base; fi) $(DEPENDENCIES); do echo $(COOKBOOKS_DIR)/$$dep/build/$$dep.ign; done) +# Here, we inject the "coreos" and "base" projects as dependencies. They can therefore be assumed to always be embeddable in project's butane specs. +DEPENDENCIES_IGNITION_FILES := $(foreach dep,$(IMPLICIT_DEPENDENCIES) $(DEPENDENCIES),$(COOKBOOKS_DIR)/$(dep)/build/$(dep).ign) # Variation of the previous variable with the built-in examples. -DEPENDENCIES_IGNITION_EXAMPLES_FILES := $(shell for dep in $$(if [ "$(PROJECT_NAME)" != "base" ]; then echo base; fi) $(DEPENDENCIES); do echo $(COOKBOOKS_DIR)/$$dep/build/$$dep.ign $(COOKBOOKS_DIR)/$$dep/build/$$dep-examples.ign; done) +DEPENDENCIES_IGNITION_EXAMPLES_FILES := $(foreach dep,$(IMPLICIT_DEPENDENCIES) $(DEPENDENCIES),$(COOKBOOKS_DIR)/$(dep)/build/$(dep).ign $(COOKBOOKS_DIR)/$(dep)/build/$(dep)-examples.ign) # User and group IDs to own the project files and directories. PROJECT_UID ?= 0 @@ -152,7 +164,7 @@ include $(HOOKS) # Dump makefile variables for debugging purposes debug: - $(foreach v, $(filter %_FILES SYSTEMD_% QUADLET% PROJECT_% DEPENDENCIES% %_DIR, $(.VARIABLES)), $(info $(v): $($(v)))) + $(foreach v, $(filter %_FILES SYSTEMD_% QUADLET% PROJECT_% DEPENDENCIES% IMPLICIT_DEPENDENCIES %_DIR, $(.VARIABLES)), $(info $(v): $($(v)))) @echo "TARGET_CHROOT: $(TARGET_CHROOT)" @echo "BUTANE_BLOCKLIST: $(BUTANE_BLOCKLIST)" @echo "BUTANE_START_TS: $(BUTANE_START_TS)" @@ -427,7 +439,7 @@ build/$(PROJECT_NAME).tar.gz build/$(PROJECT_NAME).bu build/$(PROJECT_NAME)-exam export ALL_DEPENDENCIES="$(shell $(MAKE) -s list-dependencies 2>/dev/null)"; \ set -Eeuo pipefail; \ if [ build/$(PROJECT_NAME).bu -ot "$(BUTANE_START_TS)" ] || [ build/$(PROJECT_NAME)-examples.bu -ot "$(BUTANE_START_TS)" ]; then \ - for dep in base $(DEPENDENCIES); do \ + for dep in $(IMPLICIT_DEPENDENCIES) $(DEPENDENCIES); do \ if [[ "$$dep" == "$(PROJECT_NAME)" ]]; then \ # Avoid building the current project as its own dependency. \ continue; \ @@ -476,12 +488,18 @@ $(TOP_LEVEL_DIR)/build/local.ign: $(TOP_LEVEL_DIR)/local.bu # Generate the Butane specs for development and testing by merging the current project's spec with those of the dependencies. # The development spec also includes the examples of the current project and of its dependencies. # Whereas the testing spec only includes the main specs of the current project and of its dependencies. -build/fcos-dev.bu build/fcos-test.bu: DEPS := $(if $(filter-out base,$(PROJECT_NAME)),base $(DEPENDENCIES),$(DEPENDENCIES)) +build/fcos-dev.bu build/fcos-test.bu: DEPS := $(IMPLICIT_DEPENDENCIES) $(DEPENDENCIES) build/fcos-dev.bu: DEPS := $(DEPS) $(addsuffix -examples,$(DEPS)) build/fcos-test.bu: SELF := $(PROJECT_NAME) build/fcos-dev.bu: SELF := $(PROJECT_NAME) $(PROJECT_NAME)-examples +# The ignition files are needed to detect duplicates (see default-butane-spec.sh). +build/fcos-dev.bu: build/$(PROJECT_NAME).ign build/$(PROJECT_NAME)-examples.ign $(DEPENDENCIES_IGNITION_EXAMPLES_FILES) +build/fcos-test.bu: build/$(PROJECT_NAME).ign $(DEPENDENCIES_IGNITION_FILES) build/fcos-dev.bu build/fcos-test.bu: %.bu: Makefile $(SCRIPTS_DIR)/default-butane-spec.sh build - $(SCRIPTS_DIR)/default-butane-spec.sh $(SELF) $(DEPS) > $@ + $(SCRIPTS_DIR)/default-butane-spec.sh $(foreach name,$(SELF) $(DEPS),$(call ignition_path,$(name))) > $@ + +# Path of the ignition file of the current project or of one of its dependencies (with or without the "-examples" suffix). +ignition_path = $(if $(filter $(PROJECT_NAME) $(PROJECT_NAME)-examples,$(1)),build/$(1).ign,$(COOKBOOKS_DIR)/$(patsubst %-examples,%,$(1))/build/$(1).ign) # Generate the final Fedora CoreOS ignition files (dev & test) by merging the Butane spec with the local and project-specific ignition files, as well as those of the dependencies. build/fcos-dev.ign: $(TOP_LEVEL_DIR)/build/local.ign build/$(PROJECT_NAME).ign build/$(PROJECT_NAME)-examples.ign $(DEPENDENCIES_IGNITION_EXAMPLES_FILES) diff --git a/scripts/default-butane-spec.sh b/scripts/default-butane-spec.sh index a7afe03..a06a261 100755 --- a/scripts/default-butane-spec.sh +++ b/scripts/default-butane-spec.sh @@ -2,6 +2,13 @@ set -Eeuo pipefail +# Usage: default-butane-spec.sh ... +# +# Butane inlines the "local" files as data URLs and rejects duplicate entries. +# So, ignition files whose content is identical to a previous one (typically +# the empty "-examples" ignition files) are skipped. Merging the same config +# twice would be a no-op anyway. + cat <<'EOF' variant: fcos version: 1.4.0 @@ -9,7 +16,14 @@ ignition: config: merge: EOF -for dep in "$@"; do - echo " - local: ${dep}.ign" +declare -A seen=() +for ign in "$@"; do + checksum="$(sha256sum "$ign" | cut -d' ' -f1)" + if [ -n "${seen[$checksum]:-}" ]; then + echo "Skipping $(basename "$ign"): same content as ${seen[$checksum]}." >&2 + continue + fi + seen[$checksum]="$(basename "$ign")" + echo " - local: $(basename "$ign")" done echo " - local: local.ign"