[Unit] Description=ytstream just-in-time YouTube streaming proxy Documentation=file:///opt/ytstream/plan.md After=network-online.target docker.service Wants=network-online.target # The bgutil POT provider runs in Docker on 127.0.0.1:4416 and yt-dlp cannot # fetch anything without it. Requires=docker.service [Service] Type=simple # Group=mediaserver and UMask=0002 are load-bearing: Jellyfin reaches the media # tree only through the mediaserver group. See plan.md ยง9. User=susan Group=mediaserver UMask=0002 # LOAD-BEARING. The only yt-dlp with the bgutil POT plugin is the one in this # venv. /usr/local/bin/yt-dlp is a 2023.11.16 binary that cannot talk to YouTube # at all, and if it wins the PATH race every playback fails with no clear reason. Environment=PATH=/var/lib/ytstream/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Environment=PYTHONUNBUFFERED=1 # No --first-byte-grace: requests wait for the COMPLETE mux, bounded by # --wait-timeout. Do not add a grace here without reading FIRST_BYTE_GRACE in the # proxy. Short version, both measured 2026-08-13: waiting sends nothing for ~80s # on a 46-minute upload and the client gives up, but serving the partial file # instead makes Jellyfin drag a gigabyte through this proxy to probe a fragmented # MP4 that has no duration in its header, and then TRANSCODE a stream it cannot # seek -- 3 minutes to start, 6 seconds of video, then a permanent stall. # A slow cold start is the better failure: the fetch outlives the request, so the # retry is instant. --wait-timeout is 600 to cover a 2-hour upload (2.4 GB). WorkingDirectory=/opt/ytstream ExecStart=/var/lib/ytstream/venv/bin/python3 /opt/ytstream/proxy/ytstream_proxy.py \ --host 127.0.0.1 --port 8099 \ --work /dev/shm/ytstream \ --cache-gb 8 \ --max-pipelines 2 \ --max-retries 2 \ --max-starts 20 --starts-window 3600 \ --wait-timeout 600 Restart=always RestartSec=5 NoNewPrivileges=true PrivateTmp=true ProtectSystem=full ProtectControlGroups=true ProtectKernelTunables=true RestrictSUIDSGID=true # /dev/shm is the cache; nothing else needs to be writable. ReadWritePaths=/dev/shm [Install] WantedBy=multi-user.target