Not sure how to update this. Getting closer I think
This is the error I am getting trying to deploy. I'm using the web editor and deploying the stack. Is that the right place? Should I put it in a different place?
Failed to deploy a stack: failed to load the compose file: service "fruitdeeplinks" refers to undefined volume volume1/docker/FruitDeepLinks/data: invalid compose project
I've tried it both with volume1/data & with volume1/docker with same result.
Here's my docker-compose.yml
version: '3.8'
services:
fruitdeeplinks:
build: .
container_name: fruitdeeplinks
hostname: fruitdeeplinks
shm_size: '2gb'
environment:
# Timezone
- TZ=${TZ:-America/Chicago}
# Core URLs and ports
- SERVER_URL=${SERVER_URL:-http://10.0.1.65:6655}
- FRUIT_HOST_PORT=${FRUIT_HOST_PORT:-6655}
# Channels DVR integration (optional)
- CHANNELS_DVR_IP=${CHANNELS_DVR_IP:-10.0.1.65}
- CHANNELS_SOURCE_NAME=${CHANNELS_SOURCE_NAME:-fruitdeeplinks}
# Database and output paths (inside container)
- FRUIT_DB_PATH=${FRUIT_DB_PATH:-/app/data/fruit_events.db}
- OUT_DIR=${OUT_DIR:-/app/out}
- LOG_DIR=${LOG_DIR:-/app/logs}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
# Lane configuration (BETA)
- FRUIT_LANES=${FRUIT_LANES:-50}
- FRUIT_LANE_START_CH=${FRUIT_LANE_START_CH:-9000}
- FRUIT_DAYS_AHEAD=${FRUIT_DAYS_AHEAD:-7}
- FRUIT_PADDING_MINUTES=${FRUIT_PADDING_MINUTES:-45}
- FRUIT_PLACEHOLDER_BLOCK_MINUTES=${FRUIT_PLACEHOLDER_BLOCK_MINUTES:-60}
- FRUIT_PLACEHOLDER_EXTRA_DAYS=${FRUIT_PLACEHOLDER_EXTRA_DAYS:-5}
# Scraper behavior
- HEADLESS=${HEADLESS:-true}
- NO_NETWORK=${NO_NETWORK:-false}
# Auto-refresh
- AUTO_REFRESH_ENABLED=${AUTO_REFRESH_ENABLED:-true}
- AUTO_REFRESH_TIME=${AUTO_REFRESH_TIME:-02:30}
volumes:
- ${HOST_DIR}/FruitDeepLinks/data:/app/data
- ${HOST_DIR}/FruitDeepLinks/out:/app/out
- ${HOST_DIR}/FruitDeepLinks/logs:/app/logs
# templates are baked into the image via Dockerfile (COPY templates ./templates)
# so we don't need a bind mount here
restart: unless-stopped
ports:
# host:container
# FRUIT_HOST_PORT controls the HOST port; default 6655.
# Inside the container we always listen on 6655.
- "${FRUIT_HOST_PORT:-6655}:6655"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
You’re correct on that. I checked the files thru File Station and all of the docker-compose.ylm files for my containers are there.
Create the following directory using File Station
/volume1/docker/FruitDeepLinks
Set the environment variable HOST_DIR=/volume1/docker
That should work.
Nope that doesn't work. Gonna have to think about it overnight.
Here's my recommendation for a stack, along with the minimum env var overrides for Portainer:
services:
fruitdeeplinks:
image: ghcr.io/kineticman/fruitdeeplinks:${TAG:-latest}
container_name: fruitdeeplinks
hostname: fruitdeeplinks
shm_size: '2gb'
ports:
- ${FRUIT_HOST_PORT:-6655}:6655
environment:
- TZ=${TZ:-America/Chicago}
- SERVER_URL=${SERVER_URL:-http://10.0.1.65:6655}
- FRUIT_HOST_PORT=${FRUIT_HOST_PORT:-6655}
- CHANNELS_DVR_IP=${CHANNELS_DVR_IP:-10.0.1.65}
- CHANNELS_SOURCE_NAME=${CHANNELS_SOURCE_NAME:-fruitdeeplinks}
- FRUIT_DB_PATH=${FRUIT_DB_PATH:-/app/data/fruit_events.db}
- OUT_DIR=${OUT_DIR:-/app/out}
- LOG_DIR=${LOG_DIR:-/app/logs}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- FRUIT_LANES=${FRUIT_LANES:-50}
- FRUIT_LANE_START_CH=${FRUIT_LANE_START_CH:-9000}
- FRUIT_DAYS_AHEAD=${FRUIT_DAYS_AHEAD:-7}
- FRUIT_PADDING_MINUTES=${FRUIT_PADDING_MINUTES:-45}
- FRUIT_PLACEHOLDER_BLOCK_MINUTES=${FRUIT_PLACEHOLDER_BLOCK_MINUTES:-60}
- FRUIT_PLACEHOLDER_EXTRA_DAYS=${FRUIT_PLACEHOLDER_EXTRA_DAYS:-5}
- HEADLESS=${HEADLESS:-true}
- NO_NETWORK=${NO_NETWORK:-false}
- AUTO_REFRESH_ENABLED=${AUTO_REFRESH_ENABLED:-true}
- AUTO_REFRESH_TIME=${AUTO_REFRESH_TIME:-02:30}
volumes:
- ${HOST_DIR:-.}/FruitDeepLinks/data:/app/data
- ${HOST_DIR:-.}/FruitDeepLinks/out:/app/out
- ${HOST_DIR:-.}/FruitDeepLinks/logs:/app/logs
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
max-file: 3
Sample env var overrides (everything else will use the compose-specified defaults):
TZ=US/Mountain
SERVER_URL=http://htpc6:6655
CHANNELS_DVR_IP=media-server8
CHANNELS_SOURCE_NAME=FruitDeepLinks
FRUIT_LANE_START_CH=14001
HOST_DIR=/data
Synology users should specify HOST_DIR=/volume1/docker, and the following directories need to be created BEFORE spinning up the container for the first time:
/volume1/docker/FruitDeepLinks/data
/volume1/docker/FruitDeepLinks/out
/volume1/docker/FruitDeepLinks/logs
I set this up using the above compose, and this CDVR Custom Channels setup:
Only one live event atm, but it worked on a FireStick! It was an ESPN event, so it didn't jump to live (as we've seen before) -- but given it's a streamlink, one can choose to watch from start or jump to live with the remote.
I'll start the process of getting this working with ADBTuner...
EDIT: A second event is live now, and that worked too. Brilliant work @KineticMan!
I'm working on the ADBTuner integration, and I'm not getting a response from the adb endpoints:
[13/Dec/2025 10:27:27] "GET /api/adb/lanes/sportscenter/1/deeplink?format=text HTTP/1.1" 404 -
Even though the API is responding otherwise:
Lane Schedule:
{"lane_id":1,"schedule":[{"channel_name":null,"chosen_deeplink":null,"chosen_logical_service":null,"chosen_playable_id":null,"chosen_provider":null,"end_utc":"2025-12-13T10:40:00+00:00","event_id":"placeholder-1-2025-12-13T10:00:00+00:00","is_placeholder":1,"lane_id":1,"start_utc":"2025-12-13T10:00:00+00:00","synopsis":null,"title":"Nothing Scheduled"},{"channel_name":"ESPN","chosen_deeplink":"sportscenter://x-callback-url/showWatchStream?playID=e1282492-9f76-406e-a1c3-ad420cd5a4da&x-source=AppleUMC","chosen_logical_service":"sportscenter","chosen_playable_id":"tvs.sbd.30061:e2ccc613-7bb0-465b-9d66-d196509d1f39:8f9005cf","chosen_provider":"sportscenter","end_utc":"2025-12-13T13:35:00+00:00","event_id":"appletv-umc.cse.6dighebtp9bnx0uipa8op9emt","is_placeholder":0,"lane_id":1,"start_utc":"2025-12-13T10:40:00+00:00","synopsis":"Soccer - (Australia A-League) - Perth Glory vs Sydney - Available on ESPN","title":"Australia A-League: Perth Glory vs. Sydney"},{"channel_name":null,"chosen_deeplink":null,"chosen_logical_service":null,"chosen_playable_id":null,"chosen_provider":null,"end_utc":"2025-12-13T14:00:00+00:00","event_id":"placeholder-1-2025-12-13T13:35:00+00:00","is_placeholder":1,"lane_id":1,"start_utc":"2025-12-13T13:35:00+00:00","synopsis":null,"title":"Nothing Scheduled"},{"channel_name":"NBC Sports","chosen_deeplink":"aiv://aiv/detail?gti=amzn1.dv.gti.349a1531-c3a7-431f-b91d-2166a0b68df0&action=watch&type=live&territory=US&time=live&broadcast=amzn1.dv.gti.1305ec30-27c9-4a01-97ee-1a453c426247&refMarker=atv_dvm_liv_apl_us_bd_l_src_av","chosen_logical_service":"aiv","chosen_playable_id":"tvs.sbd.12962:amzn1.dv.gti.1305ec30-27c9-4a01-97ee-1a453c426247:be5a5d82","chosen_provider":"aiv","end_utc":"2025-12-13T15:50:00+00:00","event_id":"appletv-umc.cse.2gsagwhyq83fwecrscm8ngeuv","is_placeholder":0,"lane_id":1,"start_utc":"2025-12-13T14:00:00+00:00","synopsis":"Soccer - (Premier League) - Chelsea vs Everton - Available on NBC Sports","title":"Premier League: Chelsea vs. Everton"},{"channel_name":null,"chosen_deeplink":null,"chosen_logical_service":null,"chosen_playable_id":null,"chosen_provider":null,"end_utc":"2025-12-13T16:25:00+00:00","event_id":"placeholder-1-2025-12-13T15:50:00+00:00","is_placeholder":1,"lane_id":1,"start_utc":"2025-12-13T15:50:00+00:00","synopsis":null,"title":"Nothing Scheduled"},{"channel_name":null,"chosen_deeplink":"aiv://aiv/detail?gti=amzn1.dv.gti.eb6ebe9e-43b1-4334-9c5f-1c0ec70b9b43&action=watch&type=live&territory=US&time=live&broadcast=amzn1.dv.gti.64054384-2c72-4252-b509-e0c0654a661a&refMarker=atv_dvm_liv_apl_us_bd_l_src_av","chosen_logical_service":"aiv","chosen_playable_id":"tvs.sbd.12962:amzn1.dv.gti.64054384-2c72-4252-b509-e0c0654a661a:77e31f16","chosen_provider":"aiv","end_utc":"2025-12-13T20:50:00+00:00","event_id":"appletv-umc.cse.365y0s95mo6z3om5348jnvsf4","is_placeholder":0,"lane_id":1,"start_utc":"2025-12-13T16:25:00+00:00","synopsis":"Basketball - (Men's College Basketball) - Iona Gaels vs St. John's Red Storm","title":"Men's College Basketball: Iona Gaels at #22 St. John's Red Storm"},{"channel_name":null,"chosen_deeplink":null,"chosen_logical_service":null,"chosen_playable_id":null,"chosen_provider":null,"end_utc":"2025-12-13T21:00:00+00:00","event_id":"placeholder-1-2025-12-13T20:50:00+00:00","is_placeholder":1,"lane_id":1,"start_utc":"2025-12-13T20:50:00+00:00","synopsis":null,"title":"Nothing Scheduled"},{"channel_name":null,"chosen_deeplink":"sportscenter://x-callback-url/showWatchStream?playID=068dd4e0-ee81-49e4-92eb-5d7733f79d3b&x-source=AppleUMC","chosen_logical_service":"sportscenter","chosen_playable_id":"tvs.sbd.30061:fe91cfcf-f709-4e0c-9ccd-411a1152272d:1cfca2fd","chosen_provider":"sportscenter","end_utc":"2025-12-13T23:50:00+00:00","event_id":"appletv-umc.cse.3b6s3djr5b1hsl58kvtk8ree9","is_placeholder":0,"lane_id":1,"start_utc":"2025-12-13T21:00:00+00:00","synopsis":"Basketball - (Men's College Basketball)","title":"Men's College Basketball: Bethel (TN) Wildcats at Tennessee Tech Golden Eagles"},{"channel_name":null,"chosen_deeplink":null,"chosen_logical_service":null,"chosen_playable_id":null,"chosen_provider":null,"end_utc":"2025-12-14T00:00:00+00:00","event_id":"placeholder-1-2025-12-13T23:50:00+00:00","is_placeholder":1,"lane_id":1,"start_utc":"2025-12-13T23:50:00+00:00","synopsis":null,"title":"Nothing Scheduled"},{"channel_name":null,"chosen_deeplink":"sportscenter://x-callback-url/showWatchStream?playID=8af891ec-5bce-44be-9cb5-aa979be41a74&x-source=AppleUMC","chosen_logical_service":"sportscenter","chosen_playable_id":"tvs.sbd.30061:e53427c2-66c4-4264-bafe-c60db6bec248:9cdf05cd","chosen_provider":"sportscenter","end_utc":"2025-12-14T02:50:00+00:00","event_id":"appletv-umc.cse.5svt7a12hiz5la3ywo9cxp8ik","is_placeholder":0,"lane_id":1,"start_utc":"2025-12-14T00:00:00+00:00","synopsis":"Basketball - (Men's College Basketball)","title":"Men's College Basketball: Simpson University (CA) at Pacific Tigers"}]}
Provider Lanes:
{"providers":[{"adb_enabled":1,"adb_lane_count":18,"created_at":"2025-12-13 09:40:22","provider_code":"aiv","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"apple_mls","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"cbssportsapp","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"cbstve","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"foxone","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"fsapp","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":1,"adb_lane_count":2,"created_at":"2025-12-13 09:40:22","provider_code":"gametime","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"nbcsportstve","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"nflctv","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"open.dazn.com","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"peacock_web","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"pplus","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":1,"adb_lane_count":30,"created_at":"2025-12-13 09:40:22","provider_code":"sportscenter","updated_at":"2025-12-13 09:40:22"},{"adb_enabled":0,"adb_lane_count":0,"created_at":"2025-12-13 09:40:22","provider_code":"vixapp","updated_at":"2025-12-13 09:40:22"}],"status":"success"}
I knew you'd push me along! I actually didn't add the endpoint just yet for that API response. I'll get it out.
Glad you like it - your ADB integration stuff will be the magic sauce for this project.




Thanks for the help. Stack deployed successfully and loaded into Channels. It sure does work better when you include the image info!!!!
I increased the lanes to 100 since it wasn’t listing a game that I was expecting later today. It's there with the increased lanes. Will play a little once the games begin today.
@KineticMan Thanks for you effort in putting this project together. Your assistance was greatly appreciated!!!
If you could share your exact compose and any env variables, I’d like to add to repo to docs to help others.
Also let me know the game your missing- it is theoretically possible for the scrape to miss an event. Fruit company provides the events in a webpage shelf of like 28 events at a time. The scraper keeps refreshing shelves with new search terms until it gets no new hits. So basically ain’t perfect but best I could figure out.
I grabbed your docker-compose.yml from github and made some environment variable changes. What I was missing was in the image info.
Final Compose:
services:
fruitdeeplinks:
image: ghcr.io/kineticman/fruitdeeplinks:${TAG:-latest}
container_name: fruitdeeplinks
hostname: fruitdeeplinks
shm_size: '2gb'
ports:
- ${FRUIT_HOST_PORT:-6655}:6655
environment:
- TZ=${TZ:-America/Chicago}
- SERVER_URL=${SERVER_URL:-http://10.0.1.65:6655}
- FRUIT_HOST_PORT=${FRUIT_HOST_PORT:-6655}
- CHANNELS_DVR_IP=${CHANNELS_DVR_IP:-10.0.1.65}
- CHANNELS_SOURCE_NAME=${CHANNELS_SOURCE_NAME:-fruitdeeplinks}
- FRUIT_DB_PATH=${FRUIT_DB_PATH:-/app/data/fruit_events.db}
- OUT_DIR=${OUT_DIR:-/app/out}
- LOG_DIR=${LOG_DIR:-/app/logs}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- FRUIT_LANES=${FRUIT_LANES:-50}
- FRUIT_LANE_START_CH=${FRUIT_LANE_START_CH:-9000}
- FRUIT_DAYS_AHEAD=${FRUIT_DAYS_AHEAD:-7}
- FRUIT_PADDING_MINUTES=${FRUIT_PADDING_MINUTES:-45}
- FRUIT_PLACEHOLDER_BLOCK_MINUTES=${FRUIT_PLACEHOLDER_BLOCK_MINUTES:-60}
- FRUIT_PLACEHOLDER_EXTRA_DAYS=${FRUIT_PLACEHOLDER_EXTRA_DAYS:-5}
- HEADLESS=${HEADLESS:-true}
- NO_NETWORK=${NO_NETWORK:-false}
- AUTO_REFRESH_ENABLED=${AUTO_REFRESH_ENABLED:-true}
- AUTO_REFRESH_TIME=${AUTO_REFRESH_TIME:-02:30}
volumes:
- ${HOST_DIR:-.}/FruitDeepLinks/data:/app/data
- ${HOST_DIR:-.}/FruitDeepLinks/out:/app/out
- ${HOST_DIR:-.}/FruitDeepLinks/logs:/app/logs
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
max-file: 3
Environment Variables:
TZ=US/Central
SERVER_URL=http://10.0.1.65:6655
CHANNELS_DVR_IP=http://10.0.1.65
CHANNELS_SOURCE_NAME=FruitDeepLinks
FRUIT_LANE_START_CH=14001
HOST_DIR=/volume1/docker
FRUIT_LANES=100
The game I was missing was Louisiana Ragin Cajuns vs Louisiana Tech (Today @2:00 pm CT). When I increased lanes to 100 it appeared in lane 67. I'm not sure how many lanes I can actually designate. When I was using EPlusTv, I was using ±350 channels to ensure everything I was wanting to record/watch was available.
One question, should the Channels_DVR_IP have the 8089 port specified. (10.0.1.65:8089 vs 10.0.1.65)
So just went thru 8 streams. All opened the ESPN without issue. Some tuned directly to the game and others I had to select the game to tune. On games that did not tune directly, I had to use the home button to get back to the Home Screen.
Arkansas vs Texas Tech: Tuned to app. Had to select game. Required Home button to get to Home Screen.
Albany vs Fla. Atlantic: Tuned to game @start. Backed out to Home Screen.
DePaul vs Wichita State: Had to select game. Had to use Home Button.
Mass vs Florida State: Had to select game start. Had to use Home Button.
ULM vs Miami: Tuned to game start. Backed out to Home Screen.
Howard Bison vs Hampton: Tuned to game start. Backed out to Home Screen.
Delaware State vs Georgetown. Tuned to game start. Backed out to Home Screen.
Cal State vs Delaware: Tuned to game start. Backed out to Home Screen.
What's your CDVR client device? What does your CDVR Custom Source config look like?
On the ones that didn't tune, any idea if they were marked as Live in the app? ESPN deeplinks don't work for Re-Airs on FireTV devices...
good point on re-airs... i'm not 100% sure if the apple scrape has that info like the direct ESPN API scraper did. if through this beta process we determine ReAirs are sneaking in, i'll of course dig in further to see if the fruit scrape has that info, but if it doesn't, i could in theory just change the scrape to use ESPN api for the DB (for ESPN events) and filter out those "re-airs". i tried to make this as modular as possible
All were live events. All 3 events that only opened the app were on the normal ESPN linear channels. I'll try and check some other games that are scheduled on both linear and FruitDeepLinks and see if that behavior is consistent.
I’d check now but I’m watching an ESPN+ event.
From the Apple (UMC) scrape, the ESPN/SportsCenter deep links consistently show up as sportscenter://x-callback-url/showWatchStream?...&x-source=AppleUMC, and they split into two distinct forms: asset-specific links with playID=<uuid> and linear-network links with playChannel=<network>. When we counted the Apple punchouts, ~75% were playID and ~25% were playChannel. The playChannel values were a small, consistent set that maps to ESPN’s linear brands (e.g., espn1, espn2, espnu, espnews, sec, acc, espndeportes). This suggests Apple is surfacing two different “play” concepts: open a specific stream vs tune a channel.
On the ESPN direct-API side that I used on ESPN4CC4C, the dataset shows a very similar divide in practice: the stuff that tends to be problematic for direct playback (“Re-Air”/replay-style items) overwhelmingly behaves like linear-network inventory rather than ESPN+ stream assets. In the DB extract, ~28% of events are flagged is_reair=1, and essentially all of those re-air events have no ESPN+ package attached (i.e., not ESPN_PLUS). That lines up with the Apple observation that playChannel is “network-like” content and may not reliably start playback depending on device/auth/provider context.
Putting both scrapes together: the working hypothesis is that Apple playID links correspond more often to stream-asset playback (higher success rate), while playChannel links correspond to linear tuning (more fragile / sometimes launch-only).
If folks have seen different behavior on Android/Fire vs iOS, I’d love to compare notes—especially whether playChannel ever reliably jumps straight into playback on non-iOS devices. I could add filtering to clearly note the playChannel vs playID (likely ESPN3 events). The playChannel events may not be appropriate for ADBTuner-- unless we figure out some specific keystroke to open the event as direct deeplink doesn't join the event live.
added API endpoint support (beta)
GET /api/adb/lanes/sportscenter/1/deeplink?format=text

