Skip to content

Merge testing into master to reset the merge base - #164

Merged
jsokol merged 34 commits into
masterfrom
MERGE-testing-into-master
Aug 21, 2026
Merged

Merge testing into master to reset the merge base#164
jsokol merged 34 commits into
masterfrom
MERGE-testing-into-master

Conversation

@jsokol

@jsokol jsokol commented Aug 21, 2026

Copy link
Copy Markdown
Member

Caution

Merge this with "Create a merge commit". Do NOT squash or rebase.
The entire point is the second parent — squashing produces the same files but leaves the merge base at 81cb9e2, so the next testing → master comparison conflicts exactly as badly as this one did. The repo permits squash merges, hence the warning.

Description

The first-ever testing → master merge. The merge base was still 81cb9e2 (PR #144, July 2026): every shared change in the four months since had landed as a separate cherry-pick on each branch, so git saw both sides as independently modified and the branches could never be diffed or merged cheaply.

After this, the merge base is af02e01 (current testing tip), and future syncs are ordinary small merges.

Nine conflicts, resolved deliberately rather than by picking a side.

Kept master's — testing still carries the pre-cutover release path

File Why
push-to-dockerhub.yml Stays deleted. testing's copy still has the push: master trigger that rebuilt from the prod bundle and broke the 20260820-001 GA. The other three push workflows and update_workflows.sh auto-resolved as deletes.
promote-latest.yml master's build-once GA promote (both images, GHCR mirror, currency guard) over testing's old minimal-only copy.
simplerisk/Dockerfile, simplerisk-minimal/Dockerfile testing's carry ENV version=20260519-001 — taking them would have reverted the GA version. Regenerated at 20260820-001 rather than hand-merged, since they're generated artifacts.
.grype.yaml Comment-only difference; master's wording is version-agnostic and stays correct now the minimal default is php 8.5. Ignore lists verified identical.

Took testing's — strict supersets, verified zero master-only lines

File Why
publish-testing.yml The live RC publisher with the resolve/publish-full jobs; master's copy was the old dormant one. Re-applied environment: testing from #161, which testing predates — and extended it to publish-full, which uses the same Docker Hub credentials.
entrypoint.sh, README.md The DB_UPGRADE headless schema-upgrade feature, whose db-upgrade.php auto-merged in.

CLAUDE.md resolved as a union: testing's DB_UPGRADE row, master's promote-model CI/CD bullets and php-default note.

Release Notes

None — internal change. Brings the DB_UPGRADE headless schema-upgrade mode to the master branch's image sources; it was already live on testing.

Manual Validation Steps

  1. Confirm nothing publishes on a master push — create_new_tag.yml should be the only push: master trigger, and it only tags.
  2. Confirm the five cutover deletions held: push-to-dockerhub.yml, push-to-gh-pkgs.yml, both _rw.yml, update_workflows.sh.
  3. grep '^ENV version=' simplerisk/Dockerfile simplerisk-minimal/Dockerfile → both 20260820-001.
  4. grep -c 'environment: testing' .github/workflows/publish-testing.yml2.
  5. After merge: git merge-base origin/master origin/testingaf02e01, not 81cb9e2.
  6. Next RC push to testing should behave identically — this PR does not change testing.

Type of Change

  • Build / CI configuration
  • Repository maintenance (merge-base reset)

Testing Results

  • No conflict markers anywhere in the tree.
  • Both generator harnesses pass and leave the committed Dockerfiles clean (the generator_checks guard).
  • actionlint clean on all five remaining workflows.
  • shellcheck -S error (CI's gate) clean on all tracked scripts.
  • make update_version VERSION=20260820-001 → exit 0, idempotent.
  • Verified container-validation.yml auto-merged correctly: gained testing's branches: [master, testing] while keeping master's generator_checks and php 8.5 jobs.
  • Not exercised: publish-testing.yml only runs on a testing push, so its merged form on master is dormant and untested here. It is byte-identical to what is already running on testing apart from the two environment: testing lines, which mirror what ci: declare per-workflow permissions and move registry creds to environments #161 already validated on master's copy.

CIA Impact Check

MAY negatively impact Confidentiality, Integrity and/or Availability.

Justification for the change

  • A four-month-old merge base made every cross-branch comparison unreliable and every future sync a large conflict-resolution exercise — the kind that silently reverts things, which is exactly the risk this PR had to navigate.
  • master and testing had drifted on real content (DB_UPGRADE, grype scoping, container-validation triggers) with no mechanism forcing reconciliation.

Potential impact on Confidentiality, Integrity and/or Availability

Integrity (the main risk, mitigated). A careless resolution here would have reverted the GA version to 20260519-001, restored the auto-rebuild trigger, or downgraded promote-latest.yml — each silently. Every one of those was checked explicitly and is listed above with its resolution; the verification steps assert them on the merged tree. No release-path behaviour changes: promote-latest.yml is byte-identical to what is on master today.

Availability (low). The image sources on master gain the DB_UPGRADE code path, but it is inert unless DB_UPGRADE is set, and it has been running on testing since before the last RC. container-validation now also triggers on testing-targeted PRs, which only adds coverage.

Confidentiality. Net positive — environment: testing now scopes the Docker Hub credentials on both credential-using jobs in publish-testing.yml, where testing's copy had no environment scoping at all.

Additional Implementation Steps

No schema change, no customer action.

Follow-up worth considering: testing still holds stale copies of promote-latest.yml and the four deleted push workflows. They are inert there (promote-latest is dispatch-only; the push workflows would need a master push, which cannot happen on testing), but a master → testing sync would make the branches genuinely identical and keep the next merge trivial.

SimpleRisk Updater and others added 30 commits July 9, 2026 23:28
… checker, raise publish-testing timeout, doc/comment fixes
Build-once RC publish: immutable php83/84/85 + bare + :testing (Plan 1)
GA promote (no rebuild): retag :latest to the RC digest + manual dispatch (Plan 2)
Adds a DB_UPGRADE entrypoint mode (automatic / automatic-only) that runs
SimpleRisk's core release-by-release schema upgrade (run_database_upgrade_structured
in includes/upgrade.php) headlessly via /db-upgrade.php against an already-installed
database, using the app DB user (SIMPLERISK_DB_*) — no privileged user, no HTTP
context, no credentials beyond the DB connection. Emits the structured per-release
JSON result to the log; automatic-only exits with the upgrade status so a one-shot
Job fails on a failed upgrade.

The upgrade logic is core-only (run_database_upgrade_structured + the 97 per-release
functions live in includes/upgrade.php; upgrade.php requires only core includes/),
so the mode needs only the base image + the app DB env. No Dockerfile change: common/
is already COPYed to / (db-upgrade.php -> /). Twin of the DB_SETUP mode. DB_UPGRADE
documented in simplerisk-minimal/README.md and CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
simplerisk-minimal: headless DB_UPGRADE mode for release-triggered schema upgrades
The pinned php:${php_version}-apache base ships apache2/curl at its own
build-time versions, and the Dockerfile only `install`s specific packages — so
base packages accumulate upstream-fixed CVEs. Grype (--fail-on critical
--only-fixed) flags criticals on every minimal build: curl
(CVE-2026-11856/10536/8927/8924, fixed 8.21.0) and apache2
(CVE-2026-29167/42535, fixed 2.4.68). Add `apt-get -y upgrade` after
`apt-get update` so the Debian security fixes are pulled; regenerate the
Dockerfile from the generator.

Targets testing (features land here, then promote to master). Full/Ubuntu
image currently passes Grype and is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Grype's binary classifier reads the PHP interpreter's version string (PHP 8.3.32,
embedded in /usr/local/bin/php, libphp.so, and extensions) as a "curl" binary at
8.3.32 and flags curl CVEs (CVE-2026-11856/10536/8927/8924, fixed 8.21.0). The
real curl is the Debian package, patched to 8.14.1-2+deb13u4 and correctly not
flagged. Scope the ignore to binary-type curl so the deb curl stays scanned.

With the apt-get upgrade (apache2 -> 2.4.68) this clears the minimal Grype gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security review flagged the previous `package: {name: curl, type: binary}` rule
as over-broad — it would silently suppress any future binary-curl finding. Narrow
it to the four specific PHP-version-misidentified CVEs (CVE-2026-11856/10536/8927/
8924) so a genuine curl vulnerability still surfaces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both gates only triggered on pull_request -> master, so feature PRs to testing
(where features land before promotion) were never validated. Add testing to the
branch filter so the gates cover the branch features actually target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Patch base-image CVEs in the minimal image (apt upgrade)
Reconciles the bundle-integrity feature (#141) onto testing. A single
common/download_and_verify_bundle.sh downloads the prod bundle, resolves its
bundle_sha256 (md5 fallback) from the updates feed, verifies fail-closed, then
extracts — so a swapped S3 object aborts the build instead of being baked into a
published image. Both image generators COPY + run it.

Reconciled with testing's PREGA_BUNDLE_FALLBACK: a RELEASED build is always
verified against the prod bundle; the pre-GA CI fallback to bundles-test skips
verification (the release has no published hash yet) and warns loudly — never for
a released image. The full image keeps its separate release-SQL download.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fication

Verify the release bundle hash before extracting (both images)
Lets a public demo instance restrict uploads, password change and language
change through configuration rather than by patching core files. That
matters because the hosted platform runs ONE shared image for every
customer — an instance with patched files cannot run on it, which is why
demo.simplerisk.com is currently off on its own.

Deliberately NOT a placeholder in config.sample.php. This is a
hosting-platform concern, not a self-hosted option, so the define is
APPENDED only when asked for and stays invisible to everyone else.
config.sample.php has no closing "?>", so an appended define lands inside
PHP.

ONLY an explicitly true value activates it (true/1/yes/on, case-insensitive).
This is deliberate, not defensive noise: PHP treats the STRING 'false' as
truthy, so writing define('DEMO_MODE', 'false') would put EVERY customer
into demo mode. The constant's PRESENCE is the signal — core checks
defined('DEMO_MODE'), not its value — and anything not clearly true is
logged and ignored.

The grep guard is for the fallback path in set_config: when
config.sample.php is absent the existing config.php is reused rather than
regenerated, so an unguarded append would redefine the constant on every
boot.

Verified across two boots: exactly one define for true/TRUE/1/yes/on, none
for unset/false/0/no/arbitrary strings, and no duplication when config.php
already carries it.

Minimal image only. The non-minimal image's set_config is an older shape
(hardcoded values, run-once guard) and the hosted platform uses minimal.

Core gating — the defined('DEMO_MODE') checks — is a separate change owned
by the core repo. No config.sample.php change is needed there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…up connections

The privileged setup/delete MySQL client in the simplerisk-minimal entrypoint now honors DB_SSL_ENABLED. Set to exactly "true", it appends --ssl-mode=REQUIRED --enable-cleartext-plugin to the db_setup()/delete_db() mysql calls — for databases that require the cleartext auth plugin to be sent over TLS (e.g. when DB_SETUP_PASS is a short-lived token rather than a static password). Defaults off (fail-closed): any other value or unset preserves today's plaintext-capable connection, so published images are unaffected for existing consumers. Documented in README.md and CLAUDE.md.

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

code-development be81d416f9 ("write DB schemas to database/master, drop the
database/testing branch") made master the single source for generated installer
SQL. This workflow was last touched 2026-07-01, ten days earlier, and still
fetched database/testing.

The 20260709-001 RC was cut before that change, so database/testing still held
its SQL and this step passed. 20260811-001 is the first RC cut after it, and the
step failed with a 404 on
database/testing/simplerisk-en-20260811-001.sql — blocking the testing image
publish and the tier=testing SSM promotion. The bundle fetch and its sha256
verification were unaffected.

Also retitles the error so a future failure names the branch it actually
searched rather than saying "testing schema".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An instance on a pre-release image compares itself against the production
updates endpoint, which only advertises the current GA release — so a
canary running 20260811-001 reports a phantom version problem against an
updates service still publishing 20260519-001. The same applies to
registration: a rebuilt canary would register itself into the production
licensing database.

Adds optional SIMPLERISK_{SERVICES,UPDATES,PING,BUNDLES,LICENSING}_URL,
appended to config.php only when supplied. Same shape as DEMO_MODE: no
placeholder in config.sample.php, so an instance that says nothing keeps
the code's built-in production defaults, and self-hosted users see nothing
new.

All five are supported deliberately, not for completeness. LICENSING_URL
superseded SERVICES_URL and PING_URL when registration and ping merged into
the licensing service in the July 2026 release — but the platform runs
different images per release channel, and the `latest` channel currently
points at 20260519-001, which reads the legacy pair. Writing whichever are
supplied keeps one customer config correct across both. Every consumer is
defined()-guarded with a production fallback, so a constant an image does
not know about is simply ignored.

Values are validated rather than trusted: http(s) only, and no quote or
backslash, since they are interpolated into a single-quoted PHP string.
Rejections are logged rather than silent. The grep guard makes it
idempotent for the persisted-volume path where config.php is reused rather
than regenerated.

Verified: valid URLs written once, a non-URL and a quote-injection attempt
both refused, second pass a no-op.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-testing

Allow the service endpoint URLs to be overridden per instance (testing)
An Apache CustomLog inside a vhost REPLACES the one inherited from the server
config. default-ssl.conf declared only ssl_request_log, so
conf-enabled/other-vhosts-access-log.conf never applied to :443 -- and since the
ALB speaks only to :443, the :80 vhost's access.log stayed empty too.

The result was no HTTP access logging anywhere for a dedicated-hosting customer.
ssl_request_log was the only record of a request, and it is a poor one: its %h is
the load balancer's private address rather than the caller, and its format carries
no status code, referer, or user agent. Nothing shipped it off the container
either -- it is a real file on the ephemeral /var/log volume, so it died with the
task.

Found while verifying demo's migration: a probe request could not be located in
CloudWatch at all. The log-tailer sidecar appears to cover this (it tails
/var/log/apache2/access.log) but cannot -- that path is a symlink to /dev/stdout
in the php:apache base image, and /dev/stdout is a write end, so the tail reads
nothing. The sidecar entry looked like coverage while providing none.

Adds a combined-format CustomLog to access.log, which reaches the container's
stdout and therefore the awslogs driver. Client identity comes from
X-Forwarded-For, since %h is the load balancer. The whole header is logged
deliberately: the ALB appends the true peer as the last element, so only the last
one is trustworthy, and keeping the chain visible beats hiding a forged prefix
behind a single value.

ssl_request_log is kept for the TLS protocol/cipher detail the combined format
does not carry.

Verified on a built image: `apache2ctl -t` reports Syntax OK, and requests through
the vhost emit, on stdout,

  203.0.113.9  ... "GET /?abuse-probe HTTP/1.1"     200 4031 "-" "curl/8.7.1"
  198.51.100.4 ... "GET /nonexistent-page HTTP/1.1" 404  298 "-" "curl/8.7.1"

including the 404 the previous configuration could not record.
…ting

Restore HTTP access logging on the minimal image's :443 vhost (testing)
simplerisk/generate_dockerfile.sh branched its recipe on the literal string
"testing", so a context-sourced build could only ever stamp
`ENV version=testing`. That is the one thing standing between the full-stack
image and the build-once-promote model: the RC has to carry a real version to
be promotable at GA.

Add the same (version, source_mode) split simplerisk-minimal already has:
`context` COPYs the app from the build context, `download` emits the
hash-verifying downloader stage. The default preserves back-compat exactly --
a bare "testing" still selects context, a bare version still selects download.

Also restore --fail on the schema fetch (present on master, lost on testing).
Without it curl writes the 404 body into /simplerisk.sql and the image ships
an HTML error page as its schema.

Adds test_generate_dockerfile.sh mirroring the minimal harness: 14 checks over
both modes, both back-compat paths, invalid-mode rejection and idempotence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jsokol and others added 4 commits August 21, 2026 10:55
…nimal

GA promotes digests rather than rebuilding, so every image it promotes has to
exist as an RC. simplerisk/simplerisk had no RC build at all -- it was only
ever built on master from the prod bundle, which is exactly the rebuild the
promote model removes.

Add a publish-full job tagging <VERSION>-jammy/-noble, bare <VERSION> (= noble,
the default) and :testing, amd64-only to match what the release build published
before. No SSM tier: the full-stack image is not part of the managed fleet.

Hoist version resolution into a shared resolve job so both publish jobs stamp
the same release even if the testing channel rotates mid-run, and have each
fetch that exact bundle by name rather than re-listing the channel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The generators are now load-bearing for the release path -- context mode has to
stamp a real ENV version, download mode has to keep the hash-verifying
downloader stage -- but neither harness was wired into CI. Run both on PRs, and
fail if either leaves a committed Dockerfile modified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…testing

Build the full-stack image once at RC time, so GA can promote it
First-ever testing -> master merge. The base was still 81cb9e2 (PR #144, July
2026): every shared change since had landed as a separate cherry-pick on each
branch, so git saw both sides as independently modified and the branches could
never be compared cheaply. This resets that.

Nine conflicts, resolved deliberately rather than by picking a side:

Kept master's (testing carries the pre-cutover release path):
- push-to-dockerhub.yml stays DELETED. testing's copy still has the
  `push: master` trigger that rebuilt from the prod bundle and broke the
  20260820-001 GA. The other three push workflows and update_workflows.sh
  auto-resolved as deletes.
- promote-latest.yml -- master's build-once GA promote (both images, GHCR
  mirror, currency guard) over testing's old minimal-only copy.
- Both Dockerfiles. testing's carry ENV version=20260519-001; taking them would
  have reverted the GA version. Regenerated at 20260820-001 instead of
  hand-merging, since they are generated artifacts.
- .grype.yaml -- comment-only difference; master's wording is version-agnostic
  and stays correct now the minimal default is php 8.5. Ignore lists verified
  identical.

Took testing's (strict supersets, verified: zero master-only lines):
- publish-testing.yml -- the live RC publisher with the resolve/publish-full
  jobs. master's copy was the old dormant one. Re-applied `environment: testing`
  from #161, which testing predates, and extended it to publish-full -- that job
  uses the same Docker Hub credentials.
- entrypoint.sh + README.md -- the DB_UPGRADE headless schema-upgrade feature,
  whose db-upgrade.php auto-merged in.

CLAUDE.md resolved as a union: testing's DB_UPGRADE row, master's promote-model
CI/CD bullets and php-default note.

Verified on the result: no workflow publishes on a master push (create_new_tag
is the only master-push trigger, and it only tags); both generator harnesses
pass and leave the committed Dockerfiles clean; actionlint clean; shellcheck
clean at CI severity; make update_version idempotent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jsokol
jsokol marked this pull request as ready for review August 21, 2026 21:00
@jsokol
jsokol merged commit 87a98d7 into master Aug 21, 2026
14 checks passed
@jsokol
jsokol deleted the MERGE-testing-into-master branch August 21, 2026 21:00
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.

2 participants