Add Dangerous Pi MVP implementation - complete backend and system integration

This commit adds the complete Dangerous Pi web management interface with all MVP features implemented and tested locally.

## New Features

### Backend (Python + FastAPI)
- Complete FastAPI backend with async support
- 40+ API endpoints (Health, PM3, WiFi, Updates, UPS, BLE, Plugins)
- 6 managers: Session, WiFi, Update, UPS, BLE, Plugin
- SQLite database with sessions, config, history, crash reports
- Server-Sent Events (SSE) for real-time notifications
- Mock PM3 worker for development without hardware

### WiFi Manager
- Interface detection (USB vs built-in)
- Network scanning with signal strength
- Mode switching (AP/Client/Dual/Auto/Off)
- Network connection with password support
- Hidden SSID and saved networks support
- Static IP and DHCP configuration
- 10 WiFi API endpoints

### Update Manager
- GitHub releases API integration
- Automatic periodic update checks
- Semantic version comparison
- Update download with progress tracking
- SHA256 checksum verification
- Automatic installation with backup and rollback
- PM3 client rebuild after updates
- 6 Update API endpoints

### UPS Manager
- I2C battery monitoring (MAX17040-compatible)
- Battery percentage, voltage, current tracking
- Power source detection (AC/Battery)
- Safe shutdown triggers at configurable thresholds
- Event callbacks for battery warnings
- SSE and BLE notification integration
- 3 UPS API endpoints

### BLE Manager
- Bluetooth Low Energy notification support
- Auto-detects BLE capability
- Multiple notification types (updates, battery, shutdown, etc.)
- BLE advertising management
- Device connection tracking
- 4 BLE API endpoints

### Plugin Framework
- Dynamic plugin loading/unloading
- Plugin lifecycle management (load, enable, disable, unload)
- Hook system for extensibility
- JSON-based metadata
- Example "Hello World" plugin included
- 7 Plugin API endpoints

### Frontend (Remix.js + React)
- Cyberpunk-themed responsive UI
- Dashboard with system status
- PM3 command interface with history
- Settings page with WiFi and Update management
- Command logs viewer
- Theme toggle (Dark/Light/Auto)
- Server-side rendering (SSR)
- Mobile-first responsive design

### System Integration
- Systemd service with security hardening
- Automated install/uninstall scripts
- Environment configuration template
- Hardware access groups (i2c, bluetooth, gpio, dialout)
- Pi-gen stage 04 integration for OS image building
- Port conflict resolution with ttyd-bash
- I2C interface auto-enable for UPS HAT

### Testing
- test_backend.py - Backend API tests
- test_ups.py - UPS manager tests
- test_ble.py - BLE manager tests
- test_plugins.py - Plugin manager tests
- All tests passing locally

### Documentation
- 12 comprehensive documentation files
- claude.md - AI development guide
- WIFI_MANAGER.md - WiFi management guide
- UPDATE_MANAGER.md - Update system guide
- PORT_CONFLICT.md - Port conflict resolution guide
- MVP_COMPLETE.md - MVP implementation summary
- PROJECT_STATUS.md - Project status and roadmap
- systemd/README.md - Service management docs
- pi-gen integration documentation

## Technical Details
- ~5,000+ lines of backend code
- 11 Python dependencies (smbus2 added for UPS)
- FastAPI with async/await throughout
- Type hints and docstrings on all functions
- RESTful API design with SSE for notifications
- Security hardening (non-root, protected dirs, resource limits)

## Next Steps
- Deploy to Raspberry Pi Zero 2 W hardware
- Test with real Proxmark3 device
- Test UPS HAT integration
- Test BLE on Pi hardware
- Build custom OS image with pi-gen
- Performance optimization for Pi Zero 2 W

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
michael
2025-11-26 08:11:36 -08:00
parent 0a586c5360
commit 1da6730735
68 changed files with 12673 additions and 5 deletions

241
app/backend/api/plugins.py Normal file
View File

@@ -0,0 +1,241 @@
"""Plugin management API endpoints."""
from typing import List, Dict, Any, Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from ..managers.plugin_manager import get_plugin_manager
router = APIRouter()
class PluginMetadataResponse(BaseModel):
"""Plugin metadata response model."""
id: str
name: str
version: str
description: str
author: str
homepage: Optional[str] = None
dependencies: List[str] = []
permissions: List[str] = []
class PluginInfoResponse(BaseModel):
"""Plugin info response model."""
metadata: PluginMetadataResponse
status: str
enabled: bool
error_message: Optional[str] = None
class PluginActionRequest(BaseModel):
"""Request model for plugin actions."""
plugin_id: str
@router.get("/discover")
async def discover_plugins():
"""Discover all available plugins.
Returns:
List of discovered plugin IDs
"""
try:
manager = get_plugin_manager()
discovered = await manager.discover_plugins()
return {
"success": True,
"plugins": discovered,
"count": len(discovered)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/list", response_model=List[PluginInfoResponse])
async def list_plugins():
"""Get list of all plugins.
Returns:
List of plugin information
"""
try:
manager = get_plugin_manager()
plugins = manager.get_all_plugins()
result = []
for plugin_id, plugin_info in plugins.items():
result.append(PluginInfoResponse(
metadata=PluginMetadataResponse(
id=plugin_info.metadata.id,
name=plugin_info.metadata.name,
version=plugin_info.metadata.version,
description=plugin_info.metadata.description,
author=plugin_info.metadata.author,
homepage=plugin_info.metadata.homepage,
dependencies=plugin_info.metadata.dependencies,
permissions=plugin_info.metadata.permissions
),
status=plugin_info.status.value,
enabled=plugin_info.enabled,
error_message=plugin_info.error_message
))
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{plugin_id}", response_model=PluginInfoResponse)
async def get_plugin_info(plugin_id: str):
"""Get information about a specific plugin.
Args:
plugin_id: ID of the plugin
Returns:
Plugin information
"""
try:
manager = get_plugin_manager()
plugin_info = manager.get_plugin_info(plugin_id)
if not plugin_info:
raise HTTPException(status_code=404, detail="Plugin not found")
return PluginInfoResponse(
metadata=PluginMetadataResponse(
id=plugin_info.metadata.id,
name=plugin_info.metadata.name,
version=plugin_info.metadata.version,
description=plugin_info.metadata.description,
author=plugin_info.metadata.author,
homepage=plugin_info.metadata.homepage,
dependencies=plugin_info.metadata.dependencies,
permissions=plugin_info.metadata.permissions
),
status=plugin_info.status.value,
enabled=plugin_info.enabled,
error_message=plugin_info.error_message
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/enable")
async def enable_plugin(request: PluginActionRequest):
"""Enable a plugin.
Args:
request: Plugin action request with plugin_id
Returns:
Success message
"""
try:
manager = get_plugin_manager()
success = await manager.enable_plugin(request.plugin_id)
if not success:
raise HTTPException(status_code=500, detail="Failed to enable plugin")
return {
"success": True,
"message": f"Plugin {request.plugin_id} enabled"
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/disable")
async def disable_plugin(request: PluginActionRequest):
"""Disable a plugin.
Args:
request: Plugin action request with plugin_id
Returns:
Success message
"""
try:
manager = get_plugin_manager()
success = await manager.disable_plugin(request.plugin_id)
if not success:
raise HTTPException(status_code=500, detail="Failed to disable plugin")
return {
"success": True,
"message": f"Plugin {request.plugin_id} disabled"
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/load")
async def load_plugin(request: PluginActionRequest):
"""Load a plugin into memory.
Args:
request: Plugin action request with plugin_id
Returns:
Success message
"""
try:
manager = get_plugin_manager()
success = await manager.load_plugin(request.plugin_id)
if not success:
raise HTTPException(status_code=500, detail="Failed to load plugin")
return {
"success": True,
"message": f"Plugin {request.plugin_id} loaded"
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/unload")
async def unload_plugin(request: PluginActionRequest):
"""Unload a plugin from memory.
Args:
request: Plugin action request with plugin_id
Returns:
Success message
"""
try:
manager = get_plugin_manager()
success = await manager.unload_plugin(request.plugin_id)
if not success:
raise HTTPException(status_code=500, detail="Failed to unload plugin")
return {
"success": True,
"message": f"Plugin {request.plugin_id} unloaded"
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))