New packages: - luci-app-webradio: Web radio management with Lyrion bridge tab - luci-app-turn: TURN/STUN server UI for WebRTC (Jitsi integration) - secubox-app-lyrion-bridge: Lyrion → Squeezelite → FFmpeg → Icecast pipeline - secubox-app-squeezelite: Squeezelite audio player with FIFO output - secubox-app-turn: TURN server with ACME SSL and Jitsi setup - secubox-app-webradio: Icecast/ezstream web radio server Features: - HTTPS streaming via HAProxy (stream.gk2.secubox.in) - Lyrion Music Server bridge for streaming playlists to Icecast - TURN server with time-limited credential generation - CrowdSec integration for WebRadio security - Schedule-based radio programming with jingles Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
768 lines
24 KiB
Bash
768 lines
24 KiB
Bash
#!/bin/sh
|
|
# RPCD backend for WebRadio LuCI app
|
|
# Copyright (C) 2024 CyberMind.FR
|
|
|
|
. /lib/functions.sh
|
|
. /usr/share/libubox/jshn.sh
|
|
|
|
ICECAST_INIT="/etc/init.d/icecast"
|
|
EZSTREAM_INIT="/etc/init.d/ezstream"
|
|
DARKICE_INIT="/etc/init.d/darkice"
|
|
PLAYLIST_MGR="/usr/lib/ezstream/playlist-manager.sh"
|
|
SCHEDULER="/usr/lib/webradio/scheduler.sh"
|
|
CROWDSEC_INSTALL="/usr/lib/webradio/crowdsec-install.sh"
|
|
ICECAST_URL="http://127.0.0.1:8000"
|
|
CONFIG_WEBRADIO="webradio"
|
|
CONFIG_DARKICE="darkice"
|
|
CONFIG_ICECAST="icecast"
|
|
|
|
case "$1" in
|
|
list)
|
|
cat << 'EOF'
|
|
{
|
|
"status": {},
|
|
"listeners": {},
|
|
"playlist": {},
|
|
"logs": {"lines": 50},
|
|
"start": {"service": "all"},
|
|
"stop": {"service": "all"},
|
|
"restart": {"service": "all"},
|
|
"skip": {},
|
|
"reload": {},
|
|
"generate_playlist": {"shuffle": true},
|
|
"upload": {"filename": "", "data": ""},
|
|
"schedules": {},
|
|
"current_show": {},
|
|
"add_schedule": {"name": "", "start_time": "", "end_time": "", "days": "", "playlist": ""},
|
|
"update_schedule": {"slot": "", "enabled": true},
|
|
"delete_schedule": {"slot": ""},
|
|
"generate_cron": {},
|
|
"play_jingle": {"filename": ""},
|
|
"list_jingles": {},
|
|
"live_status": {},
|
|
"live_start": {},
|
|
"live_stop": {},
|
|
"list_audio_devices": {},
|
|
"security_status": {},
|
|
"install_crowdsec": {},
|
|
"generate_ssl_cert": {"hostname": ""},
|
|
"bridge_status": {},
|
|
"bridge_start": {},
|
|
"bridge_stop": {},
|
|
"bridge_setup": {"lyrion_server": "127.0.0.1"}
|
|
}
|
|
EOF
|
|
;;
|
|
|
|
call)
|
|
case "$2" in
|
|
status)
|
|
# Get Icecast status
|
|
local icecast_running=0
|
|
local ezstream_running=0
|
|
local icecast_pid=""
|
|
local ezstream_pid=""
|
|
local listeners=0
|
|
local current_song=""
|
|
local bitrate=""
|
|
local uptime=""
|
|
|
|
# Check Icecast
|
|
icecast_pid=$(pgrep -x icecast 2>/dev/null | head -1)
|
|
[ -n "$icecast_pid" ] && icecast_running=1
|
|
|
|
# Check ezstream
|
|
ezstream_pid=$(pgrep -x ezstream 2>/dev/null | head -1)
|
|
[ -n "$ezstream_pid" ] && ezstream_running=1
|
|
|
|
# Get Icecast stats if running
|
|
if [ "$icecast_running" = "1" ]; then
|
|
local stats=$(curl -s "${ICECAST_URL}/status-json.xsl" 2>/dev/null)
|
|
if [ -n "$stats" ]; then
|
|
listeners=$(echo "$stats" | jsonfilter -e '@.icestats.source.listeners' 2>/dev/null || echo "0")
|
|
current_song=$(echo "$stats" | jsonfilter -e '@.icestats.source.title' 2>/dev/null || echo "")
|
|
bitrate=$(echo "$stats" | jsonfilter -e '@.icestats.source.audio_bitrate' 2>/dev/null || echo "")
|
|
fi
|
|
fi
|
|
|
|
# Get config values
|
|
config_load icecast
|
|
local port hostname
|
|
config_get port server port "8000"
|
|
config_get hostname server hostname "localhost"
|
|
|
|
config_load ezstream
|
|
local shuffle
|
|
config_get shuffle playlist shuffle "1"
|
|
|
|
# Playlist info
|
|
local playlist_count=0
|
|
local music_dir="/srv/webradio/music"
|
|
config_get music_dir playlist directory "$music_dir"
|
|
[ -f "/srv/webradio/playlists/current.m3u" ] && \
|
|
playlist_count=$(wc -l < /srv/webradio/playlists/current.m3u 2>/dev/null || echo 0)
|
|
|
|
cat << EOF
|
|
{
|
|
"icecast": {
|
|
"running": $([ "$icecast_running" = "1" ] && echo "true" || echo "false"),
|
|
"pid": "$icecast_pid",
|
|
"port": $port,
|
|
"hostname": "$hostname"
|
|
},
|
|
"ezstream": {
|
|
"running": $([ "$ezstream_running" = "1" ] && echo "true" || echo "false"),
|
|
"pid": "$ezstream_pid"
|
|
},
|
|
"stream": {
|
|
"listeners": ${listeners:-0},
|
|
"current_song": "$current_song",
|
|
"bitrate": "$bitrate"
|
|
},
|
|
"playlist": {
|
|
"tracks": $playlist_count,
|
|
"shuffle": $([ "$shuffle" = "1" ] && echo "true" || echo "false"),
|
|
"directory": "$music_dir"
|
|
},
|
|
"url": "http://$hostname:$port/live"
|
|
}
|
|
EOF
|
|
;;
|
|
|
|
listeners)
|
|
local stats=$(curl -s "${ICECAST_URL}/status-json.xsl" 2>/dev/null)
|
|
if [ -n "$stats" ]; then
|
|
local listeners=$(echo "$stats" | jsonfilter -e '@.icestats.source.listeners' 2>/dev/null || echo "0")
|
|
local peak=$(echo "$stats" | jsonfilter -e '@.icestats.source.listener_peak' 2>/dev/null || echo "0")
|
|
echo "{\"current\": $listeners, \"peak\": $peak}"
|
|
else
|
|
echo '{"current": 0, "peak": 0, "error": "Icecast not responding"}'
|
|
fi
|
|
;;
|
|
|
|
playlist)
|
|
local playlist_file="/srv/webradio/playlists/current.m3u"
|
|
local tracks="[]"
|
|
|
|
if [ -f "$playlist_file" ]; then
|
|
# Get first 50 tracks
|
|
tracks=$(head -50 "$playlist_file" | while read -r track; do
|
|
local name=$(basename "$track")
|
|
echo "{\"path\": \"$track\", \"name\": \"$name\"}"
|
|
done | paste -sd, | sed 's/^/[/;s/$/]/')
|
|
fi
|
|
|
|
local total=$(wc -l < "$playlist_file" 2>/dev/null || echo 0)
|
|
echo "{\"total\": $total, \"tracks\": $tracks}"
|
|
;;
|
|
|
|
logs)
|
|
read -r input
|
|
local lines=$(echo "$input" | jsonfilter -e '@.lines' 2>/dev/null)
|
|
lines=${lines:-50}
|
|
|
|
local log_content=""
|
|
if [ -f "/var/log/icecast/error.log" ]; then
|
|
log_content=$(tail -n "$lines" /var/log/icecast/error.log 2>/dev/null | \
|
|
sed 's/"/\\"/g' | paste -sd'\n')
|
|
fi
|
|
|
|
echo "{\"lines\": $lines, \"content\": \"$log_content\"}"
|
|
;;
|
|
|
|
start)
|
|
read -r input
|
|
local service=$(echo "$input" | jsonfilter -e '@.service' 2>/dev/null)
|
|
service=${service:-all}
|
|
|
|
local result="ok"
|
|
case "$service" in
|
|
icecast)
|
|
$ICECAST_INIT start 2>&1 || result="failed"
|
|
;;
|
|
ezstream)
|
|
$EZSTREAM_INIT start 2>&1 || result="failed"
|
|
;;
|
|
all|*)
|
|
$ICECAST_INIT start 2>&1 || result="failed"
|
|
sleep 2
|
|
$EZSTREAM_INIT start 2>&1 || result="failed"
|
|
;;
|
|
esac
|
|
|
|
echo "{\"result\": \"$result\", \"service\": \"$service\"}"
|
|
;;
|
|
|
|
stop)
|
|
read -r input
|
|
local service=$(echo "$input" | jsonfilter -e '@.service' 2>/dev/null)
|
|
service=${service:-all}
|
|
|
|
local result="ok"
|
|
case "$service" in
|
|
icecast)
|
|
$ICECAST_INIT stop 2>&1 || result="failed"
|
|
;;
|
|
ezstream)
|
|
$EZSTREAM_INIT stop 2>&1 || result="failed"
|
|
;;
|
|
all|*)
|
|
$EZSTREAM_INIT stop 2>&1
|
|
$ICECAST_INIT stop 2>&1 || result="failed"
|
|
;;
|
|
esac
|
|
|
|
echo "{\"result\": \"$result\", \"service\": \"$service\"}"
|
|
;;
|
|
|
|
restart)
|
|
read -r input
|
|
local service=$(echo "$input" | jsonfilter -e '@.service' 2>/dev/null)
|
|
service=${service:-all}
|
|
|
|
local result="ok"
|
|
case "$service" in
|
|
icecast)
|
|
$ICECAST_INIT restart 2>&1 || result="failed"
|
|
;;
|
|
ezstream)
|
|
$EZSTREAM_INIT restart 2>&1 || result="failed"
|
|
;;
|
|
all|*)
|
|
$EZSTREAM_INIT stop 2>&1
|
|
$ICECAST_INIT restart 2>&1 || result="failed"
|
|
sleep 2
|
|
$EZSTREAM_INIT start 2>&1 || result="failed"
|
|
;;
|
|
esac
|
|
|
|
echo "{\"result\": \"$result\", \"service\": \"$service\"}"
|
|
;;
|
|
|
|
skip)
|
|
local pid=$(cat /var/run/ezstream.pid 2>/dev/null)
|
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
kill -USR1 "$pid"
|
|
echo '{"result": "ok", "action": "skip"}'
|
|
else
|
|
echo '{"result": "failed", "error": "ezstream not running"}'
|
|
fi
|
|
;;
|
|
|
|
reload)
|
|
$ICECAST_INIT reload 2>&1
|
|
$EZSTREAM_INIT reload 2>&1
|
|
echo '{"result": "ok", "action": "reload"}'
|
|
;;
|
|
|
|
generate_playlist)
|
|
read -r input
|
|
local shuffle=$(echo "$input" | jsonfilter -e '@.shuffle' 2>/dev/null)
|
|
|
|
if [ "$shuffle" = "false" ]; then
|
|
uci set ezstream.playlist.shuffle='0'
|
|
else
|
|
uci set ezstream.playlist.shuffle='1'
|
|
fi
|
|
uci commit ezstream
|
|
|
|
if [ -x "$PLAYLIST_MGR" ]; then
|
|
local output=$($PLAYLIST_MGR generate 2>&1)
|
|
local count=$(wc -l < /srv/webradio/playlists/current.m3u 2>/dev/null || echo 0)
|
|
echo "{\"result\": \"ok\", \"tracks\": $count, \"message\": \"$output\"}"
|
|
else
|
|
echo '{"result": "failed", "error": "playlist manager not found"}'
|
|
fi
|
|
;;
|
|
|
|
upload)
|
|
read -r input
|
|
local filename=$(echo "$input" | jsonfilter -e '@.filename' 2>/dev/null)
|
|
local data=$(echo "$input" | jsonfilter -e '@.data' 2>/dev/null)
|
|
|
|
if [ -n "$filename" ] && [ -n "$data" ]; then
|
|
local dest="/srv/webradio/music/$filename"
|
|
echo "$data" | base64 -d > "$dest" 2>/dev/null
|
|
if [ -f "$dest" ]; then
|
|
echo "{\"result\": \"ok\", \"filename\": \"$filename\", \"path\": \"$dest\"}"
|
|
else
|
|
echo '{"result": "failed", "error": "write failed"}'
|
|
fi
|
|
else
|
|
echo '{"result": "failed", "error": "missing filename or data"}'
|
|
fi
|
|
;;
|
|
|
|
schedules)
|
|
config_load "$CONFIG_WEBRADIO"
|
|
|
|
local scheduling_enabled timezone
|
|
config_get scheduling_enabled scheduling enabled "0"
|
|
config_get timezone scheduling timezone "UTC"
|
|
|
|
# Build schedules array
|
|
local schedules="["
|
|
local first=1
|
|
|
|
list_schedule_cb() {
|
|
local slot="$1"
|
|
local slot_name enabled start_time end_time days playlist jingle_before jingle_after crossfade
|
|
|
|
config_get slot_name "$slot" name "$slot"
|
|
config_get enabled "$slot" enabled "0"
|
|
config_get start_time "$slot" start_time ""
|
|
config_get end_time "$slot" end_time ""
|
|
config_get days "$slot" days "0123456"
|
|
config_get playlist "$slot" playlist ""
|
|
config_get jingle_before "$slot" jingle_before ""
|
|
config_get jingle_after "$slot" jingle_after ""
|
|
config_get crossfade "$slot" crossfade "0"
|
|
|
|
[ "$first" = "1" ] || schedules="$schedules,"
|
|
first=0
|
|
|
|
schedules="$schedules{\"slot\":\"$slot\",\"name\":\"$slot_name\",\"enabled\":$([ "$enabled" = "1" ] && echo true || echo false),\"start_time\":\"$start_time\",\"end_time\":\"$end_time\",\"days\":\"$days\",\"playlist\":\"$playlist\",\"jingle_before\":\"$jingle_before\",\"jingle_after\":\"$jingle_after\",\"crossfade\":$crossfade}"
|
|
}
|
|
|
|
config_foreach list_schedule_cb schedule
|
|
schedules="$schedules]"
|
|
|
|
cat << EOF
|
|
{
|
|
"scheduling_enabled": $([ "$scheduling_enabled" = "1" ] && echo true || echo false),
|
|
"timezone": "$timezone",
|
|
"schedules": $schedules
|
|
}
|
|
EOF
|
|
;;
|
|
|
|
current_show)
|
|
if [ -x "$SCHEDULER" ]; then
|
|
$SCHEDULER current
|
|
else
|
|
echo '{"name": "Default", "slot": "", "start": "", "playlist": "current"}'
|
|
fi
|
|
;;
|
|
|
|
add_schedule)
|
|
read -r input
|
|
local name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
|
|
local start_time=$(echo "$input" | jsonfilter -e '@.start_time' 2>/dev/null)
|
|
local end_time=$(echo "$input" | jsonfilter -e '@.end_time' 2>/dev/null)
|
|
local days=$(echo "$input" | jsonfilter -e '@.days' 2>/dev/null)
|
|
local playlist=$(echo "$input" | jsonfilter -e '@.playlist' 2>/dev/null)
|
|
local jingle_before=$(echo "$input" | jsonfilter -e '@.jingle_before' 2>/dev/null)
|
|
|
|
if [ -z "$name" ] || [ -z "$start_time" ]; then
|
|
echo '{"result": "failed", "error": "name and start_time required"}'
|
|
exit 0
|
|
fi
|
|
|
|
# Generate slot ID from name
|
|
local slot=$(echo "$name" | tr '[:upper:] ' '[:lower:]_' | tr -cd 'a-z0-9_')
|
|
|
|
uci set "$CONFIG_WEBRADIO.$slot=schedule"
|
|
uci set "$CONFIG_WEBRADIO.$slot.name=$name"
|
|
uci set "$CONFIG_WEBRADIO.$slot.enabled=0"
|
|
uci set "$CONFIG_WEBRADIO.$slot.start_time=$start_time"
|
|
[ -n "$end_time" ] && uci set "$CONFIG_WEBRADIO.$slot.end_time=$end_time"
|
|
[ -n "$days" ] && uci set "$CONFIG_WEBRADIO.$slot.days=$days" || uci set "$CONFIG_WEBRADIO.$slot.days=0123456"
|
|
[ -n "$playlist" ] && uci set "$CONFIG_WEBRADIO.$slot.playlist=$playlist"
|
|
[ -n "$jingle_before" ] && uci set "$CONFIG_WEBRADIO.$slot.jingle_before=$jingle_before"
|
|
uci commit "$CONFIG_WEBRADIO"
|
|
|
|
echo "{\"result\": \"ok\", \"slot\": \"$slot\"}"
|
|
;;
|
|
|
|
update_schedule)
|
|
read -r input
|
|
local slot=$(echo "$input" | jsonfilter -e '@.slot' 2>/dev/null)
|
|
local enabled=$(echo "$input" | jsonfilter -e '@.enabled' 2>/dev/null)
|
|
local name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
|
|
local start_time=$(echo "$input" | jsonfilter -e '@.start_time' 2>/dev/null)
|
|
local end_time=$(echo "$input" | jsonfilter -e '@.end_time' 2>/dev/null)
|
|
local days=$(echo "$input" | jsonfilter -e '@.days' 2>/dev/null)
|
|
local playlist=$(echo "$input" | jsonfilter -e '@.playlist' 2>/dev/null)
|
|
local jingle_before=$(echo "$input" | jsonfilter -e '@.jingle_before' 2>/dev/null)
|
|
|
|
if [ -z "$slot" ]; then
|
|
echo '{"result": "failed", "error": "slot required"}'
|
|
exit 0
|
|
fi
|
|
|
|
# Check if slot exists
|
|
local existing=$(uci -q get "$CONFIG_WEBRADIO.$slot" 2>/dev/null)
|
|
if [ -z "$existing" ]; then
|
|
echo '{"result": "failed", "error": "slot not found"}'
|
|
exit 0
|
|
fi
|
|
|
|
# Update fields
|
|
[ "$enabled" = "true" ] && uci set "$CONFIG_WEBRADIO.$slot.enabled=1"
|
|
[ "$enabled" = "false" ] && uci set "$CONFIG_WEBRADIO.$slot.enabled=0"
|
|
[ -n "$name" ] && uci set "$CONFIG_WEBRADIO.$slot.name=$name"
|
|
[ -n "$start_time" ] && uci set "$CONFIG_WEBRADIO.$slot.start_time=$start_time"
|
|
[ -n "$end_time" ] && uci set "$CONFIG_WEBRADIO.$slot.end_time=$end_time"
|
|
[ -n "$days" ] && uci set "$CONFIG_WEBRADIO.$slot.days=$days"
|
|
[ -n "$playlist" ] && uci set "$CONFIG_WEBRADIO.$slot.playlist=$playlist"
|
|
[ -n "$jingle_before" ] && uci set "$CONFIG_WEBRADIO.$slot.jingle_before=$jingle_before"
|
|
uci commit "$CONFIG_WEBRADIO"
|
|
|
|
# Regenerate cron if scheduling enabled
|
|
local sched_enabled=$(uci -q get "$CONFIG_WEBRADIO.scheduling.enabled")
|
|
[ "$sched_enabled" = "1" ] && [ -x "$SCHEDULER" ] && $SCHEDULER generate >/dev/null 2>&1
|
|
|
|
echo '{"result": "ok"}'
|
|
;;
|
|
|
|
delete_schedule)
|
|
read -r input
|
|
local slot=$(echo "$input" | jsonfilter -e '@.slot' 2>/dev/null)
|
|
|
|
if [ -z "$slot" ]; then
|
|
echo '{"result": "failed", "error": "slot required"}'
|
|
exit 0
|
|
fi
|
|
|
|
uci delete "$CONFIG_WEBRADIO.$slot" 2>/dev/null
|
|
uci commit "$CONFIG_WEBRADIO"
|
|
|
|
# Regenerate cron
|
|
[ -x "$SCHEDULER" ] && $SCHEDULER generate >/dev/null 2>&1
|
|
|
|
echo '{"result": "ok"}'
|
|
;;
|
|
|
|
generate_cron)
|
|
if [ -x "$SCHEDULER" ]; then
|
|
$SCHEDULER generate 2>&1
|
|
echo '{"result": "ok"}'
|
|
else
|
|
echo '{"result": "failed", "error": "scheduler not found"}'
|
|
fi
|
|
;;
|
|
|
|
play_jingle)
|
|
read -r input
|
|
local filename=$(echo "$input" | jsonfilter -e '@.filename' 2>/dev/null)
|
|
|
|
if [ -z "$filename" ]; then
|
|
echo '{"result": "failed", "error": "filename required"}'
|
|
exit 0
|
|
fi
|
|
|
|
if [ -x "$SCHEDULER" ]; then
|
|
$SCHEDULER jingle "$filename" 2>&1
|
|
echo '{"result": "ok", "filename": "'"$filename"'"}'
|
|
else
|
|
echo '{"result": "failed", "error": "scheduler not found"}'
|
|
fi
|
|
;;
|
|
|
|
list_jingles)
|
|
local jingle_dir="/srv/webradio/jingles"
|
|
config_load "$CONFIG_WEBRADIO"
|
|
config_get jingle_dir jingles directory "$jingle_dir"
|
|
|
|
local jingles="[]"
|
|
if [ -d "$jingle_dir" ]; then
|
|
jingles=$(find "$jingle_dir" -type f \( -name "*.mp3" -o -name "*.ogg" -o -name "*.wav" \) 2>/dev/null | while read -r f; do
|
|
local name=$(basename "$f")
|
|
local size=$(ls -lh "$f" 2>/dev/null | awk '{print $5}')
|
|
echo "{\"name\":\"$name\",\"path\":\"$f\",\"size\":\"$size\"}"
|
|
done | paste -sd, | sed 's/^/[/;s/$/]/')
|
|
[ -z "$jingles" ] || [ "$jingles" = "[]" ] && jingles="[]"
|
|
fi
|
|
|
|
echo "{\"directory\": \"$jingle_dir\", \"jingles\": $jingles}"
|
|
;;
|
|
|
|
live_status)
|
|
local darkice_running=0
|
|
local darkice_pid=""
|
|
local device=""
|
|
local enabled=""
|
|
|
|
# Check DarkIce process
|
|
darkice_pid=$(pgrep darkice 2>/dev/null | head -1)
|
|
[ -n "$darkice_pid" ] && darkice_running=1
|
|
|
|
# Get config values
|
|
config_load "$CONFIG_DARKICE"
|
|
config_get device input device "hw:0,0"
|
|
config_get enabled main enabled "0"
|
|
|
|
cat << EOF
|
|
{
|
|
"running": $([ "$darkice_running" = "1" ] && echo "true" || echo "false"),
|
|
"pid": "$darkice_pid",
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"device": "$device"
|
|
}
|
|
EOF
|
|
;;
|
|
|
|
live_start)
|
|
local result="ok"
|
|
local error=""
|
|
|
|
# Check if DarkIce init exists
|
|
if [ -x "$DARKICE_INIT" ]; then
|
|
# Enable and start DarkIce
|
|
uci set "$CONFIG_DARKICE.main.enabled=1"
|
|
uci commit "$CONFIG_DARKICE"
|
|
|
|
$DARKICE_INIT start 2>&1 || {
|
|
result="failed"
|
|
error="failed to start darkice"
|
|
}
|
|
else
|
|
result="failed"
|
|
error="darkice not installed"
|
|
fi
|
|
|
|
if [ "$result" = "ok" ]; then
|
|
echo '{"result": "ok"}'
|
|
else
|
|
echo "{\"result\": \"failed\", \"error\": \"$error\"}"
|
|
fi
|
|
;;
|
|
|
|
live_stop)
|
|
local result="ok"
|
|
|
|
if [ -x "$DARKICE_INIT" ]; then
|
|
$DARKICE_INIT stop 2>&1
|
|
fi
|
|
|
|
# Also try to kill any running darkice process
|
|
pkill darkice 2>/dev/null
|
|
|
|
echo '{"result": "ok"}'
|
|
;;
|
|
|
|
list_audio_devices)
|
|
local devices="[]"
|
|
|
|
# Parse ALSA devices from /proc/asound/cards
|
|
if [ -f /proc/asound/cards ]; then
|
|
devices=$(cat /proc/asound/cards 2>/dev/null | grep -E '^\s*[0-9]+' | while read -r line; do
|
|
local card_num=$(echo "$line" | awk '{print $1}')
|
|
local card_name=$(echo "$line" | sed 's/^[[:space:]]*[0-9]*[[:space:]]*\[[^]]*\]:[[:space:]]*//')
|
|
# Check if card has capture capability
|
|
if [ -d "/proc/asound/card$card_num" ]; then
|
|
echo "{\"device\":\"hw:$card_num,0\",\"name\":\"$card_name\",\"type\":\"capture\"}"
|
|
fi
|
|
done | paste -sd, | sed 's/^/[/;s/$/]/')
|
|
fi
|
|
|
|
# Fallback if no devices found
|
|
[ -z "$devices" ] || [ "$devices" = "[]" ] && devices="[]"
|
|
|
|
echo "{\"devices\": $devices}"
|
|
;;
|
|
|
|
security_status)
|
|
local ssl_enabled ssl_cert ssl_key ssl_port
|
|
local crowdsec_installed=false
|
|
local crowdsec_parsers=false
|
|
local crowdsec_scenarios=false
|
|
local crowdsec_decisions=0
|
|
local ssl_cert_exists=false
|
|
local ssl_cert_expiry=""
|
|
|
|
# Load SSL config
|
|
config_load "$CONFIG_ICECAST"
|
|
config_get ssl_enabled ssl enabled "0"
|
|
config_get ssl_cert ssl certificate "/etc/ssl/certs/icecast.pem"
|
|
config_get ssl_key ssl key "/etc/ssl/private/icecast.key"
|
|
config_get ssl_port ssl port "8443"
|
|
|
|
# Check if cert exists
|
|
[ -f "$ssl_cert" ] && ssl_cert_exists=true
|
|
|
|
# Get cert expiry if exists
|
|
if [ "$ssl_cert_exists" = "true" ] && command -v openssl >/dev/null 2>&1; then
|
|
ssl_cert_expiry=$(openssl x509 -in "$ssl_cert" -noout -enddate 2>/dev/null | cut -d= -f2)
|
|
fi
|
|
|
|
# Check CrowdSec
|
|
command -v crowdsec >/dev/null 2>&1 && crowdsec_installed=true
|
|
|
|
# Check for Icecast parsers
|
|
[ -f /etc/crowdsec/parsers/s01-parse/icecast-logs.yaml ] && crowdsec_parsers=true
|
|
|
|
# Check for Icecast scenarios
|
|
[ -f /etc/crowdsec/scenarios/icecast-flood.yaml ] && crowdsec_scenarios=true
|
|
|
|
# Get active decisions count
|
|
if [ "$crowdsec_installed" = "true" ] && command -v cscli >/dev/null 2>&1; then
|
|
crowdsec_decisions=$(cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l || echo 0)
|
|
fi
|
|
|
|
cat << EOF
|
|
{
|
|
"ssl_enabled": $([ "$ssl_enabled" = "1" ] && echo "true" || echo "false"),
|
|
"ssl_port": $ssl_port,
|
|
"ssl_cert_path": "$ssl_cert",
|
|
"ssl_key_path": "$ssl_key",
|
|
"ssl_cert_exists": $ssl_cert_exists,
|
|
"ssl_cert_expiry": "$ssl_cert_expiry",
|
|
"crowdsec_installed": $crowdsec_installed,
|
|
"crowdsec_parsers": $crowdsec_parsers,
|
|
"crowdsec_scenarios": $crowdsec_scenarios,
|
|
"crowdsec_decisions": $crowdsec_decisions
|
|
}
|
|
EOF
|
|
;;
|
|
|
|
install_crowdsec)
|
|
if [ -x "$CROWDSEC_INSTALL" ]; then
|
|
local output=$($CROWDSEC_INSTALL install 2>&1)
|
|
echo '{"result": "ok", "output": "'"$(echo "$output" | tr '\n' ' ')"'"}'
|
|
else
|
|
echo '{"result": "failed", "error": "crowdsec-install.sh not found"}'
|
|
fi
|
|
;;
|
|
|
|
generate_ssl_cert)
|
|
read -r input
|
|
local hostname=$(echo "$input" | jsonfilter -e '@.hostname' 2>/dev/null)
|
|
hostname=${hostname:-localhost}
|
|
|
|
local cert_dir="/etc/ssl/certs"
|
|
local key_dir="/etc/ssl/private"
|
|
local cert_file="$cert_dir/icecast.pem"
|
|
local key_file="$key_dir/icecast.key"
|
|
|
|
mkdir -p "$cert_dir" "$key_dir"
|
|
|
|
# Generate self-signed certificate
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
|
-keyout "$key_file" \
|
|
-out "$cert_file" \
|
|
-subj "/CN=$hostname/O=WebRadio/C=FR" 2>&1
|
|
|
|
if [ -f "$cert_file" ] && [ -f "$key_file" ]; then
|
|
chmod 644 "$cert_file"
|
|
chmod 600 "$key_file"
|
|
chown root:icecast "$key_file" 2>/dev/null
|
|
|
|
# Update UCI config
|
|
uci set "$CONFIG_ICECAST.ssl=ssl"
|
|
uci set "$CONFIG_ICECAST.ssl.certificate=$cert_file"
|
|
uci set "$CONFIG_ICECAST.ssl.key=$key_file"
|
|
uci commit "$CONFIG_ICECAST"
|
|
|
|
echo '{"result": "ok", "cert": "'"$cert_file"'", "key": "'"$key_file"'"}'
|
|
else
|
|
echo '{"result": "failed", "error": "certificate generation failed"}'
|
|
fi
|
|
else
|
|
echo '{"result": "failed", "error": "openssl not installed"}'
|
|
fi
|
|
;;
|
|
|
|
bridge_status)
|
|
json_init
|
|
|
|
# Check Lyrion server
|
|
local lyrion_server=$(uci -q get lyrion-bridge.main.lyrion_server 2>/dev/null || echo "127.0.0.1")
|
|
local lyrion_port=$(uci -q get lyrion-bridge.main.lyrion_port 2>/dev/null || echo "9000")
|
|
local lyrion_online=0
|
|
if curl -s "http://${lyrion_server}:${lyrion_port}/status.html" >/dev/null 2>&1; then
|
|
lyrion_online=1
|
|
fi
|
|
|
|
# Check Squeezelite
|
|
local squeezelite_running=0
|
|
pgrep -f "squeezelite" >/dev/null 2>&1 && squeezelite_running=1
|
|
|
|
# Check FFmpeg bridge
|
|
local ffmpeg_running=0
|
|
pgrep -f "ffmpeg-bridge.sh" >/dev/null 2>&1 && ffmpeg_running=1
|
|
pgrep -f "ffmpeg.*lyrion" >/dev/null 2>&1 && ffmpeg_running=1
|
|
|
|
# Check Icecast mount
|
|
local icecast_host=$(uci -q get lyrion-bridge.icecast.host 2>/dev/null || echo "127.0.0.1")
|
|
local icecast_port=$(uci -q get lyrion-bridge.icecast.port 2>/dev/null || echo "8000")
|
|
local icecast_mount=$(uci -q get lyrion-bridge.icecast.mount 2>/dev/null || echo "/lyrion")
|
|
local mount_active=0
|
|
local listeners=0
|
|
local title=""
|
|
local artist=""
|
|
|
|
local mount_status=$(curl -s "http://${icecast_host}:${icecast_port}/status-json.xsl" 2>/dev/null)
|
|
if [ -n "$mount_status" ]; then
|
|
local sources=$(echo "$mount_status" | jsonfilter -e '@.icestats.source' 2>/dev/null)
|
|
if echo "$sources" | grep -q "lyrion"; then
|
|
mount_active=1
|
|
listeners=$(echo "$mount_status" | jsonfilter -e '@.icestats.source[@.listenurl="*lyrion*"].listeners' 2>/dev/null || echo "0")
|
|
fi
|
|
fi
|
|
|
|
# Get now playing from Lyrion
|
|
if [ "$lyrion_online" = "1" ]; then
|
|
local np=$(curl -s "http://${lyrion_server}:${lyrion_port}/jsonrpc.js" \
|
|
-d '{"id":1,"method":"slim.request","params":["",[\"status\",\"-\",1,\"tags:adl\"]]}' 2>/dev/null)
|
|
if [ -n "$np" ]; then
|
|
title=$(echo "$np" | jsonfilter -e '@.result.playlist_loop[0].title' 2>/dev/null || echo "")
|
|
artist=$(echo "$np" | jsonfilter -e '@.result.playlist_loop[0].artist' 2>/dev/null || echo "")
|
|
fi
|
|
fi
|
|
|
|
json_add_boolean lyrion_online "$lyrion_online"
|
|
json_add_string lyrion_server "$lyrion_server"
|
|
json_add_boolean squeezelite_running "$squeezelite_running"
|
|
json_add_boolean ffmpeg_running "$ffmpeg_running"
|
|
json_add_boolean bridge_running "$ffmpeg_running"
|
|
json_add_boolean mount_active "$mount_active"
|
|
json_add_int listeners "$listeners"
|
|
json_add_string title "$title"
|
|
json_add_string artist "$artist"
|
|
json_add_string stream_url "http://${icecast_host}:${icecast_port}${icecast_mount}"
|
|
|
|
json_dump
|
|
;;
|
|
|
|
bridge_start)
|
|
if [ -x /usr/sbin/lyrionstreamctl ]; then
|
|
/usr/sbin/lyrionstreamctl start >/dev/null 2>&1
|
|
echo '{"result": "ok"}'
|
|
else
|
|
echo '{"result": "error", "error": "lyrionstreamctl not found"}'
|
|
fi
|
|
;;
|
|
|
|
bridge_stop)
|
|
if [ -x /usr/sbin/lyrionstreamctl ]; then
|
|
/usr/sbin/lyrionstreamctl stop >/dev/null 2>&1
|
|
echo '{"result": "ok"}'
|
|
else
|
|
echo '{"result": "error", "error": "lyrionstreamctl not found"}'
|
|
fi
|
|
;;
|
|
|
|
bridge_setup)
|
|
read -r input
|
|
local lyrion_server=$(echo "$input" | jsonfilter -e '@.lyrion_server' 2>/dev/null)
|
|
lyrion_server=${lyrion_server:-127.0.0.1}
|
|
|
|
if [ -x /usr/sbin/lyrionstreamctl ]; then
|
|
output=$(/usr/sbin/lyrionstreamctl setup "$lyrion_server" 2>&1)
|
|
local icecast_port=$(uci -q get lyrion-bridge.icecast.port 2>/dev/null || echo "8000")
|
|
local icecast_mount=$(uci -q get lyrion-bridge.icecast.mount 2>/dev/null || echo "/lyrion")
|
|
echo '{"result": "ok", "stream_url": "http://127.0.0.1:'"$icecast_port$icecast_mount"'"}'
|
|
else
|
|
echo '{"result": "error", "error": "lyrionstreamctl not found"}'
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
echo '{"error": "unknown method"}'
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|