services :
postgres :
image : postgres:18
container_name : synctv-postgres-dev
environment :
POSTGRES_USER : synctv
POSTGRES_PASSWORD : synctv
POSTGRES_DB : synctv
ports :
- "127.0.0.1:5432:5432"
volumes :
- postgres_dev_data:/var/lib/postgresql
healthcheck :
test : [ "CMD-SHELL" , "pg_isready -U synctv" ]
interval : 5s
timeout : 5s
retries : 10
start_period : 20s
networks :
- synctv-dev
restart : unless-stopped
redis :
image : redis:8
container_name : synctv-redis-dev
ports :
- "127.0.0.1:6379:6379"
volumes :
- redis_dev_data:/data
command : redis-server --appendonly yes
healthcheck :
test : [ "CMD" , "redis-cli" , "ping" ]
interval : 5s
timeout : 3s
retries : 10
networks :
- synctv-dev
restart : unless-stopped
rustfs :
image : rustfs/rustfs:latest-glibc
container_name : synctv-rustfs-dev
profiles : [ "storage" ]
environment :
RUSTFS_ACCESS_KEY : rustfsadmin
RUSTFS_SECRET_KEY : rustfsadmin
RUSTFS_CONSOLE_ENABLE : "true"
ports :
- "127.0.0.1:9000:9000"
- "127.0.0.1:9001:9001"
volumes :
- rustfs_dev_data:/data
command : rustfs /data --address 0.0.0.0:9000 --console-address 0.0.0.0:9001
networks :
- synctv-dev
restart : unless-stopped
rustfs-init :
image : minio/mc:latest
container_name : synctv-rustfs-init-dev
profiles : [ "storage" ]
depends_on :
rustfs :
condition : service_started
entrypoint :
- /bin/sh
- -ec
- |
until mc alias set rustfs http://rustfs:9000 rustfsadmin rustfsadmin; do
sleep 2
done
mc mb --ignore-existing rustfs/synctv-dev
mc anonymous set download rustfs/synctv-dev
networks :
- synctv-dev
restart : "no"
openlist :
image : openlistteam/openlist:latest
container_name : synctv-openlist-dev
profiles : [ "media" ]
user : "0:0"
environment :
UMASK : "022"
OPENLIST_ADMIN_PASSWORD : synctv-openlist
ports :
- "127.0.0.1:5244:5244"
volumes :
- openlist_dev_data:/opt/openlist/data
- openlist_dev_media:/opt/openlist/dev-media
healthcheck :
test : [ "CMD-SHELL" , "wget -q --spider http://127.0.0.1:5244/ || exit 1" ]
interval : 10s
timeout : 5s
retries : 20
start_period : 20s
networks :
- synctv-dev
restart : unless-stopped
openlist-init :
image : curlimages/curl:latest
container_name : synctv-openlist-init-dev
profiles : [ "media" ]
user : "0:0"
depends_on :
openlist :
condition : service_healthy
volumes :
- openlist_dev_media:/opt/openlist/dev-media
entrypoint :
- /bin/sh
- -ec
- |
mkdir -p /opt/openlist/dev-media
if [ ! -f /opt/openlist/dev-media/hello.txt ]; then
printf "hello from openlist dev media\n" > /opt/openlist/dev-media/hello.txt
fi
token="$$(curl -fsS -X POST http://openlist:5244/api/auth/login \
-H 'Content-Type : application/json' \
-d '{"username":"admin","password":"synctv-openlist"}' \
| sed -n 's/.*"token":"\([^"]*\)".*/\1/p')"
if [ -z "$$token" ]; then
printf "OpenList admin login did not return a token.\n" >&2
exit 1
fi
if curl -fsS -H "Authorization: $$token" http://openlist:5244/api/admin/storage/list \
| grep -q '"mount_path":"/"'; then
curl -fsS -X POST -H "Authorization: $$token" http://openlist:5244/api/admin/storage/load_all
else
curl -fsS -X POST http://openlist:5244/api/admin/storage/create \
-H "Authorization: $$token" \
-H 'Content-Type : application/json' \
-d '{"driver":"Local","mount_path":"/","order":0,"remark":"SyncTV dev media","addition":"{\"root_folder_path\":\"/opt/openlist/dev-media\",\"thumbnail\":false,\"show_hidden\":true,\"mkdir_perm\":\"777\"}"}'
curl -fsS -X POST -H "Authorization: $$token" http://openlist:5244/api/admin/storage/load_all
fi
networks :
- synctv-dev
restart : "no"
nextcloud :
image : nextcloud:apache
container_name : synctv-nextcloud-dev
profiles : [ "media" ]
environment :
NEXTCLOUD_ADMIN_USER : synctv
NEXTCLOUD_ADMIN_PASSWORD : synctv-nextcloud
SQLITE_DATABASE : nextcloud
NEXTCLOUD_TRUSTED_DOMAINS : "localhost 127.0.0.1 nextcloud"
ports :
- "127.0.0.1:8082:80"
volumes :
- nextcloud_dev_data:/var/www/html
healthcheck :
test : [ "CMD-SHELL" , "curl -fsS http://127.0.0.1/status.php | grep -q 'installed'" ]
interval : 10s
timeout : 5s
retries : 30
start_period : 40s
networks :
- synctv-dev
restart : unless-stopped
nextcloud-init :
image : curlimages/curl:latest
container_name : synctv-nextcloud-init-dev
profiles : [ "media" ]
depends_on :
nextcloud :
condition : service_healthy
entrypoint :
- /bin/sh
- -ec
- |
endpoint=http://nextcloud
auth='synctv:synctv-nextcloud'
for path in /Movies /Movies/Nested /Playlists; do
curl -fsS -u "$$auth" -X MKCOL "$$endpoint/remote.php/dav/files/synctv$$path" || true
done
printf 'SyncTV Nextcloud development fixture\n' | curl -fsS -u "$$auth" -T - "$$endpoint/remote.php/dav/files/synctv/README.txt"
curl -fsS -u "$$auth" -X PROPPATCH \
-H 'Content-Type : application/xml; charset=utf-8' \
--data-binary '<propertyupdate xmlns="DAV:" xmlns:oc="http://owncloud.org/ns"><set><prop><oc:favorite>1</oc:favorite></prop></set></propertyupdate>' \
"$$endpoint/remote.php/dav/files/synctv/README.txt" || true
networks :
- synctv-dev
restart : "no"
seafile-db :
image : mariadb:10.11
container_name : synctv-seafile-db-dev
profiles : [ "media" ]
environment :
MARIADB_ROOT_PASSWORD : seafile-db-root
MARIADB_LOG_CONSOLE : "true"
MARIADB_AUTO_UPGRADE : "1"
volumes :
- seafile_dev_mysql:/var/lib/mysql
healthcheck :
test : [ "CMD-SHELL" , "healthcheck.sh --connect --innodb_initialized" ]
interval : 10s
timeout : 5s
retries : 30
start_period : 30s
networks :
- synctv-dev
restart : unless-stopped
seafile-memcached :
image : memcached:1.6-alpine
container_name : synctv-seafile-memcached-dev
profiles : [ "media" ]
command : memcached -m 128
networks :
- synctv-dev
restart : unless-stopped
seafile :
image : seafileltd/seafile-mc:12.0-latest
container_name : synctv-seafile-dev
profiles : [ "media" ]
depends_on :
seafile-db :
condition : service_healthy
seafile-memcached :
condition : service_started
environment :
DB_HOST : seafile-db
DB_ROOT_PASSWD : seafile-db-root
TIME_ZONE : Asia/Shanghai
SEAFILE_SERVER_HOSTNAME : 127.0 .0 .1 : 8083
SEAFILE_SERVER_PROTOCOL : http
INIT_SEAFILE_ADMIN_EMAIL : synctv@example.com
INIT_SEAFILE_ADMIN_PASSWORD : synctv-seafile
JWT_PRIVATE_KEY : synctv-dev-seafile-jwt-private-key-2026
SEAFILE_LOG_TO_STDOUT : "true"
CACHE_PROVIDER : memcached
MEMCACHED_HOST : seafile-memcached
ports :
- "127.0.0.1:8083:80"
volumes :
- seafile_dev_data:/shared
healthcheck :
test : [ "CMD-SHELL" , "curl -fsS http://127.0.0.1/api2/server-info/ | grep -q 'version'" ]
interval : 10s
timeout : 5s
retries : 60
start_period : 90s
networks :
- synctv-dev
restart : unless-stopped
seafile-init :
image : curlimages/curl:latest
container_name : synctv-seafile-init-dev
profiles : [ "media" ]
depends_on :
seafile :
condition : service_healthy
entrypoint :
- /bin/sh
- -ec
- |
endpoint=http://seafile
token="$$(curl -fsS -X POST "$$endpoint/api2/auth-token/" \
-d 'username=synctv@example.com' -d 'password=synctv-seafile' \
| sed -n 's/.*"token":"\([^"]*\)".*/\1/p')"
[ -n "$$token" ]
repos="$$(curl -fsS -H "Authorization: Token $$token" "$$endpoint/api2/repos/")"
if ! printf '%s' "$$repos" | grep -q 'SyncTV Dev Library'; then
curl -fsS -X POST -H "Authorization: Token $$token" \
-d 'name=SyncTV Dev Library' -d 'desc=SyncTV development media fixture' \
"$$endpoint/api2/repos/" >/dev/null
fi
networks :
- synctv-dev
restart : "no"
emby-init :
image : curlimages/curl:latest
container_name : synctv-emby-init-dev
profiles : [ "media" ]
depends_on :
emby :
condition : service_healthy
entrypoint :
- /bin/sh
- -ec
- |
emby=http://emby:8096
auth_header='X-Emby-Authorization : MediaBrowser Client="SyncTVDev", Device="curl", DeviceId="synctv-dev-emby", Version="1"'
until curl -fsS "$$emby/System/Info/Public" >/dev/null; do
sleep 2
done
user="$$(curl -fsS "$$emby/Startup/User" | sed -n 's/.*"Name":"\([^"]*\)".*/\1/p')"
if [ -z "$$user" ]; then
user=MyEmbyUser
fi
auth="$$(curl -fsS -X POST "$$emby/Users/AuthenticateByName" \
-H 'Content-Type : application/json' \
-H "$$auth_header" \
-d "{\"Username\":\"$$user\",\"Pw\":\"\"}" || true)"
if ! printf '%s' "$$auth" | grep -q '"AccessToken"'; then
auth="$$(curl -fsS -X POST "$$emby/Users/AuthenticateByName" \
-H 'Content-Type : application/json' \
-H "$$auth_header" \
-d "{\"Username\":\"$$user\",\"Pw\":\"synctv-emby\"}")"
fi
token="$$(printf '%s' "$$auth" | sed -n 's/.*"AccessToken":"\([^"]*\)".*/\1/p')"
user_id="$$(printf '%s' "$$auth" | sed -n 's/.*"User":{[^}]*"Id":"\([^"]*\)".*/\1/p')"
if [ -z "$$token" ] || [ -z "$$user_id" ]; then
printf "Emby login did not return token and user id.\n" >&2
exit 1
fi
curl -fsS -X POST "$$emby/Users/$$user_id/Password" \
-H "X-Emby-Token: $$token" \
-H 'Content-Type : application/json' \
-d '{"NewPw":"synctv-emby"}' >/dev/null
if ! curl -fsS "$$emby/Library/VirtualFolders" -H "X-Emby-Token: $$token" \
| grep -q '"Name":"SyncTV Dev Media"'; then
curl -fsS -X POST "$$emby/Library/VirtualFolders" \
-H "X-Emby-Token: $$token" \
-H 'Content-Type : application/json' \
-d '{"Name":"SyncTV Dev Media","CollectionType":"homevideos","RefreshLibrary":true,"LibraryOptions":{"ContentType":"homevideos","PathInfos":[{"Path":"/mnt/share1"}],"EnablePhotos":false,"EnableRealtimeMonitor":false,"SampleIgnoreSize":0}}' >/dev/null
fi
curl -fsS -X POST "$$emby/Library/Refresh" -H "X-Emby-Token: $$token" >/dev/null
networks :
- synctv-dev
restart : "no"
emby :
image : emby/embyserver:latest
container_name : synctv-emby-dev
profiles : [ "media" ]
environment :
UID : "1000"
GID : "1000"
GIDLIST : "1000"
ports :
- "127.0.0.1:8096:8096"
- "127.0.0.1:8920:8920"
volumes :
- emby_dev_config:/config
- emby_dev_media:/mnt/share1
healthcheck :
test : [ "CMD-SHELL" , "wget -q --spider http://127.0.0.1:8096/System/Info/Public || exit 1" ]
interval : 10s
timeout : 5s
retries : 30
start_period : 40s
networks :
- synctv-dev
restart : unless-stopped
jellyfin :
image : jellyfin/jellyfin:latest
container_name : synctv-jellyfin-dev
profiles : [ "media" ]
ports :
- "127.0.0.1:8097:8096"
volumes :
- jellyfin_dev_config:/config
- jellyfin_dev_cache:/cache
- jellyfin_dev_media:/media
healthcheck :
test : [ "CMD-SHELL" , "curl -fsS http://127.0.0.1:8096/System/Info/Public >/dev/null || exit 1" ]
interval : 10s
timeout : 5s
retries : 30
start_period : 40s
networks :
- synctv-dev
restart : unless-stopped
jellyfin-init :
image : curlimages/curl:latest
container_name : synctv-jellyfin-init-dev
profiles : [ "media" ]
depends_on :
jellyfin :
condition : service_healthy
entrypoint :
- /bin/sh
- -ec
- |
jellyfin=http://jellyfin:8096
auth_header='Authorization : MediaBrowser Client="SyncTVDev", Device="curl", DeviceId="synctv-dev-jellyfin", Version="1"'
until curl -fsS "$$jellyfin/System/Info/Public" >/dev/null; do
sleep 2
done
curl -fsS -X POST "$$jellyfin/Startup/Configuration" \
-H 'Content-Type : application/json' \
-d '{"ServerName":"SyncTV Jellyfin Dev","UICulture":"en-US","MetadataCountryCode":"US","PreferredMetadataLanguage":"en"}' >/dev/null || true
curl -fsS -X POST "$$jellyfin/Startup/RemoteAccess" \
-H 'Content-Type : application/json' \
-d '{"EnableRemoteAccess":true,"EnableAutomaticPortMapping":false}' >/dev/null || true
user="$$(curl -fsS "$$jellyfin/Startup/FirstUser" | sed -n 's/.*"Name":"\([^"]*\)".*/\1/p')"
if [ -z "$$user" ]; then
user=root
fi
auth="$$(curl -fsS -X POST "$$jellyfin/Users/AuthenticateByName" \
-H 'Content-Type : application/json' \
-H "$$auth_header" \
-d "{\"Username\":\"$$user\",\"Pw\":\"\"}" || true)"
if ! printf '%s' "$$auth" | grep -q '"AccessToken"'; then
auth="$$(curl -fsS -X POST "$$jellyfin/Users/AuthenticateByName" \
-H 'Content-Type : application/json' \
-H "$$auth_header" \
-d "{\"Username\":\"$$user\",\"Pw\":\"synctv-jellyfin\"}")"
fi
token="$$(printf '%s' "$$auth" | sed -n 's/.*"AccessToken":"\([^"]*\)".*/\1/p')"
user_id="$$(printf '%s' "$$auth" | sed -n 's/.*"User":{[^}]*"Id":"\([^"]*\)".*/\1/p')"
if [ -z "$$token" ] || [ -z "$$user_id" ]; then
printf "Jellyfin login did not return token and user id.\n" >&2
exit 1
fi
curl -fsS -X POST "$$jellyfin/Users/Password?userId=$$user_id" \
-H "X-Emby-Token: $$token" \
-H 'Content-Type : application/json' \
-d '{"CurrentPw":"","NewPw":"synctv-jellyfin"}' >/dev/null || true
curl -fsS -X POST "$$jellyfin/Startup/Complete" >/dev/null || true
if ! curl -fsS "$$jellyfin/Library/VirtualFolders" -H "X-Emby-Token: $$token" \
| grep -q '"Name":"SyncTV Dev Media"'; then
curl -fsS -X POST "$$jellyfin/Library/VirtualFolders?name=SyncTV%20Dev%20Media&collectionType=homevideos&paths=/media&refreshLibrary=true" \
-H "X-Emby-Token: $$token" \
-H 'Content-Type : application/json' \
-d '{"LibraryOptions":{"EnablePhotos":false,"EnableRealtimeMonitor":false}}' >/dev/null
fi
curl -fsS -X POST "$$jellyfin/Library/Refresh" -H "X-Emby-Token: $$token" >/dev/null
networks :
- synctv-dev
restart : "no"
casdoor :
image : casbin/casdoor:latest
container_name : synctv-casdoor-dev
profiles : [ "auth" ]
environment :
driverName : postgres
dataSourceName : "user=synctv password=synctv host=postgres port=5432 sslmode=disable dbname=casdoor"
runmode : dev
ports :
- "127.0.0.1:8000:8000"
depends_on :
postgres :
condition : service_healthy
healthcheck :
test : [ "CMD-SHELL" , "wget -q --spider http://127.0.0.1:8000/ || exit 1" ]
interval : 10s
timeout : 5s
retries : 30
start_period : 30s
networks :
- synctv-dev
restart : unless-stopped
volumes :
postgres_dev_data :
driver : local
redis_dev_data :
driver : local
rustfs_dev_data :
driver : local
openlist_dev_data :
driver : local
openlist_dev_media :
driver : local
nextcloud_dev_data :
driver : local
seafile_dev_mysql :
driver : local
seafile_dev_data :
driver : local
emby_dev_config :
driver : local
emby_dev_media :
driver : local
jellyfin_dev_config :
driver : local
jellyfin_dev_cache :
driver : local
jellyfin_dev_media :
driver : local
networks :
synctv-dev :
driver : bridge