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
This commit is contained in:
Karl 2025-08-18 15:13:38 +01:00
parent a80f1c8d3c
commit 3cb15005e2
2 changed files with 82 additions and 50 deletions

64
app.py
View File

@ -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__":

View File

@ -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()