Bläddra i källkod

Record the verified deployment in the docs

Documents what the first deployment actually needed, so the next person does not
rediscover it:
- Cloudflare requires TWO skip rules, not one. The outbound rule on the
  easyklima.pl zone is the non-obvious half: without it every WooCommerce and
  eksrelay/v1 call from the relay host gets a 403 challenge and every
  shop-backed tool fails.
- 'docker compose restart' does not re-read .env; env changes need 'up -d'.
- git push from the server checkout works via a temporary GIT_ASKPASS helper.
- Appends the recorded smoke-test results and the webhook cutover steps,
  including repointing $vars.relay_base in Flowise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvzjhjUY7tr5Naj1Co4N6B
Maciek 4 veckor sedan
förälder
incheckning
eca691eb71
3 ändrade filer med 121 tillägg och 14 borttagningar
  1. 57 10
      docs/DEPLOY.md
  2. 26 4
      docs/MIGRATION.md
  3. 38 0
      docs/SMOKE_TEST.md

+ 57 - 10
docs/DEPLOY.md

@@ -50,23 +50,37 @@ Verify:
 dig +short eks-relay.easyklima.com    # Cloudflare anycast IPs
 ```
 
-### Cloudflare and automated POSTs
+### Cloudflare WAF — two rules are required
 
-Cloudflare's managed challenge can block server-to-server POSTs from Chatwoot
-and Flowise. If that happens, add a **narrow** WAF skip rule — scoped to this
-hostname and these paths only, never a zone-wide bypass:
+Cloudflare's managed challenge blocks server-to-server calls in **both**
+directions. Two narrow skip rules are in place; neither is a zone-wide bypass,
+and both endpoints keep their own bearer authentication.
+
+**1. Inbound — zone `easyklima.com`** (Chatwoot and Flowise reaching the relay).
+Without it every request to the relay gets a `403` challenge page:
 
 ```
-Expression:
 (http.host eq "eks-relay.easyklima.com" and
  (starts_with(http.request.uri.path, "/webhooks/") or
-  starts_with(http.request.uri.path, "/tools/")))
+  starts_with(http.request.uri.path, "/tools/") or
+  http.request.uri.path eq "/health" or
+  http.request.uri.path eq "/ready"))
+```
+
+**2. Outbound — zone `easyklima.pl`** (the relay reaching the shop's REST APIs).
+Without it every WooCommerce and `eksrelay/v1` call gets a `403` challenge, so
+every shop-backed tool fails:
 
-Action: Skip → Managed Rules, Bot Fight Mode, Rate limiting
 ```
+(ip.src eq 18.168.156.244 and starts_with(http.request.uri.path, "/wp-json/"))
+```
+
+Both use `action: skip` over phases `http_ratelimit`,
+`http_request_firewall_managed`, `http_request_sbfm` and products
+`uaBlock, bic, hot, securityLevel, rateLimit, waf` — matching the convention of
+the pre-existing rules in these zones.
 
-The endpoints stay protected by their own bearer secrets; the skip rule only
-removes Cloudflare's browser-oriented challenges.
+If the relay ever moves to another host, rule 2's source IP must move with it.
 
 ## 2. Server checkout
 
@@ -78,7 +92,27 @@ git config user.name  "Maciek"
 git config user.email "maciek@aiac.local"
 ```
 
-It is a normal checkout, so `git pull` and `git push` both work on the server.
+It is a normal checkout, so `git pull` and `git push` both work on the server
+(push verified by tagging the deployed commit `deployed-acmycar-20260820`).
+
+Anonymous read works, so `clone` and `pull` need no credentials. `push` does;
+supply them through a temporary `GIT_ASKPASS` helper and delete it afterwards —
+never store credentials on the server or put them in the remote URL:
+
+```bash
+umask 077
+cat > /tmp/.ap.sh <<'EOF'
+#!/bin/sh
+case "$1" in
+  *Username*) printf "%s" "$GIT_USER" ;;
+  *Password*) printf "%s" "$GIT_PASS" ;;
+esac
+EOF
+chmod 700 /tmp/.ap.sh
+GIT_USER=... GIT_PASS=... GIT_ASKPASS=/tmp/.ap.sh GIT_TERMINAL_PROMPT=0 \
+  git push origin master
+rm -f /tmp/.ap.sh
+```
 
 ## 3. Environment
 
@@ -131,6 +165,19 @@ only when `package-lock.json` changed, regenerates the Prisma client only when
 
 Rebuild (`--build`) only when the Dockerfile or its base image changes.
 
+> **`restart` does not re-read `.env`.** Docker Compose bakes `env_file` values
+> into the container at create time, so after editing `.env` you need
+> `sudo docker compose up -d` (which recreates the container), not `restart`.
+> For code-only changes `restart` is the faster path.
+
+### Live-reload while debugging
+
+Set `RELAY_MODE=dev` in `.env` and run `sudo docker compose up -d`. The
+container then runs `tsx watch`, so saving a file reloads the process in place —
+no restart and no rebuild. Set it back to `prod` and `up -d` again when done;
+prod mode compiles once and runs `dist/`, which is what should serve real
+traffic.
+
 ## Database
 
 SQLite at `/home/ubuntu/eks_relay/data/eks_relay.db`, bind-mounted into the

+ 26 - 4
docs/MIGRATION.md

@@ -55,13 +55,35 @@ The WPML/multicurrency behaviour (`storeLocales.ts`) is a direct port of
 
 ## Migration order
 
-1. Deploy the Node relay to its own domain; leave PHP running. ✔
-2. Verify with `docs/SMOKE_TEST.md`, steps 1–6. ✔
-3. Point the Chatwoot webhook at the Node relay for the **test** inbox and run a
-   real e-mail test (step 7).
+1. Deploy the Node relay to its own domain; leave PHP running.
+   **Done** — `https://eks-relay.easyklima.com`, 2026-08-20.
+2. Verify with `docs/SMOKE_TEST.md`, steps 1–6.
+   **Done** — see the recorded run at the end of that document.
+3. Point the Chatwoot webhook at the Node relay and run a real e-mail test
+   (step 7). **Not done** — repointing the webhook changes live behaviour, so it
+   is left as an explicit decision.
 4. Only then consider the production inbox `info@easyklima.com`.
 5. Retire the PHP deployment once the Node relay has run clean for a while.
 
+### Switching the webhook
+
+In the Chatwoot panel, change the bot/webhook URL from
+
+```
+https://easyklima.pl/eks_relay/public/index.php?__path=/webhooks/chatwoot
+```
+
+to
+
+```
+https://eks-relay.easyklima.com/webhooks/chatwoot
+```
+
+At the same time, point the Flowise tool variable `$vars.relay_base` at
+`https://eks-relay.easyklima.com` — otherwise the agent's tools keep calling the
+PHP relay while the webhook goes to the Node one. `$vars.relay_shared_secret` is
+unchanged: the deployed `.env` reuses the PHP relay's `RELAY_SHARED_SECRET`.
+
 ## Fallback
 
 Set the Chatwoot webhook back to:

+ 38 - 0
docs/SMOKE_TEST.md

@@ -154,3 +154,41 @@ attribute to return the test conversation to its previous state.
 
 Point the Chatwoot webhook back at the PHP relay URL. The PHP deployment on the
 shop's web server is untouched and remains functional.
+
+---
+
+## Recorded run — 2026-08-20 (initial deployment)
+
+Commit `f36fc3d`, host `acmycar` / `18.168.156.244`, domain
+`https://eks-relay.easyklima.com`.
+
+| Step | Result |
+|---|---|
+| `docker compose config` | OK, one service |
+| `docker compose ps` | `eks-relay  Up (healthy)` |
+| `GET /health` | `200`, `mode: prod` |
+| `GET /ready` | `200`, `ok: true` — database, chatwoot, flowise, woocommerce, wpStore all `ok` |
+| `/tools/*` without a bearer | `401 UNAUTHORIZED` on all seven endpoints |
+| `/tools/get_payment_methods` | `200`, gateways returned (Przelewy24, PayPal, Stripe, Bancontact, bank transfer, …) |
+| `/tools/get_shipping_data` (zones) | `200`, zones with ISO country codes |
+| `/tools/get_shipping_data` (`zoneId=22`, `language=cs`) | `200`, DHL/DHL-COD priced in **CZK** — multicurrency path confirmed |
+| `/tools/get_product_data` (`search=gaz`) | `200`, published products with prices |
+| `/tools/get_car_data` (VW, 2016) | `200`, model disambiguation list |
+| `/tools/get_order_data` (bogus order) | `403 UNAUTHORIZED`, no third-party data leaked |
+| Webhook, delivery 1 | `202` + `jobId` |
+| Webhook, delivery 2 (same message id) | `200 duplicate:true`, **no second job** |
+| Webhook, outgoing message | `200 skipped: not_incoming:outgoing` |
+| Worker outcome | job `done`; `ProcessedMessage` = `skipped/ticket_mode`; audit `webhook_accepted` → `skipped_ticket_mode` |
+| `/tools/new_ticket` (test conversation 1600) | `EKS-20260820-1600`, `status: created` |
+| `/tools/new_ticket` again | same number, `status: existing` |
+| Chatwoot after ticket | label `ticket` + `ticket_number` + `handoff` + `handoff_reason` set |
+| Follow-up message on that conversation | conversation fetched from Chatwoot, `skipped_ticket_mode`, Flowise **not** called |
+| `git pull` + `docker compose restart` | new code live in ~0.4 s, no rebuild, no dependency reinstall |
+| `RELAY_MODE=dev` + file touch | process reloaded in place (uptime 47 s → 12 s), no restart, no rebuild |
+
+Test conversation 1600 was restored afterwards (labels and custom attributes
+cleared, local `Ticket` row deleted). Mode was set back to `prod`.
+
+**Not yet run:** step 7, the end-to-end e-mail test. It needs the Chatwoot
+webhook repointed at this relay, which is a deliberate, separately approved
+change.