# ============================================================================= # 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 # ----------------------------------------------------------------------------- # Redis 7 # ----------------------------------------------------------------------------- REDIS_URL=redis://localhost:6379 # 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=""`, 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= 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. # New irreversible community FI acceptances stay disabled until the fleet's # enforcement and activation baseline is verified. Disabling preserves receipts. BUZZ_NIP_FI_UPGRADES_ENABLED=false # NIP-CL compact membership discovery. Deploy fleet-wide dark before enabling. BUZZ_MEMBERSHIP_SNAPSHOT_ENABLED=false # Independent listed-open public membership lookup; also deploy disabled. BUZZ_MEMBERSHIP_PUBLIC_ENABLED=false