wip
This commit is contained in:
parent
a4ce8a291d
commit
950d72aba1
8 changed files with 375 additions and 22 deletions
166
agent.py
Normal file
166
agent.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue