From 67aefcd127930e7941e895ad154d24106d9ce496 Mon Sep 17 00:00:00 2001 From: ppzxc Date: Sat, 21 Mar 2026 05:08:03 +0900 Subject: [PATCH 1/2] =?UTF-8?q?docs(readme):=20update=20for=20v0.2.0=20?= =?UTF-8?q?=E2=80=94=20TCP=20adapter,=20CEL/Expr=20engine,=20parser=20pipe?= =?UTF-8?q?line?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove "planned" tags from TCP and expression-based routing features - Add parser pipeline feature (JSON/Form/XML/Logfmt/Regex) - Update configuration example to reflect new format: expression.defaultEngine, parser field, TCP input, template as map, routing conditions - Replace Template Variables section with Expression Variables section covering filter/mapping/routing/template CEL/Expr syntax - Add TCP Inbound API section --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a1d0c99..8aaaf52 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # relaybox -Generic relay hub: receives any inbound protocol/format and delivers to outbound channels (CEL/Expr expression filter/transform/route rules planned). +Generic relay hub: receives any inbound protocol/format and delivers to outbound channels via CEL/Expr expression filter, transform, and route rules. ``` any inbound (HTTP REST / WebSocket / TCP / ...) ↓ + parser pipeline (JSON / Form / XML / Logfmt / Regex) + ↓ CEL / Expr expression filter + transform + route ↓ any outbound (Webhook / Slack / Discord / ...) @@ -12,9 +14,9 @@ any outbound (Webhook / Slack / Discord / ...) ## Features -- **Multi-protocol inbound** — HTTP REST + WebSocket (TCP planned) -- **Expression-based routing** — CEL/Expr filter and transform rules per route (planned) -- **Template transformation** — Go `text/template` payload rendering +- **Multi-protocol inbound** — HTTP REST + WebSocket + TCP +- **Parser pipeline** — JSON, Form, XML, Logfmt, Regex per input +- **Expression-based routing** — CEL/Expr filter, mapping, and routing conditions per rule - **at-least-once delivery** — file-queue backed, survives restarts - **Exponential backoff retry** — per-channel `retryCount` / `retryDelayMs` - **Hot config reload** — change outputs / rules without restart @@ -46,27 +48,48 @@ cp internal/config/config.example.yaml config.yaml ```yaml server: port: 8080 + readTimeout: 30s + writeTimeout: 30s log: level: info # debug | info | warn | error format: json # json | text +expression: + defaultEngine: cel # cel | expr + inputs: - id: beszel type: BESZEL + parser: json # json | form | xml | logfmt | regex secret: "your-secret" + - id: tcp-input + type: GENERIC + address: ":9001" + delimiter: "\n" + parser: json + secret: "" # secret unused for TCP inputs outputs: - id: ops-webhook type: WEBHOOK url: "https://hooks.example.com/xyz" - template: '{"text": "{{ .Source }}: {{ .Payload }}"}' + template: + text: 'data.input + ": " + data.payload' retryCount: 3 retryDelayMs: 1000 rules: - inputId: beszel - outputIds: [ops-webhook] + engine: cel # override default engine per rule + filter: 'data.input == "BESZEL"' + mapping: + severity: '"HIGH"' + routing: + - condition: 'data.severity == "HIGH"' + outputIds: [ops-webhook] + - inputId: tcp-input + outputIds: [ops-webhook] # simple: no filter/routing, send to all storage: type: SQLITE @@ -78,14 +101,42 @@ queue: workerCount: 2 ``` -### Template Variables +### Expression Variables + +All expressions (filter, mapping, routing, template) share the same `data` context: | Variable | Description | |----------|-------------| -| `{{ .ID }}` | Alert ULID | -| `{{ .Source }}` | Source type (`BESZEL`, `DOZZLE`, etc.) | -| `{{ .Payload }}` | Raw JSON payload (string) | -| `{{ .CreatedAt }}` | Receive time (`time.Time`) | +| `data.id` | Message ULID | +| `data.input` | Input type (`BESZEL`, `DOZZLE`, `GENERIC`, etc.) | +| `data.payload` | Raw payload string | +| `data.created_at` | Receive timestamp | +| `data.` | Any field added via `mapping` expressions | + +**Filter** — boolean expression; message is dropped if `false`: +```yaml +filter: 'data.input == "BESZEL"' +``` + +**Mapping** — enrich `data` with computed fields: +```yaml +mapping: + severity: '"HIGH"' + label: 'data.input + "-alert"' +``` + +**Routing** — conditional output selection (evaluated after mapping): +```yaml +routing: + - condition: 'data.severity == "HIGH"' + outputIds: [ops-webhook] +``` + +**Template** — map of output fields rendered as expressions: +```yaml +template: + text: 'data.input + ": " + data.payload' +``` ## API @@ -113,6 +164,10 @@ Authorization: Bearer JSON messages sent after connect are processed identically to HTTP POST. +### TCP Inbound + +Connect to the configured `address` and send newline-delimited (or custom `delimiter`) messages. No token auth — secure via network policy. + ### Health Check ``` @@ -120,7 +175,7 @@ GET /healthz → 200 OK ``` -All responses include an `X-API-Version` header. +All HTTP responses include an `X-API-Version` header. ## Architecture From fa39923f9f661869ce767f8af771cc715ef0894a Mon Sep 17 00:00:00 2001 From: ppzxc Date: Sat, 21 Mar 2026 05:09:21 +0900 Subject: [PATCH 2/2] fix(readme): correct createdAt key name in expression variables table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8aaaf52..8222889 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ All expressions (filter, mapping, routing, template) share the same `data` conte | `data.id` | Message ULID | | `data.input` | Input type (`BESZEL`, `DOZZLE`, `GENERIC`, etc.) | | `data.payload` | Raw payload string | -| `data.created_at` | Receive timestamp | +| `data.createdAt` | Receive timestamp (RFC3339) | | `data.` | Any field added via `mapping` expressions | **Filter** — boolean expression; message is dropped if `false`: