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 @@

{{ current_track.name }}

Save Configuration + + {% if not spotify_authenticated and spotify_client_id and spotify_client_secret %} +
+
+ You need to authorize this app with Spotify +
+ + {% endif %} diff --git a/app/utils/spotify_manager.py b/app/utils/spotify_manager.py index bb37f42..d64959e 100644 --- a/app/utils/spotify_manager.py +++ b/app/utils/spotify_manager.py @@ -4,6 +4,7 @@ import spotipy from spotipy.oauth2 import SpotifyOAuth import logging +import os from typing import Optional, Dict logger = logging.getLogger(__name__) @@ -13,12 +14,17 @@ class SpotifyManager: """Manage Spotify API interactions with caching""" def __init__(self, client_id: str, client_secret: str, - redirect_uri: str, scope: str): + redirect_uri: str, scope: str, cache_path: str = None): self.client_id = client_id self.client_secret = client_secret self.redirect_uri = redirect_uri self.scope = scope + self.cache_path = cache_path or os.path.join( + os.environ.get('CONFIG_PATH', '/config').rsplit('/', 1)[0], + '.spotify_cache' + ) self._sp = None + self._auth_manager = None self._last_track_id = None self._track_cache = {} self._cache_duration = 5 @@ -31,13 +37,15 @@ def authenticate(self) -> bool: True if successful, False otherwise """ try: - auth_manager = SpotifyOAuth( + self._auth_manager = SpotifyOAuth( client_id=self.client_id, client_secret=self.client_secret, redirect_uri=self.redirect_uri, - scope=self.scope + scope=self.scope, + cache_path=self.cache_path, + open_browser=False # Don't try to open browser in Docker/headless ) - self._sp = spotipy.Spotify(auth_manager=auth_manager) + self._sp = spotipy.Spotify(auth_manager=self._auth_manager) # Test the connection self._sp.current_user() @@ -49,6 +57,50 @@ def authenticate(self) -> bool: self._sp = None return False + def get_auth_url(self) -> Optional[str]: + """ + Get the authorization URL for OAuth flow + + Returns: + Authorization URL or None if auth manager not initialized + """ + if not self._auth_manager: + logger.error("Auth manager not initialized") + return None + + return self._auth_manager.get_authorize_url() + + def handle_callback(self, code: str) -> bool: + """ + Handle OAuth callback with authorization code + + Args: + code: Authorization code from Spotify + + Returns: + True if successful, False otherwise + """ + try: + if not self._auth_manager: + logger.error("Auth manager not initialized") + return False + + # Get token using the code + token_info = self._auth_manager.get_access_token(code, as_dict=True) + + if token_info: + # Re-initialize Spotify client with the new token + self._sp = spotipy.Spotify(auth_manager=self._auth_manager) + logger.info("✓ Successfully authenticated with Spotify via callback") + return True + else: + logger.error("Failed to get access token from code") + return False + + except Exception as e: + logger.error(f"Error handling OAuth callback: {e}") + return False + def get_current_track(self) -> Optional[Dict]: """ Get currently playing track diff --git a/homeassistant/spotifytowled/README.md b/homeassistant/spotifytowled/README.md index 125d62d..bf547d2 100644 --- a/homeassistant/spotifytowled/README.md +++ b/homeassistant/spotifytowled/README.md @@ -12,6 +12,10 @@ Sync your Spotify album colors with WLED devices directly from Home Assistant! SpotifyToWLED is a Home Assistant add-on that synchronizes the colors from your currently playing Spotify album covers with your WLED LED devices. Experience an immersive, dynamic lighting experience that matches your music. +This addon supports two modes: +- **Standalone Mode**: Runs the full SpotifyToWLED application within Home Assistant +- **Integration Mode**: Connects to an external Docker server running SpotifyToWLED (recommended for multi-instance setups) + ## Features - 🎨 **Multiple Color Extraction Modes**: Vibrant, Dominant, or Average color selection @@ -21,6 +25,7 @@ SpotifyToWLED is a Home Assistant add-on that synchronizes the colors from your - 🌐 **Web Interface**: Beautiful Bootstrap 5 UI accessible from Home Assistant - ⚡ **Performance Optimized**: API caching and smart track detection - 🔒 **Secure**: No security vulnerabilities (CodeQL verified) +- 🔗 **Integration Mode**: Connect to external Docker server for centralized management ## Installation @@ -32,16 +37,18 @@ SpotifyToWLED is a Home Assistant add-on that synchronizes the colors from your - Find "SpotifyToWLED" in the add-on store - Click **Install** -3. **Configure**: - - Get your Spotify credentials from [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) - - Add your WLED device IP addresses - - Save configuration +3. **Configure** (see configuration sections below based on your chosen mode) 4. **Start**: Click **Start** and check the logs ## Configuration +### Mode 1: Standalone (Run everything in Home Assistant) + +Use this mode if you want to run SpotifyToWLED entirely within Home Assistant. + ```yaml +mode: "standalone" spotify_client_id: "your_spotify_client_id" spotify_client_secret: "your_spotify_client_secret" wled_ips: @@ -52,35 +59,112 @@ cache_duration: 5 color_extraction_method: "vibrant" ``` -### Option: `spotify_client_id` +### Mode 2: Integration (Connect to External Docker Server) -Your Spotify application Client ID. Get it from [Spotify Developer Dashboard](https://developer.spotify.com/dashboard). +Use this mode if you already have SpotifyToWLED running in Docker elsewhere and want to access it through Home Assistant. -### Option: `spotify_client_secret` +```yaml +mode: "integration" +server_url: "http://192.168.1.50:5000" +``` -Your Spotify application Client Secret. +**Benefits of Integration Mode:** +- Lighter resource usage in Home Assistant +- Share one SpotifyToWLED instance across multiple Home Assistant instances +- Easier to manage updates centrally +- Better for advanced Docker/Portainer setups -### Option: `wled_ips` +### Configuration Options -List of WLED device IP addresses on your network. +#### Required Settings (Standalone Mode) -### Option: `refresh_interval` +| Setting | Description | Example | +|---------|-------------|---------| +| `mode` | Operating mode | `standalone` or `integration` | +| `spotify_client_id` | Your Spotify Client ID | `abc123...` | +| `spotify_client_secret` | Your Spotify Client Secret | `xyz789...` | +| `wled_ips` | List of WLED device IPs | `["192.168.1.100"]` | -How often to check for track changes (in seconds). Default: 30 +#### Required Settings (Integration Mode) -### Option: `cache_duration` +| Setting | Description | Example | +|---------|-------------|---------| +| `mode` | Operating mode | `integration` | +| `server_url` | URL of your Docker server | `http://192.168.1.50:5000` | -How long to cache API responses (in seconds). Default: 5 +#### Optional Settings (Standalone Mode Only) -### Option: `color_extraction_method` +| Setting | Default | Description | +|---------|---------|-------------| +| `refresh_interval` | `30` | Check for track changes every N seconds | +| `cache_duration` | `5` | Cache API responses for N seconds | +| `color_extraction_method` | `vibrant` | Color mode: `vibrant`, `dominant`, or `average` | -Color extraction method: `vibrant`, `dominant`, or `average`. Default: `vibrant` +## Getting Spotify Credentials + +1. Go to [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) +2. Log in with your Spotify account +3. Click **Create an App** +4. Fill in: + - **App name**: SpotifyToWLED + - **App description**: Sync album colors with WLED + - Accept terms and click **Create** +5. You'll see your **Client ID** and **Client Secret** +6. Click **Edit Settings** +7. Add Redirect URI: `http://homeassistant.local:5000/callback` (or your HA IP) +8. Click **Save** ## Web Interface Access the web interface through: -- Home Assistant Ingress (click "Open Web UI") -- Direct URL: `http://homeassistant.local:5000` +- **Standalone Mode**: Home Assistant Ingress (click "Open Web UI") +- **Integration Mode**: Proxied through the addon to your external server +- **Direct URL**: `http://homeassistant.local:5000` + +## Using with Home Assistant Automations + +### Example: Start/Stop with Presence + +```yaml +automation: + - alias: "Start SpotifyToWLED when home" + trigger: + - platform: state + entity_id: person.your_name + to: "home" + action: + - service: hassio.addon_start + data: + addon: local_spotifytowled + + - alias: "Stop SpotifyToWLED when away" + trigger: + - platform: state + entity_id: person.your_name + to: "not_home" + action: + - service: hassio.addon_stop + data: + addon: local_spotifytowled +``` + +## Troubleshooting + +### Integration Mode Issues + +1. **Can't connect to external server** + - Verify server URL is correct and accessible from Home Assistant + - Check firewall rules + - Ensure the Docker server is running: `docker ps | grep spotifytowled` + - Test connectivity: `curl http://your-server:5000/health` + +2. **502 Bad Gateway** + - Server is not responding + - Check Docker server logs: `docker logs spotifytowled` + +### Standalone Mode Issues + +See the main troubleshooting section in the [full documentation](https://github.com/raphaelbleier/SpotifyToWled) ## Support @@ -90,6 +174,11 @@ For issues, feature requests, or questions: ## Changelog +### 2.1.0 +- Added Integration mode for connecting to external Docker servers +- Improved proxy support for multi-instance setups +- Enhanced configuration flexibility + ### 2.0.0 - Initial Home Assistant add-on release - Complete overhaul with modern architecture diff --git a/homeassistant/spotifytowled/config.json b/homeassistant/spotifytowled/config.json index 8b7029f..7e76925 100644 --- a/homeassistant/spotifytowled/config.json +++ b/homeassistant/spotifytowled/config.json @@ -1,8 +1,8 @@ { "name": "SpotifyToWLED", - "version": "2.0.0", + "version": "2.1.0", "slug": "spotifytowled", - "description": "Sync Spotify album colors with WLED devices", + "description": "Sync Spotify album colors with WLED devices - Integration for hosted Docker server", "arch": ["armhf", "armv7", "aarch64", "amd64", "i386"], "startup": "application", "boot": "auto", @@ -11,17 +11,20 @@ "5000/tcp": 5000 }, "ports_description": { - "5000/tcp": "Web interface" + "5000/tcp": "Web interface (only used in standalone mode)" }, - "webui": "http://[HOST]:[PORT:5000]", + "webui": "[PROTO:ssl]://[HOST]:[PORT:5000]", "ingress": true, "ingress_port": 5000, + "ingress_entry": "/", "panel_icon": "mdi:spotify", "hassio_api": true, "hassio_role": "default", "homeassistant_api": true, "auth_api": false, "options": { + "mode": "standalone", + "server_url": "", "spotify_client_id": "", "spotify_client_secret": "", "wled_ips": [], @@ -30,9 +33,11 @@ "color_extraction_method": "vibrant" }, "schema": { - "spotify_client_id": "str", - "spotify_client_secret": "password", - "wled_ips": ["str"], + "mode": "list(standalone|integration)", + "server_url": "str?", + "spotify_client_id": "str?", + "spotify_client_secret": "password?", + "wled_ips": ["str?"]?, "refresh_interval": "int(1,600)?", "cache_duration": "int(1,60)?", "color_extraction_method": "list(vibrant|dominant|average)?" diff --git a/homeassistant/spotifytowled/run.sh b/homeassistant/spotifytowled/run.sh index 36a7c8e..0a60995 100755 --- a/homeassistant/spotifytowled/run.sh +++ b/homeassistant/spotifytowled/run.sh @@ -3,48 +3,132 @@ # Start SpotifyToWLED # ============================================================================== -# Get configuration from Home Assistant -SPOTIFY_CLIENT_ID=$(bashio::config 'spotify_client_id') -SPOTIFY_CLIENT_SECRET=$(bashio::config 'spotify_client_secret') -WLED_IPS=$(bashio::config 'wled_ips') -REFRESH_INTERVAL=$(bashio::config 'refresh_interval') -CACHE_DURATION=$(bashio::config 'cache_duration') -COLOR_METHOD=$(bashio::config 'color_extraction_method') +# Get mode from configuration +MODE=$(bashio::config 'mode') +bashio::log.info "Running in ${MODE} mode" -# Create config directory -mkdir -p /config +if [ "$MODE" = "integration" ]; then + # Integration mode - create proxy to external Docker server + bashio::log.info "Starting in Integration mode - connecting to external server" + + SERVER_URL=$(bashio::config 'server_url') + + if [ -z "$SERVER_URL" ]; then + bashio::log.error "Server URL is required in integration mode!" + exit 1 + fi + + bashio::log.info "External server: ${SERVER_URL}" + + # Create a simple proxy using Python Flask + cat > /app/proxy.py << 'EOFPROXY' +import os +import requests +from flask import Flask, request, jsonify, redirect, Response +import logging -# Create config.json from Home Assistant settings using jq for safe JSON generation -jq -n \ - --arg client_id "$SPOTIFY_CLIENT_ID" \ - --arg client_secret "$SPOTIFY_CLIENT_SECRET" \ - --arg redirect_uri "http://homeassistant.local:5000/callback" \ - --arg scope "user-read-currently-playing" \ - --argjson wled_ips "$WLED_IPS" \ - --argjson refresh_interval "$REFRESH_INTERVAL" \ - --argjson cache_duration "$CACHE_DURATION" \ - '{ - SPOTIFY_CLIENT_ID: $client_id, - SPOTIFY_CLIENT_SECRET: $client_secret, - SPOTIFY_REDIRECT_URI: $redirect_uri, - SPOTIFY_SCOPE: $scope, - WLED_IPS: $wled_ips, - REFRESH_INTERVAL: $refresh_interval, - CACHE_DURATION: $cache_duration, - MAX_RETRIES: 3, - RETRY_DELAY: 2 - }' > /config/config.json +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) -bashio::log.info "Starting SpotifyToWLED..." -bashio::log.info "Spotify Client ID: ${SPOTIFY_CLIENT_ID:0:10}..." -bashio::log.info "WLED IPs: ${WLED_IPS}" -bashio::log.info "Refresh Interval: ${REFRESH_INTERVAL}s" -bashio::log.info "Color Method: ${COLOR_METHOD}" +app = Flask(__name__) +SERVER_URL = os.environ.get('SERVER_URL', '').rstrip('/') -# Set environment variables -export CONFIG_PATH=/config/config.json -export LOG_PATH=/data/spotifytowled.log +@app.route('/', defaults={'path': ''}) +@app.route('/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) +def proxy(path): + """Proxy all requests to the external server""" + url = f"{SERVER_URL}/{path}" + + # Forward query parameters + if request.query_string: + url = f"{url}?{request.query_string.decode('utf-8')}" + + logger.info(f"Proxying {request.method} {url}") + + try: + # Prepare headers (exclude host) + headers = {key: value for key, value in request.headers if key.lower() != 'host'} + + # Make request to external server + resp = requests.request( + method=request.method, + url=url, + headers=headers, + data=request.get_data(), + cookies=request.cookies, + allow_redirects=False, + timeout=30 + ) + + # Return response + excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] + response_headers = [(name, value) for name, value in resp.raw.headers.items() + if name.lower() not in excluded_headers] + + return Response(resp.content, resp.status_code, response_headers) + + except requests.exceptions.RequestException as e: + logger.error(f"Proxy error: {e}") + return jsonify({'error': 'Unable to connect to external server', 'details': str(e)}), 502 -# Start the application -cd /app -exec python run.py +if __name__ == '__main__': + logger.info(f"Starting proxy to {SERVER_URL}") + app.run(host='0.0.0.0', port=5000, debug=False) +EOFPROXY + + # Install requests if not available + pip3 install --no-cache-dir requests 2>/dev/null || true + + # Set environment and start proxy + export SERVER_URL="${SERVER_URL}" + exec python3 /app/proxy.py + +else + # Standalone mode - run full application + bashio::log.info "Starting in Standalone mode - running full application" + + # Get configuration from Home Assistant + SPOTIFY_CLIENT_ID=$(bashio::config 'spotify_client_id') + SPOTIFY_CLIENT_SECRET=$(bashio::config 'spotify_client_secret') + WLED_IPS=$(bashio::config 'wled_ips') + REFRESH_INTERVAL=$(bashio::config 'refresh_interval') + CACHE_DURATION=$(bashio::config 'cache_duration') + COLOR_METHOD=$(bashio::config 'color_extraction_method') + + # Create config directory + mkdir -p /config + + # Create config.json from Home Assistant settings using jq for safe JSON generation + jq -n \ + --arg client_id "$SPOTIFY_CLIENT_ID" \ + --arg client_secret "$SPOTIFY_CLIENT_SECRET" \ + --arg redirect_uri "http://homeassistant.local:5000/callback" \ + --arg scope "user-read-currently-playing" \ + --argjson wled_ips "$WLED_IPS" \ + --argjson refresh_interval "$REFRESH_INTERVAL" \ + --argjson cache_duration "$CACHE_DURATION" \ + '{ + SPOTIFY_CLIENT_ID: $client_id, + SPOTIFY_CLIENT_SECRET: $client_secret, + SPOTIFY_REDIRECT_URI: $redirect_uri, + SPOTIFY_SCOPE: $scope, + WLED_IPS: $wled_ips, + REFRESH_INTERVAL: $refresh_interval, + CACHE_DURATION: $cache_duration, + MAX_RETRIES: 3, + RETRY_DELAY: 2 + }' > /config/config.json + + bashio::log.info "Spotify Client ID: ${SPOTIFY_CLIENT_ID:0:10}..." + bashio::log.info "WLED IPs: ${WLED_IPS}" + bashio::log.info "Refresh Interval: ${REFRESH_INTERVAL}s" + bashio::log.info "Color Method: ${COLOR_METHOD}" + + # Set environment variables + export CONFIG_PATH=/config/config.json + export LOG_PATH=/data/spotifytowled.log + + # Start the application + cd /app + exec python run.py +fi