From c8ae3809da62706de69c04900600c6e28c25c4a7 Mon Sep 17 00:00:00 2001 From: pika Date: Sun, 30 Mar 2025 13:29:50 +0200 Subject: [PATCH] fix: host --- Caddy/addEntry.py | 192 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 166 insertions(+), 26 deletions(-) diff --git a/Caddy/addEntry.py b/Caddy/addEntry.py index 5b8a86f..9e5e652 100755 --- a/Caddy/addEntry.py +++ b/Caddy/addEntry.py @@ -1,6 +1,19 @@ #!/usr/bin/env python3 import os import re +import socket + +def get_host_ip(): + """Get the host IP address""" + try: + # Create a socket to determine the outgoing IP address + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) # Connect to Google DNS (doesn't send data) + host_ip = s.getsockname()[0] + s.close() + return host_ip + except Exception: + return "127.0.0.1" # Fallback to localhost def get_user_input(prompt, default=None): """Interactive prompt with default values""" @@ -27,16 +40,113 @@ def parse_existing_entries(caddyfile_path): with open(caddyfile_path, "r") as file: content = file.read() - # Regex to find domain blocks and associated reverse proxy targets - pattern = re.compile(r"(?P[^\s{]+(?:,\s*[^\s{]+)*)\s*{.*?reverse_proxy\s+(?Phttps?:\/\/[\d\.]+:\d+|[\d\.]+:\d+).*?}", re.DOTALL) - matches = pattern.findall(content) - - for domains, target in matches: - for domain in domains.split(", "): - existing_entries[domain] = target.strip() + # First, normalize the content to make parsing more reliable + # This removes comments and normalizes whitespace + lines = [] + in_comment = False + for line in content.splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + + # Handle inline comments + if '#' in line and not in_comment: + line = line[:line.index('#')].strip() + + lines.append(line) + + normalized_content = '\n'.join(lines) + + # Use brace matching to properly extract domain blocks + blocks = [] + current_position = 0 + + while current_position < len(normalized_content): + # Find the next domain block start + block_start = normalized_content.find('{', current_position) + if block_start == -1: + break + + # Find corresponding domain definition + domain_start = normalized_content.rfind('\n', 0, block_start) + if domain_start == -1: + domain_start = 0 + else: + domain_start += 1 # Skip the newline + + domain_def = normalized_content[domain_start:block_start].strip() + + # Find end of this block (accounting for nested braces) + brace_count = 1 + block_end = block_start + 1 + + while brace_count > 0 and block_end < len(normalized_content): + if normalized_content[block_end] == '{': + brace_count += 1 + elif normalized_content[block_end] == '}': + brace_count -= 1 + block_end += 1 + + if brace_count == 0: + # We found a complete block + block_content = normalized_content[domain_start:block_end] + + # Only process blocks with reverse_proxy directives + if 'reverse_proxy' in block_content: + blocks.append((domain_def, block_content)) + + current_position = block_end + + # Process the extracted blocks + for domain_def, block_content in blocks: + # Extract target from reverse_proxy directive + proxy_match = re.search(r'reverse_proxy\s+(https?:\/\/[\d\.]+:\d+|[\d\.]+:\d+)', block_content) + if not proxy_match: + continue + + target = proxy_match.group(1).strip() + + # Process domains (handle comma-separated lists correctly) + domains = [d.strip() for d in domain_def.split(',')] + + # Process each domain + for domain in domains: + # Skip if it looks like a directive rather than a domain + if '{' in domain or '}' in domain or not domain: + continue + + # Skip literal "Host" that are likely from host header directives rather than domains + if domain == "Host" or domain == "{host}": + continue + + # Verify domain format (basic check) + if not re.match(r'^[a-zA-Z0-9][-a-zA-Z0-9.]*[a-zA-Z0-9]$', domain) and not domain.startswith('*.'): + print(f"⚠️ Skipping invalid domain format: '{domain}'") + continue + + # Determine proxy type + proxy_type = 'standard' + if "https://" in target and "tls_insecure_skip_verify" in block_content: + if "versions h1.1" in block_content: + proxy_type = 'opnsense' + else: + proxy_type = 'https_skip_verify' + + # Store the entry + existing_entries[domain] = { + 'target': target, + 'content': block_content, + 'proxy_type': proxy_type + } + + # Debug output for special cases + if domain.lower() == "host": + print(f"⚠️ Warning: Found domain named 'host': {domain}") except Exception as e: print(f"❌ Error reading Caddyfile: {e}") + import traceback + print(traceback.format_exc()) return existing_entries @@ -98,26 +208,39 @@ def update_existing_entry(caddyfile_path, domain, new_entry): with open(caddyfile_path, "r") as file: content = file.read() - # Find and replace the domain entry - pattern = re.compile(rf"({domain}\s*\{{.*?reverse_proxy\s+[^\n]+\n.*?\}})", re.DOTALL) - content = pattern.sub(new_entry.strip(), content) - - with open(caddyfile_path, "w") as file: - file.write(content) - - print(f"✅ Updated entry for {domain}") + # New improved pattern to correctly match complete domain blocks + # This regex matches the domain block from start to finish, including all braces + domain_pattern = fr'(?m)^(?:(?:{re.escape(domain)}|[^{{,\s]+(?:,\s*{re.escape(domain)})(?:,\s*[^{{,\s]+)*|{re.escape(domain)}(?:,\s*[^{{,\s]+)+))\s*{{(?:[^{{}}]|{{(?:[^{{}}]|{{[^{{}}]*}})*}})*}}' + + pattern = re.compile(domain_pattern, re.DOTALL) + match = pattern.search(content) + + if match: + # Replace the block containing this domain with the new entry + new_content = content[:match.start()] + new_entry.strip() + content[match.end():] + + with open(caddyfile_path, "w") as file: + file.write(new_content) + + print(f"✅ Updated entry for {domain}") + else: + print(f"⚠ Could not find exact entry for {domain}. Adding as new entry.") + with open(caddyfile_path, "a") as file: + file.write(new_entry) except Exception as e: print(f"❌ Error updating Caddyfile: {e}") + print(f"Error details: {str(e)}") def add_caddy_entry(caddyfile_path): """Add new Caddy reverse proxy entries, showing existing entries first""" + host_ip = get_host_ip() existing_entries = parse_existing_entries(caddyfile_path) print("\n📌 Existing Caddy Entries:") if existing_entries: - for domain, target in existing_entries.items(): - print(f" 🔹 {domain} → {target}") + for domain, data in existing_entries.items(): + print(f" 🔹 {domain} → {data['target']}") else: print(" ⚠ No entries found.") @@ -129,19 +252,34 @@ def add_caddy_entry(caddyfile_path): continue # If domain exists, extract its current values + existing_ip = host_ip + existing_port = "8080" + proxy_type = "standard" + if domain in existing_entries: print(f"⚠ The domain {domain} already exists.") edit_existing = get_user_input("Do you want to edit this entry? (y/n)", "y").lower() == "y" if not edit_existing: continue - existing_target = existing_entries[domain] - existing_ip, existing_port = existing_target.replace("https://", "").replace("http://", "").split(":") + existing_target = existing_entries[domain]['target'] + proxy_type = existing_entries[domain]['proxy_type'] + target_without_protocol = existing_target.replace("https://", "").replace("http://", "") + + if ":" in target_without_protocol: + existing_ip, existing_port = target_without_protocol.split(":") + else: + existing_ip = target_without_protocol + existing_port = "80" - else: - existing_ip, existing_port = "192.168.1.100", "8080" - - target_ip = get_user_input("Enter the target IP", existing_ip) + # Show host IP as an option + target_ip_prompt = f"Enter the target IP (type 'host' for {host_ip})" + target_ip = get_user_input(target_ip_prompt, existing_ip) + + # Replace 'host' with actual host IP + if target_ip.lower() == 'host': + target_ip = host_ip + target_port = get_user_input("Enter the target port", existing_port) print("\nChoose the proxy mode:") @@ -149,10 +287,12 @@ def add_caddy_entry(caddyfile_path): print("2️⃣ Internal HTTPS (skip verify)") print("3️⃣ OPNsense Mode (skip verify + enforce HTTP/1.1)") - # Pre-fill proxy type if editing + # Pre-fill proxy type based on detected configuration mode_choice_default = "1" - if "https://" in existing_entries.get(domain, "") and "tls_insecure_skip_verify" in existing_entries.get(domain, ""): - mode_choice_default = "3" if "versions h1.1" in existing_entries.get(domain, "") else "2" + if proxy_type == "https_skip_verify": + mode_choice_default = "2" + elif proxy_type == "opnsense": + mode_choice_default = "3" mode_choice = get_user_input("Enter option (1/2/3)", mode_choice_default)