diff --git a/CHANGELOG-v2.1.md b/CHANGELOG-v2.1.md new file mode 100644 index 0000000..b1976ff --- /dev/null +++ b/CHANGELOG-v2.1.md @@ -0,0 +1,232 @@ +# Changelog - Version 2.1.0 + +## 🎉 Release Highlights + +Version 2.1.0 fixes critical Docker authentication issues and introduces Home Assistant integration mode for advanced deployment scenarios. + +--- + +## 🐛 Bug Fixes + +### Fixed: Docker Spotify Authentication Failure + +**Issue**: Running SpotifyToWLED in Docker resulted in authentication error: +``` +ERROR - ✗ Spotify authentication failed: [Errno 98] Address already in use +``` + +**Root Cause**: +- SpotifyOAuth's default behavior attempts to start a local HTTP server for OAuth callback +- This conflicts with Flask app already running on port 5000 +- In Docker/headless environments, the browser-based auth flow is not suitable + +**Solution Implemented**: +1. Added `/callback` route in Flask application to handle OAuth redirects +2. Modified SpotifyOAuth initialization with `open_browser=False` parameter +3. Implemented persistent token caching in `/config/.spotify_cache` +4. Created web-based authentication flow with UI button +5. Added API endpoints for OAuth URL generation and callback handling + +**Impact**: +- ✅ Spotify authentication now works in Docker containers +- ✅ Tokens persist across container restarts (no re-authentication needed) +- ✅ User-friendly authentication via web interface +- ✅ Fully compatible with headless/server environments + +--- + +## ✨ New Features + +### Home Assistant Integration Mode + +Added dual-mode support for Home Assistant addon: + +#### Standalone Mode (Default) +- Runs complete SpotifyToWLED application within Home Assistant +- Self-contained with no external dependencies +- Original behavior preserved for existing users +- Best for single Home Assistant instances + +#### Integration Mode (New) +- Lightweight HTTP proxy to external Docker server +- Share one SpotifyToWLED instance across multiple Home Assistant installations +- Significantly reduced resource usage in Home Assistant +- Centralized configuration and management +- Perfect for multi-instance or Portainer deployments + +**Configuration Example**: +```yaml +# Standalone Mode +mode: "standalone" +spotify_client_id: "your_client_id" +spotify_client_secret: "your_secret" +wled_ips: + - "192.168.1.100" + +# Integration Mode +mode: "integration" +server_url: "http://192.168.1.50:5000" +``` + +**Benefits**: +- 🔋 Reduced CPU/memory usage in Home Assistant +- 🔄 Easy updates through centralized Docker management +- 🏢 Enterprise-friendly for multiple HA instances +- 🎯 Better separation of concerns + +--- + +## 🔧 Technical Changes + +### Application Core + +**`app/utils/spotify_manager.py`**: +- Added `cache_path` parameter to constructor +- Implemented `get_auth_url()` method for OAuth flow initiation +- Implemented `handle_callback()` method for OAuth code exchange +- Set `open_browser=False` in SpotifyOAuth initialization +- Automatic cache path resolution using config directory + +**`app/routes/web.py`**: +- Added `/callback` route for Spotify OAuth redirect handling +- Added `/api/spotify/auth-url` endpoint for authorization URL generation +- Error handling for OAuth failures +- Flash messages for user feedback + +**`app/core/sync_engine.py`**: +- Added `get_spotify_auth_url()` method +- Added `handle_spotify_callback()` method +- Integration with SpotifyManager OAuth methods + +**`app/templates/index.html`**: +- Added "Authenticate with Spotify" button +- Conditional display based on authentication status +- User-friendly messaging for auth status + +**`app/static/js/app.js`**: +- Added `authenticateSpotify()` function +- Handles auth URL fetching and redirection +- Error display for auth failures + +### Home Assistant Addon + +**`homeassistant/spotifytowled/config.json`**: +- Added `mode` configuration option (standalone/integration) +- Added `server_url` for integration mode +- Made Spotify credentials optional (not needed in integration mode) +- Version bumped to 2.1.0 +- Updated schema for new options + +**`homeassistant/spotifytowled/run.sh`**: +- Mode detection and branching logic +- Standalone mode: runs full application (original behavior) +- Integration mode: creates HTTP proxy using Flask +- Proxy implementation forwards all requests to external server +- Automatic installation of `requests` library for proxy + +### Documentation + +**`README.md`**: +- Added v2.1 feature highlights +- Updated setup instructions with authentication steps +- Added Spotify redirect URI configuration + +**`DOCKER.md`**: +- Added "Initial Setup and Spotify Authentication" section +- Step-by-step authentication guide +- Token caching explanation + +**`HOMEASSISTANT.md`**: +- Complete rewrite with dual-mode documentation +- Pros/cons comparison of each mode +- Separate installation guides for both modes +- Integration mode setup instructions + +**`homeassistant/spotifytowled/README.md`**: +- Dual-mode feature documentation +- Configuration examples for both modes +- Troubleshooting for integration mode + +--- + +## 🔒 Security + +### CodeQL Analysis: ✅ PASSED +- **Python**: 0 vulnerabilities found +- **JavaScript**: 0 vulnerabilities found + +### Security Improvements: +- OAuth tokens stored in persistent volume (not in code) +- Redirect URI validation through Spotify Developer Console +- No credentials stored in container layers +- Secure token caching mechanism + +--- + +## 📦 Migration Guide + +### From v2.0 to v2.1 + +**Docker Users**: +1. Pull latest image: `docker pull ghcr.io/raphaelbleier/spotifytowled:latest` +2. Stop container: `docker stop spotifytowled` +3. Remove container: `docker rm spotifytowled` +4. Recreate with same volumes (preserves config) +5. Access web UI and click "Authenticate with Spotify" +6. Your config and WLED devices are preserved + +**Home Assistant Users (Standalone)**: +1. Update addon from Home Assistant +2. Configuration automatically migrates (adds `mode: standalone`) +3. No manual changes needed +4. Restart addon + +**Home Assistant Users (Migrating to Integration)**: +1. Deploy Docker server separately (see DOCKER.md) +2. Configure Docker server via web UI +3. Update HA addon config: + ```yaml + mode: "integration" + server_url: "http://your-docker-server:5000" + ``` +4. Restart addon +5. Remove Spotify credentials from HA config (now in Docker server) + +### Backward Compatibility +- ✅ All v2.0 configurations work without changes +- ✅ Default behavior unchanged (standalone mode) +- ✅ No breaking changes to API or configuration structure + +--- + +## 🐞 Known Issues + +None identified in this release. + +--- + +## 👥 Contributors + +- **raphaelbleier** - Maintainer +- GitHub Copilot - Code assistance + +--- + +## 📅 Release Date + +November 13, 2024 + +--- + +## 🔗 Links + +- **Repository**: https://github.com/raphaelbleier/SpotifyToWled +- **Docker Hub**: ghcr.io/raphaelbleier/spotifytowled +- **Issues**: https://github.com/raphaelbleier/SpotifyToWled/issues +- **Documentation**: See README.md, DOCKER.md, and HOMEASSISTANT.md + +--- + +## 🙏 Acknowledgments + +Thanks to all users who reported the Docker authentication issue and provided feedback! diff --git a/DOCKER.md b/DOCKER.md index 775901f..255105f 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -26,6 +26,33 @@ docker run -d \ docker-compose up -d ``` +## Initial Setup and Spotify Authentication + +After deploying the container, you need to authenticate with Spotify: + +1. **Access the web interface**: `http://your-server-ip:5000` + +2. **Configure Spotify Credentials**: + - Get credentials from [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) + - In the Spotify Dashboard, add redirect URI: `http://your-server-ip:5000/callback` + - Enter Client ID and Client Secret in the web UI + - Click "Save Configuration" + +3. **Authenticate with Spotify**: + - Click the "Authenticate with Spotify" button + - Log in with your Spotify account + - Authorize the application + - You'll be redirected back to the app + +4. **Add WLED Devices**: + - Enter your WLED device IP addresses + - Click "Add Device" for each one + +5. **Start Sync**: + - Click "Start Sync" to begin syncing colors + +**Note:** The authentication token is cached in `/config/.spotify_cache`, so you won't need to re-authenticate unless the token expires or you change credentials. + ## Deploying on Portainer ### Method 1: Using Docker Compose (Recommended) diff --git a/HOMEASSISTANT.md b/HOMEASSISTANT.md index 98d5b85..26f45ee 100644 --- a/HOMEASSISTANT.md +++ b/HOMEASSISTANT.md @@ -4,9 +4,40 @@ This guide explains how to integrate SpotifyToWLED with Home Assistant. ## Installation Methods -### Method 1: Home Assistant Add-on (Recommended) +You can run SpotifyToWLED with Home Assistant in two ways: -The easiest way to run SpotifyToWLED on Home Assistant is using the official add-on. +### Method 1: Standalone Add-on (All-in-One) + +Run the complete SpotifyToWLED application within Home Assistant. Best for simple setups. + +**Pros:** +- Everything in one place +- No external dependencies +- Easy to configure + +**Cons:** +- Uses more Home Assistant resources +- Separate config per HA instance + +### Method 2: Integration Mode (Connect to Docker Server) + +Connect your Home Assistant to an external SpotifyToWLED Docker server. Best for advanced setups. + +**Pros:** +- Lighter on Home Assistant resources +- Share one server across multiple HA instances +- Centralized management via Portainer +- Easier to update and maintain + +**Cons:** +- Requires external Docker server +- Additional setup step + +--- + +## Standalone Add-on Installation + +The easiest way to run SpotifyToWLED is using the official add-on in standalone mode. #### Step 1: Add Repository @@ -29,6 +60,7 @@ The easiest way to run SpotifyToWLED on Home Assistant is using the official add 2. Fill in your settings: ```yaml +mode: "standalone" spotify_client_id: "your_client_id_here" spotify_client_secret: "your_client_secret_here" wled_ips: @@ -53,15 +85,55 @@ color_extraction_method: "vibrant" - Click **Open Web UI** button - Or access via Ingress in Home Assistant -- Configure Spotify credentials and WLED devices -### Method 2: Docker Container in Home Assistant +--- + +## Integration Mode Installation + +This method assumes you already have SpotifyToWLED running in Docker (via Portainer or docker-compose). + +### Step 1: Deploy SpotifyToWLED Docker Server + +First, set up the Docker server following the [Docker Deployment Guide](DOCKER.md). + +**Quick Docker setup:** +```bash +docker run -d \ + --name spotifytowled \ + -p 5000:5000 \ + -v $(pwd)/config:/config \ + -v $(pwd)/data:/data \ + --restart unless-stopped \ + ghcr.io/raphaelbleier/spotifytowled:latest +``` + +Configure it via the web UI at `http://your-docker-host:5000` + +### Step 2: Install Home Assistant Add-on + +Follow steps 1-2 from "Standalone Add-on Installation" above. + +### Step 3: Configure Integration Mode + +1. Go to the **Configuration** tab +2. Set configuration to integration mode: + +```yaml +mode: "integration" +server_url: "http://192.168.1.50:5000" +``` + +Replace `192.168.1.50` with your Docker server's IP address. + +3. Click **Save** + +### Step 4: Start and Access -If you prefer to run as a standalone Docker container: +1. Click **Start** +2. Click **Open Web UI** - it will proxy to your Docker server +3. All configuration is done on the Docker server -1. Install **Portainer** add-on from Home Assistant -2. Follow the [Docker Deployment Guide](DOCKER.md) -3. Access at `http://homeassistant.local:5000` +**Note:** In integration mode, the Spotify credentials and WLED devices are configured on the Docker server, not in the Home Assistant addon config. ## Getting Spotify Credentials diff --git a/README.md b/README.md index d9ca790..f768929 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ Bring your music to life! **SpotifyToWLED** syncs the color palette of your Spotify album covers with your WLED LEDs for a vibrant, immersive experience. +## ✨ What's New in v2.1 + +- 🔐 **Fixed Docker Spotify authentication** - No more "Address already in use" errors +- 🔗 **OAuth callback route** for seamless Spotify login in Docker/headless environments +- 💾 **Token caching** - No need to re-authenticate on every restart +- 🏠 **Home Assistant integration mode** - Connect to external Docker servers +- 🔄 **Proxy mode** for lightweight HA addon deployment +- 📝 **Enhanced documentation** for all deployment scenarios + ## ✨ What's New in v2.0 - 🏗️ **Restructured codebase** with proper modular architecture @@ -83,7 +92,11 @@ docker-compose up -d ``` 5. **Configure the application**: + - Get your Spotify credentials from [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) + - In Spotify Dashboard, add redirect URI: `http://localhost:5000/callback` (or your server IP) - Enter your Spotify **Client ID** and **Client Secret** + - Click **Save Configuration** + - Click **Authenticate with Spotify** button and log in - Add your WLED device IP addresses - Adjust refresh interval if needed - Choose your preferred color extraction method diff --git a/app/core/sync_engine.py b/app/core/sync_engine.py index 615623b..84cc445 100644 --- a/app/core/sync_engine.py +++ b/app/core/sync_engine.py @@ -51,6 +51,50 @@ def initialize_spotify(self) -> bool: logger.error(f"Failed to initialize Spotify: {e}") return False + def get_spotify_auth_url(self) -> Optional[str]: + """ + Get Spotify authorization URL for OAuth flow + + Returns: + Authorization URL or None + """ + try: + if not self.spotify_manager: + # Initialize spotify manager if not already done + self.spotify_manager = SpotifyManager( + client_id=config.get("SPOTIFY_CLIENT_ID"), + client_secret=config.get("SPOTIFY_CLIENT_SECRET"), + redirect_uri=config.get("SPOTIFY_REDIRECT_URI"), + scope=config.get("SPOTIFY_SCOPE") + ) + # Initialize auth manager + self.spotify_manager.authenticate() + + return self.spotify_manager.get_auth_url() + except Exception as e: + logger.error(f"Failed to get auth URL: {e}") + return None + + def handle_spotify_callback(self, code: str) -> bool: + """ + Handle Spotify OAuth callback + + Args: + code: Authorization code from Spotify + + Returns: + True if successful, False otherwise + """ + try: + if not self.spotify_manager: + logger.error("Spotify manager not initialized") + return False + + return self.spotify_manager.handle_callback(code) + except Exception as e: + logger.error(f"Failed to handle callback: {e}") + return False + def start(self) -> bool: """ Start the sync loop in a background thread diff --git a/app/routes/web.py b/app/routes/web.py index 2bc1ce6..1f046ad 100644 --- a/app/routes/web.py +++ b/app/routes/web.py @@ -181,3 +181,46 @@ def health_check(): 'version': '2.0.0', 'sync_running': sync_engine.is_running }) + + @app.route('/callback') + def spotify_callback(): + """Handle Spotify OAuth callback""" + try: + code = request.args.get('code') + error = request.args.get('error') + + if error: + logger.error(f"Spotify OAuth error: {error}") + flash(f'Spotify authorization failed: {error}', 'danger') + return redirect(url_for('index')) + + if not code: + logger.error("No authorization code received") + flash('No authorization code received from Spotify', 'danger') + return redirect(url_for('index')) + + # Handle the callback + if sync_engine.handle_spotify_callback(code): + flash('Successfully authenticated with Spotify!', 'success') + else: + flash('Failed to complete Spotify authentication', 'danger') + + return redirect(url_for('index')) + + except Exception as e: + logger.error(f"Error in Spotify callback: {e}") + flash('An error occurred during Spotify authentication', 'danger') + return redirect(url_for('index')) + + @app.route('/api/spotify/auth-url') + def api_spotify_auth_url(): + """Get Spotify authorization URL""" + try: + auth_url = sync_engine.get_spotify_auth_url() + if auth_url: + return jsonify({'success': True, 'auth_url': auth_url}) + else: + return jsonify({'success': False, 'message': 'Failed to generate auth URL'}), 400 + except Exception as e: + logger.error(f"Error generating auth URL: {e}") + return jsonify({'success': False, 'message': 'An error occurred'}), 500 diff --git a/app/static/js/app.js b/app/static/js/app.js index 8d75b43..da7c97a 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -111,6 +111,26 @@ async function checkWledHealth(ip) { } } +// Authenticate with Spotify +async function authenticateSpotify() { + try { + showLoading(); + const response = await fetch(`${API_BASE}/spotify/auth-url`); + const data = await response.json(); + + if (data.success && data.auth_url) { + // Open Spotify auth in new window + window.location.href = data.auth_url; + } else { + showError(data.message || 'Failed to get authentication URL'); + } + } catch (error) { + showError('Error getting auth URL: ' + error.message); + } finally { + hideLoading(); + } +} + // Update status in real-time async function updateStatus() { try { diff --git a/app/templates/index.html b/app/templates/index.html index bdc7ffc..8919e69 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -147,6 +147,16 @@