From a82afbc4025a228a8a50b58b38ed39f43a81c081 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 26 Jun 2026 05:12:51 -0700 Subject: [PATCH 1/2] Add Debian Sys V init.d service scripts Restore init.d scripts for salt-minion, salt-master, salt-api, and salt-syndic that were removed from Debian packages. Systems without systemd (e.g. Devuan) need these scripts to manage Salt services. Also add test_salt_sysv_service_files to verify init.d scripts are present in Debian and RedHat packages, and remove the ubuntu/debian exclusion from the Linux PID change assertion in salt_test_upgrade so Debian/Ubuntu upgrades are also validated. Fixes #67765 --- changelog/67765.fixed.md | 1 + pkg/debian/salt-api.init | 99 ++++++++++++++++ pkg/debian/salt-master.init | 112 ++++++++++++++++++ pkg/debian/salt-minion.init | 107 +++++++++++++++++ pkg/debian/salt-syndic.init | 107 +++++++++++++++++ .../pytests/pkg/upgrade/test_salt_upgrade.py | 54 ++++++++- 6 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 changelog/67765.fixed.md create mode 100644 pkg/debian/salt-api.init create mode 100644 pkg/debian/salt-master.init create mode 100644 pkg/debian/salt-minion.init create mode 100644 pkg/debian/salt-syndic.init diff --git a/changelog/67765.fixed.md b/changelog/67765.fixed.md new file mode 100644 index 000000000000..10e44c28bccf --- /dev/null +++ b/changelog/67765.fixed.md @@ -0,0 +1 @@ +Added back support for init.d service scripts diff --git a/pkg/debian/salt-api.init b/pkg/debian/salt-api.init new file mode 100644 index 000000000000..c9887f852a51 --- /dev/null +++ b/pkg/debian/salt-api.init @@ -0,0 +1,99 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: salt-api +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: REST API for Salt +# Description: salt-api provides a REST interface to the Salt master +### END INIT INFO + +# Author: Michael Prokop + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="REST API for Salt" +NAME=salt-api +DAEMON=/usr/bin/salt-api +DAEMON_ARGS="-d" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +. /lib/init/vars.sh +. /lib/lsb/init-functions + +do_start() { + pid=$(pidofproc -p $PIDFILE $DAEMON) + if [ -n "$pid" ] ; then + log_begin_msg "$DESC already running." + log_end_msg 0 + exit 0 + fi + + log_daemon_msg "Starting salt-api daemon: " + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_ARGS + log_end_msg $? +} + +do_stop() { + log_begin_msg "Stopping $DESC ..." + start-stop-daemon --stop --retry TERM/5 --quiet --oknodo --pidfile $PIDFILE + RC=$? + [ $RC -eq 0 ] && rm -f $PIDFILE + log_end_msg $RC +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload) + # not implemented + #;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +exit 0 diff --git a/pkg/debian/salt-master.init b/pkg/debian/salt-master.init new file mode 100644 index 000000000000..1edaa3e0e1ce --- /dev/null +++ b/pkg/debian/salt-master.init @@ -0,0 +1,112 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: salt-master +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: The Salt Master daemon +# Description: The Salt Master is the central server (management +# component) to which all Salt Minions connect +### END INIT INFO + +# Author: Michael Prokop + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="The Salt Master daemon" +NAME=salt-master +DAEMON=/usr/bin/salt-master +DAEMON_ARGS="-d" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +. /lib/lsb/init-functions + +do_start() { + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + pid=$(pidofproc -p $PIDFILE $DAEMON) + if [ -n "$pid" ] ; then + return 1 + fi + + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 +} + +do_stop() { + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + pids=$(pidof -x $DAEMON) + if [ $? -eq 0 ] ; then + echo $pids | xargs kill 2&1> /dev/null + RETVAL=0 + else + RETVAL=1 + fi + + [ "$RETVAL" = 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload) + # not implemented + #;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +exit 0 diff --git a/pkg/debian/salt-minion.init b/pkg/debian/salt-minion.init new file mode 100644 index 000000000000..e7eec559789a --- /dev/null +++ b/pkg/debian/salt-minion.init @@ -0,0 +1,107 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: salt-minion +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: The Salt Minion daemon +# Description: The Salt Minion is the agent component of Salt. It listens +# for instructions from the Master, runs jobs, and returns +# results back to the Salt Master +### END INIT INFO + +# Author: Michael Prokop + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="The Salt Minion daemon" +NAME=salt-minion +DAEMON=/usr/bin/salt-minion +DAEMON_ARGS="-d" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +. /lib/lsb/init-functions + +do_start() { + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + pid=$(pidofproc -p $PIDFILE $DAEMON) + if [ -n "$pid" ] ; then + return 1 + fi + + start-stop-daemon --start --quiet --background --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 +} + +do_stop() { + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload) + # not implemented + #;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +exit 0 diff --git a/pkg/debian/salt-syndic.init b/pkg/debian/salt-syndic.init new file mode 100644 index 000000000000..b3a8191947c4 --- /dev/null +++ b/pkg/debian/salt-syndic.init @@ -0,0 +1,107 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: salt-syndic +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: The Salt Syndic daemon +# Description: The Salt Syndic is a master daemon which can receive +# instructions from a higher-level Salt Master, allowing +# for tiered organization of your Salt infrastructure +### END INIT INFO + +# Author: Michael Prokop + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="The Salt Syndic daemon" +NAME=salt-syndic +DAEMON=/usr/bin/salt-syndic +DAEMON_ARGS="-d" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +. /lib/lsb/init-functions + +do_start() { + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + pid=$(pidofproc -p $PIDFILE $DAEMON) + if [ -n "$pid" ] ; then + return 1 + fi + + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 +} + +do_stop() { + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload) + # not implemented + #;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +exit 0 diff --git a/tests/pytests/pkg/upgrade/test_salt_upgrade.py b/tests/pytests/pkg/upgrade/test_salt_upgrade.py index 3fba8400ef61..6ab884a1bf86 100644 --- a/tests/pytests/pkg/upgrade/test_salt_upgrade.py +++ b/tests/pytests/pkg/upgrade/test_salt_upgrade.py @@ -1,5 +1,7 @@ import logging +import os import pathlib +import subprocess import sys import time @@ -8,6 +10,7 @@ import pytest from pytestskipmarkers.utils import platform +import salt.utils.path from tests.support.pkg import pep440_public_equal log = logging.getLogger(__name__) @@ -184,7 +187,7 @@ def salt_test_upgrade( new_minion_pids = _get_running_named_salt_pid(process_minion_name) new_master_pids = _get_running_named_salt_pid(process_master_name) - if sys.platform == "linux" and install_salt.distro_id not in ("ubuntu", "debian"): + if sys.platform == "linux": assert new_minion_pids assert new_master_pids if start_version < packaging.version.parse(install_salt.artifact_version): @@ -254,6 +257,55 @@ def _get_installed_salt_packages(): return packages +def test_salt_sysv_service_files(install_salt): + """ + Test that init.d service scripts are present in Debian/RedHat packages + """ + if not install_salt.upgrade: + pytest.skip("Not testing an upgrade, do not run") + + if sys.platform != "linux": + pytest.skip("Not testing on a Linux platform, do not run") + + if not (salt.utils.path.which("dpkg") or salt.utils.path.which("rpm")): + pytest.skip("Not testing on a Debian or RedHat family platform, do not run") + + test_pkgs = install_salt.pkgs + for test_pkg_name in test_pkgs: + test_pkg_basename = os.path.basename(test_pkg_name) + # Debian/Ubuntu name typically salt-minion_300xxxxxx + # Redhat name typically salt-minion-300xxxxxx + test_pkg_basename_dash_underscore = test_pkg_basename.split("300")[0] + test_pkg_basename_adj = test_pkg_basename_dash_underscore[:-1] + if test_pkg_basename_adj in ( + "salt-minion", + "salt-master", + "salt-syndic", + "salt-api", + ): + test_initd_name = f"/etc/init.d/{test_pkg_basename_adj}" + if salt.utils.path.which("dpkg"): + proc = subprocess.run( + ["dpkg", "-c", f"{test_pkg_name}"], + capture_output=True, + check=True, + ) + elif salt.utils.path.which("rpm"): + proc = subprocess.run( + ["rpm", "-q", "-l", "-p", f"{test_pkg_name}"], + capture_output=True, + check=True, + ) + found_line = False + for line in proc.stdout.decode().splitlines(): + # If test_initd_name not present we should fail. + if test_initd_name in line: + found_line = True + break + + assert found_line + + def test_salt_upgrade( salt_call_cli, install_salt, debian_disable_policy_rcd, salt_master, salt_minion ): From 02704621a55a069616c4c9d440d52e3cecd943ca Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Tue, 30 Jun 2026 21:18:14 -0700 Subject: [PATCH 2/2] Skip test_salt_sysv_service_files on RPM packages The RPM spec ships systemd unit files only; /etc/init.d/salt-* scripts are exclusively part of the Debian package payload restored by this PR. Rocky Linux, Photon OS, and Amazon Linux upgrade jobs were therefore failing on an assertion that could never hold on RPM. Skip the check when dpkg is unavailable so the assertion only runs against .deb packages, and include the missing init.d path in the assertion message for future debuggers. --- .../pytests/pkg/upgrade/test_salt_upgrade.py | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/pytests/pkg/upgrade/test_salt_upgrade.py b/tests/pytests/pkg/upgrade/test_salt_upgrade.py index 6ab884a1bf86..05451490d96d 100644 --- a/tests/pytests/pkg/upgrade/test_salt_upgrade.py +++ b/tests/pytests/pkg/upgrade/test_salt_upgrade.py @@ -259,7 +259,10 @@ def _get_installed_salt_packages(): def test_salt_sysv_service_files(install_salt): """ - Test that init.d service scripts are present in Debian/RedHat packages + Test that init.d service scripts are present in Debian packages. + + RPM packages ship systemd units only; init.d scripts are not part of the + RPM payload, so this check only applies to .deb packages. """ if not install_salt.upgrade: pytest.skip("Not testing an upgrade, do not run") @@ -267,14 +270,13 @@ def test_salt_sysv_service_files(install_salt): if sys.platform != "linux": pytest.skip("Not testing on a Linux platform, do not run") - if not (salt.utils.path.which("dpkg") or salt.utils.path.which("rpm")): - pytest.skip("Not testing on a Debian or RedHat family platform, do not run") + if not salt.utils.path.which("dpkg"): + pytest.skip("Not testing on a Debian family platform, do not run") test_pkgs = install_salt.pkgs for test_pkg_name in test_pkgs: test_pkg_basename = os.path.basename(test_pkg_name) # Debian/Ubuntu name typically salt-minion_300xxxxxx - # Redhat name typically salt-minion-300xxxxxx test_pkg_basename_dash_underscore = test_pkg_basename.split("300")[0] test_pkg_basename_adj = test_pkg_basename_dash_underscore[:-1] if test_pkg_basename_adj in ( @@ -284,18 +286,11 @@ def test_salt_sysv_service_files(install_salt): "salt-api", ): test_initd_name = f"/etc/init.d/{test_pkg_basename_adj}" - if salt.utils.path.which("dpkg"): - proc = subprocess.run( - ["dpkg", "-c", f"{test_pkg_name}"], - capture_output=True, - check=True, - ) - elif salt.utils.path.which("rpm"): - proc = subprocess.run( - ["rpm", "-q", "-l", "-p", f"{test_pkg_name}"], - capture_output=True, - check=True, - ) + proc = subprocess.run( + ["dpkg", "-c", f"{test_pkg_name}"], + capture_output=True, + check=True, + ) found_line = False for line in proc.stdout.decode().splitlines(): # If test_initd_name not present we should fail. @@ -303,7 +298,7 @@ def test_salt_sysv_service_files(install_salt): found_line = True break - assert found_line + assert found_line, f"{test_initd_name} not found in {test_pkg_basename}" def test_salt_upgrade(