From 9e28c85ae6237f871d408edf9ce6132199b2b530 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 13 Nov 2025 12:49:34 +0000
Subject: [PATCH 1/5] Initial plan
From a6289cd6dd30a696b24ca3c4cccbd68d7e26b132 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 13 Nov 2025 12:53:59 +0000
Subject: [PATCH 2/5] Fix Spotify authentication for Docker environment
Co-authored-by: raphaelbleier <75416341+raphaelbleier@users.noreply.github.com>
---
app/core/sync_engine.py | 44 ++++++++++++++++++++++++++
app/routes/web.py | 43 ++++++++++++++++++++++++++
app/static/js/app.js | 20 ++++++++++++
app/templates/index.html | 10 ++++++
app/utils/spotify_manager.py | 60 +++++++++++++++++++++++++++++++++---
5 files changed, 173 insertions(+), 4 deletions(-)
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
From 4bc4abb9e1c095d5c847ff005532b88ac0552617 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 13 Nov 2025 12:57:09 +0000
Subject: [PATCH 3/5] Add Home Assistant integration mode for Docker server
Co-authored-by: raphaelbleier <75416341+raphaelbleier@users.noreply.github.com>
---
HOMEASSISTANT.md | 88 +++++++++++--
homeassistant/spotifytowled/README.md | 125 +++++++++++++++---
homeassistant/spotifytowled/config.json | 19 ++-
homeassistant/spotifytowled/run.sh | 164 ++++++++++++++++++------
4 files changed, 323 insertions(+), 73 deletions(-)
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/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
From 82242d0af3a358612b239b2ccc4730fe4556afc1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 13 Nov 2025 12:59:44 +0000
Subject: [PATCH 4/5] Update documentation for Spotify auth and HA integration
Co-authored-by: raphaelbleier <75416341+raphaelbleier@users.noreply.github.com>
---
DOCKER.md | 27 +++++++++++++++++++++++++++
README.md | 13 +++++++++++++
2 files changed, 40 insertions(+)
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/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
From a8c0d5124051f86581e1c4d47522a073cb3bffd1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 13 Nov 2025 13:01:57 +0000
Subject: [PATCH 5/5] Add comprehensive changelog for v2.1.0
Co-authored-by: raphaelbleier <75416341+raphaelbleier@users.noreply.github.com>
---
CHANGELOG-v2.1.md | 232 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 232 insertions(+)
create mode 100644 CHANGELOG-v2.1.md
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!