Files
bookstore/README.md
T
twooeyandClaude Sonnet 5 3876a6ba30 Fix DB password corruption via Docker secrets file mechanism
The reported bug (a generated password containing "$BRjTx3tlmSpz" got
silently blanked, breaking the DB connection) is the same Compose
interpolation issue as the earlier bcrypt hash, but this time in fields
that are genuinely user-chosen and can't just be avoided by convention.

Switched DB_PASSWORD/DB_ROOT_PASSWORD to Docker's official `_FILE`
secrets convention (MARIADB_PASSWORD_FILE / WORDPRESS_DB_PASSWORD_FILE),
backed by Compose's native `secrets:` mechanism: deploy.sh writes the raw
value to secrets/<env>/db_password, and the container reads that file
directly — the value never passes through Compose's ${VAR} interpolation
at all. Verified end-to-end with an actual `$`-containing password,
including a full deploy → backup → restore → still-serving round trip.

Also fixed along the way (found while actually testing, not assumed):
- Makefile never exported ENVIRONMENT, so `make up` alone (bypassing
  deploy.sh) would have left the new secrets path unresolved.
- deploy.sh chmod'd the secret files 600, unreadable by the container's
  own UID (www-data) — fixed to 644, relying on the containing directory
  (700) to keep other host users out instead.
- backup.sh/restore.sh still called `mysqldump`/`mysql`, which don't
  exist in the mariadb:11 image under those names — renamed to
  mariadb-dump/mariadb. (This means neither script had actually
  succeeded before now; both are verified working end-to-end here.)

The supplier/payment API keys remain passed the old way — nothing reads
them yet (bookstore-core is still a stub), so there's no live bug to fix
there; noted in .env.example that the same _FILE pattern should be used
once that code exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 11:36:29 -04:00

133 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bookstore
WordPress + WooCommerce bookstore. Design reference: see `docs/` for the
pre-launch plan and the technical design doc (`bookstore-core` schema,
interfaces, order state machine — the design doc is the source of truth for
*why* this repo is laid out the way it is).
This week's scope (Week 1 of the 5-week plan): environments, deploy
pipeline, and the WooCommerce/HPOS plugin scaffold. Catalog schema, pricing,
supplier adapters, and the order state machine are Week 2+ and live under
`wp-content/plugins/bookstore-core/includes/`, currently empty.
## Layout
```
docker-compose.yml base services: db, redis, wordpress, cron
docker-compose.{dev,staging,production}.yml per-environment overrides (caddy, mailhog)
docker/php/ app image: WP core image + wp-cli, composer, redis ext
docker/caddy/ one Caddyfile per environment
docker/cron/ Action Scheduler driver (system cron has no host to run on in Docker)
deploy/deploy.sh idempotent bring-up + WP/WooCommerce config
deploy/poll-deploy.sh host-crontab script: redeploys when its branch moves on Gitea
deploy/backup.sh, restore.sh off-host backup; restore is staging-only, on purpose
secrets/<env>/ DB passwords, written fresh by deploy.sh — gitignored, not manually edited
wp-content/plugins/bookstore-core/ the one plugin that owns business logic
wp-content/mu-plugins/ local-mail-catcher.php — routes mail to MailHog outside production
```
Same code runs in every environment; only `.env.<env>` and which
`docker-compose.<env>.yml` you layer in differ (design doc §01).
## Local development
```
cp .env.example .env.dev # fill in DB_PASSWORD etc.; localhost values are fine
make up ENV=dev
make deploy ENV=dev # installs WP, WooCommerce, activates bookstore-core, enables HPOS
```
Site is at `http://localhost:8080`. `make wp ENV=dev ARGS="plugin list"` runs
any wp-cli command; `make logs ENV=dev` tails everything; `make shell ENV=dev`
drops into the app container.
## Staging (this is what goes on your Docker server)
```
cp .env.example .env.staging
# fill in SITE_DOMAIN, SITE_URL, DB_*
make deploy ENV=staging
```
Staging is `noindex`'d (`blog_public=0` plus the `X-Robots-Tag` header in
Caddyfile.staging) so search engines won't index it — there's no basic-auth
wall on top of that, since this box is LAN-only and not reachable from
outside. If that ever changes (a public domain, port-forwarding, etc.),
basic-auth is worth adding back before that happens, not after. Mail never
leaves the box: it's caught by MailHog, viewable at `:8025`.
### Theme: Blocksy + the Book Store starter site
`deploy.sh` installs and activates the **Blocksy** theme and the free
**Blocksy Companion** plugin from wordpress.org automatically — nothing to
do here, in any environment.
The **Book Store starter site** itself is a paid Companion Pro template
(Business plan, $99/year — design doc Appendix A), so it can't be scripted
against a public API the way the free theme can. One-time manual step per
environment, in wp-admin:
1. Purchase/retrieve the Blocksy Business license key.
2. **Blocksy → General → License** → activate it.
3. **Blocksy → Extensions → Starter Sites** (Companion Pro) → import
**Book Store**.
After that, the starter site's content and Customizer settings persist in
the database like any other WordPress content — a redeploy or a fresh
`deploy.sh` run doesn't touch it or need to repeat it.
## Deploy pipeline
The Git server (Gitea) and the staging/production Docker hosts are separate
machines, so instead of a webhook receiver listening for an inbound POST
from Gitea, each box just polls its branch and redeploys when it moves —
no inbound port to expose or secure.
1. Clone this repo onto the box (e.g. into `/srv/bookstore`), `cp
.env.example .env.staging` (or `.env.production`) and fill it in.
2. Add a host crontab entry (not inside a container — `crontab -e` on the
box itself):
```
*/2 * * * * /srv/bookstore/deploy/poll-deploy.sh staging >> /var/log/bookstore-deploy.log 2>&1
```
(`production` + `main` on the production box.)
3. `poll-deploy.sh` fetches, compares against the last-deployed commit, and
— only if it moved — checks out the branch and runs `deploy.sh`. Silent
otherwise, so it's safe to run every couple of minutes.
4. The box needs its own git credentials to fetch from Gitea (a read-only
access token works well) — set those up once via a credential helper or
an embedded token in that box's `origin` remote.
You can also just run `./deploy/deploy.sh <env>` by hand at any time; the
poll script is only automation on top of the same idempotent script.
## Backups
```
./deploy/backup.sh staging # or production
```
Dumps the database and archives `wp-content/uploads`, gzips both, and syncs
to `$BACKUP_REMOTE` via `rclone` if set (configure `rclone config` on the
server first — this repo doesn't manage remote credentials). Before launch,
run a real restore drill:
```
./deploy/restore.sh ./backups/production/db-<ts>.sql.gz ./backups/production/uploads-<ts>.tar.gz
```
`restore.sh` only ever targets staging — there's no production argument, so
a restore drill can't accidentally overwrite the live site.
## What's deliberately not here yet
- Catalog tables, pricing engine, supplier adapters, order state machine —
Week 24, see the design doc.
- A real TLS-terminating public domain — Caddy will auto-provision certs
once `SITE_DOMAIN` points at a real host with 80/443 reachable; until
then `docker-compose.dev.yml` is the only one that works from
`localhost`.
- CI test/lint automation — nothing here runs tests yet because there's no
application code to test yet.