Skip to content

Add uninstall support + test harness; fix install (config template) and SLES MySQL/AppArmor#38

Closed
DASimp wants to merge 12 commits into
mainfrom
feature/uninstall-support
Closed

Add uninstall support + test harness; fix install (config template) and SLES MySQL/AppArmor#38
DASimp wants to merge 12 commits into
mainfrom
feature/uninstall-support

Conversation

@DASimp
Copy link
Copy Markdown
Contributor

@DASimp DASimp commented Jun 4, 2026

Summary

Adds full uninstall support and a Docker-based local/CI test harness, fixes the install flow that upstream changes had broken across all OSes, and resolves the SLES MySQL failure plus initial government/STIG hardening for SLES.

All six Docker-testable OSes pass install and uninstall locally via tests/run-tests.sh (install verify 24/0; uninstall verify 10–13/0).

Install correctness (all OSes)

  • config.php template format — upstream bundles now ship includes/config.sample.php (a __PLACEHOLDER__ template) instead of config.php, and use the file's existence as the install marker (no more SIMPLERISK_INSTALLED). set_up_simplerisk now creates config.php from the sample and set_up_database substitutes the tokens (DB_HOSTNAME/PORT/USERNAME/PASSWORD/DATABASE, USE_DATABASE_FOR_SESSIONS). This was breaking every install.
  • tests/verify-install.sh updated to the new config model.

SLES (the client's blocker)

  • MySQL install fixed: SLES ships MariaDB, which "provides" the mysql namespace and conflicts with mysql-community-server; under zypper -n the install aborted. Now installs with --allow-vendor-change --force-resolution to apply the "replace MariaDB with MySQL" resolution.
  • Pinned to 8.4 LTS: the release RPM enables the newest LTS repo (9.x) and leaves 8.4 disabled — we now enable the 8.4 repos, disable the 9.x ones, and bound the install to mysql-community-server<9 (validated: installs 8.4.9, not 9.7).
  • GPG: re-import the package-signing key so installs don't warn/fail with NOKEY.
  • firewalld is configured only when present (guarded), so it can't crash on minimal images.
  • cron is installed before the backup cronjob is set up.
  • AppArmor: detect and disable an enforced Apache profile (hardened/STIG baselines add one) so SimpleRisk runs unconfined; re-enabled on uninstall. No-op on stock SLES.

Security / robustness

  • exec_cmd_sensitive: MySQL passwords are no longer echoed into --debug output / CI logs.
  • Clear error if the SimpleRisk version lookup returns empty.
  • curl | bash installs default to headless; uninstall still requires explicit --yes.

Test plan

  • Local tests/run-tests.sh green for all 6 OSes (ubuntu 22.04/24.04, debian 12/13, centos-stream 9/10), install + uninstall.
  • SLES MySQL 8.4 conflict/GPG fix validated on openSUSE Leap 15.6 (SLES 15 SP6 equivalent).
  • Full end-to-end run on a registered SLES box (services/app load, FIPS) — not possible without a subscription.

Still open (not in this PR)

SLES gov-hardening follow-ups: exact-15.7 version gate, FIPS cert -sha256/SAN, --validate-os-only side-effect safety, non-root backup cron.

🤖 Generated with Claude Code

DASimp and others added 12 commits May 8, 2026 12:21
## Test infrastructure (new)

- tests/run-tests.sh: orchestrates build → install → verify → uninstall → verify
  across all 6 supported OSes; runs each OS sequentially with full logging to
  tests/logs/<os-slug>/
- tests/verify-install.sh: 24-point post-install checklist (services running,
  files present, DB accessible, SimpleRisk web UI reachable, cron job installed,
  SSL cert present, PHP settings applied)
- tests/verify-uninstall.sh: 12-point post-uninstall checklist (packages removed,
  files/dirs gone, DB dropped, cron job removed)
- tests/dockerfiles/Dockerfile.ubuntu-{22.04,24.04}: pre-install lamp-server^ with
  policy-rc.d deny so services are installed but not started; start them before
  running the setup script to match a real server state
- tests/dockerfiles/Dockerfile.debian-{12,13}: /etc/init.d/mysql shim that manages
  mysqld directly (no systemd); /usr/sbin/policy-rc.d deny gate during image build
- tests/dockerfiles/Dockerfile.centos-stream-{9,10}: /usr/local/bin/systemctl shim
  (takes PATH precedence over real systemctl installed by MySQL's systemd RPM dep)
  that manages mysqld and httpd directly; no-op shims for firewall-cmd, setsebool,
  chcon; innodb_use_native_aio=0 for Docker overlayfs compatibility
- .github/workflows/install-test.yml: CI matrix over all 6 OSes on push/PR to main
- CLAUDE.md: project documentation for Claude Code

## Bug fixes in simplerisk-setup.sh

- Add --uninstall flag: routes to per-OS uninstall functions with dedicated
  ask_user_uninstall() prompt and uninstall_final_message(); previously the flag
  was accepted but silently fell through to installation
- CentOS/RHEL: add php-cli to explicit dnf install list — on el10 it is a
  Recommended (weak) dep of php and was being silently omitted
- CentOS/RHEL: add --exclude 'mysql8.4*' to mysql-community-server install —
  CentOS Stream 10's AppStream ships mysql8.4-server which conflicts on
  /usr/sbin/mysqld; the exclude is a no-op on el9/el8

All 6 OS tests (ubuntu-22.04, ubuntu-24.04, debian-12, debian-13,
centos-stream-9, centos-stream-10) pass the full 7-step suite locally on
Docker Desktop for Windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fix CentOS start

Three CI failures fixed:

1. Dockerfile heredoc parse error (Debian-12, Debian-13, CentOS-9, CentOS-10)
   Docker's standard Dockerfile parser treats `RUN cat > /file << 'EOF'` as a
   single-line RUN — subsequent lines are parsed as new Dockerfile instructions,
   causing "unknown instruction: PIDFILE=..." errors on the Linux CI runner.
   Fix: extract the mysql init script and systemctl shim to separate files
   (mysql-init-debian.sh, systemctl-shim-centos.sh) and use COPY + chmod.
   Also add eol=lf to .gitattributes for *.sh, Dockerfile*, and *.yml so CRLF
   conversion on Windows never affects files parsed on Linux.

2. Ubuntu Apache restart timeout during apt post-install triggers
   The libapache2-mod-php post-install script fires `service apache2 restart`
   multiple times in quick succession.  Without --init (tini as PID 1), child
   processes are not properly reaped and a graceful restart can hang for the
   full 60s timeout.  Fix: add --init to all docker run commands in the CI
   workflow (matches what run-tests.sh already does locally).

3. CentOS CI container start used the systemd path
   The CI workflow started CentOS containers with --cgroupns=host / --tmpfs
   expecting systemd as PID 1, then waited for multi-user.target.  But the
   Dockerfiles use the /usr/local/bin/systemctl shim (no real systemd).
   Fix: unify container start for all OSes to --init + tail -f /dev/null;
   remove the separate CentOS systemd-start step and the "Wait for systemd"
   step entirely.

Also adds a "Start pre-installed services" step in CI for Ubuntu only, to
mirror what run-tests.sh does locally: start MySQL and Apache before the
setup script runs, since Ubuntu images pre-install lamp-server^ during the
Docker build (services blocked by policy-rc.d).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On CentOS/RHEL with MySQL 8.4.9, appending sql_mode to /etc/my.cnf did not
take effect after restart — the vendor-supplied files in /etc/my.cnf.d/ appear
to override it.  Two-part fix:

1. Write sql_mode to /etc/my.cnf.d/zz-simplerisk.cnf (the "zz-" prefix
   ensures it sorts last alphabetically among included files, so it wins
   regardless of what other files the MySQL RPM installs).

2. After the restart, also run SET GLOBAL sql_mode=... so the change is
   applied to the running instance immediately, without relying solely on
   config-file parsing order.

Also enhances the CI diagnostics step to dump /etc/my.cnf, all files in
/etc/my.cnf.d/, and the live sql_mode value on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch all package removal steps in uninstall_ubuntu_debian,
uninstall_centos_rhel, and uninstall_suse from exec_cmd (bail on
failure) to exec_cmd_nobail so the uninstaller continues even when
packages were never installed or only partially set up.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The mysql84-community-release RPM (updated in-place to -3) now also
enables a mysql-9.7-lts-community repo. DNF was resolving
mysql-community-server to 9.7.0 instead of 8.4.x. Add
--disablerepo='mysql-9*' to the install command so we always get
the 8.4 LTS release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates the unique SLES work onto this branch so it is the single
canonical line (install + uninstall + tests + docs):

- SSL: use OpenSSL 3 / FIPS-compatible `openssl genpkey` instead of the
  `genrsa -des3` + passphrase-file dance
- cron: install cron via zypper before setting up the backup cronjob
- firewall: configure firewalld (enable/start + open http/https/ssh),
  but guard on `firewall-cmd` being present so the install does not abort
  with "Unit file firewalld.service does not exist" on minimal/JeOS images
- uninstall_suse: mirror the firewall teardown (http/https only, leaving
  ssh intact to avoid locking out remote access), also guarded

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- exec_cmd_sensitive: new helper that runs a command without echoing it.
  set_up_database now uses it for every MySQL statement that embeds a
  password (plus the config.php DB_PASSWORD sed), so credentials no longer
  leak into --debug output or CI log artifacts (the test harness runs --debug).
- perform_installation: abort with a clear message if the SimpleRisk version
  lookup returns empty (e.g. no network) instead of a confusing wget failure.
- setup: when run via a pipe (curl | bash) with no TTY, default installs to
  headless. Uninstall is excluded so the destructive confirmation still
  requires an explicit --yes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The SLES install was dying at `zypper -n install mysql-community-server`.
Root cause: SLES ships MariaDB, whose packages "provide" the mysql RPM
namespace and conflict with mysql-community-server. Interactively zypper
asks which to keep; with -n it aborts, so MySQL never installs and every
later step (start mysql, set_up_database) fails.

Changes in setup_suse:
- Install with `--allow-vendor-change --force-resolution` so the resolver
  applies the "replace MariaDB with MySQL" solution non-interactively.
- Switch to the unversioned `mysql84-community-release-sl15.rpm` (defaults
  to the 8.4 LTS subrepo) instead of the stale `-1` pin.
- Replace the hardcoded `rpm --import` with `zypper --gpg-auto-import-keys
  refresh` so a rotated MySQL signing key can't cause repomd.xml signature
  failures.
- Defensively disable the mysql-innovation-community (9.x) track to stay
  on 8.4 LTS, mirroring the CentOS/RHEL fix.

NOTE: pending validation on a registered SLES 15 box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Upstream SimpleRisk release bundles changed: they no longer ship
includes/config.php. Instead they ship includes/config.sample.php, a
template whose values are __PLACEHOLDER__ tokens, and SimpleRisk now
treats the mere existence of config.php as the install marker (the old
SIMPLERISK_INSTALLED flag is gone). The script's set_up_database edited
config.php in place, so every install failed with:
  sed: can't read /var/www/simplerisk/includes/config.php: No such file

Fix (shared, affects all OSes):
- set_up_simplerisk: create config.php from config.sample.php after extract.
- set_up_database: replace the obsolete DB_PASSWORD/SIMPLERISK_INSTALLED
  seds with substitution of the new tokens (DB_HOSTNAME=localhost,
  DB_PORT=3306, DB_USERNAME=simplerisk, DB_PASSWORD=<generated>,
  DB_DATABASE=simplerisk, USE_DATABASE_FOR_SESSIONS=true).

Placeholder substitution validated on an openSUSE Leap 15.6 instance
against the current bundle (20260519-001).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Validated on openSUSE Leap 15.6 (SLES 15 SP6 equivalent) against the
live MySQL repo. Two issues the first pass missed:

- The mysql84-community-release-sl15 RPM actually enables the newest LTS
  repo (currently mysql-9.7-lts-community) and leaves mysql-8.4-lts-community
  DISABLED, so `mysql-community-server` resolved to 9.7. Now we explicitly
  enable the 8.4 repos, disable the 9.x ones, and bound the install to
  `mysql-community-server<9` so it can only resolve to 8.4 LTS (got 8.4.9).
- Re-add `rpm --import $MYSQL_KEY_URL`: importing the key into the rpm
  keyring is required for package-signature verification (the install was
  warning NOKEY for key a8d3785c); `--gpg-auto-import-keys` only covers
  zypper repo metadata, not rpm package signatures.

Reproduced the original MariaDB-vs-MySQL conflict (zypper aborts under -n)
and confirmed the fix resolves it: MariaDB replaced, MySQL 8.4.9 installed,
zero NOKEY warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…STALLED

The verify-install check `SIMPLERISK_INSTALLED is set to true` tested for a
flag the redesigned config.php no longer has (existence of config.php is now
the install marker). Replace it with checks that the installer substituted
the __PLACEHOLDER__ tokens and populated DB_PASSWORD.

Validated locally via tests/run-tests.sh ubuntu-22.04: install verify 24/0,
uninstall verify 13/0, ALL TESTS PASSED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stock SLES ships no AppArmor profile for Apache, but hardened/STIG/CIS
baselines commonly add one in enforce mode, which blocks SimpleRisk's
writes (uploads, log dir, config.php). setup_suse now detects a loaded
Apache profile and disables it (disable symlink + apparmor_parser -R) so
Apache runs unconfined; uninstall_suse re-enables it to restore the
original posture. Both are guarded: a clean no-op when AppArmor is off or
no Apache profile exists (the default-SLES case), so nothing changes on
unhardened systems.

Detection/no-op logic validated on openSUSE Leap 15.6. The enforce-mode
unload itself requires a real AppArmor-enabled SLES box to exercise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DASimp
Copy link
Copy Markdown
Contributor Author

DASimp commented Jun 4, 2026

Closing: setup-scripts-dev is the canonical source (it syncs to this public repo). These fixes are being ported into setup-scripts-dev as a focused PR (SLES AppArmor + MariaDB-conflict resolution); the rest (uninstall, config-template handling, tests) already exist there.

@DASimp DASimp closed this Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant