#!/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)