diff --git a/Makefile b/Makefile index 1cd905a057..685a6c86f8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ export GO111MODULE=on +WITH_INTEGRATION ?= 0 + # Canonical version of this in https://github.com/coreos/coreos-assembler/blob/6eb97016f4dab7d13aa00ae10846f26c1cd1cb02/Makefile#L19 GOARCH:=$(shell uname -m) ifeq ($(GOARCH),x86_64) @@ -43,10 +45,19 @@ butane-cross: .PHONY: install install: - for x in dracut/*; do \ - bn=$$(basename $$x); \ - install -m 0644 -D -t $(DESTDIR)/usr/lib/dracut/modules.d/$${bn} $$x/*; \ - done + if [ $(WITH_INTEGRATION) -eq 1 ]; then \ + for x in dracut/modules.d/*; do \ + bn=$$(basename $$x); \ + install -m 0644 -D -t $(DESTDIR)/usr/lib/dracut/modules.d/$${bn} $$x/*; \ + done; \ + install -m 755 -D -t $(DESTDIR)/usr/libexec scripts/libexec/ignition-write-issues; \ + install -m 0644 -D -t $(DESTDIR)/usr/lib/systemd/system systemd/ignition-write-issues.service; \ + install -m 0644 -D -t $(DESTDIR)/usr/lib/systemd/system-preset systemd/system-preset/40-ignition.preset; \ + install -m 0644 -D -t $(DESTDIR)/usr/lib/dracut/dracut.conf.d dracut/dracut.conf.d/*; \ + chmod a+x $(DESTDIR)/usr/lib/dracut/modules.d/40ignition-ostree/ignition-relabel; \ + else \ + install -m 0644 -D -t $(DESTDIR)/usr/lib/dracut/modules.d/30ignition dracut/modules.d/30ignition/*; \ + fi chmod a+x $(DESTDIR)/usr/lib/dracut/modules.d/*/*.sh $(DESTDIR)/usr/lib/dracut/modules.d/*/*-generator install -m 0644 -D -t $(DESTDIR)/usr/lib/systemd/system systemd/ignition-delete-config.service diff --git a/dracut/dracut.conf.d/60-omit-nfs.conf b/dracut/dracut.conf.d/60-omit-nfs.conf new file mode 100644 index 0000000000..907e7fd61d --- /dev/null +++ b/dracut/dracut.conf.d/60-omit-nfs.conf @@ -0,0 +1,6 @@ +# We don't support root on NFS, so we don't need it in the initramfs. It also +# conflicts with /var mount support in ignition because NFS tries to mount stuff +# in /var/ and then ignition can't cleanly unmount it. For example: +# https://github.com/dracutdevs/dracut/blob/1856ae95c873a6fe855b3dccd0144f1a96b9e71c/modules.d/95nfs/nfs-start-rpc.sh#L7 +# See also discussion in https://github.com/coreos/fedora-coreos-config/pull/60 +omit_dracutmodules+=" nfs " diff --git a/dracut/modules.d/01ignition-scsi-rules/module-setup.sh b/dracut/modules.d/01ignition-scsi-rules/module-setup.sh new file mode 100644 index 0000000000..eeaeacc230 --- /dev/null +++ b/dracut/modules.d/01ignition-scsi-rules/module-setup.sh @@ -0,0 +1,19 @@ +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +check() { + if [[ $IN_KDUMP == 1 ]]; then + return 1 + fi +} + +# 1) Fix for https://bugzilla.redhat.com/show_bug.cgi?id=1918244 +# On s390x systems with IBM 2810XIV discs multipath couldn't be configured +# because SCSI_IDENT_* udev properties are not set at boot time +# 2-) Fix for https://bugzilla.redhat.com/show_bug.cgi?id=1990506 +# Missing symlinks to disk install +install() { + inst_simple sg_inq + inst_rules 61-scsi-sg3_id.rules + inst_rules 63-scsi-sg3_symlink.rules +} diff --git a/dracut/30ignition/99-xx-ignition-systemd-cryptsetup.rules b/dracut/modules.d/30ignition/99-xx-ignition-systemd-cryptsetup.rules similarity index 100% rename from dracut/30ignition/99-xx-ignition-systemd-cryptsetup.rules rename to dracut/modules.d/30ignition/99-xx-ignition-systemd-cryptsetup.rules diff --git a/dracut/30ignition/ignition-cex b/dracut/modules.d/30ignition/ignition-cex similarity index 100% rename from dracut/30ignition/ignition-cex rename to dracut/modules.d/30ignition/ignition-cex diff --git a/dracut/30ignition/ignition-complete.target b/dracut/modules.d/30ignition/ignition-complete.target similarity index 100% rename from dracut/30ignition/ignition-complete.target rename to dracut/modules.d/30ignition/ignition-complete.target diff --git a/dracut/30ignition/ignition-diskful-subsequent.target b/dracut/modules.d/30ignition/ignition-diskful-subsequent.target similarity index 100% rename from dracut/30ignition/ignition-diskful-subsequent.target rename to dracut/modules.d/30ignition/ignition-diskful-subsequent.target diff --git a/dracut/30ignition/ignition-diskful.target b/dracut/modules.d/30ignition/ignition-diskful.target similarity index 100% rename from dracut/30ignition/ignition-diskful.target rename to dracut/modules.d/30ignition/ignition-diskful.target diff --git a/dracut/30ignition/ignition-disks.service b/dracut/modules.d/30ignition/ignition-disks.service similarity index 100% rename from dracut/30ignition/ignition-disks.service rename to dracut/modules.d/30ignition/ignition-disks.service diff --git a/dracut/30ignition/ignition-fetch-offline.service b/dracut/modules.d/30ignition/ignition-fetch-offline.service similarity index 100% rename from dracut/30ignition/ignition-fetch-offline.service rename to dracut/modules.d/30ignition/ignition-fetch-offline.service diff --git a/dracut/30ignition/ignition-fetch.service b/dracut/modules.d/30ignition/ignition-fetch.service similarity index 100% rename from dracut/30ignition/ignition-fetch.service rename to dracut/modules.d/30ignition/ignition-fetch.service diff --git a/dracut/30ignition/ignition-files.service b/dracut/modules.d/30ignition/ignition-files.service similarity index 100% rename from dracut/30ignition/ignition-files.service rename to dracut/modules.d/30ignition/ignition-files.service diff --git a/dracut/30ignition/ignition-generator b/dracut/modules.d/30ignition/ignition-generator similarity index 100% rename from dracut/30ignition/ignition-generator rename to dracut/modules.d/30ignition/ignition-generator diff --git a/dracut/30ignition/ignition-kargs-helper.sh b/dracut/modules.d/30ignition/ignition-kargs-helper.sh similarity index 100% rename from dracut/30ignition/ignition-kargs-helper.sh rename to dracut/modules.d/30ignition/ignition-kargs-helper.sh diff --git a/dracut/30ignition/ignition-kargs.service b/dracut/modules.d/30ignition/ignition-kargs.service similarity index 100% rename from dracut/30ignition/ignition-kargs.service rename to dracut/modules.d/30ignition/ignition-kargs.service diff --git a/dracut/30ignition/ignition-luks.conf b/dracut/modules.d/30ignition/ignition-luks.conf similarity index 100% rename from dracut/30ignition/ignition-luks.conf rename to dracut/modules.d/30ignition/ignition-luks.conf diff --git a/dracut/30ignition/ignition-mount.service b/dracut/modules.d/30ignition/ignition-mount.service similarity index 100% rename from dracut/30ignition/ignition-mount.service rename to dracut/modules.d/30ignition/ignition-mount.service diff --git a/dracut/30ignition/ignition-remount-sysroot.service b/dracut/modules.d/30ignition/ignition-remount-sysroot.service similarity index 100% rename from dracut/30ignition/ignition-remount-sysroot.service rename to dracut/modules.d/30ignition/ignition-remount-sysroot.service diff --git a/dracut/30ignition/ignition-subsequent.target b/dracut/modules.d/30ignition/ignition-subsequent.target similarity index 100% rename from dracut/30ignition/ignition-subsequent.target rename to dracut/modules.d/30ignition/ignition-subsequent.target diff --git a/dracut/30ignition/module-setup.sh b/dracut/modules.d/30ignition/module-setup.sh similarity index 100% rename from dracut/30ignition/module-setup.sh rename to dracut/modules.d/30ignition/module-setup.sh diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-mount-var.service b/dracut/modules.d/40ignition-ostree/ignition-ostree-mount-var.service new file mode 100644 index 0000000000..09d6c15ab3 --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-mount-var.service @@ -0,0 +1,26 @@ +[Unit] +Description=Mount OSTree /var +DefaultDependencies=false +ConditionKernelCommandLine=ostree +ConditionPathExists=!/run/ostree-live + +# Make sure ExecStop= runs before we switch root +Before=initrd-switch-root.target + +# Make sure if ExecStop= fails, the boot fails +OnFailure=emergency.target +OnFailureJobMode=isolate + +# Make sure /sysroot is mounted first, since we're mounting under there +Requires=initrd-root-fs.target +After=initrd-root-fs.target + +# Need to do this before Ignition mounts any other filesystems (potentially +# shadowing our own bind mount). +Before=ignition-mount.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/sbin/ignition-ostree-mount-var mount +ExecStop=/usr/sbin/ignition-ostree-mount-var umount diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-mount-var.sh b/dracut/modules.d/40ignition-ostree/ignition-ostree-mount-var.sh new file mode 100755 index 0000000000..6b80d83db3 --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-mount-var.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -euo pipefail + +fatal() { + echo "$@" >&2 + exit 1 +} + +if [ $# -ne 1 ] || { [[ $1 != mount ]] && [[ $1 != umount ]]; }; then + fatal "Usage: $0 " +fi + +get_ostree_arg() { + # yes, this doesn't account for spaces within args, e.g. myarg="my val", but + # it still works for our purposes + ( + IFS=$' ' + # shellcheck disable=SC2013 + for arg in $(cat /proc/cmdline); do + if [[ $arg == ostree=* ]]; then + echo "${arg#ostree=}" + fi + done + ) +} + +do_mount() { + ostree=$(get_ostree_arg) + if [ -z "${ostree}" ]; then + fatal "No ostree= kernel argument in /proc/cmdline" + fi + + deployment_path=/sysroot/${ostree} + if [ ! -L "${deployment_path}" ]; then + fatal "${deployment_path} is not a symlink" + fi + + stateroot_var_path=$(realpath "${deployment_path}/../../var") + if [ ! -d "${stateroot_var_path}" ]; then + fatal "${stateroot_var_path} is not a directory" + fi + + echo "Mounting $stateroot_var_path" + # older mount ignore 'rw' when using '-o bind,rw', so make 2 calls for now + mount --bind "$stateroot_var_path" /sysroot/var + mount -o remount,bind,rw /sysroot/var +} + +do_umount() { + echo "Unmounting /sysroot/var" + umount /sysroot/var +} + +"do_$1" diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-populate-var.service b/dracut/modules.d/40ignition-ostree/ignition-ostree-populate-var.service new file mode 100644 index 0000000000..d7aa622cae --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-populate-var.service @@ -0,0 +1,16 @@ +[Unit] +Description=Populate OSTree /var +DefaultDependencies=false +ConditionKernelCommandLine=|ostree +ConditionPathExists=|/run/ostree-live + +# Need to do this with all mount points active +After=ignition-mount.service + +# But *before* we start dumping files in there +Before=ignition-files.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/sbin/ignition-ostree-populate-var diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-populate-var.sh b/dracut/modules.d/40ignition-ostree/ignition-ostree-populate-var.sh new file mode 100755 index 0000000000..815f8d1db1 --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-populate-var.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -euo pipefail + +fatal() { + echo "$@" >&2 + exit 1 +} + +if [ $# -ne 0 ]; then + fatal "Usage: $0" +fi + +# See the similar code block in Anaconda, which handles this today for Atomic +# Host and Silverblue: +# https://github.com/rhinstaller/anaconda/blob/b9ea8ce4e68196b30a524c1cc5680dcdc4b89371/pyanaconda/payload/rpmostreepayload.py#L332 + +for varsubdir in lib log home roothome opt srv usrlocal mnt media; do + + # If the directory already existed, just ignore. This addresses the live + # image case with persistent `/var`; we don't want to relabel all the files + # there on each boot. + if [ -d "/sysroot/var/${varsubdir}" ]; then + continue + fi + + if [[ $varsubdir == lib ]] || [[ $varsubdir == log ]]; then + # Simply manually mkdir /var/{lib,log}; the tmpfiles.d entries otherwise + # reference users/groups which we don't have access to from here + # (though... we *could* import them from the sysroot, and have + # nss-altfiles in the initrd, but meh... let's just wait for + # systemd-sysusers which will make this way easier: + # https://github.com/coreos/fedora-coreos-config/pull/56/files#r262592361). + mkdir -p /sysroot/var/${varsubdir} + else + systemd-tmpfiles --create --boot --root=/sysroot --prefix="/var/${varsubdir}" + fi + + ignition-relabel "/var/${varsubdir}" +done diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-detect.service b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-detect.service new file mode 100644 index 0000000000..389dc9eedf --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-detect.service @@ -0,0 +1,20 @@ +[Unit] +Description=Ignition OSTree: Detect Partition Transposition +DefaultDependencies=false +After=ignition-fetch.service +Before=ignition-disks.service +Before=initrd-root-fs.target +Before=sysroot.mount +ConditionKernelCommandLine=ostree +OnFailure=emergency.target +OnFailureJobMode=isolate + +# This stage requires udevd to detect disks +Requires=systemd-udevd.service +After=systemd-udevd.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/libexec/ignition-ostree-transposefs detect +ExecStop=/usr/libexec/ignition-ostree-transposefs cleanup diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-restore.service b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-restore.service new file mode 100644 index 0000000000..eef3c064c7 --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-restore.service @@ -0,0 +1,23 @@ +[Unit] +Description=Ignition OSTree: Restore Partitions +DefaultDependencies=false +After=ignition-disks.service +# Avoid racing with UUID regeneration +After=ignition-ostree-uuid-root.service +After=ignition-ostree-growfs.service +# https://issues.redhat.com/browse/OCPBUGS-16157 +# On multipath systems mounting the /sysroot before +# the ignition-ostree services causes the transpose to fail. +Before=sysroot.mount +OnFailure=emergency.target +OnFailureJobMode=isolate + +ConditionKernelCommandLine=ostree +ConditionPathIsDirectory=/run/ignition-ostree-transposefs + +[Service] +Type=oneshot +RemainAfterExit=yes +# So we can transiently mount sysroot +MountFlags=slave +ExecStart=/usr/libexec/ignition-ostree-transposefs restore diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-save.service b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-save.service new file mode 100644 index 0000000000..bc03499ecb --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs-save.service @@ -0,0 +1,19 @@ +[Unit] +Description=Ignition OSTree: Save Partitions +DefaultDependencies=false +After=ignition-ostree-transposefs-detect.service +Before=ignition-disks.service +ConditionKernelCommandLine=ostree +ConditionPathIsDirectory=/run/ignition-ostree-transposefs +# Any services looking at mounts need to order after this +# because it causes device re-probing. +After=coreos-gpt-setup.service +OnFailure=emergency.target +OnFailureJobMode=isolate + +[Service] +Type=oneshot +RemainAfterExit=yes +# So we can transiently mount sysroot +MountFlags=slave +ExecStart=/usr/libexec/ignition-ostree-transposefs save diff --git a/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs.sh b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs.sh new file mode 100755 index 0000000000..ec52b21450 --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-ostree-transposefs.sh @@ -0,0 +1,322 @@ +#!/bin/bash +set -euo pipefail + +boot_sector_size=440 +esp_typeguid=c12a7328-f81f-11d2-ba4b-00a0c93ec93b +bios_typeguid=21686148-6449-6e6f-744e-656564454649 +prep_typeguid=9e1a2d38-c612-4316-aa26-8b49521e5a8b + +# This is implementation details of Ignition; in the future, we should figure +# out a way to ask Ignition directly whether there's a filesystem with label +# "root" being set up. +ignition_cfg=/run/ignition.json +root_part=/dev/disk/by-label/root +boot_part=/dev/disk/by-label/boot +esp_part=/dev/disk/by-label/EFI-SYSTEM +bios_part=/dev/disk/by-partlabel/BIOS-BOOT +prep_part=/dev/disk/by-partlabel/PowerPC-PReP-boot +saved_data=/run/ignition-ostree-transposefs +saved_root=${saved_data}/root +saved_boot=${saved_data}/boot +saved_esp=${saved_data}/esp +saved_bios=${saved_data}/bios +saved_prep=${saved_data}/prep +zram_dev=${saved_data}/zram_dev +partstate_root=/run/ignition-ostree-rootfs-partstate.sh + +is_rhcos9() { + source /etc/os-release + [ "${ID}" == "rhcos" ] && [ "${RHEL_VERSION%%.*}" -eq 9 ] +} + +# Print jq query string for wiped filesystems with label $1 +query_fslabel() { + echo ".storage?.filesystems? // [] | map(select(.label == \"$1\" and .wipeFilesystem == true))" +} + +# Print jq query string for partitions with type GUID $1 +query_parttype() { + echo ".storage?.disks? // [] | map(.partitions?) | flatten | map(select(has(\"typeGuid\") and (.typeGuid | ascii_downcase == \"$1\")))" +} + +# Print partition labels for partitions with type GUID $1 +get_partlabels_for_parttype() { + jq -r "$(query_parttype $1) | .[].label" "${ignition_cfg}" +} + +# Mounts device to directory, with extra logging of the src device +mount_verbose() { + local srcdev=$1; shift + local destdir=$1; shift + local mode=${1:-ro} + echo "Mounting ${srcdev} ${mode} ($(realpath "$srcdev")) to $destdir" + mkdir -p "${destdir}" + mount -o "${mode}" "${srcdev}" "${destdir}" +} + +# Sometimes, for some reason the by-label symlinks aren't updated. Detect these +# cases, and explicitly `udevadm trigger`. +# See: https://bugzilla.redhat.com/show_bug.cgi?id=1908780 +udev_trigger_on_label_mismatch() { + local label=$1; shift + local expected_dev=$1; shift + local actual_dev + expected_dev=$(realpath "${expected_dev}") + # We `|| :` here because sometimes /dev/disk/by-label/$label is missing. + # We've seen this on Fedora kernels with debug enabled (common in `rawhide`). + # See https://github.com/coreos/fedora-coreos-tracker/issues/1092 + actual_dev=$(realpath "/dev/disk/by-label/$label" || :) + if [ "$actual_dev" != "$expected_dev" ]; then + echo "Expected /dev/disk/by-label/$label to point to $expected_dev, but points to $actual_dev; triggering udev" + udevadm trigger --settle "$expected_dev" + fi +} + +# Print partition offset for device node $1 +get_partition_offset() { + local devpath=$(udevadm info --query=path "$1") + cat "/sys${devpath}/start" +} + +# copied from generator-lib.sh +karg() { + local name="$1" value="${2:-}" + local cmdline=( $(&2 + exit 1 + fi + modprobe zram num_devices=0 + read dev < /sys/class/zram-control/hot_add + # disksize is set arbitrarily large, as zram is capped by mem_limit + echo 10G > /sys/block/zram"${dev}"/disksize + # Limit zram to 90% of available RAM: we want to be greedy since the + # boot breaks anyway, but we still want to leave room for everything + # else so it hits ENOSPC and doesn't invoke the OOM killer + echo $(( mem_available * 90 / 100 ))K > /sys/block/zram"${dev}"/mem_limit + mkfs.xfs -q /dev/zram"${dev}" + mkdir "${saved_data}" + mount -t xfs /dev/zram"${dev}" "${saved_data}" + # save the zram device number created for when called to cleanup + echo "${dev}" > "${zram_dev}" +} + +print_zram_mm_stat() { + echo "zram usage:" + read dev < "${zram_dev}" + cat /sys/block/zram"${dev}"/mm_stat +} + +case "${1:-}" in + detect) + # Mounts are not in a private namespace so we can mount ${saved_data} + wipes_root=$(jq "$(query_fslabel root) | length" "${ignition_cfg}") + wipes_boot=$(jq "$(query_fslabel boot) | length" "${ignition_cfg}") + creates_esp=$(jq "$(query_parttype ${esp_typeguid}) | length" "${ignition_cfg}") + creates_bios=$(jq "$(query_parttype ${bios_typeguid}) | length" "${ignition_cfg}") + creates_prep=$(jq "$(query_parttype ${prep_typeguid}) | length" "${ignition_cfg}") + if [ "${wipes_root}${wipes_boot}${creates_esp}${creates_bios}${creates_prep}" = "00000" ]; then + exit 0 + fi + echo "Detected partition replacement in fetched Ignition config: /run/ignition.json" + # verify all ESP, BIOS, and PReP partitions have non-null unique labels + unique_esp=$(jq -r "$(query_parttype ${esp_typeguid}) | [.[].label | values] | unique | length" "${ignition_cfg}") + unique_bios=$(jq -r "$(query_parttype ${bios_typeguid}) | [.[].label | values] | unique | length" "${ignition_cfg}") + unique_prep=$(jq -r "$(query_parttype ${prep_typeguid}) | [.[].label | values] | unique | length" "${ignition_cfg}") + if [ "${creates_esp}" != "${unique_esp}" -o "${creates_bios}" != "${unique_bios}" -o "${creates_prep}" != "${unique_prep}" ]; then + echo "Found duplicate or missing ESP, BIOS-BOOT, or PReP labels in config" >&2 + exit 1 + fi + + ensure_zram_dev + + if [ "${wipes_root}" != "0" ]; then + mkdir "${saved_root}" + fi + if [ "${wipes_boot}" != "0" ]; then + mkdir "${saved_boot}" + fi + if [ "${creates_esp}" != "0" ]; then + mkdir "${saved_esp}" + fi + if [ "${creates_bios}" != "0" ]; then + mkdir "${saved_bios}" + fi + if [ "${creates_prep}" != "0" ]; then + mkdir "${saved_prep}" + fi + ;; + save) + # Mounts happen in a private mount namespace since we're not "offically" mounting + if [ -d "${saved_root}" ]; then + echo "Moving rootfs to RAM..." + mount_and_save_filesystem_by_label root "${saved_root}" + # also store the state of the partition + lsblk "${root_part}" --nodeps --pairs -b --paths -o NAME,TYPE,SIZE > "${partstate_root}" + fi + if [ -d "${saved_boot}" ]; then + echo "Moving bootfs to RAM..." + mount_and_save_filesystem_by_label boot "${saved_boot}" + fi + if [ -d "${saved_esp}" ]; then + echo "Moving EFI System Partition to RAM..." + mount_verbose "${esp_part}" /sysroot/boot/efi + cp -aT /sysroot/boot/efi "${saved_esp}" + fi + if [ -d "${saved_bios}" ]; then + echo "Moving BIOS Boot partition and boot sector to RAM..." + # save partition + cat "${bios_part}" > "${saved_bios}/partition" + # save boot sector + bios_disk=$(lsblk --noheadings --output PKNAME --paths "${bios_part}") + dd if="${bios_disk}" of="${saved_bios}/boot-sector" bs="${boot_sector_size}" count=1 status=none + # store partition start offset so we can check it later + get_partition_offset "${bios_part}" > "${saved_bios}/start" + fi + if [ -d "${saved_prep}" ]; then + echo "Moving PReP partition to RAM..." + cat "${prep_part}" > "${saved_prep}/partition" + fi + print_zram_mm_stat + ;; + restore) + # Mounts happen in a private mount namespace since we're not "offically" mounting + if [ -d "${saved_root}" ]; then + echo "Restoring rootfs from RAM..." + mount_and_restore_filesystem_by_label root /sysroot "${saved_root}" + chcon -v --reference "${saved_root}" /sysroot # the root of the fs itself + chattr +i $(ls -d /sysroot/ostree/deploy/*/deploy/*/) + fi + if [ -d "${saved_boot}" ]; then + echo "Restoring bootfs from RAM..." + mount_and_restore_filesystem_by_label boot /sysroot/boot "${saved_boot}" + chcon -v --reference "${saved_boot}" /sysroot/boot # the root of the fs itself + fi + if [ -d "${saved_esp}" ]; then + echo "Restoring EFI System Partition from RAM..." + get_partlabels_for_parttype "${esp_typeguid}" | while read label; do + # Don't use mount_and_restore_filesystem_by_label because: + # 1. We're mounting by partlabel, not FS label + # 2. We need to copy the contents to each partition, not move + # them once + # 3. We don't need the by-label symlink to be correct and + # nothing later in boot will be mounting the filesystem + mountpoint="/mnt/esp-${label}" + mount_verbose "/dev/disk/by-partlabel/${label}" "${mountpoint}" rw + find "${saved_esp}" -mindepth 1 -maxdepth 1 -exec cp -at "${mountpoint}" {} + + done + fi + if [ -d "${saved_bios}" ]; then + echo "Restoring BIOS Boot partition and boot sector from RAM..." + expected_start=$(cat "${saved_bios}/start") + get_partlabels_for_parttype "${bios_typeguid}" | while read label; do + cur_part="/dev/disk/by-partlabel/${label}" + # boot sector hardcodes the partition start; ensure it matches + cur_start=$(get_partition_offset "${cur_part}") + if [ "${cur_start}" != "${expected_start}" ]; then + echo "Partition ${cur_part} starts at ${cur_start}; expected ${expected_start}" >&2 + exit 1 + fi + # copy partition contents + cat "${saved_bios}/partition" > "${cur_part}" + # copy boot sector + cur_disk=$(lsblk --noheadings --output PKNAME --paths "${cur_part}") + cat "${saved_bios}/boot-sector" > "${cur_disk}" + done + fi + if [ -d "${saved_prep}" ]; then + echo "Restoring PReP partition from RAM..." + get_partlabels_for_parttype "${prep_typeguid}" | while read label; do + cat "${saved_prep}/partition" > "/dev/disk/by-partlabel/${label}" + done + fi + ;; + cleanup) + # Mounts are not in a private namespace so we can unmount ${saved_data} + if [ -d "${saved_data}" ]; then + read dev < "${zram_dev}" + umount "${saved_data}" + rm -rf "${saved_data}" "${partstate_root}" + # After unmounting, make sure zram device state is stable before we remove it. + # See https://github.com/openshift/os/issues/1149 + # Should remove when https://bugzilla.redhat.com/show_bug.cgi?id=2172058 is fixed. + # Seems the previous workaround https://github.com/coreos/fedora-coreos-config/pull/2226 + # can not completely resolve the race issue, try in loop with a small sleep for el9 + !x86_64. + if [ $(uname -m) != x86_64 ] && is_rhcos9; then + for x in {0..10}; do + if ! echo "${dev}" > /sys/class/zram-control/hot_remove 2>/dev/null; then + sleep 0.1 + else + dev= + break + fi + done + # try it one last time and let it possibly fail + if [ -n "${dev}" ]; then + echo "${dev}" > /sys/class/zram-control/hot_remove + fi + else + echo "${dev}" > /sys/class/zram-control/hot_remove + fi + fi + ;; + *) + echo "Unsupported operation: ${1:-}" 1>&2; exit 1 + ;; +esac diff --git a/dracut/modules.d/40ignition-ostree/ignition-relabel b/dracut/modules.d/40ignition-ostree/ignition-relabel new file mode 100755 index 0000000000..f4397551c0 --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/ignition-relabel @@ -0,0 +1,35 @@ +#!/bin/bash +set -euo pipefail + +err() { + echo "$@" >&2 +} + +fatal() { + err "$@" + exit 1 +} + +if [ $# -eq 0 ]; then + err "Usage: $0 [PATTERN...]" + err " e.g.: $0 /etc/passwd '/etc/group*'" +fi + +if [ ! -f /sysroot/etc/selinux/config ]; then + exit 0 +fi + +source /sysroot/etc/selinux/config + +if [ -z "${SELINUXTYPE:-}" ]; then + fatal "Couldn't find SELINUXTYPE in /sysroot/etc/selinux/config" +fi + +file_contexts="/sysroot/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts" + +prefixed_patterns=() +while [ $# -ne 0 ]; do + pattern=$1; shift + prefixed_patterns+=("/sysroot/$pattern") +done +setfiles -vFi0 -r /sysroot "$file_contexts" "${prefixed_patterns[@]}" diff --git a/dracut/modules.d/40ignition-ostree/module-setup.sh b/dracut/modules.d/40ignition-ostree/module-setup.sh new file mode 100755 index 0000000000..3423e39c27 --- /dev/null +++ b/dracut/modules.d/40ignition-ostree/module-setup.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +check() { + if [[ $IN_KDUMP == 1 ]]; then + return 1 + fi +} + +depends() { + echo ignition +} + +install_ignition_unit() { + local unit=$1; shift + local target=${1:-complete} + inst_simple "$moddir/$unit" "$systemdsystemunitdir/$unit" + # note we `|| exit 1` here so we error out if e.g. the units are missing + # see https://github.com/coreos/fedora-coreos-config/issues/799 + systemctl -q --root="$initdir" add-requires "ignition-${target}.target" "$unit" || exit 1 +} + +installkernel() { + # Used by ignition-ostree-transposefs + instmods -c zram +} + +install() { + inst_multiple \ + realpath \ + setfiles \ + chcon \ + systemd-sysusers \ + systemd-tmpfiles + + if [[ $(uname -m) = s390x ]]; then + # for Secure Execution + inst_multiple \ + veritysetup + fi + + # growpart deps + # Mostly generated from the following command: + # $ bash --rpm-requires /usr/bin/growpart | sort | uniq | grep executable + # with a few false positives (rq, rqe, -v) and one missed (mktemp) + inst_multiple \ + awk \ + cat \ + dd \ + grep \ + rm \ + find + + # In some cases we had to vendor gdisk in Ignition. + # If this is the case here use that one. + # See https://issues.redhat.com/browse/RHEL-56080 + if [ -f /usr/libexec/ignition-sgdisk ]; then + inst /usr/libexec/ignition-sgdisk /usr/sbin/sgdisk + else + inst sgdisk + fi + + for x in mount populate; do + install_ignition_unit ignition-ostree-${x}-var.service + inst_script "$moddir/ignition-ostree-${x}-var.sh" "/usr/sbin/ignition-ostree-${x}-var" + done + + inst_multiple jq chattr + inst_script "$moddir/ignition-ostree-transposefs.sh" "/usr/libexec/ignition-ostree-transposefs" + for x in detect save restore; do + install_ignition_unit ignition-ostree-transposefs-${x}.service + done + + inst_script "$moddir/ignition-relabel" /usr/bin/ignition-relabel +} diff --git a/dracut/modules.d/41ignition-network/ignition-enable-network.service b/dracut/modules.d/41ignition-network/ignition-enable-network.service new file mode 100644 index 0000000000..7f1d2a929b --- /dev/null +++ b/dracut/modules.d/41ignition-network/ignition-enable-network.service @@ -0,0 +1,24 @@ +[Unit] +Description=Ignition Enable Network +ConditionPathExists=/etc/initrd-release +DefaultDependencies=false + +# Triggering conditions for cases where we need network: +# * when Ignition signals that it is required for provisioning. +ConditionPathExists=|/run/ignition/neednet + +# Creates /run/ignition/neednet +After=ignition-fetch-offline.service +# Needs networking +Before=ignition-fetch.service + +# See hack in ignition-enable-network. +After=dracut-cmdline.service +Before=nm-initrd.service +# compat: remove when everyone is on dracut 054+ +Before=dracut-initqueue.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/sbin/ignition-enable-network diff --git a/dracut/modules.d/41ignition-network/ignition-enable-network.sh b/dracut/modules.d/41ignition-network/ignition-enable-network.sh new file mode 100755 index 0000000000..e9b0d7c50b --- /dev/null +++ b/dracut/modules.d/41ignition-network/ignition-enable-network.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -euo pipefail + +set +euo pipefail +. /usr/lib/dracut-lib.sh +set -euo pipefail + +dracut_func() { + # dracut is not friendly to set -eu + set +euo pipefail + "$@"; local rc=$? + set -euo pipefail + return $rc +} + +# If networking hasn't been requested yet, request it. +if ! dracut_func getargbool 0 'rd.neednet'; then + echo "rd.neednet=1" > /etc/cmdline.d/40-ignition-neednet.conf + + # Hack: we need to rerun the NM cmdline hook because we run after + # dracut-cmdline.service because we need udev. We should be able to move + # away from this once we run NM as a systemd unit. See also: + # https://github.com/coreos/fedora-coreos-config/pull/346#discussion_r409843428 + # + # NM and dracut will have `NetworkManager-config-initrd.service` which will do + # the same thing, but it's not in rawhide yet. Once that's available, we can + # replace this. + set +euo pipefail + . /usr/lib/dracut/hooks/cmdline/99-nm-config.sh + set -euo pipefail +fi diff --git a/dracut/modules.d/41ignition-network/module-setup.sh b/dracut/modules.d/41ignition-network/module-setup.sh new file mode 100644 index 0000000000..2bb49e9e12 --- /dev/null +++ b/dracut/modules.d/41ignition-network/module-setup.sh @@ -0,0 +1,33 @@ +check() { + if [[ $IN_KDUMP == 1 ]]; then + return 1 + fi + + # This module will only work with NetworkManager now. + # We might add support for other later. + if dracut_module_included "network-manager"; then + return 0 + fi + + return 1 +} + +depends() { + echo network +} + +install_and_enable_unit() { + unit="$1"; shift + target="$1"; shift + inst_simple "$moddir/$unit" "$systemdsystemunitdir/$unit" + # note we `|| exit 1` here so we error out if e.g. the units are missing + # see https://github.com/coreos/fedora-coreos-config/issues/799 + $SYSTEMCTL -q --root="$initdir" add-requires "$target" "$unit" || exit 1 +} + +install() { + inst_simple "$moddir/ignition-enable-network.sh" \ + "/usr/sbin/ignition-enable-network" + install_and_enable_unit "ignition-enable-network.service" \ + "ignition-complete.target" +} diff --git a/dracut/modules.d/99ignition-log-kmsg/00-journal-log-level-kmsg.conf b/dracut/modules.d/99ignition-log-kmsg/00-journal-log-level-kmsg.conf new file mode 100644 index 0000000000..a09cc0c276 --- /dev/null +++ b/dracut/modules.d/99ignition-log-kmsg/00-journal-log-level-kmsg.conf @@ -0,0 +1,4 @@ +# The default MaxLevelKMsg is 'notice'. +# Set it to 'info' to see all Ignition logs in the kernel log. +[Journal] +MaxLevelKMsg=info diff --git a/dracut/modules.d/99ignition-log-kmsg/10-stdout-kmsg.conf b/dracut/modules.d/99ignition-log-kmsg/10-stdout-kmsg.conf new file mode 100644 index 0000000000..1924c0c2b7 --- /dev/null +++ b/dracut/modules.d/99ignition-log-kmsg/10-stdout-kmsg.conf @@ -0,0 +1,4 @@ +# Redirect all ignition logs to kmsg. +[Service] +StandardOutput=kmsg +StandardError=kmsg diff --git a/dracut/modules.d/99ignition-log-kmsg/module-setup.sh b/dracut/modules.d/99ignition-log-kmsg/module-setup.sh new file mode 100644 index 0000000000..bafbdad103 --- /dev/null +++ b/dracut/modules.d/99ignition-log-kmsg/module-setup.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Installs systemd drop-ins so Ignition dracut units log to kmsg. + +install() { + services=( + ignition-disks.service + ignition-enable-network.service + ignition-fetch-offline.service + ignition-fetch.service + ignition-files.service + ignition-kargs.service + ignition-mount.service + ignition-ostree-mount-var.service + ignition-ostree-populate-var.service + ignition-ostree-transposefs-detect.service + ignition-ostree-transposefs-restore.service + ignition-ostree-transposefs-save.service + ignition-remount-sysroot.service + ) + for u in "${services[@]}"; do + inst_dir "$systemdsystemunitdir/${u}.d" + inst_simple "$moddir/10-stdout-kmsg.conf" \ + "$systemdsystemunitdir/${u}.d/10-stdout-kmsg.conf" + done + + inst_simple "$moddir/00-journal-log-level-kmsg.conf" \ + "/etc/systemd/journald.conf.d/00-journal-log-level-kmsg.conf" +} diff --git a/scripts/libexec/ignition-write-issues b/scripts/libexec/ignition-write-issues new file mode 100755 index 0000000000..8e4d6ef5da --- /dev/null +++ b/scripts/libexec/ignition-write-issues @@ -0,0 +1,67 @@ +#!/usr/bin/bash +set -euo pipefail + +IGNITION_RESULT=/etc/.ignition-result.json + +WARN='\033[0;33m' # yellow +RESET='\033[0m' # reset + +mkdir -p /run/issue.d +touch /run/issue.d/30_ignition_provisioning.issue + +d=$(date --date "$(jq -r .provisioningDate "${IGNITION_RESULT}")" +"%Y/%m/%d %H:%M:%S %Z") +ignitionBoot=$(jq -r .provisioningBootID "${IGNITION_RESULT}") +if [ $(cat /proc/sys/kernel/random/boot_id) = "${ignitionBoot}" ]; then + echo "Ignition: ran on ${d} (this boot)" \ + > /run/issue.d/30_ignition_provisioning.issue + + # checking for /run/ostree-live as the live system with persistent storage can run Ignition more than once + if ! test -f /run/ostree-live && jq -e .previousReport.provisioningDate "${IGNITION_RESULT}" &>/dev/null; then + prevdate=$(date --date "$(jq -r .previousReport.provisioningDate "${IGNITION_RESULT}")" +"%Y/%m/%d %H:%M:%S %Z") + cat << EOF > /etc/issue.d/30_ignition_run_more_than_once.issue +${WARN} +############################################################################ +WARNING: Ignition previously ran on ${prevdate}. Unexpected +behavior may occur. Ignition is not designed to run more than once per system. +############################################################################ +${RESET} +EOF + fi + # In Ignition, we've two config validation checks, the one after + # fetching a config and the second after merging configs. Sometimes, + # a warning goes away after merging, however, it's possible that a + # warning appears in case merging creates a contradiction between + # two fields. So this workflow eventually sends duplicate warnings + # in journal entries. Hence, we need to avoid displaying duplicate + # Ignition warnings on the console. + # For e.g. In the journal entries, we might see the following logs: + # + # warning at $.systemd.units.0.contents, line 1 col 997: unit "echo@.service" is enabled, but has no install section so enable does nothing + # warning at $.systemd.units.0.contents: unit "echo@.service" is enabled, but has no install section so enable does nothing + # + # In order to normalize these logs, we'd need to get rid of the line + # and column numbers entirely using the sed command, and then use + # `sort -u` to remove duplicate content. After this, we'd see the + # following warning on the console: + # + # warning at $.systemd.units.0.contents: unit "echo@.service" is enabled, but has no install section so enable does nothing + # + # TODO: find a way to query journal entries recorded before the + # system switches to real root + journalctl -t ignition -o cat -p warning | sed -r 's/, line [0-9]+ col [0-9]+//g' | sort -u | while read line; do + echo -e "${WARN}Ignition: $line${RESET}" >> /etc/issue.d/30_ignition_warnings.issue + done +else + nreboots=$(($(journalctl --list-boots | wc -l) - 1)) + [ "${nreboots}" -eq 1 ] && boot="boot" || boot="boots" + echo "Ignition: ran on ${d} (at least $nreboots $boot ago)" \ + > /run/issue.d/30_ignition_provisioning.issue +fi + +if jq -e .userConfigProvided "${IGNITION_RESULT}" &>/dev/null; then + echo "Ignition: user-provided config was applied" \ + >> /run/issue.d/30_ignition_provisioning.issue +else + echo -e "${WARN}Ignition: no config provided by user${RESET}" \ + >> /run/issue.d/30_ignition_provisioning.issue +fi diff --git a/systemd/ignition-write-issues.service b/systemd/ignition-write-issues.service new file mode 100644 index 0000000000..9127861ee9 --- /dev/null +++ b/systemd/ignition-write-issues.service @@ -0,0 +1,16 @@ +# This service writes issue files describing status +# information about the Ignition run, which includes +# Ignition warnings and information if no Ignition +# config is provided. +[Unit] +Description=Create Ignition Status Issue Files +Before=systemd-user-sessions.service +ConditionPathExists=/etc/.ignition-result.json + +[Service] +Type=oneshot +ExecStart=/usr/libexec/ignition-write-issues +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/systemd/system-preset/40-ignition.preset b/systemd/system-preset/40-ignition.preset new file mode 100644 index 0000000000..3b9c546f39 --- /dev/null +++ b/systemd/system-preset/40-ignition.preset @@ -0,0 +1,6 @@ +# Delete Ignition config from provider on platforms where it's possible +# https://github.com/coreos/ignition/pull/1350 +enable ignition-delete-config.service + +# Provide status information about the Ignition run +enable ignition-write-issues.service