From 49dcf78529a55ca95d71d88e937c57efdbff3c44 Mon Sep 17 00:00:00 2001 From: EugeneTes Date: Tue, 25 Aug 2026 09:37:57 +0000 Subject: [PATCH] Add NATS-over-WebSocket compose for Coolify Configures a single NATS 2.10 service exposing a WebSocket listener on :8080 (no_tls; Traefik terminates TLS at nats.volcanic.tes.gd) with env-driven username/password auth and a persisted JetStream store. --- .gitignore | 1 + CLAUDE.md | 122 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 24 +++++++++ nats-server.conf | 21 ++++++++ 4 files changed, 168 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 docker-compose.yml create mode 100644 nats-server.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e28bb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +deploy.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9741a48 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,122 @@ +# NATS Expert + +This project deploys a NATS server behind Coolify, reachable **over WebSockets** only. NATS' native TCP port (4222) is not exposed to the outside — the WS endpoint is the sole entry point for both applications and the managing agent. + +## Deployment target + +- **Coolify project:** `Volcanic MFEs` (`p4fts5a0kv3tahzjtbtfequk`) +- **Server:** `localhost` (`d0c4owww4kwo0ow08wws4ss8`) +- **Build pack:** `dockercompose` +- **Compose file:** `/docker-compose.yml` +- **Public domain:** `https://nats.volcanic.tes.gd` → routed by Traefik to `nats:8080` (WebSocket listener) +- **TLS:** terminated at Traefik. NATS itself runs with `no_tls: true` on the WS listener. + +Coolify's non-secret app config (project/server UUIDs, name, domain) lives in `deploy.json` at the repo root and is **gitignored** — it is regenerated locally when needed by the `deploying-to-coolify-via-api` skill flow. + +## Connection details (for agents managing this NATS) + +Both are populated from the two env vars set on the Coolify application: + +- `NATS_USER` — admin username (default: `admin`) +- `NATS_PASSWORD` — admin password (generated at deploy time, stored in the Coolify app's env vars) + +The current live values are printed by: + +```bash +: "${COOLIFY_URL:?}" "${COOLIFY_KEY:?}" +APP_UUID=$(jq -r '.app.uuid' deploy.json) # from local deploy.json +curl -sS -H "Authorization: Bearer $COOLIFY_KEY" \ + "$COOLIFY_URL/api/v1/applications/$APP_UUID/envs" \ + | jq -r '.[] | select(.is_preview==false) | "\(.key)=\(.value)"' +``` + +If `deploy.json` is not present, list applications under the Volcanic MFEs project and find the one named `nats`: + +```bash +curl -sS -H "Authorization: Bearer $COOLIFY_KEY" \ + "$COOLIFY_URL/api/v1/projects/p4fts5a0kv3tahzjtbtfequk" \ + | jq '.applications[] | {uuid, name}' +``` + +### WebSocket URL + +``` +wss://nats.volcanic.tes.gd +``` + +The NATS server accepts standard NATS-over-WebSocket framing (RFC 6455 with the `nats` subprotocol). No custom path; connect to the root URL. + +### With `nats` CLI + +```bash +nats \ + --server=wss://nats.volcanic.tes.gd \ + --user="$NATS_USER" --password="$NATS_PASSWORD" \ + server info +``` + +### With `nats.js` / `nats.ws` (Node/browser) + +```js +import { connect } from 'nats.ws'; // browser +// import { connect } from 'nats'; // node with ws + +const nc = await connect({ + servers: 'wss://nats.volcanic.tes.gd', + user: process.env.NATS_USER, + pass: process.env.NATS_PASSWORD, +}); +``` + +### With `nats-py` + +```python +import asyncio, os +from nats.aio.client import Client as NATS + +async def main(): + nc = NATS() + await nc.connect( + servers=['wss://nats.volcanic.tes.gd'], + user=os.environ['NATS_USER'], + password=os.environ['NATS_PASSWORD'], + ) + await nc.publish('hello', b'world') + await nc.drain() + +asyncio.run(main()) +``` + +## What's inside the server + +- **Listeners** + - `4222` — native NATS protocol, `expose`d only inside the compose network. Not reachable from outside. + - `8080` — WebSocket listener, `no_tls: true`. Traefik terminates TLS and forwards `ws://nats:8080` from the public `wss://nats.volcanic.tes.gd`. + - `8222` — HTTP monitoring (`/healthz`, `/varz`, `/jsz`), internal only. Used by the compose healthcheck. +- **JetStream**: enabled, persisted to the named volume `nats-data` mounted at `/data`. Limits: 256MB memory / 4GB file. Bump `max_file_store` in `nats-server.conf` if you need more. +- **Auth**: a single user, whose name and password come from the `NATS_USER` / `NATS_PASSWORD` env vars via NATS' native `$VAR` substitution in the config. No accounts, no operator/JWT mode. + +## Managing / redeploying + +Config or compose changes → commit and push to `main`, then trigger a Coolify deploy (fire-and-forget): + +```bash +: "${COOLIFY_URL:?}" "${COOLIFY_KEY:?}" +APP_UUID=$(jq -r '.app.uuid' deploy.json) # or look it up as shown above +curl -sS -X POST "$COOLIFY_URL/api/v1/deploy?uuid=$APP_UUID" \ + -H "Authorization: Bearer $COOLIFY_KEY" +# → { "deployments": [ { "deployment_uuid": "…" } ] } done, don't poll +``` + +Rotating the password: PATCH the `NATS_PASSWORD` env on the Coolify app, then redeploy. Do not edit `nats-server.conf` — auth is env-driven. + +To add a second user (e.g. a scoped app account), edit the `authorization.users` array in `nats-server.conf`, wire the new credential env vars into `docker-compose.yml`, and set them in Coolify. + +## Files in this repo + +| File | Purpose | +|---|---| +| `docker-compose.yml` | Single `nats` service, exposes 4222/8080/8222 internally; only 8080 is routed publicly via `SERVICE_FQDN_NATS_8080` + Coolify's `docker_compose_domains`. | +| `nats-server.conf` | NATS config: JetStream, WebSocket listener (`no_tls`), env-driven auth. | +| `.gitignore` | Keeps `deploy.json` out of git. | +| `deploy.json` (local only) | Coolify app config used by the `deploying-to-coolify-via-api` skill. | diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..25e80fc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + nats: + image: nats:2.10-alpine + restart: unless-stopped + command: ["-c", "/etc/nats/nats-server.conf"] + environment: + - SERVICE_FQDN_NATS_8080 + - NATS_USER=${NATS_USER} + - NATS_PASSWORD=${NATS_PASSWORD} + volumes: + - ./nats-server.conf:/etc/nats/nats-server.conf:ro + - nats-data:/data + expose: + - "4222" + - "8080" + - "8222" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8222/healthz"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + nats-data: diff --git a/nats-server.conf b/nats-server.conf new file mode 100644 index 0000000..ce923c3 --- /dev/null +++ b/nats-server.conf @@ -0,0 +1,21 @@ +server_name: "nats-volcanic" + +port: 4222 +http_port: 8222 + +jetstream { + store_dir: "/data" + max_memory_store: 256MB + max_file_store: 4GB +} + +websocket { + port: 8080 + no_tls: true +} + +authorization { + users = [ + { user: $NATS_USER, password: $NATS_PASSWORD } + ] +}