fixed duplicates and slskd crashes, implemented autoheal if containers hangs
This commit is contained in:
@@ -27,6 +27,7 @@ No Spotify. No YouTube Music. No ads. Just your music, your server, your rules.
|
|||||||
| **Pangolin** | Reverse proxy — exposes Navidrome securely to the internet |
|
| **Pangolin** | Reverse proxy — exposes Navidrome securely to the internet |
|
||||||
| **Gluetun** | VPN gateway container — all Soulseek traffic exits via ProtonVPN |
|
| **Gluetun** | VPN gateway container — all Soulseek traffic exits via ProtonVPN |
|
||||||
| **slskd** | Soulseek client — searches and downloads music from the P2P network |
|
| **slskd** | Soulseek client — searches and downloads music from the P2P network |
|
||||||
|
| **autoheal** | Watches slskd/gluetun health status and force-restarts them if they hang |
|
||||||
| **Explo** | Discovery engine — connects ListenBrainz recommendations to slskd |
|
| **Explo** | Discovery engine — connects ListenBrainz recommendations to slskd |
|
||||||
| **ListenBrainz** | Open-source scrobbler + recommendation engine |
|
| **ListenBrainz** | Open-source scrobbler + recommendation engine |
|
||||||
| **Last.fm** | Secondary scrobbler for stats and social features |
|
| **Last.fm** | Secondary scrobbler for stats and social features |
|
||||||
@@ -55,7 +56,7 @@ No Spotify. No YouTube Music. No ads. Just your music, your server, your rules.
|
|||||||
│ └── slskd.yml ← slskd config (copy from repo)
|
│ └── slskd.yml ← slskd config (copy from repo)
|
||||||
└── explo/
|
└── explo/
|
||||||
├── .env ← Explo config (copy from .env.example)
|
├── .env ← Explo config (copy from .env.example)
|
||||||
└── docker-compose.yml ← Full stack compose file (copy from repo)
|
└── config/ ← Explo's playlist cache + cover art (auto-populated, must persist)
|
||||||
|
|
||||||
/mnt/user/data/media/music/
|
/mnt/user/data/media/music/
|
||||||
├── incomplete/ ← In-progress downloads (Navidrome ignores these)
|
├── incomplete/ ← In-progress downloads (Navidrome ignores these)
|
||||||
@@ -99,7 +100,7 @@ Fill in your ListenBrainz username, Navidrome URL/credentials, and slskd API key
|
|||||||
### 3. Create required folders
|
### 3. Create required folders
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /mnt/user/appdata/{gluetun,slskd,explo}
|
mkdir -p /mnt/user/appdata/{gluetun,slskd,explo/config}
|
||||||
mkdir -p /mnt/user/data/media/music/{explo,incomplete}
|
mkdir -p /mnt/user/data/media/music/{explo,incomplete}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -134,21 +135,15 @@ Add these environment variables in the Navidrome container template:
|
|||||||
| `ND_LASTFM_ENABLED` | `true` |
|
| `ND_LASTFM_ENABLED` | `true` |
|
||||||
| `ND_SCANSCHEDULE` | `1m` |
|
| `ND_SCANSCHEDULE` | `1m` |
|
||||||
|
|
||||||
### 7. Set up automation via Unraid User Scripts
|
### 7. Start Explo and autoheal
|
||||||
|
|
||||||
Install the **User Scripts** plugin from Community Apps, then add two scripts:
|
|
||||||
|
|
||||||
**Weekly Exploration** — Cron: `15 0 * * 2`
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
docker compose up -d explo autoheal
|
||||||
docker compose -f /mnt/user/appdata/explo/docker-compose.yml up explo-weekly
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Daily Jams** — Cron: `15 0 * * *`
|
Unlike the rest of the stack, Explo doesn't need any external scheduler (no Unraid User Scripts, no cron). It runs continuously and schedules Weekly Exploration and Daily Jams itself via `WEEKLY_EXPLORATION_SCHEDULE` / `DAILY_JAMS_SCHEDULE` in `docker-compose.yml`, and `EXECUTE_ON_START=false` means restarting or updating the container never triggers an extra, out-of-schedule run. This is what keeps it from downloading the same week twice — see [Troubleshooting](#troubleshooting) below for the full explanation.
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
`autoheal` also just runs continuously in the background; it watches slskd and gluetun and restarts either one automatically if Docker marks it unhealthy.
|
||||||
docker compose -f /mnt/user/appdata/explo/docker-compose.yml up explo-daily
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -176,6 +171,32 @@ You discover new music 🎵
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### slskd hangs and needs a manual restart every ~24h
|
||||||
|
|
||||||
|
This was almost always one of two things, both addressed in the current compose file:
|
||||||
|
|
||||||
|
- **slskd itself getting stuck** (memory pressure / stuck transfers) — fixed by pinning `slskd/slskd:0.25.1`, which bundles a round of upstream fixes for stuck and failing transfers.
|
||||||
|
- **gluetun's tunnel silently hanging** — since slskd runs inside gluetun's network namespace, a frozen gluetun looks identical to a frozen slskd. Fixed by pinning `qmcgaw/gluetun:v3.41.1`, which resolves a healthcheck race condition that could make gluetun hang completely.
|
||||||
|
|
||||||
|
Both containers already ship a Docker healthcheck, but **Docker does not restart a container just because it's unhealthy** — that gap is why a manual restart was needed. The new `autoheal` service closes it: it watches both containers (via the `autoheal=true` label) and force-restarts whichever one goes unhealthy, with no manual intervention.
|
||||||
|
|
||||||
|
If slskd still misbehaves after this, check `docker ps` for its health status and `docker logs slskd`/`docker logs gluetun` before restarting manually.
|
||||||
|
|
||||||
|
### Weekly Exploration downloaded multiple times
|
||||||
|
|
||||||
|
The old setup ran two one-shot `explo-weekly` / `explo-daily` containers (`restart: "no"`, `EXECUTE_ON_START=true`) that were triggered by an external Unraid User Scripts cron. That design had two compounding problems:
|
||||||
|
|
||||||
|
1. Every container restart (a host reboot, a Docker update, the scheduler firing slightly out of sync) re-ran Explo immediately because of `EXECUTE_ON_START=true`, regardless of whether that week's playlist already existed.
|
||||||
|
2. Explo's playlist cache had nowhere to persist between runs (no `/opt/explo/config` volume), so each fresh container had no memory of "I already built this week's playlist" and would build it again.
|
||||||
|
|
||||||
|
The current compose runs a single, always-on `explo` container with its own internal cron (`WEEKLY_EXPLORATION_SCHEDULE` / `DAILY_JAMS_SCHEDULE`), `EXECUTE_ON_START=false`, and a persistent `/opt/explo/config` volume, plus the newer `v1.1.0` image which includes further upstream fixes for scheduled-job bugs. Together this means Explo only ever runs on its own schedule, and remembers what it already did across restarts.
|
||||||
|
|
||||||
|
**One-time cleanup:** this fix prevents *future* duplicates, but it won't retroactively merge the duplicate `Weekly-Exploration-2026-WeekXX` playlists already sitting in Navidrome. Delete the extras once, manually, from the Navidrome UI (or via its API) — new runs going forward should stay to one playlist per period.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
- **ListenBrainz needs time** — it takes a few weeks of scrobbling before Weekly Exploration playlists are generated. Speed this up by importing your Google Takeout YouTube Music watch history via [ytm-extractor](https://community.metabrainz.org/t/ytm-extractor-import-youtube-music-listens-to-listenbrainz/707619).
|
- **ListenBrainz needs time** — it takes a few weeks of scrobbling before Weekly Exploration playlists are generated. Speed this up by importing your Google Takeout YouTube Music watch history via [ytm-extractor](https://community.metabrainz.org/t/ytm-extractor-import-youtube-music-listens-to-listenbrainz/707619).
|
||||||
@@ -189,4 +210,4 @@ You discover new music 🎵
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT — do whatever you want with it.
|
MIT — do whatever you want with it.
|
||||||
+46
-20
@@ -1,7 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
gluetun:
|
gluetun:
|
||||||
image: qmcgaw/gluetun:latest
|
image: qmcgaw/gluetun:v3.41.1
|
||||||
container_name: gluetun
|
container_name: gluetun
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
@@ -14,14 +13,20 @@ services:
|
|||||||
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
|
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
|
||||||
- SERVER_COUNTRIES=${VPN_COUNTRY:-Switzerland}
|
- SERVER_COUNTRIES=${VPN_COUNTRY:-Switzerland}
|
||||||
- TZ=${TZ:-Europe/Zurich}
|
- TZ=${TZ:-Europe/Zurich}
|
||||||
|
- VPN_PORT_FORWARDING=${VPN_PORT_FORWARDING:-off}
|
||||||
|
- VPN_PORT_FORWARDING_PROVIDER=protonvpn
|
||||||
|
- PORT_FORWARD_ONLY=${VPN_PORT_FORWARDING:-off}
|
||||||
|
- VPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port
|
||||||
ports:
|
ports:
|
||||||
- 5030:5030
|
- 5030:5030
|
||||||
|
- 8000:8000
|
||||||
volumes:
|
volumes:
|
||||||
- ${APPDATA_PATH}/gluetun:/gluetun
|
- ${APPDATA_PATH}/gluetun:/gluetun
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- autoheal=true
|
||||||
slskd:
|
slskd:
|
||||||
image: slskd/slskd:latest
|
image: slskd/slskd:0.25.1
|
||||||
container_name: slskd
|
container_name: slskd
|
||||||
network_mode: "service:gluetun"
|
network_mode: "service:gluetun"
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -34,27 +39,48 @@ services:
|
|||||||
- ${MUSIC_PATH}:/downloads
|
- ${MUSIC_PATH}:/downloads
|
||||||
- ${MUSIC_PATH}:/music:ro
|
- ${MUSIC_PATH}:/music:ro
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
explo-weekly:
|
- autoheal=true
|
||||||
image: ghcr.io/lumepart/explo:latest
|
autoheal:
|
||||||
container_name: explo-weekly
|
image: willfarrell/autoheal:latest
|
||||||
|
container_name: autoheal
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- EXECUTE_ON_START=true
|
- AUTOHEAL_CONTAINER_LABEL=autoheal
|
||||||
|
- AUTOHEAL_INTERVAL=30
|
||||||
|
- AUTOHEAL_START_PERIOD=120
|
||||||
|
- AUTOHEAL_DEFAULT_STOP_TIMEOUT=30
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
explo:
|
||||||
|
image: ghcr.io/lumepart/explo:v1.1.0
|
||||||
|
container_name: explo
|
||||||
|
environment:
|
||||||
|
- TZ=${TZ:-Europe/Zurich}
|
||||||
|
- WEB_UI=false
|
||||||
|
- EXECUTE_ON_START=false
|
||||||
|
- WEEKLY_EXPLORATION_SCHEDULE=15 0 * * 2
|
||||||
- WEEKLY_EXPLORATION_FLAGS=--playlist=weekly-exploration --persist
|
- WEEKLY_EXPLORATION_FLAGS=--playlist=weekly-exploration --persist
|
||||||
|
- WEEKLY_JAMS_SCHEDULE=30 0 * * 1
|
||||||
|
- WEEKLY_JAMS_FLAGS=--playlist=weekly-jams --persist
|
||||||
|
- DAILY_JAMS_SCHEDULE=15 1 * * *
|
||||||
|
- DAILY_JAMS_FLAGS=--playlist=daily-jams --persist
|
||||||
volumes:
|
volumes:
|
||||||
- ${APPDATA_PATH}/explo/.env:/opt/explo/.env
|
- ${APPDATA_PATH}/explo/.env:/opt/explo/.env
|
||||||
|
- ${APPDATA_PATH}/explo/config:/opt/explo/config
|
||||||
- ${MUSIC_PATH}/explo:/data
|
- ${MUSIC_PATH}/explo:/data
|
||||||
- ${MUSIC_PATH}:/slskd
|
- ${MUSIC_PATH}:/slskd
|
||||||
restart: "no"
|
restart: unless-stopped
|
||||||
|
lidarr:
|
||||||
explo-daily:
|
image: lscr.io/linuxserver/lidarr:latest
|
||||||
image: ghcr.io/lumepart/explo:latest
|
container_name: lidarr
|
||||||
container_name: explo-daily
|
|
||||||
environment:
|
environment:
|
||||||
- EXECUTE_ON_START=true
|
- PUID=${PUID:-99}
|
||||||
- DAILY_JAMS_FLAGS=--playlist=daily-jams --download-mode=skip
|
- PGID=${PGID:-100}
|
||||||
|
- TZ=${TZ:-Europe/Zurich}
|
||||||
volumes:
|
volumes:
|
||||||
- ${APPDATA_PATH}/explo/.env:/opt/explo/.env
|
- ${APPDATA_PATH}/lidarr:/config
|
||||||
- ${MUSIC_PATH}/explo:/data
|
- ${MUSIC_PATH}:/music
|
||||||
- ${MUSIC_PATH}:/slskd
|
ports:
|
||||||
restart: "no"
|
- 8686:8686
|
||||||
|
restart: unless-stopped
|
||||||
Reference in New Issue
Block a user