2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-12 08:12:33 +00:00

fix(packaging): avoid SysV init paths in modern RPMs

This commit is contained in:
Vinícius Ferrão
2026-07-16 01:42:40 -03:00
parent d829be9e94
commit c34db2c55f
3 changed files with 445 additions and 25 deletions
+148
View File
@@ -0,0 +1,148 @@
#!/bin/sh
# Keep the xcatd SysV init script only on systems that actually use a
# legacy init implementation. XCAT_COMPAT_ROOT is used by image builders
# and tests to operate on a target root without chrooting into it.
compat_root=${XCAT_COMPAT_ROOT:-}
compat_root=${compat_root%/}
xcat_root=${XCATROOT:-/opt/xcat}
case "$xcat_root" in
/*) ;;
*)
echo "XCATROOT must be an absolute path" >&2
exit 2
;;
esac
legacy_init="$compat_root/etc/init.d/xcatd"
legacy_template="$compat_root$xcat_root/share/xcat/scripts/xcatd"
uses_systemd_os_release()
{
os_release="$compat_root/etc/os-release"
[ -r "$os_release" ] || return 1
os_id=
os_version=
while IFS='=' read -r key value; do
value=${value#\"}
value=${value%\"}
value=${value#\'}
value=${value%\'}
case "$key" in
ID) os_id=$value ;;
VERSION_ID) os_version=$value ;;
esac
done < "$os_release"
# Debian testing/sid intentionally omits VERSION_ID.
if [ "$os_id" = "debian" ] && [ -z "$os_version" ]; then
return 0
fi
os_major=${os_version%%.*}
case "$os_major" in
''|*[!0-9]*) return 1 ;;
esac
case "$os_id" in
rhel|centos|rocky|almalinux|ol|scientific)
[ "$os_major" -ge 7 ]
;;
fedora|ubuntu)
[ "$os_major" -ge 15 ]
;;
debian)
[ "$os_major" -ge 8 ]
;;
sles|sled|opensuse|opensuse-leap|opensuse-tumbleweed)
[ "$os_major" -ge 12 ]
;;
*)
return 1
;;
esac
}
uses_systemd()
{
if [ -d "$compat_root/run/systemd/system" ]; then
return 0
fi
# Chroots and minimal images may have systemd installed without a running
# manager or an /sbin/init link.
if [ -x "$compat_root/usr/lib/systemd/systemd" ] ||
[ -x "$compat_root/lib/systemd/systemd" ]; then
return 0
fi
if uses_systemd_os_release; then
return 0
fi
if [ -L "$compat_root/sbin/init" ]; then
init_target=$(readlink "$compat_root/sbin/init" 2>/dev/null || true)
case "$init_target" in
*systemd*) return 0 ;;
esac
fi
return 1
}
configure_legacy_init()
{
replace_existing=${1:-}
if uses_systemd; then
rm -f "$legacy_init"
return
fi
# A broken link cannot provide a usable init script. Replace it while
# preserving links whose administrator-managed target still exists.
if [ -L "$legacy_init" ] && [ ! -e "$legacy_init" ]; then
rm -f "$legacy_init"
fi
if [ -e "$legacy_init" ] || [ -L "$legacy_init" ]; then
if [ "$replace_existing" != "--replace" ]; then
return
fi
rm -f "$legacy_init"
fi
if [ ! -r "$legacy_template" ]; then
echo "Legacy xcatd init template not found: $legacy_template" >&2
exit 1
fi
mkdir -p "$compat_root/etc/init.d"
cp "$legacy_template" "$legacy_init"
chmod 755 "$legacy_init"
}
case "${1:-}" in
uses-systemd)
uses_systemd
;;
configure)
case "${2:-}" in
''|--replace) ;;
*)
echo "Usage: $0 {uses-systemd|configure [--replace]|remove}" >&2
exit 2
;;
esac
configure_legacy_init "${2:-}"
;;
remove)
rm -f "$legacy_init"
;;
*)
echo "Usage: $0 {uses-systemd|configure [--replace]|remove}" >&2
exit 2
;;
esac
+100 -25
View File
@@ -358,10 +358,26 @@ cp lib/xcat/shfunctions $RPM_BUILD_ROOT/%{prefix}/lib
chmod 644 $RPM_BUILD_ROOT/%{prefix}/lib/shfunctions
%if %fsm
%else
%ifos linux
cp etc/init.d/xcatd $RPM_BUILD_ROOT/%{prefix}/share/xcat/scripts/xcatd
chmod 755 $RPM_BUILD_ROOT/%{prefix}/share/xcat/scripts/xcatd
# Keep ownership continuity only on legacy RPM targets where the script is
# materialized at runtime. Modern RPM versions can create /etc/init.d from a
# ghost entry alone, which breaks later chkconfig/initscripts transactions.
%if 0%{?rhel} && 0%{?rhel} < 7
mkdir -p $RPM_BUILD_ROOT/etc/init.d
cp etc/init.d/xcatd $RPM_BUILD_ROOT/etc/init.d
touch $RPM_BUILD_ROOT/etc/init.d/xcatd
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1200
mkdir -p $RPM_BUILD_ROOT/etc/init.d
touch $RPM_BUILD_ROOT/etc/init.d/xcatd
%endif
mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/system
cp etc/init.d/xcatd.service $RPM_BUILD_ROOT/usr/lib/systemd/system
%else
mkdir -p $RPM_BUILD_ROOT/etc/init.d
cp etc/init.d/xcatd $RPM_BUILD_ROOT/etc/init.d
%endif
%endif
#TODO: the next has to me moved to postscript, to detect /etc/xcat vs /etc/opt/xcat
mkdir -p $RPM_BUILD_ROOT/etc/%httpconfigdir
@@ -428,8 +444,17 @@ rm -rf $RPM_BUILD_ROOT
/etc/%httpconfigdir
%if %fsm
%else
/etc/init.d/xcatd
%ifos linux
%if 0%{?rhel} && 0%{?rhel} < 7
%ghost %attr(0755,root,root) /etc/init.d/xcatd
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1200
%ghost %attr(0755,root,root) /etc/init.d/xcatd
%endif
/usr/lib/systemd/system/xcatd.service
%else
/etc/init.d/xcatd
%endif
/etc/apache2/conf.d/xcat-ws.conf
/etc/httpd/conf.d/xcat-ws.conf
%endif
@@ -491,31 +516,73 @@ xcat_can_use_systemctl()
[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
}
xcatd_init_compat=$RPM_INSTALL_PREFIX0/share/xcat/scripts/xcatd-init-compat
export XCATROOT="$RPM_INSTALL_PREFIX0"
if [ "$1" = "1" ]; then #Only if installing for the first time..
if xcat_can_use_systemctl; then
systemctl daemon-reload
systemctl enable xcatd.service
elif [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --add xcatd
elif [ -x /usr/lib/lsb/install_initd ]; then
/usr/lib/lsb/install_initd /etc/init.d/xcatd
if "$xcatd_init_compat" uses-systemd; then
"$xcatd_init_compat" configure || exit 1
if xcat_can_use_systemctl; then
systemctl daemon-reload
fi
if command -v systemctl >/dev/null 2>&1; then
systemctl enable xcatd.service
fi
else
echo "Unable to register init scripts on this system"
"$xcatd_init_compat" configure || exit 1
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --add xcatd
elif [ -x /usr/lib/lsb/install_initd ]; then
/usr/lib/lsb/install_initd /etc/init.d/xcatd
else
echo "Unable to register init scripts on this system"
fi
fi
fi
if [ "$1" -gt "1" ]; then #only on upgrade...
if xcat_can_use_systemctl; then
if [ -f /run/systemd/generator.late/xcatd.service ]; then
# To cover the case upgrade from no xcatd systemd unit file (cannot enable by default for HA case)
if ls /etc/rc.d/rc?.d/S??xcatd >/dev/null 2>&1; then
[ -x /sbin/chkconfig ] && /sbin/chkconfig --del xcatd
systemctl daemon-reload
systemctl enable xcatd.service
if "$xcatd_init_compat" uses-systemd; then
legacy_xcatd_enabled=0
for legacy_xcatd_link in \
/etc/rc.d/rc?.d/S??xcatd \
/etc/rc?.d/S??xcatd \
/etc/init.d/rc?.d/S??xcatd
do
if [ -e "$legacy_xcatd_link" ] || [ -L "$legacy_xcatd_link" ]; then
legacy_xcatd_enabled=1
break
fi
else
done
if [ -e /etc/init.d/xcatd ] || [ -L /etc/init.d/xcatd ]; then
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --del xcatd
elif [ -x /usr/lib/lsb/remove_initd ]; then
/usr/lib/lsb/remove_initd /etc/init.d/xcatd
fi
fi
rm -f \
/etc/rc.d/rc?.d/[SK]??xcatd \
/etc/rc?.d/[SK]??xcatd \
/etc/init.d/rc?.d/[SK]??xcatd
"$xcatd_init_compat" configure || exit 1
%if 0%{?suse_version}
%else
# Remove only an empty directory left by an older xCAT RPM. SUSE base
# packages own /etc/init.d, so leave that directory intact there.
rmdir /etc/init.d 2>/dev/null || true
%endif
if xcat_can_use_systemctl; then
systemctl daemon-reload
fi
if [ "$legacy_xcatd_enabled" = "1" ] && command -v systemctl >/dev/null 2>&1; then
# Preserve the previous enabled state without enabling HA nodes by default.
systemctl enable xcatd.service
fi
else
# This path was previously a regular RPM payload file, so upgrades replaced
# it with the current package version rather than retaining an older copy.
"$xcatd_init_compat" configure --replace || exit 1
fi
#migration issue for monitoring
XCATROOT=$RPM_INSTALL_PREFIX0 $RPM_INSTALL_PREFIX0/sbin/chtab filename=monitorctrl.pm notification -d
@@ -557,22 +624,30 @@ xcat_can_use_systemctl()
[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
}
xcatd_init_compat=$RPM_INSTALL_PREFIX0/share/xcat/scripts/xcatd-init-compat
export XCATROOT="$RPM_INSTALL_PREFIX0"
if [ $1 == 0 ]; then #This means only on -e
if [ -f "/proc/cmdline" ]; then # prevent running it during install into chroot image
if xcat_can_use_systemctl; then
systemctl stop xcatd.service
elif [ -x /etc/init.d/xcatd ]; then
elif ! "$xcatd_init_compat" uses-systemd && [ -x /etc/init.d/xcatd ]; then
/etc/init.d/xcatd stop
fi
fi
if xcat_can_use_systemctl; then
systemctl disable xcatd.service
elif [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --del xcatd
elif [ -x /usr/lib/lsb/remove_initd ]; then
/usr/lib/lsb/remove_initd /etc/init.d/xcatd
if "$xcatd_init_compat" uses-systemd; then
if command -v systemctl >/dev/null 2>&1; then
systemctl disable xcatd.service
fi
else
if [ -x /sbin/chkconfig ]; then
/sbin/chkconfig --del xcatd
elif [ -x /usr/lib/lsb/remove_initd ]; then
/usr/lib/lsb/remove_initd /etc/init.d/xcatd
fi
fi
"$xcatd_init_compat" remove
rm -f /usr/sbin/xcatd #remove the symbolic
fi
@@ -0,0 +1,197 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use Test::More;
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
my $helper = File::Spec->catfile(
$repo_root, 'xCAT-server', 'share', 'xcat', 'scripts', 'xcatd-init-compat'
);
my $template = File::Spec->catfile(
$repo_root, 'xCAT-server', 'etc', 'init.d', 'xcatd'
);
sub read_file {
my ($path) = @_;
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
my $contents = do { local $/; <$fh> };
close($fh);
return $contents;
}
sub stage_root {
my $root = tempdir( CLEANUP => 1 );
my $scripts = File::Spec->catdir(
$root, 'opt', 'xcat', 'share', 'xcat', 'scripts'
);
make_path($scripts);
copy( $template, File::Spec->catfile( $scripts, 'xcatd' ) )
or die "Unable to stage legacy init template: $!";
return $root;
}
sub run_helper {
my ( $root, @arguments ) = @_;
local $ENV{XCAT_COMPAT_ROOT} = $root;
local $ENV{XCATROOT} = '/opt/xcat';
system( '/bin/sh', $helper, @arguments );
return $? >> 8;
}
sub legacy_init {
my ($root) = @_;
return File::Spec->catfile( $root, 'etc', 'init.d', 'xcatd' );
}
my $active_systemd_root = stage_root();
make_path( File::Spec->catdir( $active_systemd_root, 'run', 'systemd', 'system' ) );
make_path( File::Spec->catdir( $active_systemd_root, 'etc', 'init.d' ) );
open( my $old_init, '>', legacy_init($active_systemd_root) )
or die "Unable to stage old init script: $!";
print {$old_init} "old init script\n";
close($old_init);
is( run_helper( $active_systemd_root, 'uses-systemd' ), 0,
'an active systemd marker identifies a systemd target' );
is( run_helper( $active_systemd_root, 'configure' ), 0,
'systemd configuration succeeds' );
ok( !-e legacy_init($active_systemd_root),
'systemd configuration removes an upgraded legacy init script' );
my $systemd_image_root = stage_root();
make_path( File::Spec->catdir( $systemd_image_root, 'sbin' ) );
symlink( '../lib/systemd/systemd', File::Spec->catfile( $systemd_image_root, 'sbin', 'init' ) )
or die "Unable to stage systemd init symlink: $!";
is( run_helper( $systemd_image_root, 'uses-systemd' ), 0,
'a target-root systemd init symlink identifies a systemd image' );
is( run_helper( $systemd_image_root, 'configure' ), 0,
'systemd image configuration succeeds without a running manager' );
ok( !-e legacy_init($systemd_image_root),
'systemd images do not receive the legacy init script' );
my $minimal_systemd_root = stage_root();
my $systemd_binary = File::Spec->catfile(
$minimal_systemd_root, 'usr', 'lib', 'systemd', 'systemd'
);
make_path( File::Spec->catdir( $minimal_systemd_root, 'usr', 'lib', 'systemd' ) );
open( my $minimal_systemd, '>', $systemd_binary )
or die "Unable to stage minimal systemd binary: $!";
close($minimal_systemd);
chmod 0755, $systemd_binary;
is( run_helper( $minimal_systemd_root, 'uses-systemd' ), 0,
'an installed systemd binary identifies a stopped or minimal systemd target' );
is( run_helper( $minimal_systemd_root, 'configure' ), 0,
'minimal systemd target configuration succeeds' );
ok( !-e legacy_init($minimal_systemd_root),
'minimal systemd targets do not receive the legacy init script' );
my $modern_os_root = stage_root();
make_path( File::Spec->catdir( $modern_os_root, 'etc' ) );
open( my $modern_os_release, '>', File::Spec->catfile( $modern_os_root, 'etc', 'os-release' ) )
or die "Unable to stage modern os-release: $!";
print {$modern_os_release} qq{ID="rocky"\nVERSION_ID="9.6"\n};
close($modern_os_release);
is( run_helper( $modern_os_root, 'uses-systemd' ), 0,
'a modern supported distro is systemd even in a stripped-down environment' );
is( run_helper( $modern_os_root, 'configure' ), 0,
'stripped-down modern distro configuration succeeds' );
ok( !-e legacy_init($modern_os_root),
'stripped-down modern distros do not receive the legacy init script' );
my $legacy_os_root = stage_root();
make_path( File::Spec->catdir( $legacy_os_root, 'etc' ) );
open( my $legacy_os_release, '>', File::Spec->catfile( $legacy_os_root, 'etc', 'os-release' ) )
or die "Unable to stage legacy os-release: $!";
print {$legacy_os_release} qq{ID="sles"\nVERSION_ID="11.4"\n};
close($legacy_os_release);
isnt( run_helper( $legacy_os_root, 'uses-systemd' ), 0,
'SLES 11 remains classified as a legacy init target' );
my $debian_sid_root = stage_root();
make_path( File::Spec->catdir( $debian_sid_root, 'etc' ) );
open( my $debian_sid_release, '>', File::Spec->catfile( $debian_sid_root, 'etc', 'os-release' ) )
or die "Unable to stage Debian sid os-release: $!";
print {$debian_sid_release} qq{ID=debian\nVERSION_CODENAME=sid\n};
close($debian_sid_release);
is( run_helper( $debian_sid_root, 'uses-systemd' ), 0,
'Debian testing and sid remain systemd targets without VERSION_ID' );
my $legacy_root = stage_root();
make_path( File::Spec->catdir( $legacy_root, 'sbin' ) );
symlink( 'upstart', File::Spec->catfile( $legacy_root, 'sbin', 'init' ) )
or die "Unable to stage upstart init symlink: $!";
isnt( run_helper( $legacy_root, 'uses-systemd' ), 0,
'an upstart target is not classified as systemd' );
is( run_helper( $legacy_root, 'configure' ), 0,
'legacy configuration succeeds' );
ok( -x legacy_init($legacy_root),
'legacy configuration materializes an executable init script' );
is( read_file( legacy_init($legacy_root) ), read_file($template),
'the materialized legacy init script matches the packaged template' );
my $dangling_legacy_root = stage_root();
make_path( File::Spec->catdir( $dangling_legacy_root, 'etc', 'init.d' ) );
symlink( '/missing/xcatd', legacy_init($dangling_legacy_root) )
or die "Unable to stage dangling init symlink: $!";
is( run_helper( $dangling_legacy_root, 'configure' ), 0,
'legacy configuration replaces a dangling init symlink' );
ok( !-l legacy_init($dangling_legacy_root),
'the replacement for a dangling init symlink is a regular file' );
is( read_file( legacy_init($dangling_legacy_root) ), read_file($template),
'the dangling init symlink replacement matches the packaged template' );
open( my $custom_init, '>', legacy_init($legacy_root) )
or die "Unable to customize staged init script: $!";
print {$custom_init} "administrator customization\n";
close($custom_init);
is( run_helper( $legacy_root, 'configure' ), 0,
'reconfiguring a legacy target succeeds' );
is( read_file( legacy_init($legacy_root) ), "administrator customization\n",
'legacy reconfiguration preserves an existing init script' );
is( run_helper( $legacy_root, 'configure', '--replace' ), 0,
'RPM-style legacy upgrade configuration succeeds' );
is( read_file( legacy_init($legacy_root) ), read_file($template),
'RPM-style legacy upgrades refresh the init script from the packaged template' );
is( run_helper( $legacy_root, 'remove' ), 0,
'legacy cleanup succeeds' );
ok( !-e legacy_init($legacy_root),
'legacy cleanup removes the generated init script' );
my $rpm_spec = read_file(
File::Spec->catfile( $repo_root, 'xCAT-server', 'xCAT-server.spec' )
);
like( $rpm_spec,
qr{cp etc/init\.d/xcatd \$RPM_BUILD_ROOT/%\{prefix\}/share/xcat/scripts/xcatd},
'RPM stages the legacy script as a compatibility template' );
like( $rpm_spec,
qr{%if 0%\{\?rhel\} && 0%\{\?rhel\} < 7\n%ghost %attr\(0755,root,root\) /etc/init\.d/xcatd},
'RPM tracks the runtime-created init path only on legacy RHEL' );
like( $rpm_spec,
qr{%if 0%\{\?suse_version\} && 0%\{\?suse_version\} < 1200\n%ghost %attr\(0755,root,root\) /etc/init\.d/xcatd},
'RPM tracks the runtime-created init path only on legacy SUSE' );
like( $rpm_spec, qr{xcatd-init-compat.*uses-systemd}s,
'RPM scriptlets select the init implementation at install time' );
like( $rpm_spec,
qr{xcatd_init_compat=.*?"\$xcatd_init_compat" configure --replace}s,
'RPM upgrades refresh an existing SysV init script' );
my @xcatroot_exports =
( $rpm_spec =~ /^export XCATROOT="\$RPM_INSTALL_PREFIX0"$/mg );
is( scalar @xcatroot_exports, 2,
'RPM install and erase scriptlets forward a relocated package root' );
my @empty_init_cleanup =
( $rpm_spec =~ /^\s*rmdir \/etc\/init\.d 2>\/dev\/null \|\| true$/mg );
is( scalar @empty_init_cleanup, 1,
'only RPM systemd upgrades remove an empty legacy init directory' );
like( $rpm_spec,
qr{%if 0%\{\?suse_version\}\s+%else\s+# Remove only an empty directory.*?rmdir /etc/init\.d 2>/dev/null \|\| true\s+%endif}s,
'RPM upgrades preserve the SUSE-owned init directory' );
like( $rpm_spec,
qr{if \[ -e "\$legacy_xcatd_link" \] \|\| \[ -L "\$legacy_xcatd_link" \]},
'RPM upgrade recognizes enabled state through dangling legacy links' );
done_testing();