Repository navigation
Expand file tree
/
Copy pathconfig.example.yaml
More file actions
379 lines (367 loc) · 19 KB
/
Copy pathconfig.example.yaml
File metadata and controls
379 lines (367 loc) · 19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
log:
level: info
format: json
telemetry:
service_name: rss2msg
# instance_id sets service.instance.id on every signal and, crucially, is added
# as a dimension/tag on CloudWatch and Graphite metrics so multiple replicas
# don't collide into one series. Defaults to OTEL_SERVICE_INSTANCE_ID, then the
# hostname. Leave unset (commented) to use the hostname.
# instance_id: ""
traces: { enabled: true }
metrics: { enabled: true }
logs: { enabled: false }
prometheus:
enabled: false
listen: ":9090"
graphite: # push metrics to a Carbon (Graphite) endpoint
enabled: false
address: "localhost:2003" # Carbon plaintext TCP endpoint (host:port)
prefix: "rss2msg" # metric-path prefix prepended to every metric
interval: 10s # push cadence (0 uses the SDK default)
sentry: # optional error/crash reporting (disabled by default)
enabled: false
# dsn: ${SENTRY_DSN} # required when enabled; falls back to SENTRY_DSN env
# environment: production # falls back to SENTRY_ENVIRONMENT
# release: ${SENTRY_RELEASE} # falls back to SENTRY_RELEASE
# server_name: "" # optional host/instance label
level: error # min log level forwarded (trace|debug|info|warn|error|fatal|panic)
sample_rate: 1.0 # error-event sampling, [0.0, 1.0]
traces_sample_rate: 0.0 # performance/transaction sampling, [0.0, 1.0]
debug: false # Sentry SDK debug logging
posthog: # optional PostHog telemetry (disabled by default)
enabled: false
# api_key: ${POSTHOG_API_KEY} # project API key; required when enabled, falls back to POSTHOG_API_KEY
endpoint: "https://us.i.posthog.com" # ingestion host; falls back to POSTHOG_ENDPOINT (EU: https://eu.i.posthog.com)
# distinct_id: "" # distinct id attached to events; defaults to service name/host
level: error # min log level forwarded; error+ becomes a $exception (Error Tracking) event
flush_interval: 0s # batch flush cadence (0 uses the SDK default)
cloudwatch: # optional AWS CloudWatch telemetry (disabled by default)
enabled: false
# region: us-east-1 # AWS region; empty uses the default SDK chain
# endpoint_url: "" # optional endpoint override (e.g. LocalStack)
logs: # ship log events to CloudWatch Logs
enabled: false
# log_group: /rss2msg/app # required when logs enabled
# log_stream: "" # defaults to the hostname
level: info # min log level forwarded (trace|debug|info|warn|error|fatal|panic)
batch_interval: 5s # flush cadence (0 uses the default)
create_group: false # auto-create the log group/stream if missing
metrics: # push metrics to CloudWatch Metrics (PutMetricData)
enabled: false
namespace: rss2msg # CloudWatch namespace
interval: 60s # push cadence (0 uses the OTEL SDK default)
http:
user_agent: "rss2msg/0.1 (+https://example.com)"
timeout: 30s
retry: # in-tick retry of a feed fetch on transient errors
max_attempts: 3 # total tries including the first; 1 disables retry
base_delay: 500ms # initial backoff (exponential, full jitter)
max_delay: 10s # cap on the backoff delay
retry:
max_attempts: 3
base_delay: 500ms
max_delay: 10s
runtime:
shutdown_drain_timeout: 30s
run_once_concurrency: 8
# Bound a single sink delivery (all retry attempts plus the DLQ handoff) so one
# wedged sink can't stall a feed's poll loop. 0 (or omitted) disables it.
deliver_timeout: 0s
heartbeat:
# Opt-in liveness signal. When enabled, emit one info log line every interval
# ("heartbeat: service alive") for as long as the service runs, so a
# quiet-but-healthy daemon still produces an alertable signal in the logs.
enabled: false
interval: 1m
health:
enabled: true
listen: ":8080" # probe listener address
liveness_path: /healthz # 200 while the process is alive
readiness_path: /readyz # 200 when started, not draining, deps reachable
startup_path: /startupz # 503 until boot completes, then 200
admin:
# Opt-in admin HTTP API: JSON introspection (status, feeds, members, health)
# and safe actions (reconcile, per-feed poll, state prune). Disabled by default.
enabled: false
listen: ":8090" # dedicated listener; required when enabled
tls:
enabled: false
cert_file: "" # server cert (PEM); required when tls.enabled
key_file: "" # server key (PEM); required when tls.enabled
client_ca_file: "" # set => require & verify client certs (mTLS)
auth:
enabled: true # ON by default; set false to deliberately disable token auth
bearer_tokens:
- name: ops
token: "${ADMIN_TOKEN}"
basic_users: []
api_keys: []
api_key_header: X-API-Key
cors:
allowed_origins: [] # empty => CORS off. e.g. ["https://ops.example.com"]
coordination:
driver: memory # memory | postgres | redis ; default memory
# postgres:
# dsn: ${POSTGRES_DSN} # optional; falls back to state.postgres.dsn
# tls: # only takes effect when DSN does not set sslmode=disable
# ca_file: /etc/ssl/pg-ca.pem
# cert_file: /etc/ssl/pg-client.pem # mTLS: both cert_file and key_file or neither
# key_file: /etc/ssl/pg-client.key
# server_name: pg.internal # SNI / cert-verify hostname override
# insecure_skip_verify: false # true = disable cert verification (test only)
# redis:
# mode: single # single (default) | sentinel | cluster
# lock_ttl: 30s # optional, default 30s
# renewal_interval: 10s # optional, default = lock_ttl / 3
# # --- single mode (default) ---
# url: ${REDIS_URL} # e.g. redis://localhost:6379/0 or rediss://...
# # --- sentinel mode (tested / supported HA path) ---
# sentinel:
# master_name: mymaster
# addrs: [sentinel-a:26379, sentinel-b:26379]
# username: redisuser # data-node credentials (master/replicas)
# password: ${REDIS_PASSWORD} # data-node credentials (master/replicas)
# sentinel_username: sentuser # sentinel-node credentials
# sentinel_password: ${SENTINEL_PASSWORD} # sentinel-node credentials
# db: 0
# # --- cluster mode (best-effort; not covered by CI integration tests) ---
# cluster:
# addrs: [node-a:6379, node-b:6379]
# username: redisuser
# password: ${REDIS_PASSWORD}
# # --- shared TLS block (applies to all modes) ---
# # For single, requires rediss:// URL. For sentinel/cluster, set server_name —
# # there is no URL to derive the SNI hostname from.
# tls:
# ca_file: /etc/ssl/redis-ca.pem
# cert_file: /etc/ssl/redis-client.pem # mTLS: both cert_file and key_file or neither
# key_file: /etc/ssl/redis-client.key
# server_name: redis.internal # SNI / cert-verify hostname override
# insecure_skip_verify: false # true = disable cert verification (test only)
# assignment: membership + rendezvous-hash partitioning so each feed is polled
# by exactly one instance. Default off (every instance polls every feed, the
# per-tick lease decides). Requires a distributed driver to have any effect.
assignment:
enabled: false
strategy: rendezvous # only "rendezvous" is supported today
heartbeat_interval: 10s
member_ttl: 30s # must exceed heartbeat_interval; dead members expire after this
rebalance_grace: 5s
state:
driver: sqlite # postgres | sqlite | dynamodb | cosmosdb
# item_ttl: 720h # retention since an item was last seen; 0/unset = keep forever (default).
# Honored by every backend: SQL sweeps; DynamoDB/Cosmos prune natively.
sqlite:
path: ./rss2msg.db # filesystem path; ":memory:" and "?_pragma=..." are accepted
# cleanup_interval: 1h # SQL-only: how often this instance sweeps expired items (default 1h when item_ttl > 0)
# postgres:
# dsn: ${POSTGRES_DSN}
# cleanup_interval: 1h # SQL-only: sweep cadence (default 1h when item_ttl > 0)
# tls: # only takes effect when DSN does not set sslmode=disable
# ca_file: /etc/ssl/pg-ca.pem
# cert_file: /etc/ssl/pg-client.pem # mTLS: both cert_file and key_file or neither
# key_file: /etc/ssl/pg-client.key
# server_name: pg.internal # SNI / cert-verify hostname override
# insecure_skip_verify: false # true = disable cert verification (test only)
# dynamodb: # shared, distributed-safe store (good with a redis/postgres coordinator)
# table: rss2msg-state # required; table with PK feed_url (S) + SK item_id (S), provisioned out of band
# region: us-east-1 # optional; SDK default chain (env / shared config) when empty
# endpoint_url: # optional; LocalStack / DynamoDB Local override
# ttl_attribute: expires_at # required when state.item_ttl is set; names the TTL attribute the table prunes on
sinks:
# A composite fans every change out to its children. Used as `default`, so
# feeds with no explicit `sinks:` list publish to both children.
- name: default
driver: composite
composite:
children: [console, pretty]
- name: console
driver: stdout
stdout:
target: stdout # stdout (default) | stderr
format: json # json (default, NDJSON) | pretty (indented)
- name: pretty
driver: stdout
stdout:
format: pretty
# The blocks below are reference examples for the other sink drivers.
# Uncomment + supply credentials, and add the sink name to a feed's
# sinks: [...] list to route to it.
# - name: pg-main
# driver: postgres
# postgres:
# dsn: ${POSTGRES_DSN}
# table: feed_changes
# tls: # custom CA / mTLS — see docs/how-to/secure-connections-tls.md#sinks
# ca_file: /etc/ssl/certs/db-ca.pem
# - name: kafka-main
# driver: kafka
# kafka:
# brokers: ["kafka-1:9092", "kafka-2:9092"]
# topic: feed.changes
# acks: all
# compression: snappy
# tls: # kafka has no URL scheme — set enabled to turn TLS on
# enabled: true
# ca_file: /etc/ssl/certs/kafka-ca.pem
# dead_letter: dlq-main
# - name: dlq-main
# driver: postgres
# postgres:
# dsn: ${POSTGRES_DSN}
# table: feed_changes_dlq
# - name: sqs-main
# driver: sqs
# sqs:
# queue_url: https://sqs.us-east-1.amazonaws.com/123456789012/feed-changes
# region: us-east-1
# # endpoint_url: http://localhost:4566 # for LocalStack
# # message_group: feed_url # FIFO only: feed_url (default) | item_id | sink
# dead_letter: dlq-main
# - name: dynamodb-main
# driver: dynamodb # datastore target (change-log table); consume via DynamoDB Streams or polling
# dynamodb:
# table: feed-changes # table must already exist
# region: us-east-1
# # endpoint_url: http://localhost:4566 # for DynamoDB Local / LocalStack
# # partition_key: feed_url # default feed_url
# # sort_key: item_id # default item_id (re-publishing the same item overwrites the row)
# # ttl_attribute: expires_at # must match the table's configured TTL attribute
# # item_ttl: 720h # detected_at + item_ttl, written as Unix epoch seconds
# dead_letter: dlq-main
# - name: sns-main
# driver: sns
# sns:
# topic_arn: arn:aws:sns:us-east-1:123456789012:feed-changes
# region: us-east-1
# # message_group: feed_url # FIFO only: feed_url (default) | item_id | sink
# dead_letter: dlq-main
# - name: pubsub-main
# driver: gcp_pubsub
# gcp_pubsub:
# project_id: my-gcp-project
# topic_id: feed-changes # topic must already exist
# # endpoint: localhost:8085 # for the Pub/Sub emulator
# # ordering_key: feed_url # ordered delivery: feed_url | item_id | sink
# dead_letter: dlq-main
# - name: dapr-main
# driver: dapr_pubsub # publish via the local Dapr sidecar; broker is chosen by Dapr component YAML
# dapr_pubsub:
# pubsub_name: rss-pubsub # Dapr pub/sub component name
# topic: rss.changes
# # address: localhost:50001 # sidecar gRPC endpoint; default = SDK env / localhost:50001
# # content_type: application/json # payload content type (default application/json)
# # metadata: # static per-publish metadata (e.g. broker partition key)
# # partitionKey: feeds
# dead_letter: dlq-main
# - name: rmq-main
# driver: amqp091
# amqp091:
# url: amqp://guest:guest@rabbit-1:5672/ # or amqps:// for TLS
# exchange: feed.changes
# exchange_type: topic # direct (default) | topic | fanout | headers
# routing_key: feed.changes
# declare: true # declare the exchange at startup
# durable: true # only meaningful when declare=true
# tls: # use an amqps:// url above; adds custom CA / mTLS
# ca_file: /etc/ssl/certs/rabbit-ca.pem
# dead_letter: dlq-main
# - name: bus-main
# driver: amqp10
# amqp10:
# url: amqps://broker:5671 # amqp:// or amqps://
# target: /queues/changes # node/queue/exchange address (required)
# username: ${AMQP_USER} # optional; else URL userinfo
# password: ${AMQP_PASS}
# tls: # use an amqps:// url; adds custom CA / mTLS
# ca_file: /etc/ssl/certs/broker-ca.pem
# dead_letter: dlq-main
# - name: stream-main
# driver: rabbitmq_stream
# rabbitmq_stream:
# uris: ["rabbitmq-stream://guest:guest@rabbit-1:5552/%2f"] # or a single url:
# stream: feed.changes # target stream (required)
# declare: true # create the stream if absent
# max_age: 168h # optional retention (declare only)
# max_length_bytes: 5368709120 # optional retention (declare only)
# # username: ${RMQ_USER} # optional; else URI userinfo
# # password: ${RMQ_PASS}
# tls: # use rabbitmq-stream+tls / IsTLS; custom CA / mTLS
# ca_file: /etc/ssl/certs/rabbit-ca.pem
# dead_letter: dlq-main
# - name: nats-main
# driver: nats
# nats:
# url: nats://nats-1:4222 # comma-separate for a cluster; tls:// for TLS
# subject: feed.changes
# # token: ${NATS_TOKEN} # at most one auth group:
# # username: rss # token | username+password | creds_file
# # password: ${NATS_PASSWORD}
# # creds_file: /etc/nats/rss.creds # JWT + NKey credentials file
# # jetstream: true # persisted, server-acked publish (stream must exist)
# # tls: # custom CA / mTLS; use a tls:// url above
# # ca_file: /etc/ssl/certs/nats-ca.pem
# dead_letter: dlq-main
# - name: asb-main
# driver: azureservicebus
# azureservicebus:
# connection_string: ${AZURE_SERVICEBUS_CONNECTION_STRING} # SAS auth; or use namespace:
# # namespace: my-bus.servicebus.windows.net # Azure AD (DefaultAzureCredential)
# queue: feed-changes # set exactly one of queue / topic
# # topic: feed-changes
# dead_letter: dlq-main
# - name: webhook
# driver: http
# http:
# url: https://example.com/hook # http:// or https://
# method: POST # POST (default) | PUT
# headers:
# Authorization: "Bearer ${HOOK_TOKEN}"
# timeout: 10s
# success_codes: [200, 201, 202, 204]
# http3: false # dial over HTTP/3 (QUIC); requires an https:// url
# tls: # for https:// targets with a private CA / mTLS
# ca_file: /etc/ssl/certs/internal-ca.pem
feeds:
- url: https://example.com/blog/rss.xml
interval: 5m
sinks: [pretty] # explicit: publish to the "pretty" sink only
- url: https://other.example/atom.xml
interval: 15m # implicit: no sinks list → falls back to "default"
http:
timeout: 10s
headers:
Authorization: "Bearer ${OTHER_FEED_TOKEN}"
retry: # per-feed override; omitted fields inherit http.retry above
max_attempts: 5
# Dynamic feed sources (optional). Ordered list; earlier entries win on URL
# collision. Omit entirely to use only the static feeds: block above. The serve
# daemon reloads feeds from these at runtime (file watch + SIGHUP) without a
# restart. Only "static", "file", "postgres", and "http" types are implemented today.
# feed_sources:
# - type: file # JSON array of feed specs; watched, reloads on change
# name: control-plane
# path: /etc/rss2msg/feeds.json
# - type: postgres # reads the feed list from a Postgres table; polled
# name: db-feeds
# interval: 30s # how often to re-read the table (min 1s)
# postgres:
# dsn: "postgres://user:pass@db:5432/rss2msg?sslmode=require"
# table: feeds # default "feeds"; columns: url (required), interval, sinks (text[])
# # query: "SELECT url, interval, sinks FROM feeds WHERE enabled" # raw SQL override (mutually exclusive with table)
# # tls:
# # ca_file: /etc/rss2msg/pg-ca.pem
# - type: http # fetches the feed list from an HTTP endpoint; polled
# name: control-plane
# interval: 30s # how often to re-fetch (min 1s)
# http:
# url: https://cp.example/feeds # required; returns {"feeds":[...]}
# timeout: 10s
# headers:
# Authorization: "Bearer ${CP_TOKEN}" # or Basic <base64>, or X-API-Key
# # tls:
# # ca_file: /etc/rss2msg/cp-ca.pem
# # cert_file: /etc/rss2msg/client.pem # cert_file + key_file: both or neither
# # key_file: /etc/rss2msg/client-key.pem
# - type: static # injects the feeds: block at this precedence position