Configuration

Settings are resolved in order: runtime (system.settings collection) > CLI flags > environment variables > config file > defaults. Config file search order: ./anvil.toml (local), then /etc/anvil/anvil.toml (system install on Linux/macOS) or %LOCALAPPDATA%\Anvil\anvil.toml (Windows).

Runtime Settings (system.settings)

Most auth, email, storage, and observability knobs can be hot-reloaded through the system.settings document collection without restarting the server. Runtime values take the highest precedence and are persisted in the snapshot, so they survive restarts.

# Read all settings
curl http://localhost:7474/system/settings -H "Authorization: Bearer $JWT"

# Update one (admin only) - takes effect immediately
curl -X PUT http://localhost:7474/system/settings/auth.email.smtp_host \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -d '{"value": "smtp.resend.com"}'

# Reset to file / env / default
curl -X DELETE http://localhost:7474/system/settings/auth.email.smtp_host \
  -H "Authorization: Bearer $ADMIN_JWT"

Or edit through the Hammer Admin page. Each setting carries category, type, description, and an audit trail (updated_at, updated_by).

System Install Paths

When installed via install.sh or install.ps1, Anvil uses system paths automatically:

Linux / macOSWindows
Config/etc/anvil/anvil.toml%LOCALAPPDATA%\Anvil\anvil.toml
Data/var/lib/anvil/%LOCALAPPDATA%\Anvil\data\
Logs/var/log/anvil/anvil.log%LOCALAPPDATA%\Anvil\log\anvil.log
Plugins/var/lib/anvil/plugins/%LOCALAPPDATA%\Anvil\data\plugins\

For local development builds (cargo run), paths default to ./data, ./plugins, and stdout logging.

Generate Config

anvil config init     # Write default anvil.toml
anvil config          # Print effective config

anvil.toml

[server]
http_port = 7474
bolt_port = 7687
bind_address = "0.0.0.0"
# tls_cert = "/etc/anvil/fullchain.pem"
# tls_key = "/etc/anvil/privkey.pem"

[storage]
data_dir = "./data"
document_storage = "unified"    # or "split"

[auth]
enabled = true
default_password = "anvil"
access_token_ttl_secs = 3600    # 1 hour
refresh_token_ttl_secs = 604800 # 7 days
require_email_verification = false
# RSA key pair auto-generated -> data/jwt_key.pem

[auth.email]
# Empty smtp_host disables email; verification / OTP become no-ops
smtp_host = ""
smtp_port = 465                 # 465 = implicit TLS, 587 = STARTTLS
smtp_user = ""
smtp_pass = ""
smtp_from = ""
smtp_starttls = true            # only consulted when port != 465
webhook_url = ""                # optional HTTP webhook instead of SMTP
otp_ttl_secs = 300
otp_max_attempts = 3
verification_ttl_secs = 86400
template_dir = "data/templates"

[logging]
level = "info"
log_file = ""                   # empty = stdout (daemon uses data/anvil.log)
slow_query_threshold_ms = 1000

TLS / HTTPS

Set tls_cert and tls_key in [server] to enable HTTPS. Both must point to PEM files.

[server]
tls_cert = "/etc/letsencrypt/live/example.com/fullchain.pem"
tls_key = "/etc/letsencrypt/live/example.com/privkey.pem"

# Or via environment variables:
# ANVIL_TLS_CERT=/etc/anvil/fullchain.pem
# ANVIL_TLS_KEY=/etc/anvil/privkey.pem

Environment Variables

Server & storage core

VariableDefault
ANVIL_HTTP_PORT7474
ANVIL_BOLT_PORT7687
ANVIL_DATA_DIR./data
ANVIL_LOG_LEVELinfo
ANVIL_LOG_FILE(empty = stdout)
ANVIL_AUTH_ENABLEDtrue
ANVIL_DOCUMENT_STORAGEunified
ANVIL_SLOW_QUERY_THRESHOLD1000
ANVIL_TLS_CERT(not set)
ANVIL_TLS_KEY(not set)

File storage (Phase 25)

VariableDefaultNotes
ANVIL_STORAGE_BACKENDlocallocal | s3 | gcs | azure
ANVIL_STORAGE_LOCAL_ROOT./data/storageContent-addressed blob root
ANVIL_STORAGE_STAGING_DIR./data/stagingTemp uploads
ANVIL_STORAGE_SIGNING_KEY_PATH./data/storage_signing_key.pemHMAC key for signed URLs
ANVIL_STORAGE_SIGNED_URL_DEFAULT_TTL1hUsed when expires_in=0
ANVIL_STORAGE_SIGNED_URL_MAX_TTL24hServer-side clamp
ANVIL_STORAGE_UPLOAD_CHUNK_SIZE5MiBTUS chunk advertised in OPTIONS
ANVIL_STORAGE_UPLOAD_EXPIRY24hResumable session lifetime
ANVIL_STORAGE_LOG_OBJECT_ACCESSfalseEmit StorageObjectAccessed events
ANVIL_STORAGE_MAX_FILE_SIZE50MiBPer-object hard ceiling
ANVIL_STORAGE_MAX_TOTAL_STORAGE00 = no global cap
ANVIL_STORAGE_MAX_USER_STORAGE0Per-:auth.User cap; 0 = unlimited
ANVIL_STORAGE_IMAGE_CACHE_DIR./data/image_cacheVariant cache root
ANVIL_STORAGE_IMAGE_MAX_CACHE_SIZE2GiBOn-disk cache budget
ANVIL_STORAGE_IMAGE_MAX_DIMENSION4096Reject larger width/height
ANVIL_STORAGE_IMAGE_ALLOWED_FORMATSwebp,jpeg,png,avifOutput encoder allow-list

S3 / R2 / MinIO (when ANVIL_STORAGE_BACKEND=s3)

VariableNotes
ANVIL_S3_KEYAccess key ID
ANVIL_S3_SECRETSecret access key
ANVIL_S3_REGIONAWS region (e.g. us-east-1)
ANVIL_S3_BUCKETBacking bucket name
ANVIL_S3_ENDPOINTCustom endpoint for R2 / MinIO / Wasabi

CLI Commands

anvil start                    # Daemon mode (logs to data/anvil.log)
anvil start --foreground       # Foreground mode
anvil stop                     # Graceful shutdown
anvil status                   # Show running state
anvil start --http-port 8080   # Override config
anvil import cypher ./data.cypher  # Import Cypher script