Files
homelab/setup_dashboard.sh
T
2026-02-13 19:41:46 +02:00

162 lines
4.2 KiB
Bash
Executable File

#!/bin/bash
set -e
BASE_DIR="$HOME/homelab"
DASHBOARD_DIR="$BASE_DIR/dashboard"
TEMPLATES_DIR="$DASHBOARD_DIR/templates"
DOCKER_COMPOSE_FILE="$BASE_DIR/docker-compose.yml"
echo "Creating dashboard directory structure..."
mkdir -p "$TEMPLATES_DIR"
echo "Writing app.py..."
cat > "$DASHBOARD_DIR/app.py" << 'EOF'
from flask import Flask, render_template, jsonify, request
import subprocess
import requests
app = Flask(__name__)
DEVICES = {
"desktop": "AA:BB:CC:DD:EE:FF", # Replace with real MAC
}
DEVICE_IPS = {
"desktop": "192.168.1.10", # Replace with real IP
}
WOL_API_URL = "https://wol.zea.lt"
def ping_host(ip):
try:
subprocess.check_output(['ping', '-c', '1', '-W', '1', ip])
return True
except subprocess.CalledProcessError:
return False
@app.route('/')
def dashboard():
status = {dev: ping_host(ip) for dev, ip in DEVICE_IPS.items()}
return render_template('dashboard.html', devices=DEVICES, status=status)
@app.route('/wake/<device>', methods=['POST'])
def wake_device(device):
if device not in DEVICES:
return jsonify({"error": "Device not found"}), 404
try:
r = requests.post(f"{WOL_API_URL}/wake/{device}")
if r.ok:
return jsonify({"message": f"Wake command sent to {device}"})
else:
return jsonify({"error": f"Failed to send wake command: {r.text}"}), r.status_code
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
EOF
echo "Writing dashboard.html template..."
cat > "$TEMPLATES_DIR/dashboard.html" << 'EOF'
<!doctype html>
<html lang="en">
<head>
<title>HomeLab Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body class="p-4">
<h1>HomeLab Dashboard</h1>
<h2>Services</h2>
<ul>
<li><a href="https://git.zea.lt" target="_blank">Gitea</a></li>
<li><a href="https://registry.zea.lt" target="_blank">Docker Registry</a></li>
<li><a href="https://traefik.zea.lt" target="_blank">Traefik Dashboard</a></li>
<li><a href="https://wol.zea.lt" target="_blank">Wake-on-LAN API</a></li>
</ul>
<h2>Devices</h2>
<table class="table table-bordered" style="max-width:600px;">
<thead>
<tr>
<th>Device</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for device, mac in devices.items() %}
<tr>
<td>{{ device }}</td>
<td>
{% if status[device] %}
<span class="badge bg-success">Online</span>
{% else %}
<span class="badge bg-danger">Offline</span>
{% endif %}
</td>
<td>
<button class="btn btn-primary btn-sm" onclick="wake('{{ device }}')">Wake</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
async function wake(device) {
const resp = await fetch(`/wake/${device}`, { method: 'POST' });
const data = await resp.json();
alert(data.message || data.error);
}
</script>
</body>
</html>
EOF
echo "Writing Dockerfile..."
cat > "$DASHBOARD_DIR/Dockerfile" << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "app.py"]
EOF
echo "Writing requirements.txt..."
cat > "$DASHBOARD_DIR/requirements.txt" << 'EOF'
Flask==2.3.2
requests==2.31.0
EOF
# Backup docker-compose.yml just in case
cp "$DOCKER_COMPOSE_FILE" "$DOCKER_COMPOSE_FILE.bak"
echo "Updating docker-compose.yml to add dashboard service..."
# Append dashboard service before the last line if it ends with '...' or just at end
# If docker-compose.yml uses version 3+ it should be fine
cat >> "$DOCKER_COMPOSE_FILE" << 'EOF'
dashboard:
build: ./dashboard
container_name: dashboard
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`dashboard.zea.lt`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=leresolver"
EOF
echo "Setup complete! You can now run:"
echo "cd $BASE_DIR && docker-compose build dashboard && docker-compose up -d dashboard"