Add subspace-tv FastAPI service with Unraid deployment

Includes FastAPI application with device authentication, Docker configuration,
Bruno API tests, and git-based deployment scripts for Unraid.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Simard
2026-01-22 09:22:32 -06:00
parent 171d55bd4e
commit 57ad4b0f33
21 changed files with 904 additions and 0 deletions

254
README.md Normal file
View File

@@ -0,0 +1,254 @@
# Subspace TV Configuration Server
A Docker-hosted FastAPI service that serves JSON configuration to tvOS clients. Authorized devices receive privileged configuration while unauthorized devices receive blank configuration.
## Features
- Device-based authorization via custom header (`X-Device-ID`)
- Hot-reload of authorization list without service restart
- Docker containerization with non-root user
- CORS support for web-based clients
- Health check endpoint for monitoring
## Architecture
```
subspace-tv/
├── src/
│ ├── __init__.py
│ ├── config.py # Pydantic settings management
│ ├── auth.py # Authorization logic with hot-reload
│ └── main.py # FastAPI application
├── bruno/
│ ├── environments/ # Bruno environment configurations
│ └── *.bru # Bruno API test requests
├── config/
│ └── authorized_devices.json # Device authorization list
├── .env.example # Environment variable template
├── Dockerfile # Container image definition
├── docker-compose.yml # Service orchestration
└── requirements.txt # Python dependencies
```
## Local Development
### Prerequisites
- Python 3.11+
- pip
### Setup
1. Clone the repository and navigate to the project directory
2. Install dependencies:
```bash
pip install -r requirements.txt
```
3. Create environment file:
```bash
cp .env.example .env
```
4. Edit `.env` with your configuration values
5. Run the development server:
```bash
uvicorn src.main:app --reload
```
6. Access the API at `http://localhost:8000`
## Docker Deployment
### Prerequisites
- Docker
- Docker Compose
### Setup
1. Create environment file:
```bash
cp .env.example .env
```
2. Configure `.env` with production values:
```env
CONFIG_URL=https://your-production-server.com
CONFIG_PORT=8000
CONFIG_TIMEZONE=America/New_York
CONFIG_UPDATE_INTERVAL=3600
```
3. Configure authorized devices in `config/authorized_devices.json`:
```json
{
"authorized_devices": [
"tvos-device-001",
"tvos-device-002"
]
}
```
4. Build and launch the container:
```bash
docker-compose up -d
```
5. Verify the service is running:
```bash
curl http://localhost:8000/health
```
## API Endpoints
### Health Check
**GET** `/health`
Returns service health status.
**Response:**
```json
{
"status": "healthy"
}
```
### Get Configuration
**GET** `/api/v1/config`
**Headers:**
- `X-Device-ID` (required): Device identifier
**Response (Authorized):**
```json
{
"authorized": true,
"config": {
"url": "https://your-server.example.com",
"port": 8000,
"timezone": "America/New_York",
"update_interval": 3600
}
}
```
**Response (Unauthorized):**
```json
{
"authorized": false,
"config": {}
}
```
**Error Response (Missing Header):**
```json
{
"detail": "X-Device-ID header is required"
}
```
Status: `400 Bad Request`
## Usage Examples
### Test with curl (Authorized Device)
```bash
curl -H "X-Device-ID: device-example-001" http://localhost:8000/api/v1/config
```
### Test with curl (Unauthorized Device)
```bash
curl -H "X-Device-ID: unknown-device" http://localhost:8000/api/v1/config
```
### Test with Bruno
A Bruno API collection is available in the `bruno/` directory for comprehensive API testing.
**Prerequisites:**
- Install [Bruno](https://www.usebruno.com/)
**Usage:**
1. Open Bruno
2. Open Collection → Select the `bruno/` directory in this project
3. Select environment (Local or Production)
4. Run individual requests or entire collection
**Available Requests:**
- `Health Check` - Verify service status
- `Config - Authorized Device` - Test authorized device response
- `Config - Unauthorized Device` - Test unauthorized device response
- `Config - Missing Header` - Test error handling for missing header
All requests include assertions to validate expected responses.
## Authorization Management
### Adding Authorized Devices
Edit `config/authorized_devices.json` and add device IDs:
```json
{
"authorized_devices": [
"tvos-device-001",
"tvos-device-002",
"new-device-id"
]
}
```
The authorization list is automatically reloaded when the file is modified. No service restart required.
### Removing Authorized Devices
Remove device IDs from `config/authorized_devices.json`. Changes take effect immediately.
## Configuration Reference
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `HOST` | Server bind address | `0.0.0.0` |
| `PORT` | Server port | `8000` |
| `DEBUG` | Debug mode | `false` |
| `AUTHORIZATION_FILE` | Path to authorization JSON | `/app/config/authorized_devices.json` |
| `CONFIG_URL` | URL returned to authorized devices | `https://your-server.example.com` |
| `CONFIG_PORT` | Port returned to authorized devices | `8000` |
| `CONFIG_TIMEZONE` | Timezone for authorized devices | `America/New_York` |
| `CONFIG_UPDATE_INTERVAL` | Update interval in seconds | `3600` |
| `CORS_ORIGINS` | Allowed CORS origins | `["*"]` |
## Security Considerations
- The service runs as a non-root user (`appuser`) in the Docker container
- Authorization file is mounted from the host filesystem
- Privileged configuration data is stored in environment variables
- No sensitive data is returned to unauthorized devices
- Health check endpoint is publicly accessible for orchestration
## Troubleshooting
### Service won't start
Check Docker logs:
```bash
docker-compose logs -f
```
### Authorization not working
1. Verify `config/authorized_devices.json` format is valid JSON
2. Check that device ID matches exactly (case-sensitive)
3. Confirm file permissions allow reading by container user
### Hot-reload not working
The authorization file is checked for modifications on each request. Ensure the file modification timestamp changes when editing.