BIT-31 Expose Bitcoin Pulse API through nginx

This commit is contained in:
Tobserver Agent 2026-07-28 17:12:28 +07:00
parent 2015aac8e4
commit 133384660e
2 changed files with 95 additions and 0 deletions

View file

@ -0,0 +1,63 @@
# Bitcoin Pulse public ingress
Bitcoin Pulse uses the dedicated API hostname
`bitcoin-pulse.tobiasostner.de`. The native iOS client calls this hostname
without a path prefix. A same-origin path proxy is unnecessary because there is
no browser frontend, and no CORS headers are configured because native iOS
requests are not subject to browser CORS enforcement.
## DNS and TLS
The `bitcoin-pulse` A record under `tobiasostner.de` points to Tobserver's public
IPv4 address, `162.55.103.218`. There is no AAAA record until Tobserver has
working public IPv6 connectivity.
The NixOS nginx virtual host obtains and renews its certificate through the
existing ACME configuration and redirects plaintext HTTP to HTTPS. Only ports
80 and 443 are public ingress for the API. The application listens on
`127.0.0.1:3000`; port 3000 is not opened by the host firewall, so nginx is the
only public path to the backend.
## Proxy policy
nginx preserves the original request path and forwards it to
`http://127.0.0.1:3000`. It supplies `Host`, `X-Real-IP`, `X-Forwarded-For`, and
`X-Forwarded-Proto`, and uses short local-upstream timeouts: two seconds to
connect and ten seconds to read or send.
Requests are limited per source IP to an average of 300 requests per minute,
with a burst of 50 requests, and rejected with HTTP 429 when the burst is
exhausted. This allows normal polling and multiple devices behind a shared NAT
while bounding abusive traffic. Request bodies are limited to 16 KiB; the
current public API consists only of small GET requests.
## Deployment verification
After a human reviews and merges the change, deploy the resulting NixOS
generation and verify locally on Tobserver:
```console
systemctl is-active nginx.service bitcoin-pulse.service
nginx -t
ss -ltn '( sport = :3000 )'
curl --fail --silent http://127.0.0.1:3000/health | jq -e '.status == "OK"'
```
The socket listing must show only `127.0.0.1:3000`, not `0.0.0.0:3000` or the
public address. From a machine outside Tobserver, verify DNS, HTTPS, the health
endpoint, and each functional endpoint:
```console
dig +short A bitcoin-pulse.tobiasostner.de
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/health \
| jq -e '.status == "OK"'
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/block-height | jq
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/difficulty-adjustment | jq
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/price | jq
```
The A lookup must return `162.55.103.218`. The HTTPS certificate must be valid
for `bitcoin-pulse.tobiasostner.de`; `/health` must return HTTP 200 and
`{"status":"OK"}`. The functional endpoints must return HTTP 200 and JSON after
the collectors have populated their data. A connection from outside Tobserver
to TCP port 3000 must fail.

View file

@ -265,5 +265,37 @@ in
};
};
services.nginx = {
appendHttpConfig = ''
limit_req_zone $binary_remote_addr zone=bitcoin_pulse_api:10m rate=300r/m;
'';
virtualHosts."bitcoin-pulse.tobiasostner.de" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
extraConfig = ''
limit_req zone=bitcoin_pulse_api burst=50 nodelay;
limit_req_status 429;
client_max_body_size 16k;
proxy_http_version 1.1;
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;
proxy_set_header Connection "";
proxy_connect_timeout 2s;
proxy_read_timeout 10s;
proxy_send_timeout 10s;
proxy_next_upstream off;
'';
};
};
};
environment.systemPackages = [ pkgs.postgresql_18 ];
}