- 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
10 KiB
Deployment and Operations
Quick Reference
- What this file contains: environment variables, systemd service files, deployment scripts, nginx/Tor configuration, Docker support, and installation procedures.
- See also: 01_project_overview.md, 03_architecture_and_data_flow.md, 05_api_reference.md, 08_security_audit.md
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-bookwormwith full build. - Runtime stage:
debian:bookworm-slimwith minimal runtime. - User: Non-root
baluser (uid 1000). - PID 1:
tinifor 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
baluser. - 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(notzmqpubrawblock). - ZMQ ports must be bound to
127.0.0.1only.
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_sizemust matchBAL_SERVER_ACTIX_MAX_BODY_SIZE.certbot --nginxobtains 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(never0.0.0.0).BAL_SERVER_BIND_PORTmatches Nginxproxy_pass(default9137).- Firewall blocks inbound connections to
BAL_SERVER_BIND_PORTfrom external interfaces.
2. Reverse Proxy (Nginx + TLS)
- Nginx installed (
contrib/download_and_install_bal.shhandles this). contrib/nginx/bal-server.conftemplate copied to/etc/nginx/sites-available/.- Real domain name replacing
BAL_DOMAIN. listen 443 ssl http2active.certbot --nginxhas obtained a valid certificate.proxy_passpoints tohttp://127.0.0.1:9137.client_max_body_sizematchesBAL_SERVER_ACTIX_MAX_BODY_SIZE.- HTTP port 80 redirects to HTTPS.
- Security headers configured.
3. Database and Secrets
- Database file owned by
baluser (chown bal:bal /var/bal/bal.db). - Database file permissions
600(chmod 600 /var/bal/bal.db). .envfiles in.gitignoreand not committed.private_key.pem/privkey.pemnot in the repository.public_key.pemreadable by Nginx if served directly.
4. Pusher and ZMQ
- ZMQ endpoints configured for
127.0.0.1only. BAL_PUSHER_SEND_STATS=falseunlesswelistendpoint is needed.- If stats enabled,
WELIST_SERVER_URLis a valid external HTTPS domain. - Firewall blocks inbound ZMQ ports from external interfaces.
5. Logging and Monitoring
RUST_LOG=infoorwarnin production (notdebug/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:
bal-servercan be reachable via a.onionaddress.bal-pushercan connect to Bitcoin RPC orwelistthrough Tor.- 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.