Skip to content

Commit

Permalink
dracut: adopt CoreOS specific modules from Ignition-Dracut
Browse files Browse the repository at this point in the history
With the merging of Ignition-Dracut into Ignition starting with version
2.5.0, we are moving the 99* and 30ignition CoreOS specific modules here.

Signed-off-by: Ben Howard <ben.howard@redhat.com>
  • Loading branch information
Ben Howard committed Jul 27, 2020
1 parent 2f5ce54 commit d90a385
Show file tree
Hide file tree
Showing 12 changed files with 549 additions and 0 deletions.
@@ -0,0 +1,57 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh

# Originally this was known as 'ignition-generator' found in ignition-dracut.
# With Ignition v 2.5.0, ignition-dracut was merged into Ignition and the CoreOS
# specific bits were deposited here.

set -e

# Generators don't have logging right now
# https://github.com/systemd/systemd/issues/15638
exec 1>/dev/kmsg; exec 2>&1

UNIT_DIR="${1:-/tmp}"

cmdline=( $(</proc/cmdline) )
cmdline_arg() {
local name="$1" value="$2"
for arg in "${cmdline[@]}"; do
if [[ "${arg%%=*}" == "${name}" ]]; then
value="${arg#*=}"
fi
done
echo "${value}"
}

cmdline_bool() {
local value=$(cmdline_arg "$@")
case "$value" in
""|0|no|off) return 1;;
*) return 0;;
esac
}

add_requires() {
local name="$1"; shift
local target="$1"; shift
local requires_dir="${UNIT_DIR}/${target}.requires"
mkdir -p "${requires_dir}"
ln -sf "../${name}" "${requires_dir}/${name}"
}

if ! $(cmdline_bool 'ignition.firstboot' 0); then
exit 0
fi

if ! command -v is-live-image >/dev/null || ! is-live-image; then
# ignition-setup-user.service should depend on the boot device node
# only on diskful boots
mkdir -p "${UNIT_DIR}/ignition-setup-user.service.d"
cat > "${UNIT_DIR}/ignition-setup-user.service.d/diskful-gpt.conf" <<EOF
[Unit]
Requires=coreos-gpt-setup.service
After=coreos-gpt-setup.service
EOF
fi
@@ -0,0 +1,33 @@
[Unit]
Description=Generate new UUID for boot disk GPT
ConditionPathExists=/etc/initrd-release
DefaultDependencies=no
Before=local-fs-pre.target systemd-fsck-root.service
Before=ignition-diskful.target
Wants=systemd-udevd.service
After=systemd-udevd.service

# This unit must the first to run when the disk holding the root partition
# becomes available. To avoid relying on the name of the root partition which
# is different between RHCOS LUKS setup and current FCOS setup, we wait for the
# partition labeled 'boot' to become available. This is reliable as we don't
# have any plan to support re-provisioning/re-writing the /boot partition,
#
# This is the only unit where it is safe to wait only on a specific disk label
# as this will call udevadm settle after the GPT setup. Units that requires the
# boot and root partitions to be available should order themselves after this
# unit.
Requires=dev-disk-by\x2dlabel-boot.device
After=dev-disk-by\x2dlabel-boot.device

# Run before services that use device nodes, preventing them from racing
# with udev activity generated by sgdisk
Before=ignition-setup-base.service ignition-setup-user.service ignition-disks.service

OnFailure=emergency.target
OnFailureJobMode=isolate

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/coreos-gpt-setup /dev/disk/by-label/boot
@@ -0,0 +1,25 @@
#!/bin/bash
# randomizes the disk guid on the disk containing the partition specified by $1
# and moves the secondary gpt header/partition table to the end of the disk where it
# should be. If the disk guid is already randomized, it does nothing.
set -euo pipefail

UNINITIALIZED_GUID='00000000-0000-4000-a000-000000000001'

# On RHEL 8 the version of lsblk doesn't have PTUUID. Let's detect
# if lsblk supports it. In the future we can remove the 'if' and
# just use the 'else'.
if ! lsblk --help | grep -q PTUUID; then
# Get the PKNAME
eval $(lsblk --output PKNAME --pairs --paths --nodeps "$1")
# Get the PTUUID
eval $(blkid -o export $PKNAME)
else
# PTUUID is the disk guid, PKNAME is the parent kernel name
eval $(lsblk --output PTUUID,PKNAME --pairs --paths --nodeps "$1")
fi

[ "$PTUUID" != "$UNINITIALIZED_GUID" ] && exit 0

sgdisk --disk-guid=R --move-second-header "$PKNAME"
udevadm settle
@@ -0,0 +1,38 @@
# Clean up the initramfs networking on first boot
# so the real network is being brought up
# https://github.com/coreos/fedora-coreos-tracker/issues/394#issuecomment-599721763

[Unit]
Description=CoreOS Tear down initramfs
DefaultDependencies=false

# We want to run the teardown after all other Ignition stages
# have run because some platforms (like Packet) do remote status
# reporting for each Ignition stage. Since we are tearing down
# the networking using an ExecStop we need to make sure we run
# the ExecStop *after* any other ignition*.service unit's ExecStop.
# The only other one right now is ignition-mount that has an ExecStop
# for doing an unmount. Since the ordering for ExecStop is the
# opposite of ExecStart we need to use `Before=ignition-mount.service`.
# https://github.com/coreos/fedora-coreos-tracker/issues/440
Before=ignition-mount.service
Before=ignition-complete.target

# Make sure ExecStop= runs before we switch root
Conflicts=initrd-switch-root.target umount.target
Before=initrd-switch-root.target

OnFailure=emergency.target
OnFailureJobMode=isolate

# If we are already heading towards emergency.target
# then don't try to stop this unit because it will fail
# when trying to access files in /sysroot/etc/. The failure
# is mostly harmless but having the extra error messages
# leads us away from the original problem.
IgnoreOnIsolate=true

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStop=/usr/sbin/coreos-teardown-initramfs
@@ -0,0 +1,187 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh

set -euo pipefail

# Load dracut libraries. Using getargbool() and getargs() from
# dracut-lib and ip_to_var() from net-lib
load_dracut_libs() {
# dracut is not friendly to set -eu
set +euo pipefail
type getargbool &>/dev/null || . /lib/dracut-lib.sh
type ip_to_var &>/dev/null || . /lib/net-lib.sh
set -euo pipefail
}

dracut_func() {
# dracut is not friendly to set -eu
set +euo pipefail
"$@"; local rc=$?
set -euo pipefail
return $rc
}

selinux_relabel() {
# If we have access to coreos-relabel then let's use that because
# it allows us to set labels on things before switching root
# If not, fallback to tmpfiles.
if command -v coreos-relabel; then
coreos-relabel $1
else
echo "Z $1 - - -" >> "/run/tmpfiles.d/$(basename $0)-relabel.conf"
fi
}

# Propagate initramfs networking if desired. The policy here is:
#
# - If a networking configuration was provided before this point
# (most likely via Ignition) and exists in the real root then
# we do nothing and don't propagate any initramfs networking.
# - If a user did not provide any networking configuration
# then we'll propagate the initramfs networking configuration
# into the real root.
#
# See https://github.com/coreos/fedora-coreos-tracker/issues/394#issuecomment-599721173
propagate_initramfs_networking() {
# Check the two locations where a user could have provided network configuration
# On FCOS we only support keyfiles, but on RHCOS we support keyfiles and ifcfg
if [ -n "$(ls -A /sysroot/etc/NetworkManager/system-connections/)" -o \
-n "$(ls -A /sysroot/etc/sysconfig/network-scripts/)" ]; then
echo "info: networking config is defined in the real root"
echo "info: will not attempt to propagate initramfs networking"
else
echo "info: no networking config is defined in the real root"
if [ -n "$(ls -A /run/NetworkManager/system-connections/)" ]; then
echo "info: propagating initramfs networking config to the real root"
cp /run/NetworkManager/system-connections/* /sysroot/etc/NetworkManager/system-connections/
selinux_relabel /etc/NetworkManager/system-connections/
else
echo "info: no initramfs networking information to propagate"
fi
fi
}

# Propagate the ip= karg hostname if desired. The policy here is:
#
# - IF a hostname is specified in static networking ip= kargs
# - AND no hostname was set via Ignition (realroot `/etc/hostname`)
# - THEN we make the last hostname specified in an ip= karg apply
# permanently by writing it into `/etc/hostname`
#
# This may no longer be needed when the following bug is fixed:
# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/419
propagate_initramfs_hostname() {
if [ -e '/sysroot/etc/hostname' ]; then
echo "info: hostname is defined in the real root"
echo "info: will not attempt to propagate initramfs hostname"
return 0
fi
# Detect if any hostname was provided via static ip= kargs
# run in a subshell so we don't pollute our environment
hostnamefile=$(mktemp)
(
last_nonempty_hostname=''
# Inspired from ifup.sh from the 40network dracut module. Note that
# $hostname from ip_to_var will only be nonempty for static networking.
for iparg in $(dracut_func getargs ip=); do
dracut_func ip_to_var $iparg
[ -n "${hostname:-}" ] && last_nonempty_hostname="$hostname"
done
echo -n "$last_nonempty_hostname" > $hostnamefile
)
hostname=$(<$hostnamefile); rm $hostnamefile
if [ -n "$hostname" ]; then
echo "info: propagating initramfs hostname (${hostname}) to the real root"
echo $hostname > /sysroot/etc/hostname
selinux_relabel /etc/hostname
else
echo "info: no initramfs hostname information to propagate"
fi
}

# Persist automatic multipath configuration, if any.
# When booting with `rd.multipath=default`, the default multipath
# configuration is written. We need to ensure that the mutlipath configuration
# is persisted to the final target.
propagate_initramfs_multipath() {
if [ ! -f /sysroot/etc/multipath.conf ] && [ -f /etc/multipath.conf ]; then
echo "info: propagating automatic multipath configuration"
cp -v /etc/multipath.conf /sysroot/etc/
mkdir -p /sysroot/etc/multipath/multipath.conf.d
selinux_relabel /etc/multipath.conf
selinux_relabel /etc/multipath/multipath.conf.d
else
echo "info: no initramfs automatic multipath configuration to propagate"
fi
}

down_interface() {
echo "info: taking down network device: $1"
# On recommendation from the NM team let's try to delete the device
# first and if that doesn't work then set it to down and flush any
# associated addresses. Deleting virtual devices (bonds, teams, bridges,
# ip-tunnels, etc) will clean up any associated kernel resources. A real
# device can't be deleted so that will fail and we'll fallback to setting
# it down and flushing addresses.
if ! ip link delete $1; then
ip link set $1 down
ip addr flush dev $1
fi
}

# Iterate through the interfaces in the machine and take them down.
# Note that in the futre we would like to possibly use `nmcli` networking off`
# for this. See the following two comments for details:
# https://github.com/coreos/fedora-coreos-tracker/issues/394#issuecomment-599721763
# https://github.com/coreos/fedora-coreos-tracker/issues/394#issuecomment-599746049
down_interfaces() {
if ! [ -z "$(ls /sys/class/net)" ]; then
for f in /sys/class/net/*; do
interface=$(basename "$f")
# The `bonding_masters` entry is not a true interface and thus
# cannot be taken down. Also skip local loopback
case "$interface" in
"lo" | "bonding_masters")
continue
;;
esac
down_interface $interface
done
fi
}

main() {
# Load libraries from dracut
load_dracut_libs

# Take down all interfaces set up in the initramfs
down_interfaces

# Clean up all routing
echo "info: flushing all routing"
ip route flush table main
ip route flush cache

# Hopefully our logic is sound enough that this is never needed, but
# user's can explicitly disable initramfs network/hostname propagation
# with the coreos.no_persist_ip karg.
if dracut_func getargbool 0 'coreos.no_persist_ip'; then
echo "info: coreos.no_persist_ip karg detected"
echo "info: skipping propagating initramfs settings"
else
propagate_initramfs_hostname
propagate_initramfs_networking
fi

# Now that the configuration has been propagated (or not)
# clean it up so that no information from outside of the
# real root is passed on to NetworkManager in the real root
rm -rf /run/NetworkManager/

# If automated multipath configuration has been enabled, ensure
# that its propagated to the real rootfs.
propagate_initramfs_multipath
}

main
@@ -0,0 +1,39 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh

depends() {
echo systemd network ignition
}

install_ignition_unit() {
local unit="$1"; shift
local target="${1:-ignition-complete.target}"; shift
local instantiated="${1:-$unit}"; shift
inst_simple "$moddir/$unit" "$systemdsystemunitdir/$unit"
mkdir -p "$initdir/$systemdsystemunitdir/$target.requires"
ln_r "../$unit" "$systemdsystemunitdir/$target.requires/$instantiated"
}

install() {
inst_multiple \
basename \
lsblk \
sgdisk

inst_simple "$moddir/coreos-diskful-generator" \
"$systemdutildir/system-generators/coreos-diskful-generator"

inst_script "$moddir/coreos-gpt-setup.sh" \
"/usr/sbin/coreos-gpt-setup"

# For consistency tear down the network and persist multipath between the initramfs and
# real root. See https://github.com/coreos/fedora-coreos-tracker/issues/394#issuecomment-599721763
inst_script "$moddir/coreos-teardown-initramfs.sh" \
"/usr/sbin/coreos-teardown-initramfs"
install_ignition_unit coreos-teardown-initramfs.service

# units only started when we have a boot disk
# path generated by systemd-escape --path /dev/disk/by-label/root
install_ignition_unit coreos-gpt-setup.service ignition-diskful.target
}
@@ -0,0 +1,13 @@
[Unit]
Description=Dump journal to virtio port
ConditionPathExists=/etc/initrd-release
DefaultDependencies=false
ConditionVirtualization=|kvm
ConditionVirtualization=|qemu
After=basic.target

[Service]
Type=oneshot
RemainAfterExit=yes
EnvironmentFile=/run/ignition.env
ExecStart=/usr/bin/ignition-virtio-dump-journal

0 comments on commit d90a385

Please sign in to comment.