Configuration Reference

Both components read a TOML config from /etc/hash-relay/. The install scripts generate these for you; the snippets below show the key sections.

server.toml

# ── Logging ──────────────────────────────────────────────
[log]
file_enabled  = true
log_dir       = "/var/log/hash-relay"
level         = "info"   # trace|debug|info|warn|error|off

# ── Network ──────────────────────────────────────────────
[server]
listen    = "0.0.0.0"
tcp_port  = 8555
quic_port = 9555

# ── Encryption / TLS ─────────────────────────────────────
[server.enc]
tls               = "self_signed"  # self_signed | acme | file
frame_encrypt_alg = 0              # 0=off 1=AES-GCM 2=ChaCha20
# For tls = "acme":
# domain  = "relay.example.com"
# contact = "mailto:admin@example.com"
# For tls = "file":
# cert_path = "/etc/ssl/certs/hash-relay/fullchain.pem"
# key_path  = "/etc/ssl/private/hash-relay/privkey.pem"

# ── Auth ─────────────────────────────────────────────────
[server.auth]
type                = "token"
max_timestamp_drift = 300

# ── Database (URL scheme selects the driver) ─────────────
[server.database]
url = "sqlite:///var/lib/hash-relay/hashrelay.db?mode=rwc"
# url = "mysql://user:pass@127.0.0.1:3306/hashrelay"
# url = "postgres://user:pass@127.0.0.1:5432/hashrelay"
max_connections = 50
min_connections = 5

# ── Management API ───────────────────────────────────────
[server.api]
listen     = "127.0.0.1"
port       = 8080
jwt_secret = "CHANGE_ME_TO_A_LONG_RANDOM_STRING"
jwt_expiry = 86400

# ── Web admin UI (served on the API port) ────────────────
[server.web]
enabled = true
dir     = "/usr/share/hash-relay/web"

client.toml

[log]
level = "info"

[client]
server_addr = "relay.example.com"
tcp_port    = 8555    # must match server [server].tcp_port
quic_port   = 9555    # must match server [server].quic_port
client_id   = "farm-a"
token       = "CHANGE_ME_ACCESS_TOKEN"

[client.enc]
frame_encrypt_alg = 0       # must match server
insecure          = true    # skip cert validation (self-signed)
# pin_sha256 = "..."        # pin self-signed cert (from server logs)

[client.connection]
reconnect_interval      = 5
max_reconnect_attempts  = 0   # 0 = unlimited
heartbeat_interval      = 60
heartbeat_timeout_count = 3
Local listen ports are not configured on the client. After handshake, the server pushes port mappings via ConfigSync and the client opens listeners dynamically. The transport (TCP or QUIC) follows each mapping's protocol.

Environment Variable Overrides

The install scripts accept these overrides; RUST_LOG and DATABASE_URL also apply at runtime via the service unit:

VariableApplies ToEffect
TCP_PORT / QUIC_PORT / API_PORTserver installSet listen ports
DATABASE_URLserverOverride [server.database].url
JWT_SECRETserver installSet [server.api].jwt_secret
SERVER_ADDR / TCP_PORT / QUIC_PORTclient installSet relay endpoint
CLIENT_ID / TOKENclient installSet node identity & token
RUST_LOGbothOverride [log].level

TLS Options

  • self_signed (default) — persistent self-signed cert for LAN / no domain; pin it on clients with pin_sha256
  • acme — automatic Let's Encrypt issuance and renewal (requires a domain)
  • file — supply your own cert_path / key_path
Certificates hot-reload — the server checks file mtime periodically, so no restart is needed after renewal.