232 lines
5.5 KiB
Markdown
232 lines
5.5 KiB
Markdown
# Route-Switcher API Design
|
|
|
|
## Overview
|
|
|
|
HTTP REST API with Basic Authentication for Home Assistant integration, exposing state machine state and ping statistics.
|
|
|
|
## Design Principles
|
|
|
|
- **Minimal surface area**: Only expose necessary information
|
|
- **Simple authentication**: HTTP Basic Auth (no JWT complexity)
|
|
- **State-focused**: Centered on state machine state and ping history
|
|
- **Home Assistant friendly**: Structured for HA REST integration
|
|
- **Opt-in**: API disabled by default
|
|
|
|
## API Endpoints
|
|
|
|
### GET /api/state
|
|
|
|
Returns current state machine state with ping statistics.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"state": "Primary",
|
|
"primary_stats": {
|
|
"success_rate": 95.5,
|
|
"failures": 2,
|
|
"total_pings": 44,
|
|
"last_ping": "Ok"
|
|
},
|
|
"secondary_stats": {
|
|
"success_rate": 98.2,
|
|
"failures": 1,
|
|
"total_pings": 56,
|
|
"last_ping": "Ok"
|
|
},
|
|
"last_failover": "2024-02-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
**Fields:**
|
|
- `state`: Current state machine state (Boot/Primary/Fallback)
|
|
- `primary_stats`: Ping statistics for primary interface
|
|
- `secondary_stats`: Ping statistics for secondary interface
|
|
- `last_failover`: ISO 8601 timestamp of last failover (null if never)
|
|
|
|
### POST /api/state
|
|
|
|
Manually set state machine state.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"state": "fallback"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"state": "Fallback",
|
|
"previous_state": "Primary",
|
|
"primary_stats": { ... },
|
|
"secondary_stats": { ... },
|
|
"last_failover": "2024-02-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
**Valid states:** `primary`, `fallback`
|
|
|
|
## Authentication
|
|
|
|
HTTP Basic Authentication with username/password configured via environment variables.
|
|
|
|
**Security considerations:**
|
|
- Passwords stored as bcrypt hash
|
|
- HTTPS recommended for production
|
|
- Local network access only
|
|
- No token management (stateless)
|
|
|
|
## Data Structures
|
|
|
|
### PingStats
|
|
|
|
Calculated from state machine ping history (60 entries per interface):
|
|
|
|
```rust
|
|
struct PingStats {
|
|
success_rate: f64, // Percentage of successful pings
|
|
failures: usize, // Number of failed pings in history
|
|
total_pings: usize, // Total pings in history
|
|
last_ping: String, // "Ok" or "Failed"
|
|
}
|
|
```
|
|
|
|
### StateResponse
|
|
|
|
```rust
|
|
struct StateResponse {
|
|
state: String,
|
|
primary_stats: PingStats,
|
|
secondary_stats: PingStats,
|
|
last_failover: Option<String>,
|
|
}
|
|
```
|
|
|
|
## Home Assistant Integration
|
|
|
|
### REST Sensor Configuration
|
|
|
|
```yaml
|
|
sensor:
|
|
- platform: rest
|
|
name: Route Switcher State
|
|
resource: http://route-switcher.local:8080/api/state
|
|
username: !secret route_switcher_user
|
|
password: !secret route_switcher_pass
|
|
value_template: "{{ value_json.state }}"
|
|
json_attributes:
|
|
- primary_stats
|
|
- secondary_stats
|
|
- last_failover
|
|
|
|
- platform: template
|
|
sensors:
|
|
route_switcher_primary_success_rate:
|
|
value_template: "{{ state_attr('sensor.route_switcher_state', 'primary_stats').success_rate | default(0) }}"
|
|
unit_of_measurement: "%"
|
|
route_switcher_secondary_success_rate:
|
|
value_template: "{{ state_attr('sensor.route_switcher_state', 'secondary_stats').success_rate | default(0) }}"
|
|
unit_of_measurement: "%"
|
|
route_switcher_primary_failures:
|
|
value_template: "{{ state_attr('sensor.route_switcher_state', 'primary_stats').failures | default(0) }}"
|
|
route_switcher_secondary_failures:
|
|
value_template: "{{ state_attr('sensor.route_switcher_state', 'secondary_stats').failures | default(0) }}"
|
|
|
|
switch:
|
|
- platform: rest
|
|
name: Route Switcher Control
|
|
resource: http://route-switcher.local:8080/api/state
|
|
username: !secret route_switcher_user
|
|
password: !secret route_switcher_pass
|
|
body_on: '{"state": "fallback"}'
|
|
body_off: '{"state": "primary"}'
|
|
is_on_template: "{{ value_json.state == 'fallback' }}"
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
```bash
|
|
# API Configuration
|
|
API_ENABLED=true
|
|
API_BIND_ADDRESS=0.0.0.0
|
|
API_PORT=8080
|
|
API_USERNAME=admin
|
|
API_PASSWORD_HASH=<bcrypt-hash>
|
|
|
|
# CORS Configuration
|
|
API_CORS_ORIGINS=http://homeassistant.local:8123
|
|
```
|
|
|
|
### Password Hash Generation
|
|
|
|
```bash
|
|
# Generate bcrypt hash
|
|
echo -n "your-password" | bcrypt
|
|
```
|
|
|
|
## Implementation Details
|
|
|
|
### Dependencies
|
|
|
|
```toml
|
|
axum = "0.7"
|
|
tokio = { version = "1.42", features = ["full"] }
|
|
tower = "0.4"
|
|
tower-http = { version = "0.5", features = ["cors", "auth"] }
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
serde_json = "1.0"
|
|
chrono = { version = "0.4", features = ["serde"] }
|
|
bcrypt = "0.15"
|
|
base64 = "0.22"
|
|
```
|
|
|
|
### Architecture
|
|
|
|
- **API Module**: `src/api.rs` - HTTP server and endpoints
|
|
- **State Sharing**: Thread-safe access to state machine and ping history
|
|
- **Authentication**: Basic Auth middleware with bcrypt validation
|
|
- **Error Handling**: Standardized JSON error responses
|
|
- **Integration**: Minimal changes to existing state machine
|
|
|
|
### Thread Safety
|
|
|
|
- `Arc<Mutex<StateMachine>>` for shared state access
|
|
- Non-blocking async operations
|
|
- Minimal locking duration
|
|
|
|
## Error Handling
|
|
|
|
Standardized error responses:
|
|
|
|
```json
|
|
{
|
|
"error": "Invalid state",
|
|
"message": "State must be 'primary' or 'fallback'"
|
|
}
|
|
```
|
|
|
|
HTTP Status Codes:
|
|
- 200: Success
|
|
- 400: Bad Request (invalid state)
|
|
- 401: Unauthorized (invalid credentials)
|
|
- 500: Internal Server Error
|
|
|
|
## Security Considerations
|
|
|
|
- Network access restrictions (local only recommended)
|
|
- HTTPS for credential protection
|
|
- Rate limiting considerations
|
|
- Audit logging for manual state changes
|
|
- No configuration exposure (state only)
|
|
|
|
## Backward Compatibility
|
|
|
|
- API disabled by default
|
|
- No changes to existing CLI functionality
|
|
- Service continues without API if disabled
|
|
- Graceful degradation on API errors
|