diff --git a/agent.py b/agent.py index 6afdffd..9c29260 100644 --- a/agent.py +++ b/agent.py @@ -20,6 +20,7 @@ load_dotenv() # Fixed configuration CADDYFILE_PATH = "/app/Caddyfile" # Fixed internal path +NGINX_CONFIG_PATH = "/app/nginx" # Fixed internal path for nginx configs DASHBOARD_URL = os.getenv('DASHBOARD_URL', 'http://caddydb-server:5000/api/update') SERVER_NAME = os.getenv('SERVER_NAME', socket.gethostname()) API_KEY = os.getenv('API_KEY') @@ -64,6 +65,9 @@ if not API_KEY: last_data_sent = None last_send_time = datetime.min +# Flag to determine what type of config to monitor +IS_NGINX = SERVER_TYPE.lower() == 'nginx' + def parse_caddyfile(): """Parse the Caddyfile to extract domains and their proxy targets""" entries = {} @@ -120,12 +124,68 @@ def create_auth_token(): logger.error(f"Error creating JWT token: {e}") return None +def parse_nginx_configs(): + """Parse Nginx config files to extract domains and their proxy targets""" + entries = {} + + if not os.path.exists(NGINX_CONFIG_PATH) or not os.path.isdir(NGINX_CONFIG_PATH): + logger.error(f"Nginx config directory not found at {NGINX_CONFIG_PATH}") + return entries + + try: + # Find all .conf files in the directory and subdirectories + conf_files = [] + for root, _, files in os.walk(NGINX_CONFIG_PATH): + for file in files: + if file.endswith('.conf'): + conf_files.append(os.path.join(root, file)) + + logger.info(f"Found {len(conf_files)} Nginx config files") + + # Pattern to match server_name and proxy_pass directives + server_name_pattern = re.compile(r'server_name\s+([^;]+);', re.IGNORECASE) + proxy_pass_pattern = re.compile(r'proxy_pass\s+([^;]+);', re.IGNORECASE) + + for conf_file in conf_files: + try: + with open(conf_file, 'r') as file: + content = file.read() + + # Extract server blocks + server_blocks = re.findall(r'server\s*{([^}]+)}', content, re.DOTALL) + + for block in server_blocks: + server_names = server_name_pattern.search(block) + proxy_pass = proxy_pass_pattern.search(block) + + if server_names and proxy_pass: + server_names = server_names.group(1).strip().split() + target = proxy_pass.group(1).strip() + + for name in server_names: + # Skip default names like "_" or localhost + if name != "_" and name != "localhost" and name != "localhost.localdomain": + entries[name] = target + + except Exception as e: + logger.error(f"Error parsing Nginx config file {conf_file}: {e}") + + return entries + + except Exception as e: + logger.error(f"Error parsing Nginx configs: {e}") + return entries + def send_update(force=False): - """Send Caddyfile data to the dashboard server""" + """Send configuration data to the dashboard server""" global last_data_sent, last_send_time - # Parse the Caddyfile - current_data = parse_caddyfile() + # Parse the appropriate configuration + if IS_NGINX: + current_data = parse_nginx_configs() + else: + current_data = parse_caddyfile() + current_time = datetime.now() # Only send if data changed or enough time passed since last update @@ -134,9 +194,9 @@ def send_update(force=False): (current_time - last_send_time).total_seconds() < CHECK_INTERVAL): return - # Always include the server name in data payload + # Create the data payload data = { - "server": SERVER_NAME, # Always include this + "server": SERVER_NAME, "entries": current_data, "timestamp": current_time.isoformat(), "type": SERVER_TYPE @@ -171,12 +231,19 @@ def send_update(force=False): except requests.exceptions.RequestException as e: logger.error(f"Connection error sending update: {e}") -class CaddyFileHandler(FileSystemEventHandler): - """Watch for changes to the Caddyfile and send updates""" +class ConfigFileHandler(FileSystemEventHandler): + """Watch for changes to configuration files and send updates""" def on_modified(self, event): - if event.src_path == CADDYFILE_PATH: - logger.info(f"Caddyfile changed, sending update") - send_update() + if IS_NGINX: + # For Nginx, check if it's a .conf file in the watched directory + if event.src_path.endswith('.conf'): + logger.info(f"Nginx config changed: {event.src_path}, sending update") + send_update() + else: + # For Caddy, check if it's the Caddyfile + if event.src_path == CADDYFILE_PATH: + logger.info(f"Caddyfile changed, sending update") + send_update() def main(): """Main function to start the agent""" @@ -184,13 +251,28 @@ def main(): send_update(force=True) # Set up file watching - event_handler = CaddyFileHandler() + event_handler = ConfigFileHandler() observer = Observer() - observer.schedule(event_handler, path=os.path.dirname(CADDYFILE_PATH), recursive=False) + + if IS_NGINX: + # Watch the Nginx config directory + if not os.path.exists(NGINX_CONFIG_PATH): + logger.error(f"Nginx config path not found: {NGINX_CONFIG_PATH}") + sys.exit(1) + observer.schedule(event_handler, path=NGINX_CONFIG_PATH, recursive=True) + logger.info(f"Watching Nginx configs in {NGINX_CONFIG_PATH} for changes") + else: + # Watch the Caddyfile + if not os.path.exists(CADDYFILE_PATH): + logger.error(f"Caddyfile not found: {CADDYFILE_PATH}") + sys.exit(1) + observer.schedule(event_handler, path=os.path.dirname(CADDYFILE_PATH), recursive=False) + logger.info(f"Watching {CADDYFILE_PATH} for changes") + observer.start() try: - logger.info(f"Agent started. Watching {CADDYFILE_PATH} for changes") + logger.info(f"{SERVER_TYPE.capitalize()} agent started successfully") while True: # Send periodic updates send_update() diff --git a/app.py b/app.py index be7a80d..68ef5f6 100644 --- a/app.py +++ b/app.py @@ -10,6 +10,9 @@ from dotenv import load_dotenv import re import signal import sys +import time +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler # Load environment variables load_dotenv() @@ -51,6 +54,29 @@ caddy_proxies = {} # Server name -> {domain: target} nginx_proxies = {} # Server name -> {domain: target} timestamps = {} # Server name -> timestamp +# File change monitoring +file_observer = None + +class ConfigFileHandler(FileSystemEventHandler): + """Watch for changes to configuration files and update data""" + def on_modified(self, event): + if USE_LOCAL_CADDYFILE and event.src_path == CADDYFILE_PATH: + logger.info(f"Local Caddyfile changed, updating entries") + entries = parse_local_caddyfile() + if entries: + caddy_proxies[LOCAL_SERVER_NAME] = entries + timestamps[LOCAL_SERVER_NAME] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + logger.info(f"Updated {len(entries)} entries from local Caddyfile") + + # For Nginx, we need to check if the modified file is in the nginx config directory + if USE_LOCAL_NGINX and NGINX_CONFIG_PATH in event.src_path and event.src_path.endswith('.conf'): + logger.info(f"Local Nginx config changed, updating entries") + entries = parse_nginx_configs() + if entries: + nginx_proxies[f"{LOCAL_SERVER_NAME} Nginx"] = entries + timestamps[f"{LOCAL_SERVER_NAME} Nginx"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + logger.info(f"Updated {len(entries)} entries from local Nginx configs") + def verify_token(token): """Verify the JWT token from an agent""" if not API_KEY: @@ -266,42 +292,72 @@ def delete_server(): logger.info(f"Deleted server: {server_name}") return jsonify({"status": "success", "message": f"Server {server_name} deleted"}) -# Initialize with local files if available +def start_file_monitoring(): + """Start monitoring configuration files for changes""" + global file_observer + + if not (USE_LOCAL_CADDYFILE or USE_LOCAL_NGINX): + logger.info("No local configuration files to monitor") + return + + event_handler = ConfigFileHandler() + file_observer = Observer() + + if USE_LOCAL_CADDYFILE: + # Monitor the Caddyfile + file_observer.schedule( + event_handler, + path=os.path.dirname(CADDYFILE_PATH), + recursive=False + ) + logger.info(f"Monitoring local Caddyfile at {CADDYFILE_PATH}") + + if USE_LOCAL_NGINX: + # Monitor Nginx config directory + file_observer.schedule( + event_handler, + path=NGINX_CONFIG_PATH, + recursive=True # Monitor all subdirectories too + ) + logger.info(f"Monitoring local Nginx configs at {NGINX_CONFIG_PATH}") + + file_observer.start() + logger.info("File monitoring started") + +# Add cleanup function for graceful shutdown +def stop_file_monitoring(): + """Stop file monitoring""" + if file_observer: + file_observer.stop() + file_observer.join() + logger.info("File monitoring stopped") + +# Enhanced signal handler +def signal_handler(sig, frame): + logger.info("Shutdown signal received, exiting gracefully...") + stop_file_monitoring() + sys.exit(0) + +signal.signal(signal.SIGTERM, signal_handler) +signal.signal(signal.SIGINT, signal_handler) + +# Initialize with local files and start monitoring if USE_LOCAL_CADDYFILE: entries = parse_local_caddyfile() if entries: - caddy_proxies[LOCAL_SERVER_NAME] = entries # Use LOCAL_SERVER_NAME for local configs + caddy_proxies[LOCAL_SERVER_NAME] = entries timestamps[LOCAL_SERVER_NAME] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") logger.info(f"Loaded {len(entries)} entries from local Caddyfile") if USE_LOCAL_NGINX: entries = parse_nginx_configs() if entries: - nginx_proxies[f"{LOCAL_SERVER_NAME} Nginx"] = entries # Use LOCAL_SERVER_NAME for Nginx too + nginx_proxies[f"{LOCAL_SERVER_NAME} Nginx"] = entries timestamps[f"{LOCAL_SERVER_NAME} Nginx"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") logger.info(f"Loaded {len(entries)} entries from local Nginx configs") -def signal_handler(sig, frame): - logger.info("Shutdown signal received, exiting gracefully...") - sys.exit(0) +# Start file monitoring after initializing +start_file_monitoring() -signal.signal(signal.SIGTERM, signal_handler) -signal.signal(signal.SIGINT, signal_handler) - -if __name__ == '__main__': - if USE_LOCAL_CADDYFILE: - logger.info(f"Local Caddyfile found at {CADDYFILE_PATH} - will display its data") - # Load it initially - local_data = parse_local_caddyfile() - if local_data: - caddy_proxies[LOCAL_SERVER_NAME] = local_data - timestamps[LOCAL_SERVER_NAME] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - if not API_KEY: - logger.warning("API_KEY not set - running without authentication!") - - # Use HTTPS in production - if DEBUG_MODE: - app.run(host='0.0.0.0', port=5000, debug=True) - else: - app.run(host='0.0.0.0', port=5000) +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=DEBUG_MODE) diff --git a/requirements.txt b/requirements.txt index 8d6c76e..3a72029 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ -flask -requests -pyjwt -cryptography -watchdog -python-dotenv \ No newline at end of file +flask>=2.0.0 +pyjwt>=2.0.0 +python-dotenv>=0.19.0 +requests>=2.25.0 +watchdog>=2.1.0 \ No newline at end of file