| Host | acmycar, 18.168.156.244 (the Chatwoot host) |
| SSH | ssh -i <maciek_priv> 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.
easyklima.com and not easyklima.pleksupport.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.comCNAME pointing at the shop origin. Without an explicit A record,eks-relay.easyklima.comresolves to the shop, not to Chatwoot. The explicit record below is mandatory, not optional.
Create a proxied A record matching the eksupport pattern:
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | eks-relay |
18.168.156.244 |
Proxied (orange cloud) |
# 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:
dig +short eks-relay.easyklima.com # Cloudflare anycast IPs
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.
ssh -i <key> 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:
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
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.
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.
curl -s https://eks-relay.easyklima.com/health | jq
curl -s https://eks-relay.easyklima.com/ready | jq
Then run SMOKE_TEST.md.
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.
restartdoes not re-read.env. Docker Compose bakesenv_filevalues into the container at create time, so after editing.envyou needsudo docker compose up -d(which recreates the container), notrestart. For code-only changesrestartis the faster path.
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.
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:
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.
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.
wp-plugins/eksrelay_api.php is unauthenticated. Its routes use
permission_callback => __return_true. The relay already sends
Authorization: Bearer <WP_STORE_API_SECRET> 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.