From 3cb15005e220a4d4c0783ce133f0c2e123957187 Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 18 Aug 2025 15:13:38 +0100 Subject: [PATCH] feat(config): enhance NPM config update with stream management - Pass authentication credentials to background threads for NPM updates - Improve regex patterns for accurate location block matching - Add support for creating new location blocks for streams - Return success status from NPM config update operations - Update test script to handle errors and apply changes automatically --- app.py | 64 +++++++++++++++++++++++++++---------------- test_npm_update.py | 68 ++++++++++++++++++++++++++++------------------ 2 files changed, 82 insertions(+), 50 deletions(-) diff --git a/app.py b/app.py index 995c6b3..37bdee2 100644 --- a/app.py +++ b/app.py @@ -273,7 +273,7 @@ def add_account() -> Union[Response, str]: cache_key_home = f"view/{session['username']}/home" cache.delete(cache_key_home) # Run the NPM config update in a background thread - thread = threading.Thread(target=_update_npm_config_in_background) + thread = threading.Thread(target=_update_npm_config_in_background, args=(session["auth_credentials"],)) thread.start() return redirect(url_for("user_accounts")) elif result is False: @@ -330,7 +330,7 @@ def validate_account() -> Tuple[Response, int]: cache_key = f"view/{session['username']}/accounts" cache.delete(cache_key) # Run the NPM config update in a background thread - thread = threading.Thread(target=_update_npm_config_in_background) + thread = threading.Thread(target=_update_npm_config_in_background, args=(session["auth_credentials"],)) thread.start() return jsonify(response_data), response.status_code except requests.exceptions.RequestException as e: @@ -514,34 +514,48 @@ class NginxProxyManager: def update_config_with_streams(config, streams): # Get all stream names from the database db_stream_names = {stream['streamName'] for stream in streams} - - # Find all location blocks in the config - location_blocks = re.findall(r'location ~ \^/(\w+)\(\.\*\)\$ \{[^}]+\}', config) + + # Find all location blocks in the config (more accurate pattern) + location_blocks = re.findall(r'location\s+~\s+\^/(\w+)\(\.\*\)\$\s*\{[^}]*return\s+302\s+([^;]+);[^}]*\}', config) + existing_stream_names = {stream_name for stream_name, _ in location_blocks} # Remove location blocks that are not in the database - for stream_name in location_blocks: + for stream_name in existing_stream_names: if stream_name not in db_stream_names: print(f"Removing location block for stream: {stream_name}") - pattern = re.compile(f'location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{[^}}]+}}\\s*', re.DOTALL) + pattern = re.compile(f'location\\s+~\\s+\\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$\\s*\\{{[^}}]*return\\s+302\\s+[^;]+;[^}}]*\\}}\\s*', re.DOTALL) config = pattern.sub('', config) - # Update existing stream URLs + # Update existing stream URLs and create new ones for stream in streams: stream_name = stream['streamName'] stream_url = stream['streamURL'] - if stream_url: # Ensure there is a URL to update to - # Use a more specific regex to avoid replacing parts of other URLs - pattern = re.compile(f'(location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{\\s*return 302 )([^;]+)(;\\s*}})') - config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config) + if stream_url: # Ensure there is a URL to update to + # Check if location block already exists for this stream + if stream_name in existing_stream_names: + # Update existing location block + pattern = re.compile(f'(location\\s+~\\s+\\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$\\s*\\{{[^}}]*return\\s+302\\s+)([^;]+)(;[^}}]*\\}})', re.DOTALL) + config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config) + else: + # Create new location block for this stream + new_location_block = f'location ~ ^/{stream_name}(.*)$ {{\n return 302 {stream_url}/$1$is_args$args;\n}}\n' + # Add the new block at the end of the config (before the last closing brace) + config = config.rstrip() + '\n' + new_location_block + print(f"Created new location block for stream: {stream_name}") return config -def _update_npm_config(): +def _update_npm_config(credentials=None): """Helper function to update the NPM config.""" - if not session.get('user_id') or int(session.get('user_id')) != 1: - print("Unauthorized attempt to update NPM config.") - return - + # Use provided credentials or get from session (for backward compatibility) + if credentials is None: + # This is for backward compatibility when called from background threads + # where session context might not be available + if not session.get('user_id') or int(session.get('user_id')) != 1: + print("Unauthorized attempt to update NPM config.") + return False + credentials = session.get("auth_credentials") + npm = NginxProxyManager(app.config['NPM_HOST'], app.config['NPM_EMAIL'], app.config['NPM_PASSWORD']) npm.login() host = npm.get_proxy_host(9) @@ -549,8 +563,8 @@ def _update_npm_config(): current_config = host.get('advanced_config', '') backend_url = f"{app.config['BACKEND_URL']}/get_all_stream_urls" - credentials = base64.b64decode(session["auth_credentials"]).decode() - username, password = credentials.split(":", 1) + decoded_credentials = base64.b64decode(credentials).decode() + username, password = decoded_credentials.split(":", 1) auth = requests.auth.HTTPBasicAuth(username, password) try: @@ -559,28 +573,30 @@ def _update_npm_config(): streams = response.json() except requests.exceptions.RequestException as e: print(f"Failed to fetch streams from backend: {e}") - return + return False if streams: new_config = update_config_with_streams(current_config, streams) npm.update_proxy_host_config(9, new_config) print("NPM config updated successfully.") + return True else: print("Failed to update NPM config.") + return False -def _update_npm_config_in_background(): +def _update_npm_config_in_background(credentials): with app.app_context(): - _update_npm_config() + _update_npm_config(credentials) @app.route('/update_host_9_config', methods=['POST']) def update_host_9_config(): if not session.get('user_id') or int(session.get('user_id')) != 1: return jsonify({'error': 'Unauthorized'}), 401 - thread = threading.Thread(target=_update_npm_config_in_background) + thread = threading.Thread(target=_update_npm_config_in_background, args=(session["auth_credentials"],)) thread.start() - return jsonify({'message': 'NPM config update started in the background.'}), 202 + return jsonify({'message': 'NPM config update started in the background. Check logs for status.'}), 202 if __name__ == "__main__": diff --git a/test_npm_update.py b/test_npm_update.py index 68dd7bf..368a0f9 100644 --- a/test_npm_update.py +++ b/test_npm_update.py @@ -119,46 +119,62 @@ def get_streams_from_db(db_host, db_user, db_pass, db_name, db_port): def update_config_with_streams(config, streams): # Get all stream names from the database db_stream_names = {stream['streamName'] for stream in streams} - - # Find all location blocks in the config - location_blocks = re.findall(r'location ~ \^/(\w+)\(\.\*\)\$ \{[^}]+\}', config) + + # Find all location blocks in the config (more accurate pattern) + location_blocks = re.findall(r'location\s+~\s+\^/(\w+)\(\.\*\)\$\s*\{[^}]*return\s+302\s+([^;]+);[^}]*\}', config) + existing_stream_names = {stream_name for stream_name, _ in location_blocks} # Remove location blocks that are not in the database - for stream_name in location_blocks: + for stream_name in existing_stream_names: if stream_name not in db_stream_names: print(f"Removing location block for stream: {stream_name}") - pattern = re.compile(f'location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{[^}}]+}}\\s*', re.DOTALL) + pattern = re.compile(f'location\\s+~\\s+\\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$\\s*\\{{[^}}]*return\\s+302\\s+[^;]+;[^}}]*\\}}\\s*', re.DOTALL) config = pattern.sub('', config) - # Update existing stream URLs + # Update existing stream URLs and create new ones for stream in streams: stream_name = stream['streamName'] stream_url = stream['streamURL'] - if stream_url: # Ensure there is a URL to update to - # Use a more specific regex to avoid replacing parts of other URLs - pattern = re.compile(f'(location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{\\s*return 302 )([^;]+)(;\\s*}})') - config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config) + if stream_url: # Ensure there is a URL to update to + # Check if location block already exists for this stream + if stream_name in existing_stream_names: + # Update existing location block + pattern = re.compile(f'(location\\s+~\\s+\\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$\\s*\\{{[^}}]*return\\s+302\\s+)([^;]+)(;[^}}]*\\}})', re.DOTALL) + config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config) + else: + # Create new location block for this stream + new_location_block = f'location ~ ^/{stream_name}(.*)$ {{\n return 302 {stream_url}/$1$is_args$args;\n}}\n' + # Add the new block at the end of the config (before the last closing brace) + config = config.rstrip() + '\n' + new_location_block + print(f"Created new location block for stream: {stream_name}") return config def main(): - npm = NginxProxyManager(app_config.NPM_HOST, app_config.NPM_EMAIL, app_config.NPM_PASSWORD) - npm.login() - host = npm.get_proxy_host(9) - if host: - current_config = host.get('advanced_config', '') - print("Current Config:") - print(current_config) - - streams = get_streams_from_db(app_config.DBHOST, app_config.DBUSER, app_config.DBPASS, app_config.DATABASE, app_config.DBPORT) - if streams: - new_config = update_config_with_streams(current_config, streams) - print("\nNew Config:") - print(new_config) + try: + npm = NginxProxyManager(app_config.NPM_HOST, app_config.NPM_EMAIL, app_config.NPM_PASSWORD) + npm.login() + host = npm.get_proxy_host(9) + if host: + current_config = host.get('advanced_config', '') + print("Current Config:") + print(current_config) - # Uncomment the following line to apply the changes - npm.update_proxy_host_config(9, new_config) - print("\nTo apply the changes, uncomment the last line in the main function.") + streams = get_streams_from_db(app_config.DBHOST, app_config.DBUSER, app_config.DBPASS, app_config.DATABASE, app_config.DBPORT) + if streams: + new_config = update_config_with_streams(current_config, streams) + print("\nNew Config:") + print(new_config) + + # Apply the changes + npm.update_proxy_host_config(9, new_config) + print("\nChanges applied successfully.") + else: + print("No streams found in database.") + else: + print("Failed to get proxy host configuration.") + except Exception as e: + print(f"An error occurred: {e}") if __name__ == "__main__": main() \ No newline at end of file