Files
btcprice/README.md
magnus 0a27d01f12
pull-request / validate (pull_request) Successful in 2m34s
Show 30-minute candles on the last-24-hours chart
2026-11-03 10:32:23 +01:00

134 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# btcprice
Bitcoin price tracker UI. Serves an HTML page with live price updates streamed from
[btcdata](../btcdata), which this server proxies over WebSocket and renders as HTML fragments
for HTMX.
## Quick Start
```bash
# Start btcdata first (provides price data)
cd ../btcdata && make run
# Local dev — creates .venv and installs requirements on first run
make run # starts on port 4000
make tailwind # watch Tailwind CSS (separate terminal)
```
Optionally `cp .env.example .env` to override `BTCDATA_URL`; without it the code default
(`http://localhost:4100`) applies. `make run-local` requires a `.env` defining
`BTCDATA_LOCAL_URL`.
Run the test suite with `make test`. Test-only packages are kept in `requirements-dev.txt` and are
not installed in the production image.
## Docker
`docker compose up -d` starts **btcprice only** — it does not bring up btcdata. Both compose
files attach to the external `btc-network`, so create it once and start btcdata separately:
```bash
docker network create btc-network # once
cd ../btcdata && docker compose up -d
cd ../btcprice && docker compose up -d
```
| Service | Local dev | Docker |
|----------|----------------|---------------------------------------|
| btcprice | localhost:4000 | localhost:4041 (container port 4040) |
| btcdata | localhost:4100 | localhost:4102 |
Inside `btc-network`, btcprice reaches btcdata as `http://btcdata:4102`.
## CI images
Pushes to the `main` branch and tags matching `v*` run
`.gitea/workflows/build.yml`. The workflow tests the application and publishes linux/amd64 images
to `harbor.pmagnus.cloud/btcprice/btcprice`. Branch builds receive an immutable
`main-<UTC timestamp>` tag (for Flux selection) and a SHA-qualified companion tag; release tags
receive the version without `v` and a SHA-qualified companion tag. The image embeds the short Git
SHA, available from `/api/version`.
The workflow requires the repository secrets `HARBOR_ROBOT_USER` and `HARBOR_ROBOT_TOKEN`. It only
pushes images and intentionally has no Kubernetes credentials.
## Production deployment
Production is GitOps-managed at **https://btcprice.k8s.pmagnus.cloud**:
```text
git push → Gitea Actions on anvil → Harbor → Flux → k3s → ingress-nginx → Traefik TLS
```
Flux image automation commits the newest approved `main-<UTC timestamp>` image to the separate
`magnus/deploy` repository. The manifests are in `../deploy/apps/btcprice`, and two replicas run with
one pod on each k3s node. CI never receives cluster credentials. Do not deploy production with
Docker Compose or manual `kubectl apply`; rollback by reverting the immutable image change in the
GitOps repository.
btcdata remains an external dependency running on citadel. Kubernetes represents it with a
selectorless Service and EndpointSlice, so pods use `http://btcdata:4102` without embedding its IP in
the Deployment.
## Legacy deployment (citadel)
```bash
./deploy-citadel.sh
```
Same method as `../elprice/deploy-citadel.sh`: builds the image locally for the target's
architecture (citadel is x86_64 → `linux/amd64`), copies `docker-compose.citadel.yml` and
`.env.citadel` to `/home/pmp/apps/btcprice` on the host as `docker-compose.yml` / `.env`,
pipes the image over SSH with `docker save | docker load`, and runs
`docker compose up -d --no-build`. No registry involved.
The temporary migration fallback listens on **http://192.168.50.195:4041** and proxies to the
btcdata running on citadel as well. It is retained only for the validation/rollback window; the
Kubernetes hostname is the production endpoint. Both attach to the external `btc-network` there, so `BTCDATA_URL` is
`http://btcdata:4102` (set in `.env.citadel`) — the service name inside the network, not a LAN
address. The deploy script creates `btc-network` on the host if it does not exist yet.
Overridable: `BTCPRICE_DEPLOY_HOST`, `BTCPRICE_DEPLOY_DIR`, `BTCPRICE_IMAGE_NAME`,
`BTCPRICE_PLATFORM`, `BTCPRICE_NETWORK`.
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `BTCDATA_URL` | `http://localhost:4100` | btcdata service URL the server proxies to |
| `APP_VERSION` | `development` | Build identifier returned by `/api/version` |
| `BTCDATA_LOCAL_URL` | — | Used by `make run-local` instead of `BTCDATA_URL` |
| `DOCKER_BTCDATA_PORT` | `4102` | btcdata's port inside `btc-network` (local compose only) |
| `BTCPRICE_PORT` | `4041` | Host port in the citadel deployment (`.env.citadel`) |
The HTTP port is not configurable via environment: `make run` hardcodes 4000 and the Docker
image's `CMD` hardcodes 4040.
## Health endpoints
- `/api/health` is a process-only liveness check and remains healthy during a btcdata outage.
- `/api/ready` checks `${BTCDATA_URL}/api/health` with a three-second timeout and returns 503 when
btcdata is unavailable.
- `/api/version` returns the build SHA embedded by CI.
## How It Works
The browser loads a page with HTMX and the WebSocket extension, which connects back to
**btcprice** at `/ws/price` and `/ws/strike` — the browser never contacts btcdata directly.
Each of those endpoints opens an upstream WebSocket to btcdata (`/api/price/ws` and
`/api/strike/ws`), converts each JSON message to an HTML fragment (`price_json_to_html`,
`strike_json_to_html`), and forwards it to the browser. HTMX swaps the received
`<div id="price-display">` / `<div id="strike-display">` into the DOM. Price changes are
color-coded (green up, red down). Reconnection is handled by HTMX.
## Tech Stack
Python · FastAPI · Uvicorn · websockets · HTMX · Tailwind CSS v4
The Clojure implementation this replaced is on the `clojure` branch.
The 24-hour chart uses 48 half-hour candles from rustbtcdata’s
`/api/kraken-half-hour` endpoint, aggregated from minute OHLC data. It refreshes
every five minutes and includes the current partial half-hour candle.