- Added multi-guild support with comma-separated AllowedGuildIDs config - Per-guild embed tracking with isolated state - Per-server action locking to prevent race conditions across guilds - Cross-guild embed updates after power actions - Debug logging for embed update triggers - Fixed resource usage field formatting (brackets now properly closed) - Updated config.ini.sample, generate_config.py for backward compatibility - Updated README.md with multi-guild documentation - Updated tests for multi-guild support
130 lines
4.3 KiB
Python
130 lines
4.3 KiB
Python
import os
|
|
import configparser
|
|
from pathlib import Path
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
REQUIRED_VARS = [
|
|
'DISCORD_TOKEN',
|
|
'ALLOWED_GUILD_ID', # Legacy
|
|
'ALLOWED_GUILD_IDS', # New
|
|
'PANEL_URL',
|
|
'CLIENT_API_KEY',
|
|
'APPLICATION_API_KEY'
|
|
]
|
|
|
|
|
|
def validate_env_vars():
|
|
"""Validate that required environment variables are set and non-empty."""
|
|
# Check for either legacy or new guild ID variable
|
|
has_guild_id = bool(os.getenv('ALLOWED_GUILD_ID'))
|
|
has_guild_ids = bool(os.getenv('ALLOWED_GUILD_IDS'))
|
|
|
|
if not has_guild_id and not has_guild_ids:
|
|
raise ValueError(
|
|
"Missing required environment variable: ALLOWED_GUILD_IDS (or legacy ALLOWED_GUILD_ID)"
|
|
)
|
|
|
|
# Check other required vars
|
|
missing_vars = [
|
|
var for var in ['DISCORD_TOKEN', 'PANEL_URL', 'CLIENT_API_KEY', 'APPLICATION_API_KEY']
|
|
if not os.getenv(var)
|
|
]
|
|
if missing_vars:
|
|
raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
|
|
|
|
|
|
def get_env_config():
|
|
"""Extract configuration from environment variables."""
|
|
# Support both new ALLOWED_GUILD_IDS and legacy ALLOWED_GUILD_ID
|
|
allowed_guild_ids = os.getenv('ALLOWED_GUILD_IDS', '')
|
|
if not allowed_guild_ids:
|
|
allowed_guild_ids = os.getenv('ALLOWED_GUILD_ID', '')
|
|
|
|
return {
|
|
'Discord': {
|
|
'Token': os.getenv('DISCORD_TOKEN'),
|
|
'AllowedGuildIDs': allowed_guild_ids
|
|
},
|
|
'Pterodactyl': {
|
|
'PanelURL': os.getenv('PANEL_URL'),
|
|
'ClientAPIKey': os.getenv('CLIENT_API_KEY'),
|
|
'ApplicationAPIKey': os.getenv('APPLICATION_API_KEY')
|
|
}
|
|
}
|
|
|
|
|
|
def configs_match(existing_config, env_config):
|
|
"""Compare existing config with environment variables."""
|
|
for section, values in env_config.items():
|
|
if not existing_config.has_section(section):
|
|
return False
|
|
for key, env_value in values.items():
|
|
if env_value is None:
|
|
# Environment variable not set, don't require match
|
|
continue
|
|
# For AllowedGuildIDs, also check legacy AllowedGuildID key
|
|
check_keys = [key]
|
|
if key == 'AllowedGuildIDs':
|
|
check_keys.append('AllowedGuildID')
|
|
|
|
matched = False
|
|
for check_key in check_keys:
|
|
existing_value = existing_config.get(section, check_key, fallback=None)
|
|
if existing_value == env_value:
|
|
matched = True
|
|
break
|
|
if not matched:
|
|
return False
|
|
return True
|
|
|
|
|
|
def generate_config():
|
|
"""
|
|
Generate config.ini from environment variables if:
|
|
1. No config file exists, OR
|
|
2. Config file exists but values don't match environment variables
|
|
|
|
Environment variables always take priority.
|
|
"""
|
|
config_path = Path(os.getenv('CONFIG_PATH', 'config.ini'))
|
|
|
|
# Check if config file exists
|
|
if not config_path.exists():
|
|
print(f"No config file found at {config_path}, generating from environment variables...")
|
|
validate_env_vars()
|
|
_write_config(config_path, get_env_config())
|
|
return
|
|
|
|
# Config file exists, read it and compare with environment
|
|
existing_config = configparser.ConfigParser()
|
|
existing_config.read(config_path)
|
|
|
|
env_config = get_env_config()
|
|
|
|
if not configs_match(existing_config, env_config):
|
|
print(f"Config file at {config_path} differs from environment variables, regenerating...")
|
|
# Validate that required env vars are present for regeneration
|
|
validate_env_vars()
|
|
_write_config(config_path, env_config)
|
|
return
|
|
|
|
print(f"Config file at {config_path} matches environment variables, using existing config.")
|
|
|
|
|
|
def _write_config(config_path, config_dict):
|
|
"""Write config dictionary to file."""
|
|
config = configparser.ConfigParser()
|
|
for section, values in config_dict.items():
|
|
config[section] = {k: str(v) for k, v in values.items() if v is not None}
|
|
|
|
config_path.parent.mkdir(exist_ok=True, parents=True)
|
|
|
|
with open(config_path, 'w') as configfile:
|
|
config.write(configfile)
|
|
print(f"Config file written to {config_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
load_dotenv()
|
|
generate_config() |