The Citadel kit
SQUIRE
Suits you up for the Citadel.
A squire carries the kit, readies the armour and gets the knight on the field. SQUIRE packs your app's Citadel deploy scripts, inspects them against the S-rules, and runs your app at home on the same image, proxy and MySQL that Citadel uses.
$ composer require waynestate/squire --dev
vendor/bin/squire init
The packing list for deploy day
One command packs the files Citadel needs, from templates, for raw PHP or Laravel. Each one is checked off in .squire.json with a SHA-256, and nothing you edited is ever repacked unless you say --force.
-
build.shCitadel's build hook. System packages only, POSIX sh, no Composer or npm (S1). -
deployments/001-first-release.shRuns once per app and calls the release with the smoke test off; Apache is not up yet. -
scripts/release.shThe idempotent release: install from the lock file, audit, build, migrate, PROCTOR as a report, smoke-test/up. Also your Quick Update post-script. -
health.phpCitadel's health check: one request to/up, never boots the framework. -
The parity kit 11 filesdocker-compose.squire.yml,.squire/,.env.squire.example,scripts/parity.shandscripts/parity.ps1: Citadel, on your machine.
5 of 5 packed, buckled for deploy day
Try the other commands
$ vendor/bin/squire init
created build.sh (written from the template)
created deployments/001-first-release.sh (written from the template)
created scripts/release.sh (written from the template)
created health.php (written from the template)
created docker-compose.squire.yml (written from the template)
created .squire/Dockerfile (written from the template)
created .squire/Dockerfile.dockerignore (written from the template)
created .squire/base-fallback.Dockerfile (written from the template)
created .squire/fallback/citadel-apache-entrypoint (written from the template)
created .squire/fallback/citadel-deploy (written from the template)
created .squire/haproxy.cfg (written from the template)
created .squire/php.ini (written from the template)
created .squire/vhost.conf (written from the template)
created scripts/parity.sh (written from the template)
created scripts/parity.ps1 (written from the template)
created .env.squire.example (written from the template)
Target: laravel. Wrote .squire.json.
Every shell script runs with set -euo pipefail, prints a [squire] ==> step N marker, and gives the same result when you run it twice. Pack half the bag with --only=release or --only=parity.
vendor/bin/squire doctor
The squire inspects the armour
Doctor reads your repository and checks the rules a machine can decide. It never runs your app and never touches a server. A fail exits 1; a warn or info doesn't. It is a lint, not a gate: wire it into CI if you want it to block.
fail exits 1 warn reported info noted
- S1
build.shruns no Composer or npm. - S3Every script has
set -e. - S4No download piped into a shell.
- S8No CRLF in shell scripts.
- S9Build, first release and release are three jobs.
- S12Install from the lock file; never update.
- S14The release runs
proctor check. - S15
auth.jsonstays out of git. - S16Laravel runs
optimize. - S17Migrations run and stay old-code friendly.
- S19Refuses to run as root.
- S20PROCTOR is a report, never a bare command.
- S21The release requests
/upat the end. - S22One marker per step.
- S35Locally, the Citadel PHP image.
- S36A forwarding proxy in front.
- S37Locally, MySQL 5.6.
- S41The Docker socket is not mounted.
- S42
.envfiles stay out of git. - S50Lock file, audit and MySQL 5.6 are PROCTOR's: run
proctor check. - S55The health check never boots the framework.
- S57npm installs with
--ignore-scripts. - S67Images pinned by digest.
- S70Containers drop capabilities.
- S71Citadel's health-check timing.
Every rule, its level and why it matters: BEST_PRACTICES.md. Drift from the templates is a warning under S9, never a failure; your copies are yours to edit.
$ vendor/bin/squire doctor
PASS S1 build.sh runs no composer or npm commands. [build.sh]
PASS S12 `composer install --no-dev` from the lock file.
PASS S14 The release script runs proctor check.
PASS S20 PROCTOR runs as a report and cannot stop the release.
PASS S55 health.php is an artisan-free health check. [health.php]
WARN S8 .gitattributes does not force LF for shell scripts. Add `*.sh text eol=lf`.
WARN S67 Not pinned by digest: citadel/php-apache:8.4r5. Run `scripts/parity.sh pin` and put the digests in .env.squire.
RESULT: pass (0 fail, 2 warn, 0 info, 5 pass)
Local parity
Practise on the real field
Your app on the Citadel PHP image, behind HA-Proxy adding the same forwarding headers, talking to the same MySQL 5.6. Its limits show up on your laptop, not on deploy day.
- Your browser on localhost
- HA-Proxy adds X-Forwarded-For and -Proto
-
Your app
Citadel PHP image, run as citadel -
MySQL 5.6.51
mysqlinside,127.0.0.1:3307outside
Docker with Compose v2.
cp .env.squire.example .env.squire # then set the passwords
scripts/parity.sh up # or: scripts/parity.sh up --fallback
Docker Desktop with the WSL 2 backend. PowerShell 5.1 or 7; no other shell needed.
Copy-Item .env.squire.example .env.squire
scripts\parity.ps1 up # or: scripts\parity.ps1 up -Fallback
Commands
updownresetlogsshellstatuspin
pin prints the digests of the images you pulled, so you can pin them. reset also removes the volumes.
Containers drop capabilities, set no-new-privileges and carry memory and process limits; the database listens on loopback only. The compose file refuses to start without real passwords.
No access to the real image? --fallback builds an approximation from the official PHP image plus Citadel's extensions. Good for a practice bout, not for proving parity.
Citadel setup
Onto the field
What lands on Citadel is the scripts SQUIRE packed; the package itself stays on your machine and in CI.
-
Point the dashboard at the kit
Build script
build.sh, health check["php","health.php"], Quick Update post-script./scripts/release.shas usercitadel. -
Commit the lock files
composer.lock, andpackage-lock.jsonorpnpm-lock.yamlif you have a frontend. The lock file is the release contract. -
Mark the scripts executable
Git on Windows forgets:
git update-index --chmod=+x build.sh scripts/*.sh deployments/*.sh -
Settings in the dashboard
Set
SQUIRE_*values in the dashboard environment, never in a file in the repository. All of them are optional.
0- pass
1- findings: drift for
init --check, a failed rule fordoctor 2- the tool itself could not run
The platform
Letters for the castle
A good squire carries messages too. SQUIRE's maintainers send platform improvement proposals to the platform team privately. Text only: SQUIRE never touches a server, and nothing in its own scripts waits on them.
Its place in WELD
Every knight rides with a party
SQUIRE is one part of WELD, the Wayne State application stack. It rides last: the others guard, sign in, render, check and test the app, and SQUIRE carries it onto the field.
HELMETsets security headers
SESAMEsigns the user inWayneUIrenders the page
PROCTORchecks the code
PREFLIGHTruns CI
SQUIREdeploys you are here
Also in the stack
The new five
BOOSTER gets you onto a supported PHP
ROLLCALL every app answers “present”
ADVISOR what applies, and what next
DRAWBRIDGE the Citadel sign-in gateway
SQUIRE the Citadel kit
Known limits in 0.1
Scuffs on the leather
SQUIRE 0.1 is unreleased, and honest about where it guessed. The full list, with the default each guess chose, is OPEN_QUESTIONS.md.
- The local
php.iniholds the safe defaults HELMET also enforces at runtime, not the platform's file. - The Citadel image has no registry digest to pin yet; doctor warns under S67 until you run
pin. - A full Update may not refresh
vendor/. Usescripts/release.shas the Quick Update post-script too. - PROCTOR can report during a release, but cannot act as a deploy gate yet.