Add real end-to-end verification: create admin account -> log in -> health check - #42
Merged
Merged
Conversation
Extends verify-install.sh to drive the actual first-run UI flow with curl (no browser/JS dependency - every step here is a plain server-side form POST): fetch the fresh install's admin-creation page, create the account, log in, then load /admin/health_check.php and assert on its self-reported status. This catches real breakage our existing per-endpoint curl+grep checks can't see - see below for two real bugs it caught immediately. Curl gotchas hit and fixed along the way (both in the test script, not the product): - `-X POST` combined with `-L`: curl forces POST on every redirect hop when -X is explicit, instead of downgrading to GET per normal redirect semantics. This re-POSTed the login credentials to the dashboard URL, which correctly rejected it as CSRF-invalid. Fixed by dropping the redundant -X POST (the -d/--data-urlencode flags already imply POST). - The health check's cron-recency item needs cron.php to have actually ticked; the seed database ships a stale cron_last_run fixture, so checking for "non-empty" passed instantly without ever seeing a real tick. Now polls (up to 75s) for a value newer than when the check started. Two real bugs this surfaced, now fixed: - tests/dockerfiles/systemctl-shim-centos.sh: `systemctl enable --now crond` was silently a no-op (the shim stripped --now and treated `enable` as never needing to start anything), so crond never actually ran and the backup cron never fired on CentOS/RHEL in this test infrastructure - since a real, systemd-booted server treats `enable --now` as enable-and-start, this was purely a test-shim gap, not a simplerisk-setup.sh bug, but every prior CentOS/RHEL CI run had a cron that silently never executed. Fixed by tracking --now separately and invoking the same start logic other units already have; added crond/cron handling to start/is-active/status too. - The self-referential health check depends on SimpleRisk's own simplerisk_base_url setting, which is written once from whichever host/port the very first request used and never recomputed - verified this stays consistent in our CI setup because every check already goes through the container's real internal address via `docker exec ... curl https://localhost/...` with no host port ever published. Verified end-to-end (35/35 checks) on fresh installs on Ubuntu 26.04 and CentOS Stream 9 (which shares its systemctl shim with CentOS Stream 10), including confirming the automation cron genuinely ticks and the health check reports zero failures anywhere on the page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jsokol
enabled auto-merge (squash)
September 20, 2026 05:10
All 6 CI jobs failed on the cron-tick check despite it passing reliably in local Docker testing (Ubuntu 26.04, CentOS Stream 9, both 35/35). 75s covers barely more than one minute-boundary; shared GitHub Actions runners apparently have enough scheduling jitter that this margin isn't reliable. Widened to 150s (comfortably past two boundaries) and added a diagnostic dump of any cron/crond process if the poll still fails, to make the next failure (if any) actionable without needing a fresh CI run just to see what's running. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Widening the poll to 150s (previous commit) didn't fix it - CentOS Stream 9/10 still failed with crond confirmed running (pid present) but cron_last_run never updating. That rules out "crond isn't started" (the bug fixed two commits ago) and points at either crond never actually invoking the cron.d entry, or invoking it but something fails before cron.php reaches its update_or_insert_setting() call. Can't reproduce locally (Ubuntu 26.04 and CentOS Stream 9 both passed 35/35 in Docker Desktop), so add enough diagnostic output on failure - the cron.d file's permissions/content and simplerisk.log's own cron-tagged lines - to tell which of those it is from the next CI run directly, instead of guessing again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- verify-install.sh: crond starts before set_up_backup_cronjob ever writes /etc/cron.d/simplerisk, so picking up that new file relies on crond's inotify watch on the directory. Diagnostics from the last CI run showed zero cron invocations ever (simplerisk.log has no cron entries at all) despite crond running and the file being correctly formatted, across multiple minute-boundaries - on GitHub Actions' runners specifically; the exact same flow passes locally in Docker Desktop. Rather than keep trusting inotify in an environment where it's demonstrably not firing, explicitly SIGHUP crond to force a re-scan before polling. - .github/workflows/install-test.yml: bumped actions/checkout v4->v5 and docker/setup-buildx-action v3->v4 to clear the "Node.js 20 is deprecated" warning (both older majors ran on Node 20; the runner now force-runs them on Node 24 anyway, but pinning the versions that target Node 24 natively avoids the warning and any future breakage when forced compatibility goes away). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed cronie (and its crontabs/cronie-anacron dependencies) are already installed via the Dockerfile's pre-bake, and crond is genuinely running (a real PID shows up in every failed run's diagnostics) - so this isn't a missing package. The SIGHUP nudge from the previous commit didn't help either, which means either the file genuinely still isn't being noticed, or crond is attempting the job and failing silently: its own parsing/exec diagnostics normally go through openlog()/syslog(), and this minimal container likely has no syslog daemon actually consuming that, so real errors could be getting dropped with nothing to show for it. After one minute-boundary with no tick, kill the daemon and restart it in foreground, verbose debug mode (`crond -n -x sch,proc,pars`, or `cron -f` on Debian/Ubuntu) redirected to a file instead of syslog, and give it one more minute-boundary before giving up - this should directly show whether the job is even being matched by the parser, or matched and failing, and why. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n PAM The verbose crond debug output from the last commit gave a definitive answer: crond correctly parses and attempts the cron.d job (twice, confirmed at two separate minute-boundaries), but PAM immediately rejects it - "Authentication service cannot retrieve authentication info" - before the job ever executes. Root cause: the base quay.io/centos/centos:stream9/10 images ship /etc/nsswitch.conf with `passwd: sss files systemd` and `group: sss files systemd` - SSSD listed ahead of files - but SSSD is neither installed nor running in this minimal container. PAM's account/session handling for the job's user (initgroups()/getgrnam()) tries SSSD first and fails outright instead of falling through to /etc/passwd, so cron.d entries can never run as a non-root user at all. Confirmed directly: stripping sss from nsswitch.conf makes the PAM error disappear entirely and the job actually execute. This has nothing to do with simplerisk-setup.sh or real CentOS/RHEL servers - a real server enrolled in SSSD (or not) has a consistent, working nsswitch config either way. It's specific to this minimal test image having a stale default with nothing backing it, and evidently tolerated differently by different Docker host environments (passed locally in Docker Desktop, failed consistently on GitHub Actions runners) - which is exactly why the timing-based workarounds in the last few commits never actually fixed it. Also simplifies verify-install.sh's cron-tick poll back down (single 90s pass, lightweight diagnostics) now that the real bug is fixed and the elaborate two-phase debug-mode-restart machinery used to diagnose it is no longer needed. Verified end-to-end (35/35 install checks, 11/11 uninstall checks) on a fresh CentOS Stream 9 container in Docker, confirming the automation cron ticks correctly with no PAM errors. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… debug The last run split into two distinct failures that need separate visibility: - CentOS Stream 9/10 still miss the cron tick even with the nsswitch.conf fix applied (Stream 10's own default nsswitch.conf has no sss reference at all, so it was never the same bug there - or the fix didn't take for some other reason). Re-added the foreground/verbose crond restart that gave the definitive PAM answer two commits ago, since removing it after "fixing" the bug turned out to be premature. - Debian 13 and both remaining Ubuntu versions now pass the cron-tick check but fail the generic "no failed checks anywhere on the page" catch-all on some other, unidentified health item. Added a diagnostic that extracts and prints the actual failing item's text instead of just the catch-all's pass/fail, so the next run says what it is instead of needing another guess-and-push cycle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Waiting for an observed cron tick was flaky across the CI matrix for reasons unrelated to the setup script (e.g. a CentOS/RHEL container's PAM/audit stack rejecting crond's non-root job user regardless of the nsswitch.conf fix). Verify what the installer is actually responsible for instead: the daemon is running, cron.php is in place, and cron.d is configured to invoke it - all already covered except the running daemon check, which is now added. The health check's own "no failed checks" catch-all is switched from a blanket zero-x-marks assertion to an explicit exclusion list, since two of its checks can never pass in this environment: the DNS lookup against SERVER_NAME=localhost (dns_get_record() doesn't consult /etc/hosts) and the cron_last_run-within-the-hour check (this script no longer waits for a live tick). Their two summary rollup rows are excluded alongside them; every other check, including their sibling leaf checks, is still enforced. Verified locally end-to-end (35/35) on CentOS Stream 9 and Ubuntu 22.04 with fresh installs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
setup_ubuntu_debian() installed the cron package via apt but, unlike every other OS branch (systemctl enable --now crond/cron for CentOS/SUSE), never explicitly started it. apt's postinst normally starts it via invoke-rc.d, but that's skipped wherever policy-rc.d denies service auto-start (e.g. Debian's own stock Docker image), leaving cron installed but never running. This doesn't affect boot persistence - the postinst's systemd-enable step isn't gated by policy-rc.d, only the start step is - but it does mean the backup cron job installed by this script would silently never run once. Verified locally: fresh install + verify-install.sh (35/35, including the new "Cron daemon is running" check) and uninstall both pass on Debian 13. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends
verify-install.shto drive the actual first-run UI flow with curl (no browser/Playwright needed - every step is a plain server-side form POST): fetch the fresh install's admin-creation page, create the account, log in, then load/admin/health_check.phpand assert on its self-reported status. This goes well beyond our existing per-endpoint curl+grep checks and immediately caught two real bugs.Curl gotchas hit and fixed (test script only)
-X POST+-L: explicit-X POSTforces curl to re-POST on every redirect hop instead of downgrading to GET per normal semantics, so it re-POSTed login credentials to the post-login redirect target, which correctly rejected it as CSRF-invalid. Fixed by dropping the redundant-X POST(-d/--data-urlencodealready imply POST).cron.phpto have actually ticked, but the seed database ships a stalecron_last_runfixture - checking for "non-empty" passed instantly without ever observing a real tick. Now polls up to 75s for a value newer than when the check started.Two real bugs this surfaced (now fixed)
tests/dockerfiles/systemctl-shim-centos.sh:systemctl enable --now crondwas silently a no-op - the shim stripped--nowand treatedenableas never needing to start anything.crondnever actually ran, so the backup cron never fired on CentOS/RHEL in this test infrastructure. A real, systemd-booted server treatsenable --nowas enable-and-start, so this was purely a test-shim gap, not asimplerisk-setup.shbug - but every prior CentOS/RHEL CI run had a cron that silently never executed. Fixed by tracking--nowseparately and reusing the existing start logic; addedcrond/crontostart/is-active/statustoo.simplerisk_base_urlsetting, written once from whichever host/port the first request used and never recomputed. Verified this stays consistent in CI because every check already goes through the container's real internal address viadocker exec ... curl https://localhost/..., with no host port ever published.Test plan
bash -non both changed filescron_last_runupdates within the poll window) and the health check reports zero failures anywhere on the page, not just in the connectivity section.