shadowdaoandClaude Opus 5 da16faaff5 feat(cac-lsphp): guarantee $_SERVER path parity via a PHP extension
A site moved from cac-fpm to cac-lsphp must see byte-identical
$_SERVER['DOCUMENT_ROOT'] and ['SCRIPT_FILENAME'] (/home/<user>/...).
The auto_prepend_file normaliser that did this was PHP_INI_PERDIR, so
any site with its own .user.ini auto_prepend_file silently displaced it
— the state 7 live shared_ols sites (Wordfence, cPanel imports) are
actually in. Hardening the hook was not an option either: making our
prepend win would have disabled those Wordfence WAFs.

Replace it with cac_path_parity, a small PHP extension that rewrites the
filesystem-path $_SERVER keys from RINIT. RINIT cannot be displaced by
.user.ini, and it occupies no userland hook, so the customer's own
auto_prepend_file stays the only prepend in play and keeps working. The
mapping lives in two PHP_INI_SYSTEM settings, which .user.ini (PERDIR /
USER only) and ini_set() cannot reach.

Mechanism is a path-component-bounded string prefix swap, not realpath():
byte-identical to cac-fpm by construction (realpath would resolve a
customer's own symlinked public_html to some third path), no syscall, and
no failure path. Every guard fails open and leaves $_SERVER untouched;
nothing here can warn, throw or 500 a site. Unconfigured it is fully
inert, so cac-fpm and cac-litespeed are unaffected.

Built in a separate Dockerfile stage keyed off the existing ARG PHPVER —
gcc/phpize/headers never reach the shipped image (verified absent; the
image grows ~155kB), and a base-image PHP bump recompiles with no human
step. A `lsphp -i | grep` assertion fails the build if the .so does not
load, so an image can never ship having silently lost parity.

The entrypoint selects the extension when present and removes any stale
prepend ini left by an older image; if the extension is somehow not
loadable it falls back to the old normaliser and logs a WARNING rather
than losing normalisation entirely. It also now logs the active parity
mode, and warns when lsphp reports no ini scan dir (previously silent).

Probe lsphp with `-i` only: it is the LSAPI SAPI, not the CLI, and
answers `-m`/`-r` by printing usage and exiting 0 — a `lsphp -m | grep`
check never matches and never errors, which is the exact class of silent
always-false assertion this change exists to remove.

Verified: 6 .phpt tests; tests/fpm-parity-check.sh proves under the FPM
SAPI that with a customer .user.ini auto_prepend_file present both keys
are still corrected AND the customer's prepend still runs, and that the
old mechanism does not; and in a real built cac-lsphp:php83 container
that SCRIPT_FILENAME is rewritten, the customer prepend still fires, and
another tenant's path is left untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 11:38:19 -07:00
2025-07-28 07:29:03 -07:00

Cloud Apache Container

This is a base container for running PHP-based applications, supporting multiple PHP versions (7.4, 8.0, 8.1, 8.2, 8.3, 8.4). The default is PHP 8.3. The container is based on AlmaLinux 9 and uses Apache with mod_ssl. It is designed for both development and production use.

You must have Docker or compatible containerization software running.


What's New?

  • Optimized Image: The Dockerfile has been refactored for smaller size, faster builds, and improved security. Unnecessary files and caches are removed during build.
  • Pre-built Images for Each PHP Version: On every push, images for all supported PHP versions are built and pushed to the registry. You can pull the exact version you need (e.g., cac:php74, cac:php84, or cac:latest).
  • .dockerignore Added: The build context is now minimized, making builds faster and more secure.

Quick Start: Local Development with local-dev.sh

The easiest way to start a local development environment is with the provided local-dev.sh script. This script automates container setup, volume creation, log directories, and WordPress installation.

Usage Example

./local-dev.sh -n local-dev

Flags:

  • -n Name of the container (required)
  • -p HTTP port (default: 80)
  • -s HTTPS port (default: 443)
  • -r Root path for files and database (default: current directory)
  • -a PHP version (default: 8.3; options: 74, 80, 81, 82, 83, 84)
  • -v Enable verbose mode
  • -h Show help

The script will:

  • Create a user directory and log folders
  • Create a Docker volume for MySQL
  • Start the container with the correct environment variables
  • Generate helper scripts in your root path:
    • instance_start Start the container
    • instance_stop Stop the container
    • instance_logs Tail Apache logs
    • instance_db_info Show MySQL credentials
  • Install WordPress in your web root
  • Print MySQL credentials

Manual Docker Usage

You can also run the container manually:

mkdir -p local-development/domain.tld
cd local-development/domain.tld
mkdir user
mkdir -p user/logs/{apache,system}
docker run -d -it -p 80:80 -p 443:443 -e PHPVER=84 -e environment=DEV --mount type=bind,source="$(pwd)"/user,target=/home/myuser -v"$name-mysql":/var/lib/mysql -e uid=30001 -e user=myuser -e domain=localhost --name local-dev  repo.anhonesthost.net/cloud-hosting-platform/cac:latest

Accessing the Container

docker exec -it local-dev /bin/bash

WordPress Installation

If using local-dev.sh, WordPress is installed automatically. For manual setup:

cat /home/myuser/mysql_creds
su - myuser
cd ~/public_html
wp core download

Then visit https://localhost (accept the SSL warning) to complete setup.


Features

  • Multiple PHP Versions: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 (set with PHPVER or -a flag)
  • Pre-built Images: Pull the image for your desired PHP version directly from the registry. No need to build locally unless customizing.
  • Optimized Build: Smaller, faster, and more secure images thanks to the improved Dockerfile and .dockerignore.
  • Automatic Database Setup: MariaDB is started in DEV mode, credentials are auto-generated and stored in /home/$user/mysql_creds.
  • Database Backups: Cron job backs up the database every 15 minutes to /home/$user/_db_backups.
  • Log Management: Log rotation compresses logs older than 3 days and deletes those older than 7 days.
  • Memcached: Started automatically in DEV mode.
  • SSL: Self-signed certificate enabled by default.
  • Default Web Content: /home/$user/public_html is the web root. /ping endpoint and phpinfo.php are available for diagnostics.
  • Helper Scripts: instance_start, instance_stop, instance_logs, instance_db_info (created by local-dev.sh).

Environment Variables

Required:

  • uid User ID for file permissions
  • user Username for file permissions
  • domain Primary domain for configuration

Optional:

  • environment Set to DEV to start memcached and MySQL locally for development
  • serveralias Comma-separated list of alternative hostnames
  • PHPVER PHP version (see above)

Helpful Notes

  • To restart the instance: ./instance_start or docker start {container-name}
  • To stop: ./instance_stop or docker stop {container-name}
  • To view logs: ./instance_logs or docker logs -f {container-name}
  • To get DB credentials: ./instance_db_info or cat /home/$user/mysql_creds
  • To delete a container: docker rm {container-name} (does not delete user files or DB volume)
  • To view running containers: docker ps
  • To view all containers: docker ps --all
  • To view images: docker images

Troubleshooting

  • The first run may take several minutes as dependencies are installed.
  • If you need to change PHP version, stop and remove the container, then recreate with the desired version.
  • For advanced configuration, see the scripts in the scripts/ directory.
  • The image is optimized for size and speed, but local development in DEV mode may install additional packages (MariaDB, memcached) at runtime using microdnf.
  • The build context is minimized by the included .dockerignore file.
S
Description
No description provided
Readme
549 KiB
Languages
Shell 76.5%
C 8.6%
Go Template 8.2%
PHP 5.1%
Dockerfile 1.2%
Other 0.4%