Skip to content

Development and releases

Arkazzae edited this page Oct 7, 2026 · 1 revision

This page is for people who work on SafeHouse itself: setting up the development environment, how the code is organised, how to add a module, and how a release is made. The README holds the short version and the rules every change must follow.

Development environment

You need Docker. You do not need PHP or Composer on your machine.

cd dev && docker compose up -d && ./setup.sh

This starts WordPress with WooCommerce and Wordfence at http://localhost:8894 (user admin, password admin), with the plugin mounted from plugin/. Mailpit catches every e-mail at http://localhost:8025.

Command What it does
./dev/wp.sh shouse status Runs WP-CLI inside the container
./dev/lint.sh PHPCS (WordPress coding standards) and PHPStan, in the composer:2 image. --fix fixes what PHPCS can.
./dev/smoke.sh HTTP and WP-CLI regression checks against the dev site
./dev/i18n.sh Regenerates shouse.pot, merges it into every .po, and compiles .mo and .l10n.php

Other test environments:

  • dev/ols/: OpenLiteSpeed with Redis, for the page cache, object cache, login limits and Cloudflare tests
  • dev/update-test.sh: signed updates end to end, on an isolated site with a throwaway key
  • dev/package-test.sh: install and uninstall from a release ZIP
  • dev/updates-test.sh and dev/ingest-test.sh: the update host Workers against a local storage bucket
  • dev/cve-watch-test.sh: the vulnerability watch issue logic against a mock GitHub API

How the code is organised

Path Holds
plugin/shouse.php Plugin header, PHP version check, autoloader
plugin/src/Plugin.php Boot order and the list of modules
plugin/src/Core/ Settings, safe mode, activity log, alerts, visitor address, updater, WP-CLI, Turnstile, object cache commands, content-change dispatcher
plugin/src/Modules/ One class per module, plus their WP-CLI command classes
plugin/src/ObjectCache/ The Redis object cache drop-in and its loader
plugin/src/Admin/Page.php The settings page
plugin/languages/ Translations
updates/ The update host Worker, and updates/ingest/, the token-protected Worker that accepts release files and vulnerability data
site/ The landing page
dev/ Docker environments, tests and release tools

Release ZIPs are built from plugin/ only.

Add a module

A module is a class in plugin/src/Modules/ that extends SafeHouse\Core\AbstractModule. The settings page, the switch, WP-CLI status and SHOUSE_MODULES all work from the class alone.

  1. Implement the required methods:

    Method Returns
    id() The stable ID used as the settings key and in WP-CLI
    label(), description() Translated name and one-paragraph description
    defaults() Field defaults; must not call translation functions, because it runs before init
    fields() The field schema: type (toggle, text, email_list, textarea, html, code, number, select), label, and optional help, options, min, max, max_length
    boot() Registers hooks; called on plugins_loaded only when the module is enabled and available
  2. Override what the module needs:

    Method Use
    default_enabled() Return true to have the module on before anyone saves a choice
    available(), unavailable_reason() When the module cannot run on a site, and why
    coverage() field => provider name for features another plugin already provides; those fields step aside
    tasks(), handle_task() Buttons in the module's Details; capability and nonce are checked for you
    render_panel() Read-only status under the fields; escape all output
    settings_saved() React to a save, whether or not the module is enabled
  3. Add the class to Plugin::MODULES in plugin/src/Plugin.php. The order there is the order on the settings page.

  4. If the module replaces other plugins, add their slugs to PluginHealth::REPLACEABLE, and update GROUPS in design/hero/index.html (see its README).

  5. Run ./dev/i18n.sh for the new strings, then ./dev/lint.sh.

  6. Document the module in the README, plugin/readme.txt and this wiki.

Release process

Releases are made by maintainers.

  1. Add a ## x.y.z section to CHANGELOG.md. WordPress shows it in the update's View details window.
  2. On a clean main, run ./dev/release.sh x.y.z. It sets the version in plugin/shouse.php and plugin/readme.txt, runs the linters, commits "Release x.y.z" and creates the tag vx.y.z.
  3. Push: git push origin main vx.y.z.
  4. The Release workflow lints and test-builds without secrets, then waits for a maintainer to approve the release environment. After approval it builds and signs the release from the tag and publishes the same three files to the update host and as a GitHub release.

Without GitHub Actions, a maintainer with the release key can run ./dev/release.sh build x.y.z && ./dev/publish.sh x.y.z.

Before each release, also run ./dev/cloudflare-ips.sh. It refreshes the Cloudflare address ranges built into the plugin, which sites receive only through updates.

How sites verify releases, and how anyone can rebuild one, is on Signed updates and Verify a download.

Report a vulnerability

Report vulnerabilities privately: on the repository's Security tab, select Report a vulnerability. Do not open a public issue.

Related pages

Clone this wiki locally