Skip to content

deutsia/Radio-Registry-API

Repository files navigation

Radio Registry API

A privacy-focused radio station directory designed for Tor and I2P networks. This FastAPI application provides both a JSON API and a server-rendered HTML interface for discovering, submitting, and managing radio stations accessible through anonymous networks.

Features

  • Radio Station Directory - Browse and search stations by network (Tor/I2P), genre, and online status
  • Station Submission - Submit new stations with automatic stream validation and approval
  • Stream Validation - Validates that URLs point to actual audio streams (not HTML, images, etc.)
  • Health Monitoring - Periodic checks to track station online/offline status
  • Cover Art Mirroring - Downloads and hosts all cover art locally, serving via Tor-accessible URLs for privacy
  • Admin Dashboard - Web-based admin panel for station moderation
  • Admin CLI - Command-line tools for bulk operations (import, export, approve, reject)
  • Network-Aware Routing - Automatic proxy routing through Tor SOCKS5 or I2P HTTP
  • Rate Limiting - API protection with slowapi (60/min for listings, 5/min for submissions)
  • No JavaScript

Technology Stack

  • FastAPI - Modern async web framework
  • Uvicorn - ASGI server
  • Pydantic - Data validation
  • SQLite - Embedded database
  • Jinja2 - HTML templating
  • aiohttp - Async HTTP client with SOCKS proxy support
  • slowapi - Rate limiting

Admin Dashboard

Screenshot preview of the web-based admin panel can be found in /static/covers. Cover art is not available for clearnet mirrors, as all the cover art is hosted over Tor. The rest of the UI can be explored at the Radio Registry website: https://api.deutsia.com

Installation

Prerequisites

  • Python 3.8+
  • Tor service running (for Tor station validation)
  • I2P router with HTTP proxy (for I2P station validation)

Quick Setup

# Clone the repository
git clone <repository-url>
cd Radio-Registry-API

# Run the setup script
./setup.sh

# Or manually:
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python -c "import database; database.init_db()"

Configuration

Edit config.py to customize settings:

# Server settings
HOST = "127.0.0.1"
PORT = 8080

# Proxy settings (adjust to your Tor/I2P configuration)
TOR_SOCKS_PROXY = "socks5://127.0.0.1:9050"
I2P_HTTP_PROXY = "http://127.0.0.1:4444"

# IMPORTANT: Change these in production!
ADMIN_PASSWORD = "changeme"
ADMIN_SECRET_KEY = "super-secret-key-change-me"

Running the Server

source venv/bin/activate
uvicorn main:app --host 127.0.0.1 --port 8080

API Reference

JSON API Endpoints

Method Endpoint Description Rate Limit
GET /api/stations List approved stations (paginated) 60/min
GET /api/stations/{id} Get station details -
GET /api/stations/{id}/cover Get station cover art -
POST /api/submit Submit a new station 5/min
GET /api/stats Get directory statistics -
GET /api/genres List available genres -
GET /api/health API health check -

Query Parameters

GET /api/stations

Parameter Type Description
network string Filter by network: tor or i2p
genre string Filter by genre
online_only boolean Only show online stations
page integer Page number (default: 1)
per_page integer Items per page (default: 50, max: 200)

Example Requests

# List all Tor stations
curl "http://localhost:8080/api/stations?network=tor"

# List online electronic stations
curl "http://localhost:8080/api/stations?genre=Electronic&online_only=true"

# Get directory stats
curl "http://localhost:8080/api/stats"

# Submit a new station
curl -X POST "http://localhost:8080/api/submit" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Radio Station",
    "stream_url": "http://example.onion:8000/stream",
    "network": "tor",
    "genre": "Electronic"
  }'

Response Examples

Station List Response

{
  "stations": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Example Radio",
      "stream_url": "http://example.onion:8000/stream",
      "homepage": "http://example.onion",
      "network": "tor",
      "genre": "Electronic",
      "codec": "MP3",
      "bitrate": 128,
      "language": "English",
      "country": "Unknown",
      "is_online": true,
      "last_check_time": "2024-01-15T12:00:00Z"
    }
  ],
  "total": 42,
  "page": 1,
  "per_page": 50,
  "pages": 1
}

Stats Response

{
  "total_stations": 100,
  "online_stations": 75,
  "tor_stations": 60,
  "i2p_stations": 40,
  "pending_submissions": 5
}

HTML Interface

The server-rendered HTML interface is accessible at:

Path Description
/ Station listing with filters
/station/{id} Station detail page
/submit Station submission form
/about About page
/admin Admin login
/admin/dashboard Admin dashboard

Admin CLI

The admin.py script provides command-line management tools:

# List all stations
python admin.py list

# List pending submissions
python admin.py pending

# Show statistics
python admin.py stats

# Approve a station
python admin.py approve <station-id>

# Reject a station
python admin.py reject <station-id> --reason "Invalid stream"

# Delete a station
python admin.py delete <station-id>

# Get station info
python admin.py info <station-id>

# Import stations from JSON
python admin.py import stations.json --network tor --approve

# Export stations to JSON
python admin.py export output.json --network tor --status approved

Import JSON Format

[
  {
    "name": "Station Name",
    "stream_url": "http://example.onion:8000/stream",
    "homepage": "http://example.onion",
    "genre": "Electronic",
    "codec": "MP3",
    "bitrate": 128,
    "language": "English",
    "country": "Unknown"
  }
]

Health Checks

The checker.py script performs smart health checks with escalating rechecks to prevent false offline status from transient failures.

Health Status

Stations have three health states:

Status Description
online Last check succeeded
offline Failed recently, but was online within 12 hours (being rechecked)
dead No successful response for 12+ hours

Escalating Rechecks

When a station fails a check, it doesn't immediately show as offline. Instead, the system performs escalating rechecks:

✓ Online     → next check in 4 hours
✗ Fail #1    → recheck in 5 minutes
✗ Fail #2    → recheck in 15 minutes
✗ Fail #3    → recheck in 1 hour
✗ Fail #4+   → confirmed offline, regular 4-hour checks resume

If a station remains unreachable for 12+ hours, it's marked as dead.

This prevents reliable stations from appearing offline due to temporary network issues on Tor/I2P.

Configuration

In config.py:

# Recheck intervals for failed stations (in minutes)
RECHECK_INTERVALS_MINUTES = [5, 15, 60]  # 5 min, 15 min, 1 hour

# Time threshold for "dead" status (in hours)
DEAD_THRESHOLD_HOURS = 12

# Regular check interval for online stations
HEALTH_CHECK_INTERVAL_HOURS = 4

Running the Checker

# Run manually
python checker.py

# Set up cron job (every 5 minutes - uses smart scheduling)
*/5 * * * * /path/to/venv/bin/python /path/to/checker.py >> /path/to/checker.log 2>&1

Note: The cron runs every 5 minutes, but the checker only checks stations that are actually due. Online stations won't be hammered - they're only checked every 4 hours. The frequent cron is to catch the escalating rechecks for recently-failed stations.

Checker Output

============================================================
Health check run - 2024-01-15 12:00:00
============================================================
Stations due for check: 5
  Tor: 3, I2P: 2
  Regular: 2, Rechecks: 2, Dead: 1

Checking 3 Tor stations via socks5://127.0.0.1:9050...
  [regular] http://example.onion/stream
    ✓ online
  [recheck #2] http://failing.onion/stream
    ✗ offline
  [dead-check] http://dead.onion/stream
    ✗ offline

============================================================
Health check complete
  Checked: 5
  Online: 2, Offline: 3
  Recovered: 1 (were offline, now online)
============================================================

Production Deployment

Systemd Service

A systemd service file is provided (radio-api.service):

# Copy and edit the service file
sudo cp radio-api.service /etc/systemd/system/
sudo nano /etc/systemd/system/radio-api.service

# Enable and start
sudo systemctl enable radio-api
sudo systemctl start radio-api

# Check status
sudo systemctl status radio-api

Security Hardening

The service file includes security features:

  • Memory limit (200MB)
  • CPU quota (50%)
  • No new privileges
  • Read-only home directory
  • Private /tmp

Reverse Proxy

For HTTPS access, use a reverse proxy like nginx or Cloudflare Tunnel:

server {
    listen 443 ssl;
    server_name your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Project Structure

.
├── main.py              # FastAPI application and routes
├── database.py          # SQLite database operations
├── models.py            # Pydantic models
├── config.py            # Configuration settings
├── stream_validator.py  # Audio stream validation
├── cover_downloader.py  # Cover art downloading
├── checker.py           # Health check script
├── admin.py             # CLI admin tool
├── requirements.txt     # Python dependencies
├── setup.sh             # Setup script
├── radio-api.service    # Systemd service file
├── stations.db          # SQLite database
├── templates/           # Jinja2 HTML templates
│   ├── base.html
│   ├── index.html
│   ├── station.html
│   ├── submit.html
│   ├── about.html
│   ├── admin_login.html
│   ├── admin.html
│   ├── 404.html
│   └── 500.html
└── static/              # Static files
    └── covers/          # Cached cover art

Network Configuration

Tor Setup

Ensure Tor is running with SOCKS proxy on port 9050:

# Install Tor
sudo apt install tor

# Verify it's running
curl --socks5 127.0.0.1:9050 https://check.torproject.org/api/ip

I2P Setup

Ensure I2P HTTP proxy is available on port 4444:

# Download I2P installer
wget https://geti2p.net/en/download/2.10.0/clearnet/https/files.i2p-projekt.de/i2pinstall_2.10.0.jar/download -O i2pinstall.jar

# Install I2P (follow the GUI installer prompts)
java -jar i2pinstall.jar

# Start I2P router
~/i2p/i2prouter start

# Verify proxy (should return I2P content)
curl --proxy http://127.0.0.1:4444 http://i2p-projekt.i2p

Deploying Your Own Mirrors

Once deployed, configure your mirrors in config.py. Example:

Network URL Notes
Tor http://your-onion-address.onion Use OnionBalance if your service needs 24/7 Tor uptime
I2P http://your-i2p-address.b32.i2p
Clearnet https://your-domain.com Optional

Supported Formats

Audio Codecs

  • MP3
  • AAC
  • OGG Vorbis
  • Opus
  • FLAC
  • WAV
  • WMA

Streaming Protocols

  • Direct streams (Icecast/Shoutcast with ICY metadata)
  • HLS (HTTP Live Streaming / m3u8 playlists)
  • DASH (Dynamic Adaptive Streaming)

Cover Art

  • JPEG
  • PNG
  • GIF
  • WebP
  • SVG

Cover Art Mirroring

To protect user privacy, all cover art is downloaded and hosted locally rather than loading from external sources. When a station is submitted with a cover art URL:

  1. The image is downloaded through Tor (even for clearnet URLs)
  2. Saved locally to static/covers/ with a hashed filename
  3. Served via the Tor hidden service URL

This prevents external tracking and ensures cover art works reliably over Tor/I2P. Cover art is limited to 8MB per image.

Configuration in config.py:

# Base URL for Tor-accessible cover art
TOR_BASE_URL = "http://your-onion-address.onion"

Rate Limits

Endpoint Limit
/api/stations 60 requests/minute
/api/submit 5 requests/minute
Other endpoints No limit

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

License

This project is licensed under the Apache License 2.0. See LICENSE for details.

About

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published