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" 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}}' >/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 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