From c9d637c907f9ad817686c89b7f3075cff3781334 Mon Sep 17 00:00:00 2001 From: Twooey Date: Thu, 27 Aug 2026 09:50:18 -0400 Subject: [PATCH] Week 1 infrastructure: Docker environments, deploy pipeline, bookstore-core scaffold Docker Compose environments for dev/staging/production (MariaDB, Redis, Caddy, Action Scheduler cron sidecar), an idempotent deploy script, git-hook-based deploy pipeline, backup/restore scripts, and the bookstore-core plugin stub with WooCommerce HPOS compatibility declared. Co-Authored-By: Claude Sonnet 5 --- .env.example | 48 +++++++ .gitignore | 9 ++ Makefile | 29 +++++ README.md | 122 ++++++++++++++++++ deploy/backup.sh | 41 ++++++ deploy/deploy.sh | 76 +++++++++++ deploy/hooks/post-receive | 26 ++++ deploy/restore.sh | 33 +++++ docker-compose.dev.yml | 29 +++++ docker-compose.production.yml | 32 +++++ docker-compose.staging.yml | 40 ++++++ docker-compose.yml | 95 ++++++++++++++ docker/caddy/Caddyfile.dev | 10 ++ docker/caddy/Caddyfile.production | 22 ++++ docker/caddy/Caddyfile.staging | 22 ++++ docker/cron/entrypoint.sh | 14 ++ docker/php/Dockerfile | 23 ++++ docker/php/php.ini | 13 ++ ...okstore_prelaunch_implementation_plan.docx | Bin 0 -> 50532 bytes wp-content/mu-plugins/local-mail-catcher.php | 22 ++++ .../plugins/bookstore-core/bookstore-core.php | 38 ++++++ .../plugins/bookstore-core/composer.json | 13 ++ .../plugins/bookstore-core/includes/.gitkeep | 0 23 files changed, 757 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100755 deploy/backup.sh create mode 100755 deploy/deploy.sh create mode 100755 deploy/hooks/post-receive create mode 100755 deploy/restore.sh create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.production.yml create mode 100644 docker-compose.staging.yml create mode 100644 docker-compose.yml create mode 100644 docker/caddy/Caddyfile.dev create mode 100644 docker/caddy/Caddyfile.production create mode 100644 docker/caddy/Caddyfile.staging create mode 100755 docker/cron/entrypoint.sh create mode 100644 docker/php/Dockerfile create mode 100644 docker/php/php.ini create mode 100644 docs/online_bookstore_prelaunch_implementation_plan.docx create mode 100644 wp-content/mu-plugins/local-mail-catcher.php create mode 100644 wp-content/plugins/bookstore-core/bookstore-core.php create mode 100644 wp-content/plugins/bookstore-core/composer.json create mode 100644 wp-content/plugins/bookstore-core/includes/.gitkeep diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2502ac4 --- /dev/null +++ b/.env.example @@ -0,0 +1,48 @@ +# Copy this to .env.dev / .env.staging / .env.production and fill in real +# values for that environment. The filled-in files are gitignored — never +# commit them. Same code everywhere; only these values differ (see +# design doc §01, Environments & deployment). + +# --- Site --- +# (WP_ENVIRONMENT_TYPE is NOT set here — it's hardcoded per environment in +# docker-compose.{dev,staging,production}.yml so it can't go stale.) +SITE_DOMAIN=staging.example.com +SITE_URL=https://staging.example.com +SITE_TITLE="Bookstore (staging)" + +# --- WordPress admin bootstrap (used only on first install) --- +WP_ADMIN_USER=admin +WP_ADMIN_PASSWORD=changeme +WP_ADMIN_EMAIL=admin@example.com + +# --- Database --- +DB_NAME=bookstore +DB_USER=bookstore +DB_PASSWORD=changeme +DB_ROOT_PASSWORD=changeme +WP_TABLE_PREFIX=wp_ + +# --- Staging only: basic-auth wall + noindex --- +# Generate the hash with: +# docker run --rm caddy:2-alpine caddy hash-password --plaintext 'your-password' +STAGING_BASIC_AUTH_USER=staging +STAGING_BASIC_AUTH_HASH= + +# --- Backups (deploy/backup.sh) --- +BACKUP_DIR=./backups +BACKUP_REMOTE= +BACKUP_RETENTION_DAYS=14 + +# --- Supplier adapters (design doc §04) --- +# staging: gutenberg_test production: booksrun,ingram +ACTIVE_SUPPLIER_ADAPTERS=gutenberg_test +BOOKSRUN_API_KEY= +INGRAM_API_KEY= +INGRAM_ACCOUNT_ID= + +# --- Payments (Helcim) --- +HELCIM_API_TOKEN= +HELCIM_ACCOUNT_ID= + +# --- Marketing --- +MAILERLITE_API_KEY= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c4fc3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.env +/.env.* +!/.env.example + +/backups/ +vendor/ +node_modules/ +*.log +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..76c39b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +ENV ?= dev +COMPOSE = docker compose -f docker-compose.yml -f docker-compose.$(ENV).yml --env-file .env.$(ENV) + +.PHONY: up down ps logs shell wp deploy backup + +up: + $(COMPOSE) up -d --build + +down: + $(COMPOSE) down + +ps: + $(COMPOSE) ps + +logs: + $(COMPOSE) logs -f + +shell: + $(COMPOSE) exec wordpress bash + +# make wp ENV=staging ARGS="plugin list" +wp: + $(COMPOSE) exec wordpress wp $(ARGS) --allow-root + +deploy: + ./deploy/deploy.sh $(ENV) + +backup: + ./deploy/backup.sh $(ENV) diff --git a/README.md b/README.md new file mode 100644 index 0000000..abd82b9 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# 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/hooks/post-receive git-server hook: push to `staging`/`main` deploys +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 + +This assumes your own bare Git server, not a hosted CI. The mechanism: + +1. A bare repo lives on the server (e.g. `/srv/git/bookstore.git`). +2. `deploy/hooks/post-receive` is copied into `.git/hooks/` and + made executable. Edit `DEPLOY_ROOT` at the top of it first. +3. Pushing to `staging` checks that branch out into + `$DEPLOY_ROOT/staging` and runs `deploy.sh staging`; pushing to `main` + deploys `$DEPLOY_ROOT/production` the same way. +4. `deploy.sh` is idempotent — it's safe to re-run and safe to be the thing + the hook calls on every push. + +You can also just run `./deploy/deploy.sh ` by hand from a checked-out +copy on the server; the hook is only automation on top of the same 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. diff --git a/deploy/backup.sh b/deploy/backup.sh new file mode 100755 index 0000000..6c49165 --- /dev/null +++ b/deploy/backup.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Off-host database + uploads backup (design doc §01, launch gate: "A full +# backup has been restored successfully into staging" — see restore.sh). +set -euo pipefail + +ENVIRONMENT="${1:?Usage: backup.sh }" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +ENV_FILE=".env.${ENVIRONMENT}" +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +TIMESTAMP="$(date +%Y%m%d-%H%M%S)" +BACKUP_DIR="${BACKUP_DIR:-./backups}/${ENVIRONMENT}" +mkdir -p "$BACKUP_DIR" + +COMPOSE="docker compose -f docker-compose.yml -f docker-compose.${ENVIRONMENT}.yml --env-file ${ENV_FILE}" + +echo "==> dumping database" +$COMPOSE exec -T db sh -c "exec mysqldump -u\"\$MARIADB_USER\" -p\"\$MARIADB_PASSWORD\" \"\$MARIADB_DATABASE\"" \ + | gzip > "$BACKUP_DIR/db-${TIMESTAMP}.sql.gz" + +echo "==> archiving uploads" +$COMPOSE run --rm -T wordpress tar -czf - -C /var/www/html/wp-content uploads \ + > "$BACKUP_DIR/uploads-${TIMESTAMP}.tar.gz" + +if [[ -n "${BACKUP_REMOTE:-}" ]]; then + echo "==> syncing to off-host storage ($BACKUP_REMOTE)" + rclone copy "$BACKUP_DIR/db-${TIMESTAMP}.sql.gz" "$BACKUP_REMOTE/${ENVIRONMENT}/" + rclone copy "$BACKUP_DIR/uploads-${TIMESTAMP}.tar.gz" "$BACKUP_REMOTE/${ENVIRONMENT}/" +else + echo "==> BACKUP_REMOTE not set — backup stayed local only; configure rclone before launch" +fi + +echo "==> pruning local backups older than ${BACKUP_RETENTION_DAYS:-14} days" +find "$BACKUP_DIR" -type f -mtime "+${BACKUP_RETENTION_DAYS:-14}" -delete + +echo "==> backup complete: $BACKUP_DIR" diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..5bbbcb0 --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Builds and brings up an environment, then makes sure WordPress/WooCommerce +# are installed and configured to match design doc §01/§03/§07. Safe to run +# repeatedly — every step is idempotent. +set -euo pipefail + +ENVIRONMENT="${1:?Usage: deploy.sh }" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +case "$ENVIRONMENT" in + dev|staging|production) ;; + *) echo "environment must be 'dev', 'staging', or 'production'" >&2; exit 1 ;; +esac + +ENV_FILE=".env.${ENVIRONMENT}" +if [[ ! -f "$ENV_FILE" ]]; then + echo "missing $ENV_FILE — copy .env.example to $ENV_FILE and fill it in" >&2 + exit 1 +fi +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +COMPOSE="docker compose -f docker-compose.yml -f docker-compose.${ENVIRONMENT}.yml --env-file ${ENV_FILE}" + +echo "==> building and starting ${ENVIRONMENT}" +$COMPOSE up -d --build + +echo "==> waiting for the wordpress container to come up" +for i in $(seq 1 30); do + if $COMPOSE exec -T wordpress php -v >/dev/null 2>&1; then + break + fi + if [[ "$i" -eq 30 ]]; then + echo "wordpress container did not become ready in time" >&2 + exit 1 + fi + sleep 2 +done + +echo "==> ensuring WordPress is installed" +if ! $COMPOSE exec -T wordpress wp core is-installed --allow-root; then + $COMPOSE exec -T wordpress wp core install \ + --url="${SITE_URL:?set SITE_URL in ${ENV_FILE}}" \ + --title="${SITE_TITLE:-Bookstore}" \ + --admin_user="${WP_ADMIN_USER:?set WP_ADMIN_USER in ${ENV_FILE}}" \ + --admin_password="${WP_ADMIN_PASSWORD:?set WP_ADMIN_PASSWORD in ${ENV_FILE}}" \ + --admin_email="${WP_ADMIN_EMAIL:?set WP_ADMIN_EMAIL in ${ENV_FILE}}" \ + --skip-email \ + --allow-root +fi + +echo "==> installing/activating theme (Blocksy — free; Book Store starter site needs a manual license + import, see README)" +$COMPOSE exec -T wordpress wp theme install blocksy --activate --allow-root + +echo "==> installing/activating required plugins" +$COMPOSE exec -T wordpress wp plugin install woocommerce redis-cache blocksy-companion --activate --allow-root +$COMPOSE exec -T wordpress wp plugin activate bookstore-core --allow-root +$COMPOSE exec -T wordpress wp redis enable --allow-root || true + +echo "==> enabling WooCommerce HPOS (custom order tables)" +$COMPOSE exec -T wordpress wp option update woocommerce_custom_orders_table_enabled yes --allow-root +$COMPOSE exec -T wordpress wp option update woocommerce_custom_orders_table_data_sync_enabled yes --allow-root + +if [[ "$ENVIRONMENT" != "production" ]]; then + echo "==> discouraging search engines (non-production)" + $COMPOSE exec -T wordpress wp option update blog_public 0 --allow-root +fi + +echo "==> flushing caches and rewrite rules" +$COMPOSE exec -T wordpress wp cache flush --allow-root +$COMPOSE exec -T wordpress wp rewrite flush --allow-root + +echo "==> deploy complete: ${ENVIRONMENT}" diff --git a/deploy/hooks/post-receive b/deploy/hooks/post-receive new file mode 100755 index 0000000..29374ec --- /dev/null +++ b/deploy/hooks/post-receive @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Install this at .git/hooks/post-receive on your own Git server +# (chmod +x it) and adjust DEPLOY_ROOT below. Pushing to `staging` deploys +# to the staging checkout; pushing to `main` deploys to production. +# +# git remote add origin @:/srv/git/bookstore.git +# git push origin staging +# +set -euo pipefail + +DEPLOY_ROOT="/srv/bookstore" + +while read -r oldrev newrev refname; do + branch="${refname#refs/heads/}" + + case "$branch" in + staging) target="$DEPLOY_ROOT/staging"; env="staging" ;; + main) target="$DEPLOY_ROOT/production"; env="production" ;; + *) echo "post-receive: ignoring push to $branch"; continue ;; + esac + + echo "post-receive: deploying $branch -> $env ($target)" + mkdir -p "$target" + git --work-tree="$target" --git-dir="$(pwd)" checkout -f "$branch" + ( cd "$target" && ./deploy/deploy.sh "$env" ) +done diff --git a/deploy/restore.sh b/deploy/restore.sh new file mode 100755 index 0000000..e301b6a --- /dev/null +++ b/deploy/restore.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Restores a backup INTO STAGING ONLY. Deliberately has no production path — +# the launch gate is "a full backup has been restored into staging," never +# production overwritten by a drill. +set -euo pipefail + +DB_DUMP="${1:?Usage: restore.sh }" +UPLOADS_ARCHIVE="${2:?Usage: restore.sh }" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +ENV_FILE=".env.staging" +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +COMPOSE="docker compose -f docker-compose.yml -f docker-compose.staging.yml --env-file ${ENV_FILE}" + +echo "!! this OVERWRITES the staging database and uploads !!" +read -r -p "type 'restore' to continue: " confirm +[[ "$confirm" == "restore" ]] || { echo "aborted"; exit 1; } + +echo "==> restoring database" +gunzip -c "$DB_DUMP" | $COMPOSE exec -T db sh -c "exec mysql -u\"\$MARIADB_USER\" -p\"\$MARIADB_PASSWORD\" \"\$MARIADB_DATABASE\"" + +echo "==> restoring uploads" +ARCHIVE_DIR="$(cd "$(dirname "$UPLOADS_ARCHIVE")" && pwd)" +ARCHIVE_NAME="$(basename "$UPLOADS_ARCHIVE")" +$COMPOSE run --rm -T -v "${ARCHIVE_DIR}:/restore:ro" wordpress \ + tar -xzf "/restore/${ARCHIVE_NAME}" -C /var/www/html/wp-content + +echo "==> restore complete — verify the site before treating the drill as passed" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..b31dc68 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,29 @@ +x-env-type: &env-type + WP_ENVIRONMENT_TYPE: local + +services: + wordpress: + environment: *env-type + + cron: + environment: *env-type + + caddy: + image: caddy:2-alpine + restart: unless-stopped + depends_on: + - wordpress + ports: + - "8080:80" + volumes: + - ./docker/caddy/Caddyfile.dev:/etc/caddy/Caddyfile:ro + - wp_core:/var/www/html:ro + - wp_uploads:/var/www/html/wp-content/uploads:ro + - wp_themes:/var/www/html/wp-content/themes:ro + - ./wp-content/plugins/bookstore-core:/var/www/html/wp-content/plugins/bookstore-core:ro + - caddy_data:/data + - caddy_config:/config + +volumes: + caddy_data: + caddy_config: diff --git a/docker-compose.production.yml b/docker-compose.production.yml new file mode 100644 index 0000000..781bdd4 --- /dev/null +++ b/docker-compose.production.yml @@ -0,0 +1,32 @@ +x-env-type: &env-type + WP_ENVIRONMENT_TYPE: production + +services: + wordpress: + environment: *env-type + + cron: + environment: *env-type + + caddy: + image: caddy:2-alpine + restart: unless-stopped + depends_on: + - wordpress + ports: + - "80:80" + - "443:443" + environment: + SITE_DOMAIN: ${SITE_DOMAIN} + volumes: + - ./docker/caddy/Caddyfile.production:/etc/caddy/Caddyfile:ro + - wp_core:/var/www/html:ro + - wp_uploads:/var/www/html/wp-content/uploads:ro + - wp_themes:/var/www/html/wp-content/themes:ro + - ./wp-content/plugins/bookstore-core:/var/www/html/wp-content/plugins/bookstore-core:ro + - caddy_data:/data + - caddy_config:/config + +volumes: + caddy_data: + caddy_config: diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml new file mode 100644 index 0000000..bd19f5b --- /dev/null +++ b/docker-compose.staging.yml @@ -0,0 +1,40 @@ +x-env-type: &env-type + WP_ENVIRONMENT_TYPE: staging + +services: + wordpress: + environment: *env-type + + cron: + environment: *env-type + + caddy: + image: caddy:2-alpine + restart: unless-stopped + depends_on: + - wordpress + ports: + - "80:80" + - "443:443" + environment: + SITE_DOMAIN: ${SITE_DOMAIN} + STAGING_BASIC_AUTH_USER: ${STAGING_BASIC_AUTH_USER} + STAGING_BASIC_AUTH_HASH: ${STAGING_BASIC_AUTH_HASH} + volumes: + - ./docker/caddy/Caddyfile.staging:/etc/caddy/Caddyfile:ro + - wp_core:/var/www/html:ro + - wp_uploads:/var/www/html/wp-content/uploads:ro + - wp_themes:/var/www/html/wp-content/themes:ro + - ./wp-content/plugins/bookstore-core:/var/www/html/wp-content/plugins/bookstore-core:ro + - caddy_data:/data + - caddy_config:/config + + mailhog: + image: mailhog/mailhog:v1.0.1 + restart: unless-stopped + ports: + - "8025:8025" + +volumes: + caddy_data: + caddy_config: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..127ed95 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,95 @@ +# wordpress and cron are the same image and MUST share the same +# environment: the official image's wp-config.php reads getenv() live, at +# PHP runtime, in whichever container is executing — it does not bake +# values into the file once. If cron's env drifts from wordpress's (e.g. +# WORDPRESS_CONFIG_EXTRA missing), cron silently loses WP_REDIS_HOST, +# DISABLE_WP_CRON, etc. The anchor below is what prevents that drift. +x-bookstore-env: &bookstore-env + WORDPRESS_DB_HOST: db + WORDPRESS_DB_NAME: ${DB_NAME} + WORDPRESS_DB_USER: ${DB_USER} + WORDPRESS_DB_PASSWORD: ${DB_PASSWORD} + WORDPRESS_TABLE_PREFIX: ${WP_TABLE_PREFIX:-wp_} + MAIL_CATCHER_HOST: mailhog + MAIL_CATCHER_PORT: 1025 + WORDPRESS_CONFIG_EXTRA: | + define('DISABLE_WP_CRON', true); + define('WP_REDIS_HOST', 'redis'); + define('WP_MEMORY_LIMIT', '256M'); + # Passed through for the bookstore-core plugin's supplier adapters / + # payment integration (design doc §04/§05) — not read by WordPress core. + ACTIVE_SUPPLIER_ADAPTERS: ${ACTIVE_SUPPLIER_ADAPTERS:-gutenberg_test} + BOOKSRUN_API_KEY: ${BOOKSRUN_API_KEY:-} + INGRAM_API_KEY: ${INGRAM_API_KEY:-} + INGRAM_ACCOUNT_ID: ${INGRAM_ACCOUNT_ID:-} + HELCIM_API_TOKEN: ${HELCIM_API_TOKEN:-} + HELCIM_ACCOUNT_ID: ${HELCIM_ACCOUNT_ID:-} + MAILERLITE_API_KEY: ${MAILERLITE_API_KEY:-} + +services: + db: + image: mariadb:11 + restart: unless-stopped + environment: + MARIADB_DATABASE: ${DB_NAME} + MARIADB_USER: ${DB_USER} + MARIADB_PASSWORD: ${DB_PASSWORD} + MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mariadb-admin ping -h 127.0.0.1 -u$$MARIADB_USER -p$$MARIADB_PASSWORD --silent"] + interval: 5s + timeout: 5s + retries: 20 + + redis: + image: redis:7-alpine + restart: unless-stopped + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 20 + + wordpress: + build: + context: ./docker/php + restart: unless-stopped + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + environment: *bookstore-env + volumes: + - wp_core:/var/www/html + - wp_uploads:/var/www/html/wp-content/uploads + - wp_themes:/var/www/html/wp-content/themes + - ./wp-content/plugins/bookstore-core:/var/www/html/wp-content/plugins/bookstore-core + - ./wp-content/mu-plugins:/var/www/html/wp-content/mu-plugins + + cron: + build: + context: ./docker/php + restart: unless-stopped + depends_on: + - wordpress + entrypoint: ["/bin/sh", "/usr/local/bin/cron-entrypoint.sh"] + environment: *bookstore-env + volumes: + - wp_core:/var/www/html + - wp_uploads:/var/www/html/wp-content/uploads + - wp_themes:/var/www/html/wp-content/themes + - ./wp-content/plugins/bookstore-core:/var/www/html/wp-content/plugins/bookstore-core + - ./wp-content/mu-plugins:/var/www/html/wp-content/mu-plugins + - ./docker/cron/entrypoint.sh:/usr/local/bin/cron-entrypoint.sh:ro + +volumes: + db_data: + redis_data: + wp_core: + wp_uploads: + wp_themes: diff --git a/docker/caddy/Caddyfile.dev b/docker/caddy/Caddyfile.dev new file mode 100644 index 0000000..f435659 --- /dev/null +++ b/docker/caddy/Caddyfile.dev @@ -0,0 +1,10 @@ +:80 { + root * /var/www/html + + request_body { + max_size 32MB + } + + php_fastcgi wordpress:9000 + file_server +} diff --git a/docker/caddy/Caddyfile.production b/docker/caddy/Caddyfile.production new file mode 100644 index 0000000..bbb3293 --- /dev/null +++ b/docker/caddy/Caddyfile.production @@ -0,0 +1,22 @@ +{$SITE_DOMAIN} { + encode gzip + + root * /var/www/html + + @no_cache path /cart* /checkout* /my-account* /wp-admin* /wp-login.php + header @no_cache Cache-Control "no-store, no-cache, must-revalidate" + + request_body { + max_size 32MB + } + + php_fastcgi wordpress:9000 + file_server + + log { + output file /data/access.log { + roll_size 50MiB + roll_keep 10 + } + } +} diff --git a/docker/caddy/Caddyfile.staging b/docker/caddy/Caddyfile.staging new file mode 100644 index 0000000..649f008 --- /dev/null +++ b/docker/caddy/Caddyfile.staging @@ -0,0 +1,22 @@ +{$SITE_DOMAIN} { + encode gzip + + # Launch gate: "No staging URLs are publicly indexed." Belt-and-suspenders + # with wp_option blog_public=0, which deploy.sh sets on staging. + basic_auth { + {$STAGING_BASIC_AUTH_USER} {$STAGING_BASIC_AUTH_HASH} + } + header X-Robots-Tag "noindex, nofollow, noarchive" + + root * /var/www/html + + @no_cache path /cart* /checkout* /my-account* /wp-admin* /wp-login.php + header @no_cache Cache-Control "no-store, no-cache, must-revalidate" + + request_body { + max_size 32MB + } + + php_fastcgi wordpress:9000 + file_server +} diff --git a/docker/cron/entrypoint.sh b/docker/cron/entrypoint.sh new file mode 100755 index 0000000..01c8d84 --- /dev/null +++ b/docker/cron/entrypoint.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# System-cron replacement for Action Scheduler / WP-Cron (design doc §01). +# WORDPRESS_CONFIG_EXTRA sets DISABLE_WP_CRON, so this loop is the only +# thing driving scheduled events in every environment. +set -eu + +echo "bookstore-core cron sidecar starting (60s interval)" + +while true; do + if ! wp cron event run --due-now --path=/var/www/html --allow-root 2>/tmp/cron-last-error.log; then + echo "cron run failed: $(cat /tmp/cron-last-error.log)" + fi + sleep 60 +done diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile new file mode 100644 index 0000000..87beea5 --- /dev/null +++ b/docker/php/Dockerfile @@ -0,0 +1,23 @@ +# PHP-FPM + WordPress core, plus the tooling deploy.sh and the cron +# sidecar need: wp-cli, composer, and the redis extension for object cache. +FROM wordpress:php8.3-fpm + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git \ + unzip \ + less \ + default-mysql-client \ + && pecl install redis \ + && docker-php-ext-enable redis \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +RUN curl -o /usr/local/bin/wp -sSL \ + https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ + && chmod +x /usr/local/bin/wp + +COPY php.ini /usr/local/etc/php/conf.d/zz-bookstore.ini + +WORKDIR /var/www/html diff --git a/docker/php/php.ini b/docker/php/php.ini new file mode 100644 index 0000000..8471b91 --- /dev/null +++ b/docker/php/php.ini @@ -0,0 +1,13 @@ +[PHP] +upload_max_filesize = 32M +post_max_size = 32M +memory_limit = 256M +max_execution_time = 120 +max_input_vars = 3000 + +[opcache] +opcache.enable = 1 +opcache.memory_consumption = 128 +opcache.max_accelerated_files = 10000 +opcache.validate_timestamps = 1 +opcache.revalidate_freq = 2 diff --git a/docs/online_bookstore_prelaunch_implementation_plan.docx b/docs/online_bookstore_prelaunch_implementation_plan.docx new file mode 100644 index 0000000000000000000000000000000000000000..ec5016b4641c38add9280fd8c69303a794505991 GIT binary patch literal 50532 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 Xl!FAFy)PXZI$#~t3pn<^y!-zEM-l@U literal 0 HcmV?d00001 diff --git a/wp-content/mu-plugins/local-mail-catcher.php b/wp-content/mu-plugins/local-mail-catcher.php new file mode 100644 index 0000000..7a729f2 --- /dev/null +++ b/wp-content/mu-plugins/local-mail-catcher.php @@ -0,0 +1,22 @@ +isSMTP(); + $phpmailer->Host = getenv('MAIL_CATCHER_HOST') ?: 'mailhog'; + $phpmailer->Port = getenv('MAIL_CATCHER_PORT') ?: 1025; + $phpmailer->SMTPAuth = false; + $phpmailer->SMTPAutoTLS = false; +}); diff --git a/wp-content/plugins/bookstore-core/bookstore-core.php b/wp-content/plugins/bookstore-core/bookstore-core.php new file mode 100644 index 0000000..ff32de4 --- /dev/null +++ b/wp-content/plugins/bookstore-core/bookstore-core.php @@ -0,0 +1,38 @@ +=8.1" + }, + "autoload": { + "psr-4": { + "Bookstore\\Core\\": "includes/" + } + } +} diff --git a/wp-content/plugins/bookstore-core/includes/.gitkeep b/wp-content/plugins/bookstore-core/includes/.gitkeep new file mode 100644 index 0000000..e69de29