Initial commit
This commit is contained in:
25
doc/README.md
Normal file
25
doc/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Documentation Index
|
||||
|
||||
This directory contains project documentation for the Bitcoin Core Admin Dashboard.
|
||||
|
||||
## Document Map
|
||||
|
||||
- `doc/architecture.md` - System architecture, module responsibilities, and runtime flow.
|
||||
- `doc/api.md` - HTTP API reference, authentication model, and endpoint contracts.
|
||||
- `doc/data-models.md` - SQLite schema, data invariants, and retention behavior.
|
||||
- `doc/build-and-deploy.md` - Build, runtime, and deployment rules for local and Docker usage.
|
||||
- `doc/environment.md` - Environment variables, defaults, and production guidance.
|
||||
- `doc/conventions.md` - Coding patterns and implementation conventions for this repository.
|
||||
|
||||
## Reading Order
|
||||
|
||||
1. Start with `doc/architecture.md`.
|
||||
2. Use `doc/api.md` and `doc/data-models.md` for implementation details.
|
||||
3. Use `doc/build-and-deploy.md` and `doc/environment.md` for operations.
|
||||
4. Use `doc/conventions.md` when adding or modifying code.
|
||||
|
||||
## Cross-References
|
||||
|
||||
- API endpoints depend on persisted settings described in `doc/data-models.md`.
|
||||
- Runtime behavior and deployment constraints are described in `doc/architecture.md` and `doc/build-and-deploy.md`.
|
||||
- Configuration contracts in `doc/environment.md` are consumed by modules listed in `doc/architecture.md`.
|
||||
133
doc/api.md
Normal file
133
doc/api.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# API Reference
|
||||
|
||||
## Base Behavior
|
||||
|
||||
- Base URL: same origin as UI host.
|
||||
- UI route:
|
||||
- `GET /` returns the dashboard HTML shell.
|
||||
- API route namespace:
|
||||
- All API endpoints are under `/api/...`.
|
||||
- Cache behavior:
|
||||
- Responses under `/api/` and `/static/` include no-store cache headers.
|
||||
|
||||
## Authentication Model
|
||||
|
||||
- Login endpoint sets a session value on success.
|
||||
- Protected endpoints require `Depends(require_auth)`.
|
||||
- Session cookie behavior:
|
||||
- `same_site=lax`
|
||||
- max age: 8 hours
|
||||
- secure flag controlled by `SESSION_COOKIE_SECURE`
|
||||
|
||||
Related references:
|
||||
|
||||
- Configuration: `doc/environment.md`
|
||||
- Auth implementation: `app/auth.py`
|
||||
|
||||
## Error Contract
|
||||
|
||||
- `401` for authentication failures.
|
||||
- `400` for missing required node configuration values.
|
||||
- `422` for request validation and unsupported chart window values.
|
||||
- `502` for RPC or SSH operation failures.
|
||||
- Error shape for handled exceptions:
|
||||
- `{ "detail": "<message>" }`
|
||||
|
||||
## Endpoint Contracts
|
||||
|
||||
### Health and Session
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `GET` | `/api/health` | No | Returns service liveness: `{ "ok": true }`. |
|
||||
| `GET` | `/api/auth/me` | No | Returns current authentication state and username if authenticated. |
|
||||
| `POST` | `/api/auth/login` | No | Validates credentials and creates session. |
|
||||
| `POST` | `/api/auth/logout` | Yes | Clears session. |
|
||||
|
||||
`POST /api/auth/login` request body:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "secret"
|
||||
}
|
||||
```
|
||||
|
||||
### Node Settings
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `GET` | `/api/settings` | Yes | Returns persisted node connection and control settings. |
|
||||
| `PUT` | `/api/settings` | Yes | Validates and persists node settings. |
|
||||
|
||||
`PUT /api/settings` request body fields:
|
||||
|
||||
- `rpc_url` string
|
||||
- `rpc_username` string
|
||||
- `rpc_password` string
|
||||
- `rpc_wallet` string
|
||||
- `config_path` string
|
||||
- `ssh_host` string
|
||||
- `ssh_port` integer `1..65535`
|
||||
- `ssh_username` string
|
||||
- `ssh_password` string
|
||||
- `ssh_key_path` string
|
||||
- `bitcoin_binary` string
|
||||
|
||||
All string fields are trimmed before persistence.
|
||||
|
||||
### Dashboard
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `GET` | `/api/dashboard/summary` | Yes | Returns chain, network, mempool, mining, uptime, optional wallet data, and `updated_at`. |
|
||||
| `GET` | `/api/dashboard/history` | Yes | Returns metric history for query-param `window` and `limit`. |
|
||||
| `GET` | `/api/dashboard/history/{window}` | Yes | Same as above with `window` as a path parameter. |
|
||||
|
||||
Supported `window` values:
|
||||
|
||||
- `5m`
|
||||
- `30m`
|
||||
- `2h`
|
||||
- `6h`
|
||||
- `all`
|
||||
|
||||
`limit` is clamped server-side to `[1, 20000]`.
|
||||
|
||||
### RPC Explorer
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `POST` | `/api/rpc/call` | Yes | Executes a JSON-RPC method with params and optional wallet override. |
|
||||
| `GET` | `/api/rpc/commands` | Yes | Calls node `help` and returns parsed method catalog. |
|
||||
| `GET` | `/api/rpc/help/{method_name}` | Yes | Returns detailed `help` output for one method. |
|
||||
|
||||
`POST /api/rpc/call` request body:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "getblockchaininfo",
|
||||
"params": [],
|
||||
"wallet": null
|
||||
}
|
||||
```
|
||||
|
||||
### Node Actions
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `POST` | `/api/actions/stop` | Yes | Calls RPC `stop`. |
|
||||
| `POST` | `/api/actions/start` | Yes | Runs SSH start command built from persisted settings. |
|
||||
| `POST` | `/api/actions/restart` | Yes | Attempts RPC stop then runs SSH start command. |
|
||||
|
||||
Action responses include action metadata and remote execution payload when SSH is involved.
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Startup and restart require:
|
||||
- valid SSH connectivity
|
||||
- `config_path`
|
||||
- SSH user credentials or key access
|
||||
- When running in Docker, `rpc_url` should usually target `host.docker.internal` if the node is on the Docker host.
|
||||
|
||||
See `doc/build-and-deploy.md` for Docker network details.
|
||||
98
doc/architecture.md
Normal file
98
doc/architecture.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The project is a single-service web application that provides operational control and observability for a remote Bitcoin Core node.
|
||||
|
||||
Core runtime components:
|
||||
|
||||
- FastAPI server for UI and API delivery.
|
||||
- Session-based authentication middleware.
|
||||
- SQLite persistence for node settings and sampled metrics.
|
||||
- Bitcoin Core JSON-RPC client for node data and RPC execution.
|
||||
- SSH command runner for daemon start and restart operations.
|
||||
- Vanilla JavaScript frontend with Chart.js visualizations.
|
||||
|
||||
Related references:
|
||||
|
||||
- API contracts: `doc/api.md`
|
||||
- Persistence schema: `doc/data-models.md`
|
||||
- Deployment rules: `doc/build-and-deploy.md`
|
||||
|
||||
## Component Boundaries
|
||||
|
||||
### Backend Modules
|
||||
|
||||
- `app/main.py`
|
||||
- Composes FastAPI app, middleware, startup/shutdown hooks, and all route handlers.
|
||||
- Validates payloads via Pydantic models.
|
||||
- Aggregates dashboard summary data and persists metric samples.
|
||||
- Starts a background sampler thread that periodically stores metrics.
|
||||
- `app/auth.py`
|
||||
- Verifies login credentials.
|
||||
- Enforces authenticated API access through `require_auth`.
|
||||
- `app/config.py`
|
||||
- Loads and validates environment-driven runtime configuration.
|
||||
- Creates the data directory when needed.
|
||||
- `app/db.py`
|
||||
- Initializes schema.
|
||||
- Persists and reads node settings.
|
||||
- Persists and reads metric history with retention trimming.
|
||||
- `app/bitcoin_rpc.py`
|
||||
- Encapsulates JSON-RPC request and batch request behavior.
|
||||
- Normalizes RPC errors through `BitcoinRPCError`.
|
||||
- Parses `help` output into command catalog entries.
|
||||
- `app/ssh_control.py`
|
||||
- Resolves SSH host and connection parameters.
|
||||
- Executes remote commands and returns execution result payloads.
|
||||
- Builds safe daemon start command strings.
|
||||
|
||||
### Frontend Assets
|
||||
|
||||
- `app/templates/index.html`
|
||||
- Declares login, dashboard, settings modal, control actions, and RPC explorer regions.
|
||||
- `app/static/app.js`
|
||||
- Owns client state, auth transitions, API calls, chart hydration, and polling.
|
||||
- Caches metric history in browser local storage.
|
||||
- `app/static/styles.css`
|
||||
- Provides responsive layout and UI styling.
|
||||
|
||||
## Runtime Flow
|
||||
|
||||
1. Application startup calls `init_db()` and starts the metrics sampler thread.
|
||||
2. Browser loads `/`, then frontend checks `/api/auth/me`.
|
||||
3. After login, frontend loads settings, RPC command catalog, history, and summary.
|
||||
4. Frontend refreshes summary every 15 seconds while page is open.
|
||||
5. Backend stores sampled metrics:
|
||||
- on each summary API call.
|
||||
- in the background sampler at `METRICS_SAMPLER_INTERVAL_SECONDS` cadence.
|
||||
|
||||
## Data and Control Paths
|
||||
|
||||
- Read-only node visibility:
|
||||
- `/api/dashboard/summary` uses batch RPC to fetch chain, network, mempool, mining, and uptime data.
|
||||
- `/api/dashboard/history` and `/api/dashboard/history/{window}` read persisted metric points.
|
||||
- Node management:
|
||||
- Stop uses Bitcoin RPC `stop`.
|
||||
- Start uses SSH execution of `bitcoind -daemon -conf=<config_path>`.
|
||||
- Restart attempts RPC stop, waits briefly, then uses SSH start.
|
||||
- RPC explorer:
|
||||
- Command catalog derives from `help` output.
|
||||
- Arbitrary RPC call endpoint accepts method and JSON-array params.
|
||||
|
||||
## Error and Resilience Model
|
||||
|
||||
- `BitcoinRPCError` and `SSHControlError` are mapped to HTTP `502`.
|
||||
- Missing required node settings are surfaced as HTTP `400`.
|
||||
- Validation issues return HTTP `422`.
|
||||
- Background sampler is best-effort and suppresses internal exceptions to avoid service interruption.
|
||||
|
||||
## Security Model
|
||||
|
||||
- Authentication is session-cookie based using `SessionMiddleware`.
|
||||
- Access to operational endpoints requires `Depends(require_auth)`.
|
||||
- Credential verification supports:
|
||||
- plaintext password via `APP_PASSWORD`
|
||||
- bcrypt hash via `APP_PASSWORD_HASH`
|
||||
|
||||
See `doc/environment.md` for security-relevant configuration fields.
|
||||
85
doc/build-and-deploy.md
Normal file
85
doc/build-and-deploy.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Build and Deployment
|
||||
|
||||
## Runtime Targets
|
||||
|
||||
- Local Python process using Uvicorn.
|
||||
- Docker container via `Dockerfile`.
|
||||
- Docker Compose service via `docker-compose.yml`.
|
||||
|
||||
Related references:
|
||||
|
||||
- Configuration fields: `doc/environment.md`
|
||||
- Architecture: `doc/architecture.md`
|
||||
|
||||
## Local Run
|
||||
|
||||
Typical local launch command:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app --reload --port 8080
|
||||
```
|
||||
|
||||
Service endpoint:
|
||||
|
||||
- `http://localhost:8080`
|
||||
|
||||
Notes:
|
||||
|
||||
- The app reads environment variables at startup.
|
||||
- Database parent directory is created automatically.
|
||||
|
||||
## Docker Image Build Rules
|
||||
|
||||
Build file: `Dockerfile`
|
||||
|
||||
Current build behavior:
|
||||
|
||||
1. Base image: `python:3.12-slim`
|
||||
2. Install Python dependencies from `requirements.txt`
|
||||
3. Copy `app/` and `data/` into image
|
||||
4. Expose container port `8080`
|
||||
5. Start with Uvicorn on `0.0.0.0:8080`
|
||||
|
||||
## Docker Compose Rules
|
||||
|
||||
Compose file: `docker-compose.yml`
|
||||
|
||||
Dashboard service settings:
|
||||
|
||||
- Publishes `8080:8080`
|
||||
- Loads environment from `.env`
|
||||
- Sets:
|
||||
- `DATA_DIR=/app/data`
|
||||
- `DB_PATH=/app/data/dashboard.db`
|
||||
- Mounts volume:
|
||||
- `./data:/app/data`
|
||||
- Adds host gateway alias:
|
||||
- `host.docker.internal:host-gateway`
|
||||
- Restart policy:
|
||||
- `unless-stopped`
|
||||
|
||||
## Persistence Rules
|
||||
|
||||
- Persist `./data` on the host to retain:
|
||||
- node settings
|
||||
- chart metric history
|
||||
- If `data/dashboard.db` is not persisted, settings and history reset when the container is recreated.
|
||||
|
||||
## Network Rules
|
||||
|
||||
If the Bitcoin node runs on the Docker host:
|
||||
|
||||
- Prefer RPC URL `http://host.docker.internal:8332`.
|
||||
|
||||
If using remote node hosts:
|
||||
|
||||
- Set RPC URL directly to the node endpoint.
|
||||
- Ensure SSH host/credentials are valid for start/restart actions.
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
1. Set strong `APP_PASSWORD` or provide `APP_PASSWORD_HASH`.
|
||||
2. Set a long random `SESSION_SECRET`.
|
||||
3. Set `SESSION_COOKIE_SECURE=true` behind HTTPS.
|
||||
4. Persist the database volume.
|
||||
5. Restrict dashboard network exposure to trusted operators.
|
||||
54
doc/conventions.md
Normal file
54
doc/conventions.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Conventions and Coding Patterns
|
||||
|
||||
This document defines repository conventions to preserve existing architecture and behavior.
|
||||
|
||||
## Module Boundaries
|
||||
|
||||
- Keep backend concerns separated by module:
|
||||
- auth in `app/auth.py`
|
||||
- configuration in `app/config.py`
|
||||
- persistence in `app/db.py`
|
||||
- RPC transport in `app/bitcoin_rpc.py`
|
||||
- SSH control in `app/ssh_control.py`
|
||||
- route composition in `app/main.py`
|
||||
- Do not collapse these responsibilities into one file.
|
||||
|
||||
## API and Error Patterns
|
||||
|
||||
- Protected routes must use `Depends(require_auth)`.
|
||||
- Domain errors should raise module-specific exceptions:
|
||||
- `BitcoinRPCError`
|
||||
- `SSHControlError`
|
||||
- Domain exceptions are converted to HTTP `502` in centralized exception handlers.
|
||||
- Input validation should use Pydantic models and explicit HTTP errors for invalid state.
|
||||
|
||||
## Persistence Patterns
|
||||
|
||||
- Access SQLite through `app/db.py` helpers.
|
||||
- Keep node settings as a single upserted row with `id = 1`.
|
||||
- Preserve metrics retention behavior:
|
||||
- 30-day time window
|
||||
- 20000 row cap
|
||||
|
||||
## Frontend Patterns
|
||||
|
||||
- Keep frontend implementation in vanilla JavaScript plus Chart.js.
|
||||
- Maintain centralized client `state` object in `app/static/app.js`.
|
||||
- Use existing API helpers (`api`, error handling, auth transitions).
|
||||
- Keep responsive behavior aligned with current CSS breakpoints.
|
||||
|
||||
## Security and Operational Patterns
|
||||
|
||||
- Require authentication for all state-changing and node-control APIs.
|
||||
- Keep SSH command composition shell-safe using quoting.
|
||||
- Treat background sampling as best-effort to avoid impacting request flow.
|
||||
|
||||
## Documentation and Change Tracking
|
||||
|
||||
When behavior, contracts, or operations change:
|
||||
|
||||
1. Update affected docs in `/doc`.
|
||||
2. Update `README.md` if repository landing information changes.
|
||||
3. Record meaningful changes in `CHANGELOG.md` under `[Unreleased]`.
|
||||
|
||||
Reference index: `doc/README.md`.
|
||||
108
doc/data-models.md
Normal file
108
doc/data-models.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Data Models and Schema
|
||||
|
||||
## Storage Backend
|
||||
|
||||
- Database engine: SQLite.
|
||||
- Default file path: `./data/dashboard.db`.
|
||||
- Effective path is controlled by `DB_PATH`.
|
||||
|
||||
Related references:
|
||||
|
||||
- Configuration values: `doc/environment.md`
|
||||
- Persistence implementation: `app/db.py`
|
||||
|
||||
## Tables
|
||||
|
||||
### `node_settings`
|
||||
|
||||
Single-row settings table used as the source of truth for RPC and SSH connectivity.
|
||||
|
||||
Schema fields:
|
||||
|
||||
- `id INTEGER PRIMARY KEY CHECK (id = 1)`
|
||||
- `rpc_url TEXT NOT NULL`
|
||||
- `rpc_username TEXT NOT NULL`
|
||||
- `rpc_password TEXT NOT NULL`
|
||||
- `rpc_wallet TEXT NOT NULL`
|
||||
- `config_path TEXT NOT NULL`
|
||||
- `ssh_host TEXT NOT NULL`
|
||||
- `ssh_port INTEGER NOT NULL`
|
||||
- `ssh_username TEXT NOT NULL`
|
||||
- `ssh_password TEXT NOT NULL`
|
||||
- `ssh_key_path TEXT NOT NULL`
|
||||
- `bitcoin_binary TEXT NOT NULL`
|
||||
- `updated_at TEXT NOT NULL` (UTC ISO 8601 string)
|
||||
|
||||
Invariants:
|
||||
|
||||
- Exactly one logical settings record exists with `id = 1`.
|
||||
- `save_settings` performs an upsert.
|
||||
- Missing settings row is represented by in-code defaults.
|
||||
|
||||
### `metrics_history`
|
||||
|
||||
Time-series table for chart data and historical dashboard views.
|
||||
|
||||
Schema fields:
|
||||
|
||||
- `ts INTEGER PRIMARY KEY` (Unix timestamp, seconds)
|
||||
- `blocks INTEGER NOT NULL`
|
||||
- `headers INTEGER NOT NULL`
|
||||
- `mempool_bytes INTEGER NOT NULL`
|
||||
- `peers INTEGER NOT NULL`
|
||||
|
||||
Index:
|
||||
|
||||
- `idx_metrics_history_ts` on `ts`
|
||||
|
||||
Invariants:
|
||||
|
||||
- A timestamp has at most one row.
|
||||
- Inserts on duplicate timestamp overwrite existing point.
|
||||
- Returned history is chronological for UI consumption.
|
||||
|
||||
## Retention and Limits
|
||||
|
||||
Retention behavior is enforced during each metric insert:
|
||||
|
||||
- Time retention: rows older than 30 days are deleted.
|
||||
- Count retention: rows beyond the newest 20000 samples are deleted.
|
||||
- Query limit input is clamped to `[1, 20000]`.
|
||||
|
||||
## Metric Sampling Sources
|
||||
|
||||
A metric point is composed from RPC responses:
|
||||
|
||||
- `blocks` and `headers` from `getblockchaininfo`
|
||||
- `mempool_bytes` from `getmempoolinfo.bytes`
|
||||
- `peers` from `getnetworkinfo.connections`
|
||||
|
||||
Points are written:
|
||||
|
||||
- On each `/api/dashboard/summary` request.
|
||||
- In a background sampler loop at configured interval.
|
||||
|
||||
## Settings Defaults
|
||||
|
||||
Default values used when no persisted row exists:
|
||||
|
||||
- `rpc_url`: `http://127.0.0.1:8332`
|
||||
- `rpc_username`: empty string
|
||||
- `rpc_password`: empty string
|
||||
- `rpc_wallet`: empty string
|
||||
- `config_path`: empty string
|
||||
- `ssh_host`: empty string
|
||||
- `ssh_port`: `22`
|
||||
- `ssh_username`: empty string
|
||||
- `ssh_password`: empty string
|
||||
- `ssh_key_path`: empty string
|
||||
- `bitcoin_binary`: `bitcoind`
|
||||
- `updated_at`: empty string
|
||||
|
||||
## Data Consumers
|
||||
|
||||
- `/api/settings` and `/api/settings` (PUT) read and write `node_settings`.
|
||||
- `/api/dashboard/history*` reads `metrics_history`.
|
||||
- `/api/dashboard/summary` and sampler thread write `metrics_history`.
|
||||
|
||||
See `doc/api.md` for endpoint-level contracts.
|
||||
49
doc/environment.md
Normal file
49
doc/environment.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Environment Requirements
|
||||
|
||||
## Environment Loading
|
||||
|
||||
- Environment values are loaded from process environment.
|
||||
- `.env` is supported through `python-dotenv` at app startup.
|
||||
- Configuration is cached in memory after first load.
|
||||
|
||||
Source implementation: `app/config.py`
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `APP_USERNAME` | `admin` | Login username accepted by the dashboard. |
|
||||
| `APP_PASSWORD` | `changeme` | Plaintext password used when hash is not provided. |
|
||||
| `APP_PASSWORD_HASH` | unset | Bcrypt hash used instead of plaintext password when present. |
|
||||
| `SESSION_SECRET` | `change-this-secret` | Session signing secret for cookie middleware. |
|
||||
| `SESSION_COOKIE_SECURE` | `false` | When true, session cookie is sent only over HTTPS. |
|
||||
| `RPC_TIMEOUT_SECONDS` | `15` | Timeout for Bitcoin RPC HTTP requests. |
|
||||
| `METRICS_SAMPLER_INTERVAL_SECONDS` | `60` | Background metrics sampling interval in seconds. |
|
||||
| `DATA_DIR` | `./data` | Data directory root. Created if missing. |
|
||||
| `DB_PATH` | `./data/dashboard.db` | SQLite file path. Overrides default path under `DATA_DIR`. |
|
||||
|
||||
## Validation and Normalization Rules
|
||||
|
||||
- `METRICS_SAMPLER_INTERVAL_SECONDS` minimum is clamped to `15`.
|
||||
- `SESSION_COOKIE_SECURE` accepts truthy values:
|
||||
- `1`
|
||||
- `true`
|
||||
- `yes`
|
||||
- `on`
|
||||
- `DB_PATH` parent directory is created automatically when needed.
|
||||
|
||||
## Security Guidance
|
||||
|
||||
Production baseline:
|
||||
|
||||
1. Do not use default `APP_USERNAME`.
|
||||
2. Use `APP_PASSWORD_HASH` instead of plaintext password where possible.
|
||||
3. Use a long random `SESSION_SECRET`.
|
||||
4. Set `SESSION_COOKIE_SECURE=true` when served over HTTPS.
|
||||
5. Scope dashboard network access to trusted hosts only.
|
||||
|
||||
## Example `.env`
|
||||
|
||||
Reference example is provided in `.env.example`.
|
||||
|
||||
See `doc/build-and-deploy.md` for environment injection in Docker Compose.
|
||||
Reference in New Issue
Block a user