Radio-in-a-box는 Icecast, Liquidsoap, Rust CLI를 묶어 직접 운영하는 인터넷 라디오 방송국이다. 음악을 등록하고 재생 목록과 라이브 재생을 관리할 수 있으며, 청취용 웹페이지는 별도 프런트엔드 빌드 과정 없이 제공된다.
설치와 재생
Docker Compose v2(up --wait 지원)와 Rust를 설치한 뒤 프로젝트 디렉터리에서 실행한다.
cargo install --path . --locked
radio init
radio add /path/to/music/
radio start
웹페이지는 http://localhost:8080에서, MP3 스트림은 http://localhost:8080/radio.mp3에서 들을 수 있다. 음악 라이브러리가 비어 있으면 무음이 송출된다. CLI를 설치하지 않으려면 cargo run -- <command>를 사용할 수 있으며, 명령은 프로젝트 디렉터리에서 실행해야 한다.
음악과 라이브 재생 관리
radio list
radio add '/path/to/Another Track.flac'
radio queue 'Another Track.flac'
radio skip
radio reload
radio status
radio logs
radio stop
오디오 엔진은 곡마다 새로 생성하는 스크립트가 아니라 안정적으로 유지되는 .liq 스크립트다. Rust CLI는 전체 트랙을 music/에 복사하고 컨테이너 내 경로를 music/playlist.m3u에 추가한다. Liquidsoap은 재생 목록의 실시간 변경을 감시하며, 대기열의 곡을 무작위 라이브러리 재생보다 먼저 재생한다. 건너뛰기와 다시 불러오기는 공개 관리 포트 없이 docker compose exec를 통해 Liquidsoap 명령 소켓으로 전달된다. 트랙이 바뀌어도 스트림은 재시작되지 않는다.
MP3, Ogg, FLAC, WAV, M4A, AAC 가져오기를 지원하지만 실제 디코딩 가능 여부는 파일의 코덱에 달려 있다. 파일, 디렉터리 또는 셸이 확장한 와일드카드를 지정할 수 있다.
radio add /path/to/music/ # Includes all nested directories
radio add /path/to/* # Your shell expands the wildcard
지원하지 않는 파일과 심볼릭 링크 디렉터리는 건너뛴다. 가져온 파일은 music/ 아래에 복사되며 원본은 이동하지 않는다. 디렉터리 구조는 평탄화하고, 이름이 중복된 파일은 기존 파일을 덮어쓰지 않는다. 중복 파일이나 입출력 오류가 발생하면 가져오기를 중단하지만, 이미 가져온 트랙은 유지된다. CLI 가져오기 작업은 한 번에 하나만 실행해야 한다. 디스크 쓰기나 재생 목록 기록에 실패해도 복사된 파일은 삭제하지 않고 수동 복구를 위해 남긴다.
재생 순서를 바꾸려면 music/playlist.m3u를 직접 수정한 뒤 radio reload를 실행한다. 파일에는 /music/filename 형식의 경로가 한 줄에 하나씩 들어간다. 고급 오디오 동작은 docker/radio.liq를 편집하고 radio restart를 실행해 변경할 수 있다.
징글 설정
radio init # Safe upgrade: existing files stay untouched
radio jingles add /path/to/jingles/
radio jingles list
radio jingles every 3 # One random jingle after every 3 songs
radio restart # Once after upgrading to the jingle-aware script
징글은 music/jingles/playlist.m3u에서 별도로 관리하며, 곡과 마찬가지로 하위 디렉터리를 재귀적으로 가져오고 중복을 방지한다. 기본값은 비활성화인 every 0이다. 재생 간격은 music/jingles/every.txt에 저장되며 radio jingles every N으로 재시작 없이 바꿀 수 있다. 가져온 징글은 실시간으로 감시된다.
일반 곡과 대기열 곡 모두 간격 계산에 포함되고, 건너뛰거나 중단한 곡도 한 번의 재생 차례로 센다. 재생 중인 징글은 끝까지 재생한 뒤 대기열 곡이 시작된다. 재생 가능한 징글이 없으면 징글을 건너뛰며, 재생할 곡이 없으면 스트림은 무음 상태로 유지된다. 재생 횟수는 재시작할 때 초기화되고, 간격 변경은 다음 트랙 경계부터 적용된다.
방송국 설정과 외부 공개
radio init은 서로 독립적인 임의 비밀번호 세 개가 담긴 .env 파일을 만든다. 기존 설정이나 음악 파일을 덮어쓰지 않는다. Unix에서는 .env의 접근 권한이 소유자로 제한되며, .env와 음악 파일은 Git에서 무시되고 Docker 빌드 컨텍스트에서도 제외된다. 두 항목은 별도로 안전하게 백업해야 한다.
.env를 수정한 뒤 radio restart를 실행하면 설정이 적용된다.
STATION_NAME=My Radio
RADIO_BIND=127.0.0.1
RADIO_PORT=8080
ICECAST_PORT=8000
생성된 비밀번호 줄은 유지해야 한다. 비밀번호를 바꿀 경우 영숫자 24자 이상을 사용해야 하며, XML에서 특수한 의미를 갖는 문자는 허용되지 않는다. 청취 페이지는 Icecast에서 방송국 이름, 현재 트랙, 청취자 수를 가져오며 스트림과 상태 요청은 같은 출처를 사용한다.
Icecast 관리자 페이지는 http://localhost:8000/admin/이다. 사용자 이름은 admin이며 비밀번호는 .env의 ICECAST_ADMIN_PASSWORD 값이다. 이 포트는 로컬호스트에만 연결된다. 웹페이지는 스트림과 공개 상태 정보만 프록시하며 관리자 엔드포인트는 프록시하지 않는다.
기본 설정에서는 웹사이트가 로컬호스트에만 연결된다. 다른 기기에서 청취하게 하려면 RADIO_BIND=0.0.0.0으로 바꾸고 radio restart를 실행한다. 인터넷에 공개하려면 8080 포트 앞에 HTTPS 역방향 프록시를 두고 DNS와 방화벽을 설정해야 한다. Icecast 관리자 포트와 Docker 소켓은 비공개로 유지해야 한다. 제공되는 구성은 도메인이나 TLS 인증서를 설정해 주지 않는다. Icecast의 기본 청취자 한도는 100명이다. 더 많은 청취자가 필요하면 docker/icecast.sh와 사용 가능한 대역폭을 조정해야 한다.
모든 서비스는 상태 점검을 수행하고 자동으로 재시작된다. 오디오 서비스는 루트가 아닌 사용자로 실행되며 읽기 전용 파일 시스템과 임시 쓰기 공간을 사용한다. 컨테이너를 중지하거나 다시 만들어도 음악과 설정은 삭제되지 않는다.
점검 명령
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --check
node --check web/app.js
node --test tests/web.test.cjs
# After building the station images (no live station needed):
python3 tests/jingles.py
# After radio init:
docker compose config --quiet
# With a running Docker daemon:
radio start
curl -f http://localhost:8080/status-json.xsl
curl -f --max-time 3 http://localhost:8080/radio.mp3 -o /tmp/radio-sample.mp3
실시간 스트림은 끝나지 않으므로 스트림을 확인하는 curl 명령은 보통 시간 초과 코드 28로 종료된다. 오디오 데이터가 수신됐는지 확인하고, 디코더나 연결 오류는 radio logs에서 살펴본다. web/의 프런트엔드 파일을 수정한 뒤에는 다시 빌드할 필요 없이 브라우저를 새로고침하면 된다. 방송 권한이 있는 음악만 송출해야 한다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요