From 5fd5df54b487e5c81cd5851a736d69efab994fd6 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Thu, 9 Jul 2026 13:47:50 -0400 Subject: [PATCH 1/4] Install UEK8 in so-kernel-upgrade when no UEK kernel is present The script assumed the UEK8 kernel was already installed and only switched the boot default to it. On a node running the EL9 stock kernel (RHCK 5.14) there is no kernel-uek* package at all, so `dnf update` has nothing to upgrade and UEK8 never lands -- the script just logged "nothing to do" and exited 0. When no 6.x UEK boot entry exists, install the kernel-uek metapackage (it pulls kernel-uek-core plus the module subpackages, including kernel-uek-modules-extra-netfilter) and then proceed with the grubby switch. Fail loudly if securityonionkernel is not an enabled repo, since that assignment is gated on the NIC-pin marker and the salt version match and a silent no-op there is hard to diagnose. Also point DEFAULTKERNEL at kernel-uek-core so later kernel updates stay on the UEK line rather than falling back to RHCK. Still idempotent and still never reboots. --- salt/common/tools/sbin/so-kernel-upgrade | 77 ++++++++++++++++++------ 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/salt/common/tools/sbin/so-kernel-upgrade b/salt/common/tools/sbin/so-kernel-upgrade index 46d471051..f901dad8d 100755 --- a/salt/common/tools/sbin/so-kernel-upgrade +++ b/salt/common/tools/sbin/so-kernel-upgrade @@ -5,40 +5,81 @@ # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. # -# so-kernel-upgrade — switch the boot default to the installed UEK8 (6.x) kernel. +# so-kernel-upgrade — install the UEK8 (6.x) kernel if needed and make it the boot default. # -# Security Onion is moving off the EL9 stock kernel / UEK7 (5.x) onto UEK8 (6.x). -# Installing the kernel-uek-core package adds a UEK8 boot entry but does NOT make it the -# default: kernel-install/grubby only auto-promote a new kernel within the running -# kernel's flavor lineage, and we're crossing from a 5.x kernel to the new 6.x UEK flavor. -# So even with UPDATEDEFAULT=yes and DEFAULTKERNEL=kernel-uek-core the box keeps booting -# the old kernel. This tool finds the newest installed 6.x UEK kernel and makes it the -# GRUB default via grubby so the next boot comes up on UEK8. +# Security Onion is moving off the EL9 stock kernel (RHCK, 5.14) / UEK7 (5.15) onto UEK8 (6.x). +# Two separate things have to happen, and neither is automatic: # -# Idempotent: if the UEK8 kernel is already the default it does nothing. It only sets the -# boot default; it does NOT reboot — the admin reboots the node on their own schedule. +# 1. Install it. A node running RHCK has no kernel-uek* package at all, so 'dnf update' +# never pulls UEK8 in — there is nothing to upgrade. The kernel-uek metapackage has to +# be installed explicitly from the securityonionkernel repo. +# 2. Boot it. Installing kernel-uek-core adds a UEK8 boot entry but does NOT make it the +# default: kernel-install/grubby only auto-promote a new kernel within the running +# kernel's flavor lineage, and we're crossing from a 5.x kernel to the new 6.x UEK +# flavor. So even with UPDATEDEFAULT=yes and DEFAULTKERNEL=kernel-uek-core the box +# keeps booting the old kernel until grubby is told otherwise. +# +# This tool does both, then points DEFAULTKERNEL at kernel-uek-core so later kernel updates +# stay on the UEK line. +# +# Idempotent: an already-installed, already-default UEK8 kernel is left alone. It only sets +# the boot default; it does NOT reboot — the admin reboots the node on their own schedule. + +KERNEL_REPO="securityonionkernel" +KERNEL_PKG="kernel-uek" log() { echo "[so-kernel-upgrade] $*"; } [ "$(id -u)" -eq 0 ] || { log "must run as root"; exit 1; } command -v grubby >/dev/null 2>&1 || { log "grubby not found"; exit 1; } +command -v dnf >/dev/null 2>&1 || { log "dnf not found"; exit 1; } # Newest installed UEK8 (6.x) kernel known to the bootloader. UEK8 vmlinuz paths look like -# /boot/vmlinuz-6.12.0-203.76.7.5.el9uek.x86_64; the 5.x UEK7 and 5.14 RHCK won't match. -target="$(grubby --info=ALL 2>/dev/null \ - | sed -n 's/^kernel="\(.*\)"$/\1/p' \ - | grep -E '/vmlinuz-6\.[0-9]+.*uek' \ - | sort -V | tail -1)" +# /boot/vmlinuz-6.12.0-203.76.7.5.el9uek.x86_64; the 5.15 UEK7 and 5.14 RHCK won't match. +find_uek8() { + grubby --info=ALL 2>/dev/null \ + | sed -n 's/^kernel="\(.*\)"$/\1/p' \ + | grep -E '/vmlinuz-6\.[0-9]+.*uek' \ + | sort -V | tail -1 +} + +target="$(find_uek8)" if [ -z "$target" ]; then - log "no installed 6.x UEK (UEK8) kernel found — confirm the kernel repo is assigned and" - log "'dnf update' has installed kernel-uek-core. Nothing to do." - exit 0 + log "no UEK8 kernel installed — installing $KERNEL_PKG from $KERNEL_REPO" + + # The repo is assigned by the repo.client highstate, and only once NICs are pinned by MAC + # (/opt/so/state/nic_names_pinned) so the kernel swap can't renumber interfaces SO binds + # by name. Without the repo we'd silently pull nothing, so fail loudly instead. + if ! dnf -q repolist --enabled 2>/dev/null | awk '{print $1}' | grep -qx "$KERNEL_REPO"; then + log "ERROR: repo '$KERNEL_REPO' is not enabled on this node." + log "Run a highstate first; it is skipped until /opt/so/state/nic_names_pinned exists" + log "(run so-nic-pin) and this node's salt matches the version this release ships." + exit 1 + fi + + dnf -y install "$KERNEL_PKG" || { log "ERROR: failed to install $KERNEL_PKG"; exit 1; } + + target="$(find_uek8)" + if [ -z "$target" ]; then + log "ERROR: $KERNEL_PKG installed but no 6.x UEK boot entry appeared — check 'grubby --info=ALL'" + exit 1 + fi + log "installed UEK8 kernel: $target" +fi + +# Keep future kernel updates on the UEK line rather than falling back to RHCK. Oracle ships +# this file; only rewrite it when it's actually pointing somewhere else. +if [ -f /etc/sysconfig/kernel ] && ! grep -q '^DEFAULTKERNEL=kernel-uek-core$' /etc/sysconfig/kernel; then + log "setting DEFAULTKERNEL=kernel-uek-core in /etc/sysconfig/kernel" + sed -i 's/^DEFAULTKERNEL=.*/DEFAULTKERNEL=kernel-uek-core/' /etc/sysconfig/kernel fi current="$(grubby --default-kernel 2>/dev/null)" if [ "$current" = "$target" ]; then log "UEK8 kernel is already the boot default: $target" + [ "$(uname -r)" = "$(basename "$target" | sed 's/^vmlinuz-//')" ] \ + || log "REBOOT REQUIRED to start using it (currently running $(uname -r))." exit 0 fi From 40c02b3149c1bb4edf5d30f074c3607f8f52594c Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Thu, 9 Jul 2026 14:21:08 -0400 Subject: [PATCH 2/4] Make so-kernel-upgrade populate the kernel repo and fail loudly Three stages of the UEK8 path fail silently, and the script only handled the last one: 1. Populate. so-repo-sync runs before the highstate deploys the [securityonionkernel] section into repodownload.conf, so the first kernel-aware soup skips the kernel sync. kernelrepo_init_empty then seeds valid-but-empty repodata, leaving an enabled repo with zero packages. dnf resolves it happily and installs nothing, no error. 2. Install. `dnf install kernel-uek` on a UEK7 node sees kernel-uek 5.15 already installed, prints "Nothing to do" and exits 0 -- so the script sailed past the install and died later with a misleading grubby error. 3. Boot. Already handled: grubby only auto-promotes within the running kernel's flavor lineage, so 5.x -> 6.x UEK never promotes on its own. Add ensure_kernel_repo(), which verifies the repo is enabled (necessary because skip_if_unavailable=1 hides a broken repo) and that it can serve a 6.x kernel-uek. When it cannot, a manager runs so-repo-sync to populate /nsm/kernelrepo and re-checks; a minion cannot fix it and exits non-zero pointing the admin at the manager. Airgap managers bail, since their repo comes from the ISO rather than a sync. Install the explicit UEK8 NEVRA instead of the bare package name so the "Nothing to do" exit-0 case cannot mask a no-op, and pin the repoquery to securityonionkernel so a UEK7 kernel-uek in the main repo is never picked. Still idempotent and still never reboots. --- salt/common/tools/sbin/so-kernel-upgrade | 160 +++++++++++++++++------ 1 file changed, 119 insertions(+), 41 deletions(-) diff --git a/salt/common/tools/sbin/so-kernel-upgrade b/salt/common/tools/sbin/so-kernel-upgrade index f901dad8d..d04ca533c 100755 --- a/salt/common/tools/sbin/so-kernel-upgrade +++ b/salt/common/tools/sbin/so-kernel-upgrade @@ -5,37 +5,56 @@ # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. # -# so-kernel-upgrade — install the UEK8 (6.x) kernel if needed and make it the boot default. +# so-kernel-upgrade — install the UEK8 (6.x) kernel and make it the boot default. # -# Security Onion is moving off the EL9 stock kernel (RHCK, 5.14) / UEK7 (5.15) onto UEK8 (6.x). -# Two separate things have to happen, and neither is automatic: +# Security Onion is moving off the EL9 stock kernel (RHCK, 5.14) and UEK7 (5.15) onto UEK8 +# (6.x). Three things have to happen, and none of them are automatic: # -# 1. Install it. A node running RHCK has no kernel-uek* package at all, so 'dnf update' -# never pulls UEK8 in — there is nothing to upgrade. The kernel-uek metapackage has to -# be installed explicitly from the securityonionkernel repo. -# 2. Boot it. Installing kernel-uek-core adds a UEK8 boot entry but does NOT make it the -# default: kernel-install/grubby only auto-promote a new kernel within the running -# kernel's flavor lineage, and we're crossing from a 5.x kernel to the new 6.x UEK -# flavor. So even with UPDATEDEFAULT=yes and DEFAULTKERNEL=kernel-uek-core the box -# keeps booting the old kernel until grubby is told otherwise. +# 1. Populate. The manager mirrors the UEK8 packages into /nsm/kernelrepo via so-repo-sync, +# and serves them to the grid over https:///kernelrepo. Until that sync runs the +# repo is valid but EMPTY -- dnf resolves it happily and installs nothing, with no error. +# 2. Install. A node on RHCK has no kernel-uek* package at all, so there is nothing for +# 'dnf update' to upgrade. A node on UEK7 does have kernel-uek installed, so +# 'dnf install kernel-uek' reports "Nothing to do" and exits 0 without installing 6.x. +# Both cases need an explicit install of the UEK8 NEVRA. +# 3. Boot it. Installing kernel-uek-core adds a UEK8 boot entry but does NOT make it the +# default: kernel-install/grubby only auto-promote within the running kernel's flavor +# lineage, and we're crossing from 5.x to the 6.x UEK flavor. So even with +# UPDATEDEFAULT=yes and DEFAULTKERNEL=kernel-uek-core the box keeps booting the old +# kernel until grubby is told otherwise. # -# This tool does both, then points DEFAULTKERNEL at kernel-uek-core so later kernel updates -# stay on the UEK line. +# Every one of those failure modes is silent by default. This tool does all three and fails +# loudly when it cannot, rather than reporting success while changing nothing. +# +# Manager vs minion: only the manager owns /nsm/kernelrepo, so only the manager can populate +# it. If the repo is empty here, a manager runs so-repo-sync itself; a minion has no way to +# fix it and exits non-zero telling the admin to sync the manager first. # # Idempotent: an already-installed, already-default UEK8 kernel is left alone. It only sets -# the boot default; it does NOT reboot — the admin reboots the node on their own schedule. +# the boot default; it does NOT reboot -- the admin reboots the node on their own schedule. + +. /usr/sbin/so-common KERNEL_REPO="securityonionkernel" KERNEL_PKG="kernel-uek" +KERNEL_REPO_DIR="/nsm/kernelrepo" +REPOSYNC_CONF="/opt/so/conf/reposync/repodownload.conf" +GLOBAL_PILLAR="/opt/so/saltstack/local/pillar/global/soc_global.sls" log() { echo "[so-kernel-upgrade] $*"; } +die() { echo "[so-kernel-upgrade] ERROR: $*" >&2; exit 1; } -[ "$(id -u)" -eq 0 ] || { log "must run as root"; exit 1; } -command -v grubby >/dev/null 2>&1 || { log "grubby not found"; exit 1; } -command -v dnf >/dev/null 2>&1 || { log "dnf not found"; exit 1; } +command -v grubby >/dev/null 2>&1 || die "grubby not found" +command -v dnf >/dev/null 2>&1 || die "dnf not found" + +ARCH="$(rpm -E '%{_arch}')" + +is_airgap() { + [ -f "$GLOBAL_PILLAR" ] && grep -q 'airgap: *[Tt]rue' "$GLOBAL_PILLAR" +} # Newest installed UEK8 (6.x) kernel known to the bootloader. UEK8 vmlinuz paths look like -# /boot/vmlinuz-6.12.0-203.76.7.5.el9uek.x86_64; the 5.15 UEK7 and 5.14 RHCK won't match. +# /boot/vmlinuz-6.12.0-204.92.4.2.el9uek.x86_64; UEK7 (5.15) and RHCK (5.14) won't match. find_uek8() { grubby --info=ALL 2>/dev/null \ | sed -n 's/^kernel="\(.*\)"$/\1/p' \ @@ -43,28 +62,86 @@ find_uek8() { | sort -V | tail -1 } +# Newest UEK8 kernel-uek NEVRA offered by the kernel repo, empty if the repo has none. +# Restricted to the kernel repo so a UEK7 kernel-uek in the main repo can't be picked up, +# and filtered to 6.x so we never "succeed" by reinstalling the 5.15 we already have. +uek8_available() { + dnf -q repoquery --disablerepo='*' --enablerepo="$KERNEL_REPO" \ + --arch="$ARCH" --latest-limit=1 \ + --qf '%{name}-%{evr}.%{arch}\n' "$KERNEL_PKG" 2>/dev/null \ + | grep -E "^${KERNEL_PKG}-6\." | tail -1 +} + +kernelrepo_rpm_count() { + find "$KERNEL_REPO_DIR" -maxdepth 1 -name '*.rpm' 2>/dev/null | wc -l +} + +# The kernel repo starts life as valid-but-empty (kernelrepo_init_empty in +# salt/manager/init.sls) and is filled by so-repo-sync. During a soup, so-repo-sync runs +# BEFORE the highstate deploys the [securityonionkernel] section into repodownload.conf, so +# the first kernel-aware soup leaves the repo empty until the next nightly sync. +sync_kernel_repo() { + if is_airgap; then + log "airgap install: $KERNEL_REPO_DIR is populated from the airgap ISO, not by so-repo-sync." + return 1 + fi + if ! grep -q "^\[${KERNEL_REPO}\]" "$REPOSYNC_CONF" 2>/dev/null; then + log "$REPOSYNC_CONF has no [${KERNEL_REPO}] section -- run a highstate to deploy it." + return 1 + fi + + log "populating $KERNEL_REPO_DIR with so-repo-sync (mirrors upstream; can take several minutes)" + su socore -c '/usr/sbin/so-repo-sync' || { log "so-repo-sync failed"; return 1; } + + dnf -q clean expire-cache >/dev/null 2>&1 + return 0 +} + +# Make the kernel repo actually able to serve a UEK8 package, or fail trying. +ensure_kernel_repo() { + # The repo is assigned by the repo.client highstate, and only once NICs are pinned by MAC + # (/opt/so/state/nic_names_pinned) so the kernel swap can't renumber interfaces SO binds + # by name. skip_if_unavailable=1 means a broken repo is silently ignored, so check first. + if ! dnf -q repolist --enabled 2>/dev/null | awk '{print $1}' | grep -qx "$KERNEL_REPO"; then + log "repo '$KERNEL_REPO' is not enabled on this node." + log "Run a highstate first; the repo is skipped until /opt/so/state/nic_names_pinned" + log "exists (run so-nic-pin) and this node's salt matches the version this release ships." + die "kernel repo unavailable" + fi + + [ -n "$(uek8_available)" ] && return 0 + + log "repo '$KERNEL_REPO' is enabled but offers no UEK8 $KERNEL_PKG package" + + if ! is_manager_node; then + log "This is a minion; it consumes the kernel repo from the manager and cannot populate it." + log "On the manager, run: su socore -c /usr/sbin/so-repo-sync" + log "then re-run this script here." + die "manager's kernel repo is empty" + fi + + log "this is a manager and $KERNEL_REPO_DIR holds $(kernelrepo_rpm_count) rpm(s)" + sync_kernel_repo || die "could not populate $KERNEL_REPO_DIR" + + [ -n "$(uek8_available)" ] \ + || die "so-repo-sync completed but $KERNEL_REPO still offers no UEK8 $KERNEL_PKG" +} + target="$(find_uek8)" if [ -z "$target" ]; then - log "no UEK8 kernel installed — installing $KERNEL_PKG from $KERNEL_REPO" + log "no UEK8 kernel installed (running $(uname -r))" - # The repo is assigned by the repo.client highstate, and only once NICs are pinned by MAC - # (/opt/so/state/nic_names_pinned) so the kernel swap can't renumber interfaces SO binds - # by name. Without the repo we'd silently pull nothing, so fail loudly instead. - if ! dnf -q repolist --enabled 2>/dev/null | awk '{print $1}' | grep -qx "$KERNEL_REPO"; then - log "ERROR: repo '$KERNEL_REPO' is not enabled on this node." - log "Run a highstate first; it is skipped until /opt/so/state/nic_names_pinned exists" - log "(run so-nic-pin) and this node's salt matches the version this release ships." - exit 1 - fi + ensure_kernel_repo + nevra="$(uek8_available)" - dnf -y install "$KERNEL_PKG" || { log "ERROR: failed to install $KERNEL_PKG"; exit 1; } + # Install the explicit NEVRA, not the bare package name. On a UEK7 node 'dnf install + # kernel-uek' sees kernel-uek-5.15 already installed, prints "Nothing to do" and exits 0. + log "installing $nevra from $KERNEL_REPO" + dnf -y install "$nevra" || die "failed to install $nevra" target="$(find_uek8)" - if [ -z "$target" ]; then - log "ERROR: $KERNEL_PKG installed but no 6.x UEK boot entry appeared — check 'grubby --info=ALL'" - exit 1 - fi + [ -n "$target" ] || die "$nevra installed but no 6.x UEK boot entry appeared -- check 'grubby --info=ALL'" log "installed UEK8 kernel: $target" fi @@ -75,24 +152,25 @@ if [ -f /etc/sysconfig/kernel ] && ! grep -q '^DEFAULTKERNEL=kernel-uek-core$' / sed -i 's/^DEFAULTKERNEL=.*/DEFAULTKERNEL=kernel-uek-core/' /etc/sysconfig/kernel fi +reboot_notice() { + [ "$(uname -r)" = "$(basename "$1" | sed 's/^vmlinuz-//')" ] \ + || log "REBOOT REQUIRED to start using the UEK8 kernel (currently running $(uname -r))." +} + current="$(grubby --default-kernel 2>/dev/null)" if [ "$current" = "$target" ]; then log "UEK8 kernel is already the boot default: $target" - [ "$(uname -r)" = "$(basename "$target" | sed 's/^vmlinuz-//')" ] \ - || log "REBOOT REQUIRED to start using it (currently running $(uname -r))." + reboot_notice "$target" exit 0 fi log "current default kernel: ${current:-unknown}" log "switching boot default to UEK8 kernel: $target" -grubby --set-default="$target" || { log "ERROR: grubby --set-default failed for $target"; exit 1; } +grubby --set-default="$target" || die "grubby --set-default failed for $target" # Verify the change actually took before claiming success. now="$(grubby --default-kernel 2>/dev/null)" -if [ "$now" != "$target" ]; then - log "ERROR: default kernel is still '${now:-unknown}' after set-default" - exit 1 -fi +[ "$now" = "$target" ] || die "default kernel is still '${now:-unknown}' after set-default" log "boot default is now $target" -log "REBOOT REQUIRED to start using the UEK8 kernel (currently running $(uname -r))." +reboot_notice "$target" From 9a71f64a358121ce0bd7bca6fd872c03d17a5ba0 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Thu, 9 Jul 2026 15:10:13 -0400 Subject: [PATCH 3/4] Branch so-kernel-upgrade on the running kernel flavor Only the RHCK->UEK flavor cross needs grubby --set-default; a UEK7->UEK8 update stays in the kernel-uek lineage and auto-promotes on its own. Detect the running kernel and act accordingly: - UEK8: already on target, no-op. - UEK7: populate the repo and install UEK8, then verify it auto-promoted (warn with the manual grubby command if it did not) -- no grubby change. - RHCK: install UEK8 and set the boot default explicitly, as before. Also make an already-installed UEK8 skip the repo entirely so a disabled or empty kernel repo can't block flipping the default, and correct the header comment that claimed every transition needs grubby. --- salt/common/tools/sbin/so-kernel-upgrade | 153 ++++++++++++++++------- 1 file changed, 108 insertions(+), 45 deletions(-) diff --git a/salt/common/tools/sbin/so-kernel-upgrade b/salt/common/tools/sbin/so-kernel-upgrade index d04ca533c..960505aee 100755 --- a/salt/common/tools/sbin/so-kernel-upgrade +++ b/salt/common/tools/sbin/so-kernel-upgrade @@ -8,7 +8,7 @@ # so-kernel-upgrade — install the UEK8 (6.x) kernel and make it the boot default. # # Security Onion is moving off the EL9 stock kernel (RHCK, 5.14) and UEK7 (5.15) onto UEK8 -# (6.x). Three things have to happen, and none of them are automatic: +# (6.x). Three things have to happen, and the tool has to drive each one: # # 1. Populate. The manager mirrors the UEK8 packages into /nsm/kernelrepo via so-repo-sync, # and serves them to the grid over https:///kernelrepo. Until that sync runs the @@ -17,13 +17,17 @@ # 'dnf update' to upgrade. A node on UEK7 does have kernel-uek installed, so # 'dnf install kernel-uek' reports "Nothing to do" and exits 0 without installing 6.x. # Both cases need an explicit install of the UEK8 NEVRA. -# 3. Boot it. Installing kernel-uek-core adds a UEK8 boot entry but does NOT make it the -# default: kernel-install/grubby only auto-promote within the running kernel's flavor -# lineage, and we're crossing from 5.x to the 6.x UEK flavor. So even with -# UPDATEDEFAULT=yes and DEFAULTKERNEL=kernel-uek-core the box keeps booting the old -# kernel until grubby is told otherwise. +# 3. Boot it. Whether a newly installed UEK8 kernel becomes the boot default depends on the +# RUNNING kernel's flavor. kernel-install/grubby (with UPDATEDEFAULT=yes) only auto-promote +# within the running kernel's flavor lineage: +# - From UEK7 (5.x, kernel-uek) the install stays in the kernel-uek lineage and IS +# auto-promoted, so no grubby change is needed -- just make sure the repo is populated +# and install UEK8. +# - From the stock EL9 kernel (RHCK, 5.14, no UEK) it is a flavor CROSS that is NOT +# auto-promoted, so the box keeps booting RHCK until grubby is told otherwise. +# This tool inspects the running kernel and only runs 'grubby --set-default' for RHCK. # -# Every one of those failure modes is silent by default. This tool does all three and fails +# Every one of those failure modes is silent by default. This tool handles each case and fails # loudly when it cannot, rather than reporting success while changing nothing. # # Manager vs minion: only the manager owns /nsm/kernelrepo, so only the manager can populate @@ -62,6 +66,19 @@ find_uek8() { | sort -V | tail -1 } +# Classify the RUNNING kernel (uname -r) -- this, not what's installed, is what decides whether +# a UEK8 install auto-promotes to the boot default: +# uek8 6.x UEK already on the target line; nothing to do +# uek7 5.x UEK a UEK8 install stays in the kernel-uek lineage and auto-promotes (no grubby) +# rhck 5.14 EL9 crossing into the UEK flavor does NOT auto-promote (needs grubby --set-default) +running_flavor() { + case "$(uname -r)" in + 6.*uek*) echo uek8 ;; + *uek*) echo uek7 ;; + *) echo rhck ;; + esac +} + # Newest UEK8 kernel-uek NEVRA offered by the kernel repo, empty if the repo has none. # Restricted to the kernel repo so a UEK7 kernel-uek in the main repo can't be picked up, # and filtered to 6.x so we never "succeed" by reinstalling the 5.15 we already have. @@ -127,50 +144,96 @@ ensure_kernel_repo() { || die "so-repo-sync completed but $KERNEL_REPO still offers no UEK8 $KERNEL_PKG" } -target="$(find_uek8)" - -if [ -z "$target" ]; then - log "no UEK8 kernel installed (running $(uname -r))" - - ensure_kernel_repo - nevra="$(uek8_available)" - - # Install the explicit NEVRA, not the bare package name. On a UEK7 node 'dnf install - # kernel-uek' sees kernel-uek-5.15 already installed, prints "Nothing to do" and exits 0. - log "installing $nevra from $KERNEL_REPO" - dnf -y install "$nevra" || die "failed to install $nevra" - - target="$(find_uek8)" - [ -n "$target" ] || die "$nevra installed but no 6.x UEK boot entry appeared -- check 'grubby --info=ALL'" - log "installed UEK8 kernel: $target" -fi - -# Keep future kernel updates on the UEK line rather than falling back to RHCK. Oracle ships -# this file; only rewrite it when it's actually pointing somewhere else. -if [ -f /etc/sysconfig/kernel ] && ! grep -q '^DEFAULTKERNEL=kernel-uek-core$' /etc/sysconfig/kernel; then - log "setting DEFAULTKERNEL=kernel-uek-core in /etc/sysconfig/kernel" - sed -i 's/^DEFAULTKERNEL=.*/DEFAULTKERNEL=kernel-uek-core/' /etc/sysconfig/kernel -fi - reboot_notice() { [ "$(uname -r)" = "$(basename "$1" | sed 's/^vmlinuz-//')" ] \ || log "REBOOT REQUIRED to start using the UEK8 kernel (currently running $(uname -r))." } -current="$(grubby --default-kernel 2>/dev/null)" -if [ "$current" = "$target" ]; then - log "UEK8 kernel is already the boot default: $target" - reboot_notice "$target" +# Keep future kernel updates on the UEK line rather than falling back to RHCK. Oracle ships +# /etc/sysconfig/kernel; only rewrite it when it's actually pointing somewhere else. +set_default_kernel_conf() { + if [ -f /etc/sysconfig/kernel ] && ! grep -q '^DEFAULTKERNEL=kernel-uek-core$' /etc/sysconfig/kernel; then + log "setting DEFAULTKERNEL=kernel-uek-core in /etc/sysconfig/kernel" + sed -i 's/^DEFAULTKERNEL=.*/DEFAULTKERNEL=kernel-uek-core/' /etc/sysconfig/kernel + fi +} + +# Make sure a UEK8 kernel is installed, leaving its boot entry in INSTALLED_UEK8. If one is +# already present we leave the repo alone -- it may be disabled or empty and we don't need it +# just to flip the boot default. Otherwise install the explicit NEVRA, not the bare package +# name: on a UEK7 node 'dnf install kernel-uek' sees 5.15 already present, prints "Nothing to +# do" and exits 0 without installing 6.x. +ensure_uek8_installed() { + INSTALLED_UEK8="$(find_uek8)" + if [ -n "$INSTALLED_UEK8" ]; then + log "UEK8 kernel already installed: $INSTALLED_UEK8" + return 0 + fi + + ensure_kernel_repo + local nevra; nevra="$(uek8_available)" + log "installing $nevra from $KERNEL_REPO" + dnf -y install "$nevra" || die "failed to install $nevra" + + INSTALLED_UEK8="$(find_uek8)" + [ -n "$INSTALLED_UEK8" ] || die "$nevra installed but no 6.x UEK boot entry appeared -- check 'grubby --info=ALL'" + log "installed UEK8 kernel: $INSTALLED_UEK8" +} + +case "$(running_flavor)" in +uek8) + # Already on the 6.x UEK line. A plain 'dnf update' keeps this node current within the + # lineage and auto-promotes newer builds, so there is nothing for this tool to do. + log "already running a UEK8 kernel ($(uname -r)); nothing to do." exit 0 -fi + ;; -log "current default kernel: ${current:-unknown}" -log "switching boot default to UEK8 kernel: $target" -grubby --set-default="$target" || die "grubby --set-default failed for $target" +uek7) + # On a 5.x UEK kernel. Installing UEK8 stays inside the kernel-uek lineage, so dnf/grubby + # (UPDATEDEFAULT=yes) auto-promote it and we do NOT touch grubby. A node still on UEK7 + # usually means the kernel repo was empty when it last updated, so populate it and install. + log "running UEK7 kernel ($(uname -r)); the kernel repo was likely not yet populated when" + log "this node last updated. Populating it and installing UEK8 -- the update stays on the" + log "kernel-uek line, so it becomes the boot default automatically (no grubby change needed)." + set_default_kernel_conf + ensure_uek8_installed -# Verify the change actually took before claiming success. -now="$(grubby --default-kernel 2>/dev/null)" -[ "$now" = "$target" ] || die "default kernel is still '${now:-unknown}' after set-default" + now="$(grubby --default-kernel 2>/dev/null)" + if [ "$now" = "$INSTALLED_UEK8" ]; then + log "boot default auto-promoted to UEK8 kernel: $INSTALLED_UEK8" + else + log "WARNING: expected the UEK8 kernel to auto-promote but the default is still" + log "'${now:-unknown}'. Run 'grubby --set-default=$INSTALLED_UEK8' to force it." + fi + reboot_notice "$INSTALLED_UEK8" + ;; -log "boot default is now $target" -reboot_notice "$target" +rhck) + # On the stock EL9 kernel (5.14, no UEK installed). Crossing from RHCK into the UEK flavor + # does NOT auto-promote -- kernel-install/grubby only auto-promote within the running + # kernel's flavor lineage -- so after installing we must set the boot default explicitly. + log "running stock EL9 (RHCK) kernel ($(uname -r)); installing UEK8 and setting it as the" + log "boot default explicitly (a RHCK->UEK flavor change does not auto-promote)." + set_default_kernel_conf + ensure_uek8_installed + target="$INSTALLED_UEK8" + + current="$(grubby --default-kernel 2>/dev/null)" + if [ "$current" = "$target" ]; then + log "UEK8 kernel is already the boot default: $target" + reboot_notice "$target" + exit 0 + fi + + log "current default kernel: ${current:-unknown}" + log "switching boot default to UEK8 kernel: $target" + grubby --set-default="$target" || die "grubby --set-default failed for $target" + + # Verify the change actually took before claiming success. + now="$(grubby --default-kernel 2>/dev/null)" + [ "$now" = "$target" ] || die "default kernel is still '${now:-unknown}' after set-default" + + log "boot default is now $target" + reboot_notice "$target" + ;; +esac From 52885e28c5012116fad1d096caad45fa0c70856e Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Thu, 9 Jul 2026 17:02:47 -0400 Subject: [PATCH 4/4] Name the reposync-side kernel repo securityonionkernelsync The reposync section in repodownload.conf and the client repo assigned in repo/client/oracle.sls both used the bare name securityonionkernel, colliding across the two roles. Rename the reposync-side section (and its --repoid, the so-repo-sync guard, and the so-kernel-upgrade presence check) to securityonionkernelsync, mirroring the existing securityonion/securityonionsync split for the main repo. The client repo stays securityonionkernel. Also give the section its own name=Security Onion Kernel Repo repo. --- salt/common/tools/sbin/so-kernel-upgrade | 10 +++++++--- salt/manager/files/repodownload.conf | 4 ++-- salt/manager/tools/sbin/so-repo-sync | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/salt/common/tools/sbin/so-kernel-upgrade b/salt/common/tools/sbin/so-kernel-upgrade index 960505aee..e750c1a52 100755 --- a/salt/common/tools/sbin/so-kernel-upgrade +++ b/salt/common/tools/sbin/so-kernel-upgrade @@ -39,7 +39,11 @@ . /usr/sbin/so-common +# Client-side repo id (what dnf enables on this node, from repo/client/oracle.sls) vs the +# reposync-side section in repodownload.conf that the manager mirrors from (mirrors the +# securityonion/securityonionsync split for the main repo). KERNEL_REPO="securityonionkernel" +KERNEL_REPO_SYNC="securityonionkernelsync" KERNEL_PKG="kernel-uek" KERNEL_REPO_DIR="/nsm/kernelrepo" REPOSYNC_CONF="/opt/so/conf/reposync/repodownload.conf" @@ -95,15 +99,15 @@ kernelrepo_rpm_count() { # The kernel repo starts life as valid-but-empty (kernelrepo_init_empty in # salt/manager/init.sls) and is filled by so-repo-sync. During a soup, so-repo-sync runs -# BEFORE the highstate deploys the [securityonionkernel] section into repodownload.conf, so +# BEFORE the highstate deploys the [securityonionkernelsync] section into repodownload.conf, so # the first kernel-aware soup leaves the repo empty until the next nightly sync. sync_kernel_repo() { if is_airgap; then log "airgap install: $KERNEL_REPO_DIR is populated from the airgap ISO, not by so-repo-sync." return 1 fi - if ! grep -q "^\[${KERNEL_REPO}\]" "$REPOSYNC_CONF" 2>/dev/null; then - log "$REPOSYNC_CONF has no [${KERNEL_REPO}] section -- run a highstate to deploy it." + if ! grep -q "^\[${KERNEL_REPO_SYNC}\]" "$REPOSYNC_CONF" 2>/dev/null; then + log "$REPOSYNC_CONF has no [${KERNEL_REPO_SYNC}] section -- run a highstate to deploy it." return 1 fi diff --git a/salt/manager/files/repodownload.conf b/salt/manager/files/repodownload.conf index 67ae4b121..9c9cb5109 100644 --- a/salt/manager/files/repodownload.conf +++ b/salt/manager/files/repodownload.conf @@ -11,8 +11,8 @@ name=Security Onion Repo repo mirrorlist=file:///opt/so/conf/reposync/mirror.txt enabled=1 gpgcheck=1 -[securityonionkernel] -name=Security Onion Repo repo +[securityonionkernelsync] +name=Security Onion Kernel Repo repo mirrorlist=file:///opt/so/conf/reposync/mirror-kernel.txt enabled=1 gpgcheck=1 diff --git a/salt/manager/tools/sbin/so-repo-sync b/salt/manager/tools/sbin/so-repo-sync index 6c1b9d509..d6a290c25 100755 --- a/salt/manager/tools/sbin/so-repo-sync +++ b/salt/manager/tools/sbin/so-repo-sync @@ -17,9 +17,9 @@ createrepo /nsm/repo # The kernel repo section is deployed to repodownload.conf by the manager highstate, which # runs AFTER this script during soup. On the first upgrade to a kernel-aware version the # on-disk config still predates the section, so guard on its presence to avoid dnf's -# "Unknown repo: 'securityonionkernel'" aborting the sync (set -e). The next sync after the +# "Unknown repo: 'securityonionkernelsync'" aborting the sync (set -e). The next sync after the # highstate deploys the section will pick it up. -if grep -q '^\[securityonionkernel\]' /opt/so/conf/reposync/repodownload.conf; then - dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionkernel --download-metadata -p /nsm/kernelrepo/ +if grep -q '^\[securityonionkernelsync\]' /opt/so/conf/reposync/repodownload.conf; then + dnf reposync --norepopath -g --delete -m -c /opt/so/conf/reposync/repodownload.conf --repoid=securityonionkernelsync --download-metadata -p /nsm/kernelrepo/ createrepo /nsm/kernelrepo fi