# Deployment — EKS Support Relay ## Target | | | |---|---| | Host | `acmycar`, `18.168.156.244` (the Chatwoot host) | | SSH | `ssh -i ubuntu@18.168.156.244` | | Path | `/home/ubuntu/eks_relay` (a real git checkout of the Gogs repo) | | Domain | `https://eks-relay.easyklima.com` | | Reverse proxy | Traefik `traefik-traefik-1`, docker network `web` | | DNS | Cloudflare zone `easyklima.com`, proxied A record | The relay is co-located with Chatwoot rather than with Flowise: the Chatwoot webhook is the latency-sensitive hop (Chatwoot waits for the `202`), while the Flowise → `/tools/*` calls happen inside an already long-running LLM turn. ### Why `easyklima.com` and not `easyklima.pl` `eksupport.easyklima.com` and `botek.easyklima.com` already live in the `easyklima.com` Cloudflare zone and terminate on the same Traefik. Putting the relay in the same zone keeps one certificate resolver, one WAF policy surface and one set of DNS conventions. `easyklima.pl` points at the shop's origin and would need separate Cloudflare and Traefik work for no benefit. > Note: the zone has a wildcard `*.easyklima.com` CNAME pointing at the shop > origin. Without an explicit A record, `eks-relay.easyklima.com` resolves to the > **shop**, not to Chatwoot. The explicit record below is mandatory, not optional. ## 1. DNS (Cloudflare) Create a proxied A record matching the `eksupport` pattern: | Type | Name | Content | Proxy | |---|---|---|---| | A | `eks-relay` | `18.168.156.244` | Proxied (orange cloud) | ```bash # CLOUDFLARE_TOKEN comes from the local secrets file — never inline it. ZONE=3ff752c975f4bfc6286f0f357f306c2f curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" \ --data '{"type":"A","name":"eks-relay","content":"18.168.156.244","proxied":true,"ttl":1}' ``` Verify: ```bash dig +short eks-relay.easyklima.com # Cloudflare anycast IPs ``` ### Cloudflare WAF — two rules are required 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: ``` (http.host eq "eks-relay.easyklima.com" and (starts_with(http.request.uri.path, "/webhooks/") or 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: ``` (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. If the relay ever moves to another host, rule 2's source IP must move with it. ## 2. Server checkout ```bash ssh -i ubuntu@18.168.156.244 git clone https://gogs.tenteg.es/aiac/eks_relay.git /home/ubuntu/eks_relay cd /home/ubuntu/eks_relay 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 (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 ```bash cp .env.example .env chmod 600 .env # fill in the real values (migrated from the PHP relay's .env.php) ``` Required before first start: `CHATWOOT_API_TOKEN`, `FLOWISE_PREDICT_URL`, `FLOWISE_API_KEY`, `WOOCOMMERCE_CONSUMER_KEY`, `WOOCOMMERCE_CONSUMER_SECRET`, `RELAY_SHARED_SECRET`, `ADMIN_TOKEN`. `RELAY_SHARED_SECRET` **must** stay identical to the value configured in Flowise as `$vars.relay_shared_secret`, otherwise every tool call returns 401. ## 4. Start ```bash cd /home/ubuntu/eks_relay sudo docker compose config # validate sudo docker compose up -d --build sudo docker compose ps sudo docker compose logs -f relay ``` The `web` network already exists (Traefik owns it); the compose file joins it as external. ## 5. Verify ```bash curl -s https://eks-relay.easyklima.com/health | jq curl -s https://eks-relay.easyklima.com/ready | jq ``` Then run [`SMOKE_TEST.md`](SMOKE_TEST.md). ## 6. Updating ```bash cd /home/ubuntu/eks_relay git pull sudo docker compose restart relay ``` No rebuild: the repo is bind-mounted and the entrypoint reinstalls dependencies only when `package-lock.json` changed, regenerates the Prisma client only when `prisma/schema.prisma` changed, and always applies pending migrations. 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 container at `/app/data`. It is gitignored. Back it up before a schema change: ```bash cp data/eks_relay.db data/eks_relay.db.$(date +%F-%H%M) ``` Browse it via Prisma Studio over an SSH tunnel — see the README. Studio is never routed through Traefik. ## Chatwoot webhook Point the Chatwoot bot/webhook at: ``` https://eks-relay.easyklima.com/webhooks/chatwoot ``` Keep the old PHP URL noted until the Node relay has passed a real e-mail test; switching back is a one-field change in the Chatwoot panel. ## Known follow-ups - **`wp-plugins/eksrelay_api.php` is unauthenticated.** Its routes use `permission_callback => __return_true`. The relay already sends `Authorization: Bearer ` when that variable is set; enforcing it in the plugin is a separate, coordinated change on the shop's WordPress. - **`npm audit` reports a high-severity advisory** in `deepmerge-ts`, reached through `@prisma/config` → `prisma`. That is the Prisma **CLI** dependency chain (build/migration time), not the runtime `@prisma/client` used to serve requests. The only offered fix downgrades Prisma to 6.12; the advisory is therefore accepted and tracked rather than force-fixed.