Files
bal-server/docs/07_deployment_and_ops.md
svatantrya eacb2e1450 docs: update knowledge base to match current codebase
- Fix framework references (actix-web, not hyper)
- Update all env var names (BAL_SERVER_*/BAL_PUSHER_* prefix)
- Add validation.rs module documentation
- Fix function signatures in xpub.rs and db.rs
- Update API response formats (InfoResponse, StatsResponse)
- Fix database schema (date_creation/date_update, push_err, tbl_stats)
- Mark fixed vulnerabilities with current status
- Add Docker support and actix tuning documentation
- Remove outdated references (confy, bal-stats.rs.dontcompile)
- Add regression test summary table
2026-07-20 15:43:20 -04:00

10 KiB

Deployment and Operations

Quick Reference


Environment Variables

bal-server (all prefixed BAL_SERVER_)

Core Settings

Variable Default Description
BAL_SERVER_DB_FILE "bal.db" Path to the SQLite database file
BAL_SERVER_BIND_ADDRESS "127.0.0.1" TCP address to bind to (never use 0.0.0.0 in production)
BAL_SERVER_BIND_PORT 9137 TCP port to listen on
BAL_SERVER_EXPOSE_STATS false Enable/disable the GET /:network/stats endpoint
BAL_SERVER_PUB_KEY_PATH "public_key.pem" Path to the Ed25519 public key PEM file
BAL_SERVER_INFO "Will Executor Server" String returned by GET /

Per-Network Settings

For each network (regtest, testnet, testnet4, signet, bitcoin):

Variable Default Description
BAL_SERVER_{NETWORK}_ADDRESS (empty) The xpub/zpub/ypub or fixed address for fee collection
BAL_SERVER_{NETWORK}_FIXED_FEE 50000 Minimum fee in satoshis required for transaction acceptance

Example: BAL_SERVER_REGTEST_ADDRESS=tpub..., BAL_SERVER_BITCOIN_FIXED_FEE=50000.

Actix-Web Tuning

Variable Default Description
BAL_SERVER_ACTIX_MAX_BODY_SIZE 1048576 (1 MiB) Maximum HTTP request body size
BAL_SERVER_ACTIX_TIMEOUT_SECS 5 Request timeout in seconds
BAL_SERVER_ACTIX_WORKERS 4 Number of actix-web worker threads
BAL_SERVER_ACTIX_MAX_CONNECTIONS 100 Maximum concurrent connections
BAL_SERVER_ACTIX_PUSHTXS_PER_SEC 1 Rate limit: pushtxs requests per second
BAL_SERVER_ACTIX_PUSHTXS_BURST 3 Rate limit: pushtxs burst size
BAL_SERVER_ACTIX_SEARCHTX_PER_SEC 5 Rate limit: searchtx requests per second
BAL_SERVER_ACTIX_SEARCHTX_BURST 10 Rate limit: searchtx burst size
BAL_SERVER_ACTIX_INFO_PER_SEC 20 Rate limit: info requests per second
BAL_SERVER_ACTIX_INFO_BURST 30 Rate limit: info burst size
BAL_SERVER_ACTIX_DEFAULT_PER_SEC 50 Rate limit: default requests per second
BAL_SERVER_ACTIX_DEFAULT_BURST 100 Rate limit: default burst size

bal-pusher (prefixed BAL_PUSHER_)

Core Settings

Variable Default Description
BAL_PUSHER_DB_FILE "bal.db" Path to the SQLite database file
BAL_PUSHER_BITCOIN_DIR "" Bitcoin data directory (for cookie file path resolution)
BAL_PUSHER_SEND_STATS false Enable/disable remote stats reporting
BAL_SERVER_URL "http://localhost/" URL of the bal-server for internal communication
SSL_KEY_PATH "privkey.pem" Path to Ed25519 private key for signing stats
BAL_PUSHER_PREFER_IPV6 false Pin HTTP connection to first IPv6 address (for broken IPv4 routes)
WELIST_SERVER_URL "https://welist.bitcoin-after.life" URL to POST signed stats to (validated against SSRF)
WELIST_SKIP_URL_VALIDATION false Bypass SSRF URL validation (for testing only)

Per-Network Settings

For each network (regtest, testnet, testnet4, signet, bitcoin):

Variable Default (regtest) Description
BAL_PUSHER_{NETWORK}_HOST "127.0.0.1" Bitcoin Core RPC host
BAL_PUSHER_{NETWORK}_PORT 18443 Bitcoin Core RPC port
BAL_PUSHER_{NETWORK}_DIR_PATH ".bitcoin" Relative directory under $HOME for cookie file
BAL_PUSHER_{NETWORK}_DB_FIELD (empty) Database field name for this network
BAL_PUSHER_{NETWORK}_COOKIE_FILE (empty) Absolute path to cookie file (overrides DIR_PATH)
BAL_PUSHER_{NETWORK}_RPC_USER (empty) RPC username (if using user/pass auth)
BAL_PUSHER_{NETWORK}_RPC_PASSWORD (empty) RPC password (if using user/pass auth)
BAL_PUSHER_{NETWORK}_ZMQ_HASHBLOCK "tcp://127.0.0.1:21332" ZMQ hashblock endpoint

Default ports per network:

Network RPC Port ZMQ Port
bitcoin 8332 28332
regtest 18443 21332
testnet 18332 23332
testnet4 48332 24332
signet 18332 22332

Docker

The project includes a multi-stage Dockerfile:

  • Builder stage: rust:1.95-bookworm with full build.
  • Runtime stage: debian:bookworm-slim with minimal runtime.
  • User: Non-root bal user (uid 1000).
  • PID 1: tini for proper signal handling.
  • Healthcheck: curl -f http://localhost:9137/ || exit 1.

Build and run:

docker build -t bal-server .
docker run -d \
  --name bal-server \
  -v /var/bal:/var/bal \
  --env-file bal-server.env \
  -p 127.0.0.1:9137:9137 \
  bal-server

System Services

bal-server.service (Systemd Unit)

[Unit]
Description=Bal Server
After=network.target
[Service]
User=bal
Group=bal
ExecStart=/usr/local/bin/bal-server
Restart=always
RestartSec=5
WorkingDirectory=/var/bal
EnvironmentFile=/var/bal/bal-server.env
ProtectSystem=full
NoNewPrivileges=true
PrivateDevices=true
MemoryDenyWriteExecute=true

[Install]
WantedBy=multi-user
  • Runs as a dedicated non-privileged bal user.
  • Hardened with ProtectSystem=full, NoNewPrivileges=true, PrivateDevices=true, MemoryDenyWriteExecute=true.

bitcoind.service (Bitcoin Core Daemon)

[Unit]
Description=Bitcoin Core Daemon
After=network.target
[Service]
User=bitcoin
Group=bitcoin
ExecStart=/usr/local/bin/bitcoind ... -zmqpubhashblock=tcp://127.0.0.1:28332 ...
Restart=on-failure
RestartSec=30
[Install]
WantedBy=multi-user
  • Must be started with zmqpubhashblock (not zmqpubrawblock).
  • ZMQ ports must be bound to 127.0.0.1 only.

tbitcoind.service (Testnet Bitcoind)

Same as bitcoind.service but for testnet with a different data directory and ZMQ port (e.g., tcp://127.0.0.1:23332).


Bash Scripts

bal-server.sh (Development Server Startup)

Sources bal-server.env and runs the development server:

export $(grep -v '^#' bal-server.env | xargs)
RUST_LOG=info cargo run --bin=bal-server 2>&1

bal-pusher.sh (Development Pusher Startup)

Sources bal-pusher.env and runs the pusher with a network argument:

export $(grep -v '^#' bal-pusher.env | xargs)
RUST_LOG=info cargo run --bin=bal-pusher $1

sendtx.sh (Test Transaction Sender)

A helper script that wraps bitcoin-cli for manual testing.

make_release.sh (Release Builder)

Builds release binaries, creates Git tags, and uploads to Gitea. Signs the release tarball with GPG. Release assets include .tar.gz, .sha256, .sig, and .asc files. Token is loaded from .env (not hardcoded).

download_bal_db.sh (Database Pull Script)

Uses scp to pull the production bal.db from a remote server.


Nginx and SSL Configuration

The bal-server is a plain HTTP server. A reverse proxy (Nginx) with TLS termination is required for production.

Template: contrib/nginx/bal-server.conf

server {
    listen 80;
    server_name BAL_DOMAIN;
    return 301 https://$server_name$request_uri;
}
server {
    listen 443 ssl http2;
    server_name BAL_DOMAIN;
    ssl_certificate /etc/letsencrypt/live/BAL_DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/BAL_DOMAIN/privkey.pem;

    client_max_body_size 1m;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header Referrer-Policy no-referrer;

    location / {
        proxy_pass http://127.0.0.1:9137;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    # Uncomment for rate limiting:
    # limit_req zone=pal limit=10 nodelay;
}

Key points:

  • client_max_body_size must match BAL_SERVER_ACTIX_MAX_BODY_SIZE.
  • certbot --nginx obtains the certificate automatically.
  • Security headers: X-Frame-Options, X-Content-Type-Options, Referrer-Policy.

Production Deployment Checklist

1. bal-server Bind Address

  • BAL_SERVER_BIND_ADDRESS=127.0.0.1 (never 0.0.0.0).
  • BAL_SERVER_BIND_PORT matches Nginx proxy_pass (default 9137).
  • Firewall blocks inbound connections to BAL_SERVER_BIND_PORT from external interfaces.

2. Reverse Proxy (Nginx + TLS)

  • Nginx installed (contrib/download_and_install_bal.sh handles this).
  • contrib/nginx/bal-server.conf template copied to /etc/nginx/sites-available/.
  • Real domain name replacing BAL_DOMAIN.
  • listen 443 ssl http2 active.
  • certbot --nginx has obtained a valid certificate.
  • proxy_pass points to http://127.0.0.1:9137.
  • client_max_body_size matches BAL_SERVER_ACTIX_MAX_BODY_SIZE.
  • HTTP port 80 redirects to HTTPS.
  • Security headers configured.

3. Database and Secrets

  • Database file owned by bal user (chown bal:bal /var/bal/bal.db).
  • Database file permissions 600 (chmod 600 /var/bal/bal.db).
  • .env files in .gitignore and not committed.
  • private_key.pem / privkey.pem not in the repository.
  • public_key.pem readable by Nginx if served directly.

4. Pusher and ZMQ

  • ZMQ endpoints configured for 127.0.0.1 only.
  • BAL_PUSHER_SEND_STATS=false unless welist endpoint is needed.
  • If stats enabled, WELIST_SERVER_URL is a valid external HTTPS domain.
  • Firewall blocks inbound ZMQ ports from external interfaces.

5. Logging and Monitoring

  • RUST_LOG=info or warn in production (not debug/trace).
  • Log files rotated and stored under /var/log/bal/ or systemd journal.
  • Log files not in the same directory as the database or private key.

Tor and Privacy

The contrib/install_tor.sh script installs Tor for onion-routed proxy use:

  1. bal-server can be reachable via a .onion address.
  2. bal-pusher can connect to Bitcoin RPC or welist through Tor.
  3. The server can run behind NAT without exposing the real IP.

The script uses ControlPort 9051 with CookieAuthentication. The bal-pusher supports SOCKS5 proxy via the reqwest socks feature for .onion connectivity.