# deploy/Caddyfile.example — reference Caddy config for this box. # # The deployment runs TWO bots behind one Caddy: # # arb.elghaly.dev -> 127.0.0.1:8080 the Rust bot (basicauth, private) # bot.elghaly.dev -> 127.0.0.1:8081 this Python bot (public, read-only) # # Copy to /etc/caddy/Caddyfile, adjust, then: # # sudo caddy validate --config /etc/caddy/Caddyfile # sudo systemctl reload caddy # # ───────────────────────────────────────────────────────────────────────── # THE PATH ALLOWLIST IS THE POINT OF THIS FILE # ───────────────────────────────────────────────────────────────────────── # A bare `reverse_proxy 127.0.0.1:8081` publishes EVERY route the Python # bot serves, and two of them are not public routes: # # POST /command fund-moving. Runs /hunt, toggles ghost and mint mode. # GET /logs operational detail — wallet addresses, tx signatures, # route sizes, internal errors. # # Both are Bearer-token gated in the application (modules/bot_api_server.py) # and both are rate-limited and lockout-protected (modules/http_guard.py), # so a bare proxy is not an immediate compromise. It is still the wrong # shape. Anything that can move funds should be unreachable for two # independent reasons, so that no single mistake — a token in a shell # history, a bad `git add`, a regression in the auth check — is sufficient # on its own. # # So this config lists what MAY be reached and 404s everything else. Note # the direction: an allowlist fails closed, so a new endpoint added to the # Python server tomorrow is private until someone deliberately publishes # it. A blocklist would fail open and publish it by default. arb.elghaly.dev { basicauth { # Generate with: caddy hash-password # The hash is not a password, but it is still an offline-crackable # artifact — keep it out of screenshots and out of git. admin REPLACE_WITH_BCRYPT_HASH } reverse_proxy 127.0.0.1:8080 } bot.elghaly.dev { encode gzip # The public read-only surface. Nothing here can act, and nothing here # returns a secret — see modules/bot_api_server.py's own audit note for # the field-by-field reasoning. @public { path / path /status path /api/pnl path /api/receipts path /api/routes path /api/nearmiss path /api/engine path /receipts.csv } handle @public { reverse_proxy 127.0.0.1:8081 } # Everything else, including /command and /logs, does not exist as far # as the internet is concerned. 404 rather than 403: a 403 confirms the # path is real and worth attacking, which is free reconnaissance. handle { respond "not found" 404 } header { # This page loads no external anything — no CDN, no fonts, no # analytics — so the policy can be this tight honestly. 'unsafe-inline' # covers the one inline