Configuration Reference
dvb-WarpPool has two configuration files plus a set of env vars for optional subsystems:
config.toml— all non-sensitive settings (pool behavior, Stratum, VarDiff, notifier schema)secrets.toml— auth hashes, RPC credentials, Sv2 keys; always chmod 600- env vars — opt-in subsystems (auto-update, health-check interval, notifier secrets, debounce tuning)
Defaults are defined in crates/config/src/lib.rs; this page is the
operator's view.
config.toml
[branding]
[branding]
status_brand_name = "dvb-WarpPool"
server_location = "self-hosted"
fiat_currency = "EUR"
pool_donation_address = ""
Controls the strings shown in the UI and in the Prometheus build_info
metric. No effect on functionality.
[logging]
[logging]
debug = false
net_debug = false
json = true # JSON-formatted for Loki/Promtail; set false for readable stdout
Runtime override via env var: RUST_LOG=warppool=debug takes precedence
over the config setting.
[mining]
[mining]
payout_address = "bc1q..." # REQUIRED — block-reward recipient
pool_fee_percent = 0.0 # Default = no pool fee (solo)
pool_fee_address = "" # Required when fee_percent > 0
pooltag_prefix = "dvbprojekt-WarpPool" # 19 chars max, written to coinbase scriptSig
operator_donation_address = ""
operator_donation_percent = 0.0
For multi-user-solo (hosting-service setup): set pool_fee_percent = 1.0
and pool_fee_address. The sum of fee + donation must not exceed 99%.
[node]
[node]
rpc_url = "http://127.0.0.1:8332"
zmq_hashblock_addr = "tcp://127.0.0.1:28332"
zmq_rawblock_addr = "tcp://127.0.0.1:28333"
rpc_cookie_path = "/home/bitcoin/.bitcoin/.cookie" # optional, alternative to user/pass in secrets.toml
The cookie takes precedence over user/pass in secrets.toml. Leaving the
ZMQ fields empty enables poll-only mode (slower but functional).
[server]
[server]
pool_listen = ":3333"
status_listen = ":18334"
status_tls_listen = ":18443" # optional
status_public_url = "https://pool.example.com" # for push notifications
trust_proxy_headers = false # only set true when the daemon runs behind Caddy/nginx
status_public_url is required by the UI to build absolute URLs for web
push and email links. Leave empty when not running behind a reverse
proxy.
[stratum]
[stratum]
stratum_tls_listen = ":3443" # optional, V1 TLS
stratum_password = "" # empty = address-format auth only
safe_mode = true # Conservative validator
tls_cert_path = "/etc/dvb-warppool/cert.pem"
tls_key_path = "/etc/dvb-warppool/key.pem"
sv2_listen = ":3334" # optional, Stratum V2
sv2_max_connections = 1024
Sv2 requires secrets.sv2_static_priv_key_hex. If it is missing, Sv2 is
disabled with a warning (the daemon still starts).
[tuning]
[tuning]
max_saved_workers_per_user = 64
max_saved_workers_pool_total = 1024
Storage cap. Above these thresholds, old/idle workers are evicted (FIFO
by last_seen_at).
[profile]
[profile]
kind = "klein" # klein | mittel | gross | enterprise; omit = auto
expected_miner_count = 5 # for auto: estimate used by auto-detection
The profile affects defaults and resource limits. Hot-switchable via
POST /api/admin/profile — the config value is only the fallback at first
start.
[vardiff]
[vardiff]
enabled = true
target_seconds_per_share = 30.0
window = 16
min_diff = 1.0
max_diff = 65536.0
retarget_after_n_shares = 8
hysteresis = 0.30 # 30% — a new diff that deviates > 30% from the old one triggers a retarget
max_step = 4.0 # max 4× per retarget
initial_diff = 1.0
EMA-based, with persisted snapshots in the vardiff_state table. Tests:
crates/stratum-v1/src/vardiff.rs.
[ratelimit]
[ratelimit]
enabled = true
connects_per_sec = 5.0 # per peer IP
connect_burst = 20
auths_per_sec = 1.0
auth_burst = 10
idle_evict_secs = 300 # after 5 min without traffic → bucket state is evicted
Per-peer-IP token bucket. Triggered limits return mining.authorize with
result: false (no disconnect — the miner should not get stuck in a
reconnect loop).
[notifier]
See Notifications for detailed sink setup guides.
[notifier]
webpush_subscriptions_path = "/var/lib/dvb-warppool/webpush.json"
[notifier.ntfy]
topic_url = "https://ntfy.sh/my-pool-topic-secret"
on_block_found = true
on_miner_disconnect = false
on_rpc_down = true
[notifier.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
chat_id = "123456789"
on_block_found = true
[notifier.discord]
webhook_url_env = "DISCORD_WEBHOOK_URL"
on_block_found = true
[notifier.slack]
webhook_url_env = "SLACK_WEBHOOK_URL"
on_block_found = true
on_miner_disconnect = false
on_rpc_down = true
[notifier.email]
smtp_url = "smtps://pool@mail.example.com:465"
from = "pool@example.com"
to = ["operator@example.com"]
password_env = "POOL_SMTP_PASSWORD"
on_block_found = true
on_rpc_down = true
All sinks are optional. The daemon starts fine without a [notifier]
section at all. Sinks whose env vars are missing are skipped with a
notifier sink skipped warning.
secrets.toml (chmod 600)
rpc_user = "warppool" # optional, alternative to cookie
rpc_pass = "..." # optional, ditto
admin_username = "admin" # default "admin"
admin_password_hash = "$argon2id$..." # via `dvb-warppool-cli hash-password`
jwt_secret = "..." # min 32 bytes recommended; empty → admin auth disabled
sv2_static_priv_key_hex = "..." # via `dvb-warppool-cli gen-sv2-key`
An empty admin_password_hash or empty jwt_secret makes every
/api/admin/* endpoint return 503 "auth disabled". Useful for a
read-only setup without an admin UI.
Phase 21: VAPID Web Push Secrets
vapid_public_key = "B..." # via `dvb-warppool-cli gen-vapid-keys`
vapid_private_key = "..." # same command
vapid_contact = "mailto:operator@example.org" # optional, default mailto:operator@localhost
The public key is served via /api/push/vapid-public-key (public,
no-auth — the UI needs it for PushManager.subscribe). The private key
stays daemon-side only. If either value is empty, web push is disabled —
the daemon log reports "web-push disabled (no vapid keys)".
Environment Variables
Bitcoin Health Check
| Var | Default | Description |
|---|---|---|
WARPPOOL_HEALTH_CHECK_INTERVAL_SECONDS | 60 | Interval of the daemon's periodic Bitcoin health check. 0 = off. |
When the check is enabled, the daemon emits HealthSnapshot SSE events
and fires RpcDown/RpcRecovered notifications on transitions.
Auto-Update
| Var | Default | Description |
|---|---|---|
WARPPOOL_AUTOUPDATE_REPO | (unset) | e.g. dvb-projekt/dvb-WarpPool. When set, /api/admin/update is active. |
WARPPOOL_AUTOUPDATE_INTERVAL_HOURS | 24 | How often to poll GitHub for new releases. 0 = off. |
WARPPOOL_COSIGN_BIN | (unset) | Path to the cosign binary for verify-blob. Required when cosign_verify=true in the update request. |
Stratum / Notifier Tuning
| Var | Default | Description |
|---|---|---|
WARPPOOL_DISCONNECT_DEBOUNCE_SECS | 30 | Per-worker debounce for MinerDisconnect notifications. |
Solar/PV Provider (Phase 20.5)
The electricity tariff switches to excess_rate_eur_kwh (typically 0.0)
when the PV array produces more than house + pool consume together.
Currently implemented: Home Assistant via its REST API.
[mining.electricity.solar]
enabled = true
kind = "home_assistant"
url_env = "WARPPOOL_HA_URL" # e.g. http://homeassistant.local:8123
token_env = "WARPPOOL_HA_TOKEN" # Long-Lived Access Token
pv_entity_id = "sensor.pv_power_total"
consumption_entity_id = "sensor.grid_load_total" # optional
poll_interval_secs = 60 # 60s is enough to track PV trends
surplus_buffer_w = 200 # safety buffer
excess_rate_eur_kwh = 0.0 # self-generated power = free
stale_after_secs = 300 # fall back after 5 min without a snapshot
Create the token in HA: Profile → Long-Lived Access Tokens → Create Token. Entity IDs are listed under Developer Tools → States.
If consumption_entity_id is omitted, the pool compares the PV value
directly against its own consumption (not the whole-house total).
/api/energy reports current_rate_source = "solar-excess" and a
solar object containing pv_w / consumption_w / excess_w / age_seconds.
Vendor Probes (Phase 22)
| Var | Default | Description |
|---|---|---|
WARPPOOL_AUTO_PROBE_DISCOVERED | (off) | 1/true/yes: in addition to DB miners, devices found via mDNS are polled every 30s. Telemetry values land in an in-memory cache (no DB write) and are exposed in /metrics with label="discovered". |
Health Anomaly Check (Phase 20.3b)
| Var | Default | Description |
|---|---|---|
WARPPOOL_ANOMALY_CHECK_INTERVAL_SECS | 300 | Interval of the periodic anomaly detection. 0 = off. |
WARPPOOL_ANOMALY_DEBOUNCE_SECS | 1800 | Per-(miner, alert_kind) debounce. While a symptom persists, the notifier does not fire on every tick — only again after this interval. |
Critical alerts (FanStuck, StaleData) are sent to every sink with
on_health_alert = true (default). Warnings (ThermalThrottling,
VoltageDrop, HashrateDrop) are UI-only via /api/miners/:id/alerts.
Notifier Secrets
The variable names are FREELY CHOSEN — you set the name in config.toml,
and the daemon reads that env var.
Typical names:
TELEGRAM_BOT_TOKEN→notifier.telegram.bot_token_envDISCORD_WEBHOOK_URL→notifier.discord.webhook_url_envSLACK_WEBHOOK_URL→notifier.slack.webhook_url_envPOOL_SMTP_PASSWORD→notifier.email.password_env
Test/Debug
| Var | Default | Description |
|---|---|---|
RUST_LOG | info | Standard tracing-subscriber filter. Use warppool=debug,info for selective debug. |
WARPPOOL_E2E_REGTEST_* | — | Set by scripts/regtest-up.sh. See Testing. |
CLI Override
dvb-warppool-daemon --help lists the CLI flags that can override
individual config values. Common ones:
dvb-warppool-daemon \
--config /etc/dvb-warppool/config.toml \
--secrets /etc/dvb-warppool/secrets.toml \
--db-url sqlite:///var/lib/dvb-warppool/pool.db \
--ui-dir /usr/share/dvb-warppool/ui \
--no-zmq # debug-only: force poll-only mode
Reload Behavior
There is currently no hot reload for config.toml. Restart the
daemon after every change:
systemctl restart dvb-warppool
The only hot-switchable items are:
- Profile (
POST /api/admin/profile) — affects display and defaults, not themax_connectionssemaphore - Admin tokens (
POST /api/admin/tokens) — effective immediately - 2FA (
POST /api/auth/2fa/enable) — effective immediately
See Also
- Getting Started — minimum config for first boot
- Notifications — sink-specific examples
- Setup Health Checks — wizard workflow
- Troubleshooting — common failure modes