#!/bin/sh # WebRadio Controller - SecuBox Backend CLI # Manages Icecast, Ezstream, DarkIce streaming services set -e CONF_DIR="/srv/webradio/config" MUSIC_DIR="/srv/webradio/music" JINGLE_DIR="/srv/webradio/jingles" PLAYLIST_FILE="/tmp/webradio_playlist.m3u" ICECAST_XML="$CONF_DIR/icecast.xml" EZSTREAM_XML="$CONF_DIR/ezstream.xml" DARKICE_CFG="$CONF_DIR/darkice.cfg" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' log() { echo -e "${GREEN}[WebRadio]${NC} $1"; } warn() { echo -e "${YELLOW}[WebRadio]${NC} $1"; } error() { echo -e "${RED}[WebRadio]${NC} $1" >&2; } uci_get() { uci -q get "webradio.$1" 2>/dev/null || echo "$2"; } #--- Status --- cmd_status() { echo -e "${CYAN}=== WebRadio Status ===${NC}" local enabled=$(uci_get main.enabled 0) local name=$(uci_get main.name "SecuBox Radio") local port=$(uci_get main.port 8000) echo "Station: $name" echo "Enabled: $([ "$enabled" = "1" ] && echo "Yes" || echo "No")" echo "" # Icecast status if pgrep -f "icecast" >/dev/null 2>&1; then echo -e "Icecast: ${GREEN}Running${NC} (port $port)" # Get listener count local listeners=$(curl -s "http://127.0.0.1:$port/status-json.xsl" 2>/dev/null | jsonfilter -e '@.icestats.source.listeners' 2>/dev/null || echo "0") echo "Listeners: $listeners" else echo -e "Icecast: ${RED}Stopped${NC}" fi # Ezstream status if pgrep -f "ezstream" >/dev/null 2>&1; then echo -e "Ezstream: ${GREEN}Running${NC} (playlist mode)" else echo -e "Ezstream: ${YELLOW}Stopped${NC}" fi # DarkIce status if pgrep -f "darkice" >/dev/null 2>&1; then echo -e "DarkIce: ${GREEN}Running${NC} (live mode)" else echo -e "DarkIce: ${YELLOW}Stopped${NC}" fi # Exposure status local domain=$(uci_get exposure.domain "") if [ -n "$domain" ]; then echo "" echo "Exposed at: https://$domain/" fi } #--- Generate Configs --- cmd_genconfig() { log "Generating configuration files..." mkdir -p "$CONF_DIR" # Generate Icecast config generate_icecast_config # Generate Ezstream config generate_ezstream_config # Generate DarkIce config generate_darkice_config log "Configuration files generated in $CONF_DIR" } generate_icecast_config() { local name=$(uci_get main.name "SecuBox Radio") local port=$(uci_get main.port 8000) local max_listeners=$(uci_get main.max_listeners 100) local source_pass=$(uci_get main.source_password "hackme") local admin_pass=$(uci_get main.admin_password "admin123") local relay_pass=$(uci_get main.relay_password "relay123") cat > "$ICECAST_XML" < SecuBox admin@secubox.local $max_listeners 5 524288 30 15 10 1 65535 $source_pass $relay_pass admin $admin_pass localhost $port 0.0.0.0 /stream /silence.mp3 1 $name SecuBox Community Radio Various 0 /live $name - Live 0 1 /usr/share/icecast /var/log/webradio /usr/share/icecast/web /usr/share/icecast/admin /var/run/icecast.pid access.log error.log 3 0 icecast icecast EOF } generate_ezstream_config() { local port=$(uci_get main.port 8000) local source_pass=$(uci_get main.source_password "hackme") local format=$(uci_get stream.format "mp3") local bitrate=$(uci_get stream.bitrate 128) local samplerate=$(uci_get stream.samplerate 44100) local channels=$(uci_get stream.channels 2) local shuffle=$(uci_get playlist.shuffle 1) cat > "$EZSTREAM_XML" < http://localhost:$port/stream $source_pass MP3 $PLAYLIST_FILE 0 $shuffle 0 -1 SecuBox Radio https://secubox.in Various SecuBox Community Radio $bitrate $channels $samplerate 0 EOF } generate_darkice_config() { local port=$(uci_get main.port 8000) local source_pass=$(uci_get main.source_password "hackme") local device=$(uci_get live.device "default") local bitrate=$(uci_get live.bitrate 192) local samplerate=$(uci_get stream.samplerate 44100) local channels=$(uci_get stream.channels 2) cat > "$DARKICE_CFG" < /tmp/music_files.txt # Shuffle if enabled (using awk for BusyBox compatibility) if [ "$shuffle" = "1" ]; then awk 'BEGIN{srand()} {print rand()"\t"$0}' /tmp/music_files.txt | sort -n | cut -f2- > /tmp/music_shuffled.txt mv /tmp/music_shuffled.txt /tmp/music_files.txt fi # Generate playlist with jingles rm -f "$PLAYLIST_FILE" local count=0 while read -r file; do echo "$file" >> "$PLAYLIST_FILE" count=$((count + 1)) # Insert jingle every N tracks if [ "$jingle_interval" -gt 0 ] && [ $((count % jingle_interval)) -eq 0 ]; then local jingle=$(find "$jingle_dir" -type f -name "*.mp3" 2>/dev/null | sort -R | head -1) [ -n "$jingle" ] && echo "$jingle" >> "$PLAYLIST_FILE" fi done < /tmp/music_files.txt rm -f /tmp/music_files.txt local total=$(wc -l < "$PLAYLIST_FILE" 2>/dev/null || echo 0) log "Playlist generated: $total tracks" } cmd_playlist_list() { echo -e "${CYAN}=== Playlist ===${NC}" if [ -f "$PLAYLIST_FILE" ]; then awk '{print NR": "$0}' "$PLAYLIST_FILE" | head -20 local total=$(wc -l < "$PLAYLIST_FILE") echo "..." echo "Total: $total tracks" else warn "No playlist generated. Run: webradioctl playlist generate" fi } cmd_playlist_add() { local file="$1" if [ -f "$file" ]; then cp "$file" "$MUSIC_DIR/" log "Added: $(basename "$file")" else error "File not found: $file" return 1 fi } #--- Stream Mode (FFmpeg) --- cmd_stream() { case "$1" in start) cmd_stream_start ;; stop) cmd_stream_stop ;; *) cmd_stream_status ;; esac } cmd_stream_start() { if pgrep -f "ffmpeg.*icecast" >/dev/null 2>&1; then warn "Stream already running" return 0 fi # Ensure playlist exists if [ ! -f "$PLAYLIST_FILE" ] || [ ! -s "$PLAYLIST_FILE" ]; then cmd_playlist_generate fi if [ ! -s "$PLAYLIST_FILE" ]; then error "No music files found in $MUSIC_DIR" return 1 fi local port=$(uci_get main.port 8000) local source_pass=$(uci_get main.source_password "hackme") local bitrate=$(uci_get stream.bitrate 128) local name=$(uci_get main.name "SecuBox Radio") log "Starting stream to icecast..." # Create a loop script for continuous streaming cat > /tmp/webradio_stream.sh << 'STREAMEOF' #!/bin/sh PLAYLIST="$1" PORT="$2" PASS="$3" BITRATE="$4" NAME="$5" while true; do while read -r file; do [ -f "$file" ] || continue ffmpeg -re -i "$file" \ -vn -acodec libmp3lame -ab ${BITRATE}k -ar 44100 -ac 2 \ -content_type audio/mpeg \ -f mp3 "icecast://source:${PASS}@127.0.0.1:${PORT}/stream" \ 2>/var/log/webradio/ffmpeg.log done < "$PLAYLIST" # Re-shuffle for next loop (BusyBox compatible) awk 'BEGIN{srand()} {print rand()"\t"$0}' "$PLAYLIST" | sort -n | cut -f2- > "${PLAYLIST}.tmp" && mv "${PLAYLIST}.tmp" "$PLAYLIST" done STREAMEOF chmod +x /tmp/webradio_stream.sh nohup /tmp/webradio_stream.sh "$PLAYLIST_FILE" "$port" "$source_pass" "$bitrate" "$name" \ >/dev/null 2>&1 & sleep 3 if pgrep -f "webradio_stream.sh" >/dev/null 2>&1 || pgrep -f "ffmpeg" >/dev/null 2>&1; then log "Stream started on http://127.0.0.1:$port/stream" else error "Failed to start stream - check /var/log/webradio/ffmpeg.log" return 1 fi } cmd_stream_status() { local port=$(uci_get main.port 8000) if pgrep -f "webradio_stream.sh" >/dev/null 2>&1 || pgrep -f "ffmpeg" >/dev/null 2>&1; then echo -e "FFmpeg Stream: ${GREEN}Running${NC}" echo "URL: http://127.0.0.1:$port/stream" # Get current listener count local listeners=$(curl -s "http://127.0.0.1:$port/status-json.xsl" 2>/dev/null | jsonfilter -e '@.icestats.source.listeners' 2>/dev/null || echo "0") echo "Listeners: $listeners" else echo -e "FFmpeg Stream: ${YELLOW}Stopped${NC}" fi } cmd_stream_stop() { log "Stopping stream..." pkill -f "webradio_stream.sh" 2>/dev/null || true pkill -f "ffmpeg.*icecast" 2>/dev/null || true log "Stream stopped" } #--- Live Mode --- cmd_live() { case "$1" in start) cmd_live_start ;; stop) cmd_live_stop ;; status) cmd_live_status ;; devices) cmd_live_devices ;; *) cmd_live_status ;; esac } cmd_live_start() { if pgrep -f "darkice" >/dev/null 2>&1; then warn "DarkIce already running" return 0 fi if [ ! -f "$DARKICE_CFG" ]; then cmd_genconfig fi log "Starting live broadcast..." darkice -c "$DARKICE_CFG" & sleep 1 if pgrep -f "darkice" >/dev/null 2>&1; then log "Live broadcast started on /live" else error "Failed to start DarkIce" return 1 fi } cmd_live_stop() { log "Stopping live broadcast..." pkill -f "darkice" 2>/dev/null || true log "Live broadcast stopped" } cmd_live_status() { if pgrep -f "darkice" >/dev/null 2>&1; then echo -e "DarkIce: ${GREEN}Running${NC}" local device=$(uci_get live.device "default") echo "Device: $device" else echo -e "DarkIce: ${YELLOW}Stopped${NC}" fi } cmd_live_devices() { echo -e "${CYAN}=== Audio Devices ===${NC}" if command -v arecord >/dev/null 2>&1; then arecord -l 2>/dev/null || echo "No capture devices found" else echo "alsa-utils not installed" fi } #--- Exposure (Punk Model) --- cmd_expose() { local domain="$1" local channel="${2:-dns}" # dns, tor, mesh, all if [ -z "$domain" ]; then error "Usage: webradioctl expose [dns|tor|mesh|all]" return 1 fi local port=$(uci_get main.port 8000) log "Exposing WebRadio on $domain (channel: $channel)..." case "$channel" in dns|all) # Create HAProxy vhost if command -v haproxyctl >/dev/null 2>&1; then # Create backend haproxyctl backend add webradio_stream 127.0.0.1 "$port" 2>/dev/null || true # Create vhost haproxyctl vhost add "$domain" webradio_stream haproxyctl reload log "DNS exposure: https://$domain/" else error "haproxyctl not available" fi ;; esac case "$channel" in tor|all) # Add Tor hidden service if command -v torctl >/dev/null 2>&1; then torctl hidden-service add webradio "$port" local onion=$(torctl hidden-service get webradio 2>/dev/null) [ -n "$onion" ] && log "Tor exposure: http://$onion/" else warn "torctl not available - skipping Tor" fi ;; esac case "$channel" in mesh|all) # Publish to mesh if command -v vortexctl >/dev/null 2>&1; then vortexctl mesh publish webradio "$domain" "$port" log "Mesh exposure: Published to P2P network" else warn "vortexctl not available - skipping Mesh" fi ;; esac # Save exposure config uci set webradio.exposure.domain="$domain" [ "$channel" = "tor" ] || [ "$channel" = "all" ] && uci set webradio.exposure.tor='1' [ "$channel" = "mesh" ] || [ "$channel" = "all" ] && uci set webradio.exposure.mesh='1' uci commit webradio log "Exposure complete!" } cmd_unexpose() { local domain=$(uci_get exposure.domain "") if [ -z "$domain" ]; then warn "No exposure configured" return 0 fi log "Removing exposure for $domain..." # Remove HAProxy vhost if command -v haproxyctl >/dev/null 2>&1; then haproxyctl vhost del "$domain" 2>/dev/null || true haproxyctl reload fi # Remove Tor hidden service if command -v torctl >/dev/null 2>&1; then torctl hidden-service del webradio 2>/dev/null || true fi # Remove from mesh if command -v vortexctl >/dev/null 2>&1; then vortexctl mesh unpublish webradio 2>/dev/null || true fi # Clear config uci set webradio.exposure.domain='' uci set webradio.exposure.tor='0' uci set webradio.exposure.mesh='0' uci commit webradio log "Exposure removed" } #--- Service Control --- cmd_start() { /etc/init.d/webradio start } cmd_stop() { /etc/init.d/webradio stop } cmd_restart() { /etc/init.d/webradio restart } cmd_enable() { uci set webradio.main.enabled='1' uci commit webradio /etc/init.d/webradio enable log "WebRadio enabled" } cmd_disable() { uci set webradio.main.enabled='0' uci commit webradio /etc/init.d/webradio disable log "WebRadio disabled" } #--- Skip Track --- cmd_skip() { if pgrep -f "ezstream" >/dev/null 2>&1; then pkill -USR1 -f "ezstream" log "Skipped to next track" else warn "Ezstream not running" fi } #--- Help --- cmd_help() { cat < [options] ${GREEN}Service Commands:${NC} status Show service status start Start WebRadio services stop Stop WebRadio services restart Restart services enable Enable autostart disable Disable autostart ${GREEN}Configuration:${NC} genconfig Generate Icecast/Ezstream/DarkIce configs ${GREEN}Playlist:${NC} playlist List current playlist playlist generate Regenerate playlist from music directory playlist add Add file to music directory ${GREEN}Streaming:${NC} stream Show stream status stream start Start FFmpeg stream to Icecast stream stop Stop streaming skip Skip to next track ${GREEN}Live Broadcast:${NC} live Show live status live start Start DarkIce live input live stop Stop live broadcast live devices List audio capture devices ${GREEN}Exposure (Punk Model):${NC} expose [channel] Expose radio (dns|tor|mesh|all) unexpose Remove all exposure ${GREEN}Examples:${NC} webradioctl enable webradioctl playlist generate webradioctl stream start webradioctl expose radio.secubox.in dns webradioctl expose radio.secubox.in all EOF } #--- Main --- case "$1" in status) cmd_status ;; start) cmd_start ;; stop) cmd_stop ;; restart) cmd_restart ;; enable) cmd_enable ;; disable) cmd_disable ;; genconfig) cmd_genconfig ;; playlist) shift; cmd_playlist "$@" ;; stream) shift; cmd_stream "$@" ;; skip) cmd_skip ;; live) shift; cmd_live "$@" ;; expose) shift; cmd_expose "$@" ;; unexpose) cmd_unexpose ;; help|--help|-h|"") cmd_help ;; *) error "Unknown command: $1" cmd_help exit 1 ;; esac