Files
firefrost-operations-manual/deployments/whitelist-manager/app.py
Claude dd2b57bce6 feat: Whitelist Manager v1.0 - Complete deployment package
- Flask web application for managing Minecraft whitelists
- Manages all 11 game servers (TX1 + NC1)
- TailwindCSS Fire & Frost themed UI
- Single player and bulk operations
- HTTP Basic Auth with password hashing
- Nginx reverse proxy + SSL configuration
- systemd service for auto-start
- Complete deployment automation

Components:
- app.py: Main Flask application with Pterodactyl API integration
- templates/index.html: Responsive web dashboard
- requirements.txt: Python dependencies
- .env: Configuration with API keys and credentials
- deploy.sh: Automated deployment script
- DEPLOYMENT.md: Step-by-step manual deployment guide
- nginx.conf: Reverse proxy configuration
- whitelist-manager.service: systemd service

Target: Ghost VPS (64.50.188.14)
Domain: whitelist.firefrostgaming.com
Login: mkrause612 / Butter2018!!

Transforms 15-minute manual task into 30-second web operation.
Zero-error whitelist management with full visibility.

Ready for deployment - FFG-STD-001 compliant
2026-02-17 03:36:48 +00:00

222 lines
7.5 KiB
Python

#!/usr/bin/env python3
"""
Firefrost Gaming - Whitelist Manager
A web dashboard for managing Minecraft server whitelists across all game servers.
Author: Michael "Frostystyle" Krause & Claude "The Chronicler"
Version: 1.0.0
Date: 2026-02-16
"""
import os
import requests
from flask import Flask, render_template, request, jsonify, redirect, url_for
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
app = Flask(__name__)
auth = HTTPBasicAuth()
# Configuration from environment
PTERODACTYL_URL = os.getenv('PTERODACTYL_URL', 'https://panel.firefrostgaming.com')
PTERODACTYL_API_KEY = os.getenv('PTERODACTYL_API_KEY')
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME', 'admin')
ADMIN_PASSWORD_HASH = os.getenv('ADMIN_PASSWORD_HASH')
# Server list with UUIDs (from infrastructure manifest)
MINECRAFT_SERVERS = {
# Texas Node (TX1)
'Reclamation': '1eb33479-a6bc-4e8f-b64d-d1e4bfa0a8b4',
'Stoneblock 4': 'a0efbfe8-4b97-4a90-869d-ffe6d3072bd5',
'Society: Sunlit Valley': '9310d0a6-62a6-4fe6-82c4-eb483dc68876',
'Vanilla 1.21.11': '3bed1bda-f648-4630-801a-fe9f2e3d3f27',
'All The Mons': '668a5220-7e72-4379-9165-bdbb84bc9806',
# North Carolina Node (NC1)
'The Ember Project': '124f9060-58a7-457a-b2cf-b4024fce2951',
'Minecolonies: Create and Conquer': 'a14201d2-83b2-44e6-ae48-e6c4cbc56f24',
'All The Mods 10': '82e63949-8fbf-4a44-b32a-53324e8492bf',
'Homestead': '2f85d4ef-aa49-4dd6-b448-beb3fca1db12',
'EMC Subterra Tech': '09a95f38-9f8c-404a-9557-3a7c44258223',
}
# Authentication
users = {
ADMIN_USERNAME: ADMIN_PASSWORD_HASH
}
@auth.verify_password
def verify_password(username, password):
if username in users and check_password_hash(users.get(username), password):
return username
return None
# Helper function to send Pterodactyl API requests
def send_pterodactyl_command(server_uuid, command):
"""Send a console command to a Pterodactyl server."""
url = f"{PTERODACTYL_URL}/api/client/servers/{server_uuid}/command"
headers = {
'Authorization': f'Bearer {PTERODACTYL_API_KEY}',
'Accept': 'application/vnd.pterodactyl.v1+json',
'Content-Type': 'application/json'
}
data = {'command': command}
try:
response = requests.post(url, headers=headers, json=data, timeout=10)
response.raise_for_status()
return True, "Command sent successfully"
except requests.exceptions.RequestException as e:
return False, str(e)
def get_server_status(server_uuid):
"""Get the current status of a Pterodactyl server."""
url = f"{PTERODACTYL_URL}/api/client/servers/{server_uuid}"
headers = {
'Authorization': f'Bearer {PTERODACTYL_API_KEY}',
'Accept': 'application/vnd.pterodactyl.v1+json'
}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
return data['attributes'].get('status', 'unknown')
except requests.exceptions.RequestException:
return 'unknown'
# Routes
@app.route('/')
@auth.login_required
def index():
"""Main dashboard showing all servers."""
servers = []
for name, uuid in MINECRAFT_SERVERS.items():
status = get_server_status(uuid)
servers.append({
'name': name,
'uuid': uuid,
'status': status
})
return render_template('index.html', servers=servers)
@app.route('/api/whitelist/toggle', methods=['POST'])
@auth.login_required
def toggle_whitelist():
"""Toggle whitelist on/off for a specific server."""
data = request.get_json()
server_uuid = data.get('server_uuid')
enabled = data.get('enabled', True)
if not server_uuid:
return jsonify({'success': False, 'error': 'No server UUID provided'}), 400
command = 'whitelist on' if enabled else 'whitelist off'
success, message = send_pterodactyl_command(server_uuid, command)
return jsonify({'success': success, 'message': message})
@app.route('/api/whitelist/add', methods=['POST'])
@auth.login_required
def add_to_whitelist():
"""Add a player to whitelist on specified servers."""
data = request.get_json()
player = data.get('player')
server_uuids = data.get('servers', [])
if not player:
return jsonify({'success': False, 'error': 'No player name provided'}), 400
if not server_uuids:
return jsonify({'success': False, 'error': 'No servers selected'}), 400
results = []
for server_uuid in server_uuids:
command = f'whitelist add {player}'
success, message = send_pterodactyl_command(server_uuid, command)
# Get server name from UUID
server_name = next((name for name, uuid in MINECRAFT_SERVERS.items() if uuid == server_uuid), 'Unknown')
results.append({
'server': server_name,
'success': success,
'message': message
})
return jsonify({'results': results})
@app.route('/api/whitelist/remove', methods=['POST'])
@auth.login_required
def remove_from_whitelist():
"""Remove a player from whitelist on specified servers."""
data = request.get_json()
player = data.get('player')
server_uuids = data.get('servers', [])
if not player:
return jsonify({'success': False, 'error': 'No player name provided'}), 400
if not server_uuids:
return jsonify({'success': False, 'error': 'No servers selected'}), 400
results = []
for server_uuid in server_uuids:
command = f'whitelist remove {player}'
success, message = send_pterodactyl_command(server_uuid, command)
# Get server name from UUID
server_name = next((name for name, uuid in MINECRAFT_SERVERS.items() if uuid == server_uuid), 'Unknown')
results.append({
'server': server_name,
'success': success,
'message': message
})
return jsonify({'results': results})
@app.route('/api/whitelist/bulk', methods=['POST'])
@auth.login_required
def bulk_operation():
"""Perform bulk add/remove operations."""
data = request.get_json()
operation = data.get('operation') # 'add' or 'remove'
players = data.get('players', []) # List of player names
server_uuids = data.get('servers', [])
if operation not in ['add', 'remove']:
return jsonify({'success': False, 'error': 'Invalid operation'}), 400
if not players:
return jsonify({'success': False, 'error': 'No players provided'}), 400
if not server_uuids:
return jsonify({'success': False, 'error': 'No servers selected'}), 400
results = []
for player in players:
for server_uuid in server_uuids:
command = f'whitelist {operation} {player}'
success, message = send_pterodactyl_command(server_uuid, command)
server_name = next((name for name, uuid in MINECRAFT_SERVERS.items() if uuid == server_uuid), 'Unknown')
results.append({
'player': player,
'server': server_name,
'success': success,
'message': message
})
return jsonify({'results': results})
@app.route('/health')
def health():
"""Health check endpoint."""
return jsonify({'status': 'healthy', 'service': 'whitelist-manager'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=False)