From aa1fdc2508bbcf6ca0ec092d9309ce6636ef1383 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Wed, 4 Nov 2026 16:52:23 +0100 Subject: [PATCH] Read the MOTD's OS version at login motd_ssh wrote the release into the login banner and /etc/motd as fixed text, so a container upgraded in place kept showing the release it was created with (ProxmoxVE#18451). The profile.d line now reads /etc/os-release at login, and /etc/motd no longer carries the version. update_motd_ip turns an old fixed line into the new one and runs from on_exit, since most ct scripts exit inside update_script and never reached it. --- .github/workflows/verify-split-files.yml | 4 ++ core/error_handler.func | 1 + lxc/install.func | 17 +----- tests/motd-os-version.sh | 78 ++++++++++++++++++++++++ ui/menu.func | 5 +- ui/validate.func | 11 ++-- 6 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 tests/motd-os-version.sh diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index cf1bada..2480a58 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -55,6 +55,7 @@ on: - "tests/silent-verbose.sh" - "tests/github-release-noapi.sh" - "tests/vm-dialog-esc.sh" + - "tests/motd-os-version.sh" - ".github/workflows/verify-split-files.yml" push: branches: [main] @@ -101,6 +102,7 @@ on: - "tests/silent-verbose.sh" - "tests/github-release-noapi.sh" - "tests/vm-dialog-esc.sh" + - "tests/motd-os-version.sh" - ".github/workflows/verify-split-files.yml" workflow_dispatch: @@ -207,6 +209,8 @@ jobs: run: bash tests/silent-verbose.sh - name: VM dialog Esc tests run: bash tests/vm-dialog-esc.sh + - name: MOTD OS version tests + run: bash tests/motd-os-version.sh - name: GitHub release without the API tests run: bash tests/github-release-noapi.sh diff --git a/core/error_handler.func b/core/error_handler.func index 9142221..dce5db5 100644 --- a/core/error_handler.func +++ b/core/error_handler.func @@ -685,6 +685,7 @@ on_exit() { if [[ $exit_code -eq 0 && "${_CS_UPDATE_CLEANUP:-}" == "1" ]] && declare -f cleanup_lxc >/dev/null 2>&1; then _CS_UPDATE_CLEANUP=0 cleanup_lxc || true + declare -f update_motd_ip >/dev/null 2>&1 && { update_motd_ip || true; } fi # fetch_and_deploy_release leaves a pending marker next to the stamp; only a diff --git a/lxc/install.func b/lxc/install.func index 97d832c..5aaafd9 100644 --- a/lxc/install.func +++ b/lxc/install.func @@ -1503,16 +1503,8 @@ fi EOF fi - # Get OS information - local os_name="$OS_TYPE" - local os_version="$OS_VERSION" - - if [[ -f /etc/os-release ]]; then - os_name=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"' || true) - os_version=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"' || true) - [[ -z "$os_name" ]] && os_name="$OS_TYPE" - [[ -z "$os_version" ]] && os_version="${OS_VERSION:-rolling}" - fi + # No & or \: update_motd_ip puts this into a sed replacement. + local os_line='$(if [ -r /etc/os-release ]; then . /etc/os-release; fi; echo "${NAME:-Linux} - Version: ${VERSION_ID:-rolling}")' # Create MOTD profile script. The testing block is conditional -- printed # unconditionally it told every Incus container it was a DevScripts build. @@ -1544,7 +1536,7 @@ EOF cat >>"$PROFILE_FILE" </dev/null | sed -n 's/.* src \\([0-9.]\\+\\).*/\\1/p' | head -1)${CL:-}" EOF @@ -1576,7 +1568,6 @@ EOF ${APPLICATION:-Container} LXC Container - TESTING BUILD This script is still being tested. Without feedback it cannot be finished. - OS: ${os_name} - Version: ${os_version} Report back: ${CS_TESTING_URL} Works, broken, or anything odd - one line is enough. @@ -1587,7 +1578,6 @@ EOF ${APPLICATION:-Container} LXC Container - DEV Repository WARNING: This is a DEVELOPMENT version (DevScripts). Do NOT use in production! - OS: ${os_name} - Version: ${os_version} Repository: https://github.com/community-scripts/DevScripts EOF @@ -1595,7 +1585,6 @@ EOF cat >/etc/motd < in <$3>" >&2 + failed=$((failed + 1)) + fi +} + +os_release() { printf 'NAME="%s"\n%s' "$1" "${2:+VERSION_ID=\"$2\"$'\n'}" >"$T/etc/os-release"; } +get_current_ip() { echo 192.0.2.10; } +post_progress_to_api() { :; } +_is_container_context() { return 0; } +# shellcheck disable=SC2034 +TAB="" NETWORK="" YW="" GN="" CL="" OS="" HOSTNAME="" INFO="" GATEWAY="" BOLD="" RD="" OS_TYPE=debian OS_VERSION=12 + +shells=() +for sh in bash dash "busybox sh"; do + command -v "${sh%% *}" >/dev/null 2>&1 && $sh -c : 2>/dev/null && shells+=("$sh") +done + +os_release "Debian GNU/Linux" 12 +APPLICATION=Test motd_ssh +os_release "Debian GNU/Linux" 13 +for sh in "${shells[@]}"; do + check "$sh: login shows the upgraded release" "OS: Debian GNU/Linux - Version: 13" "$($sh -c ". '$PROFILE'" 2>&1)" + check "$sh: NAME stays out of the login shell" "unset" "$($sh -c ". '$PROFILE' >/dev/null 2>&1; echo \${NAME-unset}")" +done + +os_release "Arch Linux" +check "no VERSION_ID reads as rolling" "Arch Linux - Version: rolling" "$(bash -c ". '$PROFILE'")" +rm -f "$T/etc/os-release" +check "no os-release still prints a line" "OS: Linux - Version: rolling" "$(bash -c ". '$PROFILE'")" +check "/etc/motd carries no version" "none" "$(grep -q 'Version' "$T/etc/motd" && echo found || echo none)" + +esc=$'\033' +cat >"$PROFILE" <"$T/etc/motd" +os_release "Debian GNU/Linux" 13 +update_motd_ip >/dev/null 2>&1 +check "update turns a fixed OS line into one read at login" "Debian GNU/Linux - Version: 13" "$(bash -c ". '$PROFILE'")" +check "update drops the fixed OS line from /etc/motd" "none" "$(grep -q 'Version' "$T/etc/motd" && echo found || echo none)" +cp "$PROFILE" "$T/before" +update_motd_ip >/dev/null 2>&1 +check "a second update leaves the OS line alone" "same" "$(cmp -s "$PROFILE" "$T/before" && echo same || echo changed)" + +result=$( + cleanup_lxc() { :; } + update_motd_ip() { echo motd; } + _CS_UPDATE_CLEANUP=1 + trap on_exit EXIT + exit 0 +) +check "an update that exits inside update_script still refreshes the MOTD" "motd" "$result" + +printf '%s passed, %s failed (shells: %s)\n' "$passed" "$failed" "${shells[*]}" +((failed == 0)) diff --git a/ui/menu.func b/ui/menu.func index 8463747..60cc9ba 100644 --- a/ui/menu.func +++ b/ui/menu.func @@ -1126,8 +1126,8 @@ check_breaking_change_guard() { # The update path, shared by the three branches of start() that reach it. # exit, not return: returning continues into the ct script's host-only tail. -# Most ct scripts exit inside update_script, so the two calls after it are -# unreachable for them; the cleanup therefore runs from on_exit. +# Most ct scripts exit inside update_script, so the call after it is +# unreachable for them; cleanup and MOTD therefore run from on_exit. _cs_run_update() { ensure_profile_loaded ensure_recorded_toolchains @@ -1140,7 +1140,6 @@ _cs_run_update() { _CS_UPDATE_CLEANUP=1 update_script run_addon_updates - update_motd_ip exit 0 } diff --git a/ui/validate.func b/ui/validate.func index 53db13a..6ca529a 100644 --- a/ui/validate.func +++ b/ui/validate.func @@ -73,7 +73,7 @@ update_motd_ip() { # always checked whose file it is; this one never did. if [ -f "$MOTD_FILE" ] && grep -q "LXC Container" "$MOTD_FILE" 2>/dev/null; then # Remove existing IP Address lines to prevent duplication - sed -i '/IP Address:/d' "$MOTD_FILE" + sed -i '/IP Address:/d; / OS: .* - Version: /d' "$MOTD_FILE" IP=$(get_current_ip) # Add the new IP address @@ -104,7 +104,7 @@ update_motd_ip() { fi # Get current values - local current_os="$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - Version: $(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"')" + local os_line='$(if [ -r /etc/os-release ]; then . /etc/os-release; fi; echo "${NAME:-Linux} - Version: ${VERSION_ID:-rolling}")' local current_hostname="$(hostname 2>/dev/null || uname -n)" # get_current_ip, not `hostname -I`: BusyBox's hostname has no -I, so on # Alpine this aborted the update with "unrecognized option: I". The helper @@ -114,15 +114,14 @@ update_motd_ip() { # $GN/$CL are unset in a login shell, so a line using them never colors. # The staleness checks below only compare the value text, so a line with a # current value but a dead placeholder is never revisited; force it here. - local esc dead_color_os dead_color_hostname dead_color_ip + local esc dead_color_hostname dead_color_ip esc=$'\033' - grep -qE 'OS:.*\$\{GN\}' "$profile_file" 2>/dev/null && dead_color_os=1 grep -qE 'Hostname:.*\$\{GN\}' "$profile_file" 2>/dev/null && dead_color_hostname=1 grep -qE 'IP Address:.*\$\{GN\}' "$profile_file" 2>/dev/null && dead_color_ip=1 # Update only if values actually changed (or the color placeholder is dead) - if [[ -n "${dead_color_os:-}" ]] || ! grep -q "OS:.*$current_os" "$profile_file" 2>/dev/null; then - sed -i "s|OS:.*|OS: ${esc}[1;92m$current_os${esc}[m\\\"|" "$profile_file" + if ! grep -q 'OS:.*os-release' "$profile_file" 2>/dev/null; then + sed -i "s|OS:.*|OS: ${esc}[1;92m${os_line}${esc}[m\\\"|" "$profile_file" fi if [[ -n "${dead_color_hostname:-}" ]] || ! grep -q "Hostname:.*$current_hostname" "$profile_file" 2>/dev/null; then sed -i "s|Hostname:.*|Hostname: ${esc}[1;92m$current_hostname${esc}[m\\\"|" "$profile_file"