feat: add wordpress site variant

This commit is contained in:
bisco
2026-06-25 07:36:25 +02:00
parent cae9180bc6
commit c66dd7e511
46 changed files with 2091 additions and 136 deletions
+58
View File
@@ -0,0 +1,58 @@
# ADR-0001: WordPress single-page architecture
Date: 2026-06-24
Status: Accepted
## Context
Azione!Lab needs a warm editorial single page whose primary content can be edited by
non-developers. This branch explicitly explores WordPress as an alternative product
architecture and starts independently from `main`.
## Decision
Use pinned WordPress 7.0/PHP 8.3 with MariaDB 11.8 LTS. Build a dependency-free custom
classic theme for exact semantic markup and responsive design. Store homepage fields as
sanitized theme modifications, while a must-use plugin owns Shows and Gallery custom
post types so structured content is not lost when changing themes.
NGINX is the only public entry point. WP-CLI provides an idempotent opt-in bootstrap;
Certbot provides opt-in direct TLS. Docker networks isolate the database and functional
tests use separate volumes.
## Consequences
- Editors use familiar WordPress screens without a page builder or required plugin.
- WordPress renders the page directly; there is no headless API/frontend deployment.
- WordPress and MariaDB replace Astro, Wagtail, Django, and PostgreSQL in this branch.
- Core/theme/plugin patches require an image rebuild in production.
- Fixed participation cards remain code until editorial requirements justify another
structured content type.
## Alternatives considered
- A block theme was not selected because the first version prioritizes controlled
markup and a compact implementation over full-site editing.
- A commercial/custom-fields plugin was rejected as an unnecessary dependency.
- A headless WordPress frontend was rejected because it would recreate the operational
complexity this variant is intended to remove.
## Security impact
The architecture inherits WordPress's public CMS attack surface. The implementation
reduces it with network isolation, no direct application port, disabled file editing
and XML-RPC, production immutability, sanitization/escaping, rate limiting, security
headers, and fail-closed TLS. Admin MFA and allowlisting remain external controls.
## Operational impact
Database and WordPress files are coordinated state and must be backed up together.
Operators must monitor upstream security releases and rebuild pinned images. SMTP and
off-host storage are not part of this minimal base.
## Rollback
Revert the implementation commit and rebuild. Preserve WordPress, database, and
certificate volumes unless data deletion is intentional. Restore data only from a
verified coordinated backup.
+19 -9
View File
@@ -1,13 +1,23 @@
# Architecture
Describe the project architecture here.
NGINX is the only public entry point for `azionelab.org`. It proxies HTTP to the
official WordPress 7.0/PHP 8.3 Apache image over the private `web` network. WordPress
connects to MariaDB 11.8 LTS over a separate internal `data` network. Neither WordPress
nor MariaDB publishes a host port.
Include:
The custom `azionelab` classic theme renders the public single page. Theme modifications
store the hero, manifesto, laboratory, teacher, lesson, and contact fields. The
`azionelab-content` must-use plugin registers Shows and Gallery custom post types so
structured editorial content survives a theme change. Images use WordPress featured
images with local SVG fallbacks.
- main components;
- runtime dependencies;
- data flow;
- persistence;
- external integrations;
- deployment topology;
- relevant ADRs.
WP-CLI is an opt-in tools-profile service. Its idempotent bootstrap installs WordPress,
activates the theme, configures the site, and creates realistic demo content. Certbot is
another optional service, enabled only for direct deployments. It shares challenge and
certificate volumes with NGINX but has no container-control access.
Persistent state lives in `db_data`, `wordpress_data`, and the certificate volumes.
Functional tests replace the first two with isolated test volumes and reach NGINX via
an internal `azionelab.org` network alias.
See [ADR-0001](adr/0001-wordpress-single-page.md).
+37 -11
View File
@@ -1,15 +1,41 @@
# Deployment
Describe how this project is deployed.
## Local
Include:
Copy `.env.example` to `.env`, add `127.0.0.1 azionelab.org` to the hosts file, then:
- environments;
- Docker/Compose usage;
- required configuration;
- secrets handling;
- exposed ports;
- volumes;
- networks;
- deployment commands;
- rollback procedure.
```bash
docker compose up --build -d
docker compose --profile tools run --rm wp-cli /scripts/bootstrap.sh
```
NGINX binds to loopback ports 8080/8443. WordPress and MariaDB remain private. The
bootstrap is safe to rerun and does not duplicate demo records.
## Production
Required controls:
- `WP_ENVIRONMENT_TYPE=production`;
- unique non-placeholder MariaDB passwords of at least 16 characters;
- `WORDPRESS_DEBUG=0` and a strong administrator password;
- an HTTPS `WP_URL`;
- managed secrets outside the Compose file;
- either direct Let's Encrypt termination or a trusted external load balancer;
- off-host database/file backups and monitoring.
When a load balancer terminates TLS, Certbot stays disabled. `TRUST_PROXY_HEADERS=1`
is safe only when firewall/network policy makes the load balancer the sole NGINX
caller and it overwrites `X-Forwarded-*` headers.
For direct TLS, public DNS and inbound ports 80/443 must reach NGINX. Start with
`LETSENCRYPT_STAGING=1`; application HTTP returns 503 until a certificate exists, then
redirects to HTTPS. Switch to the production CA only after validating DNS and firewall
behavior, removing only the staging certificate volume when necessary.
## State and rollback
Database and WordPress file volumes must be backed up together. Code rollback is a
container rebuild from a prior commit and does not require deleting volumes. Never use
`docker compose down --volumes` where content must survive. Test database restoration
in a disposable environment before any production restore.
+37 -9
View File
@@ -1,13 +1,41 @@
# Operations
Describe operational procedures.
## Routine commands
Include:
```bash
docker compose up --build -d
docker compose ps
docker compose logs -f proxy wordpress db certbot
docker compose --profile tools run --rm wp-cli -c 'wp core version'
docker compose down
```
- startup and shutdown;
- health checks;
- logs;
- monitoring;
- backup and restore;
- routine maintenance;
- known operational risks.
The proxy healthcheck reaches WordPress, and WordPress health reaches its login route.
MariaDB uses its official readiness check. WordPress access logs are disabled while
PHP warnings/errors remain visible. NGINX is the request audit trail; apply an explicit
retention policy because it contains client metadata.
## Updates
WordPress, PHP, MariaDB, NGINX, Certbot, and Playwright use explicit image versions.
Review security releases routinely, update pins in a task branch, rebuild, run the full
test suite, and deploy. Production disables WordPress web-based file modifications, so
image rebuilds are the update path.
## Backup and restore
Create database and `wordpress_data` backups in one maintenance window. Backups contain
credentials, accounts, contact information, and uploaded media; encrypt them, restrict
access, set retention, and store copies off-host.
A restore is destructive. Validate it on isolated volumes, then stop WordPress, restore
the database and file volume together, restart, and verify the homepage, media,
`/wp-admin/`, and user accounts.
## Known risks
- Local named volumes are not backups.
- SMTP is not configured; WordPress password-reset email needs an external mail service.
- Admin MFA and network allowlisting are deployment concerns and are not bundled.
- WordPress plugins expand the attack surface; install only reviewed, maintained,
necessary plugins.
+21 -10
View File
@@ -1,19 +1,30 @@
# Runbook
Operational runbook for this project.
## Installation screen remains visible
## Common tasks
1. Check `docker compose ps` for healthy database and WordPress services.
2. Run `docker compose --profile tools run --rm wp-cli /scripts/bootstrap.sh`.
3. Inspect `docker compose logs wordpress db` without printing secret values.
Document routine operational tasks here.
## NGINX returns 502
## Troubleshooting
1. Run `docker compose ps` and `docker compose exec proxy nginx -t`.
2. Check the WordPress health status and `docker compose logs proxy wordpress`.
3. Confirm the request host is exactly `azionelab.org`; unknown hosts return 404.
Document known issues, symptoms, checks, and remediation steps.
## Images or theme are missing
1. Confirm `wordpress_data` is mounted in both WordPress and WP-CLI.
2. Run `docker compose --profile tools run --rm wp-cli -c 'wp theme status azionelab'`.
3. Verify file ownership before changing permissions; never make the tree world-writable.
## Certificate issuance fails
Verify public DNS, inbound port 80, the operator email, and staging mode. Request a
missing `/.well-known/acme-challenge/` path: an NGINX 404 confirms the route is yours.
Avoid repeated production-CA retries while debugging.
## Rollback
Document rollback procedures here.
## Emergency contacts
Document project-specific escalation paths if appropriate.
Revert the deployment commit and rebuild while preserving all named volumes. Restore
database/files only for a data rollback and only from a verified coordinated backup.
+29 -13
View File
@@ -1,16 +1,32 @@
# Security
Describe security assumptions and controls.
- NGINX is the only published service. MariaDB is restricted to an internal network;
WordPress is reachable only from NGINX and the database network.
- Production startup rejects placeholder or short database passwords. `.env` is
ignored and real secrets must come from the deployment secret manager.
- WordPress uses its normal capability, nonce, authentication, cookie, and password
controls. Theme settings sanitize input and templates escape output.
- File editing is always disabled. Production also disables web-based core, theme, and
plugin changes; patched images are rebuilt and redeployed instead.
- XML-RPC and comments are disabled. NGINX blocks PHP execution below uploads, dotfiles,
and direct `wp-config.php` requests, and rate-limits login/public requests. Public
REST user enumeration and author archives are disabled.
- Security headers include CSP, same-origin framing, content-type protection, a strict
referrer policy, and a restrictive Permissions Policy. WordPress compatibility still
requires inline style/script CSP allowances; do not treat this CSP as an XSS sanitizer.
- Forwarded client/protocol headers are overwritten by default. Trust mode is permitted
only behind a restricted load balancer that overwrites those headers.
- Direct TLS is fail-closed until a certificate exists. Certbot has a read-only root,
no Docker socket, dropped capabilities, and narrowly scoped volumes.
- Containers are not privileged and do not use host networking or the Docker socket.
WordPress/Apache retains the capabilities needed by the official image internally,
but no WordPress port is published.
- WordPress/Apache access logs are disabled to avoid duplicate client metadata and
healthcheck noise; NGINX remains the single request log and PHP warnings/errors stay
visible.
- Admin MFA, an IP/VPN allowlist, SMTP, malware scanning, log aggregation, and a secret
manager remain production operator responsibilities.
- Backups and uploads can contain personal data. Encrypt, restrict, retain, and delete
them according to the applicable privacy policy.
Include:
- authentication;
- authorization;
- network exposure;
- TLS/certificates;
- secrets management;
- logging of sensitive data;
- container privileges;
- filesystem permissions;
- dependency management;
- relevant ADRs.
See [ADR-0001](adr/0001-wordpress-single-page.md).
+17 -16
View File
@@ -1,23 +1,24 @@
# Testing
Describe how tests are executed.
All tests should run inside Docker containers.
## Canonical test command
All checks run inside Docker containers.
```bash
CHANGE_ME
docker compose run --rm --no-deps wordpress php -l /opt/azionelab/theme/functions.php
docker compose run --rm --no-deps wordpress php -l /opt/azionelab/theme/front-page.php
docker compose run --rm --no-deps wordpress php -l /opt/azionelab/azionelab-content.php
docker compose run --rm --no-deps proxy nginx -t
docker compose -f docker-compose.yml -f docker-compose.test.yml up --build -d db wordpress proxy
docker compose -f docker-compose.yml -f docker-compose.test.yml --profile tools run --rm wp-cli /scripts/bootstrap.sh
docker compose -f docker-compose.yml -f docker-compose.test.yml --profile test run --build --rm functional-tests
docker compose config --quiet
LETSENCRYPT_ENABLED=1 docker compose config --quiet
```
## Test categories
The override replaces normal database and WordPress volumes, maps `azionelab.org` to
the internal proxy, and never publishes an extra port. Browser tests cover content and
section order, contact actions, mobile overflow/navigation, semantic landmarks, image
alternatives, admin routing, security headers, blocked sensitive routes, and unknown
virtual hosts.
Describe applicable categories:
- unit tests;
- integration tests;
- linting;
- formatting checks;
- Ansible syntax checks;
- Docker/Compose validation;
- smoke tests.
Subjective visual review and a real-device accessibility audit remain manual release
checks.