Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/install-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
- debian-13
- centos-stream-9
- centos-stream-10
- opensuse-leap-16
- sles-16

steps:
- name: Checkout repository
Expand Down Expand Up @@ -129,10 +131,12 @@ jobs:
docker exec "$CONTAINER" journalctl -n 50 2>/dev/null || true
echo "=== Apache error log ===" && \
docker exec "$CONTAINER" cat /var/log/apache2/error.log 2>/dev/null || \
docker exec "$CONTAINER" cat /var/log/httpd/error_log 2>/dev/null || true
docker exec "$CONTAINER" cat /var/log/httpd/error_log 2>/dev/null || \
docker exec "$CONTAINER" cat /var/log/apache2/error_log 2>/dev/null || true
echo "=== MySQL error log (last 30 lines) ===" && \
docker exec "$CONTAINER" tail -30 /var/log/mysql/error.log 2>/dev/null || \
docker exec "$CONTAINER" tail -30 /var/log/mysqld.log 2>/dev/null || true
docker exec "$CONTAINER" tail -30 /var/log/mysqld.log 2>/dev/null || \
docker exec "$CONTAINER" tail -30 /var/log/mysql/mysqld.log 2>/dev/null || true
echo "=== /root/passwords.txt ===" && \
docker exec "$CONTAINER" cat /root/passwords.txt 2>/dev/null || true

Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,37 @@
*is* covered by CI below, but RHEL itself isn't independently tested: Red Hat's official container images require a
paid subscription, and the free UBI images can't substitute because `firewalld` and `sendmail`, both of which the
script installs, aren't published to any repo UBI can reach without one.
- openSUSE Leap 16.0
- Leap 16.0 ships PHP 8.4 natively; a real MySQL Community Server RPM (built for SLES 15) installs and runs on it
without issue - see `setup_suse`/`uninstall_suse` in `simplerisk-setup.sh`.
- SUSE Linux Enterprise Server (SLES) 16.0
- SLES 16.0 and openSUSE Leap 16.0 share the exact same package builds (`setup_suse`/`uninstall_suse` is one code path
for both), and unlike RHEL, SLES 16 *is* independently tested: SUSE's free, unauthenticated BCI (Base Container
Image) - `registry.suse.com/bci/bci-base:16.0` - carries its own public `SLE_BCI` repo with no SCC subscription
needed, so CI runs the real installer against real SLES 16, not just its openSUSE proxy.

SUSE Linux Enterprise Server (SLES) is not currently supported: SimpleRisk requires PHP >= 8.3, and SLES 15's own
repositories only offer PHP 8.2 with no upgrade path currently available. Support may return once a SLES release with a
newer PHP is available.
## Explicitly unsupported versions

A few versions are excluded on purpose, not simply because they haven't been tried yet:

- **openSUSE Tumbleweed** - a rolling release, not a stable/LTS-equivalent target. Even though it always carries a
current PHP, this script intentionally only targets openSUSE's stable branch (Leap).
- **openSUSE Leap 15.x and earlier** - capped at PHP 8.2 (the same `php8` package SLES 15 ships) with no upgrade path,
and Leap 15.6, the last 15.x release, is now end-of-life.
- **SUSE Linux Enterprise Server (SLES) 15** (all service packs) - not supported, and won't be added without a way to
verify it. SLES 15 SP7's release notes list PHP 8.3.x as available, which would clear SimpleRisk's PHP >= 8.3
requirement, but that can't currently be confirmed: openSUSE Leap 15.x, which would otherwise serve as SLES 15's
free/testable proxy the way Leap 16.0 does for SLES 16.0, ended at 15.6 (see above) and never exceeded PHP 8.2, and
real SLES 15 container images require a paid SCC subscription to test directly.
- **Ubuntu releases older than 22.04** (18.04, 20.04, etc.), and interim (non-LTS) releases - see the LTS-only policy
above.
- **Debian releases older than 13** (11, 12, etc.) - out of scope; only the version listed above is targeted.
- **CentOS Stream / RHEL releases older than 9** (8, 7, etc.) - out of scope; CentOS 8 reached end-of-life in December
2021, and older major versions aren't targeted regardless of a given release's own support status.
- **RHEL-compatible rebuilds** (Rocky Linux, AlmaLinux, Oracle Linux, etc.) - not tested, even though they're binary-
compatible with RHEL: `/etc/os-release`'s `NAME` differs from `Red Hat Enterprise Linux`/`Red Hat Enterprise Linux
Server`, so `validate_os_and_version()` doesn't recognize them and the script exits rather than assuming
compatibility.

## Instructions

Expand Down
82 changes: 50 additions & 32 deletions simplerisk-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ readonly CENTOS_STREAM_OSVAR='CentOS Stream'
readonly RHEL_OSVAR='Red Hat Enterprise Linux'
readonly RHELS_OSVAR='Red Hat Enterprise Linux Server'
readonly SLES_OSVAR='SLES'
readonly SLES_15_SUPPORTED_SP="15"
readonly OPENSUSE_LEAP_OSVAR='openSUSE Leap'

readonly MYSQL_KEY_URL='https://repo.mysql.com/RPM-GPG-KEY-mysql-2025'
readonly MYSQL_GPG_KEY='B7B3B788A8D3785C' # Key taken from https://dev.mysql.com/doc/refman/8.4/en/checking-gpg-signature.html
Expand Down Expand Up @@ -132,6 +132,7 @@ load_os_variables(){

validate_os_and_version(){
local valid
local untested_note
case "${OS}" in
"${UBUNTU_OSVAR}")
# LTS releases only - interim (non-LTS) releases like 25.04/25.10
Expand All @@ -155,30 +156,50 @@ validate_os_and_version(){
if [[ "${VER}" = 9* ]] || [[ "${VER}" = 10* ]]; then
valid=y
SETUP_TYPE=rhel
# RHEL shares the exact same install code path as CentOS
# Stream (which CI does cover), but RHEL itself has never
# actually been run in CI: Red Hat's official container
# images require a paid subscription, and the free UBI
# images can't substitute (see README). Let the user know
# this specific OS is unverified, not just "supported".
untested_note="RHEL is not independently tested in CI (it shares its install code with CentOS Stream, which is tested) - see the README for why."
fi;;
"${SLES_OSVAR}")
# SLES 15 (all service packs) is not supported: SimpleRisk's
# current release requires PHP >= 8.3 (Composer platform check),
# and SLES 15's own repositories cap out at PHP 8.2 (the php8
# package) with no upgrade path. openSUSE's community
# devel:languages:php OBS project, which sometimes backports a
# newer PHP to older releases, has dropped 15.6 support entirely
# and only targets the next major release (16.0), which is not
# yet generally available for SLES. openSUSE Leap 16.0 already
# ships PHP 8.4 natively, so SLES 16 (once released) should be a
# viable target - but setup_suse()/uninstall_suse() below are
# written entirely around SLES 15's package names, module
# structure, and MySQL repo RPM naming
# (mysql84-community-release-sl15), so adding SLES 16 support
# needs its own dedicated pass, not just changing this version
# check.
print_error_message "SLES/openSUSE is not currently supported: SimpleRisk requires PHP >= 8.3, and SLES 15's repositories only offer PHP 8.2 with no upgrade path currently available.";;
# SLES 15 (all service packs, including SP7's PHP 8.3) is not
# supported here: even though SP7's release notes list PHP 8.3.x,
# there's no way to verify that install path - openSUSE Leap
# 15.x (which would normally serve as SLES 15's free/testable
# proxy) ended at 15.6, which is now EOL and still capped at PHP
# 8.2, and real SLES 15 containers require a paid SCC
# subscription. See the README's SLES caveat for the same
# reasoning in more detail.
#
# SLES 16.0 (see below) ships PHP 8.4 and is supported and
# CI-tested directly.
if [[ "${VER}" = 16.* ]]; then
valid=y
SETUP_TYPE=suse
fi;;
"${OPENSUSE_LEAP_OSVAR}")
# Leap 16.0 ships PHP 8.4 (php8 package) and a real MySQL
# Community Server RPM (built for SLES 15) installs and runs on
# it cleanly - see setup_suse()/uninstall_suse() below. Point
# releases within 16.x are expected to stay compatible, mirroring
# how Ubuntu's 24.*/26.* are accepted above. Older Leap releases
# (15.x and earlier) cap out at PHP 8.2, same as SLES 15.
if [[ "${VER}" = 16.* ]]; then
valid=y
SETUP_TYPE=suse
fi;;
*)
local unknown=y;;
esac

if [ -n "${valid:-}" ]; then
echo "Detected OS is ${OS} ${VER}, which is supported by this script."
if [ -n "${untested_note:-}" ]; then
echo "NOTE: ${untested_note}"
fi
elif [ -z "${valid:-}" ] && [ ! -v unknown ]; then
echo "Detected OS is ${OS} ${VER}, but this version is not currently supported by this script."
exit 1
Expand Down Expand Up @@ -857,21 +878,18 @@ setup_suse(){
# "The Tokenizer extension is required for the routing attribute loader"),
# php8-ctype, php8-xmlreader, php8-xmlwriter. Note 'php-xml' does not exist
# on SUSE, and SimpleRisk's healthcheck only flags dom, so the others were
# silently missing.
run_cmd zypper -n install php8 php8-mysql apache2-mod_php8 php8-ldap php8-curl php8-zlib php8-phar php8-mbstring php8-intl php8-posix php8-gd php8-zip php8-dom php8-openssl php8-tokenizer php8-ctype php8-xmlreader php8-xmlwriter

if [[ "${VER}" = "${SLES_15_SUPPORTED_SP}"* ]]; then
print_status 'Enabling PHP and Apache modules...'
for module in php8 rewrite ssl mod_ssl; do
run_cmd a2enmod "$module"
done
fi

print_status 'Enabling Rewrite Module for Apache...'
if [[ "${VER}" = "${SLES_15_SUPPORTED_SP}"* ]]; then
grep -qF 'mod_rewrite.so' /etc/apache2/loadmodule.conf 2>/dev/null || \
echo 'LoadModule rewrite_module /usr/lib64/apache2-prefork/mod_rewrite.so' >> /etc/apache2/loadmodule.conf
fi
# silently missing. php8-cli is also separate here (unlike Debian/RHEL,
# where the base PHP package includes the CLI binary) and is needed for
# the backup cron job, which invokes `php` directly.
run_cmd zypper -n install php8 php8-cli php8-mysql apache2-mod_php8 php8-ldap php8-curl php8-zlib php8-phar php8-mbstring php8-intl php8-posix php8-gd php8-zip php8-dom php8-openssl php8-tokenizer php8-ctype php8-xmlreader php8-xmlwriter

print_status 'Enabling PHP and Apache modules...'
# a2enmod is idempotent and self-appends the needed LoadModule line, so
# this runs unconditionally rather than only on modules that aren't
# already enabled by default (e.g. php8/ssl on Leap 16.0).
for module in php8 rewrite ssl mod_ssl; do
run_cmd a2enmod "$module"
done

print_status 'Setting up SimpleRisk Virtual Host and SSL Self-Signed Cert'
grep -qxF 'Listen 443' /etc/apache2/vhosts.d/simplerisk.conf 2>/dev/null || echo 'Listen 443' >> /etc/apache2/vhosts.d/simplerisk.conf
Expand Down
33 changes: 33 additions & 0 deletions tests/dockerfiles/Dockerfile.opensuse-leap-16
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM opensuse/leap:16.0

ENV container=docker

# wget and which are pre-installed on a real openSUSE Leap server but absent
# from the minimal container image; simplerisk-setup.sh relies on both
# (wget to fetch the SimpleRisk bundle/database, which to locate the php
# binary for the backup cron entry) without installing them itself, since a
# real server is assumed to already have them.
RUN zypper --non-interactive install wget which && \
zypper clean --all

# MySQL uses native AIO by default, which fails on Docker's overlayfs driver.
RUN mkdir -p /etc/my.cnf.d && \
printf '[mysqld]\ninnodb_use_native_aio=0\n' > /etc/my.cnf.d/docker.cnf

# systemd cannot start in Docker Desktop for Windows (cgroup v2 unavailable).
# This shim replaces /usr/bin/systemctl so the setup script's
# `systemctl start/stop/restart/enable/disable` calls work by managing
# processes directly instead.
# The script is kept in a separate file to avoid heredoc parsing issues across
# different Docker builder versions.
COPY systemctl-shim-suse.sh /usr/local/bin/systemctl
RUN chmod +x /usr/local/bin/systemctl

# firewall-cmd needs a no-op shim: firewalld cannot run in Docker Desktop (no
# nftables/iptables backend). Placing it in /usr/local/bin/ ensures it takes
# precedence over the real binary in /usr/sbin/ when the setup script's PATH
# is evaluated.
RUN printf '#!/bin/bash\nexit 0\n' > /usr/local/bin/firewall-cmd && \
chmod +x /usr/local/bin/firewall-cmd

CMD ["/bin/bash"]
35 changes: 35 additions & 0 deletions tests/dockerfiles/Dockerfile.sles-16
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM registry.suse.com/bci/bci-base:16.0

ENV container=docker

# SUSE's BCI (Base Container Image) ships with its own free, unauthenticated
# `SLE_BCI` repo baked in - no SCC subscription/registration needed, unlike
# SLES 15-era images. It carries the exact same package builds as openSUSE
# Leap 16.0 (identical `160000.x.x` release strings for php8/apache2/etc.),
# just from a more minimal base: gawk, wget, and which aren't preinstalled
# here the way they are on Leap 16.0's base image, but would be on a real
# (non-container) SLES 16 server, so they're added here to match. gawk in
# particular is required by simplerisk-setup.sh's own use of `awk`.
RUN zypper --non-interactive install wget which gawk && \
zypper clean --all

# MySQL uses native AIO by default, which fails on Docker's overlayfs driver.
RUN mkdir -p /etc/my.cnf.d && \
printf '[mysqld]\ninnodb_use_native_aio=0\n' > /etc/my.cnf.d/docker.cnf

# systemd cannot start in Docker Desktop for Windows (cgroup v2 unavailable).
# This shim replaces /usr/bin/systemctl so the setup script's
# `systemctl start/stop/restart/enable/disable` calls work by managing
# processes directly instead. Shared with the openSUSE Leap 16.0 image since
# setup_suse()/uninstall_suse() is the same code path for both.
COPY systemctl-shim-suse.sh /usr/local/bin/systemctl
RUN chmod +x /usr/local/bin/systemctl

# firewall-cmd needs a no-op shim: firewalld cannot run in Docker Desktop (no
# nftables/iptables backend). Placing it in /usr/local/bin/ ensures it takes
# precedence over the real binary in /usr/sbin/ when the setup script's PATH
# is evaluated.
RUN printf '#!/bin/bash\nexit 0\n' > /usr/local/bin/firewall-cmd && \
chmod +x /usr/local/bin/firewall-cmd

CMD ["/bin/bash"]
124 changes: 124 additions & 0 deletions tests/dockerfiles/systemctl-shim-suse.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/bin/bash
# Minimal systemctl shim — handles the subset used by simplerisk-setup.sh
# on openSUSE Leap without requiring a running systemd PID 1.

now_flag=
args=()
for arg in "$@"; do
if [[ "$arg" == "--now" ]]; then
now_flag=1
continue
fi
[[ "$arg" == --* ]] && continue
args+=("$arg")
done
action="${args[0]:-}"
unit="${args[1]%.service}"

start_mysqld() {
mysqladmin ping --silent >/dev/null 2>&1 && return 0 # already running
mkdir -p /var/log/mysql && chown mysql:mysql /var/log/mysql
# Pre-create the log file with mysql ownership so mysqld (which drops to
# the mysql user before opening it) can write to it - a bare shell
# redirect below would otherwise create it root-owned and unwritable.
touch /var/log/mysql/mysqld.log && chown mysql:mysql /var/log/mysql/mysqld.log
# mysqld_pre_systemd is the same helper the real mysql.service unit runs
# as ExecStartPre; it's idempotent (skips init if the datadir is already
# populated) and internally runs `mysqld --initialize`, which is meant to
# write the temp root password as a [Note] line to /var/log/mysql/mysqld.log
# (per log-error in /etc/my.cnf) for simplerisk-setup.sh to read - but on
# some hosts (observed on GitHub Actions runners, not reproducible in local
# Docker Desktop testing) that Note only reaches mysqld_pre_systemd's own
# stdout/stderr, never the file. Redirect explicitly into the log file so
# the Note lands there regardless, matching the CentOS shim's same
# defensive redirect around its own `mysqld --initialize` call.
/usr/bin/mysqld_pre_systemd >>/var/log/mysql/mysqld.log 2>&1
nohup /usr/sbin/mysqld --user=mysql >>/var/log/mysql/mysqld.log 2>&1 &
local i=0
while [ $i -lt 60 ]; do
mysqladmin ping --silent >/dev/null 2>&1 && return 0
sleep 1; i=$((i+1))
done
echo "systemctl shim: mysqld did not start within 60s" >&2; return 1
}

stop_mysqld() {
# Send SIGTERM directly to the mysqld process so that no root password is
# needed (mysqladmin shutdown requires auth after setup changes the password).
local pidfile=/var/run/mysql/mysqld.pid
if [ -f "$pidfile" ]; then
local pid
pid=$(cat "$pidfile" 2>/dev/null)
[ -n "$pid" ] && kill -TERM "$pid" 2>/dev/null || true
else
pkill -TERM mysqld 2>/dev/null || true
fi
local i=0
while mysqladmin ping --silent >/dev/null 2>&1 && [ $i -lt 30 ]; do
sleep 1; i=$((i+1))
done
}

start_cron() {
pgrep -x cron >/dev/null 2>&1 && return 0
nohup /usr/sbin/cron -n >/dev/null 2>&1 &
}

case "$action" in
start)
case "$unit" in
mysql|mysqld) start_mysqld ;;
apache2) start_apache2 -k start ;;
cron) start_cron ;;
firewalld) exit 0 ;; # no-op: firewalld not available in Docker
*) echo "systemctl shim: unsupported unit '$unit'" >&2; exit 1 ;;
esac ;;
stop)
case "$unit" in
mysql|mysqld) stop_mysqld ;;
apache2) start_apache2 -k stop ;;
*) exit 0 ;; # non-fatal for unknown units on uninstall
esac ;;
restart)
case "$unit" in
mysql|mysqld) stop_mysqld; sleep 1; start_mysqld ;;
# `systemctl restart` maps to a real stop+start (ExecStop then
# ExecStart), not the `-k graceful` rolling reload systemd uses
# for `systemctl reload` - a graceful reload here reliably
# segfaults newly-forked children (observed repeatedly in
# testing), while a full stop/start does not.
apache2) start_apache2 -k stop; sleep 1; start_apache2 -k start ;;
*) echo "systemctl shim: unsupported unit '$unit'" >&2; exit 1 ;;
esac ;;
is-active)
case "$unit" in
mysql|mysqld) mysqladmin ping --silent >/dev/null 2>&1 ;;
apache2) pgrep -x httpd-prefork >/dev/null 2>&1 ;;
cron) pgrep -x cron >/dev/null 2>&1 ;;
*) exit 1 ;;
esac ;;
status)
case "$unit" in
mysql|mysqld) mysqladmin ping --silent >/dev/null 2>&1 && echo "active" || exit 3 ;;
apache2) pgrep -x httpd-prefork >/dev/null 2>&1 && echo "active" || exit 3 ;;
cron) pgrep -x cron >/dev/null 2>&1 && echo "active" || exit 3 ;;
*) exit 3 ;;
esac ;;
enable)
# We don't manage boot-time units, but `enable --now` also means
# "start it now" on a real system - honor the --now part.
if [ -n "$now_flag" ]; then
case "$unit" in
mysql|mysqld) start_mysqld ;;
apache2) start_apache2 -k start ;;
cron) start_cron ;;
*) exit 0 ;;
esac
else
exit 0
fi ;;
disable|daemon-reload|mask|unmask|is-enabled|reset-failed)
exit 0 ;; # no-op — we don't manage boot-time units
*)
echo "systemctl shim: unknown action '$action'" >&2; exit 1 ;;
esac
2 changes: 1 addition & 1 deletion tests/verify-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ check "PHP CLI is functional" php -r "echo 'OK';"
check "PHP version is 8.x" bash -c "php --version | grep -qE '^PHP 8\.'"

for ext in mysqli mbstring xml curl gd zip intl ldap; do
check "PHP extension '$ext' is loaded" php -m | grep -qi "$ext"
check "PHP extension '$ext' is loaded" bash -c "php -m | grep -qi '$ext'"
done

# ── Web server (OS-conditional) ───────────────────────────────────────────────
Expand Down
Loading