Something went wrong. Try again.
Read-only ActivityPub → atproto bridge for the threadiverse using Coves lexicons
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143# Tidepool production environment — copy to .env on the server# (/opt/tidepool/.env) and fill in real values. Compose reads it# automatically. NEVER commit the filled-in file.
# Postgres (container-internal; not exposed on the host)POSTGRES_DB=tidepoolPOSTGRES_USER=tidepool# Generate with: openssl rand -hex 24POSTGRES_PASSWORD=CHANGE_ME
# Public domain of the bridge. Handles are subdomains two levels below# (alice.lemmy-world.tdpl.io), so DNS needs BOTH the apex A record and a# wildcard: tdpl.io -> server IP, *.tdpl.io -> server IP. The wildcard MUST# resolve directly to the server (Cloudflare: DNS-only / grey cloud) —# Caddy issues per-handle certificates on demand via HTTP-01, which a# proxying CDN edge breaks.BRIDGE_HOSTNAME=tdpl.io
# Origin the NATIVE Coves users' ActivityPub actors are served under — the# second origin on the bridge's listener, split from BRIDGE_HOSTNAME by a Host# router. REQUIRED IN PRODUCTION: unset, the process refuses to start with# config: AP_USER_ORIGIN is required in production# (this is why a HEAD deploy onto a pre-v2 .env fails at boot — fail closed).# Must be https here, and must not be BRIDGE_HOSTNAME or a subdomain of it.## Baked into every actor_id minted under it. Serving reads the stored actor_id,# never this value, so changing it does NOT move existing actors — it only# changes where new ones appear. Treat it as permanent.# Caddy must proxy this origin's AP paths to the bridge; see DEPLOY.md.## NO TRAILING SLASH. It must be a bare scheme://host — any path (including a# lone "/"), query, fragment, or userinfo is refused at boot.AP_USER_ORIGIN=https://coves.social
# 32-byte key-encryption key sealing per-actor signing keys at rest# (AES-256-GCM). Generate with: openssl rand -hex 32# Losing it means losing every repo's signing keys — back it up.# CHANGING IT IS A PROCEDURE, NOT AN EDIT. Set BRIDGE_KEK_PREVIOUS below and# follow the runbook; editing this value on its own orphans every sealed key,# across THREE tables, with no recovery:# - bridged_actors.signing_key every bridged (Lemmy-origin) identity's# escrowed secp256k1 atproto repo key# - service_keys.key_material the 'plc-rotation' row: the PLC escrow# key, the only DID recovery path# - ap_actors.rsa_key_sealed EVERY NATIVE COVES USER'S AP signing# key (v2 / task 13). Easy to omit from a# rotation or backup plan written before# v2 existed — and it is the entire# native-user population.BRIDGE_KEK=CHANGE_ME
# The KEK being rotated AWAY from. Unset in steady state; set only for the# length of a rotation, during which the bridge opens material under either key# and `tidepool rotate-kek` moves every sealed blob onto BRIDGE_KEK. Unset it# again only after the walk reports zero re-seals and zero failures — until# then it is the only key some rows still open under.# Full procedure: DEPLOY.md §6, "Runbook: rotating BRIDGE_KEK".#BRIDGE_KEK_PREVIOUS=
# Bearer token for the /admin API. Generate with: openssl rand -hex 32ADMIN_TOKEN=CHANGE_ME
# Relays to announce the bridge to on startup (requestCrawl). bsky.network# is wider-network visibility only (it caps new PDSs at 100 accounts; our# repos past the quota are throttled/dropped there). The app path into# Coves is the self-hosted relay+jetstream in docker-compose.prod.yml,# which is NOT listed here — production requestCrawl egress is# SSRF-guarded (refuses private addresses); it's bootstrapped once via# localhost:2470 instead (SELF_HOSTED_RELAY.md).RELAY_HOSTS=https://bsky.network
# Admin password for the self-hosted relay's /admin API + web UI# (host-local port 2470). Generate with: openssl rand -hex 32RELAY_ADMIN_PASSWORD=CHANGE_ME
# Task-14 Jetstream consumer: native Coves users' opt-outs, profiles, posts,# comments and votes flowing outward. DEFAULT OFF. The seams it feeds are now# all wired — outbound delivery (15), the acceptance engine (16), echo# suppression and moderation (17) — so turning it on runs the REAL pipeline:# postv2 is admitted and a community-signed acceptance written, opt-out# deleteRemote and confirmed account deletions actually purge at peers. It is# still default-off because it writes durable outbound state, and because# nothing about being able to run it makes it safe to run it un-canaried.# Read the staged rollout in DEPLOY.md before flipping this.CONSUMER_ENABLED=false
# The self-hosted Jetstream the consumer subscribes to (ws:// or wss://).# REQUIRED when CONSUMER_ENABLED=true; validated at boot whenever set, so a bad# value fails fast instead of becoming a silent reconnect loop. Safe to stage# ahead of flipping CONSUMER_ENABLED — which is the point of staging it.## Points at the jetstream service in docker-compose.prod.yml, which listens on# :8080 (JETSTREAM_ADDR). :6060 is its debug/metrics listener and serves no# /subscribe. Compose already defaults this; uncomment only to override.# JETSTREAM_URL=ws://tidepool-prod-jetstream:8080/subscribe
# Delivery workers. 0 = OFF, and AND-gated on CONSUMER_ENABLED — there is no# OUTBOUND_ENABLED. With the consumer on and this at 0, outbound intent# accumulates durably and nothing is POSTed; raising it starts draining that# backlog immediately, so raise it deliberately.OUTBOUND_WORKERS=0
# Kill switches. A blocked delivery is PARKED — still pending, resumed when the# switch clears — never dropped. Read once at boot; changing one needs a# service recreate, not a signal.# OUTBOUND_DRY_RUN translate + log, POST nothing# OUTBOUND_DISABLED global stop# OUTBOUND_DISABLED_HOSTS comma-separated inbox hosts (lowercased)# OUTBOUND_DISABLED_COMMUNITIES comma-separated community AP ids, EXACT# match ("https://lemmy.world/c/comicstrips")# OUTBOUND_DISABLED_ACTORS comma-separated actor DIDs, EXACT match# There is no allowlist form: a one-community canary is spelled by disabling# every OTHER community in the follow list. See DEPLOY.md.# OUTBOUND_DRY_RUN=false# OUTBOUND_DISABLED=false# OUTBOUND_DISABLED_HOSTS=# OUTBOUND_DISABLED_COMMUNITIES=# OUTBOUND_DISABLED_ACTORS=
# Acceptance-engine flood cap: how many posts one native author may have# accepted into one bridged community. 0 = unlimited. Tidepool signs the# community's acceptance, so this bounds what it vouches for.# ADMISSION_MAX_PER_AUTHOR_PER_COMMUNITY=50
# Reconciliation sweep cadence. NOT an on/off switch: the sweep is wired# unconditionally, runs once at startup, and 0 is refused. It is read-only# (reports divergence, repairs nothing), so leaving it alone is correct.# DIVERGENCE_INTERVAL=15m# DIVERGENCE_ACCEPTANCE_STALE_AFTER=12h
# Bridged handle resolution over DNS. Empty DNS_LISTEN disables the server.# Requires the ns1/ns2.<BRIDGE_HOSTNAME> A records pointing at this host.# DNS_LISTEN=:53# DNS_PUBLIC_IPV4=51.222.47.2# DNS_PUBLIC_IPV6=
# Per-instance NS delegation in Cloudflare. Empty token disables it. The token# needs only Zone → DNS → Edit on the BRIDGE_HOSTNAME zone; use its own token,# not Caddy's. Ceiling: 3000 on Cloudflare Pro (3,500 records), 150 on Free.# CLOUDFLARE_API_TOKEN=# CLOUDFLARE_ZONE_ID=# DELEGATION_MAX_NS_RECORDS=3000