Switch deploy pipeline to Gitea polling; fix env-parsing and volume-isolation bugs

- Replace the bare-repo post-receive hook with deploy/poll-deploy.sh: Gitea
  and the Docker hosts are separate machines, so each box polls its branch
  via host crontab instead of needing an exposed webhook receiver.
- Add Blocksy theme + Blocksy Companion auto-install to deploy.sh (free
  tier; the paid Book Store starter site still needs a manual license step).
- Fix deploy.sh/backup.sh/restore.sh sourcing .env files as bash: a bcrypt
  hash's `$2a$14$...` shape breaks under `set -u`. Replaced with
  deploy/lib/env.sh, a literal (non-executing) KEY=VALUE reader.
- Fix docker compose itself mangling the same kind of value: both
  `environment: ${VAR}` and `env_file:` run values through Compose's
  interpolation, which silently blanks `$identifier`-shaped substrings.
  The staging basic-auth hash is now rendered directly into the Caddyfile
  by deploy.sh, bypassing Compose's variable system entirely.
- Fix dev/staging/production silently sharing one Compose project (and
  therefore one db_data volume) by pinning an explicit -p per environment.
- cron and wordpress now share one environment anchor so they can't drift
  apart again (cron was silently missing WORDPRESS_CONFIG_EXTRA before).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 10:10:12 -04:00
co-authored by Claude Sonnet 5
parent c9d637c907
commit b43852e733
13 changed files with 132 additions and 62 deletions
+21 -12
View File
@@ -19,7 +19,7 @@ docker/php/ app image: WP core image + wp-cli, composer, red
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/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
@@ -78,19 +78,28 @@ the database like any other WordPress content — a redeploy or a fresh
## Deploy pipeline
This assumes your own bare Git server, not a hosted CI. The mechanism:
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. A bare repo lives on the server (e.g. `/srv/git/bookstore.git`).
2. `deploy/hooks/post-receive` is copied into `<bare-repo>.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.
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 from a checked-out
copy on the server; the hook is only automation on top of the same script.
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