This commit is contained in:
pika 2025-03-24 17:51:57 +01:00
parent a4ce8a291d
commit 950d72aba1
8 changed files with 375 additions and 22 deletions

166
agent.py Normal file
View file

@ -0,0 +1,166 @@
#!/usr/bin/env python3
import os
import time
import json
import jwt
import requests
import socket
import re
import logging
from datetime import datetime, timedelta
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Configuration (with environment variable support)
CADDYFILE_PATH = os.getenv('CADDYFILE_PATH', '/opt/docker/caddy/conf/Caddyfile')
DASHBOARD_URL = os.getenv('DASHBOARD_URL', 'https://dashboard.example.com/api/update')
SERVER_NAME = os.getenv('SERVER_NAME', socket.gethostname())
API_KEY = os.getenv('API_KEY') # Required for authentication
CHECK_INTERVAL = int(os.getenv('CHECK_INTERVAL', '60')) # Seconds between checks even if no file change
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('caddy-agent')
# Last data sent to avoid unnecessary updates
last_data_sent = None
last_send_time = datetime.min
def parse_caddyfile():
"""Parse the Caddyfile to extract domains and their proxy targets"""
entries = {}
try:
if not os.path.exists(CADDYFILE_PATH):
logger.error(f"Caddyfile not found at {CADDYFILE_PATH}")
return entries
with open(CADDYFILE_PATH, "r") as file:
content = file.read()
pattern = re.compile(r"(?P<domains>[^\s{]+(?:,\s*[^\s{]+)*)\s*{.*?reverse_proxy\s+(?P<target>https?:\/\/[\d\.]+:\d+|[\d\.]+:\d+).*?}", re.DOTALL)
matches = pattern.findall(content)
for domains, target in matches:
for domain in domains.split(", "):
domain = domain.strip()
if domain: # Only add non-empty domains
entries[domain] = target.strip()
logger.info(f"Found {len(entries)} domain entries in Caddyfile")
except Exception as e:
logger.error(f"Error parsing Caddyfile: {e}")
return entries
def create_auth_token():
"""Create a JWT token for authentication"""
if not API_KEY:
logger.error("API_KEY is not set. Authentication will fail.")
return None
# Create a token that expires in 5 minutes
payload = {
'server': SERVER_NAME,
'exp': datetime.utcnow() + timedelta(minutes=5)
}
try:
return jwt.encode(payload, API_KEY, algorithm='HS256')
except Exception as e:
logger.error(f"Error creating authentication token: {e}")
return None
def send_update(force=False):
"""Send Caddyfile data to the dashboard server"""
global last_data_sent, last_send_time
# Parse the Caddyfile
current_data = parse_caddyfile()
current_time = datetime.now()
# Only send if data changed or enough time passed since last update
if (not force and
current_data == last_data_sent and
(current_time - last_send_time).total_seconds() < CHECK_INTERVAL):
return
# Create the data payload
data = {
"server": SERVER_NAME,
"entries": current_data,
"timestamp": current_time.isoformat()
}
# Create authentication token
token = create_auth_token()
if not token:
return
# Prepare headers with authentication
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}'
}
try:
response = requests.post(
DASHBOARD_URL,
json=data,
headers=headers,
timeout=10 # Set a reasonable timeout
)
if response.status_code == 200:
logger.info(f"Update sent successfully: {response.json()}")
last_data_sent = current_data
last_send_time = current_time
else:
logger.error(f"Error sending update: {response.status_code} - {response.text}")
except requests.exceptions.RequestException as e:
logger.error(f"Connection error sending update: {e}")
class CaddyfileHandler(FileSystemEventHandler):
"""Watch for changes to the Caddyfile"""
def on_modified(self, event):
if event.src_path == CADDYFILE_PATH:
logger.info(f"Caddyfile modified: {event.src_path}")
send_update()
def main():
"""Main function to run the agent"""
logger.info(f"Starting Caddy agent for {SERVER_NAME}")
logger.info(f"Monitoring Caddyfile at: {CADDYFILE_PATH}")
logger.info(f"Dashboard URL: {DASHBOARD_URL}")
# Initial update
send_update(force=True)
# Setup file watching
observer = Observer()
event_handler = CaddyfileHandler()
# Watch the directory containing the Caddyfile
caddyfile_dir = os.path.dirname(CADDYFILE_PATH)
observer.schedule(event_handler, caddyfile_dir, recursive=False)
observer.start()
try:
while True:
# Periodic check even if file doesn't change
time.sleep(CHECK_INTERVAL)
send_update()
except KeyboardInterrupt:
observer.stop()
observer.join()
if __name__ == "__main__":
main()