├── data └── logs │ └── soularr.log ├── images └── Soularr-dashboard_final.png ├── dashboard ├── __pycache__ │ └── dashboard.cpython-312.pyc ├── requirements.txt ├── Dockerfile ├── templates │ ├── index.html.example │ └── index.html └── dashboard.py ├── requirements.txt ├── .gitignore ├── Dockerfile ├── .github └── workflows │ └── publish.yml ├── DEPENDENCIES.md ├── CHANGELOG.md ├── LICENSE.txt ├── process_failures.py ├── CONTRIBUTING.md ├── docker-compose.yml ├── docker-compose.yml.example ├── CONFIG.md ├── templates ├── index.html.example └── index.html ├── config.ini.example ├── TROUBLESHOOTING.md ├── dashboard.py ├── INSTALL.md └── README.md /data/logs/soularr.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/Soularr-dashboard_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricH9958/Soularr-Dashboard/HEAD/images/Soularr-dashboard_final.png -------------------------------------------------------------------------------- /dashboard/__pycache__/dashboard.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricH9958/Soularr-Dashboard/HEAD/dashboard/__pycache__/dashboard.cpython-312.pyc -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==3.0.0 2 | flask-socketio==5.3.6 3 | python-socketio==5.0.4 4 | python-engineio==4.0.0 5 | gunicorn==21.2.0 6 | eventlet==0.33.3 7 | simple-websocket 8 | -------------------------------------------------------------------------------- /dashboard/requirements.txt: -------------------------------------------------------------------------------- 1 | flask==3.0.0 2 | flask-socketio==5.3.6 3 | python-socketio==5.0.4 4 | python-engineio==4.0.0 5 | gunicorn==21.2.0 6 | eventlet==0.33.3 7 | simple-websocket 8 | -------------------------------------------------------------------------------- /dashboard/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | WORKDIR /app 3 | COPY requirements.txt . 4 | RUN pip install --no-cache-dir -r requirements.txt 5 | COPY . . 6 | CMD ["python", "dashboard.py"] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Sensitive files 2 | config.ini 3 | *.env 4 | **/config.ini 5 | 6 | # Runtime and temp files 7 | soularr.log 8 | .current_page.txt 9 | failure_list.txt 10 | docker-compose.yml 11 | dashboard/templates/index.html 12 | 13 | # Cache and system files 14 | __pycache__/ 15 | *.pyc 16 | *.bak 17 | .DS_Store 18 | 19 | # IDE specific 20 | .vscode/ 21 | .idea/ 22 | __pycache__/ 23 | *.pyc 24 | soularr.log 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt . 6 | RUN pip install --no-cache-dir -r requirements.txt 7 | 8 | COPY . . 9 | 10 | EXPOSE 8080 11 | 12 | CMD ["python", "dashboard.py"] 13 | 14 | LABEL org.opencontainers.image.source=https://github.com/EricH9958/Soularr-Dashboard 15 | LABEL org.opencontainers.image.description="Soularr Dashboard - A web interface for Soularr logs and failure lists" 16 | LABEL org.opencontainers.image.licenses=MIT 17 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | push_to_registry: 9 | name: Push Docker image to GitHub Container Registry 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out the repo 13 | uses: actions/checkout@v2 14 | 15 | - name: Log in to GitHub Container Registry 16 | uses: docker/login-action@v1 17 | with: 18 | registry: ghcr.io 19 | username: ${{ github.actor }} 20 | password: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | 23 | - name: Build and push Docker image 24 | uses: docker/build-push-action@v2 25 | with: 26 | context: . 27 | push: true 28 | tags: ghcr.io/erich9958/soularr-dashboard:latest 29 | -------------------------------------------------------------------------------- /DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | # Dependencies for Soularr Dashboard 2 | 3 | ## Python Requirements 4 | - Flask v3.0.0 5 | - Gunicorn v21.2.0 6 | 7 | ## Container Requirements 8 | - Python 3.12-slim base image 9 | - Docker version 20.10 or higher 10 | - Docker Compose v2.0 or higher 11 | 12 | ## System Requirements 13 | - Linux-based OS (tested on Ubuntu) 14 | - Minimum 512MB RAM 15 | - 1GB free disk space 16 | - Network access to port 8080 17 | 18 | ## Browser Requirements 19 | - Any modern web browser with JavaScript enabled 20 | - Minimum resolution: 1024x768 21 | - Supported browsers: 22 | - Chrome/Chromium (latest) 23 | - Firefox (latest) 24 | - Safari (latest) 25 | - Edge (latest) 26 | 27 | ## Optional Dependencies 28 | - curl (for troubleshooting) 29 | - netstat (for port checking) 30 | - systemctl (for service management) 31 | 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for Soularr Dashboard 2 | 3 | ## [1.0.0] - 2024-12-08 4 | Initial Release 5 | 6 | ### Added 7 | - Real-time log monitoring 8 | - Dual window display interface 9 | - Automatic log refresh 10 | - Reverse chronological log display 11 | - Equal window sizing 12 | - Docker container support 13 | - Web-based interface 14 | - Failure list integration 15 | 16 | ### Technical Features 17 | - Flask web server 18 | - Gunicorn production server 19 | - Docker container support 20 | - Volume mapping for logs 21 | - Configurable refresh rate 22 | - Error handling 23 | - Permission management 24 | 25 | ### Documentation 26 | - Installation guide 27 | - Configuration documentation 28 | - Troubleshooting guide 29 | - Dependency list 30 | - Contributing guidelines 31 | 32 | ## [Future Versions] 33 | Planned features and improvements will be listed here as they are developed. 34 | 35 | Note: Dates are in YYYY-MM-DD format 36 | 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 EricH9958 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /process_failures.py: -------------------------------------------------------------------------------- 1 | import time 2 | import re 3 | from datetime import datetime 4 | 5 | def parse_logs_continuously(input_file, output_file): 6 | with open(input_file, 'r') as infile: 7 | infile.seek(0, 2) # Move to end of file 8 | while True: 9 | line = infile.readline() 10 | if not line: 11 | time.sleep(1) 12 | continue 13 | 14 | # Check both failure patterns 15 | import_match = re.search(r'Failed to import from: .+/complete/(.+)', line) 16 | move_match = re.search(r'Failed import moved to: failed_imports/(.+)', line) 17 | 18 | if import_match or move_match: 19 | # Only log the first occurrence of a failure 20 | if import_match: 21 | album_name = import_match.group(1) 22 | current_time = datetime.now().strftime('%d/%m/%Y %H:%M:%S') 23 | failure_entry = f"{current_time} - {album_name}, Failed Import\n" 24 | with open(output_file, 'a') as outfile: 25 | outfile.write(failure_entry) 26 | 27 | if __name__ == '__main__': 28 | parse_logs_continuously('/data/soularr.log', '/data/failure_list.txt') 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Soularr Dashboard 2 | 3 | ## How to Contribute 4 | 5 | ### Reporting Issues 6 | 1. Check existing issues to avoid duplicates 7 | 2. Use the issue template when available 8 | 3. Include: 9 | - Your OS version 10 | - Docker version 11 | - Steps to reproduce 12 | - Expected vs actual behavior 13 | - Error messages/logs 14 | 15 | ### Pull Requests 16 | 1. Fork the repository 17 | 2. Create a new branch for your feature 18 | 3. Follow the existing code style 19 | 4. Test your changes 20 | 5. Submit a pull request with a clear description of changes 21 | 22 | ## Development Guidelines 23 | 24 | ### Code Style 25 | - Use consistent indentation (4 spaces) 26 | - Follow PEP 8 for Python code 27 | - Comment your code when necessary 28 | - Keep functions small and focused 29 | - Use meaningful variable names 30 | 31 | ### Testing 32 | - Test all changes locally before submitting 33 | - Verify logs display correctly 34 | - Check both windows function properly 35 | - Ensure auto-refresh works 36 | - Test on different browsers if making UI changes 37 | 38 | ### Documentation 39 | - Update README.md if adding features 40 | - Document any new configuration options 41 | - Update CHANGELOG.md with your changes 42 | 43 | ## Questions or Problems? 44 | - Open an issue for general questions 45 | - Join discussions in existing issues 46 | 47 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | dashboard: 3 | build: . 4 | container_name: soularr-dashboard 5 | network_mode: "host" 6 | volumes: 7 | - /opt/Soularr_docker/logs:/logs 8 | - /opt/Soularr_docker/config:/data 9 | environment: 10 | - TZ=America/Los_Angeles 11 | - LOG_PATH=/logs/soularr.log 12 | - FAILURE_PATH=/data/failure_list.txt 13 | restart: unless-stopped 14 | ======= 15 | soularr: 16 | restart: unless-stopped 17 | container_name: soularr 18 | network_mode: host 19 | environment: 20 | - PUID=1000 21 | - PGID=1000 22 | - TZ=America/Los_Angeles 23 | - SCRIPT_INTERVAL=30 24 | user: "root:root" 25 | volumes: 26 | - /mnt/Plex11/slskd/complete:/mnt/Plex11/slskd/complete 27 | - /mnt/Plex11/slskd/complete:/downloads 28 | - /home/eric/Soularr:/data 29 | - /home/eric/Soularr/data/logs:/data/logs 30 | image: mrusse08/soularr:latest 31 | command: sh -c "mkdir -p /data/logs && python soularr.py 2>&1 | tee -a /data/logs/soularr.log" 32 | logging: 33 | driver: "json-file" 34 | options: 35 | max-size: "10m" 36 | max-file: "3" 37 | 38 | dashboard: 39 | build: 40 | context: ./dashboard 41 | dockerfile: Dockerfile 42 | restart: unless-stopped 43 | container_name: soularr-dashboard 44 | network_mode: host 45 | environment: 46 | - PUID=1000 47 | - PGID=1000 48 | - TZ=America/Los_Angeles 49 | volumes: 50 | - /home/eric/Soularr/dashboard:/app 51 | - /home/eric/Soularr:/data 52 | - /home/eric/Soularr/data/logs:/data/logs 53 | - /var/run/docker.sock:/var/run/docker.sock 54 | working_dir: /app 55 | 56 | -------------------------------------------------------------------------------- /docker-compose.yml.example: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | soularr: 5 | restart: unless-stopped 6 | container_name: soularr 7 | network_mode: host 8 | environment: 9 | - PUID=1000 10 | - PGID=1000 11 | - TZ=America/Los_Angeles 12 | # Script interval in seconds 13 | - SCRIPT_INTERVAL=30 14 | user: "1000:1000" 15 | volumes: 16 | # Path to the completed downloads directory (adjust based on your setup) 17 | - /path/to/slskd/complete:/downloads 18 | # Path to the Soularr configuration directory 19 | - /opt/Soularr_docker/config:/data 20 | # Path to the Soularr logs directory 21 | - /opt/Soularr_docker/logs:/data/logs 22 | image: mrusse08/soularr:latest 23 | command: sh -c "mkdir -p /data/logs && python soularr.py 2>&1 | tee -a /data/logs/soularr.log" 24 | logging: 25 | driver: "json-file" 26 | options: 27 | max-size: "10m" 28 | max-file: "3" 29 | 30 | dashboard: 31 | build: 32 | context: ./dashboard 33 | dockerfile: Dockerfile 34 | restart: unless-stopped 35 | container_name: soularr-dashboard 36 | network_mode: host 37 | environment: 38 | - PUID=1000 39 | - PGID=1000 40 | - TZ=America/Los_Angeles 41 | # Path to the Soularr log file and failure list file for monitoring 42 | - LOG_PATH=/data/logs/soularr.log 43 | - FAILURE_PATH=/data/failure_list.txt 44 | volumes: 45 | # Path to the dashboard application directory (adjust based on your setup) 46 | - /opt/Soularr_docker/dashboard:/app 47 | # Path to the Soularr configuration directory for shared access with Soularr service 48 | - /opt/Soularr_docker/config:/data 49 | # Path to the Soularr logs directory for monitoring logs in real-time 50 | - /opt/Soularr_docker/logs:/data/logs 51 | # Docker socket for direct Docker access (use with caution) 52 | - /var/run/docker.sock:/var/run/docker.sock 53 | working_dir: /app 54 | 55 | 56 | -------------------------------------------------------------------------------- /CONFIG.md: -------------------------------------------------------------------------------- 1 | onfiguration Guide for Soularr Dashboard 2 | 3 | ## Directory Structure 4 | /home//Soularr/ 5 | ├── data/ 6 | │ └── logs/ 7 | │ └── soularr.log 8 | ├── dashboard/ 9 | │ ├── templates/ 10 | │ ├── dashboard.py 11 | │ ├── Dockerfile 12 | │ └── requirements.txt 13 | ├── docker-compose.yml 14 | └── config.ini 15 | 16 | ## Logging Configuration 17 | 18 | ### config.ini Settings 19 | [Logging] 20 | level = INFO 21 | format = [%(levelname)s|%(module)s|L%(lineno)d] %(asctime)s: %(message)s 22 | datefmt = %Y-%m-%dT%H:%M:%S%z 23 | filename = /data/logs/soularr.log 24 | 25 | ### Log Levels Available 26 | - DEBUG: Detailed information for debugging 27 | - INFO: General operational information 28 | - WARNING: Warning messages for potential issues 29 | - ERROR: Error messages for serious problems 30 | - CRITICAL: Critical errors that may prevent operation 31 | 32 | ### Docker Log Rotation 33 | Configured in docker-compose.yml: 34 | logging: 35 | driver: "json-file" 36 | options: 37 | max-size: "10m" 38 | max-file: "3" 39 | 40 | ## Docker Configuration 41 | 42 | ### Dashboard Service Settings 43 | - Port: 8080 (default) 44 | - Network Mode: host 45 | - User Permissions: PUID=1000, PGID=1000 46 | - Timezone: Configurable via TZ environment variable 47 | - Volume Mappings: 48 | - /home//Soularr/dashboard:/app 49 | - /home//Soularr:/data 50 | - /home//Soularr/data/logs:/data/logs 51 | 52 | ### Environment Variables 53 | - PUID: User ID (default: 1000) 54 | - PGID: Group ID (default: 1000) 55 | - TZ: Timezone (example: America/Los_Angeles) 56 | 57 | ## Dashboard Settings 58 | 59 | ### Web Interface 60 | - Default URL: http://your-server-ip:8080 61 | - Auto-refresh interval: 1 second 62 | - Log display: 50 most recent entries 63 | - Window split: 50/50 for logs and failures 64 | 65 | ### Failure Tracking 66 | - Location: /data/failure_list.txt 67 | - Format: [Date Time] - [Artist/Album], Failed Import 68 | - Auto-updates when failures occur 69 | 70 | ## Security Considerations 71 | - Default network mode is 'host' 72 | - No authentication required (internal network use recommended) 73 | - Log files are readable by user:group 1000:1000 74 | - Docker socket mounted read-only 75 | 76 | ## Maintenance 77 | - Logs automatically rotate at 10MB 78 | - Keeps last 3 log files 79 | - Failed imports list grows indefinitely (manual cleanup may be needed) 80 | - Docker images should be updated regularly 81 | 82 | ## Troubleshooting 83 | - Check log file permissions 84 | - Verify directory structure exists 85 | - Ensure Docker has proper access rights 86 | - Monitor disk space for log storage 87 | 88 | -------------------------------------------------------------------------------- /templates/index.html.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Soularr Dashboard 6 | 50 | 51 | 52 |

Soularr Dashboard

53 |
54 |
55 |

Docker Logs

56 |
57 |
58 |
59 |

Failure List

60 |
61 |
62 |
63 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /dashboard/templates/index.html.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Soularr Dashboard 6 | 50 | 51 | 52 |

Soularr Dashboard

53 |
54 |
55 |

Docker Logs

56 |
57 |
58 |
59 |

Failure List

60 |
61 |
62 |
63 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Soularr Dashboard 7 | 51 | 52 | 53 |

Soularr Dashboard

54 |
55 |
56 |

Docker Logs

57 |
58 |
59 |
60 |

Failure List

61 |
62 |
63 |
64 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /dashboard/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Soularr Dashboard 7 | 51 | 52 | 53 |

Soularr Dashboard

54 |
55 |
56 |

Docker Logs

57 |
58 |
59 |
60 |

Failure List

61 |
62 |
63 |
64 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /config.ini.example: -------------------------------------------------------------------------------- 1 | [Lidarr] 2 | api_key = YOUR_API_KEY_HERE 3 | host_url = http://localhost:8686 4 | #This should be the path mounted in lidarr that points to your slskd download directory. 5 | #If Lidarr is not running in Docker then this may just be the same dir as Slskd is using below. 6 | download_dir = /mnt/Plex11/slskd/complete 7 | 8 | [Slskd] 9 | #Api key from Slskd. Need to set this up manually. See link to Slskd docs above. 10 | api_key = YOUR_API_KEY_HERE 11 | host_url = http://localhost:5030 12 | #Slskd download directory. Should have set it up when installing Slskd. 13 | download_dir = /mnt/Plex11/slskd/complete 14 | #Removes searches from Slskd after the search finishes. 15 | delete_searches = True 16 | #Maximum time (in seconds) that the script will wait for downloads to complete. 17 | #This is used to prevent the script from running forever due to a stalled download. Defaults to 1 hour. 18 | stalled_timeout = 3600 19 | 20 | [Release Settings] 21 | #Selects the release with the most common amount of tracks out of all the releases. 22 | use_most_common_tracknum = True 23 | allow_multi_disc = True 24 | #See full list of countries below. 25 | accepted_countries = Europe,Japan,United Kingdom,United States,[Worldwide],Australia,Canada 26 | #See full list of formats below. 27 | accepted_formats = CD,Digital Media,Vinyl 28 | 29 | [Search Settings] 30 | search_timeout = 5000 31 | maximum_peer_queue = 50 32 | #Min upload speed in bit/s 33 | minimum_peer_upload_speed = 0 34 | #Replace "flac,mp3" with "flac" if you just want flacs. 35 | allowed_filetypes = flac,mp3 36 | ignored_users = User1,User2,Fred,Bob 37 | #Set to False if you only want to search for complete albums 38 | search_for_tracks = True 39 | #Set to True if you want to add the artist's name to the beginning of the search for albums 40 | album_prepend_artist = True 41 | track_prepend_artist = True 42 | #Valid search types: all || incrementing_page || first_page 43 | #"all" will search for every wanted record everytime soularr is run. 44 | #"incrementing_page" will start with the first page and increment to the next on each run. 45 | #"first_page" will repeatedly search the first page. 46 | #If using the search type "first_page" remove_wanted_on_failure should be enabled. 47 | search_type = incrementing_page 48 | #How mancy records to grab each run, must be a number between 1 - 2,147,483,647 49 | number_of_albums_to_grab = 5 50 | #Unmonitors the album if Soularr can't find it and places it in "failure_list.txt". 51 | #Failed albums can be re monitored by filtering "Unmonitored" in the Lidarr wanted list. 52 | remove_wanted_on_failure = True 53 | #Comma separated list of words that can't be in the title of albums or tracks. Case insensitive. 54 | title_blacklist = BlacklistWord1,blacklistword2 55 | 56 | [Logging] 57 | #These options are passed into the logger's basicConfig() method as-is. 58 | #This means, if you're familiar with Python's logging module, you can configure 59 | #the logger with options beyond what's listed here by default. 60 | #For more information on available options -- https://docs.python.org/3/library/logging.html#logging.basicConfig 61 | level = INFO 62 | # Format of log message -- https://docs.python.org/3/library/logging.html#logrecord-attributes 63 | format = [%(levelname)s|%(module)s|L%(lineno)d] %(asctime)s: %(message)s 64 | # Format of datetimes -- https://docs.python.org/3/library/time.html#time.strftime 65 | datefmt = %Y-%m-%dT%H:%M:%S%z 66 | -------------------------------------------------------------------------------- /TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting Guide for Soularr Dashboard 2 | 3 | ## Common Issues and Solutions 4 | 5 | ### Logs Not Appearing in Dashboard 6 | 7 | 1. Check Log File Location 8 | - Verify /home//Soularr/data/logs exists 9 | - Ensure soularr.log is present 10 | - Check permissions: 11 | ls -l /home//Soularr/data/logs/soularr.log 12 | - Should show: -rw-r--r-- 1 1000 1000 13 | 14 | 2. Check Docker Volume Mounts 15 | - Verify in docker-compose.yml: 16 | - /home//Soularr:/data 17 | - /home//Soularr/data/logs:/data/logs 18 | - Run: docker compose config 19 | - Check container can access logs: 20 | docker exec soularr-dashboard ls -l /data/logs/soularr.log 21 | 22 | 3. Check Log Configuration 23 | - Verify config.ini has correct path: 24 | filename = /data/logs/soularr.log 25 | - Ensure log level is set correctly: 26 | level = INFO 27 | 28 | ### Dashboard Not Loading 29 | 30 | 1. Check Container Status 31 | docker compose ps 32 | docker logs soularr-dashboard 33 | 34 | 2. Check Port Access 35 | - Verify port 8080 is available: 36 | netstat -tuln | grep 8080 37 | - Test local access: 38 | curl http://localhost:8080 39 | 40 | 3. Check Network Mode 41 | - Verify 'network_mode: host' in docker-compose.yml 42 | - Restart container if needed: 43 | docker compose restart dashboard 44 | 45 | ### Log Rotation Issues 46 | 47 | 1. Check Docker Logging Config 48 | - Verify logging settings in docker-compose.yml: 49 | logging: 50 | driver: "json-file" 51 | options: 52 | max-size: "10m" 53 | max-file: "3" 54 | 55 | 2. Monitor Log Size 56 | ls -lh /home//Soularr/data/logs/ 57 | du -h /home//Soularr/data/logs/soularr.log 58 | 59 | 3. Manual Log Cleanup (if needed) 60 | cd /home//Soularr/data/logs/ 61 | mv soularr.log soularr.log.old 62 | touch soularr.log 63 | chmod 644 soularr.log 64 | docker compose restart 65 | 66 | ### Permission Issues 67 | 68 | 1. Check File Ownership 69 | ls -l /home//Soularr/data/logs/ 70 | sudo chown -R 1000:1000 /home//Soularr/data/logs/ 71 | 72 | 2. Check Directory Permissions 73 | chmod 755 /home//Soularr/data/logs 74 | chmod 644 /home//Soularr/data/logs/soularr.log 75 | 76 | 3. Verify Docker User Settings 77 | - Check PUID and PGID in docker-compose.yml 78 | - Should match your user: 79 | id 80 | 81 | ### Container Communication Issues 82 | 83 | 1. Check Network Settings 84 | docker network ls 85 | docker compose ps 86 | 87 | 2. Verify Host Network Mode 88 | - Both containers should use: 89 | network_mode: host 90 | 91 | 3. Check Container Logs 92 | docker logs soularr 93 | docker logs soularr-dashboard 94 | 95 | ### Dashboard Display Issues 96 | 97 | 1. Check Web Browser Console 98 | - Open browser developer tools (F12) 99 | - Look for errors in console 100 | 101 | 2. Verify WebSocket Connection 102 | - Check browser network tab 103 | - Look for socket.io connections 104 | 105 | 3. Test Dashboard Access 106 | curl http://localhost:8080 107 | curl http://your-server-ip:8080 108 | 109 | ### Quick Reset Procedure 110 | 111 | If all else fails: 112 | 1. Stop containers: 113 | docker compose down 114 | 115 | 2. Clear logs: 116 | cd /home//Soularr/data/logs/ 117 | rm soularr.log* 118 | touch soularr.log 119 | chmod 644 soularr.log 120 | 121 | 3. Reset permissions: 122 | sudo chown -R 1000:1000 /home//Soularr/data/logs/ 123 | 124 | 4. Restart containers: 125 | docker compose up -d 126 | 127 | 5. Monitor logs: 128 | docker logs -f soularr-dashboard 129 | 130 | -------------------------------------------------------------------------------- /dashboard/dashboard.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | from flask_socketio import SocketIO 3 | import json 4 | import subprocess 5 | import time 6 | import re 7 | from datetime import datetime 8 | import threading 9 | import os 10 | 11 | app = Flask(__name__) 12 | socketio = SocketIO(app) 13 | 14 | # Define constant paths 15 | LOG_PATH = '/data/logs/soularr.log' 16 | FAILURE_PATH = '/data/failure_list.txt' 17 | 18 | def ensure_directories(): 19 | """Create necessary directories if they don't exist""" 20 | os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True) 21 | os.makedirs(os.path.dirname(FAILURE_PATH), exist_ok=True) 22 | 23 | def monitor_logs(): 24 | ensure_directories() 25 | 26 | # Create failure_list.txt if it doesn't exist 27 | if not os.path.exists(FAILURE_PATH): 28 | open(FAILURE_PATH, 'w').close() 29 | 30 | while True: 31 | try: 32 | with open(LOG_PATH, 'r') as log_file: 33 | log_file.seek(0, 2) # Move to end of file 34 | while True: 35 | line = log_file.readline() 36 | if not line: 37 | time.sleep(1) 38 | continue 39 | 40 | # Emit log update to dashboard 41 | socketio.emit('log_update', {'data': line}) 42 | 43 | # Check for both types of failures 44 | import_match = re.search(r'Failed to import from: .+/complete/(.+)', line) 45 | move_match = re.search(r'Failed import moved to: failed_imports/(.+)', line) 46 | 47 | if import_match or move_match: 48 | artist_name = import_match.group(1) if import_match else move_match.group(1) 49 | current_time = datetime.now().strftime('%d/%m/%Y %H:%M:%S') 50 | failure_entry = f"{current_time} - {artist_name}, Failed Import\n" 51 | 52 | with open(FAILURE_PATH, 'a+') as f: 53 | f.seek(0) 54 | if failure_entry not in f.readlines(): 55 | f.write(failure_entry) 56 | 57 | except FileNotFoundError: 58 | print(f"Waiting for log file to be created: {LOG_PATH}") 59 | time.sleep(5) 60 | except Exception as e: 61 | print(f"Error in monitor_logs: {str(e)}") 62 | time.sleep(5) 63 | 64 | def get_docker_logs(): 65 | """Retrieve the last 50 lines of logs""" 66 | try: 67 | with open(LOG_PATH, 'r') as f: 68 | logs = f.readlines() 69 | return logs[-50:][::-1] # Return last 50 lines in reverse order 70 | except FileNotFoundError: 71 | return ["Log file not found. Waiting for logs to be generated..."] 72 | except Exception as e: 73 | return [f"Error accessing logs: {str(e)}"] 74 | 75 | @app.route('/') 76 | def index(): 77 | return render_template('index.html') 78 | 79 | @app.route('/logs') 80 | def get_logs(): 81 | try: 82 | docker_logs = get_docker_logs() 83 | 84 | try: 85 | with open(FAILURE_PATH, 'r') as f: 86 | failure_logs = f.readlines() 87 | except FileNotFoundError: 88 | open(FAILURE_PATH, 'w').close() 89 | failure_logs = [] 90 | except Exception as e: 91 | failure_logs = [f"Error reading failure logs: {str(e)}"] 92 | 93 | return json.dumps({ 94 | 'docker_logs': docker_logs, 95 | 'failure_logs': failure_logs 96 | }) 97 | except Exception as e: 98 | return json.dumps({ 99 | 'docker_logs': [f"Error accessing logs: {str(e)}"], 100 | 'failure_logs': [] 101 | }) 102 | 103 | def main(): 104 | ensure_directories() 105 | 106 | # Start the log monitoring thread 107 | monitor_thread = threading.Thread(target=monitor_logs, daemon=True) 108 | monitor_thread.start() 109 | 110 | # Run the Flask-SocketIO app 111 | socketio.run(app, 112 | host='0.0.0.0', 113 | port=8080, 114 | debug=False, 115 | allow_unsafe_werkzeug=True) 116 | 117 | if __name__ == '__main__': 118 | main() 119 | 120 | -------------------------------------------------------------------------------- /dashboard.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | from flask_socketio import SocketIO 3 | import json 4 | import subprocess 5 | import time 6 | import re 7 | from datetime import datetime 8 | import threading 9 | import os 10 | 11 | app = Flask(__name__) 12 | socketio = SocketIO(app) 13 | 14 | # Define constant paths 15 | # LOG_PATH = '/home/eric/soularr/logs/soularr.log' 16 | # FAILURE_PATH = '/home/eric/soularr/failure_list.txt' 17 | 18 | LOG_PATH = os.environ.get('LOG_PATH', '/logs/soularr.log') 19 | FAILURE_PATH = os.environ.get('FAILURE_PATH', '/data/failure_list.txt') 20 | 21 | 22 | def ensure_directories(): 23 | """Create necessary directories if they don't exist""" 24 | os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True) 25 | os.makedirs(os.path.dirname(FAILURE_PATH), exist_ok=True) 26 | 27 | def monitor_logs(): 28 | ensure_directories() 29 | 30 | # Create failure_list.txt if it doesn't exist 31 | if not os.path.exists(FAILURE_PATH): 32 | open(FAILURE_PATH, 'w').close() 33 | 34 | while True: 35 | try: 36 | with open(LOG_PATH, 'r') as log_file: 37 | log_file.seek(0, 2) # Move to end of file 38 | while True: 39 | line = log_file.readline() 40 | if not line: 41 | time.sleep(1) 42 | continue 43 | 44 | # Emit log update to dashboard 45 | socketio.emit('log_update', {'data': line}) 46 | 47 | # Check for both types of failures 48 | import_match = re.search(r'Failed to import from: .+/complete/(.+)', line) 49 | move_match = re.search(r'Failed import moved to: failed_imports/(.+)', line) 50 | 51 | if import_match or move_match: 52 | artist_name = import_match.group(1) if import_match else move_match.group(1) 53 | current_time = datetime.now().strftime('%d/%m/%Y %H:%M:%S') 54 | failure_entry = f"{current_time} - {artist_name}, Failed Import\n" 55 | 56 | with open(FAILURE_PATH, 'a+') as f: 57 | f.seek(0) 58 | if failure_entry not in f.readlines(): 59 | f.write(failure_entry) 60 | 61 | except FileNotFoundError: 62 | print(f"Waiting for log file to be created: {LOG_PATH}") 63 | time.sleep(5) 64 | except Exception as e: 65 | print(f"Error in monitor_logs: {str(e)}") 66 | time.sleep(5) 67 | 68 | def get_docker_logs(): 69 | """Retrieve the last 50 lines of logs""" 70 | try: 71 | with open(LOG_PATH, 'r') as f: 72 | logs = f.readlines() 73 | return logs[-50:][::-1] # Return last 50 lines in reverse order 74 | except FileNotFoundError: 75 | return ["Log file not found. Waiting for logs to be generated..."] 76 | except Exception as e: 77 | return [f"Error accessing logs: {str(e)}"] 78 | 79 | @app.route('/') 80 | def index(): 81 | return render_template('index.html') 82 | 83 | @app.route('/logs') 84 | def get_logs(): 85 | try: 86 | docker_logs = get_docker_logs() 87 | 88 | try: 89 | with open(FAILURE_PATH, 'r') as f: 90 | failure_logs = f.readlines() 91 | except FileNotFoundError: 92 | open(FAILURE_PATH, 'w').close() 93 | failure_logs = [] 94 | except Exception as e: 95 | failure_logs = [f"Error reading failure logs: {str(e)}"] 96 | 97 | return json.dumps({ 98 | 'docker_logs': docker_logs, 99 | 'failure_logs': failure_logs 100 | }) 101 | except Exception as e: 102 | return json.dumps({ 103 | 'docker_logs': [f"Error accessing logs: {str(e)}"], 104 | 'failure_logs': [] 105 | }) 106 | 107 | def main(): 108 | ensure_directories() 109 | 110 | # Start the log monitoring thread 111 | monitor_thread = threading.Thread(target=monitor_logs, daemon=True) 112 | monitor_thread.start() 113 | 114 | # Run the Flask-SocketIO app 115 | socketio.run(app, 116 | host='0.0.0.0', 117 | port=8082, 118 | debug=False, 119 | allow_unsafe_werkzeug=True) 120 | 121 | if __name__ == '__main__': 122 | main() 123 | 124 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | Installation Guide for Soularr Dashboard 2 | 3 | ## Directory Setup 4 | 1. Create the required directory structure: 5 | 6 | mkdir -p /home//Soularr/dashboard/templates 7 | mkdir -p /home//Soularr/data/logs 8 | chmod 755 /home//Soularr/data/logs 9 | 10 | ### Directory Structure 11 | 12 | /home//Soularr/ 13 | ├── data/ 14 | │ └── logs/ 15 | └── dashboard/ 16 | └── templates/ 17 | 18 | ## Required Files 19 | 20 | 1. Create dashboard.py in /home//Soularr/dashboard/: 21 | 22 | from flask import Flask, render_template 23 | from flask_socketio import SocketIO 24 | import json 25 | import subprocess 26 | import time 27 | import re 28 | from datetime import datetime 29 | import threading 30 | import os 31 | 32 | app = Flask(__name__) 33 | socketio = SocketIO(app) 34 | 35 | # Define constant paths 36 | LOG_PATH = '/data/logs/soularr.log' 37 | FAILURE_PATH = '/data/failure_list.txt' 38 | 39 | def ensure_directories(): 40 | """Create necessary directories if they don't exist""" 41 | os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True) 42 | os.makedirs(os.path.dirname(FAILURE_PATH), exist_ok=True) 43 | 44 | def monitor_logs(): 45 | ensure_directories() 46 | 47 | # Create failure_list.txt if it doesn't exist 48 | if not os.path.exists(FAILURE_PATH): 49 | open(FAILURE_PATH, 'w').close() 50 | 51 | while True: 52 | try: 53 | with open(LOG_PATH, 'r') as log_file: 54 | log_file.seek(0, 2) # Move to end of file 55 | while True: 56 | line = log_file.readline() 57 | if not line: 58 | time.sleep(1) 59 | continue 60 | 61 | # Emit log update to dashboard 62 | socketio.emit('log_update', {'data': line}) 63 | 64 | # Check for both types of failures 65 | import_match = re.search(r'Failed to import from: .+/complete/(.+)', line) 66 | move_match = re.search(r'Failed import moved to: failed_imports/(.+)', line) 67 | 68 | if import_match or move_match: 69 | artist_name = import_match.group(1) if import_match else move_match.group(1) 70 | current_time = datetime.now().strftime('%d/%m/%Y %H:%M:%S') 71 | failure_entry = f"{current_time} - {artist_name}, Failed Import\n" 72 | 73 | with open(FAILURE_PATH, 'a+') as f: 74 | f.seek(0) 75 | if failure_entry not in f.readlines(): 76 | f.write(failure_entry) 77 | 78 | except FileNotFoundError: 79 | print(f"Waiting for log file to be created: {LOG_PATH}") 80 | time.sleep(5) 81 | except Exception as e: 82 | print(f"Error in monitor_logs: {str(e)}") 83 | time.sleep(5) 84 | 85 | def get_docker_logs(): 86 | """Retrieve the last 50 lines of logs""" 87 | try: 88 | with open(LOG_PATH, 'r') as f: 89 | logs = f.readlines() 90 | return logs[-50:][::-1] # Return last 50 lines in reverse order 91 | except FileNotFoundError: 92 | return ["Log file not found. Waiting for logs to be generated..."] 93 | except Exception as e: 94 | return [f"Error accessing logs: {str(e)}"] 95 | 96 | @app.route('/') 97 | def index(): 98 | return render_template('index.html') 99 | 100 | @app.route('/logs') 101 | def get_logs(): 102 | try: 103 | docker_logs = get_docker_logs() 104 | 105 | try: 106 | with open(FAILURE_PATH, 'r') as f: 107 | failure_logs = f.readlines() 108 | except FileNotFoundError: 109 | open(FAILURE_PATH, 'w').close() 110 | failure_logs = [] 111 | except Exception as e: 112 | failure_logs = [f"Error reading failure logs: {str(e)}"] 113 | 114 | return json.dumps({ 115 | 'docker_logs': docker_logs, 116 | 'failure_logs': failure_logs 117 | }) 118 | except Exception as e: 119 | return json.dumps({ 120 | 'docker_logs': [f"Error accessing logs: {str(e)}"], 121 | 'failure_logs': [] 122 | }) 123 | 124 | def main(): 125 | ensure_directories() 126 | 127 | # Start the log monitoring thread 128 | monitor_thread = threading.Thread(target=monitor_logs, daemon=True) 129 | monitor_thread.start() 130 | 131 | # Run the Flask-SocketIO app 132 | socketio.run(app, 133 | host='0.0.0.0', 134 | port=8080, 135 | debug=False, 136 | allow_unsafe_werkzeug=True) 137 | 138 | if __name__ == '__main__': 139 | main() 140 | 141 | 2. Create requirements.txt in /home//Soularr/dashboard/: 142 | 143 | flask==3.0.0 144 | flask-socketio==5.3.6 145 | python-socketio==5.10.0 146 | python-engineio==4.8.0 147 | gunicorn==21.2.0 148 | eventlet==0.33.3 149 | 150 | 3. Create Dockerfile in /home//Soularr/dashboard/: 151 | 152 | FROM python:3.12-slim 153 | WORKDIR /app 154 | COPY requirements.txt . 155 | RUN pip install --no-cache-dir -r requirements.txt 156 | COPY . . 157 | CMD ["python", "dashboard.py"] 158 | 159 | 4. Create failure_list.txt in /home//Soularr/: 160 | 161 | touch /home//Soularr/failure_list.txt 162 | chmod 644 /home//Soularr/failure_list.txt 163 | 164 | 5. Configure logging in config.ini: 165 | 166 | [Logging] 167 | level = INFO 168 | format = [%(levelname)s|%(module)s|L%(lineno)d] %(asctime)s: %(message)s 169 | datefmt = %Y-%m-%dT%H:%M:%S%z 170 | filename = /data/logs/soularr.log 171 | 172 | 6. Create index.html in /home//Soularr/dashboard/templates/: 173 | 174 | 175 | 176 | 177 | Soularr Log Monitor 178 | 222 | 223 | 224 | 225 |

Soularr Log Monitor

226 |
227 |
228 |

Docker Logs

229 |
230 |
231 |
232 |

Failure List

233 |
234 |
235 |
236 | 268 | 269 | 270 | 271 | 7. Add to docker-compose.yml: 272 | 273 | dashboard: 274 | build: 275 | context: ./dashboard 276 | dockerfile: Dockerfile 277 | restart: unless-stopped 278 | container_name: soularr-dashboard 279 | network_mode: host 280 | environment: 281 | - PUID=1000 282 | - PGID=1000 283 | - TZ=America/Los_Angeles 284 | volumes: 285 | - /home//Soularr/dashboard:/app 286 | - /home//Soularr:/data 287 | - /home//Soularr/data/logs:/data/logs 288 | - /var/run/docker.sock:/var/run/docker.sock 289 | working_dir: /app 290 | logging: 291 | driver: "json-file" 292 | options: 293 | max-size: "10m" 294 | max-file: "3" 295 | 296 | 8. Configure Scheduled Operation: 297 | Create a cron job for the current user: 298 | 299 | crontab -e 300 | 301 | Add the following lines: 302 | 303 | 0 1 * * * cd /home//Soularr && docker compose up -d 304 | 0 6 * * * cd /home//Soularr && docker compose down 305 | 306 | 9. Build and Start: 307 | 308 | docker compose down 309 | docker compose build dashboard 310 | docker compose up -d 311 | 312 | 10. Access: 313 | Open your web browser and navigate to: http://your-server-ip:8080 314 | 315 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![banner](https://raw.githubusercontent.com/mrusse/soularr/refs/heads/main/resources/banner.png) 3 | 4 |

Soularr

5 |

6 | A Python script that connects Lidarr with Soulseek! 7 |

8 | 9 |

10 | 11 | Join our Discord 12 | 13 |

14 | 15 | # About 16 | 17 | Soularr reads all of your "wanted" albums/artists from Lidarr and downloads them using Slskd. It uses the libraries: [pyarr](https://github.com/totaldebug/pyarr) and [slskd-api](https://github.com/bigoulours/slskd-python-api) to make this happen. View the demo below! 18 | 19 | ![Soularr_small](https://github.com/user-attachments/assets/15c47a82-ddf2-40e3-b143-2ad7f570730f) 20 | 21 | 22 | After the downloads are complete in Slskd the script will tell Lidarr to import the downloaded files, making it a truly hands off process. 23 | # Setup 24 | 25 | ### Install and configure Lidarr and Slskd 26 | 27 | **Lidarr** 28 | [https://lidarr.audio/](https://lidarr.audio/) 29 | 30 | Make sure Lidarr can see your Slskd download directory, if you are running Lidarr in a Docker container you may need to mount the directory. You will then need add it to your config (see "download_dir" under "Lidarr" in the example config). 31 | 32 | **Slskd** 33 | [https://github.com/slskd/slskd](https://github.com/slskd/slskd) 34 | 35 | The script requires an api key from Slskd. Take a look at their [docs](https://github.com/slskd/slskd/blob/master/docs/config.md#authentication) on how to set it up (all you have to do is add it to the yml file under `web, authentication, api_keys, my_api_key`). 36 | 37 | ### Configure your config file 38 | 39 | The config file has a bunch of different settings that affect how the script runs. Any lists in the config such as "accepted_countries" need to be comma separated with no spaces (e.g. `","` not `" , "` or `" ,"`). 40 | 41 | **Example config:** 42 | 43 | ```ini 44 | [Lidarr] 45 | api_key = yourlidarrapikeygoeshere 46 | host_url = http://localhost:8686 47 | #This should be the path mounted in lidarr that points to your slskd download directory. 48 | #If Lidarr is not running in Docker then this may just be the same dir as Slskd is using below. 49 | download_dir = /lidarr/path/to/slskd/downloads 50 | 51 | [Slskd] 52 | #Api key from Slskd. Need to set this up manually. See link to Slskd docs above. 53 | api_key = yourslskdapikeygoeshere 54 | host_url = http://localhost:5030 55 | #Slskd download directory. Should have set it up when installing Slskd. 56 | download_dir = /path/to/your/Slskd/downloads 57 | #Removes searches from Slskd after the search finishes. 58 | delete_searches = False 59 | #Maximum time (in seconds) that the script will wait for downloads to complete. 60 | #This is used to prevent the script from running forever due to a stalled download. Defaults to 1 hour. 61 | stalled_timeout = 3600 62 | 63 | [Release Settings] 64 | #Selects the release with the most common amount of tracks out of all the releases. 65 | use_most_common_tracknum = True 66 | allow_multi_disc = True 67 | #See full list of countries below. 68 | accepted_countries = Europe,Japan,United Kingdom,United States,[Worldwide],Australia,Canada 69 | #See full list of formats below. 70 | accepted_formats = CD,Digital Media,Vinyl 71 | 72 | [Search Settings] 73 | search_timeout = 5000 74 | maximum_peer_queue = 50 75 | #Min upload speed in bit/s 76 | minimum_peer_upload_speed = 0 77 | #Min match ratio accepted when comparing lidarr track names to soulseek filenames. 78 | minimum_filename_match_ratio = 0.5 79 | #Specify the file types you prefer from most to least. As well as their attributes such as bitrate / samplerate / bitdepth. 80 | #For flacs you can choose the bitdepth/samplerate. And for mp3s the bitrate. 81 | #If you do not care about the specific quality you can still just put "flac" or "mp3". 82 | #Soularr will then just look at the filetype and ignore file attributes. 83 | allowed_filetypes = flac 24/192,flac 16/44.1,flac,mp3 320,mp3 84 | ignored_users = User1,User2,Fred,Bob 85 | #Set to False if you only want to search for complete albums 86 | search_for_tracks = True 87 | #Set to True if you want to add the artist's name to the beginning of the search for albums 88 | album_prepend_artist = False 89 | track_prepend_artist = True 90 | #Valid search types: all || incrementing_page || first_page 91 | #"all" will search for every wanted record everytime soularr is run. 92 | #"incrementing_page" will start with the first page and increment to the next on each run. 93 | #"first_page" will repeatedly search the first page. 94 | #If using the search type "first_page" remove_wanted_on_failure should be enabled. 95 | search_type = incrementing_page 96 | #How mancy records to grab each run, must be a number between 1 - 2,147,483,647 97 | number_of_albums_to_grab = 10 98 | #Unmonitors the album if Soularr can't find it and places it in "failure_list.txt". 99 | #Failed albums can be re monitored by filtering "Unmonitored" in the Lidarr wanted list. 100 | remove_wanted_on_failure = False 101 | #Comma separated list of words that can't be in the title of albums or tracks. Case insensitive. 102 | title_blacklist = BlacklistWord1,blacklistword2 103 | #Lidarr source to use for searching. Accepted values are "missing" or "cutoff_unmet". The default value is "missing". 104 | search_source = missing 105 | 106 | [Logging] 107 | #These options are passed into the logger's basicConfig() method as-is. 108 | #This means, if you're familiar with Python's logging module, you can configure 109 | #the logger with options beyond what's listed here by default. 110 | #For more information on available options -- https://docs.python.org/3/library/logging.html#logging.basicConfig 111 | level = INFO 112 | # Format of log message -- https://docs.python.org/3/library/logging.html#logrecord-attributes 113 | format = [%(levelname)s|%(module)s|L%(lineno)d] %(asctime)s: %(message)s 114 | # Format of datetimes -- https://docs.python.org/3/library/time.html#time.strftime 115 | datefmt = %Y-%m-%dT%H:%M:%S%z 116 | ``` 117 | 118 | [Full list of countries from Musicbrainz.](https://musicbrainz.org/doc/Release/Country) 119 | 120 | [Full list of formats (also from Musicbrainz but for some reason they don't have a nice list)](https://pastebin.com/raw/pzGVUgaE) 121 | 122 | 123 | I have included this [example config](https://github.com/mrusse/soularr/blob/main/config.ini) in the repo. 124 | 125 | 126 | ## Docker 127 | 128 | The best way to run the script is through Docker. A Docker image is available through [dockerhub](https://hub.docker.com/r/mrusse08/soularr). 129 | 130 | Example docker run command: 131 | ```shell 132 | docker run -d \ 133 | --name soularr \ 134 | --restart unless-stopped \ 135 | --hostname soularr \ 136 | -e PUID=1000 \ 137 | -e PGID=1000 \ 138 | -e TZ=Etc/UTC \ 139 | -e SCRIPT_INTERVAL=300 \ 140 | -v /path/to/slskd/downloads:/downloads \ 141 | -v /path/to/config/dir:/data \ 142 | --user 1000:1000 \ 143 | mrusse08/soularr:latest 144 | ``` 145 | 146 | Or you can also set it up with the provided [Docker Compose](https://github.com/mrusse/soularr/blob/main/docker-compose.yml). 147 | ```yml 148 | version: "3" 149 | services: 150 | soularr: 151 | restart: unless-stopped 152 | container_name: soularr 153 | hostname: soularr 154 | environment: 155 | - PUID=1000 156 | - PGID=1000 157 | - TZ=Etc/UTC 158 | #Script interval in seconds 159 | - SCRIPT_INTERVAL=300 160 | user: "1000:1000" 161 | volumes: 162 | #"You can set /downloads to whatever you want but will then need to change the Slskd download dir in your config file" 163 | - /path/to/slskd/downloads:/downloads 164 | #Select where you are storing your config file. 165 | #Leave "/data" since thats where the script expects the config file to be 166 | - /path/to/config/dir:/data 167 | image: mrusse08/soularr:latest 168 | ``` 169 | 170 | Note: You **must** edit both volumes in the docker compose above. 171 | 172 | - `/path/to/slskd/downloads:/downloads` 173 | 174 | + This is where you put your Slskd downloads path. 175 | 176 | + You can point it to whatever dir you want but make sure to put the same dir in your config file under `[Slskd] -> download_dir`. 177 | 178 | + For example you could leave it as `/downloads` then in your config your entry would be `download_dir = /downloads`. 179 | 180 | - `/path/to/config/dir:/data` 181 | 182 | + This is where put the path you are storing your config file. It must point to `/data`. 183 | 184 | You can also edit `SCRIPT_INTERVAL` to choose how often (in seconds) you want the script to run (default is every 300 seconds). Another thing to note is that by default the user perms are set to PUID:1000 and PGID:1000. If you wish to edit this change `user: "1000:1000"` in the Docker compose to whatever you prefer. 185 | 186 | ## Running Manually 187 | 188 | Install the requirements: 189 | ``` 190 | python -m pip install -r requirements.txt 191 | ``` 192 | 193 | You can simply run the script with: 194 | ``` 195 | python soularr.py 196 | ``` 197 | Note: the `config.ini` file needs to be in the same directory as `soularr.py`. 198 | 199 | ### Scheduling the script: 200 | 201 | Even if you are not using Docker you can still schedule the script. I have included an example bash script below that can be scheduled using a [cron job](https://crontab.guru/every-5-minutes). 202 | 203 | ```bash 204 | #!/bin/bash 205 | cd /path/to/soularr/python/script 206 | 207 | dt=$(date '+%d/%m/%Y %H:%M:%S'); 208 | echo "Starting Soularr! $dt" 209 | 210 | if ps aux | grep "[s]oularr.py" > /dev/null; then 211 | echo "Soularr is already running. Exiting..." 212 | else 213 | python soularr.py 214 | fi 215 | ``` 216 | 217 | **Example cron job setup:** 218 | 219 | Edit crontab file with 220 | 221 | ``` 222 | crontab -e 223 | ``` 224 | 225 | Then enter in your schedule followed by the command. For example: 226 | 227 | ``` 228 | */5 * * * * /path/to/run.sh 229 | ``` 230 | 231 | This would run the bash script every 5 minutes. 232 | 233 | All of this is focused on Linux but the Python script runs fine on Windows as well. You can use things like the [Windows Task Scheduler](https://en.wikipedia.org/wiki/Windows_Task_Scheduler) to perform similar scheduling operations. 234 | 235 | ## Logging 236 | 237 | There are some very basic options for logging found under the `[Logging]` section of the `config.ini` file. The defaults 238 | should be sensible for a typical logging scenario, but are still somewhat opinionated. Some users may not like how the 239 | log messages are formatted and would prefer a much simpler output than what is provided by default. 240 | 241 | For example, if you want the logs to only show the message and none of the other detailed information, edit the 242 | `[Logging]` section's `format` property to look like this: 243 | 244 | ```ini 245 | [Logging] 246 | format = %(message)s 247 | ``` 248 | 249 | For more information on the options available for logging, including more options for changing how the messages are 250 | formatted, see the comments in the `[Logging]` section from the [example config.ini](#configure-your-config-file). 251 | 252 | ### Log to a File 253 | 254 | Currently, the logs are only output to stdout which should be suitable for most users who just want a basic and simple 255 | set up for Soularr. For users running Soularr as a long-running process, or as part of a cronjob, or for any reason where 256 | logging to a file is desired, normal command-line tools can be used without needing to touch the logger's configuration. 257 | 258 | For example, the `tee` command on Linux and MacOS can be used to allow Soularr to log its output to both stdout and a 259 | file of your choosing (here the file `soularr.log` is used, but it can be any file you want): 260 | 261 | ```sh 262 | python soularr.py 2>&1 | tee -a soularr.log 263 | ``` 264 | 265 | Or on Windows PowerShell using the similar `Tee-Object` cmdlet: 266 | 267 | ```powershell 268 | python soularr.py 2>&1 | Tee-Object -FilePath soularr.log -Append 269 | ``` 270 | 271 | ### View logs in WebUI 272 | 273 | [EricH9958](https://github.com/EricH9958) has made a log viewer that lets you monitor the Soularr logs in your browser! Check his repo out here: 274 | 275 | [https://github.com/EricH9958/Soularr-Dashboard](https://github.com/EricH9958/Soularr-Dashboard) 276 | 277 | ### Advanced Logging Usage 278 | 279 | The current logging setup for Soularr is *very* simple and only allows for the most basic configuration options 280 | provided by [Python's builtin logging module](https://docs.python.org/3/library/logging.html). Logging was kept simple 281 | to avoid over-complicating things, and it seems like the desire for more advanced logging capabilities is currently 282 | quite low. 283 | 284 | **If you would like more advanced logging configuration options to be implemented** (such as configuring filters, 285 | formatters, handlers, additional streams, and multi-logger setups), consider submitting a feature request in 286 | [the official discord](https://discord.gg/EznhgYBayN) or [submitting an Issue in the GitHub repository itself](https://github.com/mrusse/soularr/issues). 287 | 288 | ## 289 |

290 | Buy Me a Coffee at ko-fi.com 291 |

292 | 293 | ## Docker Deployment 294 | 295 | The Soularr Dashboard is now available as a Docker container, making it easy to deploy alongside your Soularr installation. 296 | 297 | ### How to Pull the Docker Image 298 | 299 | You can pull the prebuilt Docker image from GitHub Container Registry: `docker pull ghcr.io/erich9958/soularr-dashboard:latest` 300 | 301 | ### Docker Compose Setup 302 | 303 | Create a `docker-compose.yml` file with the following content: 304 | 305 | version: "3" 306 | services: 307 | dashboard: 308 | image: ghcr.io/erich9958/soularr-dashboard:latest 309 | container_name: soularr-dashboard 310 | network_mode: "host" 311 | volumes: 312 | - /opt/Soularr_docker/logs:/logs 313 | - /opt/Soularr_docker/config:/data 314 | environment: 315 | - LOG_PATH=/logs/soularr.log 316 | - FAILURE_PATH=/data/failure_list.txt 317 | - TZ=America/Los_Angeles 318 | restart: unless-stopped 319 | 320 | This setup assumes that your Soularr container is already configured to write logs to `/opt/Soularr_docker/logs` and uses `/opt/Soularr_docker/config` for its configuration files. 321 | 322 | ### Environment Variables 323 | 324 | The following environment variables can be configured: 325 | 326 | | Variable | Description | Default | 327 | |----------------|---------------------------------|------------------------| 328 | | LOG_PATH | Path to the Soularr log file | `/logs/soularr.log` | 329 | | FAILURE_PATH | Path to the failure list file | `/data/failure_list.txt` | 330 | | TZ | Timezone for the container | `UTC` | 331 | 332 | ### Running Manually 333 | 334 | If you prefer not to use Docker Compose, you can run the container manually using: 335 | 336 | docker run -d \ 337 | --name soularr-dashboard \ 338 | --network host \ 339 | -v /opt/Soularr_docker/logs:/logs \ 340 | -v /opt/Soularr_docker/config:/data \ 341 | -e LOG_PATH=/logs/soularr.log \ 342 | -e FAILURE_PATH=/data/failure_list.txt \ 343 | -e TZ=America/Los_Angeles \ 344 | ghcr.io/erich9958/soularr-dashboard:latest 345 | 346 | This will start the dashboard container and allow it to monitor logs and failures from your Soularr instance. 347 | ======= 348 | # Soularr Dashboard 349 | 350 | ![Soularr Dashboard](images/Soularr-dashboard_final.png) 351 | 352 | A web interface that displays Soularr logs and failure list in real-time using a dual-window display. 353 | 354 | ## What it Does 355 | - Shows Soularr logs in the left window (newest entries at top) 356 | - Shows failure list in the right window 357 | - Auto-refreshes every second 358 | - Equal window sizing for easy viewing 359 | - Real-time log monitoring 360 | - Failed import tracking 361 | - Clean, responsive web interface 362 | - Docker-based deployment 363 | - Automatic log rotation 364 | - Configurable logging levels 365 | 366 | ## Requirements 367 | - Docker and Docker Compose 368 | - Soularr already installed and running 369 | - Linux system (tested on Ubuntu) 370 | - Port 8080 available 371 | 372 | ## Directory Structure 373 | 374 | ### Docker Installation 375 | /opt/Soularr_docker/ 376 | ├── config/ 377 | ├── logs/ 378 | │ └── soularr.log 379 | ├── dashboard/ 380 | │ ├── templates/ 381 | │ ├── dashboard.py 382 | │ ├── Dockerfile 383 | │ └── requirements.txt 384 | ├── docker-compose.yml.example 385 | └── config.ini 386 | 387 | ### Manual Installation 388 | /home//Soularr/ 389 | ├── data/ 390 | │ └── logs/ 391 | │ └── soularr.log 392 | ├── dashboard/ 393 | │ ├── templates/ 394 | │ ├── dashboard.py 395 | │ ├── Dockerfile 396 | │ └── requirements.txt 397 | ├── docker-compose.yml 398 | └── config.ini 399 | 400 | 401 | ## Quick Setup 402 | 1. Clone this repository to your Soularr directory 403 | 2. Add the dashboard service to your docker-compose.yml 404 | 3. Build and start the containers: 405 | 406 | docker compose down 407 | docker compose build dashboard 408 | docker compose up -d 409 | 410 | ## Logging 411 | 412 | ### Docker Installation 413 | Logs are stored in the `/data/logs` directory, which maps to `/opt/Soularr_docker/logs` on the host system. The main log file is `soularr.log`. This directory is mounted as a volume in the Docker container, ensuring persistent logging across container restarts. 414 | 415 | ### Manual Installation 416 | Logs are stored in the `logs` directory within the Soularr installation folder. For example: /home//Soularr/logs/soularr.log 417 | Ensure that the `logs` directory exists and is writable by the Soularr application. 418 | 419 | ### Log Configuration 420 | - Logs are automatically created in the specified directory 421 | - Docker handles log rotation (10MB max size, keeps 3 files) 422 | - Log path can be configured in config.ini 423 | - Dashboard automatically monitors and displays the latest logs 424 | 425 | ## Usage 426 | Open your web browser and go to: 427 | http://your-server-ip:8080 428 | 429 | ## Configuration 430 | See INSTALL.md for detailed configuration options and setup instructions. 431 | 432 | ## Contributing 433 | Contributions are welcome! Please feel free to submit a Pull Request. 434 | 435 | ## Support 436 | If you encounter any issues or need help, please open an issue on GitHub. 437 | 438 | ## License 439 | This project is licensed under the MIT License - see the LICENSE.txt file for details 440 | 441 | ## Acknowledgments 442 | - Soularr project team 443 | - Flask-SocketIO contributors 444 | - Docker 445 | 446 | --------------------------------------------------------------------------------