Python Backend (Flask API)
Create project structure:
mkdir -p /opt/seedbox-panel
cd /opt/seedbox-panel
python3 -m venv venv
source venv/bin/activate
pip install flask flask-cors docker psutil gunicorn
Create app.py:
#!/usr/bin/env python3
"""
SeedBox Control Panel - Backend API
Save as: /opt/seedbox-panel/app.py
"""
from flask import Flask, jsonify, request
from flask_cors import CORS
import docker
import psutil
import os
import time
from datetime import datetime, timedelta
app = Flask(__name__)
CORS(app)
# Docker client
client = docker.from_env()
# App configurations
APPS = {
'radarr': {
'name': 'Radarr',
'image': 'linuxserver/radarr:latest',
'port': 7878,
'volumes': {'/opt/seedbox/radarr/config': {'bind': '/config', 'mode': 'rw'},
'/opt/seedbox/downloads': {'bind': '/downloads', 'mode': 'rw'},
'/opt/seedbox/movies': {'bind': '/movies', 'mode': 'rw'}},
'environment': {'PUID': '1000', 'PGID': '1000', 'TZ': 'UTC'}
},
'sonarr': {
'name': 'Sonarr',
'image': 'linuxserver/sonarr:latest',
'port': 8989,
'volumes': {'/opt/seedbox/sonarr/config': {'bind': '/config', 'mode': 'rw'},
'/opt/seedbox/downloads': {'bind': '/downloads', 'mode': 'rw'},
'/opt/seedbox/tv': {'bind': '/tv', 'mode': 'rw'}},
'environment': {'PUID': '1000', 'PGID': '1000', 'TZ': 'UTC'}
},
'plex': {
'name': 'Plex',
'image': 'linuxserver/plex:latest',
'port': 32400,
'volumes': {'/opt/seedbox/plex/config': {'bind': '/config', 'mode': 'rw'},
'/opt/seedbox/movies': {'bind': '/movies', 'mode': 'rw'},
'/opt/seedbox/tv': {'bind': '/tv', 'mode': 'rw'}},
'environment': {'PUID': '1000', 'PGID': '1000', 'TZ': 'UTC', 'VERSION': 'docker'}
},
'utorrent': {
'name': 'uTorrent',
'image': 'ekho/utorrent:latest',
'port': 8080,
'volumes': {'/opt/seedbox/utorrent/config': {'bind': '/utorrent/settings', 'mode': 'rw'},
'/opt/seedbox/downloads': {'bind': '/data', 'mode': 'rw'}},
'environment': {}
},
'overseerr': {
'name': 'Overseerr',
'image': 'linuxserver/overseerr:latest',
'port': 5055,
'volumes': {'/opt/seedbox/overseerr/config': {'bind': '/config', 'mode': 'rw'}},
'environment': {'PUID': '1000', 'PGID': '1000', 'TZ': 'UTC'}
}
}
def get_container(app_id):
"""Get container by app_id"""
try:
return client.containers.get(f"seedbox-{app_id}")
except docker.errors.NotFound:
return None
@app.route('/api/health', methods=['GET'])
def health():
return jsonify({'status': 'ok', 'timestamp': datetime.now().isoformat()})
@app.route('/api/system/stats', methods=['GET'])
def system_stats():
"""Get system statistics"""
# CPU
cpu_percent = psutil.cpu_percent(interval=1)
# Memory
memory = psutil.virtual_memory()
memory_used = memory.used / (1024**3) # GB
memory_total = memory.total / (1024**3) # GB
# Disk
disk = psutil.disk_usage('/')
disk_used = disk.used / (1024**4) # TB
disk_total = disk.total / (1024**4) # TB
disk_free = disk.free / (1024**4) # TB
# Uptime
boot_time = datetime.fromtimestamp(psutil.boot_time())
uptime = datetime.now() - boot_time
return jsonify({
'cpu': {'percent': cpu_percent},
'memory': {
'used': round(memory_used, 2),
'total': round(memory_total, 2),
'percent': memory.percent
},
'disk': {
'used': round(disk_used, 2),
'total': round(disk_total, 2),
'free': round(disk_free, 2),
'percent': disk.percent
},
'uptime': {
'days': uptime.days,
'hours': uptime.seconds // 3600,
'minutes': (uptime.seconds % 3600) // 60,
'formatted': f"{uptime.days}d {uptime.seconds // 3600}h {(uptime.seconds % 3600) // 60}m"
}
})
@app.route('/api/apps', methods=['GET'])
def list_apps():
"""List all applications with their status"""
apps = []
for app_id, config in APPS.items():
container = get_container(app_id)
status = 'stopped'
if container:
status = container.status
apps.append({
'id': app_id,
'name': config['name'],
'status': status,
'port': config['port'],
'image': config['image'],
'installed': container is not None
})
return jsonify(apps)
@app.route('/api/apps//install', methods=['POST'])
def install_app(app_id):
"""Install/deploy an application"""
if app_id not in APPS:
return jsonify({'error': 'App not found'}), 404
config = APPS[app_id]
# Create directories
for host_path in config['volumes'].keys():
os.makedirs(host_path, exist_ok=True)
try:
# Pull image
client.images.pull(config['image'])
# Create and start container
container = client.containers.run(
config['image'],
name=f"seedbox-{app_id}",
detach=True,
restart_policy={'Name': 'unless-stopped'},
ports={f"{config['port']}/tcp": config['port']},
volumes=config['volumes'],
environment=config['environment']
)
return jsonify({
'success': True,
'message': f"{config['name']} installed successfully",
'container_id': container.id[:12]
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/apps//start', methods=['POST'])
def start_app(app_id):
"""Start an application"""
container = get_container(app_id)
if not container:
return jsonify({'error': 'Container not found'}), 404
try:
container.start()
return jsonify({'success': True, 'message': f'{app_id} started'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/apps//stop', methods=['POST'])
def stop_app(app_id):
"""Stop an application"""
container = get_container(app_id)
if not container:
return jsonify({'error': 'Container not found'}), 404
try:
container.stop()
return jsonify({'success': True, 'message': f'{app_id} stopped'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/apps//restart', methods=['POST'])
def restart_app(app_id):
"""Restart an application"""
container = get_container(app_id)
if not container:
return jsonify({'error': 'Container not found'}), 404
try:
container.restart()
return jsonify({'success': True, 'message': f'{app_id} restarted'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/apps//update', methods=['POST'])
def update_app(app_id):
"""Update an application to latest version"""
if app_id not in APPS:
return jsonify({'error': 'App not found'}), 404
config = APPS[app_id]
container = get_container(app_id)
try:
# Pull latest image
client.images.pull(config['image'])
if container:
# Stop and remove old container
container.stop()
container.remove()
# Create new container with updated image
new_container = client.containers.run(
config['image'],
name=f"seedbox-{app_id}",
detach=True,
restart_policy={'Name': 'unless-stopped'},
ports={f"{config['port']}/tcp": config['port']},
volumes=config['volumes'],
environment=config['environment']
)
return jsonify({
'success': True,
'message': f'{config["name"]} updated successfully',
'container_id': new_container.id[:12]
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/apps//delete', methods=['DELETE'])
def delete_app(app_id):
"""Delete/uninstall an application"""
container = get_container(app_id)
if not container:
return jsonify({'error': 'Container not found'}), 404
try:
container.stop()
container.remove()
return jsonify({'success': True, 'message': f'{app_id} deleted'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/apps//logs', methods=['GET'])
def get_logs(app_id):
"""Get application logs"""
container = get_container(app_id)
if not container:
return jsonify({'error': 'Container not found'}), 404
try:
logs = container.logs(tail=100, timestamps=True).decode('utf-8')
return jsonify({'logs': logs.split('\n')})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Create systemd service:
# Create service file
sudo nano /etc/systemd/system/seedbox-panel.service
[Unit]
Description=SeedBox Control Panel API
After=network.target docker.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/seedbox-panel
Environment=PATH=/opt/seedbox-panel/venv/bin
ExecStart=/opt/seedbox-panel/venv/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app
Restart=always
[Install]
WantedBy=multi-user.target
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable seedbox-panel
sudo systemctl start seedbox-panel