mirror of
https://github.com/block/buzz.git
synced 2026-11-04 16:31:38 +00:00
🤖 Adds an access log for relay reads, logins, connections and HTTP requests on the dedicated `buzz_relay::access` target. It writes to stdout, not `audit_log`, so reads never take the audit advisory lock. The log is shaped for volume: one line per HTTP request, one line per WebSocket read, and one line per WebSocket connection. ## Lines per operation | Operation | Lines | |---|---| | HTTP request on a matched route | 1 (`http`), except the skip list below | | HTTP request matching no route | 1 (`http`, `route="<unmatched>"`) at status 400 and above, otherwise none | | WebSocket connection | 1 (`close`, carrying the login) | | WebSocket `REQ` / `COUNT` / NIP-AR sync | 1 each (`req` / `count` / `sync`), plus the `buzz_ws_reads_total` counter | | WebSocket NIP-42 login | 1 (`auth`) per decided or cut-off attempt, capped after the decision as below | | Audio login | 1 (`auth`, at its final admission check) | | WebSocket `EVENT` | 1 (`Event ingested`, not on the access target) | ## HTTP requests One middleware, `http_access_log`, writes every HTTP line. It sits between `track_metrics` and `http_trace_layer`, so rebuilt errors, body-limit refusals and localhost refusals all reach it. It writes the line when the response is ready, which is not proof the body was delivered. A request dropped before then logs `request=aborted` at WARN with no `status`. - Every line carries `route` (the matched route template, never the raw path), `peer_addr` when the listener has one, `status`, `time_to_response_ms` and `http_method` (the HTTP verb). The verb stays on the line itself because the `http.request` span is absent under an access-only filter and when a pending request is dropped. - Handlers record what they learn on the request's `AccessSlot`: the login (`method`, `pubkey`, `pubkey_verified`, `community`), the decision, the filter summary, and the route's own fields. The decision is `outcome` (`success` / `denied` / `failed` / `challenged`) with a fixed `reason`, or `undecided` when the route recorded none. A refusal is final: a later admission doesn't replace it, so a replayed NIP-98 proof that verifies and is then refused logs `failed` / `nip98_replay`. - `outcome` describes the login. What the route did is in `status` and the route's fields: a rejected `/events` write logs `outcome=success` with `status=400`, `accepted=false` and its `reject_reason`. - Route fields: - `/query` and `/count`: the filter summary and `result_count`. An incremental `/query` response (`{changes:[...]}`) counts its changes, removals included. Artifact reads record their summary before the query runs, so a read dropped mid-query keeps it. - `/events`: `accepted`, `kind`, `event_id`, and on a refused write the parse `category` / `line` / `column` or a fixed `reject_reason` (such as `invalid` or `restricted`), never the ingest message. - Invite mint: `invite_id`, `expires_at` and `max_uses`. Invite claim: `joined`. - Admin feedback attachment reads: `feedback_id` and `attachment_sha256`. With `BUZZ_ADMIN_AUTH=disabled` they log `method=network_trusted` with no key, and Host or Origin refusals log their fixed reason. - Every other route logs only the login fields. - No field can hold an invite code, a token, or a Blossom read hash. Media routes log the `/media/{sha256_ext}` template. - Skip list: NIP-11 and other non-upgrade requests on `/`, `/info`, `/.well-known/nostr.json`, `/health`, `/_liveness`, `/_readiness`, and the SPA and static files the relay serves when enabled (web assets, invite landing pages, git web pages, the admin bundle) at any status. Successful WebSocket upgrades (101) log no `http` line. Refusals before the upgrade on `/` and the audio path log one, with the status actually sent. - Routes that aren't Nostr-key logins (the command-token disconnect endpoint, the workflow webhook, the git server's internal callback) log `outcome=undecided` with no key. - Dev-mode `X-Pubkey` requests log `method=x_pubkey`, `pubkey_verified=false`. - A successful `/query` or `/count` that returned nothing (`result_count=0`) logs at DEBUG: on staging, 93% of `/query` lines were empty polls. Denied, failed, aborted and error reads, reads with no `result_count`, writes and logins keep their levels. - Other successful lines and git's credential-less first request (`challenged`, with its 401) are INFO. Refusals, statuses of 400 and above, and aborted requests are WARN. In Enforce mode the line records the remapped status (for example 403). Denial bodies and headers are unchanged. ## WebSocket reads - `REQ` / `COUNT` / `sync` lines carry `pubkey`, `conn_id`, `sub_id`, the filter summary, the outcome (`served` / `denied` / `aborted` / `error` plus a fixed reason), and `events_queued` (REQ and sync) or `count` (a COUNT that counted). A `REQ` is `served` only once its EOSE is queued to the client. A read logs WARN only when it ends in `error`. `served`, `denied` and `aborted` are INFO, since `aborted` means the read ended before completing: the client disconnected or sent CLOSE, a same-ID `REQ` replaced it, or the session was cancelled. - A NIP-AR sync subscription writes its `sync` line when its initial set completes, not when live delivery ends. Its summary is the registered live filter (kinds and channels, never artifact predicates), and `events_queued` counts the initial set. - `REQ` lines stay at INFO even when empty at EOSE, because the subscription can still deliver live events later. - Filter summary, shared with HTTP `/query` and `/count`: `kinds`, channel ids, authors and `p` values each list the first 10 distinct values. The exact `kind_count` / `channel_count` / `author_count` / `p_count` (summed per filter) appears only when the list is `*` or shows fewer values than the filters held. A dimension that any filter leaves open logs `*`. `since` / `until` / `limit` take the widest bound and are absent when any filter leaves them open, so no field understates what the read covered. `ids` is a count only. NIP-50 search logs `search: true`, never the text. Only well-formed channel ids and pubkeys are listed. An artifact read's summary uses only the standard dimensions of each raw filter, so artifact predicates and client-defined tags (such as `#1`) are never logged. The summary is skipped when the access target is off. Under `=warn`, every WARN line, HTTP or WebSocket, still carries it. - Per connection, only the first 16 reads that are refused before they run (`auth_required`, quota, concurrency, overload) or denied are logged. The rest are counted as `reads_suppressed` on the close line. A refusal that arrives after the close line has been written, from a read still running when the client left, logs its own line, so every refusal is either logged or counted, never both. Served reads are never capped. ## WebSocket read counter `buzz_ws_reads_total{type="req"|"count"|"sync"}` counts every parsed `REQ`, `COUNT` and NIP-AR sync frame before admission, exported at zero from startup. It is a metric, so it counts at any `RUST_LOG`, including production's current `error`. ## WebSocket logins A NIP-42 login writes one `auth` line when it is decided. A successful login logs INFO `outcome=success` at its commit point, decided under the connection's close-reason lock, so a disconnect racing the login (session expiry, admin disconnect, revocation, ban or roster removal) either wins, and the login logs no success, or loses and closes the connection right after the success line. A refused or failed login writes one WARN `auth` line with `outcome`, a fixed `reason` from the shared `AuthReason` code set, `pubkey` and `pubkey_verified`. Root WebSocket `auth` lines carry `peer_addr`. AUTH frames after the login is decided are capped at 16 per connection, and the rest are counted as `auth_suppressed`. An attempt cut off by close or session expiry logs `aborted`. Audio logins log one `auth` line at their final admission check. ## Connections The close line is the connection's only line. It is owned by the upgraded socket, so it is written exactly once however the connection ends: a normal close, a refusal while checking the community is active (`refused=not_admitted`), the connection limit (`refused=connection_limit`), or the handler being dropped. It carries `conn_id`, `community`, `peer_addr`, `duration_ms`, the login, `events_queued` / `bytes_queued` (frames the writer accepted and flushed, not client delivery), and the two suppression counters. ## Error text - Database errors log as `DbError::kind()` (SQLSTATE or variant), including the command-executor event insert. Search errors, on both read paths and in the search-cancel task, log as `SearchError::kind()` / the same SQLx classifier. Workflow database and webhook errors log as their codes, since webhook URLs can be secrets. - Git NIP-98 failures log a fixed code. Git subprocess stderr moved to DEBUG because hook output can contain message text. ## Operator notes `.env.example` documents the target, its `event` values (`http`, `auth`, `req`, `count`, `sync`, `close`), the skip list, the levels, and how to turn it down: `buzz_relay::access=warn` keeps refusals, errors and aborted HTTP requests, and of WebSocket reads only those that end in `error`, and `=off` drops everything. It also lists the removed lines: `NIP-42 auth successful/failed`, `WebSocket connection established/closed`, `event=connect`, `HTTP bridge request`, `Event ingested via pipeline`, `relay invite minted`, `relay member added via invite`, `admin feedback attachment read`, and `addr` (now `peer_addr`). JSON lines no longer repeat the current span as `span`, since `spans` already ends with it. A line logged under an explicit `parent:` span loses that parent's fields. The default OTel export filter sets `buzz_relay::access=off`, so access lines aren't exported a second time as span events. `peer_addr` is the TCP peer. `BUZZ_CLIENT_IP_HEADER` optionally names a request header (for example `cf-connecting-ip` behind Cloudflare or `x-real-ip` behind nginx, matched case-insensitively). When set, `http`, `auth` (root and audio), `close` and connection-limit lines also log `client_ip`: the rightmost comma-separated entry of the header's last field, only if it parses as an IP address. It never replaces `peer_addr`, and it is only trustworthy when every request arrives through a proxy that overwrites the header. --------- Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <wpfleger@block.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
485 lines
27 KiB
Plaintext
485 lines
27 KiB
Plaintext
# =============================================================================
|
||
# Buzz Backend — Local Development Environment
|
||
# =============================================================================
|
||
# Copy this file to .env and adjust as needed:
|
||
# cp .env.example .env
|
||
#
|
||
# All defaults here work with `docker compose up` out of the box.
|
||
#
|
||
# Service ports (defaults):
|
||
# Postgres → localhost:5432
|
||
# Redis → localhost:6379
|
||
# Typesense → localhost:8108
|
||
# Adminer → localhost:8082 (DB browser UI)
|
||
#
|
||
# Note: If port 8082 conflicts, change the adminer port in docker-compose.yml
|
||
# =============================================================================
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Database (Postgres 17)
|
||
# -----------------------------------------------------------------------------
|
||
DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz
|
||
# Optional read-replica URL; unset/blank keeps all reads on the writer.
|
||
# READ_DATABASE_URL=postgres://buzz:buzz_dev@localhost:5433/buzz
|
||
PGHOST=localhost
|
||
PGPORT=5432
|
||
PGUSER=buzz
|
||
PGPASSWORD=buzz_dev
|
||
PGDATABASE=buzz
|
||
|
||
# Fleet usage telemetry is replica-only and independent of serving-read
|
||
# routing. This freshness budget defaults to 30000ms; 0 disables the database
|
||
# telemetry families. When the reader is absent, stale, or unavailable, the
|
||
# relay skips the collection and reports its availability as 0 instead of
|
||
# falling back to the writer.
|
||
# BUZZ_USAGE_METRICS_REPLICA_MAX_AGE_MS=30000
|
||
# Per-community metric series are disabled by default to bound relay memory and
|
||
# monitoring cardinality. `all` is the temporary rollback mode.
|
||
# BUZZ_USAGE_METRICS_PER_COMMUNITY=off
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Redis 7
|
||
# -----------------------------------------------------------------------------
|
||
REDIS_URL=redis://localhost:6379
|
||
# Max connections in the relay's shared Redis pool (default 16).
|
||
# BUZZ_REDIS_POOL_SIZE=16
|
||
|
||
# Max connections in each of the relay's Postgres pools — writer and, when
|
||
# READ_DATABASE_URL is set, reader (default 50).
|
||
# BUZZ_DB_POOL_SIZE=50
|
||
# Read-only partition catalog audit interval, clamped to 60s..24h (default 900s).
|
||
# BUZZ_PARTITION_AUDIT_INTERVAL_SECS=900
|
||
# Emergency kill switch for all automatic partition DDL, including catch-all
|
||
# advancement; audits remain enabled.
|
||
# BUZZ_PARTITION_MANAGER_CREATE_ENABLED=true
|
||
# Replace an empty partition catch-all with six months of dedicated monthlies,
|
||
# at startup and on every audit interval (default true). Has no effect while the
|
||
# kill switch above is off.
|
||
# BUZZ_PARTITION_MANAGER_ADVANCE_ENABLED=true
|
||
|
||
# Writer-session Postgres timeouts for buzz-db-backed pools and the relay audit
|
||
# pool, all in milliseconds; 0 disables the lock and idle timeouts. The
|
||
# separately deployed push gateway owns its own database and session policy
|
||
# and does not consume these knobs.
|
||
# lock_timeout: fail a statement that waits this long on any lock instead of
|
||
# parking behind a wedged holder (default 5000).
|
||
# BUZZ_DB_LOCK_TIMEOUT_MS=5000
|
||
# idle_in_transaction_session_timeout: reap sessions idle inside an open
|
||
# transaction — bounds how long a wedged client can hold locks (default 60000).
|
||
# BUZZ_DB_IDLE_TXN_TIMEOUT_MS=60000
|
||
# statement_timeout: cap any single statement's runtime (default 120000). A
|
||
# session cannot disable it: 0 or anything above 300000 installs 300000 (5
|
||
# minutes) and logs a warning at startup. Migrations, schema destruction, and
|
||
# community-deletion inventory and purge transactions lift it for themselves.
|
||
# Generic event reads (REQ history, /query, /count) and read-replica sessions
|
||
# are further capped at 20s. Warning: a pathologically low value (e.g. 1) also
|
||
# times out connection setup and can prevent any DB connection from
|
||
# establishing.
|
||
# BUZZ_DB_STATEMENT_TIMEOUT_MS=120000
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Typesense (search)
|
||
# -----------------------------------------------------------------------------
|
||
TYPESENSE_API_KEY=buzz_dev_key
|
||
TYPESENSE_URL=http://localhost:8108
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Relay (WebSocket server)
|
||
# -----------------------------------------------------------------------------
|
||
# Bind address for the relay (host:port)
|
||
BUZZ_BIND_ADDR=0.0.0.0:3000
|
||
# Public WebSocket URL — used in NIP-42 auth challenges
|
||
RELAY_URL=ws://localhost:3000
|
||
# Stable relay signing key (required). `just bootstrap` generates a random key in
|
||
# the gitignored .env file. Preserve that value across restarts and backups.
|
||
# BUZZ_RELAY_PRIVATE_KEY=<32-byte hex private key>
|
||
# Optional: path to the web UI dist directory. When set, the relay serves
|
||
# the web frontend at / for browser requests. Leave unset for local dev
|
||
# (use `just web` for Vite HMR instead).
|
||
# BUZZ_WEB_DIR=./web/dist
|
||
|
||
# NIP-PL mobile push is an explicit deployment opt-in. A gateway URL alone
|
||
# never enables it. When enabled, the exact HTTP(S) delivery URL is required.
|
||
# Rollback requires replacing every enabled writer/worker process. Inspect and
|
||
# explicitly decide retained queue disposition before re-enabling (see docs/push-gateway-deployment.md).
|
||
BUZZ_PUSH_ENABLED=false
|
||
BUZZ_PUSH_GATEWAY_DELIVERY_URL=https://push.buzz.xyz/v1/deliveries/apns
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Admin Dashboard (private moderation surface)
|
||
# -----------------------------------------------------------------------------
|
||
# Host name that serves the moderation dashboard and its /api/admin/v1
|
||
# endpoints. Leave unset to keep the admin surface absent.
|
||
# BUZZ_ADMIN_HOST=admin.localhost:3000
|
||
#
|
||
# Authentication mode. Accepted values: nip98 (default), disabled.
|
||
# Any other value is a startup error. Token authentication was removed:
|
||
# BUZZ_ADMIN_TOKEN is ignored with a startup warning — remove it from the environment.
|
||
# BUZZ_ADMIN_AUTH=nip98
|
||
#
|
||
# Option A — BUZZ_ADMIN_AUTH=nip98 (Nostr pubkey-based auth, default):
|
||
# NIP-98 HTTP Auth. Each request must carry an Authorization: Nostr header
|
||
# with a signed kind-27235 event. Authorized principals are resolved from:
|
||
# 1. RELAY_OPERATOR_PUBKEYS — comma-separated 64-char hex pubkeys (config Operators).
|
||
# 2. RELAY_OWNER_PUBKEY — implicit Operator fallback when RELAY_OPERATOR_PUBKEYS is unset.
|
||
# 3. relay_operators table — DB-managed Operator/Moderator roster.
|
||
# The dashboard requires a NIP-07 browser extension.
|
||
# Setting RELAY_OPERATOR_PUBKEYS for the admin console does NOT require
|
||
# RELAY_OPERATOR_API_ORIGIN; that origin is only for community provisioning
|
||
# (see below). When BUZZ_ADMIN_HOST is set, the relay advertises the admin
|
||
# origin in its NIP-11 document (`admin_api` field) so clients can auto-discover
|
||
# the console without manual URL entry.
|
||
# RELAY_OPERATOR_PUBKEYS=<64-char hex pubkey>[,<64-char hex pubkey>...]
|
||
#
|
||
# Option B — BUZZ_ADMIN_AUTH=disabled (read-only, no authentication):
|
||
# Anyone who can reach the relay can read, in every community: reports (with
|
||
# reporter notes and reported message content, deleted included), feedback and
|
||
# attachments, bans and timeouts with reasons, the community directory, member
|
||
# search and profiles (not staff status), and any stored message by ID.
|
||
# Writes and staffing are refused.
|
||
# Only people allowed to see all of that should be able to reach it. The admin
|
||
# API is on the relay's main listener and its hostname check uses the caller's
|
||
# Host header, so the whole relay port must be private, not just the admin
|
||
# hostname. The relay logs a WARN on every startup.
|
||
# `just admin` defaults to this mode for local review.
|
||
#
|
||
# Directory holding the built dashboard assets (`pnpm -C admin-web build`).
|
||
# BUZZ_ADMIN_WEB_DIR=./admin-web/dist
|
||
#
|
||
# Canonical origin (http(s)://host[:port], no path) that community-provisioning
|
||
# NIP-98 requests are verified against. Required only to USE the provisioning
|
||
# endpoints (POST /operator/communities) — not for the admin console. When
|
||
# RELAY_OPERATOR_PUBKEYS is set but this is unset, the relay boots with a WARN
|
||
# and provisioning requests fail closed until it is set.
|
||
# RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000
|
||
|
||
# Deployment-global operator-listener routes. Each configured Nostr identity
|
||
# may register target pubkeys and receives mention notifications at its URL.
|
||
# URLs may use HTTP or HTTPS. Use HTTP only for operator-controlled internal
|
||
# connections: NIP-98 signs requests but does not encrypt them, and
|
||
# notifications carry full message content.
|
||
# Format: pubkey:deliveryUrl;pubkey:deliveryUrl
|
||
# BUZZ_OPERATOR_LISTENER_TIMEOUT_MS=5000
|
||
# BUZZ_OPERATOR_LISTENERS=<64-char hex pubkey>:https://listener.example/mentions
|
||
|
||
# Optional endpoint that receives each new per-community storage snapshot
|
||
# written by `buzz-admin storage-snapshot`, in NIP-98 relay-signed POST batches
|
||
# shaped like kgoose's storage-usage record request. One relay pod checks for a
|
||
# new snapshot every 5 minutes. Receivers must treat a repeated community and
|
||
# measured_at as already recorded.
|
||
# BUZZ_STORAGE_USAGE_URL=https://kgoose.example/cash-app/goose/v1/buzz/storage-usage/record
|
||
|
||
# Optional relay-owned KLIPY key. When set, NIP-11 advertises GIF search and
|
||
# authenticated desktop clients use this relay as the metadata/search proxy.
|
||
# Keep the real value in your deployment's secret manager; never commit it.
|
||
# BUZZ_KLIPY_API_KEY=
|
||
|
||
# Shared Redis-backed admission limits. Defaults shown below; each value must
|
||
# be a positive integer.
|
||
# BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=60
|
||
# BUZZ_RATE_LIMIT_GIF_SEARCHES_PER_MIN=30
|
||
# BUZZ_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN=300
|
||
# BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=10
|
||
# BUZZ_RATE_LIMIT_AGENT_STANDARD_MESSAGES_PER_MIN=120
|
||
# BUZZ_RATE_LIMIT_AGENT_STANDARD_API_CALLS_PER_MIN=600
|
||
# BUZZ_RATE_LIMIT_AGENT_ELEVATED_MESSAGES_PER_MIN=300
|
||
# BUZZ_RATE_LIMIT_AGENT_PLATFORM_MESSAGES_PER_MIN=600
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Git (NIP-34 bare repositories)
|
||
# -----------------------------------------------------------------------------
|
||
# Root directory for ephemeral Git workspaces and the disposable pack cache.
|
||
# Default: ./repos (relative to CWD).
|
||
# BUZZ_GIT_REPO_PATH=./repos
|
||
# BUZZ_GIT_MAX_PACK_BYTES=524288000
|
||
# BUZZ_GIT_MAX_REPO_BYTES=1048576000
|
||
# Process-local immutable pack/index cache. Zero disables retention.
|
||
# BUZZ_GIT_PACK_CACHE_PATH=./repos/.pack-cache
|
||
# BUZZ_GIT_PACK_CACHE_MAX_BYTES=5368709120
|
||
# BUZZ_GIT_PACK_CACHE_MAX_CONCURRENT_POPULATIONS=2
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# S3-Compatible Object Storage (media + Git/CAS)
|
||
# -----------------------------------------------------------------------------
|
||
# The local MinIO container is reachable from host processes at localhost:9000.
|
||
# Path style keeps the bucket in the URL path and is required by this local DNS
|
||
# setup. Use `virtual` only when the provider requires bucket-as-subdomain URLs.
|
||
BUZZ_S3_ENDPOINT=http://localhost:9000
|
||
BUZZ_S3_ACCESS_KEY=buzz_dev
|
||
BUZZ_S3_SECRET_KEY=buzz_dev_secret
|
||
BUZZ_S3_BUCKET=buzz-media
|
||
BUZZ_S3_REGION=us-east-1
|
||
BUZZ_S3_ADDRESSING_STYLE=path
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Media Upload Admission
|
||
# -----------------------------------------------------------------------------
|
||
# Bound image/video parser and storage work admitted by one relay process.
|
||
# BUZZ_MEDIA_MAX_CONCURRENT_UPLOADS=8
|
||
# BUZZ_MEDIA_MAX_CONCURRENT_UPLOADS_PER_PUBKEY=2
|
||
# BUZZ_MEDIA_UPLOADS_PER_MINUTE=30
|
||
# GET/HEAD /media/* always require Blossom t=get auth and relay membership.
|
||
# BUZZ_REQUIRE_MEDIA_GET_AUTH and BUZZ_REQUIRE_MEDIA_READ_AUTH are no longer
|
||
# read; setting either (including to false) changes nothing and the relay warns
|
||
# about it at startup.
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Ephemeral Channels (TTL testing)
|
||
# -----------------------------------------------------------------------------
|
||
# Override the TTL for all ephemeral channels (in seconds). When set, any
|
||
# channel created with a TTL tag will use this value instead of the
|
||
# client-provided one. Unset to use the client-provided TTL.
|
||
# BUZZ_EPHEMERAL_TTL_OVERRIDE=60
|
||
|
||
# How often the reaper checks for expired ephemeral channels (default: 60s).
|
||
# BUZZ_REAPER_INTERVAL_SECS=5
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Logging / Tracing
|
||
# -----------------------------------------------------------------------------
|
||
RUST_LOG=buzz_relay=debug,buzz_datastore=info,buzz_db=debug,buzz_auth=debug,buzz_pubsub=debug,tower_http=debug
|
||
# Optional OpenTelemetry-only target filter. This is deliberately independent
|
||
# from RUST_LOG so log verbosity changes cannot break trace parentage.
|
||
# BUZZ_OTEL_FILTER=buzz_relay=info,buzz_relay::access=off,buzz_datastore=info
|
||
#
|
||
# Access log: the relay writes JSON lines for security-relevant actions under
|
||
# the `buzz_relay::access` target, with an `event` field of `http`, `auth`,
|
||
# `req`, `count`, `sync`, or `close`. `http` is one line per HTTP request, written
|
||
# by one layer when the response is ready (not proof the body was delivered):
|
||
# `route` is the matched route template (never the raw path), with the response
|
||
# `status` and `time_to_response_ms` on every line. It carries the login
|
||
# (`method`, `pubkey`, `pubkey_verified`) and `outcome` (`success`, `denied`,
|
||
# `failed`, `challenged`, or `undecided` when the route recorded no decision; a
|
||
# refusal is final). `/query` and `/count` add the filter summary and
|
||
# `result_count`; `/events` adds `accepted`, `kind`, `event_id` and a fixed
|
||
# `reject_reason` code; invite and admin attachment routes add their own fields
|
||
# (`invite_id`, `expires_at`, `max_uses`, `joined`, `feedback_id`,
|
||
# `attachment_sha256`). Invite codes, tokens and Blossom read hashes are never
|
||
# logged. An attachment read with admin auth disabled logs
|
||
# `method=network_trusted` and no key. A request dropped before its response
|
||
# logs `request=aborted` with no `status`.
|
||
# Every matched route is logged except `/` (non-upgrade), `/info`,
|
||
# `/.well-known/nostr.json`, `/health`, `/_liveness` and `/_readiness`.
|
||
# Requests matching no route log only at status 400 and above, as
|
||
# `route="<unmatched>"`, except that the SPA and static files the relay
|
||
# serves when enabled (web assets, invite landing pages, git web pages, the
|
||
# admin bundle) log nothing at any status, including 404. Successful WebSocket
|
||
# upgrades log no `http` line.
|
||
# `auth` is one line per WebSocket login, written when it succeeds or is
|
||
# refused; every root WebSocket `auth` line carries the TCP `peer_addr`.
|
||
# `req` / `count` / `sync` is one line per WebSocket read (a NIP-AR sync
|
||
# subscription is a `sync` read), with the same filter summary
|
||
# (search text is never logged, only `search: true`). A `sync` line is written
|
||
# when its initial set completes, not when live delivery ends: its summary is
|
||
# the registered live filter (kinds and channels, never artifact predicates)
|
||
# and `events_queued` counts the initial set. `buzz_ws_reads_total` counts
|
||
# parsed reads by `type` (`req`, `count`, `sync`). The summary lists up to
|
||
# 10 kinds, channel ids, authors and `p` values each; `kind_count`,
|
||
# `channel_count`, `author_count` and `p_count` appear only when the list is
|
||
# `*` or shows fewer values than the filters held. `close` is the
|
||
# connection's one line: duration, totals, the login's `pubkey`, `method` and
|
||
# `pubkey_verified`, and `reads_suppressed` / `auth_suppressed` for repeated
|
||
# refusals past the first 16. It is written however the connection ends, with
|
||
# `refused` (`not_admitted`, `connection_limit`) when the relay refused it
|
||
# before serving it. Refusals that arrive after `close` has been written log
|
||
# their own lines.
|
||
# Successful lines and git's credential-less first request
|
||
# (`outcome=challenged`) are INFO; refusals, error statuses and aborted
|
||
# requests are WARN. A successful `/query` or `/count` that returned nothing
|
||
# (`result_count=0`) is DEBUG, since polling clients make most of them. A
|
||
# WebSocket read is WARN only when it ends in `error`;
|
||
# refused and `aborted` reads stay INFO, since `aborted` means the read ended
|
||
# before completing: the client closed the connection or sent CLOSE, a same-ID
|
||
# REQ replaced it, or the session was cancelled, all of which are routine. Turn it down with
|
||
# `buzz_relay::access=warn` (keeps refusals, errors and aborted HTTP requests,
|
||
# and of WebSocket reads only `error` outcomes) or `buzz_relay::access=off` in
|
||
# RUST_LOG.
|
||
# It is off in the OTel export by default so lines are not shipped twice.
|
||
# Removed or renamed: `NIP-42 auth successful/failed`, `WebSocket connection
|
||
# established/closed` and the `event=connect` line (folded into `close`), `HTTP
|
||
# bridge request` (folded into the bridge's `http` line), `Event ingested via
|
||
# pipeline` (the WebSocket `Event ingested` line and the `/events` line carry
|
||
# its fields), `relay invite minted`, `relay member added via invite` and `admin
|
||
# feedback attachment read` (folded into the route's `http` line), the `addr`
|
||
# field, now `peer_addr`, and the JSON `span` field (`spans` already ends with
|
||
# the current span; a line logged under an explicit parent span loses that
|
||
# parent's fields). Git subprocess stderr moved from WARN to DEBUG because hook
|
||
# output can contain message text.
|
||
|
||
# Request header holding the real client IP, logged as `client_ip` on `http`,
|
||
# `auth`, `close` and connection-limit lines next to `peer_addr` (optional,
|
||
# case-insensitive; unset or empty turns it off). A list value logs its
|
||
# rightmost entry; a value that isn't an IP address is not logged. Set it only
|
||
# when every request reaches the relay through a proxy that overwrites this
|
||
# header (for example `cf-connecting-ip` behind Cloudflare or `x-real-ip`
|
||
# behind nginx); otherwise clients can forge it.
|
||
# BUZZ_CLIENT_IP_HEADER=x-real-ip
|
||
|
||
# OTLP tracing endpoint (optional — leave unset to disable)
|
||
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# ACP (Agent Communication Protocol — buzz-acp harness)
|
||
# -----------------------------------------------------------------------------
|
||
# The ACP harness bridges Buzz events to AI agents. Each env var below maps
|
||
# to a CLI flag of the same name (lowercase, hyphens → underscores). All values
|
||
# are optional unless noted; defaults are shown in comments.
|
||
#
|
||
# Quick start:
|
||
# BUZZ_PRIVATE_KEY=<hex> BUZZ_RELAY_URL=ws://localhost:3000 buzz-acp
|
||
|
||
# ── Identity & auth ──────────────────────────────────────────────────────────
|
||
# Nostr private key (hex or bech32). REQUIRED — identifies the agent on the relay.
|
||
# BUZZ_PRIVATE_KEY=<32-byte hex or nsec1… private key>
|
||
|
||
# Relay WebSocket URL the harness connects to.
|
||
# Note: the relay itself uses RELAY_URL (above); this is the ACP harness's
|
||
# connection target — they happen to point at the same place in local dev.
|
||
# BUZZ_RELAY_URL=ws://localhost:3000
|
||
|
||
# ── Agent subprocess ─────────────────────────────────────────────────────────
|
||
# Binary to spawn as the AI agent (e.g. "goose", "codex-acp", "claude-code").
|
||
# BUZZ_ACP_AGENT_COMMAND=goose
|
||
|
||
# Comma-separated arguments passed to the agent binary.
|
||
# Goose default: "acp". Codex/Claude default: "" (empty).
|
||
# BUZZ_ACP_AGENT_ARGS=acp
|
||
|
||
# Binary for an optional MCP server sidecar (e.g. buzz-dev-mcp for buzz-agent).
|
||
# BUZZ_ACP_MCP_COMMAND=
|
||
|
||
# Number of parallel agent subprocesses (1–32).
|
||
# BUZZ_ACP_AGENTS=1
|
||
|
||
# Desired LLM model ID. Applied to every new ACP session.
|
||
# Use `buzz-acp models` to discover available model IDs.
|
||
# BUZZ_ACP_MODEL=
|
||
|
||
# Optional Databricks model-picker visibility filter. Discovery-only; this does
|
||
# not grant inference access. Comma-separated full-string * / ? patterns are
|
||
# OR-matched against raw workspace endpoint and Unity Catalog model-service IDs.
|
||
# Unset or blank shows every catalog entry. A nonblank value with no usable patterns is invalid.
|
||
# DATABRICKS_MODEL_FILTER=databricks-*,data_tools.goose.*
|
||
|
||
# ── Timeouts & sessions ──────────────────────────────────────────────────────
|
||
# Max seconds per agent turn before timeout (default 320 = ~5 min).
|
||
# BUZZ_ACP_TURN_TIMEOUT=320
|
||
|
||
# Max turns per session before proactive rotation. 0 = disabled (rotate only
|
||
# on MaxTokens / MaxTurnRequests). Recommended: 50 for long-running agents.
|
||
# BUZZ_ACP_MAX_TURNS_PER_SESSION=0
|
||
|
||
# ── Prompts ──────────────────────────────────────────────────────────────────
|
||
# System prompt injected into every agent session (inline text).
|
||
# BUZZ_ACP_SYSTEM_PROMPT=
|
||
|
||
# Path to a file containing the system prompt (mutually exclusive with above).
|
||
# BUZZ_ACP_SYSTEM_PROMPT_FILE=
|
||
|
||
# Message sent to the agent immediately after session creation.
|
||
# BUZZ_ACP_INITIAL_MESSAGE=
|
||
|
||
# ── Heartbeat ────────────────────────────────────────────────────────────────
|
||
# Seconds between heartbeat prompts. 0 = disabled. Must be 0 or ≥10.
|
||
# Recommended: 60 for long-running agents to prevent idle session timeouts.
|
||
# BUZZ_ACP_HEARTBEAT_INTERVAL=0
|
||
|
||
# Heartbeat prompt text (inline). Mutually exclusive with file variant.
|
||
# BUZZ_ACP_HEARTBEAT_PROMPT=
|
||
|
||
# Path to a file containing the heartbeat prompt.
|
||
# BUZZ_ACP_HEARTBEAT_PROMPT_FILE=
|
||
|
||
# ── Desktop development ──────────────────────────────────────────────────────
|
||
# DEV-only: replay first-run onboarding and the Welcome Team kickoff on each
|
||
# app launch while keeping the current identity and relay data.
|
||
# VITE_BUZZ_FORCE_FRESH_ONBOARDING=true
|
||
|
||
# Protected internal builds only: selects the module graph that contains the
|
||
# default-off Bestie experiment. Official OSS builds must leave this unset.
|
||
# VITE_BUZZ_BESTIE=1
|
||
|
||
# ── Subscription & filtering ─────────────────────────────────────────────────
|
||
# Subscribe mode: "mentions" (default), "all", or "config" (rule-based).
|
||
# BUZZ_ACP_SUBSCRIBE=mentions
|
||
|
||
# Comma-separated event kind numbers to subscribe to (overrides mode defaults).
|
||
# BUZZ_ACP_KINDS=
|
||
|
||
# Comma-separated channel UUIDs to limit subscription scope.
|
||
# BUZZ_ACP_CHANNELS=
|
||
|
||
# Set to true to disable the @-mention filter in mentions mode.
|
||
# BUZZ_ACP_NO_MENTION_FILTER=false
|
||
|
||
# Path to TOML config file for rule-based subscriptions (config mode).
|
||
# BUZZ_ACP_CONFIG=./buzz-acp.toml
|
||
|
||
# ── Dedup & self-ignore ──────────────────────────────────────────────────────
|
||
# How to handle duplicate events: "queue" (default) or "drop".
|
||
# BUZZ_ACP_DEDUP=queue
|
||
|
||
# Set to true to process the agent's own messages (default: ignore self).
|
||
# BUZZ_ACP_NO_IGNORE_SELF=false
|
||
|
||
# ── Session scoping ──────────────────────────────────────────────────────────
|
||
# How ACP provider sessions are scoped in channels: "channel" (default) or
|
||
# "thread". "channel" keeps one provider session per channel (legacy). "thread"
|
||
# gives each canonical channel thread its own isolated provider session; direct
|
||
# messages stay conversation-scoped either way. Ships as "channel" so thread
|
||
# scoping can be canaried and rolled back without code changes.
|
||
# BUZZ_ACP_SESSION_POLICY=channel
|
||
|
||
# ── Context ──────────────────────────────────────────────────────────────────
|
||
# Max context messages fetched for thread replies and DMs (0–100). 0 = disabled.
|
||
# BUZZ_ACP_CONTEXT_MESSAGE_LIMIT=12
|
||
|
||
# ── Presence & typing ────────────────────────────────────────────────────────
|
||
# Set to true to disable automatic online/offline presence status.
|
||
# BUZZ_ACP_NO_PRESENCE=false
|
||
|
||
# Set to true to disable typing indicators while the agent is processing.
|
||
# BUZZ_ACP_NO_TYPING=false
|
||
|
||
# ── Advanced tuning ──────────────────────────────────────────────────────────
|
||
# Event channel buffer capacity (WebSocket → harness). Increase for
|
||
# high-throughput agents. Minimum 1.
|
||
# BUZZ_ACP_EVENT_BUFFER=256
|
||
|
||
# ── Legacy aliases ───────────────────────────────────────────────────────────
|
||
# These are accepted for backward compatibility but the canonical names above
|
||
# are preferred:
|
||
# BUZZ_ACP_PRIVATE_KEY → BUZZ_PRIVATE_KEY
|
||
|
||
# Optional relay join policy. Markdown is served by the relay so every join
|
||
# surface can present the same documents. Each document and the independent age
|
||
# attestation are optional; configuring any one enables policy acceptance.
|
||
# BUZZ_TERMS_OF_SERVICE_MARKDOWN="# Terms of Service\n\nFull terms here."
|
||
# BUZZ_PRIVACY_POLICY_MARKDOWN="# Privacy Policy\n\nFull policy here."
|
||
# BUZZ_AGE_ATTESTATION_REQUIRED=true
|
||
|
||
# ── NIP-FI database community source ─────────────────────────────────────────
|
||
# Where NIP-FI communities come from: "env" (BUZZ_NIP_FI_COMMUNITIES, default)
|
||
# or "database" (bindings created by provisioning; needs
|
||
# BUZZ_NIP_FI_PROVISION_BINDING and must not be combined with
|
||
# BUZZ_NIP_FI_COMMUNITIES). See docs/operator-nip-fi-bindings.md.
|
||
# BUZZ_NIP_FI_COMMUNITY_SOURCE=env
|
||
# Most community-issuer rows one snapshot may hold (1..=10000000).
|
||
# BUZZ_NIP_FI_SNAPSHOT_MAX_ROWS=1000000
|
||
# Snapshot load timeout in ms; must be under the 30s refresh interval.
|
||
# BUZZ_NIP_FI_SNAPSHOT_REFRESH_TIMEOUT_MS=10000
|
||
# Hosts whose "not found" is cached for 5s (1..=1000000).
|
||
# BUZZ_NIP_FI_RESOLVER_NEGATIVE_CACHE_MAX=100000
|
||
# Concurrent live lookups; clamped below the database pool size.
|
||
# BUZZ_NIP_FI_RESOLVER_MAX_INFLIGHT=8
|
||
|
||
# Optional absolute legacy-v1 invite cutoff, UTC RFC3339 whole seconds.
|
||
# Unset preserves natural token expiry; never changes v2 invitations.
|
||
# BUZZ_INVITE_V1_INVALID_AFTER=2026-12-01T00:00:00Z
|
||
|
||
# Optional NIP-FI historical verified profiles (disabled by default): issuer JSON
|
||
# entries may set public_profile=true, public_issuer_label="Example Corp", and
|
||
# verified_at_refresh_seconds=3600 (integer 0..86400). Requires a stable relay
|
||
# key and NIP-FI enforcement. Profiles are historical records and are never
|
||
# retracted.
|