Make OAuth callback ports configurable via environment variables

Hardcoded ports 8888/8889 conflict when SoulSync runs behind Gluetun or
other containers that claim those ports. Introduce SOULSYNC_SPOTIFY_CALLBACK_PORT
and SOULSYNC_TIDAL_CALLBACK_PORT env vars (defaulting to 8888/8889) so
users can remap without rebuilding the image.

docker-compose.yml exposes the vars with comments explaining how to keep
the port mappings in sync with the redirect URI in Settings → Connections.
This commit is contained in:
Broque Thomas 2026-04-14 14:08:31 -07:00
parent 6e405143a7
commit 1071b2ebe5
3 changed files with 39 additions and 30 deletions

View file

@ -1,3 +1,4 @@
import os
import requests
import time
import re
@ -119,7 +120,8 @@ class TidalClient:
self.alt_base_url = "https://api.tidal.com/v1" # Alternative API base
self.auth_url = "https://login.tidal.com/authorize"
self.token_url = "https://auth.tidal.com/v1/oauth2/token"
self.redirect_uri = "http://127.0.0.1:8889/tidal/callback" # Default, will be updated from config
_tidal_port = int(os.environ.get('SOULSYNC_TIDAL_CALLBACK_PORT', 8889))
self.redirect_uri = f"http://127.0.0.1:{_tidal_port}/tidal/callback" # Default, will be updated from config
self.session = requests.Session()
self.auth_server = None
self.auth_code = None
@ -347,7 +349,7 @@ class TidalClient:
pass # Suppress server logs
try:
port = 8889
port = int(os.environ.get('SOULSYNC_TIDAL_CALLBACK_PORT', 8889))
self.auth_server = HTTPServer(('localhost', port), CallbackHandler)
server_thread = threading.Thread(target=self.auth_server.serve_forever)
server_thread.daemon = True

View file

@ -20,10 +20,15 @@ services:
- SOULSYNC_CONFIG_PATH=/app/config/config.json
# Set timezone (change to your timezone)
- TZ=America/New_York
# OAuth callback ports. If 8888/8889 conflict with another container (e.g. Gluetun),
# change the port numbers below AND update the matching port mappings in the ports section.
# Then update the redirect URI in SoulSync → Settings → Connections to match.
- SOULSYNC_SPOTIFY_CALLBACK_PORT=8888
- SOULSYNC_TIDAL_CALLBACK_PORT=8889
ports:
- "8008:8008" # Main web app
- "8888:8888" # Spotify OAuth callback
- "8889:8889" # Tidal OAuth callback
- "8008:8008" # Main web app
- "8888:8888" # Spotify OAuth callback — keep in sync with SOULSYNC_SPOTIFY_CALLBACK_PORT above
- "8889:8889" # Tidal OAuth callback — keep in sync with SOULSYNC_TIDAL_CALLBACK_PORT above
volumes:
# Persistent data volumes
- ./config:/app/config

View file

@ -2887,25 +2887,25 @@ class WebUIDownloadMonitor:
sources_str = f' (tried {len(tried_sources)} source{"s" if len(tried_sources) != 1 else ""})' if tried_sources else ''
print(f"Task failed after 3 error retry attempts")
task['status'] = 'failed'
# Tidal-specific error: check if this was a quality issue.
# task['username'] is popped on error-retry (line ~2866) so we can't rely on it;
# used_sources keys are formatted as "{username}_{filename}", so startswith is exact.
is_tidal = any(s.startswith('tidal_') for s in tried_sources)
if is_tidal:
tidal_quality = config_manager.get('tidal_download.quality', 'lossless')
allow_fb = config_manager.get('tidal_download.allow_fallback', True)
if tidal_quality == 'hires' and not allow_fb:
task['error_message'] = (
f'Tidal download failed for "{track_label}" — HiRes quality is unavailable for this track '
f'on your account or in your region. Enable "Quality Fallback" in Tidal settings to fall back to Lossless.'
)
else:
task['error_message'] = (
f'Tidal download failed for "{track_label}"{sources_str}'
f'check Tidal authentication and quality settings.'
)
else:
task['error_message'] = f'Soulseek transfer errored 3 times for "{track_label}"{sources_str} — all sources failed or became unavailable'
# Tidal-specific error: check if this was a quality issue.
# task['username'] is popped on error-retry (line ~2866) so we can't rely on it;
# used_sources keys are formatted as "{username}_{filename}", so startswith is exact.
is_tidal = any(s.startswith('tidal_') for s in tried_sources)
if is_tidal:
tidal_quality = config_manager.get('tidal_download.quality', 'lossless')
allow_fb = config_manager.get('tidal_download.allow_fallback', True)
if tidal_quality == 'hires' and not allow_fb:
task['error_message'] = (
f'Tidal download failed for "{track_label}" — HiRes quality is unavailable for this track '
f'on your account or in your region. Enable "Quality Fallback" in Tidal settings to fall back to Lossless.'
)
else:
task['error_message'] = (
f'Tidal download failed for "{track_label}"{sources_str}'
f'check Tidal authentication and quality settings.'
)
else:
task['error_message'] = f'Soulseek transfer errored 3 times for "{track_label}"{sources_str} — all sources failed or became unavailable'
# CRITICAL: Notify batch manager so track is added to permanently_failed_tracks
batch_id = task.get('batch_id')
@ -49711,15 +49711,16 @@ def start_oauth_callback_servers():
# Start Spotify callback server
def run_spotify_server():
spotify_port = int(os.environ.get('SOULSYNC_SPOTIFY_CALLBACK_PORT', 8888))
try:
bind_addr = ('0.0.0.0', 8888)
bind_addr = ('0.0.0.0', spotify_port)
spotify_server = HTTPServer(bind_addr, SpotifyCallbackHandler)
_oauth_logger.info(f"Spotify OAuth callback server listening on {bind_addr[0]}:{bind_addr[1]}")
print(f"Started Spotify OAuth callback server on {bind_addr[0]}:{bind_addr[1]}")
spotify_server.serve_forever()
except OSError as e:
_oauth_logger.error(f"Failed to start Spotify callback server on port 8888: {e} — port may already be in use")
print(f"Failed to start Spotify callback server on port 8888: {e}")
_oauth_logger.error(f"Failed to start Spotify callback server on port {spotify_port}: {e} — port may already be in use")
print(f"Failed to start Spotify callback server on port {spotify_port}: {e}")
except Exception as e:
_oauth_logger.error(f"Failed to start Spotify callback server: {e}")
print(f"Failed to start Spotify callback server: {e}")
@ -49789,9 +49790,10 @@ def start_oauth_callback_servers():
def run_tidal_server():
try:
tidal_server = HTTPServer(('0.0.0.0', 8889), TidalCallbackHandler)
print("Started Tidal OAuth callback server on port 8889")
print(f"Tidal server listening on all interfaces, port 8889")
tidal_port = int(os.environ.get('SOULSYNC_TIDAL_CALLBACK_PORT', 8889))
tidal_server = HTTPServer(('0.0.0.0', tidal_port), TidalCallbackHandler)
print(f"Started Tidal OAuth callback server on port {tidal_port}")
print(f"Tidal server listening on all interfaces, port {tidal_port}")
tidal_server.serve_forever()
except Exception as e:
print(f"Failed to start Tidal callback server: {e}")