Retarget deploy pipeline to POS and guard against cross-service clobber
Deploy Ladill POS / deploy (push) Successful in 24s

The pipeline was copied from Ladill Mini and still deployed into
/var/www/ladill-mini, so the first POS push overwrote mini.ladill.com.
Retarget every mini reference to pos (app root, archive/workspace names,
concurrency group, nginx subdomain, supervisor worker), add a guard step
that fails the run unless basename(LADILL_APP_ROOT) matches the repo name,
and document the incident + a forking checklist in DEPLOY.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
isaacclad
2026-06-23 23:16:00 +00:00
co-authored by Claude Opus 4.8
parent e5d2b84388
commit 57862acb47
3 changed files with 64 additions and 12 deletions
+26 -9
View File
@@ -1,4 +1,4 @@
name: Deploy Ladill Mini name: Deploy Ladill POS
on: on:
push: push:
@@ -10,7 +10,7 @@ permissions:
contents: read contents: read
concurrency: concurrency:
group: deploy-mini group: deploy-pos
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
@@ -19,10 +19,27 @@ jobs:
env: env:
NODE_ROOT: /tmp/ladill-node-22-r1 NODE_ROOT: /tmp/ladill-node-22-r1
NODE_VERSION: "22.14.0" NODE_VERSION: "22.14.0"
RELEASE_ARCHIVE: /tmp/ladill-mini-release-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tgz RELEASE_ARCHIVE: /tmp/ladill-pos-release-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tgz
WORKSPACE: /tmp/${{ gitea.repository_owner }}-mini-${{ gitea.run_id }}-${{ gitea.run_attempt }} WORKSPACE: /tmp/${{ gitea.repository_owner }}-pos-${{ gitea.run_id }}-${{ gitea.run_attempt }}
LADILL_APP_ROOT: /var/www/ladill-mini LADILL_APP_ROOT: /var/www/ladill-pos
steps: steps:
- name: Guard deploy target
# Refuse to deploy if this workflow was copied from another service
# without retargeting LADILL_APP_ROOT. The app dir basename MUST match
# the repo name (ladill-pos -> /var/www/ladill-pos), otherwise a copied
# pipeline would clobber a sibling service. See DEPLOY.md "Forking this pipeline".
shell: bash {0}
run: |
set -Eeuo pipefail
REPO_NAME="${{ gitea.repository }}"; REPO_NAME="${REPO_NAME##*/}"
APP_DIR_NAME="$(basename "$LADILL_APP_ROOT")"
if [ "$REPO_NAME" != "$APP_DIR_NAME" ]; then
echo "FATAL: repo '$REPO_NAME' != deploy target '$APP_DIR_NAME' (LADILL_APP_ROOT=$LADILL_APP_ROOT)." >&2
echo "Refusing to deploy — a mis-targeted workflow would overwrite another service. Retarget deploy.yml env." >&2
exit 1
fi
echo "Guard OK: $REPO_NAME -> $LADILL_APP_ROOT"
- name: Checkout - name: Checkout
shell: bash {0} shell: bash {0}
run: | run: |
@@ -78,7 +95,7 @@ jobs:
- name: Deploy release - name: Deploy release
shell: bash {0} shell: bash {0}
env: env:
LADILL_RELEASE_ARCHIVE: /tmp/ladill-mini-release-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tgz LADILL_RELEASE_ARCHIVE: /tmp/ladill-pos-release-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tgz
run: | run: |
set -Eeuo pipefail set -Eeuo pipefail
: "${LADILL_APP_ROOT:?Set LADILL_APP_ROOT}" : "${LADILL_APP_ROOT:?Set LADILL_APP_ROOT}"
@@ -93,11 +110,11 @@ jobs:
NGINX_SCRIPT="/var/www/ladill.com/current/deployment/setup-service-subdomain-nginx.sh" NGINX_SCRIPT="/var/www/ladill.com/current/deployment/setup-service-subdomain-nginx.sh"
fi fi
if [ ! -f "$NGINX_SCRIPT" ]; then if [ ! -f "$NGINX_SCRIPT" ]; then
echo "WARN: setup-service-subdomain-nginx.sh not found — configure mini.ladill.com vhost manually" echo "WARN: setup-service-subdomain-nginx.sh not found — configure pos.ladill.com vhost manually"
exit 0 exit 0
fi fi
if sudo -n bash "$NGINX_SCRIPT" mini --app /var/www/ladill-mini/current; then if sudo -n bash "$NGINX_SCRIPT" pos --app /var/www/ladill-pos/current; then
echo "nginx vhost updated for mini.ladill.com" echo "nginx vhost updated for pos.ladill.com"
else else
echo "WARN: nginx vhost step skipped (deploy user cannot sudo) — vhost must already be provisioned" echo "WARN: nginx vhost step skipped (deploy user cannot sudo) — vhost must already be provisioned"
fi fi
+36 -1
View File
@@ -12,7 +12,42 @@ Standalone **in-store register** at `pos.ladill.com`. Charges via Ladill Pay (`s
## Deploy ## Deploy
Same release model as Mini/Merchant — see `deploy/deploy.sh`. Same release model as Mini/Merchant — see `deploy/deploy.sh`. Pushing to `main`
triggers `.gitea/workflows/deploy.yml` on the host `deploy` runner, which builds a
release and deploys into `/var/www/ladill-pos`.
## ⚠️ Forking this pipeline (read before copying to a new service)
This workflow was bootstrapped by copying Mini's. **If you copy `.gitea/workflows/deploy.yml`,
`deploy/deploy.sh`, or `DEPLOY.md` to a new service, retarget EVERY `mini`/`pos` reference**,
or the new repo's pipeline will deploy its code on top of the service you copied from.
Things that must match the new service (search for the old slug and replace all):
- `deploy.yml`: `name:`, `concurrency.group`, `RELEASE_ARCHIVE`, `WORKSPACE`,
`LADILL_APP_ROOT`, the nginx step subdomain (`pos --app /var/www/ladill-pos/current`)
- `deploy.sh`: `APP_ROOT` default and the `supervisorctl restart <service>-worker:*` name
A **guard step** in `deploy.yml` enforces `basename(LADILL_APP_ROOT) == <repo name>` and
fails the run on a mismatch — keep repo name and app dir name identical (`ladill-pos`
`/var/www/ladill-pos`).
> **Incident 2026-06-24:** the initial POS pipeline still pointed at `/var/www/ladill-mini`,
> so the first POS push overwrote mini.ladill.com (POS code + a stray `create_pos_tables`
> migration against the `ladill_mini` DB). Remediated by repointing mini's `current` symlink
> to the last genuine Mini release and dropping the 4 stray `pos_*` tables. The guard step
> above exists to make this impossible to repeat.
## Server prerequisites (one-time, as root)
Done for `pos.ladill.com`; replicate for any new service:
1. MySQL: `CREATE DATABASE ladill_pos` + user `ladill_pos@127.0.0.1` (`ALL` on its own db;
`SELECT` on `ladill_merchant` + `ladilldb` for commerce links)
2. `/var/www/ladill-pos/{releases,shared}` owned `deploy:www-data`
3. `shared/.env` (mode 640, `deploy:www-data`) with generated `APP_KEY`, DB creds, and the
integration keys from the monolith (see "Platform wiring")
4. nginx vhost via `deployment/setup-service-subdomain-nginx.sh pos --app /var/www/ladill-pos/current`
```bash ```bash
php artisan migrate --force php artisan migrate --force
+2 -2
View File
@@ -2,7 +2,7 @@
# Fast on-host release deploy (same model as climpme/web/deploy/deploy.sh). # Fast on-host release deploy (same model as climpme/web/deploy/deploy.sh).
set -Eeuo pipefail set -Eeuo pipefail
APP_ROOT="${LADILL_APP_ROOT:-/var/www/ladill-mini}" APP_ROOT="${LADILL_APP_ROOT:-/var/www/ladill-pos}"
RELEASES_DIR="$APP_ROOT/releases" RELEASES_DIR="$APP_ROOT/releases"
SHARED_DIR="$APP_ROOT/shared" SHARED_DIR="$APP_ROOT/shared"
CURRENT_LINK="$APP_ROOT/current" CURRENT_LINK="$APP_ROOT/current"
@@ -236,7 +236,7 @@ if [ -L "$CURRENT_LINK" ] && [ -f "$CURRENT_LINK/artisan" ]; then
(cd "$CURRENT_LINK" && php artisan queue:restart) || true (cd "$CURRENT_LINK" && php artisan queue:restart) || true
fi fi
if command -v supervisorctl >/dev/null 2>&1; then if command -v supervisorctl >/dev/null 2>&1; then
supervisorctl restart ladill-mini-worker:* 2>/dev/null || true supervisorctl restart ladill-pos-worker:* 2>/dev/null || true
fi fi
log "Cleaning old releases" log "Cleaning old releases"