yt-dlp 를 반드시 설치해야하는데, pipx로 설치하면 yt-dlp는 보통 ~/.local/bin/yt-dlp 또는 pipx venv 경로에 다
따라서 서비스에 PATH를 명시하고 스크립트에 절대경로를 없애거나 또는 정확한 절대경로로 수정 (/home/ubuntu/.local/bin/yt-dlp) 하면 된다.
[Unit]
Description=HLS live stream author.KJW
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/live-stream
Environment="PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
Environment=PYTHONUNBUFFERED=1
ExecStart=/usr/bin/bash -lc /opt/live-stream/live-stream.sh
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=false
ProtectSystem=full
ReadWritePaths=/var/www/videos/hls/live/720p /opt/live-stream
StandardOutput=journal
StandardError=inherit
[Install]
WantedBy=multi-user.target
권한/마운트 이슈를 우회하고 진단도 쉬운 형태로 설정.
ExecStart=/usr/bin/bash -lc /opt/live-stream/live-stream.sh
참고로 -l(login)은 /etc/profile 등을 읽어서 PATH가 더 풍부해지지만, 사용자 셸 설정(~/.bashrc)은 기본으론 안 읽힐 수 있다. 그래서 서비스 파일에서 Environment=PATH=...를 명시하는 것이다.
#!/usr/bin/env bash
set -Eeuo pipefail
VIDEO_URL="${VIDEO_URL:-https://www.youtube.com/watch?v=채널_id}"
COOKIES="${COOKIES:-/opt/live-stream/cookies.txt}"
OUTPUT_DIR="${OUTPUT_DIR:-/var/www/videos/hls/live/720p}"
YTDLP_BIN="${YTDLP_BIN:-$(command -v yt-dlp)}"
FFMPEG_BIN="${FFMPEG_BIN:-$(command -v ffmpeg)}"
if [[ -z "${YTDLP_BIN}" ]]; then
echo "yt-dlp not found in PATH" >&2; exit 127
fi
if [[ -z "${FFMPEG_BIN}" ]]; then
echo "ffmpeg not found in PATH" >&2; exit 127
fi
mkdir -p "$OUTPUT_DIR"
RESTART_DELAY=15
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115 Safari/537.36"
REF="https://www.youtube.com/"
if [[ -s "$COOKIES" ]]; then
COOKIES_ARG=(--cookies "$COOKIES")
FFMPEG_COOKIE_ARG=() # 필요 시 -headers "Cookie: ..." 방식으로 교체
else
COOKIES_ARG=()
FFMPEG_COOKIE_ARG=()
fi
while true; do
echo "[$(date)] Fetching fresh HLS URL from YouTube..."
if ! HLS_URL="$("$YTDLP_BIN" -g "${COOKIES_ARG[@]}" --no-warnings -f "bv*[height<=720]+ba/b" "$VIDEO_URL")"; then
echo "[$(date)] yt-dlp failed. Retrying in $RESTART_DELAY sec..."
sleep "$RESTART_DELAY"; continue
fi
if [[ -z "$HLS_URL" ]]; then
echo "[$(date)] Empty HLS URL. Retrying in $RESTART_DELAY sec..."
sleep "$RESTART_DELAY"; continue
fi
echo "[$(date)] Got HLS URL: $HLS_URL"
echo "[$(date)] Starting ffmpeg HLS restream..."
set +e
"$FFMPEG_BIN" -hide_banner -loglevel warning \
-user_agent "$UA" -referer "$REF" "${FFMPEG_COOKIE_ARG[@]}" \
-re -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 15 \
-rw_timeout 20000000 -timeout 20000000 -seekable 0 \
-fflags +genpts+discardcorrupt -probesize 15M -analyzeduration 20M \
-i "$HLS_URL" -c copy -bufsize 20M -max_delay 5000000 \
-f hls -hls_time 10 -hls_list_size 20 \
-hls_flags delete_segments+append_list+omit_endlist \
"$OUTPUT_DIR/index.m3u8"
CODE=$?
set -e
echo "[$(date)] ffmpeg exited with code $CODE. Waiting $RESTART_DELAY sec before restart..."
sleep "$RESTART_DELAY"
done
출력 경로(/var/www/videos/hls/live/720p)는 User=ubuntu가 쓰기 가능해야 한다.
sudo mkdir -p /var/www/videos/hls/live/720p
sudo chown -R ubuntu:www-data /var/www/videos
sudo chmod -R 775 /var/www/videos
혹은 서비스에 아래 줄을 넣어주면 되는데,
ReadWritePaths=/var/www/videos/hls/live/720p /opt/live-stream
이 코드는 systemd sandbox 가 쓰기를 허용하는 줄이다.
참고로 디렉터리 자체 권한, 소유권까지 제대로 맞춰줘야 한다.
작동 후 품질 개선을 위해 쿠키 및 헤더 옵션을 넣어준다.
ffmpeg의 -cookies 옵션은 name=value; name2=value2 형태가 일반적인데,
-cookies "file=/path" 는 ffmpeg 표준 옵션은 아니니(빌드마다 다를 수 있음) 헤더로 넘기는 방법을 고려하였다.
쿠키 파일을 헤더로 붙이는 예 (Netscape cookies.txt라면 변환 필요)
FFMPEG_COOKIE_ARG=(-headers "Cookie: $(awk 'BEGIN{ORS="; "}$0 !~ /^#/ {print $6"="$7}' "$COOKIES")")
일단 지금처럼 yt-dlp -g로 m3u8을 받아서 -user_agent와 -referer만 맞춰도 동작하는 경우가 많으니, 우선 권한/경로부터 해결하고 필요 시 다듬자.
# 각 경로 구성요소의 권한을 한 눈에 보기
namei -l /opt/live-stream/live-stream.sh
# 퍼미션/소유자 확인
ls -l /opt/live-stream/live-stream.sh
ls -ld /opt/live-stream
조건:
live-stream.sh에 실행 비트가 있어야 함 => -rwxr-xr-x(0755) 추천
/opt 와 /opt/live-stream 디렉터리에 실행 권한이 있어야 User=사용자명 가 경로를 탐색(traverse) 가능 (보통 755)
수정
sudo chown 사용자명:사용자명 /opt/live-stream/live-stream.sh /opt/live-stream
sudo chmod 0755 /opt/live-stream /opt/live-stream/live-stream.sh
참고: 디렉터리의 x 권한은 “들어갈 수 있음”이다. 따라서 아무리 파일을 755로 바꿔도, 디렉터리가 700이면 여전히 Permission denied가 뜰 수 있다.
윈도우에서 만든 스크립트면 커널이 인터프리터를 못 찾아서 문제를 낼 수 있다.
head -n1 /opt/live-stream/live-stream.sh
file /opt/live-stream/live-stream.sh
첫번째 명령어는 쉬뱅인 #!/usr/bin/env bash 가,
두번째 명령어는 with CRLF line terminators 가 보이면 안 된다.
캐리지 리턴, 라인 피드 정리를 위해선 아래 명령어를 입력하면 된다.
sudo sed -i 's/\r$//' /opt/live-stream/live-stream.sh
참고로 그럴일은 없겠지만 opt가 noexec로 마운트 되어있으면 실행 자체가 막힌다.
따라서 아래 명령어를 실행한 뒤,
findmnt -no TARGET,OPTIONS /opt
# 또는
mount | grep ' /opt '
noexec 가 보이면 가급적 스크립트를 noexec 아닌 경로(예: /usr/local/bin)로 옮기거나, 서비스에서 인터프리터로 실행하면 된다.
ExecStart=/usr/bin/bash /opt/live-stream/live-stream.sh
참고로 이 방식은 스크립트에 x 비트가 없어도 동작한다. 대신 보안상 최선은 아님.
# 1) 권한/소유/개행 정리
sudo chown ubuntu:ubuntu /opt/live-stream /opt/live-stream/live-stream.sh
sudo chmod 0755 /opt/live-stream /opt/live-stream/live-stream.sh
sudo sed -i 's/\r$//' /opt/live-stream/live-stream.sh
# 2) 출력 디렉터리 준비
sudo mkdir -p /var/www/videos/hls/live/720p
sudo chown -R ubuntu:www-data /var/www/videos
sudo chmod -R 775 /var/www/videos
혹은 서비스 파일에 명시
# 3) /opt noexec 여부 점검(필요 시만 보통은 필요 없을꺼임.)
findmnt -no TARGET,OPTIONS /opt
# 4) 서비스 파일 수정(예: /etc/systemd/system/live-stream.service)
# - ExecStart=/usr/bin/bash -lc /opt/live-stream/live-stream.sh
# - Environment="PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
# 5) systemd 반영 및 재기동
sudo systemctl daemon-reload
sudo systemctl restart live-stream
sudo journalctl -u live-stream -e --no-pager