feat: Add auto-scan on startup and live Icecast/Liquidsoap status checks
- Auto-scan music library on startup to load existing tracks - Add check-icecast-status() function to query Icecast API - Add check-liquidsoap-status() function to check Docker container - Update admin dashboard to show real-time streaming status - Eliminates need to manually copy files from incoming on every restart
This commit is contained in:
parent
ce39a0ca1a
commit
24feeddfa8
|
|
@ -17,19 +17,6 @@
|
|||
*.wx32fsl
|
||||
/slime.lisp
|
||||
asteroid
|
||||
buildapp
|
||||
quicklisp-manifest.txt
|
||||
notes/
|
||||
run-asteroid.sh
|
||||
build-sbcl.sh
|
||||
|
||||
# Music files - don't commit audio files to repository
|
||||
*.mp3
|
||||
*.flac
|
||||
*.ogg
|
||||
*.wav
|
||||
*.m4a
|
||||
*.aac
|
||||
*.wma
|
||||
|
||||
# Docker music directory - keep folder but ignore music files
|
||||
|
|
@ -56,15 +43,6 @@ docker-compose.yml.backup.*
|
|||
# Log files
|
||||
*.log
|
||||
logs/
|
||||
performance-logs/
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Shell scripts (exclude from repository)
|
||||
*.sh
|
||||
# Exception: Docker utility scripts should be included
|
||||
!docker/start.sh
|
||||
!docker/stop.sh
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Asteroid Radio Performance Analysis Tool
|
||||
Generates graphs and reports from performance test data
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
import glob
|
||||
import os
|
||||
from datetime import datetime
|
||||
import numpy as np
|
||||
|
||||
# Set up plotting style
|
||||
plt.style.use('dark_background')
|
||||
sns.set_palette("husl")
|
||||
|
||||
def load_performance_data():
|
||||
"""Load all CSV performance data files"""
|
||||
csv_files = glob.glob('performance-logs/*_data_*.csv')
|
||||
data_frames = {}
|
||||
|
||||
for file in csv_files:
|
||||
# Extract test type from filename
|
||||
filename = os.path.basename(file)
|
||||
if 'aac' in filename:
|
||||
test_type = 'AAC 96kbps'
|
||||
elif 'mp3-high' in filename:
|
||||
test_type = 'MP3 128kbps'
|
||||
elif 'mp3-low' in filename:
|
||||
test_type = 'MP3 64kbps'
|
||||
else:
|
||||
test_type = filename.split('_')[1]
|
||||
|
||||
try:
|
||||
df = pd.read_csv(file)
|
||||
df['test_type'] = test_type
|
||||
df['timestamp'] = pd.to_datetime(df['timestamp'])
|
||||
data_frames[test_type] = df
|
||||
print(f"✅ Loaded {len(df)} records from {test_type} test")
|
||||
except Exception as e:
|
||||
print(f"❌ Error loading {file}: {e}")
|
||||
|
||||
return data_frames
|
||||
|
||||
def create_performance_dashboard(data_frames):
|
||||
"""Create comprehensive performance dashboard"""
|
||||
|
||||
# Combine all data
|
||||
all_data = pd.concat(data_frames.values(), ignore_index=True)
|
||||
|
||||
# Create figure with subplots
|
||||
fig, axes = plt.subplots(2, 3, figsize=(20, 12))
|
||||
fig.suptitle('🎵 Asteroid Radio Performance Analysis Dashboard', fontsize=16, y=0.98)
|
||||
|
||||
# 1. CPU Usage Over Time (Asteroid App)
|
||||
ax1 = axes[0, 0]
|
||||
for test_type, df in data_frames.items():
|
||||
if 'asteroid_cpu' in df.columns:
|
||||
ax1.plot(df.index, df['asteroid_cpu'], label=test_type, linewidth=2)
|
||||
ax1.set_title('Asteroid App CPU Usage Over Time')
|
||||
ax1.set_xlabel('Time (samples)')
|
||||
ax1.set_ylabel('CPU %')
|
||||
ax1.legend()
|
||||
ax1.grid(True, alpha=0.3)
|
||||
|
||||
# 2. Memory Usage Over Time (Asteroid App)
|
||||
ax2 = axes[0, 1]
|
||||
for test_type, df in data_frames.items():
|
||||
if 'asteroid_mem_mb' in df.columns:
|
||||
ax2.plot(df.index, df['asteroid_mem_mb'], label=test_type, linewidth=2)
|
||||
ax2.set_title('Asteroid App Memory Usage Over Time')
|
||||
ax2.set_xlabel('Time (samples)')
|
||||
ax2.set_ylabel('Memory (MB)')
|
||||
ax2.legend()
|
||||
ax2.grid(True, alpha=0.3)
|
||||
|
||||
# 3. Docker Container CPU Usage
|
||||
ax3 = axes[0, 2]
|
||||
for test_type, df in data_frames.items():
|
||||
if 'icecast_cpu' in df.columns and 'liquidsoap_cpu' in df.columns:
|
||||
ax3.plot(df.index, df['icecast_cpu'], label=f'{test_type} - Icecast', linestyle='--', alpha=0.7)
|
||||
ax3.plot(df.index, df['liquidsoap_cpu'], label=f'{test_type} - Liquidsoap', linestyle='-', alpha=0.9)
|
||||
ax3.set_title('Docker Container CPU Usage')
|
||||
ax3.set_xlabel('Time (samples)')
|
||||
ax3.set_ylabel('CPU %')
|
||||
ax3.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
|
||||
ax3.grid(True, alpha=0.3)
|
||||
|
||||
# 4. System Memory Usage
|
||||
ax4 = axes[1, 0]
|
||||
for test_type, df in data_frames.items():
|
||||
if 'system_mem_used_gb' in df.columns and 'system_mem_total_gb' in df.columns:
|
||||
memory_percent = (df['system_mem_used_gb'] / df['system_mem_total_gb']) * 100
|
||||
ax4.plot(df.index, memory_percent, label=test_type, linewidth=2)
|
||||
ax4.set_title('System Memory Usage')
|
||||
ax4.set_xlabel('Time (samples)')
|
||||
ax4.set_ylabel('Memory Usage %')
|
||||
ax4.legend()
|
||||
ax4.grid(True, alpha=0.3)
|
||||
|
||||
# 5. Average Performance Comparison
|
||||
ax5 = axes[1, 1]
|
||||
metrics = ['cpu_percent', 'memory_mb', 'stream_response_ms', 'web_response_ms']
|
||||
test_types = list(data_frames.keys())
|
||||
|
||||
performance_summary = {}
|
||||
for test_type, df in data_frames.items():
|
||||
performance_summary[test_type] = {
|
||||
'Asteroid CPU (%)': df['asteroid_cpu'].mean() if 'asteroid_cpu' in df.columns else 0,
|
||||
'Asteroid Mem (MB)': df['asteroid_mem_mb'].mean() if 'asteroid_mem_mb' in df.columns else 0,
|
||||
'Icecast CPU (%)': df['icecast_cpu'].mean() if 'icecast_cpu' in df.columns else 0,
|
||||
'Liquidsoap CPU (%)': df['liquidsoap_cpu'].mean() if 'liquidsoap_cpu' in df.columns else 0
|
||||
}
|
||||
|
||||
summary_df = pd.DataFrame(performance_summary).T
|
||||
summary_df.plot(kind='bar', ax=ax5)
|
||||
ax5.set_title('Average Performance Metrics')
|
||||
ax5.set_ylabel('Value')
|
||||
ax5.tick_params(axis='x', rotation=45)
|
||||
ax5.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
|
||||
|
||||
# 6. CPU Load Distribution
|
||||
ax6 = axes[1, 2]
|
||||
if 'asteroid_cpu' in all_data.columns:
|
||||
# Create boxplot data manually since pandas boxplot by group is tricky
|
||||
cpu_data = []
|
||||
labels = []
|
||||
for test_type, df in data_frames.items():
|
||||
if 'asteroid_cpu' in df.columns:
|
||||
cpu_data.append(df['asteroid_cpu'].values)
|
||||
labels.append(test_type.replace(' ', '\n'))
|
||||
|
||||
if cpu_data:
|
||||
ax6.boxplot(cpu_data, labels=labels)
|
||||
ax6.set_title('Asteroid CPU Load Distribution')
|
||||
ax6.set_xlabel('Stream Type')
|
||||
ax6.set_ylabel('CPU %')
|
||||
ax6.tick_params(axis='x', rotation=0)
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('performance-logs/asteroid_performance_dashboard.png', dpi=300, bbox_inches='tight')
|
||||
print("📊 Dashboard saved as: performance-logs/asteroid_performance_dashboard.png")
|
||||
|
||||
return fig
|
||||
|
||||
def generate_performance_report(data_frames):
|
||||
"""Generate detailed performance report"""
|
||||
|
||||
report = []
|
||||
report.append("🎵 ASTEROID RADIO PERFORMANCE ANALYSIS REPORT")
|
||||
report.append("=" * 50)
|
||||
report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
report.append("")
|
||||
|
||||
for test_type, df in data_frames.items():
|
||||
report.append(f"📡 {test_type} Stream Analysis:")
|
||||
report.append("-" * 30)
|
||||
|
||||
if 'asteroid_cpu' in df.columns:
|
||||
cpu_stats = df['asteroid_cpu'].describe()
|
||||
report.append(f" Asteroid App CPU:")
|
||||
report.append(f" Average: {cpu_stats['mean']:.1f}%")
|
||||
report.append(f" Peak: {cpu_stats['max']:.1f}%")
|
||||
report.append(f" Minimum: {cpu_stats['min']:.1f}%")
|
||||
|
||||
if 'asteroid_mem_mb' in df.columns:
|
||||
mem_stats = df['asteroid_mem_mb'].describe()
|
||||
report.append(f" Asteroid App Memory:")
|
||||
report.append(f" Average: {mem_stats['mean']:.1f} MB")
|
||||
report.append(f" Peak: {mem_stats['max']:.1f} MB")
|
||||
report.append(f" Minimum: {mem_stats['min']:.1f} MB")
|
||||
|
||||
if 'icecast_cpu' in df.columns:
|
||||
icecast_stats = df['icecast_cpu'].describe()
|
||||
report.append(f" Icecast CPU:")
|
||||
report.append(f" Average: {icecast_stats['mean']:.2f}%")
|
||||
report.append(f" Peak: {icecast_stats['max']:.2f}%")
|
||||
|
||||
if 'liquidsoap_cpu' in df.columns:
|
||||
liquidsoap_stats = df['liquidsoap_cpu'].describe()
|
||||
report.append(f" Liquidsoap CPU:")
|
||||
report.append(f" Average: {liquidsoap_stats['mean']:.1f}%")
|
||||
report.append(f" Peak: {liquidsoap_stats['max']:.1f}%")
|
||||
|
||||
if 'stream_response_ms' in df.columns:
|
||||
stream_stats = df['stream_response_ms'].dropna().describe()
|
||||
if len(stream_stats) > 0:
|
||||
report.append(f" Stream Response:")
|
||||
report.append(f" Average: {stream_stats['mean']:.1f} ms")
|
||||
report.append(f" 95th percentile: {stream_stats.quantile(0.95):.1f} ms")
|
||||
|
||||
if 'web_response_ms' in df.columns:
|
||||
web_stats = df['web_response_ms'].dropna().describe()
|
||||
if len(web_stats) > 0:
|
||||
report.append(f" Web Response:")
|
||||
report.append(f" Average: {web_stats['mean']:.1f} ms")
|
||||
report.append(f" 95th percentile: {web_stats.quantile(0.95):.1f} ms")
|
||||
|
||||
report.append("")
|
||||
|
||||
# Performance recommendations
|
||||
report.append("🎯 PERFORMANCE RECOMMENDATIONS:")
|
||||
report.append("-" * 30)
|
||||
|
||||
# Find best performing stream
|
||||
avg_cpu = {}
|
||||
for test_type, df in data_frames.items():
|
||||
if 'asteroid_cpu' in df.columns:
|
||||
avg_cpu[test_type] = df['asteroid_cpu'].mean()
|
||||
|
||||
if avg_cpu:
|
||||
best_stream = min(avg_cpu, key=avg_cpu.get)
|
||||
worst_stream = max(avg_cpu, key=avg_cpu.get)
|
||||
|
||||
report.append(f" • Most efficient stream: {best_stream} ({avg_cpu[best_stream]:.1f}% avg CPU)")
|
||||
report.append(f" • Most resource-intensive: {worst_stream} ({avg_cpu[worst_stream]:.1f}% avg CPU)")
|
||||
|
||||
if avg_cpu[worst_stream] > 80:
|
||||
report.append(" ⚠️ High CPU usage detected - consider optimizing or scaling")
|
||||
elif avg_cpu[best_stream] < 20:
|
||||
report.append(" ✅ Excellent resource efficiency - system has headroom for more users")
|
||||
|
||||
report.append("")
|
||||
report.append("📈 SCALING INSIGHTS:")
|
||||
report.append("-" * 20)
|
||||
|
||||
total_tests = sum(len(df) for df in data_frames.values())
|
||||
report.append(f" • Total test duration: ~{total_tests} minutes across all streams")
|
||||
report.append(f" • System stability: {'✅ Excellent' if total_tests > 40 else '⚠️ Needs more testing'}")
|
||||
|
||||
# Save report
|
||||
with open('performance-logs/asteroid_performance_report.txt', 'w') as f:
|
||||
f.write('\n'.join(report))
|
||||
|
||||
print("📄 Report saved as: performance-logs/asteroid_performance_report.txt")
|
||||
return '\n'.join(report)
|
||||
|
||||
def main():
|
||||
print("🎵 Asteroid Radio Performance Analyzer")
|
||||
print("=" * 40)
|
||||
|
||||
# Load data
|
||||
data_frames = load_performance_data()
|
||||
|
||||
if not data_frames:
|
||||
print("❌ No performance data found!")
|
||||
return
|
||||
|
||||
# Create visualizations
|
||||
print("\n📊 Creating performance dashboard...")
|
||||
create_performance_dashboard(data_frames)
|
||||
|
||||
# Generate report
|
||||
print("\n📄 Generating performance report...")
|
||||
report = generate_performance_report(data_frames)
|
||||
|
||||
print("\n✅ Analysis complete!")
|
||||
print("\nGenerated files:")
|
||||
print(" 📊 performance-logs/asteroid_performance_dashboard.png")
|
||||
print(" 📄 performance-logs/asteroid_performance_report.txt")
|
||||
|
||||
print(f"\n🎯 Quick Summary:")
|
||||
print(f" Tests completed: {len(data_frames)}")
|
||||
total_records = sum(len(df) for df in data_frames.values())
|
||||
print(f" Data points collected: {total_records}")
|
||||
print(f" Stream formats tested: {', '.join(data_frames.keys())}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -262,6 +262,28 @@
|
|||
(serve-file (merge-pathnames (concatenate 'string "static/" path)
|
||||
(asdf:system-source-directory :asteroid))))
|
||||
|
||||
;; Status check functions
|
||||
(defun check-icecast-status ()
|
||||
"Check if Icecast server is running and accessible"
|
||||
(handler-case
|
||||
(let ((response (drakma:http-request "http://localhost:8000/status-json.xsl"
|
||||
:want-stream nil
|
||||
:connection-timeout 2)))
|
||||
(if response "🟢 Running" "🔴 Not Running"))
|
||||
(error () "🔴 Not Running")))
|
||||
|
||||
(defun check-liquidsoap-status ()
|
||||
"Check if Liquidsoap is running via Docker"
|
||||
(handler-case
|
||||
(let* ((output (with-output-to-string (stream)
|
||||
(uiop:run-program '("docker" "ps" "--filter" "name=liquidsoap" "--format" "{{.Status}}")
|
||||
:output stream
|
||||
:error-output nil
|
||||
:ignore-error-status t)))
|
||||
(running-p (search "Up" output)))
|
||||
(if running-p "🟢 Running" "🔴 Not Running"))
|
||||
(error () "🔴 Not Running")))
|
||||
|
||||
;; Admin page (requires authentication)
|
||||
(define-page admin #@"/admin" ()
|
||||
"Admin dashboard"
|
||||
|
|
@ -278,8 +300,8 @@
|
|||
:database-status (handler-case
|
||||
(if (db:connected-p) "🟢 Connected" "🔴 Disconnected")
|
||||
(error () "🔴 No Database Backend"))
|
||||
:liquidsoap-status "🔴 Not Running"
|
||||
:icecast-status "🔴 Not Running"
|
||||
:liquidsoap-status (check-liquidsoap-status)
|
||||
:icecast-status (check-icecast-status)
|
||||
:track-count (format nil "~d" track-count)
|
||||
:library-path "/home/glenn/Projects/Code/asteroid/music/library/")))
|
||||
|
||||
|
|
@ -404,5 +426,13 @@
|
|||
;; Initialize user management before server starts
|
||||
(initialize-user-system)
|
||||
|
||||
;; Scan music library on startup to load existing tracks
|
||||
(format t "Scanning music library for existing tracks...~%")
|
||||
(handler-case
|
||||
(let ((tracks-added (scan-music-library)))
|
||||
(format t "✅ Loaded ~a tracks from library~%" tracks-added))
|
||||
(error (e)
|
||||
(format t "⚠️ Library scan failed: ~a~%" e)))
|
||||
|
||||
(run-server))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,263 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Comprehensive Asteroid Performance Testing Script
|
||||
# Tests Docker streaming + Asteroid web app together
|
||||
# Usage: ./comprehensive-performance-test.sh [aac|mp3-high|mp3-low]
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOCKER_DIR="$SCRIPT_DIR/docker"
|
||||
TEST_DURATION=900 # 15 minutes in seconds
|
||||
STREAM_TYPE="${1:-aac}" # Default to AAC if not specified
|
||||
|
||||
# Log file names based on stream type
|
||||
case "$STREAM_TYPE" in
|
||||
"aac")
|
||||
LOG_PREFIX="test-aac"
|
||||
STREAM_DESC="AAC 96kbps"
|
||||
;;
|
||||
"mp3-high")
|
||||
LOG_PREFIX="test-mp3-high"
|
||||
STREAM_DESC="MP3 128kbps"
|
||||
;;
|
||||
"mp3-low")
|
||||
LOG_PREFIX="test-mp3-low"
|
||||
STREAM_DESC="MP3 64kbps"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [aac|mp3-high|mp3-low]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create logs directory
|
||||
LOGS_DIR="$SCRIPT_DIR/performance-logs"
|
||||
mkdir -p "$LOGS_DIR"
|
||||
|
||||
# Timestamp for this test run
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
LOG_FILE="$LOGS_DIR/${LOG_PREFIX}_${TIMESTAMP}.log"
|
||||
|
||||
echo "=== Comprehensive Asteroid Performance Test ===" | tee "$LOG_FILE"
|
||||
echo "Stream Type: $STREAM_DESC" | tee -a "$LOG_FILE"
|
||||
echo "Test Duration: 15 minutes" | tee -a "$LOG_FILE"
|
||||
echo "Started at: $(date)" | tee -a "$LOG_FILE"
|
||||
echo "Log file: $LOG_FILE" | tee -a "$LOG_FILE"
|
||||
echo "=========================================" | tee -a "$LOG_FILE"
|
||||
|
||||
# Function to cleanup on exit
|
||||
cleanup() {
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "=== CLEANUP STARTED ===" | tee -a "$LOG_FILE"
|
||||
|
||||
# Stop Asteroid application
|
||||
if [ ! -z "$ASTEROID_PID" ] && kill -0 "$ASTEROID_PID" 2>/dev/null; then
|
||||
echo "Stopping Asteroid application (PID: $ASTEROID_PID)..." | tee -a "$LOG_FILE"
|
||||
kill "$ASTEROID_PID" 2>/dev/null || true
|
||||
sleep 2
|
||||
kill -9 "$ASTEROID_PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Stop Docker containers
|
||||
echo "Stopping Docker containers..." | tee -a "$LOG_FILE"
|
||||
cd "$DOCKER_DIR"
|
||||
docker compose down 2>/dev/null || true
|
||||
|
||||
# Stop monitoring
|
||||
if [ ! -z "$MONITOR_PID" ] && kill -0 "$MONITOR_PID" 2>/dev/null; then
|
||||
echo "Stopping monitoring..." | tee -a "$LOG_FILE"
|
||||
kill "$MONITOR_PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "Cleanup completed at: $(date)" | tee -a "$LOG_FILE"
|
||||
echo "=== TEST FINISHED ===" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Set trap for cleanup
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# Step 1: Start Docker containers
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "=== STARTING DOCKER CONTAINERS ===" | tee -a "$LOG_FILE"
|
||||
cd "$DOCKER_DIR"
|
||||
|
||||
# Stop any existing containers
|
||||
docker compose down 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Start containers
|
||||
echo "Starting Icecast2 and Liquidsoap containers..." | tee -a "$LOG_FILE"
|
||||
docker compose up -d 2>&1 | tee -a "$LOG_FILE"
|
||||
|
||||
# Wait for containers to be ready
|
||||
echo "Waiting for containers to initialize..." | tee -a "$LOG_FILE"
|
||||
sleep 10
|
||||
|
||||
# Verify containers are running
|
||||
if ! docker compose ps | grep -q "Up"; then
|
||||
echo "ERROR: Docker containers failed to start!" | tee -a "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Docker containers started successfully" | tee -a "$LOG_FILE"
|
||||
|
||||
# Step 2: Start Asteroid application
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "=== STARTING ASTEROID APPLICATION ===" | tee -a "$LOG_FILE"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Build if needed
|
||||
if [ ! -f "./asteroid" ]; then
|
||||
echo "Building Asteroid executable..." | tee -a "$LOG_FILE"
|
||||
make 2>&1 | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Start Asteroid in background
|
||||
echo "Starting Asteroid web application..." | tee -a "$LOG_FILE"
|
||||
./asteroid > "$LOGS_DIR/${LOG_PREFIX}_asteroid_${TIMESTAMP}.log" 2>&1 &
|
||||
ASTEROID_PID=$!
|
||||
|
||||
# Wait for Asteroid to start
|
||||
echo "Waiting for Asteroid to initialize..." | tee -a "$LOG_FILE"
|
||||
sleep 5
|
||||
|
||||
# Verify Asteroid is running
|
||||
if ! kill -0 "$ASTEROID_PID" 2>/dev/null; then
|
||||
echo "ERROR: Asteroid application failed to start!" | tee -a "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Asteroid application started successfully (PID: $ASTEROID_PID)" | tee -a "$LOG_FILE"
|
||||
|
||||
# Step 3: Wait for full system initialization
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "=== SYSTEM INITIALIZATION ===" | tee -a "$LOG_FILE"
|
||||
echo "Waiting for full system initialization..." | tee -a "$LOG_FILE"
|
||||
sleep 10
|
||||
|
||||
# Test connectivity
|
||||
echo "Testing system connectivity..." | tee -a "$LOG_FILE"
|
||||
|
||||
# Test Icecast
|
||||
if curl -s "http://localhost:8000/" > /dev/null; then
|
||||
echo "✓ Icecast2 responding on port 8000" | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "⚠ Icecast2 not responding" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Test Asteroid web interface
|
||||
if curl -s "http://localhost:8080/asteroid/" > /dev/null; then
|
||||
echo "✓ Asteroid web interface responding on port 8080" | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "⚠ Asteroid web interface not responding" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Step 4: Start monitoring
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "=== STARTING PERFORMANCE MONITORING ===" | tee -a "$LOG_FILE"
|
||||
echo "Stream: $STREAM_DESC" | tee -a "$LOG_FILE"
|
||||
echo "Duration: 15 minutes" | tee -a "$LOG_FILE"
|
||||
echo "Monitoring started at: $(date)" | tee -a "$LOG_FILE"
|
||||
|
||||
# Create monitoring function
|
||||
monitor_performance() {
|
||||
local monitor_log="$LOGS_DIR/${LOG_PREFIX}_monitor_${TIMESTAMP}.log"
|
||||
local csv_log="$LOGS_DIR/${LOG_PREFIX}_data_${TIMESTAMP}.csv"
|
||||
|
||||
# CSV header
|
||||
echo "timestamp,icecast_cpu,icecast_mem_mb,liquidsoap_cpu,liquidsoap_mem_mb,asteroid_cpu,asteroid_mem_mb,system_mem_used_gb,system_mem_total_gb" > "$csv_log"
|
||||
|
||||
local start_time=$(date +%s)
|
||||
local end_time=$((start_time + TEST_DURATION))
|
||||
|
||||
while [ $(date +%s) -lt $end_time ]; do
|
||||
local current_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Get Docker container stats
|
||||
local icecast_stats=$(docker stats asteroid-icecast --no-stream --format "{{.CPUPerc}},{{.MemUsage}}" 2>/dev/null || echo "0.00%,0B / 0B")
|
||||
local liquidsoap_stats=$(docker stats asteroid-liquidsoap --no-stream --format "{{.CPUPerc}},{{.MemUsage}}" 2>/dev/null || echo "0.00%,0B / 0B")
|
||||
|
||||
# Parse Docker stats
|
||||
local icecast_cpu=$(echo "$icecast_stats" | cut -d',' -f1 | sed 's/%//')
|
||||
local icecast_mem_raw=$(echo "$icecast_stats" | cut -d',' -f2 | cut -d'/' -f1 | sed 's/[^0-9.]//g')
|
||||
local icecast_mem_mb=$(echo "$icecast_mem_raw" | awk '{print $1/1024/1024}')
|
||||
|
||||
local liquidsoap_cpu=$(echo "$liquidsoap_stats" | cut -d',' -f1 | sed 's/%//')
|
||||
local liquidsoap_mem_raw=$(echo "$liquidsoap_stats" | cut -d',' -f2 | cut -d'/' -f1 | sed 's/[^0-9.]//g')
|
||||
local liquidsoap_mem_mb=$(echo "$liquidsoap_mem_raw" | awk '{print $1/1024/1024}')
|
||||
|
||||
# Get Asteroid process stats
|
||||
local asteroid_cpu="0.0"
|
||||
local asteroid_mem_mb="0.0"
|
||||
if kill -0 "$ASTEROID_PID" 2>/dev/null; then
|
||||
local asteroid_stats=$(ps -p "$ASTEROID_PID" -o %cpu,rss --no-headers 2>/dev/null || echo "0.0 0")
|
||||
asteroid_cpu=$(echo "$asteroid_stats" | awk '{print $1}')
|
||||
local asteroid_mem_kb=$(echo "$asteroid_stats" | awk '{print $2}')
|
||||
asteroid_mem_mb=$(echo "$asteroid_mem_kb" | awk '{print $1/1024}')
|
||||
fi
|
||||
|
||||
# Get system memory
|
||||
local mem_info=$(free -g | grep "^Mem:")
|
||||
local system_mem_used=$(echo "$mem_info" | awk '{print $3}')
|
||||
local system_mem_total=$(echo "$mem_info" | awk '{print $2}')
|
||||
|
||||
# Log to console and file
|
||||
printf "[%s] Icecast: %s%% CPU, %.1fMB | Liquidsoap: %s%% CPU, %.1fMB | Asteroid: %s%% CPU, %.1fMB | System: %sGB/%sGB\n" \
|
||||
"$current_time" "$icecast_cpu" "$icecast_mem_mb" "$liquidsoap_cpu" "$liquidsoap_mem_mb" \
|
||||
"$asteroid_cpu" "$asteroid_mem_mb" "$system_mem_used" "$system_mem_total" | tee -a "$LOG_FILE"
|
||||
|
||||
# Log to CSV
|
||||
printf "%s,%.2f,%.1f,%.2f,%.1f,%.2f,%.1f,%s,%s\n" \
|
||||
"$current_time" "$icecast_cpu" "$icecast_mem_mb" "$liquidsoap_cpu" "$liquidsoap_mem_mb" \
|
||||
"$asteroid_cpu" "$asteroid_mem_mb" "$system_mem_used" "$system_mem_total" >> "$csv_log"
|
||||
|
||||
sleep 5 # Sample every 5 seconds
|
||||
done
|
||||
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "Monitoring completed at: $(date)" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Start monitoring in background
|
||||
monitor_performance &
|
||||
MONITOR_PID=$!
|
||||
|
||||
# Step 5: Generate some web traffic during monitoring
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "=== GENERATING WEB TRAFFIC ===" | tee -a "$LOG_FILE"
|
||||
|
||||
# Function to generate light web traffic
|
||||
generate_traffic() {
|
||||
sleep 60 # Wait 1 minute before starting traffic
|
||||
|
||||
for i in {1..10}; do
|
||||
# Test main pages
|
||||
curl -s "http://localhost:8080/asteroid/" > /dev/null 2>&1 || true
|
||||
sleep 30
|
||||
|
||||
# Test API endpoints
|
||||
curl -s "http://localhost:8080/asteroid/api/icecast-status" > /dev/null 2>&1 || true
|
||||
sleep 30
|
||||
|
||||
# Test player page
|
||||
curl -s "http://localhost:8080/asteroid/player/" > /dev/null 2>&1 || true
|
||||
sleep 30
|
||||
done
|
||||
} &
|
||||
|
||||
# Wait for monitoring to complete
|
||||
wait $MONITOR_PID
|
||||
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "=== TEST SUMMARY ===" | tee -a "$LOG_FILE"
|
||||
echo "Stream Type: $STREAM_DESC" | tee -a "$LOG_FILE"
|
||||
echo "Test completed at: $(date)" | tee -a "$LOG_FILE"
|
||||
echo "Log files created:" | tee -a "$LOG_FILE"
|
||||
echo " - Main log: $LOG_FILE" | tee -a "$LOG_FILE"
|
||||
echo " - CSV data: $LOGS_DIR/${LOG_PREFIX}_data_${TIMESTAMP}.csv" | tee -a "$LOG_FILE"
|
||||
echo " - Asteroid log: $LOGS_DIR/${LOG_PREFIX}_asteroid_${TIMESTAMP}.log" | tee -a "$LOG_FILE"
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "To run next test, switch stream format and run:" | tee -a "$LOG_FILE"
|
||||
echo " ./comprehensive-performance-test.sh [aac|mp3-high|mp3-low]" | tee -a "$LOG_FILE"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
; meta (:version 1.0 :package "COMMON-LISP-USER")
|
||||
[hash-table equal
|
||||
(:sessions
|
||||
[hash-table equalp
|
||||
(#1="AC457BD7-3E40-469A-83FA-E805C1514C6D" [session:session #1#])])]
|
||||
|
|
@ -33,12 +33,12 @@ Fade requested setting up Liquidsoap in Docker for the Asteroid Radio project, a
|
|||
|
||||
** Check Status
|
||||
#+BEGIN_SRC bash
|
||||
docker-compose ps
|
||||
docker compose ps
|
||||
#+END_SRC
|
||||
|
||||
** View Logs
|
||||
#+BEGIN_SRC bash
|
||||
docker-compose logs -f
|
||||
docker compose logs -f
|
||||
#+END_SRC
|
||||
|
||||
** Stop Services
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
#+TITLE: Asteroid Radio - Interface Reference
|
||||
#+AUTHOR: Interface Team
|
||||
#+DATE: 2025-10-03
|
||||
|
||||
* Current Interfaces
|
||||
|
||||
Asteroid Radio currently operates as a Docker-based streaming platform using Icecast2 and Liquidsoap. The system provides streaming interfaces and control mechanisms rather than a traditional REST API.
|
||||
|
||||
** Available Interfaces
|
||||
|
||||
*** Streaming Endpoints
|
||||
- **High Quality MP3**: http://localhost:8000/asteroid.mp3 (128kbps)
|
||||
- **High Quality AAC**: http://localhost:8000/asteroid.aac (96kbps)
|
||||
- **Low Quality MP3**: http://localhost:8000/asteroid-low.mp3 (64kbps)
|
||||
|
||||
*** Administrative Interfaces
|
||||
- **Icecast Admin**: http://localhost:8000/admin/ (admin/asteroid_admin_2024)
|
||||
- **Liquidsoap Control**: =telnet localhost 1234= (telnet interface)
|
||||
|
||||
* Streaming Interface
|
||||
|
||||
** Stream Access
|
||||
All streams are accessible via standard HTTP and can be played in any media player that supports internet radio streams.
|
||||
|
||||
*** Testing Stream Connectivity
|
||||
#+BEGIN_SRC bash
|
||||
# Test all three streams
|
||||
curl -I http://localhost:8000/asteroid.mp3 # 128kbps MP3
|
||||
curl -I http://localhost:8000/asteroid.aac # 96kbps AAC
|
||||
curl -I http://localhost:8000/asteroid-low.mp3 # 64kbps MP3
|
||||
#+END_SRC
|
||||
|
||||
*** Playing Streams
|
||||
#+BEGIN_SRC bashfutu
|
||||
# With VLC
|
||||
vlc http://localhost:8000/asteroid.mp3
|
||||
|
||||
# With mpv
|
||||
mpv http://localhost:8000/asteroid.aac
|
||||
|
||||
# With curl (save to file)
|
||||
curl http://localhost:8000/asteroid-low.mp3 > stream.mp3
|
||||
#+END_SRC
|
||||
|
||||
* Icecast Admin Interface
|
||||
|
||||
** Web Administration
|
||||
Access the Icecast admin interface at http://localhost:8000/admin/
|
||||
|
||||
*** Login Credentials
|
||||
- **Username**: admin
|
||||
- **Password**: asteroid_admin_2024
|
||||
|
||||
*** Available Functions
|
||||
- **Stream Status**: View current streams and listener counts
|
||||
- **Mount Points**: Manage stream mount points
|
||||
- **Listener Statistics**: Real-time listener data
|
||||
- **Server Configuration**: View server settings
|
||||
- **Log Files**: Access server logs
|
||||
|
||||
** Icecast Status XML
|
||||
Get server status in XML format:
|
||||
#+BEGIN_SRC bash
|
||||
curl http://localhost:8000/admin/stats.xml
|
||||
#+END_SRC
|
||||
|
||||
** Stream Statistics
|
||||
Get individual stream stats:
|
||||
#+BEGIN_SRC bash
|
||||
curl http://localhost:8000/admin/stats.xml?mount=/asteroid.mp3
|
||||
curl http://localhost:8000/admin/stats.xml?mount=/asteroid.aac
|
||||
curl http://localhost:8000/admin/stats.xml?mount=/asteroid-low.mp3
|
||||
#+END_SRC
|
||||
|
||||
* Liquidsoap Control Interface
|
||||
|
||||
** Telnet Access
|
||||
Connect to Liquidsoap's telnet interface for real-time control:
|
||||
#+BEGIN_SRC bash
|
||||
telnet localhost 1234
|
||||
#+END_SRC
|
||||
|
||||
** Available Commands
|
||||
Once connected via telnet, you can use these commands:
|
||||
|
||||
*** Basic Information
|
||||
#+BEGIN_SRC
|
||||
help # List all available commands
|
||||
version # Show Liquidsoap version
|
||||
uptime # Show server uptime
|
||||
#+END_SRC
|
||||
|
||||
*** Source Control
|
||||
#+BEGIN_SRC
|
||||
request.queue # Show current queue
|
||||
request.push <uri> # Add track to queue
|
||||
request.skip # Skip current track
|
||||
#+END_SRC
|
||||
|
||||
*** Metadata
|
||||
#+BEGIN_SRC
|
||||
request.metadata # Show current track metadata
|
||||
request.on_air # Show what's currently playing
|
||||
#+END_SRC
|
||||
|
||||
*** Volume and Audio
|
||||
#+BEGIN_SRC
|
||||
var.get amplify # Get current amplification level
|
||||
var.set amplify 1.2 # Set amplification level
|
||||
#+END_SRC
|
||||
|
||||
** Telnet Scripting
|
||||
You can script Liquidsoap commands:
|
||||
#+BEGIN_SRC bash
|
||||
# Get current track info
|
||||
echo "request.metadata" | nc localhost 1234
|
||||
|
||||
# Skip current track
|
||||
echo "request.skip" | nc localhost 1234
|
||||
|
||||
# Check queue status
|
||||
echo "request.queue" | nc localhost 1234
|
||||
#+END_SRC
|
||||
|
||||
* Docker Container Management
|
||||
|
||||
** Container Status
|
||||
#+BEGIN_SRC bash
|
||||
# Check running containers
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs icecast
|
||||
docker compose logs liquidsoap
|
||||
|
||||
# Restart services
|
||||
docker compose restart
|
||||
#+END_SRC
|
||||
|
||||
** Music Library Management
|
||||
#+BEGIN_SRC bash
|
||||
# Add music files (container will detect automatically)
|
||||
cp ~/path/to/music/*.mp3 docker/music/
|
||||
cp ~/path/to/music/*.flac docker/music/
|
||||
|
||||
# Check what Liquidsoap is seeing
|
||||
echo "request.queue" | nc localhost 1234
|
||||
#+END_SRC
|
||||
|
||||
* Future Development
|
||||
|
||||
** Potential REST API
|
||||
A REST API may be developed in the future if deemed necessary for:
|
||||
- **Web Interface**: Browser-based control panel
|
||||
- **Mobile Applications**: Native mobile apps
|
||||
- **Third-party Integration**: External service integration
|
||||
- **User Management**: Account and playlist management
|
||||
|
||||
Such an API would likely be built using the RADIANCE Common Lisp web framework and would provide endpoints for:
|
||||
- Track and playlist management
|
||||
- User authentication and profiles
|
||||
- Streaming control and statistics
|
||||
- System administration
|
||||
|
||||
However, the current Docker streaming setup provides all essential functionality through existing interfaces (Icecast admin, Liquidsoap telnet, and direct stream access).
|
||||
|
||||
* Getting Help
|
||||
|
||||
For support with interfaces and streaming setup:
|
||||
- Check project documentation and troubleshooting guides
|
||||
- Review Docker container logs for error messages
|
||||
- Join our IRC chat room: **#asteroid.music** on **irc.libera.chat**
|
||||
- Submit issues with detailed system information
|
||||
|
||||
This interface reference covers all currently available methods for interacting with Asteroid Radio's streaming infrastructure.
|
||||
#+END_SRC
|
||||
|
|
@ -0,0 +1,412 @@
|
|||
#+TITLE: Asteroid Radio - Development Guide
|
||||
#+AUTHOR: Development Team
|
||||
#+DATE: 2025-10-03
|
||||
|
||||
* Development Setup
|
||||
|
||||
** Prerequisites
|
||||
|
||||
*** System Dependencies
|
||||
- SBCL (Steel Bank Common Lisp)
|
||||
- Quicklisp package manager
|
||||
- Git version control
|
||||
- Docker and Docker Compose
|
||||
- taglib for metadata extraction (for local development)
|
||||
|
||||
*** Ubuntu/Debian Installation
|
||||
#+BEGIN_SRC bash
|
||||
# Install system packages
|
||||
sudo apt update
|
||||
sudo apt install sbcl git docker.io docker compose
|
||||
|
||||
# Add user to docker group
|
||||
sudo usermod -a -G docker $USER
|
||||
# Log out and back in for group changes to take effect
|
||||
|
||||
# Install Quicklisp (if not already installed)
|
||||
curl -O https://beta.quicklisp.org/quicklisp.lisp
|
||||
sbcl --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --quit
|
||||
#+END_SRC
|
||||
|
||||
** Project Setup
|
||||
|
||||
*** Clone Repository
|
||||
#+BEGIN_SRC bash
|
||||
git clone <repository-url>
|
||||
cd asteroid
|
||||
#+END_SRC
|
||||
|
||||
*** Install Lisp Dependencies
|
||||
#+BEGIN_SRC bash
|
||||
# Start SBCL and load the system
|
||||
sbcl
|
||||
(ql:quickload :asteroid)
|
||||
#+END_SRC
|
||||
|
||||
*** ASDF Configuration (Optional but Recommended)
|
||||
For easier development, configure ASDF to find the asteroid system:
|
||||
#+BEGIN_SRC bash
|
||||
# Create ASDF source registry configuration
|
||||
mkdir -p ~/.config/common-lisp
|
||||
cat > ~/.config/common-lisp/source-registry.conf
|
||||
;; -*-lisp-*-
|
||||
(:source-registry
|
||||
(:tree "/path/to/your/projects/")
|
||||
:inherit-configuration)
|
||||
#+END_SRC
|
||||
|
||||
This allows you to load the asteroid system from any directory without changing paths.
|
||||
|
||||
* Development Workflow
|
||||
|
||||
** Local Development Server
|
||||
|
||||
*** Starting Development Environment
|
||||
#+BEGIN_SRC bash
|
||||
# Start Docker streaming services
|
||||
cd docker/
|
||||
docker compose up -d
|
||||
|
||||
# Verify containers are running
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Start RADIANCE web server (local development)
|
||||
sbcl --eval "(ql:quickload :asteroid)" --eval "(asteroid:start-server)"
|
||||
#+END_SRC
|
||||
|
||||
*** Development URLs
|
||||
- *Web Interface*: http://localhost:8080/asteroid/
|
||||
- *Admin Panel*: http://localhost:8080/asteroid/admin
|
||||
- *Live Stream*: http://localhost:8000/asteroid.mp3
|
||||
- *Icecast Admin*: http://localhost:8000/admin/ (admin/asteroid_admin_2024)
|
||||
|
||||
** Music Library Management
|
||||
|
||||
*** Directory Structure
|
||||
The music directory structure is:
|
||||
#+BEGIN_SRC
|
||||
asteroid/docker/music/ # Host directory (mounted to containers)
|
||||
├── artist1/
|
||||
│ ├── album1/
|
||||
│ │ ├── track1.mp3
|
||||
│ │ └── track2.flac
|
||||
│ └── album2/
|
||||
│ └── track3.ogg
|
||||
└── artist2/
|
||||
└── single.wav
|
||||
#+END_SRC
|
||||
|
||||
*** Recursive Scanning Capabilities
|
||||
The Asteroid application includes built-in recursive directory scanning:
|
||||
- *Function*: =scan-music-library= in =stream-media.lisp=
|
||||
- *Supports*: MP3, FLAC, OGG, WAV formats
|
||||
- *Recursive*: Automatically scans all subdirectories
|
||||
- *Metadata*: Extracts title, artist, album, duration using taglib
|
||||
- *Database*: Stores track information in RADIANCE database
|
||||
|
||||
*** Adding Music to Development Environment
|
||||
#+BEGIN_SRC bash
|
||||
# Option 1: Copy music files directly
|
||||
cp -r /path/to/your/music/* docker/music/
|
||||
|
||||
# Option 2: Mount remote directory (for large collections)
|
||||
# Edit docker-compose.yml to change volume mount:
|
||||
# volumes:
|
||||
# - /mnt/remote-music:/app/music:ro
|
||||
|
||||
# Option 3: Symlink to existing collection
|
||||
ln -s /path/to/existing/music docker/music/collection
|
||||
|
||||
# Trigger library scan via API
|
||||
curl -X POST http://localhost:8080/asteroid/api/scan-library
|
||||
#+END_SRC
|
||||
|
||||
** Code Organization
|
||||
|
||||
*** Main Components
|
||||
- =asteroid.lisp= - Main server with RADIANCE routes and API endpoints
|
||||
- =asteroid.asd= - System definition with dependencies
|
||||
- =template/= - CLIP HTML templates for web interface
|
||||
- =static/= - CSS stylesheets and static assets
|
||||
- =asteroid-radio.liq= - Liquidsoap streaming configuration
|
||||
|
||||
*** Key Modules
|
||||
- *Web Routes*: RADIANCE framework with =#@= URL patterns
|
||||
- *Database*: RADIANCE DB abstraction for track metadata
|
||||
- *Streaming*: Docker containers with Icecast2 and Liquidsoap
|
||||
- *File Processing*: Metadata extraction and library management
|
||||
- *Docker Integration*: Containerized streaming infrastructure
|
||||
|
||||
** Development Practices
|
||||
|
||||
*** Code Style
|
||||
- Use 2-space indentation for Lisp code
|
||||
- Follow Common Lisp naming conventions
|
||||
- Document functions with docstrings
|
||||
- Use meaningful variable and function names
|
||||
|
||||
*** Database Development
|
||||
#+BEGIN_SRC lisp
|
||||
;; Always use quoted symbols for field names
|
||||
(db:select 'tracks (db:query (:= 'artist "Artist Name")))
|
||||
|
||||
;; Primary key is "_id" internally, "id" in JSON responses
|
||||
(gethash "_id" track-record)
|
||||
#+END_SRC
|
||||
|
||||
*** Template Development
|
||||
- Use CLIP templating with =data-text= attributes
|
||||
- Keep templates in =template/= directory
|
||||
- Test template changes with browser refresh
|
||||
- Maintain responsive design principles
|
||||
|
||||
*** CSS Development with LASS
|
||||
- CSS is generated dynamically from =static/asteroid.lass= using LASS (Lisp Augmented Style Sheets)
|
||||
- Edit the =.lass= file, not the generated =.css= file
|
||||
- CSS is automatically compiled when the server starts via =compile-styles= function
|
||||
- Use Lisp syntax for CSS: =(body :background "#0a0a0a" :color "#00ffff")=
|
||||
- Supports nested selectors, variables, and programmatic CSS generation
|
||||
|
||||
** Testing
|
||||
|
||||
*** Manual Testing Checklist
|
||||
- [ ] Web interface loads correctly
|
||||
- [ ] Admin panel functions work
|
||||
- [ ] File upload and processing works
|
||||
- [ ] Live stream plays audio
|
||||
- [ ] Database queries return expected results
|
||||
- [ ] API endpoints respond correctly
|
||||
|
||||
*** Docker Container Testing
|
||||
#+BEGIN_SRC bash
|
||||
# Check container status
|
||||
docker compose ps
|
||||
|
||||
# Test stream connectivity
|
||||
curl -I http://localhost:8000/asteroid.mp3
|
||||
|
||||
# Test with media player
|
||||
vlc http://localhost:8000/asteroid.mp3
|
||||
|
||||
# Check container logs
|
||||
docker compose logs icecast
|
||||
docker compose logs liquidsoap
|
||||
#+END_SRC
|
||||
|
||||
*** API Testing
|
||||
#+BEGIN_SRC bash
|
||||
# Test track listing
|
||||
curl http://localhost:8080/asteroid/api/tracks
|
||||
|
||||
# Test file processing
|
||||
curl -X POST http://localhost:8080/asteroid/api/copy-files
|
||||
#+END_SRC
|
||||
|
||||
** Debugging
|
||||
|
||||
*** Common Development Issues
|
||||
|
||||
**** Stream Not Playing
|
||||
- Check Docker container status: =docker compose ps=
|
||||
- Check Liquidsoap container logs: =docker compose logs liquidsoap=
|
||||
- Check Icecast2 container logs: =docker compose logs icecast=
|
||||
- Verify music files exist in =docker/music/library/=
|
||||
- Restart containers: =docker compose restart=
|
||||
|
||||
**** Database Errors
|
||||
- Ensure proper field name quoting in queries
|
||||
- Check RADIANCE database configuration
|
||||
- Verify database file permissions
|
||||
|
||||
**** Template Rendering Issues
|
||||
- Check CLIP template syntax
|
||||
- Verify template file paths
|
||||
- Test with simplified templates first
|
||||
|
||||
*** Debug Configuration
|
||||
#+BEGIN_SRC bash
|
||||
# Enable verbose logging in Docker containers
|
||||
# Edit docker/liquidsoap/asteroid-radio.liq
|
||||
settings.log.level := 4
|
||||
settings.log.stdout := true
|
||||
settings.log.file := true
|
||||
settings.log.file.path := "/var/log/liquidsoap/asteroid.log"
|
||||
|
||||
# View real-time container logs
|
||||
docker compose logs -f liquidsoap
|
||||
docker compose logs -f icecast
|
||||
#+END_SRC
|
||||
|
||||
** Contributing Guidelines
|
||||
|
||||
*** Branch Strategy
|
||||
- =main= - Stable production code
|
||||
- =develop= - Integration branch for new features
|
||||
- =feature/*= - Individual feature development
|
||||
- =bugfix/*= - Bug fixes and patches
|
||||
|
||||
*** Commit Messages
|
||||
- Use clear, descriptive commit messages
|
||||
- Reference issue numbers when applicable
|
||||
- Keep commits focused on single changes
|
||||
|
||||
*** Pull Request Process
|
||||
1. Create feature branch from =develop=
|
||||
2. Implement changes with tests
|
||||
3. Update documentation if needed
|
||||
4. Submit pull request with description
|
||||
5. Address code review feedback
|
||||
6. Merge after approval
|
||||
|
||||
*** Code Review Checklist
|
||||
- [ ] Code follows project style guidelines
|
||||
- [ ] Functions are properly documented
|
||||
- [ ] No hardcoded values or credentials
|
||||
- [ ] Error handling is appropriate
|
||||
- [ ] Performance considerations addressed
|
||||
|
||||
** Development Tools
|
||||
|
||||
*** Recommended Editor Setup
|
||||
- *Emacs*: SLIME for interactive Lisp development
|
||||
|
||||
*** Useful Development Commands
|
||||
#+BEGIN_SRC lisp
|
||||
;; Reload system during development
|
||||
(ql:quickload :asteroid :force t)
|
||||
|
||||
;; Restart RADIANCE server
|
||||
(radiance:shutdown)
|
||||
(asteroid:start-server)
|
||||
|
||||
;; Clear database for testing
|
||||
(db:drop 'tracks)
|
||||
(asteroid:setup-database)
|
||||
#+END_SRC
|
||||
|
||||
** Performance Considerations
|
||||
|
||||
*** Development vs Production
|
||||
- Use smaller music libraries in =docker/music/= for faster testing
|
||||
- Enable debug logging in Docker containers only when needed
|
||||
- Consider memory usage with large track collections in containers
|
||||
- Test with realistic concurrent user loads using Docker scaling
|
||||
- Use =docker compose.dev.yml= for development-specific settings
|
||||
|
||||
*** Optimization Tips
|
||||
- Cache database queries where appropriate
|
||||
- Optimize playlist generation for large libraries
|
||||
- Monitor memory usage during development
|
||||
- Profile streaming performance under load
|
||||
|
||||
* Configuration Files
|
||||
- =radiance-core.conf.lisp= - RADIANCE framework configuration
|
||||
- =docker/liquidsoap/asteroid-radio.liq= - Liquidsoap streaming setup
|
||||
- =docker/icecast.xml= - Icecast2 server configuration
|
||||
- =docker/docker-compose.yml= - Container orchestration
|
||||
|
||||
** Docker Development
|
||||
#+BEGIN_SRC bash
|
||||
# Start development containers
|
||||
cd docker/
|
||||
docker compose up -d
|
||||
|
||||
# Build development container with changes
|
||||
docker compose up --build
|
||||
|
||||
# Access container shell for debugging
|
||||
docker compose exec liquidsoap bash
|
||||
docker compose exec icecast bash
|
||||
|
||||
# Stop all containers
|
||||
docker compose down
|
||||
#+END_SRC
|
||||
|
||||
* Troubleshooting
|
||||
|
||||
** Development Environment Issues
|
||||
|
||||
*** SBCL/Quicklisp Problems
|
||||
- Ensure Quicklisp is properly installed
|
||||
- Check for conflicting Lisp installations
|
||||
- Verify system dependencies are installed
|
||||
|
||||
*** Docker Container Issues
|
||||
- Check container status: =docker compose ps=
|
||||
- Verify Docker daemon is running: =docker info=
|
||||
- Check container logs: =docker compose logs [service]=
|
||||
- Restart containers: =docker compose restart=
|
||||
|
||||
*** Network Access Issues
|
||||
- Check firewall settings for ports 8000, 8080
|
||||
- Verify WSL networking configuration if applicable
|
||||
- Test container networking: =docker compose exec liquidsoap ping icecast=
|
||||
- Check port binding: =docker compose port icecast 8000=
|
||||
|
||||
*** File Permission Issues
|
||||
- Ensure =docker/music/= directory is accessible
|
||||
- Check ownership: =ls -la docker/music/=
|
||||
- Fix permissions: =sudo chown -R $USER:$USER docker/music/=
|
||||
- Verify container volume mounts in =docker-compose.yml=
|
||||
- For remote mounts: ensure network storage is accessible
|
||||
|
||||
*** Music Library Issues
|
||||
- Check if music files exist: =find docker/music/ -name "*.mp3" -o -name "*.flac"=
|
||||
- Verify supported formats: MP3, FLAC, OGG, WAV
|
||||
- Test recursive scanning: =curl -X POST http://localhost:8080/asteroid/api/scan-library=
|
||||
- Check database for tracks: =curl http://localhost:8080/asteroid/api/tracks=
|
||||
- For large collections: avoid network mounts, use local storage (see memory about 175+ files causing timeouts)
|
||||
|
||||
** Getting Help
|
||||
- Check existing issues in project repository
|
||||
- Review RADIANCE framework documentation
|
||||
- Consult Liquidsoap manual for streaming issues
|
||||
- Join our IRC chat room: **#asteroid.music** on **irc.libera.chat**
|
||||
- Ask questions in project discussions
|
||||
|
||||
This development guide provides the foundation for contributing to Asteroid Radio. For deployment and production considerations, see the Installation Guide and Performance Testing documentation.
|
||||
|
||||
* Development Stack Links
|
||||
|
||||
** Core Technologies
|
||||
- **SBCL** (Steel Bank Common Lisp): https://www.sbcl.org/
|
||||
- **Quicklisp** (Common Lisp package manager): https://www.quicklisp.org/
|
||||
- **ASDF** (Another System Definition Facility): https://common-lisp.net/project/asdf/
|
||||
|
||||
** Web Framework & Libraries
|
||||
- **RADIANCE** (Web framework): https://shirakumo.github.io/radiance/
|
||||
- **CLIP** (HTML templating): https://shinmera.github.io/clip/
|
||||
- **LASS** (CSS in Lisp): https://shinmera.github.io/LASS/
|
||||
- **Alexandria** (Utility library): https://alexandria.common-lisp.dev/
|
||||
- **Local-Time** (Time handling): https://common-lisp.net/project/local-time/
|
||||
|
||||
** Audio & Streaming
|
||||
- **Docker** (Containerization): https://www.docker.com/
|
||||
- **Icecast2** (Streaming server): https://icecast.org/
|
||||
- **Liquidsoap** (Audio streaming): https://www.liquidsoap.info/
|
||||
- **TagLib** (Audio metadata): https://taglib.org/
|
||||
|
||||
** Database & Data
|
||||
- **cl-json** (JSON handling): https://common-lisp.net/project/cl-json/
|
||||
- **cl-fad** (File/directory utilities): https://edicl.github.io/cl-fad/
|
||||
- **Ironclad** (Cryptography): https://github.com/sharplispers/ironclad
|
||||
- **Babel** (Character encoding): https://common-lisp.net/project/babel/
|
||||
|
||||
** Development Tools
|
||||
- **Emacs** (Editor): https://www.gnu.org/software/emacs/
|
||||
- **SLIME** (Emacs Lisp IDE): https://common-lisp.net/project/slime/
|
||||
- **Slynk** (SLIME backend): https://github.com/joaotavora/sly
|
||||
- **Git** (Version control): https://git-scm.com/
|
||||
|
||||
** System Libraries
|
||||
- **Bordeaux-Threads** (Threading): https://common-lisp.net/project/bordeaux-threads/
|
||||
- **Drakma** (HTTP client): https://edicl.github.io/drakma/
|
||||
- **CIFS-Utils** (Network file systems): https://wiki.samba.org/index.php/LinuxCIFS_utils
|
||||
|
||||
** Documentation & Standards
|
||||
- **Common Lisp HyperSpec**: http://www.lispworks.com/documentation/HyperSpec/Front/
|
||||
- **Docker Compose**: https://docs.docker.com/compose/
|
||||
- **Org Mode** (Documentation format): https://orgmode.org/
|
||||
|
|
@ -0,0 +1,614 @@
|
|||
#+TITLE: Asteroid Radio - Docker Streaming Setup
|
||||
#+AUTHOR: Docker Team
|
||||
#+DATE: 2025-10-03
|
||||
|
||||
* Docker Streaming Overview
|
||||
|
||||
This guide covers the complete Docker-based streaming setup for Asteroid Radio using Icecast2 and Liquidsoap containers. This approach provides a containerized, portable streaming infrastructure that's easy to deploy and maintain.
|
||||
|
||||
* Architecture
|
||||
|
||||
** Container Stack
|
||||
- *Icecast2 Container*: Streaming server handling client connections
|
||||
- *Liquidsoap Container*: Audio processing and stream generation
|
||||
- *Shared Volumes*: Music library and configuration sharing
|
||||
|
||||
** Stream Formats
|
||||
- *High Quality MP3*: 128kbps MP3 stream at /asteroid.mp3
|
||||
- *High Quality AAC*: 96kbps AAC stream at /asteroid.aac (better efficiency than MP3)
|
||||
- *Low Quality MP3*: 64kbps MP3 stream at /asteroid-low.mp3 (compatibility)
|
||||
|
||||
** Network Configuration
|
||||
- *Icecast2*: Port 8000 (streaming and admin)
|
||||
- *Liquidsoap Telnet*: Port 1234 (remote control)
|
||||
- *Internal Network*: Container-to-container communication
|
||||
|
||||
* Quick Start
|
||||
|
||||
** Prerequisites
|
||||
#+BEGIN_SRC bash
|
||||
# Install Docker and Docker Compose
|
||||
sudo apt update
|
||||
sudo apt install docker.io docker compose
|
||||
sudo usermod -a -G docker $USER
|
||||
# Log out and back in for group changes
|
||||
#+END_SRC
|
||||
|
||||
** One-Command Setup
|
||||
#+BEGIN_SRC bash
|
||||
# Clone and start
|
||||
git clone <repository-url> asteroid-radio
|
||||
cd asteroid-radio/docker
|
||||
docker compose up -d
|
||||
#+END_SRC
|
||||
|
||||
** Verify Setup
|
||||
#+BEGIN_SRC bash
|
||||
# Check container status
|
||||
docker compose ps
|
||||
|
||||
# Test streaming (all three formats)
|
||||
curl -I http://localhost:8000/asteroid.mp3 # 128kbps MP3
|
||||
curl -I http://localhost:8000/asteroid.aac # 96kbps AAC
|
||||
curl -I http://localhost:8000/asteroid-low.mp3 # 64kbps MP3
|
||||
#+END_SRC
|
||||
|
||||
* Docker Compose Configuration
|
||||
|
||||
** Complete docker-compose.yml
|
||||
#+BEGIN_SRC yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
icecast:
|
||||
image: infiniteproject/icecast:latest
|
||||
container_name: asteroid-icecast
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./icecast.xml:/etc/icecast.xml
|
||||
environment:
|
||||
- ICECAST_SOURCE_PASSWORD=H1tn31EhsyLrfRmo
|
||||
- ICECAST_ADMIN_PASSWORD=asteroid_admin_2024
|
||||
- ICECAST_RELAY_PASSWORD=asteroid_relay_2024
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- asteroid-network
|
||||
|
||||
liquidsoap:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.liquidsoap
|
||||
container_name: asteroid-liquidsoap
|
||||
ports:
|
||||
- "1234:1234" # Telnet control port
|
||||
depends_on:
|
||||
- icecast
|
||||
volumes:
|
||||
- ./music:/app/music:ro
|
||||
- ./asteroid-radio-docker.liq:/app/asteroid-radio.liq:ro
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- asteroid-network
|
||||
|
||||
networks:
|
||||
asteroid-network:
|
||||
driver: bridge
|
||||
#+END_SRC
|
||||
|
||||
* Container Configurations
|
||||
|
||||
** Icecast2 Container Setup
|
||||
|
||||
*** Custom Icecast Configuration (icecast.xml)
|
||||
#+BEGIN_SRC xml
|
||||
<icecast>
|
||||
<location>Asteroid Radio Docker</location>
|
||||
<admin>admin@asteroid-radio.docker</admin>
|
||||
|
||||
<limits>
|
||||
<clients>100</clients>
|
||||
<sources>10</sources>
|
||||
<queue-size>524288</queue-size>
|
||||
<client-timeout>30</client-timeout>
|
||||
<header-timeout>15</header-timeout>
|
||||
<source-timeout>10</source-timeout>
|
||||
<burst-on-connect>1</burst-on-connect>
|
||||
</limits>
|
||||
|
||||
<authentication>
|
||||
<source-password>H1tn31EhsyLrfRmo</source-password>
|
||||
<relay-password>asteroid_relay_2024</relay-password>
|
||||
<admin-user>admin</admin-user>
|
||||
<admin-password>asteroid_admin_2024</admin-password>
|
||||
</authentication>
|
||||
|
||||
<hostname>icecast</hostname>
|
||||
<listen-socket>
|
||||
<port>8000</port>
|
||||
<bind-address>0.0.0.0</bind-address>
|
||||
</listen-socket>
|
||||
|
||||
<!-- High Quality Stream -->
|
||||
<mount type="normal">
|
||||
<mount-name>/asteroid.mp3</mount-name>
|
||||
<username>source</username>
|
||||
<password>H1tn31EhsyLrfRmo</password>
|
||||
<max-listeners>50</max-listeners>
|
||||
<public>1</public>
|
||||
<stream-name>Asteroid Radio - High Quality</stream-name>
|
||||
<stream-url>http://localhost:8080/asteroid/</stream-url>
|
||||
<genre>Electronic/Alternative</genre>
|
||||
<bitrate>128</bitrate>
|
||||
</mount>
|
||||
|
||||
<!-- AAC High Quality Stream -->
|
||||
<mount type="normal">
|
||||
<mount-name>/asteroid.aac</mount-name>
|
||||
<username>source</username>
|
||||
<password>H1tn31EhsyLrfRmo</password>
|
||||
<max-listeners>50</max-listeners>
|
||||
<public>1</public>
|
||||
<stream-name>Asteroid Radio - AAC</stream-name>
|
||||
<stream-description>Music for Hackers - 96kbps AAC</stream-description>
|
||||
<stream-url>http://localhost:8080/asteroid/</stream-url>
|
||||
<genre>Electronic/Alternative</genre>
|
||||
<bitrate>96</bitrate>
|
||||
</mount>
|
||||
|
||||
<!-- Low Quality Stream -->
|
||||
<mount type="normal">
|
||||
<mount-name>/asteroid-low.mp3</mount-name>
|
||||
<username>source</username>
|
||||
<password>H1tn31EhsyLrfRmo</password>
|
||||
<max-listeners>100</max-listeners>
|
||||
<public>1</public>
|
||||
<stream-name>Asteroid Radio - Low Quality</stream-name>
|
||||
<stream-description>Music for Hackers - 64kbps</stream-description>
|
||||
<stream-url>http://localhost:8080/asteroid/</stream-url>
|
||||
<genre>Electronic/Alternative</genre>
|
||||
<bitrate>64</bitrate>
|
||||
</mount>
|
||||
|
||||
<fileserve>1</fileserve>
|
||||
<paths>
|
||||
<basedir>/usr/share/icecast2</basedir>
|
||||
<logdir>/var/log/icecast2</logdir>
|
||||
<webroot>/usr/share/icecast2/web</webroot>
|
||||
<adminroot>/usr/share/icecast2/admin</adminroot>
|
||||
<alias source="/" destination="/status.xsl"/>
|
||||
</paths>
|
||||
|
||||
<logging>
|
||||
<accesslog>access.log</accesslog>
|
||||
<errorlog>error.log</errorlog>
|
||||
<loglevel>3</loglevel>
|
||||
<logsize>10000</logsize>
|
||||
</logging>
|
||||
</icecast>
|
||||
#+END_SRC
|
||||
|
||||
** Liquidsoap Container Setup
|
||||
|
||||
*** Liquidsoap Configuration (asteroid-radio-docker.liq)
|
||||
#+BEGIN_SRC liquidsoap
|
||||
#!/usr/bin/liquidsoap
|
||||
|
||||
# Asteroid Radio - Docker streaming script
|
||||
# Streams music library continuously to Icecast2 running in Docker
|
||||
|
||||
# Allow running as root in Docker
|
||||
set("init.allow_root", true)
|
||||
|
||||
# Set log level for debugging
|
||||
log.level.set(4)
|
||||
|
||||
# Enable telnet server for remote control
|
||||
settings.server.telnet.set(true)
|
||||
settings.server.telnet.port.set(1234)
|
||||
settings.server.telnet.bind_addr.set("0.0.0.0")
|
||||
|
||||
# Create playlist source from mounted music directory
|
||||
radio = playlist(
|
||||
mode="randomize",
|
||||
reload=3600,
|
||||
reload_mode="watch",
|
||||
"/app/music/"
|
||||
)
|
||||
|
||||
# Add some audio processing
|
||||
radio = amplify(1.0, radio)
|
||||
radio = normalize(radio)
|
||||
|
||||
# Add crossfade between tracks
|
||||
radio = crossfade(radio)
|
||||
|
||||
# Create a fallback with emergency content
|
||||
emergency = sine(440.0)
|
||||
emergency = amplify(0.1, emergency)
|
||||
|
||||
# Make source safe with fallback
|
||||
radio = fallback(track_sensitive=false, [radio, emergency])
|
||||
|
||||
# Add metadata
|
||||
radio = map_metadata(fun(m) ->
|
||||
[("title", m["title"] ?? "Unknown Track"),
|
||||
("artist", m["artist"] ?? "Unknown Artist"),
|
||||
("album", m["album"] ?? "Unknown Album")], radio)
|
||||
|
||||
# High Quality MP3 Stream (128kbps)
|
||||
output.icecast(
|
||||
%mp3(bitrate=128),
|
||||
host="icecast", # Docker service name
|
||||
port=8000,
|
||||
password="H1tn31EhsyLrfRmo",
|
||||
mount="asteroid.mp3",
|
||||
name="Asteroid Radio",
|
||||
description="Music for Hackers - Streaming from the Asteroid",
|
||||
genre="Electronic/Alternative",
|
||||
url="http://localhost:8080/asteroid/",
|
||||
public=true,
|
||||
radio
|
||||
)
|
||||
|
||||
# AAC High Quality Stream (96kbps - better quality than 128kbps MP3)
|
||||
output.icecast(
|
||||
%fdkaac(bitrate=96),
|
||||
host="icecast",
|
||||
port=8000,
|
||||
password="H1tn31EhsyLrfRmo",
|
||||
mount="asteroid.aac",
|
||||
name="Asteroid Radio (AAC)",
|
||||
description="Music for Hackers - High efficiency AAC stream",
|
||||
genre="Electronic/Alternative",
|
||||
url="http://localhost:8080/asteroid/",
|
||||
public=true,
|
||||
radio
|
||||
)
|
||||
|
||||
# Low Quality MP3 Stream (for compatibility)
|
||||
output.icecast(
|
||||
%mp3(bitrate=64),
|
||||
host="icecast",
|
||||
port=8000,
|
||||
password="H1tn31EhsyLrfRmo",
|
||||
mount="asteroid-low.mp3",
|
||||
name="Asteroid Radio (Low Quality)",
|
||||
description="Music for Hackers - Low bandwidth stream",
|
||||
genre="Electronic/Alternative",
|
||||
url="http://localhost:8080/asteroid/",
|
||||
public=true,
|
||||
radio
|
||||
)
|
||||
|
||||
print("🎵 Asteroid Radio Docker streaming started!")
|
||||
print("High Quality MP3: http://localhost:8000/asteroid.mp3")
|
||||
print("High Quality AAC: http://localhost:8000/asteroid.aac")
|
||||
print("Low Quality MP3: http://localhost:8000/asteroid-low.mp3")
|
||||
print("Icecast Admin: http://localhost:8000/admin/")
|
||||
print("Telnet control: telnet localhost 1234")
|
||||
#+END_SRC
|
||||
|
||||
|
||||
* Management Scripts
|
||||
|
||||
** Start Script (start-streaming.sh)
|
||||
#+BEGIN_SRC bash
|
||||
#!/bin/bash
|
||||
|
||||
# Asteroid Radio Docker Streaming Startup Script
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting Asteroid Radio Docker Streaming..."
|
||||
|
||||
# Check if Docker is running
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo "❌ Docker is not running. Please start Docker first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create required directories
|
||||
mkdir -p music/incoming music/library logs
|
||||
|
||||
# Set permissions
|
||||
chmod 755 music/incoming music/library
|
||||
chmod 777 logs
|
||||
|
||||
# Pull latest images
|
||||
echo "📦 Pulling latest Docker images..."
|
||||
docker compose pull
|
||||
|
||||
# Start services
|
||||
echo "🎵 Starting streaming services..."
|
||||
docker compose up -d
|
||||
|
||||
# Wait for services to be ready
|
||||
echo "⏳ Waiting for services to start..."
|
||||
sleep 10
|
||||
|
||||
# Check service status
|
||||
echo "📊 Checking service status..."
|
||||
docker compose ps
|
||||
|
||||
# Test connectivity
|
||||
echo "🔍 Testing streaming connectivity..."
|
||||
if curl -s -I http://localhost:8000/asteroid.mp3 | grep -q "200 OK"; then
|
||||
echo "✅ High quality stream is working"
|
||||
else
|
||||
echo "⚠️ High quality stream may not be ready yet"
|
||||
fi
|
||||
|
||||
if curl -s -I http://localhost:8000/asteroid-low.mp3 | grep -q "200 OK"; then
|
||||
echo "✅ Low quality MP3 stream is working"
|
||||
else
|
||||
echo "⚠️ Low quality MP3 stream may not be ready yet"
|
||||
fi
|
||||
|
||||
if curl -s -I http://localhost:8000/asteroid.aac | grep -q "200 OK"; then
|
||||
echo "✅ AAC stream is working"
|
||||
else
|
||||
echo "⚠️ AAC stream may not be ready yet"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 Asteroid Radio Docker setup complete!"
|
||||
echo ""
|
||||
echo "📻 Stream URLs:"
|
||||
echo " High Quality MP3: http://localhost:8000/asteroid.mp3 (128kbps)"
|
||||
echo " High Quality AAC: http://localhost:8000/asteroid.aac (96kbps)"
|
||||
echo " Low Quality MP3: http://localhost:8000/asteroid-low.mp3 (64kbps)"
|
||||
echo ""
|
||||
echo "🔧 Admin Interfaces:"
|
||||
echo " Icecast: http://localhost:8000/admin/ (admin/asteroid_admin_2024)"
|
||||
echo " Telnet: telnet localhost 1234"
|
||||
echo ""
|
||||
echo "📁 Add music files to: ./music/"
|
||||
echo " Files are automatically detected and streamed."
|
||||
#+END_SRC
|
||||
|
||||
** Stop Script (stop-streaming.sh)
|
||||
#+BEGIN_SRC bash
|
||||
#!/bin/bash
|
||||
|
||||
# Asteroid Radio Docker Streaming Stop Script
|
||||
|
||||
echo "🛑 Stopping Asteroid Radio Docker Streaming..."
|
||||
|
||||
# Stop all services
|
||||
docker compose down
|
||||
|
||||
# Optional: Remove volumes (uncomment to clean up completely)
|
||||
# docker compose down -v
|
||||
|
||||
echo "✅ All services stopped."
|
||||
#+END_SRC
|
||||
|
||||
** Test Script (test-streaming.sh)
|
||||
#+BEGIN_SRC bash
|
||||
#!/bin/bash
|
||||
|
||||
# Asteroid Radio Docker Streaming Test Script
|
||||
|
||||
echo "🧪 Testing Asteroid Radio Docker Setup..."
|
||||
|
||||
# Test container status
|
||||
echo "📊 Container Status:"
|
||||
docker compose ps
|
||||
|
||||
echo ""
|
||||
echo "🔍 Testing Connectivity:"
|
||||
|
||||
# Test Icecast2
|
||||
if curl -s -I http://localhost:8000/ | grep -q "200 OK"; then
|
||||
echo "✅ Icecast2 server is responding"
|
||||
else
|
||||
echo "❌ Icecast2 server is not responding"
|
||||
fi
|
||||
|
||||
# Test high quality stream
|
||||
if curl -s -I http://localhost:8000/asteroid.mp3 | grep -q "200 OK"; then
|
||||
echo "✅ High quality stream is available"
|
||||
else
|
||||
echo "❌ High quality stream is not available"
|
||||
fi
|
||||
|
||||
# Test low quality stream
|
||||
if curl -s -I http://localhost:8000/asteroid-low.mp3 | grep -q "200 OK"; then
|
||||
echo "✅ Low quality MP3 stream is available"
|
||||
else
|
||||
echo "❌ Low quality MP3 stream is not available"
|
||||
fi
|
||||
|
||||
# Test AAC stream
|
||||
if curl -s -I http://localhost:8000/asteroid.aac | grep -q "200 OK"; then
|
||||
echo "✅ AAC stream is available"
|
||||
else
|
||||
echo "❌ AAC stream is not available"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📋 Service Logs (last 10 lines):"
|
||||
echo "--- Icecast2 ---"
|
||||
docker compose logs --tail=10 icecast
|
||||
|
||||
echo "--- Liquidsoap ---"
|
||||
docker compose logs --tail=10 liquidsoap
|
||||
|
||||
#+END_SRC
|
||||
|
||||
* Volume Management
|
||||
|
||||
** Music Library Setup
|
||||
#+BEGIN_SRC bash
|
||||
# Music directory already exists in repository
|
||||
# Copy sample music directly to the music directory
|
||||
cp ~/path/to/music/*.mp3 docker/music/
|
||||
|
||||
# Set permissions
|
||||
chmod 755 docker/music/
|
||||
sudo chown -R $USER:$USER docker/music/
|
||||
#+END_SRC
|
||||
|
||||
** Persistent Data
|
||||
- *Music Library*: =./music/= - Mounted as volume
|
||||
- *Logs*: =./logs/= - Container logs and streaming logs
|
||||
- *Configuration*: =./liquidsoap/= and =./icecast.xml= - Read-only configs
|
||||
|
||||
* Networking
|
||||
|
||||
** Internal Container Network
|
||||
- Containers communicate via =asteroid-network= bridge
|
||||
- Liquidsoap connects to Icecast using hostname =icecast=
|
||||
- Telnet control available on port 1234 for Liquidsoap management
|
||||
|
||||
** External Access
|
||||
- *Port 8000*: Icecast2 streaming and admin interface
|
||||
- *Port 1234*: Liquidsoap telnet control interface
|
||||
- All services bind to =0.0.0.0= for external access
|
||||
|
||||
** WSL Compatibility
|
||||
#+BEGIN_SRC bash
|
||||
# Find WSL IP for external access
|
||||
ip addr show eth0 | grep inet
|
||||
|
||||
# Access from Windows host
|
||||
# http://[IP-ADDRESS]:8000/asteroid.mp3 # 128kbps MP3
|
||||
# http://[IP-ADDRESS]:8000/asteroid.aac # 96kbps AAC
|
||||
# http://[IP-ADDRESS]:8000/asteroid-low.mp3 # 64kbps MP3
|
||||
#+END_SRC
|
||||
|
||||
* Production Deployment
|
||||
|
||||
** Docker Swarm Setup
|
||||
#+BEGIN_SRC yaml
|
||||
# docker compose.prod.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
icecast:
|
||||
image: moul/icecast
|
||||
deploy:
|
||||
replicas: 1
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
# ... rest of configuration
|
||||
|
||||
liquidsoap:
|
||||
image: savonet/liquidsoap:v2.2.x
|
||||
deploy:
|
||||
replicas: 1
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
# ... rest of configuration
|
||||
#+END_SRC
|
||||
|
||||
** Environment Variables
|
||||
#+BEGIN_SRC bash
|
||||
# Production environment
|
||||
export ASTEROID_ENV=production
|
||||
export ASTEROID_STREAM_QUALITY=high
|
||||
export ASTEROID_MAX_LISTENERS=200
|
||||
export ICECAST_ADMIN_PASSWORD=secure_password_here
|
||||
#+END_SRC
|
||||
|
||||
** SSL/TLS Setup
|
||||
Use reverse proxy (nginx/traefik) for HTTPS termination:
|
||||
#+BEGIN_SRC yaml
|
||||
# Add to docker-compose.yml
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./ssl:/etc/ssl:ro
|
||||
#+END_SRC
|
||||
|
||||
* Monitoring and Logging
|
||||
|
||||
** Container Health Checks
|
||||
#+BEGIN_SRC bash
|
||||
# Check container health
|
||||
docker compose exec icecast curl -f http://localhost:8000/status.xsl
|
||||
docker compose exec liquidsoap ps aux | grep liquidsoap
|
||||
|
||||
# Test telnet control interface
|
||||
echo "help" | nc localhost 1234
|
||||
#+END_SRC
|
||||
|
||||
** Log Management
|
||||
#+BEGIN_SRC bash
|
||||
# View real-time logs
|
||||
docker compose logs -f
|
||||
|
||||
# View specific service logs
|
||||
docker compose logs -f icecast
|
||||
docker compose logs -f liquidsoap
|
||||
|
||||
# Log rotation setup
|
||||
docker run --log-driver=json-file --log-opt max-size=10m --log-opt max-file=3
|
||||
#+END_SRC
|
||||
|
||||
* Troubleshooting
|
||||
|
||||
** Common Docker Issues
|
||||
|
||||
*** Container Won't Start
|
||||
#+BEGIN_SRC bash
|
||||
# Check container logs
|
||||
docker compose logs [service-name]
|
||||
|
||||
# Check resource usage
|
||||
docker stats
|
||||
|
||||
# Verify configuration files
|
||||
docker compose config
|
||||
#+END_SRC
|
||||
|
||||
*** Streaming Issues
|
||||
#+BEGIN_SRC bash
|
||||
# Test internal connectivity
|
||||
docker compose exec liquidsoap ping icecast
|
||||
|
||||
# Check Liquidsoap connection and logs
|
||||
docker compose logs liquidsoap
|
||||
|
||||
# Test telnet interface
|
||||
echo "request.queue" | nc localhost 1234
|
||||
#+END_SRC
|
||||
|
||||
*** Permission Issues
|
||||
#+BEGIN_SRC bash
|
||||
# Fix music directory permissions
|
||||
sudo chown -R $USER:$USER docker/music/
|
||||
chmod 755 docker/music/
|
||||
#+END_SRC
|
||||
|
||||
** Performance Tuning
|
||||
|
||||
*** Resource Limits
|
||||
#+BEGIN_SRC yaml
|
||||
# Add to services in docker-compose.yml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: '0.5'
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: '0.25'
|
||||
#+END_SRC
|
||||
|
||||
*** Network Optimization
|
||||
#+BEGIN_SRC yaml
|
||||
# Optimize network settings
|
||||
networks:
|
||||
asteroid-network:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.driver.mtu: 1500
|
||||
#+END_SRC
|
||||
|
||||
This Docker streaming setup provides a complete containerized solution for Asteroid Radio with professional streaming capabilities and easy deployment.
|
||||
|
|
@ -0,0 +1,561 @@
|
|||
#+TITLE: Asteroid Radio - Installation Guide
|
||||
#+AUTHOR: Installation Team
|
||||
#+DATE: 2025-10-03
|
||||
|
||||
* Installation Overview
|
||||
|
||||
This guide covers the complete installation and deployment of Asteroid Radio. The **recommended approach** is Docker-based installation for easy deployment and consistency. Native installation is also available for development or custom deployments.
|
||||
|
||||
* Quick Start (Docker - Recommended)
|
||||
|
||||
** Prerequisites Check
|
||||
#+BEGIN_SRC bash
|
||||
# Check if Docker is installed and running
|
||||
docker --version
|
||||
docker compose version
|
||||
docker info
|
||||
#+END_SRC
|
||||
|
||||
** One-Command Setup
|
||||
#+BEGIN_SRC bash
|
||||
# Clone and setup (replace with actual repository URL)
|
||||
git clone <repository-url> asteroid-radio
|
||||
cd asteroid-radio/docker
|
||||
docker compose up -d
|
||||
#+END_SRC
|
||||
|
||||
** Verify Installation
|
||||
#+BEGIN_SRC bash
|
||||
# Check all three streams are working
|
||||
curl -I http://localhost:8000/asteroid.mp3 # 128kbps MP3
|
||||
curl -I http://localhost:8000/asteroid.aac # 96kbps AAC
|
||||
curl -I http://localhost:8000/asteroid-low.mp3 # 64kbps MP3
|
||||
#+END_SRC
|
||||
|
||||
* Detailed Installation
|
||||
|
||||
** System Requirements
|
||||
|
||||
*** Docker Installation Requirements
|
||||
- *OS*: Any OS with Docker support (Linux, macOS, Windows)
|
||||
- *Docker*: Docker Engine 20.10+ and Docker Compose 2.0+
|
||||
- *RAM*: 2GB minimum, 4GB recommended
|
||||
- *Storage*: 20GB minimum, 500GB+ for music library
|
||||
- *CPU*: 2 cores minimum, 4+ cores recommended
|
||||
- *Network*: Stable internet connection for streaming
|
||||
|
||||
*** Native Installation Requirements (Advanced)
|
||||
- *OS*: Ubuntu 20.04+ / Debian 11+ (for native installation)
|
||||
- *RAM*: 1GB minimum, 2GB recommended
|
||||
- *Storage*: 10GB minimum, 100GB+ for music library
|
||||
- *CPU*: 1 core minimum, 2+ cores recommended
|
||||
- *Dependencies*: SBCL, Icecast2, Liquidsoap, TagLib
|
||||
|
||||
** Docker Installation (Recommended)
|
||||
|
||||
*** Step 1: Install Docker
|
||||
#+BEGIN_SRC bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt update
|
||||
sudo apt install -y docker.io docker compose
|
||||
sudo usermod -a -G docker $USER
|
||||
# Log out and back in for group changes
|
||||
|
||||
# CentOS/RHEL
|
||||
sudo dnf install -y docker docker compose
|
||||
sudo systemctl enable --now docker
|
||||
sudo usermod -a -G docker $USER
|
||||
|
||||
# macOS
|
||||
brew install docker docker compose
|
||||
# Or install Docker Desktop
|
||||
|
||||
# Windows
|
||||
# Install Docker Desktop from docker.com
|
||||
#+END_SRC
|
||||
|
||||
*** Step 2: Clone and Setup
|
||||
#+BEGIN_SRC bash
|
||||
# Clone repository
|
||||
git clone <repository-url> asteroid-radio
|
||||
cd asteroid-radio/docker
|
||||
|
||||
# Start services
|
||||
docker compose up -d
|
||||
|
||||
# Check status
|
||||
docker compose ps
|
||||
#+END_SRC
|
||||
|
||||
*** Step 3: Add Music
|
||||
#+BEGIN_SRC bash
|
||||
# Copy music files to the docker music directory
|
||||
cp ~/path/to/music/*.mp3 music/
|
||||
cp ~/path/to/music/*.flac music/
|
||||
|
||||
# Set proper permissions
|
||||
sudo chown -R $USER:$USER music/
|
||||
#+END_SRC
|
||||
|
||||
*** Step 4: Access Streams
|
||||
- **High Quality MP3**: http://localhost:8000/asteroid.mp3 (128kbps)
|
||||
- **High Quality AAC**: http://localhost:8000/asteroid.aac (96kbps)
|
||||
- **Low Quality MP3**: http://localhost:8000/asteroid-low.mp3 (64kbps)
|
||||
- **Icecast Admin**: http://localhost:8000/admin/ (admin/asteroid_admin_2024)
|
||||
- **Telnet Control**: =telnet localhost 1234=
|
||||
|
||||
** Native Installation (Advanced Users)
|
||||
|
||||
*** Step 1: System Updates
|
||||
#+BEGIN_SRC bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
#+END_SRC
|
||||
|
||||
*** Step 2: Install System Dependencies
|
||||
#+BEGIN_SRC bash
|
||||
# Core dependencies
|
||||
sudo apt install -y sbcl git curl wget build-essential
|
||||
|
||||
# Streaming dependencies
|
||||
sudo apt install -y icecast2 liquidsoap
|
||||
|
||||
# Audio processing dependencies
|
||||
sudo apt install -y libtag1-dev libtagc0-dev
|
||||
|
||||
# Optional: Development tools
|
||||
sudo apt install -y emacs vim htop tree
|
||||
#+END_SRC
|
||||
|
||||
*** Step 3: Configure Icecast2
|
||||
#+BEGIN_SRC bash
|
||||
# Configure Icecast2 during installation
|
||||
sudo dpkg-reconfigure icecast2
|
||||
|
||||
# Or manually edit configuration
|
||||
sudo nano /etc/icecast2/icecast.xml
|
||||
#+END_SRC
|
||||
|
||||
*Icecast2 Configuration*:
|
||||
#+BEGIN_SRC xml
|
||||
<icecast>
|
||||
<location>Asteroid Radio Station</location>
|
||||
<admin>admin@asteroid-radio.local</admin>
|
||||
|
||||
<limits>
|
||||
<clients>100</clients>
|
||||
<sources>2</sources>
|
||||
<queue-size>524288</queue-size>
|
||||
<client-timeout>30</client-timeout>
|
||||
<header-timeout>15</header-timeout>
|
||||
<source-timeout>10</source-timeout>
|
||||
</limits>
|
||||
|
||||
<authentication>
|
||||
<source-password>b3l0wz3r0</source-password>
|
||||
<relay-password>asteroid_relay_2024</relay-password>
|
||||
<admin-user>admin</admin-user>
|
||||
<admin-password>asteroid_admin_2024</admin-password>
|
||||
</authentication>
|
||||
|
||||
<hostname>localhost</hostname>
|
||||
<listen-socket>
|
||||
<port>8000</port>
|
||||
</listen-socket>
|
||||
|
||||
<mount type="normal">
|
||||
<mount-name>/asteroid.mp3</mount-name>
|
||||
<username>source</username>
|
||||
<password>b3l0wz3r0</password>
|
||||
<max-listeners>50</max-listeners>
|
||||
<dump-file>/var/log/icecast2/asteroid.dump</dump-file>
|
||||
<burst-on-connect>1</burst-on-connect>
|
||||
<fallback-mount>/silence.mp3</fallback-mount>
|
||||
<fallback-override>1</fallback-override>
|
||||
</mount>
|
||||
|
||||
<fileserve>1</fileserve>
|
||||
<paths>
|
||||
<basedir>/usr/share/icecast2</basedir>
|
||||
<logdir>/var/log/icecast2</logdir>
|
||||
<webroot>/usr/share/icecast2/web</webroot>
|
||||
<adminroot>/usr/share/icecast2/admin</adminroot>
|
||||
<alias source="/" destination="/status.xsl"/>
|
||||
</paths>
|
||||
|
||||
<logging>
|
||||
<accesslog>access.log</accesslog>
|
||||
<errorlog>error.log</errorlog>
|
||||
<loglevel>3</loglevel>
|
||||
<logsize>10000</logsize>
|
||||
</logging>
|
||||
</icecast>
|
||||
#+END_SRC
|
||||
|
||||
*** Step 4: Install Quicklisp
|
||||
#+BEGIN_SRC bash
|
||||
# Download and install Quicklisp
|
||||
cd /tmp
|
||||
curl -O https://beta.quicklisp.org/quicklisp.lisp
|
||||
sbcl --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql:add-to-init-file)" --quit
|
||||
#+END_SRC
|
||||
|
||||
*** Step 5: Clone and Setup Project
|
||||
#+BEGIN_SRC bash
|
||||
# Clone repository (replace with actual URL)
|
||||
git clone <repository-url> /opt/asteroid-radio
|
||||
cd /opt/asteroid-radio
|
||||
|
||||
# Create required directories
|
||||
sudo mkdir -p music/incoming music/library static template
|
||||
sudo chown -R $USER:$USER music/
|
||||
|
||||
# Set permissions
|
||||
chmod 755 music/incoming music/library
|
||||
chmod +x *.sh
|
||||
#+END_SRC
|
||||
|
||||
*** Step 6: Install Lisp Dependencies
|
||||
#+BEGIN_SRC bash
|
||||
# Start SBCL and install dependencies
|
||||
sbcl --eval "(ql:quickload :asteroid)" --quit
|
||||
#+END_SRC
|
||||
|
||||
** CentOS/RHEL Installation
|
||||
|
||||
*** Step 1: Enable EPEL Repository
|
||||
#+BEGIN_SRC bash
|
||||
sudo dnf install -y epel-release
|
||||
sudo dnf update -y
|
||||
#+END_SRC
|
||||
|
||||
*** Step 2: Install Dependencies
|
||||
#+BEGIN_SRC bash
|
||||
# Core dependencies
|
||||
sudo dnf install -y sbcl git curl wget gcc make
|
||||
|
||||
# Streaming dependencies (may require additional repositories)
|
||||
sudo dnf install -y icecast liquidsoap
|
||||
|
||||
# Audio processing
|
||||
sudo dnf install -y taglib-devel
|
||||
#+END_SRC
|
||||
|
||||
*** Step 3: Follow Ubuntu Steps 3-6
|
||||
The remaining steps are similar to Ubuntu installation.
|
||||
|
||||
** macOS Installation (Development Only)
|
||||
|
||||
*** Step 1: Install Homebrew
|
||||
#+BEGIN_SRC bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
#+END_SRC
|
||||
|
||||
*** Step 2: Install Dependencies
|
||||
#+BEGIN_SRC bash
|
||||
# Core dependencies
|
||||
brew install sbcl git
|
||||
|
||||
# Streaming dependencies
|
||||
brew install icecast2 liquidsoap
|
||||
|
||||
# Audio processing
|
||||
brew install taglib
|
||||
#+END_SRC
|
||||
|
||||
*** Step 3: Follow Similar Setup Steps
|
||||
Adapt the Linux steps for macOS paths and conventions.
|
||||
|
||||
* Service Configuration
|
||||
|
||||
** Systemd Service Setup (Linux)
|
||||
|
||||
*** Icecast2 Service
|
||||
#+BEGIN_SRC bash
|
||||
# Enable and start Icecast2
|
||||
sudo systemctl enable icecast2
|
||||
sudo systemctl start icecast2
|
||||
sudo systemctl status icecast2
|
||||
#+END_SRC
|
||||
|
||||
*** Asteroid Radio Service
|
||||
Create systemd service file:
|
||||
#+BEGIN_SRC bash
|
||||
sudo nano /etc/systemd/system/asteroid-radio.service
|
||||
#+END_SRC
|
||||
|
||||
*Service Configuration*:
|
||||
#+BEGIN_SRC ini
|
||||
[Unit]
|
||||
Description=Asteroid Radio Streaming Service
|
||||
After=network.target icecast2.service
|
||||
Requires=icecast2.service
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
User=asteroid
|
||||
Group=asteroid
|
||||
WorkingDirectory=/opt/asteroid-radio
|
||||
ExecStart=/opt/asteroid-radio/start-asteroid-radio.sh
|
||||
ExecStop=/opt/asteroid-radio/stop-asteroid-radio.sh
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
#+END_SRC
|
||||
|
||||
*** Enable and Start Service
|
||||
#+BEGIN_SRC bash
|
||||
# Create service user
|
||||
sudo useradd -r -s /bin/false asteroid
|
||||
sudo chown -R asteroid:asteroid /opt/asteroid-radio
|
||||
|
||||
# Enable and start service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable asteroid-radio
|
||||
sudo systemctl start asteroid-radio
|
||||
sudo systemctl status asteroid-radio
|
||||
#+END_SRC
|
||||
|
||||
* Network Configuration
|
||||
|
||||
** Firewall Setup
|
||||
|
||||
*** Ubuntu/Debian (ufw)
|
||||
#+BEGIN_SRC bash
|
||||
# Allow required ports
|
||||
sudo ufw allow 8000/tcp # Icecast2 streaming and admin
|
||||
sudo ufw allow 1234/tcp # Liquidsoap telnet control (optional)
|
||||
sudo ufw enable
|
||||
#+END_SRC
|
||||
|
||||
*** CentOS/RHEL (firewalld)
|
||||
#+BEGIN_SRC bash
|
||||
# Allow required ports
|
||||
sudo firewall-cmd --permanent --add-port=8000/tcp # Icecast2
|
||||
sudo firewall-cmd --permanent --add-port=1234/tcp # Liquidsoap telnet (optional)
|
||||
sudo firewall-cmd --reload
|
||||
#+END_SRC
|
||||
|
||||
** Reverse Proxy Setup (Optional)
|
||||
|
||||
*** Nginx Configuration
|
||||
#+BEGIN_SRC bash
|
||||
# Install Nginx
|
||||
sudo apt install nginx
|
||||
|
||||
# Create configuration
|
||||
sudo nano /etc/nginx/sites-available/asteroid-radio
|
||||
#+END_SRC
|
||||
|
||||
*Nginx Configuration*:
|
||||
#+BEGIN_SRC nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
|
||||
# Web interface
|
||||
location / {
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
# Streaming endpoint
|
||||
location /stream {
|
||||
proxy_pass http://localhost:8000/asteroid.mp3;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
*** Enable Nginx Site
|
||||
#+BEGIN_SRC bash
|
||||
sudo ln -s /etc/nginx/sites-available/asteroid-radio /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
#+END_SRC
|
||||
|
||||
* Docker Management
|
||||
|
||||
** Container Management
|
||||
#+BEGIN_SRC bash
|
||||
# Start services
|
||||
docker compose up -d
|
||||
|
||||
# Stop services
|
||||
docker compose down
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Restart services
|
||||
docker compose restart
|
||||
#+END_SRC
|
||||
|
||||
** Docker Configuration
|
||||
See =docker/docker-compose.yml= for complete Docker setup with Icecast2 and Liquidsoap containers. The setup includes:
|
||||
- **Icecast2**: Streaming server with three output formats
|
||||
- **Liquidsoap**: Audio processing and stream generation
|
||||
- **Music Volume**: Mounted from =./music/= directory
|
||||
|
||||
* Initial Configuration
|
||||
|
||||
** First-Time Setup
|
||||
|
||||
*** Access Streaming Services
|
||||
1. **Icecast Admin**: http://localhost:8000/admin/ (admin/asteroid_admin_2024)
|
||||
2. **Stream URLs**:
|
||||
- High Quality MP3: http://localhost:8000/asteroid.mp3 (128kbps)
|
||||
- High Quality AAC: http://localhost:8000/asteroid.aac (96kbps)
|
||||
- Low Quality MP3: http://localhost:8000/asteroid-low.mp3 (64kbps)
|
||||
3. **Telnet Control**: =telnet localhost 1234= (for Liquidsoap management)
|
||||
|
||||
*** Add Music Library
|
||||
#+BEGIN_SRC bash
|
||||
# Copy music files to music directory
|
||||
cp ~/path/to/music/*.mp3 ~/asteroid-radio/music/
|
||||
|
||||
# Files are automatically detected by Liquidsoap
|
||||
# No additional processing needed - just add files to the music directory
|
||||
#+END_SRC
|
||||
|
||||
*** Test Streaming
|
||||
#+BEGIN_SRC bash
|
||||
# Test all streams with curl
|
||||
curl -I http://localhost:8000/asteroid.mp3 # 128kbps MP3
|
||||
curl -I http://localhost:8000/asteroid.aac # 96kbps AAC
|
||||
curl -I http://localhost:8000/asteroid-low.mp3 # 64kbps MP3
|
||||
|
||||
# Test with media player
|
||||
vlc http://localhost:8000/asteroid.mp3 # High quality MP3
|
||||
vlc http://localhost:8000/asteroid.aac # High quality AAC
|
||||
#+END_SRC
|
||||
|
||||
** Configuration Files
|
||||
|
||||
*** Key Configuration Locations
|
||||
*Docker Setup:*
|
||||
- =docker/asteroid-radio-docker.liq= - Liquidsoap streaming configuration
|
||||
- =docker/icecast.xml= - Icecast2 server settings
|
||||
- =docker/docker-compose.yml= - Container orchestration
|
||||
|
||||
*Native Setup:*
|
||||
- =asteroid-radio.liq= - Liquidsoap streaming configuration
|
||||
- =/etc/icecast2/icecast.xml= - Icecast2 server settings
|
||||
- =radiance-core.conf.lisp= - RADIANCE framework configuration
|
||||
|
||||
|
||||
* Production Deployment
|
||||
|
||||
** Security Considerations
|
||||
|
||||
*** Change Default Passwords
|
||||
- Update Icecast2 admin password
|
||||
- Change streaming source password
|
||||
- Secure database access if using external DB
|
||||
|
||||
*** File Permissions
|
||||
#+BEGIN_SRC bash
|
||||
# Secure file permissions
|
||||
sudo chown -R asteroid:asteroid /opt/asteroid-radio
|
||||
sudo chmod 750 /opt/asteroid-radio
|
||||
sudo chmod 640 /opt/asteroid-radio/config/*
|
||||
#+END_SRC
|
||||
|
||||
*** Network Security
|
||||
- Use HTTPS with SSL certificates
|
||||
- Implement rate limiting
|
||||
- Configure fail2ban for brute force protection
|
||||
|
||||
** Performance Tuning
|
||||
|
||||
*** System Limits
|
||||
#+BEGIN_SRC bash
|
||||
# Increase file descriptor limits
|
||||
echo "asteroid soft nofile 65536" | sudo tee -a /etc/security/limits.conf
|
||||
echo "asteroid hard nofile 65536" | sudo tee -a /etc/security/limits.conf
|
||||
#+END_SRC
|
||||
|
||||
*** Icecast2 Optimization
|
||||
- Adjust client limits based on server capacity
|
||||
- Configure appropriate buffer sizes
|
||||
- Enable burst-on-connect for better user experience
|
||||
|
||||
** Monitoring Setup
|
||||
|
||||
*** Log Monitoring
|
||||
#+BEGIN_SRC bash
|
||||
# Docker setup - monitor container logs
|
||||
docker compose logs -f icecast
|
||||
docker compose logs -f liquidsoap
|
||||
|
||||
# Native setup - monitor system logs
|
||||
sudo tail -f /var/log/icecast2/error.log
|
||||
sudo tail -f /var/log/asteroid-radio/asteroid.log
|
||||
#+END_SRC
|
||||
|
||||
*** Health Checks
|
||||
#+BEGIN_SRC bash
|
||||
# Create health check script
|
||||
cat > ~/asteroid-radio/health-check.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
# Check all three streams
|
||||
curl -I http://localhost:8000/asteroid.mp3 | grep -q "200 OK" || exit 1
|
||||
curl -I http://localhost:8000/asteroid.aac | grep -q "200 OK" || exit 1
|
||||
curl -I http://localhost:8000/asteroid-low.mp3 | grep -q "200 OK" || exit 1
|
||||
# Check Icecast admin interface
|
||||
curl -f http://localhost:8000/admin/ || exit 1
|
||||
EOF
|
||||
chmod +x ~/asteroid-radio/health-check.sh
|
||||
#+END_SRC
|
||||
|
||||
* Troubleshooting
|
||||
|
||||
** Common Installation Issues
|
||||
|
||||
*** Dependency Problems
|
||||
- Ensure all system packages are installed
|
||||
- Check Quicklisp installation
|
||||
- Verify SBCL can load all required libraries
|
||||
|
||||
*** Permission Issues
|
||||
- Check file ownership and permissions
|
||||
- Verify service user has access to required directories
|
||||
- Ensure music directories are writable
|
||||
|
||||
*** Network Issues
|
||||
- Confirm firewall allows required ports
|
||||
- Check service binding addresses
|
||||
- Verify no port conflicts with other services
|
||||
|
||||
*** Streaming Issues
|
||||
- Check Icecast2 configuration and logs
|
||||
- Verify Liquidsoap can access music files
|
||||
- Test stream connectivity from different networks
|
||||
|
||||
** Getting Support
|
||||
- Check project documentation and FAQ
|
||||
- Review system logs for error messages
|
||||
- Submit issues with detailed system information
|
||||
- Join our IRC chat room: **#asteroid.music** on **irc.libera.chat**
|
||||
- Join community discussions for help
|
||||
|
||||
* Maintenance
|
||||
|
||||
** Regular Maintenance Tasks
|
||||
- Update system packages monthly
|
||||
- Monitor disk space for music library
|
||||
- Review and rotate log files
|
||||
- Backup configuration files
|
||||
- Test streaming functionality
|
||||
|
||||
** Updates and Upgrades
|
||||
- Follow project release notes
|
||||
- Test updates in development environment first
|
||||
- Backup before major upgrades
|
||||
- Monitor service status after updates
|
||||
|
||||
This installation guide provides comprehensive setup instructions for Asteroid Radio. For development-specific setup, see the Development Guide.
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
#+TITLE: Asteroid Radio - Project Overview
|
||||
#+AUTHOR: Glenn Thompson & Brian O'Reilly (Fade)
|
||||
#+DATE: 2025-10-03
|
||||
|
||||
* 🎯 Mission
|
||||
|
||||
Asteroid Radio is a modern, web-based music streaming platform designed for hackers and music enthusiasts. Built with Common Lisp and the Radiance web framework, it combines the power of functional programming with contemporary web technologies.
|
||||
|
||||
* 🏗️ Architecture
|
||||
|
||||
** Core Components
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Asteroid Radio Platform │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Web Application Layer (Common Lisp + Radiance) │
|
||||
│ ├── Authentication & User Management │
|
||||
│ ├── Music Library Management │
|
||||
│ ├── Web Player Interface │
|
||||
│ └── API Endpoints │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Streaming Infrastructure (Docker) │
|
||||
│ ├── Icecast2 (Streaming Server) │
|
||||
│ ├── Liquidsoap (Audio Processing) │
|
||||
│ └── Multiple Format Support (AAC, MP3) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Data Layer │
|
||||
│ ├── PostgreSQL Database (via Radiance) │
|
||||
│ ├── User Accounts & Profiles │
|
||||
│ └── Music Metadata │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Technology Stack
|
||||
|
||||
**Backend:**
|
||||
- **Common Lisp** (SBCL) - Core application language
|
||||
- **Radiance Framework** - Web framework and module system
|
||||
- **LASS** - CSS preprocessing in Lisp
|
||||
- **PostgreSQL** - Database backend for user accounts and metadata
|
||||
|
||||
**Frontend:**
|
||||
- **HTML5** with semantic templates
|
||||
- **CSS3** with dark hacker theme
|
||||
- **JavaScript** for interactive features
|
||||
- **VT323 Font** for retro terminal aesthetic
|
||||
|
||||
**Streaming:**
|
||||
- **Docker Compose** - Container orchestration
|
||||
- **Icecast2** - HTTP streaming server
|
||||
- **Liquidsoap** - Audio processing and encoding
|
||||
- **Multiple Formats** - AAC 96kbps, MP3 128kbps/64kbps
|
||||
|
||||
**Development & Testing:**
|
||||
- **Make** - Build system
|
||||
- **Python** - Performance analysis tools
|
||||
- **Bash** - Testing and deployment scripts
|
||||
|
||||
## 🎨 Design Philosophy
|
||||
|
||||
### Visual Theme
|
||||
- **Dark terminal aesthetic** - Black background with colored text
|
||||
- **Hacker-friendly** - Monospace fonts and terminal-inspired UI
|
||||
- **Color scheme** - Black → Blue-grey → Cyan → Blue progression
|
||||
- **Minimalist** - Clean, functional interface without clutter
|
||||
|
||||
### Technical Principles
|
||||
- **Functional programming** - Leveraging Lisp's strengths
|
||||
- **Modular architecture** - Radiance's interface system
|
||||
- **Performance first** - Sub-1% CPU usage for web app
|
||||
- **Self-contained** - Minimal external dependencies
|
||||
- **Docker-ready** - Containerized streaming infrastructure
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
### Current Features
|
||||
- ✅ **User Authentication** - Registration, login, profiles
|
||||
- ✅ **Music Streaming** - Multiple quality formats
|
||||
- ✅ **Web Player** - Browser-based music player
|
||||
- ✅ **Rate Limiting** - Anti-abuse protection
|
||||
- ✅ **Docker Integration** - Icecast2/Liquidsoap streaming
|
||||
- ✅ **Responsive Design** - Works on desktop and mobile
|
||||
|
||||
### Planned Features
|
||||
- 🔄 **User Playlists** - Personal music collections
|
||||
- 🔄 **Social Features** - Sharing and discovery
|
||||
- 🔄 **Advanced Player** - Queue management, crossfade
|
||||
- 🔄 **Admin Interface** - System management tools
|
||||
- 🔄 **API Extensions** - Mobile app support
|
||||
|
||||
|
||||
## 🔮 Vision
|
||||
|
||||
Asteroid Radio is the premier streaming platform for **Asteroid Music** - the perfect soundtrack for developers, hackers, and anyone who spends hours deep in code. Our mission is to curate and deliver music that enhances focus, creativity, and the flow state that every programmer knows.
|
||||
|
||||
**What is Asteroid Music?**
|
||||
- **Focus-Enhancing** - Ambient, electronic, and instrumental tracks that don't distract
|
||||
- **Coding-Optimized** - Rhythms and textures that complement the mental rhythm of programming
|
||||
- **Hacker Culture** - Music that resonates with the developer mindset and aesthetic
|
||||
- **Flow State** - Carefully selected tracks that help maintain deep concentration
|
||||
|
||||
**Platform Features:**
|
||||
- **Multi-Format Streaming** - High-quality AAC, MP3 128k, and MP3 64k streams
|
||||
- **User Community** - Accounts, playlists, and sharing among fellow developers
|
||||
- **Developer-Friendly** - Built with Common Lisp, fully hackable and extensible
|
||||
- **Professional Quality** - Crossfading, normalization, metadata, and telnet control
|
||||
- **Always-On Broadcasting** - Continuous streams perfect for long coding sessions
|
||||
|
||||
Asteroid Radio isn't just another music platform - it's the soundtrack to the hacker lifestyle, designed by hackers for hackers who understand that the right music can make the difference between good code and great code.
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
#+TITLE: Asteroid Radio - Documentation Index
|
||||
#+AUTHOR: Documentation Team
|
||||
#+DATE: 2025-10-03
|
||||
|
||||
* Welcome to Asteroid Radio Documentation
|
||||
|
||||
Asteroid Radio is a modern internet radio platform designed for developers and music enthusiasts who want to run their own radio stations streaming **Asteroid Music** - the perfect soundtrack for coding and hacking sessions.
|
||||
|
||||
* Quick Start
|
||||
|
||||
For immediate setup, see:
|
||||
1. **[[file:INSTALLATION.org][Installation Guide]]** - Get Asteroid Radio running
|
||||
2. **[[file:DOCKER-STREAMING.org][Docker Streaming Setup]]** - Docker-based streaming infrastructure
|
||||
|
||||
* Documentation Structure
|
||||
|
||||
** Core Documentation
|
||||
|
||||
*** [[file:PROJECT-OVERVIEW.org][Project Overview]]
|
||||
Complete overview of Asteroid Radio's architecture, technology stack, and vision. Start here to understand what Asteroid Radio is and how it works.
|
||||
|
||||
*** [[file:INSTALLATION.org][Installation Guide]]
|
||||
Comprehensive installation instructions for multiple operating systems, including system requirements, dependencies, and production deployment considerations.
|
||||
|
||||
*** [[file:DOCKER-STREAMING.org][Docker Streaming Setup]]
|
||||
Complete guide to the Docker-based streaming infrastructure using Icecast2 and Liquidsoap. Includes container configuration, management scripts, and troubleshooting.
|
||||
|
||||
** Development & Integration
|
||||
|
||||
*** [[file:DEVELOPMENT.org][Development Guide]]
|
||||
Development environment setup, contributing guidelines, coding standards, and debugging procedures for developers working on Asteroid Radio.
|
||||
|
||||
*** [[file:API-REFERENCE.org][Interface Reference]]
|
||||
Documentation of all available interfaces including streaming endpoints, Icecast admin interface, Liquidsoap telnet control, and Docker management commands.
|
||||
|
||||
* Current System Status
|
||||
|
||||
** What's Working Now
|
||||
- **Docker Streaming Infrastructure**: Icecast2 + Liquidsoap containers
|
||||
- **Three Quality Streams**: 128kbps MP3, 96kbps AAC, 64kbps MP3
|
||||
- **Admin Interface**: Icecast web admin at http://localhost:8000/admin/
|
||||
- **Telnet Control**: Liquidsoap control via telnet localhost:1234
|
||||
- **Professional Features**: Crossfading, normalization, metadata support
|
||||
|
||||
** Stream URLs (when running)
|
||||
- **High Quality MP3**: http://localhost:8000/asteroid.mp3 (128kbps)
|
||||
- **High Quality AAC**: http://localhost:8000/asteroid.aac (96kbps)
|
||||
- **Low Quality MP3**: http://localhost:8000/asteroid-low.mp3 (64kbps)
|
||||
|
||||
* Getting Started
|
||||
|
||||
** New Users
|
||||
1. Read the **[[file:PROJECT-OVERVIEW.org][Project Overview]]** to understand Asteroid Radio
|
||||
2. Follow the **[[file:INSTALLATION.org][Installation Guide]]** for your operating system
|
||||
3. Set up streaming with the **[[file:DOCKER-STREAMING.org][Docker Guide]]**
|
||||
|
||||
** Developers
|
||||
1. Review the **[[file:DEVELOPMENT.org][Development Guide]]** for setup procedures
|
||||
2. Check the **[[file:API-REFERENCE.org][Interface Reference]]** for available controls
|
||||
3. Join our IRC channel: **#asteroid.music** on **irc.libera.chat**
|
||||
|
||||
** System Administrators
|
||||
1. Follow the **[[file:INSTALLATION.org][Installation Guide]]** production deployment section
|
||||
2. Review **[[file:DOCKER-STREAMING.org][Docker Streaming Setup]]** for container management
|
||||
3. Monitor system resources and streaming performance
|
||||
|
||||
* Support & Community
|
||||
|
||||
** Getting Help
|
||||
- **Documentation**: Start with the relevant guide above
|
||||
- **IRC Chat**: Join **#asteroid.music** on **irc.libera.chat**
|
||||
- **Issues**: Submit detailed bug reports with system information
|
||||
- **Logs**: Check Docker container logs for troubleshooting
|
||||
|
||||
** Contributing
|
||||
- Review the **[[file:DEVELOPMENT.org][Development Guide]]** for contribution guidelines
|
||||
- Follow coding standards and testing procedures
|
||||
- Submit pull requests with clear descriptions
|
||||
|
||||
* About Asteroid Music
|
||||
|
||||
Asteroid Radio streams **Asteroid Music** - a carefully curated genre designed for developers:
|
||||
|
||||
- **Focus-Enhancing**: Ambient, electronic, and instrumental tracks
|
||||
- **Coding-Optimized**: Rhythms that complement programming flow
|
||||
- **Hacker Culture**: Music that resonates with developer aesthetics
|
||||
- **Flow State**: Tracks selected to maintain deep concentration
|
||||
|
||||
This isn't just background music - it's the soundtrack to the hacker lifestyle, designed by hackers for hackers who understand that the right music can elevate your code.
|
||||
|
||||
* Technical Architecture
|
||||
|
||||
Asteroid Radio uses a modern, containerized architecture:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Asteroid Radio Platform │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Streaming Infrastructure (Docker) │
|
||||
│ ├── Icecast2 (HTTP Streaming Server) │
|
||||
│ ├── Liquidsoap (Audio Processing Pipeline) │
|
||||
│ └── Multiple Format Support (AAC, MP3) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Control Interfaces │
|
||||
│ ├── Icecast Admin Web Interface │
|
||||
│ ├── Liquidsoap Telnet Control │
|
||||
│ └── Docker Container Management │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
#+END_EXAMPLE
|
||||
|
||||
For detailed technical information, see the **[[file:PROJECT-OVERVIEW.org][Project Overview]]**.
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2025-10-03*
|
||||
*Documentation Version: 1.0*
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Helper script to run all three stream format tests
|
||||
# Usage: ./run-all-tests.sh
|
||||
|
||||
echo "=== Asteroid Comprehensive Performance Testing Suite ==="
|
||||
echo ""
|
||||
echo "This will run three 15-minute tests:"
|
||||
echo "1. AAC 96kbps stream"
|
||||
echo "2. MP3 128kbps stream"
|
||||
echo "3. MP3 64kbps stream"
|
||||
echo ""
|
||||
echo "Each test will:"
|
||||
echo "- Start Docker containers (Icecast2 + Liquidsoap)"
|
||||
echo "- Start Asteroid web application"
|
||||
echo "- Monitor performance for 15 minutes"
|
||||
echo "- Generate light web traffic"
|
||||
echo "- Save detailed logs and CSV data"
|
||||
echo ""
|
||||
|
||||
read -p "Press Enter to start the test suite, or Ctrl+C to cancel..."
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo ""
|
||||
echo "=== TEST 1/3: AAC 96kbps Stream ==="
|
||||
echo "Starting AAC test..."
|
||||
"$SCRIPT_DIR/comprehensive-performance-test.sh" aac
|
||||
|
||||
echo ""
|
||||
echo "AAC test completed. Please switch to AAC stream format in Liquidsoap if needed."
|
||||
read -p "Press Enter when ready for MP3 High Quality test..."
|
||||
|
||||
echo ""
|
||||
echo "=== TEST 2/3: MP3 128kbps Stream ==="
|
||||
echo "Starting MP3 High Quality test..."
|
||||
"$SCRIPT_DIR/comprehensive-performance-test.sh" mp3-high
|
||||
|
||||
echo ""
|
||||
echo "MP3 High test completed. Please switch to MP3 Low Quality stream format if needed."
|
||||
read -p "Press Enter when ready for MP3 Low Quality test..."
|
||||
|
||||
echo ""
|
||||
echo "=== TEST 3/3: MP3 64kbps Stream ==="
|
||||
echo "Starting MP3 Low Quality test..."
|
||||
"$SCRIPT_DIR/comprehensive-performance-test.sh" mp3-low
|
||||
|
||||
echo ""
|
||||
echo "=== ALL TESTS COMPLETED ==="
|
||||
echo ""
|
||||
echo "Results saved in: $SCRIPT_DIR/performance-logs/"
|
||||
echo ""
|
||||
echo "Log files created:"
|
||||
ls -la "$SCRIPT_DIR/performance-logs/" | grep "$(date +%Y%m%d)"
|
||||
|
||||
echo ""
|
||||
echo "To analyze results, check the CSV files for detailed performance data."
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
;;;; Setup script for Asteroid Radio Radiance environment
|
||||
;;;; This creates the necessary symbolic links for the custom environment
|
||||
|
||||
(defun setup-asteroid-environment ()
|
||||
"Set up the asteroid Radiance environment with symbolic links to project config"
|
||||
(let* ((project-root (asdf:system-source-directory :asteroid))
|
||||
(config-dir (merge-pathnames "config/" project-root))
|
||||
(radiance-env-dir (merge-pathnames ".config/radiance/asteroid/"
|
||||
(user-homedir-pathname))))
|
||||
|
||||
;; Ensure the radiance environment directory exists
|
||||
(ensure-directories-exist radiance-env-dir)
|
||||
|
||||
;; Create symbolic links for each config file
|
||||
(dolist (config-file '("radiance-core.conf.lisp"
|
||||
"i-lambdalite.conf.lisp"
|
||||
"simple-auth.conf.lisp"
|
||||
"simple-sessions.conf.lisp"
|
||||
"i-hunchentoot.conf.lisp"))
|
||||
(let ((source (merge-pathnames config-file config-dir))
|
||||
(target (merge-pathnames config-file radiance-env-dir)))
|
||||
(when (probe-file target)
|
||||
(delete-file target))
|
||||
(when (probe-file source)
|
||||
#+unix
|
||||
(sb-posix:symlink (namestring source) (namestring target))
|
||||
#-unix
|
||||
(progn
|
||||
(format t "Warning: Symbolic links not supported on this platform~%")
|
||||
(format t "Please manually copy ~a to ~a~%" source target)))))
|
||||
|
||||
(format t "Asteroid environment setup complete!~%")
|
||||
(format t "Config directory: ~a~%" config-dir)
|
||||
(format t "Radiance environment: ~a~%" radiance-env-dir)))
|
||||
|
||||
;; Auto-setup when loaded
|
||||
(setup-asteroid-environment)
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Asteroid Radio Performance Analysis
|
||||
Uses only matplotlib (no seaborn dependency)
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import glob
|
||||
import os
|
||||
from datetime import datetime
|
||||
import numpy as np
|
||||
|
||||
def load_performance_data():
|
||||
"""Load all CSV performance data files"""
|
||||
csv_files = glob.glob('performance-logs/*_data_*.csv')
|
||||
data_frames = {}
|
||||
|
||||
for file in csv_files:
|
||||
# Extract test type from filename
|
||||
filename = os.path.basename(file)
|
||||
if 'aac' in filename:
|
||||
test_type = 'AAC 96kbps'
|
||||
elif 'mp3-high' in filename:
|
||||
test_type = 'MP3 128kbps'
|
||||
elif 'mp3-low' in filename:
|
||||
test_type = 'MP3 64kbps'
|
||||
else:
|
||||
test_type = filename.split('_')[1]
|
||||
|
||||
try:
|
||||
df = pd.read_csv(file)
|
||||
df['test_type'] = test_type
|
||||
data_frames[test_type] = df
|
||||
print(f"✅ Loaded {len(df)} records from {test_type} test")
|
||||
except Exception as e:
|
||||
print(f"❌ Error loading {file}: {e}")
|
||||
|
||||
return data_frames
|
||||
|
||||
def create_simple_charts(data_frames):
|
||||
"""Create simple performance charts"""
|
||||
|
||||
# Create figure with subplots
|
||||
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
||||
fig.suptitle('Asteroid Radio Performance Analysis', fontsize=14)
|
||||
|
||||
colors = ['#ff6b6b', '#4ecdc4', '#45b7d1']
|
||||
|
||||
# 1. CPU Usage Over Time
|
||||
ax1 = axes[0, 0]
|
||||
for i, (test_type, df) in enumerate(data_frames.items()):
|
||||
if 'cpu_percent' in df.columns:
|
||||
ax1.plot(range(len(df)), df['cpu_percent'],
|
||||
label=test_type, color=colors[i % len(colors)], linewidth=2)
|
||||
ax1.set_title('CPU Usage Over Time')
|
||||
ax1.set_xlabel('Time (samples)')
|
||||
ax1.set_ylabel('CPU %')
|
||||
ax1.legend()
|
||||
ax1.grid(True, alpha=0.3)
|
||||
|
||||
# 2. Memory Usage Over Time
|
||||
ax2 = axes[0, 1]
|
||||
for i, (test_type, df) in enumerate(data_frames.items()):
|
||||
if 'memory_mb' in df.columns:
|
||||
ax2.plot(range(len(df)), df['memory_mb'],
|
||||
label=test_type, color=colors[i % len(colors)], linewidth=2)
|
||||
ax2.set_title('Memory Usage Over Time')
|
||||
ax2.set_xlabel('Time (samples)')
|
||||
ax2.set_ylabel('Memory (MB)')
|
||||
ax2.legend()
|
||||
ax2.grid(True, alpha=0.3)
|
||||
|
||||
# 3. Average Performance Comparison
|
||||
ax3 = axes[1, 0]
|
||||
test_names = []
|
||||
cpu_avgs = []
|
||||
mem_avgs = []
|
||||
|
||||
for test_type, df in data_frames.items():
|
||||
test_names.append(test_type.replace(' ', '\n'))
|
||||
cpu_avgs.append(df['cpu_percent'].mean() if 'cpu_percent' in df.columns else 0)
|
||||
mem_avgs.append(df['memory_mb'].mean() if 'memory_mb' in df.columns else 0)
|
||||
|
||||
x = np.arange(len(test_names))
|
||||
width = 0.35
|
||||
|
||||
ax3.bar(x - width/2, cpu_avgs, width, label='CPU %', color=colors[0], alpha=0.8)
|
||||
ax3_twin = ax3.twinx()
|
||||
ax3_twin.bar(x + width/2, mem_avgs, width, label='Memory MB', color=colors[1], alpha=0.8)
|
||||
|
||||
ax3.set_title('Average Resource Usage')
|
||||
ax3.set_xlabel('Stream Type')
|
||||
ax3.set_ylabel('CPU %', color=colors[0])
|
||||
ax3_twin.set_ylabel('Memory (MB)', color=colors[1])
|
||||
ax3.set_xticks(x)
|
||||
ax3.set_xticklabels(test_names)
|
||||
|
||||
# 4. Response Time Summary
|
||||
ax4 = axes[1, 1]
|
||||
response_summary = {}
|
||||
|
||||
for test_type, df in data_frames.items():
|
||||
stream_resp = df['stream_response_ms'].mean() if 'stream_response_ms' in df.columns else 0
|
||||
web_resp = df['web_response_ms'].mean() if 'web_response_ms' in df.columns else 0
|
||||
response_summary[test_type] = {'Stream': stream_resp, 'Web': web_resp}
|
||||
|
||||
if response_summary:
|
||||
test_types = list(response_summary.keys())
|
||||
stream_times = [response_summary[t]['Stream'] for t in test_types]
|
||||
web_times = [response_summary[t]['Web'] for t in test_types]
|
||||
|
||||
x = np.arange(len(test_types))
|
||||
ax4.bar(x - width/2, stream_times, width, label='Stream Response', color=colors[0], alpha=0.8)
|
||||
ax4.bar(x + width/2, web_times, width, label='Web Response', color=colors[1], alpha=0.8)
|
||||
|
||||
ax4.set_title('Average Response Times')
|
||||
ax4.set_xlabel('Stream Type')
|
||||
ax4.set_ylabel('Response Time (ms)')
|
||||
ax4.set_xticks(x)
|
||||
ax4.set_xticklabels([t.replace(' ', '\n') for t in test_types])
|
||||
ax4.legend()
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('performance-logs/asteroid_performance_charts.png', dpi=300, bbox_inches='tight')
|
||||
print("📊 Charts saved as: performance-logs/asteroid_performance_charts.png")
|
||||
|
||||
return fig
|
||||
|
||||
def generate_text_report(data_frames):
|
||||
"""Generate simple text report"""
|
||||
|
||||
report = []
|
||||
report.append("🎵 ASTEROID RADIO PERFORMANCE REPORT")
|
||||
report.append("=" * 45)
|
||||
report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
report.append("")
|
||||
|
||||
for test_type, df in data_frames.items():
|
||||
report.append(f"📡 {test_type}:")
|
||||
report.append("-" * 25)
|
||||
|
||||
if 'cpu_percent' in df.columns:
|
||||
cpu_mean = df['cpu_percent'].mean()
|
||||
cpu_max = df['cpu_percent'].max()
|
||||
report.append(f" CPU: {cpu_mean:.1f}% avg, {cpu_max:.1f}% peak")
|
||||
|
||||
if 'memory_mb' in df.columns:
|
||||
mem_mean = df['memory_mb'].mean()
|
||||
mem_max = df['memory_mb'].max()
|
||||
report.append(f" Memory: {mem_mean:.1f} MB avg, {mem_max:.1f} MB peak")
|
||||
|
||||
if 'stream_response_ms' in df.columns:
|
||||
stream_resp = df['stream_response_ms'].dropna()
|
||||
if len(stream_resp) > 0:
|
||||
report.append(f" Stream Response: {stream_resp.mean():.1f} ms avg")
|
||||
|
||||
if 'web_response_ms' in df.columns:
|
||||
web_resp = df['web_response_ms'].dropna()
|
||||
if len(web_resp) > 0:
|
||||
report.append(f" Web Response: {web_resp.mean():.1f} ms avg")
|
||||
|
||||
report.append(f" Test Duration: {len(df)} samples")
|
||||
report.append("")
|
||||
|
||||
# Summary
|
||||
report.append("📊 SUMMARY:")
|
||||
report.append("-" * 15)
|
||||
|
||||
# Find most efficient stream
|
||||
cpu_usage = {}
|
||||
for test_type, df in data_frames.items():
|
||||
if 'cpu_percent' in df.columns:
|
||||
cpu_usage[test_type] = df['cpu_percent'].mean()
|
||||
|
||||
if cpu_usage:
|
||||
best = min(cpu_usage, key=cpu_usage.get)
|
||||
worst = max(cpu_usage, key=cpu_usage.get)
|
||||
report.append(f" Most efficient: {best} ({cpu_usage[best]:.1f}% CPU)")
|
||||
report.append(f" Most intensive: {worst} ({cpu_usage[worst]:.1f}% CPU)")
|
||||
|
||||
total_samples = sum(len(df) for df in data_frames.values())
|
||||
report.append(f" Total test samples: {total_samples}")
|
||||
report.append(f" Stream formats tested: {len(data_frames)}")
|
||||
|
||||
# Save report
|
||||
with open('performance-logs/asteroid_simple_report.txt', 'w') as f:
|
||||
f.write('\n'.join(report))
|
||||
|
||||
print("📄 Report saved as: performance-logs/asteroid_simple_report.txt")
|
||||
return '\n'.join(report)
|
||||
|
||||
def main():
|
||||
print("🎵 Asteroid Radio Performance Analyzer (Simple)")
|
||||
print("=" * 45)
|
||||
|
||||
# Load data
|
||||
data_frames = load_performance_data()
|
||||
|
||||
if not data_frames:
|
||||
print("❌ No performance data found!")
|
||||
return
|
||||
|
||||
# Create charts
|
||||
print("\n📊 Creating performance charts...")
|
||||
create_simple_charts(data_frames)
|
||||
|
||||
# Generate report
|
||||
print("\n📄 Generating performance report...")
|
||||
report = generate_text_report(data_frames)
|
||||
|
||||
print("\n✅ Analysis complete!")
|
||||
print("\nFiles created:")
|
||||
print(" 📊 performance-logs/asteroid_performance_charts.png")
|
||||
print(" 📄 performance-logs/asteroid_simple_report.txt")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -234,8 +234,8 @@
|
|||
const query = document.getElementById('track-search').value.toLowerCase();
|
||||
const filtered = tracks.filter(track =>
|
||||
(track.title || '').toLowerCase().includes(query) ||
|
||||
(track.artist || '').toLowerCase().includes(query) ||
|
||||
(track.album || '').toLowerCase().includes(query)
|
||||
(track.artist || '').toLowerCase().includes(query) ||
|
||||
(track.album || '').toLowerCase().includes(query)
|
||||
);
|
||||
displayTracks(filtered);
|
||||
}
|
||||
|
|
@ -244,11 +244,9 @@
|
|||
function sortTracks() {
|
||||
const sortBy = document.getElementById('sort-tracks').value;
|
||||
const sorted = [...tracks].sort((a, b) => {
|
||||
/* const aVal = a[sortBy] ? a[sortBy][0] : '';
|
||||
* const bVal = b[sortBy] ? b[sortBy][0] : ''; */
|
||||
const aVal = a[sortBy] ? a[sortBy] : '';
|
||||
const bVal = b[sortBy] ? b[sortBy] : '';
|
||||
return aVal.localeCompare(bVal);
|
||||
const aVal = a[sortBy] || '';
|
||||
const bVal = b[sortBy] || '';
|
||||
return aVal.localeCompare(bVal);
|
||||
});
|
||||
displayTracks(sorted);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue