# 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 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.` and which `docker-compose..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_*, and: docker run --rm caddy:2-alpine caddy hash-password --plaintext 'pick-a-password' # -> paste result into STAGING_BASIC_AUTH_HASH make deploy ENV=staging ``` Staging is `noindex`'d and sits behind HTTP basic auth (Caddyfile.staging) in addition to `blog_public=0` — two independent reasons search engines and random visitors won't see it, per the launch gate. 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 ` 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-.sql.gz ./backups/production/uploads-.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 2–4, 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.