diff --git a/.github/workflows/board-controller-docker.yml b/.github/workflows/board-controller-docker.yml new file mode 100644 index 00000000..28bddbeb --- /dev/null +++ b/.github/workflows/board-controller-docker.yml @@ -0,0 +1,67 @@ +name: Build and Publish Board Controller Docker Image + +on: + push: + branches: [ main ] + paths: + - 'board-controller/**' + - '.github/workflows/board-controller-docker.yml' + release: + types: [published] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: boardsesh-board-controller + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v5 + with: + context: ./board-controller + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-name: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true \ No newline at end of file diff --git a/app/components/queue-control/hooks/use-controller-websocket.ts b/app/components/queue-control/hooks/use-controller-websocket.ts index 9fd2567c..e7f040b5 100644 --- a/app/components/queue-control/hooks/use-controller-websocket.ts +++ b/app/components/queue-control/hooks/use-controller-websocket.ts @@ -74,7 +74,13 @@ export const useControllerWebSocket = (): UseControllerWebSocketReturn => { } // Ensure the WebSocket URL includes the /ws path - const wsUrl = controllerUrl.endsWith('/ws') ? controllerUrl : `${controllerUrl}/ws`; + let wsUrl = controllerUrl.endsWith('/ws') ? controllerUrl : `${controllerUrl}/ws`; + + // Use WSS if the page is loaded over HTTPS + if (typeof window !== 'undefined' && window.location.protocol === 'https:') { + wsUrl = wsUrl.replace(/^ws:\/\//, 'wss://'); + } + console.log('🎮 Connecting to Board Controller:', wsUrl); wsRef.current = new WebSocket(wsUrl); @@ -175,7 +181,7 @@ export const useControllerWebSocket = (): UseControllerWebSocketReturn => { wsRef.current.close(1000, 'Component unmounting'); } }; - }, [isControllerMode, connect]); + }, [isControllerMode, controllerUrl, connect]); return { isControllerMode, diff --git a/board-controller/.dockerignore b/board-controller/.dockerignore new file mode 100644 index 00000000..20b93024 --- /dev/null +++ b/board-controller/.dockerignore @@ -0,0 +1,43 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Database (will be created in container) +board_controller.db +*.db + +# Logs +*.log + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docker +.dockerignore +Dockerfile +docker-compose.yml + +# Git +.git/ +.gitignore + +# Static frontend files (we redirect to BoardSesh now) +static/ + +# Data directory (mounted as volume) +data/ \ No newline at end of file diff --git a/board-controller/Dockerfile b/board-controller/Dockerfile new file mode 100644 index 00000000..850bce55 --- /dev/null +++ b/board-controller/Dockerfile @@ -0,0 +1,32 @@ +FROM python:3.11-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + bluez \ + python3-dbus \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy requirements and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY main.py . +COPY controller.py . + +# Create data directory for persistent storage +RUN mkdir -p /app/data + +# Expose port +EXPOSE 8000 + +# Set environment variables +ENV DB_PATH=/app/data/board_controller.db +ENV PYTHONUNBUFFERED=1 +ENV DOCKER_CONTAINER=1 + +# Run the application with auto-SSL enabled +CMD ["python", "main.py", "--host", "0.0.0.0", "--port", "8000", "--auto-ssl"] \ No newline at end of file diff --git a/board-controller/README.md b/board-controller/README.md index 66c6c78e..2579d841 100644 --- a/board-controller/README.md +++ b/board-controller/README.md @@ -1,35 +1,34 @@ # Board Controller -A unified Python server that combines Bluetooth control for Kilter/Tension boards with WebSocket integration for BoardSesh.com queue management. +A Python WebSocket server that enables persistent queue management and collaborative control for Kilter/Tension climbing boards. Integrates with BoardSesh.com to provide synchronized queue state across multiple devices and apps. ## Features - **Single Entry Point**: One command starts everything -- **Bluetooth Support**: Accepts commands from Kilter/Tension mobile apps -- **Web Integration**: Seamlessly integrates with BoardSesh.com via WebSocket -- **Queue Persistence**: SQLite database stores queue state across restarts -- **QR Code Interface**: Simple web UI with QR code for easy connection -- **API Caching**: BoardSesh API responses cached locally for performance +- **Bluetooth Support**: Accepts commands from Kilter/Tension mobile apps (optional) +- **WebSocket Integration**: Real-time synchronization with BoardSesh.com +- **Queue Persistence**: SQLite database maintains queue state across restarts +- **Auto-redirect**: localhost:8000 automatically redirects to BoardSesh with controller integration +- **Multi-device Support**: Control queue from both web browser and mobile apps simultaneously ## Quick Start ### Prerequisites - Python 3.8+ -- Node.js 18+ (for building frontend) -- Bluetooth adapter (for Bluetooth functionality) +- Bluetooth adapter (optional, for Bluetooth functionality) ### Installation ```bash # Install Python dependencies -pip install -r requirements.txt +pip install fastapi uvicorn aiosqlite -# Build React frontend -cd frontend -chmod +x build.sh -./build.sh -cd .. +# Or create requirements.txt: +echo "fastapi>=0.100.0" > requirements.txt +echo "uvicorn>=0.23.0" >> requirements.txt +echo "aiosqlite>=0.19.0" >> requirements.txt +pip install -r requirements.txt ``` ### Usage @@ -49,20 +48,18 @@ python main.py --port 8080 1. Run `python main.py` 2. Open http://localhost:8000 in your browser -3. Scan the QR code with your phone -4. This opens BoardSesh with controller integration -5. The controller takes over queue management -6. Use both web interface and Kilter/Tension apps +3. You'll be automatically redirected to BoardSesh with controller integration +4. The controller takes over queue management from BoardSesh +5. Add climbs to the queue - they persist across browser refreshes +6. Use both BoardSesh web interface and Kilter/Tension mobile apps ## Architecture ### Components -- **main.py**: Unified server entry point -- **bluetooth_controller.py**: Bluetooth service wrapper -- **boardsesh_client.py**: API client with caching -- **frontend/**: React web interface -- **board_controller.db**: SQLite database (auto-created) +- **main.py**: Unified FastAPI server with WebSocket support +- **controller.py**: Original Bluetooth controller integration (optional) +- **board_controller.db**: SQLite database for queue persistence (auto-created) ### API Endpoints @@ -75,49 +72,53 @@ python main.py --port 8080 ### WebSocket Protocol -Messages sent between BoardSesh and controller: +Key message types between BoardSesh and controller: -```typescript -// Handshake +```json +// Initial handshake from controller { "type": "controller-handshake", "sessionId": "uuid", + "controllerId": "uuid", "capabilities": ["queue", "bluetooth", "persistence"] } +// New connection request (triggers queue data response) +{ + "type": "new-connection", + "source": "boardsesh-client" +} + +// Queue state update +{ + "type": "initial-queue-data", + "queue": [...], + "currentClimbQueueItem": {...} +} + // Queue operations { "type": "add-queue-item", - "item": { /* queue item */ } + "item": { "climb": {...}, "addedBy": "user", "uuid": "..." } } -// Bluetooth updates { - "type": "bluetooth-update", - "ledData": [...], - "inferredClimb": "climb-uuid" + "type": "update-current-climb", + "item": {...}, + "shouldAddToQueue": false } ``` ## Development -### Frontend Development - -```bash -cd frontend -npm run dev # Starts Vite dev server on port 3001 -``` - -### Python Development - The server uses FastAPI with automatic reload: ```bash -# Install in development mode -pip install -e . +# Run with auto-reload for development +python main.py --no-bluetooth -# Run with auto-reload -uvicorn main:app --reload --port 8000 +# Or use uvicorn directly +uvicorn main:BoardController().app --reload --port 8000 ``` ### Database @@ -145,6 +146,36 @@ Options: - `--no-bluetooth` - Disable Bluetooth support - `--port PORT` - Server port (default: 8000) - `--host HOST` - Server host (default: 0.0.0.0) +- `--ssl-cert FILE` - SSL certificate file for HTTPS/WSS +- `--ssl-key FILE` - SSL private key file for HTTPS/WSS + +### SSL Setup (HTTPS/WSS Support) + +When accessing the controller from HTTPS sites like boardsesh.com, you need SSL support: + +**Generate development certificates:** +```bash +# Install cryptography library +pip install cryptography + +# Generate self-signed certificate +python generate_cert.py --ip 192.168.1.112 + +# Start server with SSL +python main.py --ssl-cert server.crt --ssl-key server.key +``` + +**Production certificates:** +```bash +# Use Let's Encrypt or other CA +certbot certonly --standalone -d your-domain.com + +# Start with real certificates +python main.py --ssl-cert /etc/letsencrypt/live/your-domain.com/fullchain.pem \ + --ssl-key /etc/letsencrypt/live/your-domain.com/privkey.pem +``` + +The server automatically detects SSL and uses WSS for WebSocket connections. ## Troubleshooting @@ -169,7 +200,7 @@ Options: 1. Check firewall settings 2. Ensure port 8000 is accessible -3. For HTTPS sites, controller needs HTTPS/WSS too +3. For HTTPS sites, controller needs HTTPS/WSS too (see SSL Setup below) ### Database Issues @@ -184,31 +215,80 @@ Options: ```bash # Install system dependencies sudo apt update -sudo apt install python3-pip bluez python3-dbus +sudo apt install python3-pip python3-venv + +# Optional: for Bluetooth support +sudo apt install bluez python3-dbus # Clone and setup git clone cd board-controller -pip install -r requirements.txt -cd frontend && ./build.sh && cd .. +python3 -m venv venv +source venv/bin/activate +pip install fastapi uvicorn aiosqlite + +# Run +python main.py + +# Run as service (create systemd service file) +sudo tee /etc/systemd/system/board-controller.service > /dev/null <