import os import subprocess import random import time import datetime import requests import re import sys from psql_utils import connect_to_database, update_or_insert_ip, should_skip_file from openvpn_manager import establish_vpn_connection, is_vpn_active, disconnect_vpn, get_external_ip from ovpn_template import DEFAULT_OVPN_CONFIG # Variables CREDENTIAL_FILE = "nord.creds" TEMP_OVPN_DIR = "/tmp/ovpn_configs" # Directory for temporary OVPN files LOG_FILE = "openvpn.log" # This is not used anymore RESULT_FILE = "vpnlist.txt" PING_URL = "https://health.ext.ben.io/ping/a1a55915-1051-48ed-bd13-ea3f1717a3ee" # NordVPN API endpoint (updated to get all servers) NORDVPN_API_URL = "https://api.nordvpn.com/v1/servers?limit=16384" # Define DEBUG_MODE DEBUG_MODE = False if len(sys.argv) > 1 and sys.argv[1] == "-d": DEBUG_MODE = True def debug_print(message): if DEBUG_MODE: print(f"{datetime.datetime.now()} + [Debug Mode]:{message}") try: requests.get(PING_URL, auth=('local', 'local'), timeout=10) print(f"{datetime.datetime.now()} [Main Script]: Ping sent to: %s" % PING_URL) except requests.RequestException as e: # Log ping failure here... print(f"{datetime.datetime.now()} [Main Script]: Ping failed: %s" % e) # Function to check if the script is already running def is_script_running(): """Checks if another instance of the script is already running.""" try: # Use pgrep to find processes with the exact command line of the script command_line = f"/usr/bin/python3 {os.path.abspath(__file__)} >> /var/log/nord-checker.log 2>&1" # Include redirection process = subprocess.run(["pgrep", "-f", command_line], capture_output=True, text=True) # If pgrep finds any matches, it means the script is already running if process.returncode == 0 and process.stdout: # Get the PIDs of the running instances pids = [int(pid) for pid in process.stdout.strip().split("\n")] # Exclude the current process's PID pids = [pid for pid in pids if pid != os.getpid()] # Only quit if there are two or more other instances running if len(pids) >= 2: for pid in pids: try: # Get the CMD value using ps process_info = subprocess.check_output(["ps", "-o", "cmd=", "-p", str(pid)], text=True).strip() print(f"{datetime.datetime.now()} [Main Script]: Another instance of the script is already running (PID: {pid}): {process_info}") except subprocess.CalledProcessError: print(f"{datetime.datetime.now()} [Main Script]: Another instance of the script is already running (PID: {pid}), but unable to retrieve process information.") return True except Exception as e: print(f"{datetime.datetime.now()} [Main Script]: Error checking for running instances: {e}") return False # Function to get a list of all NordVPN servers def get_nordvpn_servers(): """Fetches a list of all NordVPN servers from the API.""" try: response = requests.get(NORDVPN_API_URL) response.raise_for_status() # Raise an exception for bad status codes servers = response.json() return servers except requests.exceptions.RequestException as e: print(f"{datetime.datetime.now()} [Main Script]: Error fetching NordVPN servers: {e}") return [] # Get current IP before starting (using get_external_ip) CURRENT_IP = get_external_ip() if CURRENT_IP is None: print(f"{datetime.datetime.now()} [Main Script]: Unable to get current IP. Exiting.") sys.exit(1) # Exit if unable to get current IP else: print(f"{datetime.datetime.now()} [Main Script]: Current IP before VPN connections: {CURRENT_IP}") if is_script_running(): sys.exit(1) # Terminate any existing OpenVPN connections try: result = subprocess.run(["pgrep", "openvpn"], check=True, capture_output=True) pids = result.stdout.decode().strip().split('\n') # Get PIDs as a list for pid in pids: subprocess.run(["kill", pid]) print(f"{datetime.datetime.now()} [Main Script]: Existing OpenVPN connections terminated.") except subprocess.CalledProcessError: pass # No openvpn process found # Connect to PostgreSQL database conn, cursor = connect_to_database() # Get the list of recommended NordVPN servers servers = get_nordvpn_servers() # Create the temporary directory if it doesn't exist if not os.path.exists(TEMP_OVPN_DIR): os.makedirs(TEMP_OVPN_DIR) # Process each server server_count = len(servers) for index, server in enumerate(servers): # Check if the server uses OpenVPN TCP technology technologies = server.get('technologies', []) has_openvpn_tcp = any(tech.get('identifier') == 'openvpn_tcp' for tech in technologies) has_openvpn_dedicated_tcp = any(tech.get('identifier') == 'openvpn_dedicated_tcp' for tech in technologies) technologies_str = ', '.join([t.get('identifier', 'N/A') for t in technologies]) # Get technologies as a string if not has_openvpn_tcp or has_openvpn_dedicated_tcp: server_name = server.get('hostname') print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Skipping server without OpenVPN TCP or with OpenVPN Dedicated TCP. Technologies: {technologies_str}") continue # Skip to the next server server_name = server.get('hostname') server_ip = server.get('station') # Get the server IP address server_load = server.get('load', 'N/A') # Get server load, default to 'N/A' if not available server_status = server.get('status', 'N/A') # Get server status, default to 'N/A' if not available debug_print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Load: {server_load}, Status: {server_status}, Technologies: {technologies_str} Processing server... ") # Construct the temporary OVPN filename ovpn_filename = f"{server_name}.tcp.ovpn" ovpn_filepath = os.path.join(TEMP_OVPN_DIR, ovpn_filename) # Check if the server should be skipped based on last check time if should_skip_file(cursor, ovpn_filename, index+1, server_count, debug_print=debug_print): debug_print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Skipping server, checked within the last 3 days.") continue # Create temporary OVPN file from template with open(ovpn_filepath, "w") as f: f.write(DEFAULT_OVPN_CONFIG.format(server_ip=server_ip, server_cn=server_name)) # Establish VPN connection connection_successful, connection_message = establish_vpn_connection( ovpn_filepath, CREDENTIAL_FILE, LOG_FILE, debug_print=debug_print ) if connection_successful: print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: {connection_message}") # Check if the VPN connection is active try: subprocess.run(["pgrep", "-f", ovpn_filename], check=True, capture_output=True) # Check external IP (with validation and retries) external_ip = get_external_ip() if external_ip is None: print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Unable to get external IP. Skipping this server.") continue print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: External IP via VPN: {external_ip}") # Update or insert into database update_or_insert_ip(cursor, conn, ovpn_filename, external_ip) except subprocess.CalledProcessError: print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: VPN connection failed to establish.") else: print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Error starting OpenVPN connection: {connection_message}") # Disconnect VPN disconnect_vpn() print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: VPN disconnected.") # Wait for a random time between 5 and 8 seconds sleep_time = random.randint(2, 4) print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Waiting for {sleep_time} seconds before the next connection...") time.sleep(sleep_time) print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Resuming...") # Clean up the temporary OVPN file os.remove(ovpn_filepath) # Close the database connection cursor.close() conn.close()