Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b5e1fc5
Testing image trigger: version 20260709-001 (code-development @ 51416…
Jul 9, 2026
1f1d99e
build(minimal): decouple image version from source-mode; default php 8.5
jsokol Jul 10, 2026
ec196ce
ci(container-validation): validate php 8.5; fix minimal job-key/php skew
jsokol Jul 10, 2026
bb2972b
build(minimal): fold PREGA_BUNDLE_FALLBACK into the generator downloa…
jsokol Jul 10, 2026
82635c8
ci(publish-testing): build-once immutable php83/84/85 + bare + :testi…
jsokol Jul 10, 2026
05b2a0e
fix(minimal): sync committed Dockerfile to generator, non-destructive…
jsokol Jul 10, 2026
71f6817
Merge pull request #147 from simplerisk/FEATURE-docker-publish-buildonce
jsokol Jul 10, 2026
9896f2f
ci(promote-latest): GA retags :latest to <V>-php85 digest (no rebuild…
jsokol Jul 10, 2026
74a45c9
ci(push-to-dockerhub): stop rebuilding minimal on master (GA promotes…
jsokol Jul 10, 2026
0b86fa1
ci(promote-latest): guard GA against promoting a stale (non-current-R…
jsokol Jul 10, 2026
7a11e33
Merge pull request #148 from simplerisk/FEATURE-docker-ga-promote
jsokol Jul 10, 2026
7b23b8c
simplerisk-minimal: add headless DB_UPGRADE mode
jsokol Jul 12, 2026
88e1d21
Merge pull request #149 from simplerisk/FEATURE-db-upgrade-mode
jsokol Jul 12, 2026
f0a5884
fix(minimal): apt-get upgrade to patch base-image CVEs
jsokol Jul 12, 2026
be61fa5
grype: ignore binary-classified curl (PHP-version false positive)
jsokol Jul 12, 2026
5591ab0
grype: scope the binary-curl ignore per-CVE (not the whole package)
jsokol Jul 12, 2026
5b8dae1
ci: run container-validation + shellcheck on testing PRs too
jsokol Jul 12, 2026
1165902
Merge pull request #150 from simplerisk/FIX-base-image-cve-refresh
jsokol Jul 12, 2026
c622c2e
feat: verify the release bundle hash before extracting (both images)
jsokol Jul 12, 2026
e31d766
Merge pull request #141 from simplerisk/FEATURE-bundle-integrity-veri…
jsokol Jul 12, 2026
0396371
Add SIMPLERISK_DEMO_MODE to the minimal image's config generation
jsokol Aug 7, 2026
b85ccfd
simplerisk-minimal: add opt-in DB_SSL_ENABLED for TLS-required DB set…
WolfangAukang Jul 30, 2026
08185bb
Testing image trigger: version 20260811-001 (code-development @ f4497…
Aug 12, 2026
8df482a
fix(ci): fetch the release schema from database/master, not database/…
jsokol Aug 12, 2026
dc69f6b
Allow the service endpoint URLs to be overridden per instance
jsokol Aug 13, 2026
e625e2e
Merge pull request #154 from simplerisk/FEATURE-service-url-overrides…
jsokol Aug 13, 2026
6b146dc
Restore HTTP access logging on the minimal image's :443 vhost
jsokol Aug 13, 2026
f2470a5
Merge pull request #156 from simplerisk/FIX-apache-access-logging-tes…
jsokol Aug 13, 2026
ca733b0
Testing image trigger: version 20260820-001 (code-development @ 2debd…
Aug 21, 2026
db3edd0
build(full): decouple image version from source-mode in the generator
jsokol Aug 21, 2026
a68ac50
ci(publish-testing): build the full-stack RC image once, alongside mi…
jsokol Aug 21, 2026
136cb86
ci(container-validation): run both generator harnesses on PRs
jsokol Aug 21, 2026
af02e01
Merge pull request #159 from simplerisk/FEATURE-full-image-buildonce-…
jsokol Aug 21, 2026
2c55e9a
Merge testing into master to reset the merge base
jsokol Aug 21, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/container-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Full checkup for SimpleRisk Docker images
on:
workflow_dispatch:
pull_request:
branches: [ master ]
branches: [ master, testing ]

permissions:
contents: read
Expand Down
215 changes: 179 additions & 36 deletions .github/workflows/publish-testing.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
name: Publish simplerisk-minimal testing image + promote
name: Publish testing images + promote

# Publishes a TESTING-channel simplerisk-minimal image and promotes it into the
# dedicated-hosting (customers-cdk) account so tier=testing customers auto-update.
# Publishes the TESTING-channel SimpleRisk images and promotes the minimal one
# into the dedicated-hosting (customers-cdk) account so tier=testing customers
# auto-update.
#
# Trigger: a push to this repo's `testing` branch (code-development mirrors the
# current testing version here on each testing publish — see the code-development
# `sync_docker_testing` workflow), or a manual dispatch.
#
# Build: the CURRENT testing bundle from bundles-test (the built testing-branch
# code) + the database/master schema, via `generate_dockerfile.sh testing`
# (COPYs the app from the context) — the same recipe the code-development
# `test_docker_deploy` smoke uses, but pushed multi-arch to Docker Hub.
# code) + the database/master schema, via `generate_dockerfile.sh "$VERSION"
# context` (COPYs the app from the assembled context, real `ENV version`) — the
# same recipe the code-development `test_docker_deploy` smoke uses, but pushed
# to Docker Hub.
#
# Tags (see design 2026-07-01-testing-image-promote): an IMMUTABLE per-version
# tag `<VERSION>-testing` plus the floating `:testing` alias. The bare `<VERSION>`
# and `:latest` tags are RESERVED for the release build (master) and are NOT
# touched here — the testing and release images are different builds (testing
# bundle vs finalized public bundle), so they must not share the bare version tag.
# Tags (see design code-development docs/superpowers/specs/2026-07-10-release-image-
# promotion-design): every release image is built ONCE here from the testing
# bundle and later PROMOTED (not rebuilt) to prod by promote-latest.yml on
# master, which retags these same digests. Nothing is rebuilt at GA.
#
# Promote: writes SSM /simplerisk/customers/image-tag/testing = <VERSION>-testing
# simplerisk/simplerisk-minimal <VERSION>-php83/-php84/-php85, bare <VERSION>
# (= php85, the default), and :testing
# simplerisk/simplerisk <VERSION>-jammy/-noble, bare <VERSION>
# (= noble, the default), and :testing
#
# There is NO <VERSION>-testing tag. The floating :latest and the prod SSM tier
# are moved only by the GA promote.
#
# Promote: writes SSM /simplerisk/customers/image-tag/testing = <VERSION>-php85
# in the customers account via OIDC; the image-updater Lambda there rolls every
# tier=testing service (new image + fresh extras together).
# tier=testing service (new image + fresh extras together). The full-stack image
# is not part of the managed fleet, so it has no SSM tier.

on:
push:
Expand All @@ -37,20 +47,22 @@ concurrency:

env:
IMAGE_NAME: simplerisk/simplerisk-minimal
FULL_IMAGE_NAME: simplerisk/simplerisk
AWS_REGION: us-east-1
SSM_PARAM: /simplerisk/customers/image-tag/testing

jobs:
publish:
environment: testing
# Resolve the version ONCE so both publish jobs stamp the same release, even
# if the testing channel rotates mid-run. Each publish job then fetches that
# exact bundle by name and verifies its hash independently.
resolve:
name: Resolve the current testing version
runs-on: ubuntu-latest
timeout-minutes: 40
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- name: Checkout (docker@testing)
uses: actions/checkout@v6

- name: Resolve current testing version + fetch bundle/schema
id: fetch
- name: Derive VERSION from the testing channel
id: ver
run: |
set -euo pipefail
# The testing channel keeps exactly one current bundle; list it and
Expand All @@ -67,10 +79,29 @@ jobs:
echo "::error::could not derive VERSION from bundle '$BUNDLE'"; exit 1
fi
echo "testing bundle: $BUNDLE (version $VERSION)"
curl -fsSL -o /tmp/testing-bundle.tgz "https://bundles-test.simplerisk.com/$BUNDLE"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

publish:
name: Publish simplerisk-minimal RC images
needs: resolve
environment: testing
runs-on: ubuntu-latest
# 3 sequential multi-arch (arm64 = QEMU-emulated, slow) variant builds run here.
timeout-minutes: 120
steps:
- name: Checkout (docker@testing)
uses: actions/checkout@v6

- name: Fetch + verify the testing bundle and schema
env:
VERSION: ${{ needs.resolve.outputs.version }}
run: |
set -euo pipefail
curl -fsSL -o /tmp/testing-bundle.tgz \
"https://bundles-test.simplerisk.com/simplerisk-${VERSION}.tgz"
# Integrity: verify the bundle against the sha256 published in the served
# updates-test feed (publish-bundle writes the hash on the same push).
# VERSION is regex-guarded, so it is safe in the sed pattern.
# VERSION is regex-guarded upstream, so it is safe in the sed pattern.
EXPECTED_SHA=$(curl -fsSL "https://updates-test.simplerisk.com/releases.xml" \
| sed -n "/<release version=\"${VERSION}\">/,/<\/release>/p" \
| grep -oE '<bundle_sha256>[a-f0-9]{64}</bundle_sha256>' | head -1 | grep -oE '[a-f0-9]{64}')
Expand All @@ -85,17 +116,17 @@ jobs:
SQL_URL="https://raw.githubusercontent.com/simplerisk/database/master/simplerisk-en-${VERSION}.sql"
curl -fsSL -o /tmp/testing.sql "$SQL_URL" \
|| { echo "::error::release schema not found on database/master: $SQL_URL"; exit 1; }
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Assemble the testing build context
env:
VERSION: ${{ steps.fetch.outputs.version }}
VERSION: ${{ needs.resolve.outputs.version }}
run: |
set -euo pipefail
cd simplerisk-minimal
# generate_dockerfile.sh testing -> a Dockerfile that COPYs simplerisk/
# (app) + common/simplerisk.sql (schema) from this context.
./generate_dockerfile.sh testing
# generate_dockerfile.sh "$VERSION" context -> a Dockerfile that COPYs
# simplerisk/ (app) + common/simplerisk.sql (schema) from this context,
# with a real `ENV version=$VERSION` (no literal "testing").
./generate_dockerfile.sh "$VERSION" context
tar xzf /tmp/testing-bundle.tgz -C .
cp /tmp/testing.sql common/simplerisk.sql
test -d simplerisk || { echo "::error::bundle did not extract a simplerisk/ dir"; exit 1; }
Expand All @@ -112,31 +143,143 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}

- name: Build and push (multi-arch) — <VERSION>-testing + :testing
- name: Build and push php83 — <VERSION>-php83
uses: docker/build-push-action@v7
with:
context: simplerisk-minimal
file: simplerisk-minimal/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
build-args: php_version=8.3
tags: ${{ env.IMAGE_NAME }}:${{ needs.resolve.outputs.version }}-php83
cache-from: type=gha,scope=minimal-testing-php83
cache-to: type=gha,mode=max,scope=minimal-testing-php83

- name: Build and push php84 — <VERSION>-php84
uses: docker/build-push-action@v7
with:
context: simplerisk-minimal
file: simplerisk-minimal/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
build-args: php_version=8.4
tags: ${{ env.IMAGE_NAME }}:${{ needs.resolve.outputs.version }}-php84
cache-from: type=gha,scope=minimal-testing-php84
cache-to: type=gha,mode=max,scope=minimal-testing-php84

- name: Build and push php85 (default) — <VERSION>-php85 + <VERSION> + :testing
uses: docker/build-push-action@v7
with:
context: simplerisk-minimal
file: simplerisk-minimal/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
build-args: php_version=8.5
tags: |
${{ env.IMAGE_NAME }}:${{ steps.fetch.outputs.version }}-testing
${{ env.IMAGE_NAME }}:${{ needs.resolve.outputs.version }}-php85
${{ env.IMAGE_NAME }}:${{ needs.resolve.outputs.version }}
${{ env.IMAGE_NAME }}:testing
cache-from: type=gha,scope=minimal-testing
cache-to: type=gha,mode=max,scope=minimal-testing
cache-from: type=gha,scope=minimal-testing-php85
cache-to: type=gha,mode=max,scope=minimal-testing-php85

- name: Configure AWS credentials (OIDC → customers account)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.IMAGE_PROMOTER_TESTING_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Promote — SSM /image-tag/testing = <VERSION>-testing
- name: Promote — SSM /image-tag/testing = <VERSION>-php85
env:
VERSION: ${{ steps.fetch.outputs.version }}
VERSION: ${{ needs.resolve.outputs.version }}
run: |
set -euo pipefail
aws ssm put-parameter --name "$SSM_PARAM" \
--value "${VERSION}-testing" --type String --overwrite \
--value "${VERSION}-php85" --type String --overwrite \
--region "$AWS_REGION"
echo "promoted $SSM_PARAM = ${VERSION}-testing" >> "$GITHUB_STEP_SUMMARY"
echo "promoted $SSM_PARAM = ${VERSION}-php85" >> "$GITHUB_STEP_SUMMARY"

# The full-stack image is built here for the same build-once reason as the
# minimal one: GA promotes this digest rather than rebuilding from the prod
# bundle. It is amd64-only, matching what the release build published before.
publish-full:
name: Publish simplerisk (full-stack) RC images
needs: resolve
environment: testing
runs-on: ubuntu-latest
timeout-minutes: 90
permissions:
contents: read
steps:
- name: Checkout (docker@testing)
uses: actions/checkout@v6

- name: Fetch + verify the testing bundle and schema
env:
VERSION: ${{ needs.resolve.outputs.version }}
run: |
set -euo pipefail
curl -fsSL -o /tmp/testing-bundle.tgz \
"https://bundles-test.simplerisk.com/simplerisk-${VERSION}.tgz"
EXPECTED_SHA=$(curl -fsSL "https://updates-test.simplerisk.com/releases.xml" \
| sed -n "/<release version=\"${VERSION}\">/,/<\/release>/p" \
| grep -oE '<bundle_sha256>[a-f0-9]{64}</bundle_sha256>' | head -1 | grep -oE '[a-f0-9]{64}')
if ! printf '%s' "$EXPECTED_SHA" | grep -qE '^[a-f0-9]{64}$'; then
echo "::error::no bundle_sha256 for $VERSION in updates-test releases.xml"; exit 1
fi
ACTUAL_SHA=$(sha256sum /tmp/testing-bundle.tgz | cut -d' ' -f1)
if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
echo "::error::bundle sha256 mismatch for $VERSION (expected $EXPECTED_SHA, got $ACTUAL_SHA)"; exit 1
fi
echo "bundle sha256 verified"
SQL_URL="https://raw.githubusercontent.com/simplerisk/database/master/simplerisk-en-${VERSION}.sql"
curl -fsSL -o /tmp/testing.sql "$SQL_URL" \
|| { echo "::error::release schema not found on database/master: $SQL_URL"; exit 1; }

- name: Assemble the testing build context
env:
VERSION: ${{ needs.resolve.outputs.version }}
run: |
set -euo pipefail
cd simplerisk
# Same context recipe as the minimal image: COPY the app + schema from
# the build context, with a real `ENV version=$VERSION`.
./generate_dockerfile.sh "$VERSION" context
tar xzf /tmp/testing-bundle.tgz -C .
cp /tmp/testing.sql common/simplerisk.sql
test -d simplerisk || { echo "::error::bundle did not extract a simplerisk/ dir"; exit 1; }

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}

- name: Build and push jammy — <VERSION>-jammy
uses: docker/build-push-action@v7
with:
context: simplerisk
file: simplerisk/Dockerfile
push: true
platforms: linux/amd64
build-args: ubuntu_version_code=jammy
tags: ${{ env.FULL_IMAGE_NAME }}:${{ needs.resolve.outputs.version }}-jammy
cache-from: type=gha,scope=full-testing-jammy
cache-to: type=gha,mode=max,scope=full-testing-jammy

- name: Build and push noble (default) — <VERSION>-noble + <VERSION> + :testing
uses: docker/build-push-action@v7
with:
context: simplerisk
file: simplerisk/Dockerfile
push: true
platforms: linux/amd64
build-args: ubuntu_version_code=noble
tags: |
${{ env.FULL_IMAGE_NAME }}:${{ needs.resolve.outputs.version }}-noble
${{ env.FULL_IMAGE_NAME }}:${{ needs.resolve.outputs.version }}
${{ env.FULL_IMAGE_NAME }}:testing
cache-from: type=gha,scope=full-testing-noble
cache-to: type=gha,mode=max,scope=full-testing-noble
2 changes: 1 addition & 1 deletion .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Shell script checkup with ShellCheck

on:
pull_request:
branches: [ master ]
branches: [ master, testing ]

permissions:
contents: read
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ If `TARGETARCH` is unset (plain `docker build` without buildx), the amd64 path r
The entrypoint script handles:
- Writing `config.php` by substituting env vars via `sed`
- Automatic database provisioning (`DB_SETUP=automatic|automatic-only|manual|delete`)
- Headless schema upgrade of an already-installed database (`DB_UPGRADE=automatic|automatic-only`) — runs SimpleRisk's core release-by-release upgrade (`run_database_upgrade_structured`) as the app DB user via `/db-upgrade.php`, emitting the structured per-release JSON to the log; `automatic-only` exits with the upgrade status (used by the EKS release upgrade Job)
- SSL certificate generation (minimal image generates a CA + signed cert; full-stack generates a self-signed cert)
- Cron setup (`SIMPLERISK_CRON_SETUP` in minimal; always-on in full-stack)
- Supervisor start (full-stack) or `apache2-foreground` (minimal)
Expand All @@ -94,6 +95,7 @@ The entrypoint script handles:
|---|---|
| `DB_SETUP` | `automatic`, `automatic-only`, `manual`, `delete` |
| `DB_SETUP_PASS` | Password used when setting up the DB |
| `DB_UPGRADE` | `automatic`, `automatic-only` — headless release-by-release **schema upgrade** of an existing DB (uses the `SIMPLERISK_DB_*` app creds, no privileged user; JSON result to the log; non-zero exit on failure) |
| `DB_SSL_ENABLED` | Opt-in, **default off**. Set to exactly `true` to add `--ssl-mode=REQUIRED --enable-cleartext-plugin` to the privileged setup/delete MySQL client (for databases that require the cleartext auth plugin to be sent over TLS). Any other value / unset ⇒ unchanged plaintext-capable connection. |
| `SIMPLERISK_DB_HOSTNAME` | External DB host |
| `SIMPLERISK_DB_USERNAME/PASSWORD/DATABASE` | DB credentials |
Expand Down
1 change: 1 addition & 0 deletions simplerisk-minimal/.testing-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20260820-001
1 change: 1 addition & 0 deletions simplerisk-minimal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ docker run -d --name simplerisk -e SIMPLERISK_DB_PASSWORD=pass -e SIMPLERISK_DB_
| `DB_SETUP_USER` | `root` | Used when `DB_SETUP=automatic\|automatic-only\|delete`. User name of database privileged user to install SimpleRisk schema and other components |
| `DB_SETUP_PASS` | `root` (the bundled `stack.yml` ships `simplerisk_setup`) | Used when `DB_SETUP=automatic\|automatic-only\|delete`. Password of the privileged MySQL user used **only** to install the SimpleRisk schema and create the app DB user. In `stack.yml` it is also the bundled MySQL root password; since that MySQL is not exposed outside the stack network, a documented default is used for the zero-config trial. Override it (and `MYSQL_ROOT_PASSWORD` in `stack.yml`) for any non-trial deployment. |
| `DB_SETUP_WAIT` | 20 | Used when `DB_SETUP=automatic\|automatic-only`. Time, in seconds, the application is going to wait to set up the database. Useful if you are deploying the database and SimpleRisk at the same time |
| `DB_UPGRADE` | `null` (Accepts any value) | Run SimpleRisk's release-by-release database **schema upgrade** against an already-installed database (as opposed to `DB_SETUP`, which installs a fresh one), then continue running (`automatic`) or stop the container (`automatic-only`, for a one-shot upgrade job). Uses the `SIMPLERISK_DB_*` application credentials — no privileged user. The structured per-release JSON result is written to the container log, and the container exits non-zero if the upgrade fails. Idempotent — a no-op when the schema is already current |
| `DB_SSL_ENABLED` | `false` (off) | Opt-in, used when `DB_SETUP=automatic\|automatic-only\|delete`. Set to exactly `true` to require TLS on the privileged setup/delete MySQL client connection (adds `--ssl-mode=REQUIRED --enable-cleartext-plugin`). Any other value, or unset, leaves the connection unchanged (plaintext-capable) |
| `SIMPLERISK_DB_HOSTNAME` | `localhost` | Hostname of the database server |
| `SIMPLERISK_DB_PORT` | 3306 | Port to contact the database |
Expand Down
23 changes: 23 additions & 0 deletions simplerisk-minimal/common/db-upgrade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* Headless database schema upgrade. Runs SimpleRisk's core release-by-release
* schema upgrade (run_database_upgrade_structured, defined in
* includes/upgrade.php) against the database, with no HTTP context and no
* credentials beyond the DB connection. Requiring upgrade.php pulls in the full
* app bootstrap (bootstrap.php loads config.php + functions.php and is
* PHP_SAPI==='cli' aware), so db_open() and the upgrade run standalone. Emits
* the structured per-release JSON result to stdout and exits 0 on success,
* 1 on failure.
*
* Triggered by the DB_UPGRADE entrypoint mode; runs against the instance's own
* database (the same SIMPLERISK_DB_* env as the serving container).
*/

require_once('/var/www/simplerisk/includes/upgrade.php');

$db = db_open();
$result = run_database_upgrade_structured($db);
db_close($db);

fwrite(STDOUT, json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
exit(!empty($result['success']) ? 0 : 1);
Loading