diff --git a/src/app.py b/src/app.py index 2ebbf6a..58c4b9b 100644 --- a/src/app.py +++ b/src/app.py @@ -1,110 +1,88 @@ -from flask import Flask, request, jsonify -import json -import subprocess -from flask_jwt_extended import JWTManager, create_access_token -import os -import datetime - -app = Flask(__name__) - -# Setup the Flask-JWT-Extended extension -app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "default-secret-key") -jwt = JWTManager(app) - -def split_directory_and_file(path): - directory, file_with_wildcard = os.path.split(path) - file_name = file_with_wildcard.split('*')[0] # Get the part before the wildcard - return directory, file_name - -def read_log_from_dir(dir_path, pattern, lines): - ret_var = "" - three_hours_ago = datetime.datetime.now() - datetime.timedelta(hours=3) - log_files = [f for f in os.listdir(dir_path) if pattern in f and datetime.datetime.fromtimestamp(os.path.getmtime(os.path.join(dir_path, f))) > three_hours_ago] - for file_name in log_files: - file_path = os.path.join(dir_path, file_name) - ret_var += f"{file_name}\n{subprocess.check_output(['tail', '-n', str(lines), file_path]).decode('utf-8')}\n\n" - return ret_var - -def read_log(log_file, lines): - return subprocess.check_output(['tail', '-n', str(lines), log_file]).decode('utf-8') - -def read_top(): - return subprocess.check_output(['top', '-b', '-n', '1']).decode('utf-8') - -@app.route('/login', methods=['POST']) -def login(): - username = request.json.get("username") - password = request.json.get("password") - if username != "admin" or password != "password": - return jsonify({"msg": "Bad username or password"}), 401 - access_token = create_access_token(identity=username) - return jsonify(access_token=access_token) - -# Function to replace placeholders in the issue configuration -def replace_placeholders(issue_config, paths): - for log in issue_config.get('logs', []): - log_file = log.get('log_file') - if log_file: - for placeholder, actual_path in paths.items(): - log['log_file'] = log['log_file'].replace(f"{{{placeholder}}}", actual_path) - for command in issue_config.get('commands', []): - comm = command.get('comm') - if comm: - for placeholder, actual_path in paths.items(): - command['comm'] = command['comm'].replace(f"{{{placeholder}}}", actual_path) - -# Function to merge custom config into main config -def merge_configs(main_config, custom_config): - for key, value in custom_config.items(): - if key in main_config: - # Merge commands and logs if issue type already exists - main_config[key]['commands'].extend(value.get('commands', [])) - main_config[key]['logs'].extend(value.get('logs', [])) - else: - # Add new issue type if it doesn't exist - main_config[key] = value - -@app.route('/get_logs', methods=['GET']) -def get_logs(): - issue_type = request.args.get("issue_type") - try: - # Load config_paths.json for dynamic paths - with open('/opt/ticket-ai/src/config_paths.json') as paths_file: - paths = json.load(paths_file) - - # Load main config.json - with open('/opt/ticket-ai/src/config.json') as config_file: - config = json.load(config_file) - - # Check for custom.json and merge if it exists - custom_config_path = '/opt/ticket-ai/src/custom.json' - if os.path.exists(custom_config_path): - with open(custom_config_path) as custom_file: - custom_config = json.load(custom_file) - merge_configs(config, custom_config) - - if issue_type not in config: - return jsonify({"error": "Invalid issue type"}) - - # Get the specific configuration for the issue type - issue_config = config[issue_type] - - # Replace placeholders in logs and commands - replace_placeholders(issue_config, paths) - - # Process logs and commands - response = { - log.get('log_file').split('*')[0]: f"```\n{read_log_from_dir(*split_directory_and_file(log.get('log_file')), log['lines'])}\n```" if '*' in log.get('log_file') else f"```\n{read_log(log['log_file'], log['lines'])}\n```" - for log in issue_config['logs'] - } - response.update({ - comm.get('tag'): f"```\n{os.popen(comm.get('comm')).read()}\n```" for comm in issue_config['commands'] - }) - - except Exception as e: - return jsonify({"error": str(e)}) - return jsonify(response) - - -if __name__ == '__main__': - app.run(debug=True, host="0.0.0.0") +from flask import Flask, request, jsonify +import json +import subprocess +import os +import datetime + +app = Flask(__name__) + +# Load paths from config_paths.json for placeholder replacement +with open('/opt/ticket-ai/src/config_paths.json') as paths_file: + paths = json.load(paths_file) + +# Function to split directory and file +def split_directory_and_file(path): + directory, file_with_wildcard = os.path.split(path) + file_name = file_with_wildcard.split('*')[0] # Get the part before the wildcard + return directory, file_name + +def replace_placeholders(text): + """Replace placeholders in the text with paths from config_paths.json.""" + for placeholder, actual_path in paths.items(): + text = text.replace(f"{{{placeholder}}}", actual_path) + return text + +def read_log_from_dir(dir_path, pattern, lines): + ret_var = "" + three_hours_ago = datetime.datetime.now() - datetime.timedelta(hours=3) + log_files = [f for f in os.listdir(dir_path) if pattern in f and datetime.datetime.fromtimestamp(os.path.getmtime(os.path.join(dir_path, f))) > three_hours_ago] + for file_name in log_files: + file_path = os.path.join(dir_path, file_name) + ret_var += f"{file_name}\n{subprocess.check_output(['tail', '-n', str(lines), file_path]).decode('utf-8')}\n\n" + return ret_var + +def read_log(log_file, lines): + # Replace placeholder in log file path + log_file = replace_placeholders(log_file) + return subprocess.check_output(['tail', '-n', str(lines), log_file]).decode('utf-8') + +@app.route('/get_logs', methods=['GET']) +def get_logs(): + tags = request.args.getlist("tags") + try: + # Load main config-tags.json + with open('/opt/ticket-ai/src/config.json') as config_file: + config = json.load(config_file) + + # Check for custom.json and merge if it exists + custom_config_path = '/opt/ticket-ai/src/custom.json' + if os.path.exists(custom_config_path): + with open(custom_config_path) as custom_file: + custom_config = json.load(custom_file) + for key, value in custom_config.items(): + if key in config: + config[key].extend(value) + else: + config[key] = value + + response = {} + + # Process each tag in the list + for tag in tags: + # Search for the tag in commands and logs + tag_data = next((item for item in config["tags"] if item["tag"] == tag), None) + if not tag_data: + response[tag] = "Tag not found" + continue + + # Determine if the tag is a command or a log + if tag_data["type"] == "command": + response[tag] = f"```\n{os.popen(tag_data['content']).read()}\n```" + elif tag_data["type"] == "log": + log_file = tag_data["content"] + # Replace any placeholders in the log file path + log_file = replace_placeholders(log_file) + if "*" in log_file: + # Handle wildcard in log file path + response[tag] = f"```\n{read_log_from_dir(*split_directory_and_file(log_file), tag_data['lines'])}\n```" + else: + # Regular log file + response[tag] = f"```\n{read_log(log_file, tag_data['lines'])}\n```" + + except Exception as e: + return jsonify({"error": str(e)}) + + return jsonify(response) + +if __name__ == '__main__': + app.run(debug=True, host="0.0.0.0") diff --git a/src/config.json b/src/config.json index a6b0e0d..0ba1458 100644 --- a/src/config.json +++ b/src/config.json @@ -1,175 +1,162 @@ { - "APEX Upgrade Request": { - "commands": [ - { - "comm": "df -h | head -n 1 && df -h | grep '^/dev/sd'", - "tag": "disk_usage" - }, - { - "comm": "/opt/ticket-ai/src/scripts/tablespaceUsage.sh;sleep 5;cat /tmp/tablespace_usage.tmp", - "tag": "tablespace_usage" - }, - { - "comm": "/opt/ticket-ai/src/scripts/apexUsage.sh;sleep 5;cat /tmp/apex_usage.tmp", - "tag": "apex_usage" - } - ], - "logs": [ - { - "log_file": "{oracle_diag_log}", - "lines": 10 - } - ] - }, - - "Server Performance Problem": { - "commands": [ - { - "comm": "top -b -n 1", - "tag": "top" - }, - { - "comm": "iostat -x 1 3", - "tag": "iostat" - }, - { - "comm": "df -h | head -n 1 && df -h | grep '^/dev/sd'", - "tag": "df" - }, - { - "comm": "/opt/ticket-ai/src/scripts/processfinder.sh;sleep 5;cat /tmp/findsql.tmp", - "tag": "processfinder" - }, - { - "comm": "/opt/ticket-ai/src/scripts/tablespaceUsage.sh;sleep 5;cat /tmp/tablespace_usage.tmp", - "tag": "tablespaceusage" - }, - { - "comm": "cat /opt/ords/config/databases/default/pool.xml", - "tag": "ordsconfig" - }, - { - "comm": "echo Current Date and time on server: ; date", - "tag": "datetime" - }, - { - "comm": "find {virtualmin_error_log} -type f -name '*error_log*' -mmin -15 -exec sh -c 'echo \"===== {} =====\"; tail -n 50 {}' \\;", - "tag": "apacheerrorlogs" - } - ], - "logs": [ - { - "log_file": "{tomcat_catalina_log}", - "lines": 150 - }, - { - "log_file": "{oracle_diag_log}", - "lines": 50 - }, - { - "log_file": "/var/log/messages", - "lines": 25 - } - ] - }, - - "Server Unavailable": { - "commands": [ - { - "comm": "top -b -n 1", - "tag": "top" - }, - { - "comm": "iostat -x 1 3", - "tag": "iostat" - }, - { - "comm": "df -h | head -n 1 && df -h | grep '^/dev/sd'", - "tag": "df" - }, - { - "comm": "/opt/ticket-ai/src/scripts/processfinder.sh;sleep 5;cat /tmp/findsql.tmp", - "tag": "processfinder" - }, - { - "comm": "/opt/ticket-ai/src/scripts/tablespaceUsage.sh;sleep 5;cat /tmp/tablespace_usage.tmp", - "tag": "tablespaceusage" - }, - { - "comm": "cat /opt/ords/config/databases/default/pool.xml", - "tag": "ordsconfig" - }, - { - "comm": "echo Current Date and time on server: ; date", - "tag": "datetime" - }, - { - "comm": "find {virtualmin_error_log} -type f -name '*error_log*' -mmin -15 -exec sh -c 'echo \"===== {} =====\"; tail -n 50 {}' \\;", - "tag": "apacheerrorlogs" - } - ], - "logs": [ - { - "log_file": "{tomcat_catalina_log}", - "lines": 150 - }, - { - "log_file": "{oracle_diag_log}", - "lines": 50 - }, - { - "log_file": "/var/log/messages", - "lines": 25 - } - ] - }, - - "Email Problem": { - "commands": [ - { - "comm": "timeout 5 bash -c 'echo > /dev/tcp/relay.maxapex.net/2525' && echo 'Relay Connected' || echo 'Relay Connection failed'", - "tag": "relay_connection" - }, - { - "comm": "df -h", - "tag": "df" - } - ], - "logs": [ - { - "log_file": "/var/log/messages", - "lines": 150 - } - ] - }, - - "Password Problem": { - "commands": [ - { - "comm": "for jail in $(sudo fail2ban-client status | grep \"Jail list\" | cut -d ':' -f2 | tr ',' ' '); do echo \"$jail:\"; sudo fail2ban-client status \"$jail\" | grep \"Banned IP list\"; done", - "tag": "banned_ips" - } - ], - "logs": [ - { - "log_file": "/var/log/secure", - "lines": 150 - } - ] - }, - - "Wallet Problem": { - "commands": [ - { - "comm": "output=$(su - oracle -s /bin/bash -c 'orapki wallet display -wallet /home/oracle/wallet/') && echo \"$output\"", - "tag": "wallet_certs" - } - ], - "logs": [ - { - "log_file": "/var/log/secure", - "lines": 0 - } - ] - } + "tags": [ + { + "tag": "disk_usage", + "type": "command", + "content": "df -h | head -n 1 && df -h | grep '^/dev/sd'" + }, + { + "tag": "tablespace_usage", + "type": "command", + "content": "/opt/ticket-ai/src/scripts/tablespaceUsage.sh;sleep 5;cat /tmp/tablespace_usage.tmp" + }, + { + "tag": "datafile_usage", + "type": "command", + "content": "/opt/ticket-ai/src/scripts/datafileUsage.sh;sleep 5;cat /tmp/datafile_usage.tmp" + }, + { + "tag": "db_version_info", + "type": "command", + "content": "/opt/ticket-ai/src/scripts/db_version_info.sh;sleep 5;cat /tmp/db_version_info.tmp" + }, + { + "tag": "apex_usage", + "type": "command", + "content": "/opt/ticket-ai/src/scripts/apexUsage.sh;sleep 5;cat /tmp/apex_usage.tmp" + }, + { + "tag": "top", + "type": "command", + "content": "top -b -n 1" + }, + { + "tag": "iostat", + "type": "command", + "content": "iostat -x 1 3" + }, + { + "tag": "df", + "type": "command", + "content": "df -h | head -n 1 && df -h | grep '^/dev/sd'" + }, + { + "tag": "processfinder", + "type": "command", + "content": "/opt/ticket-ai/src/scripts/processfinder.sh;sleep 5;cat /tmp/findsql.tmp" + }, + { + "tag": "ordsconfig", + "type": "command", + "content": "cat /opt/ords/config/databases/default/pool.xml" + }, + { + "tag": "datetime", + "type": "command", + "content": "echo Current Date and time on server: ; date" + }, + { + "tag": "apacheerrorlogs", + "type": "command", + "content": "find {virtualmin_error_log} -type f -name '*error_log*' -mmin -15 -exec sh -c 'echo \"===== {} =====\"; tail -n 50 {}' \\;" + }, + { + "tag": "relay_connection", + "type": "command", + "content": "timeout 5 bash -c 'echo > /dev/tcp/relay.maxapex.net/2525' && echo 'Relay Connected' || echo 'Relay Connection failed'" + }, + { + "tag": "banned_ips", + "type": "command", + "content": "for jail in $(sudo fail2ban-client status | grep \"Jail list\" | cut -d ':' -f2 | tr ',' ' '); do echo \"$jail:\"; sudo fail2ban-client status \"$jail\" | grep \"Banned IP list\"; done" + }, + { + "tag": "wallet_certs", + "type": "command", + "content": "output=$(su - oracle -s /bin/bash -c 'orapki wallet display -wallet /home/oracle/wallet/') && echo \"$output\"" + }, + { + "tag": "journalctl", + "type": "command", + "content": "output=$(journalctl -p 3 -xb) && echo \"$output\"" + }, + { + "tag": "postqueue", + "type": "command", + "content": "output=$(/usr/sbin/postqueue -p) && echo \"$output\"" + }, + { + "tag": "postfix_status", + "type": "command", + "content": "output=$(systemctl is-active postfix) && echo \"$output\"" + }, + { + "tag": "dovecot_status", + "type": "command", + "content": "output=$(systemctl is-active dovecot) && echo \"$output\"" + }, + { + "tag": "ipv6_status", + "type": "command", + "content": "output=$(ip -6 addr show | grep -q 'inet6' && echo 'IPv6 is active' || echo 'IPv6 is not active') && echo \"$output\"" + }, + { + "tag": "iptables", + "type": "command", + "content": "output=$(iptables -L -n) && echo \"$output\"" + }, + { + "tag": "java_heap", + "type": "command", + "content": "output=$(ps aux | grep java | grep -E 'Xms|Xmx') && echo \"$output\"" + }, + { + "tag": "oracle_alert_logs", + "type": "log", + "content": "{oracle_alert_logs}", + "lines": 50 + }, + { + "tag": "ords_logs", + "type": "log", + "content": "{ords_log}", + "lines": 150 + }, + { + "tag": "var_log_messages", + "type": "log", + "content": "/var/log/messages", + "lines": 150 + }, + { + "tag": "var_log_secure", + "type": "log", + "content": "/var/log/secure", + "lines": 150 + }, + { + "tag": "os-release", + "type": "log", + "content": "/etc/os-release", + "lines": 150 + }, + { + "tag": "var_log_maillog", + "type": "log", + "content": "/var/log/maillog", + "lines": 150 + }, + { + "tag": "var_log_fail2ban", + "type": "log", + "content": "/var/log/fail2ban.log", + "lines": 150 + }, + { + "tag": "var_log_mariadb", + "type": "log", + "content": "/var/log/mariadb/mariadb.log", + "lines": 150 + } + ] } diff --git a/src/config_paths.json b/src/config_paths.json index bc7613e..b931f45 100644 --- a/src/config_paths.json +++ b/src/config_paths.json @@ -1,5 +1,5 @@ { - "oracle_diag_log": "/opt/oracle/diag/rdbms/xe/XE/trace/alert_XE.log", + "oracle_alert_logs": "/opt/oracle/diag/rdbms/xe/XE/trace/alert_XE.log", "virtualmin_error_log": "/var/log/virtualmin/error_log", - "tomcat_catalina_log": "/opt/tomcat/logs/catalina.out" + "ords_log": "/opt/tomcat/logs/catalina.out" } diff --git a/src/scripts/datafileUsage.sh b/src/scripts/datafileUsage.sh new file mode 100755 index 0000000..09f4fde --- /dev/null +++ b/src/scripts/datafileUsage.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +# File paths for output +OUTPUT_FILE="/tmp/datafile_usage.tmp" +PDB_LIST_FILE="/tmp/pdb_list.tmp" +> "$OUTPUT_FILE" # Clear the output file before starting +touch $OUTPUT_FILE +chown oracle $OUTPUT_FILE + +# Function to execute the datafile usage query in a specific container +run_query() { + local container_name="$1" + su - oracle -s /bin/bash -c " + sqlplus -s / as sysdba <> \"$OUTPUT_FILE\" + whenever sqlerror exit sql.sqlcode; + set echo off + set heading on + SET UNDERLINE '=' + set feedback off + set linesize 150 + + -- Switch to the specified container + ALTER SESSION SET CONTAINER = $container_name; + + -- Print the container name for clarity + PROMPT Datafile usage for container: $container_name; + + SELECT d.file_name AS datafile_path, + d.tablespace_name, + ROUND(d.bytes / 1024 / 1024, 2) AS allocated_mb, + ROUND((d.bytes - NVL(fs.bytes, 0)) / 1024 / 1024, 2) AS used_mb, + ROUND(NVL(fs.bytes, 0) / 1024 / 1024, 2) AS free_mb, + ROUND(((d.bytes - NVL(fs.bytes, 0)) / d.bytes) * 100, 2) AS pct_used + FROM dba_data_files d + LEFT JOIN ( + SELECT file_id, SUM(bytes) AS bytes + FROM dba_free_space + GROUP BY file_id + ) fs ON d.file_id = fs.file_id + ORDER BY d.tablespace_name, d.file_name; + + exit; +EOF + " +} + +# Run the query for the CDB (root container) +echo "Gathering datafile usage for CDB (root container)..." >> "$OUTPUT_FILE" +run_query 'CDB\$ROOT' + +# Get a list of all PDBs, excluding PDB$SEED, and write to a temporary file +su - oracle -s /bin/bash -c " + sqlplus -s / as sysdba <<'EOF' + set heading off + set feedback off + set pagesize 0 + spool $PDB_LIST_FILE + SELECT NAME FROM v\$pdbs WHERE NAME NOT IN ('PDB\$SEED'); + spool off + exit; +EOF +" + +# Verify that the PDB_LIST_FILE was created and contains data +if [[ -f "$PDB_LIST_FILE" && -s "$PDB_LIST_FILE" ]]; then + # Read each valid PDB name from the temporary file + while IFS= read -r PDB; do + PDB=$(echo "$PDB" | xargs) # Trim any leading/trailing whitespace + if [[ -n "$PDB" ]]; then + echo "Gathering datafile usage for PDB: $PDB..." >> "$OUTPUT_FILE" + run_query "$PDB" + fi + done < "$PDB_LIST_FILE" +else + echo "No PDBs found or could not access v\$pdbs view" >> "$OUTPUT_FILE" +fi + +# Clean up the temporary file +rm -f "$PDB_LIST_FILE" diff --git a/src/scripts/db_version_info.sh b/src/scripts/db_version_info.sh new file mode 100755 index 0000000..67a16a4 --- /dev/null +++ b/src/scripts/db_version_info.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# File path for output +OUTPUT_FILE="/tmp/db_version_info.tmp" +PDB_LIST_FILE="/tmp/pdb_list.tmp" +> "$OUTPUT_FILE" # Clear the output file before starting +touch $OUTPUT_FILE +chown oracle $OUTPUT_FILE + +# Function to get the Oracle Database version +get_oracle_version() { + su - oracle -s /bin/bash -c " + sqlplus -s / as sysdba <<'EOF' >> \"$OUTPUT_FILE\" + whenever sqlerror exit sql.sqlcode; + set echo off + set heading on + SET UNDERLINE '=' + set feedback off + set linesize 150 + + PROMPT Oracle Database Version:; + SELECT banner FROM v\$version WHERE banner LIKE 'Oracle%' AND ROWNUM = 1; + + exit; +EOF + " +} + + +# Function to get APEX and ORDS versions within a specific container +get_apex_ords_versions() { + local container_name="$1" + su - oracle -s /bin/bash -c " + sqlplus -s / as sysdba <> \"$OUTPUT_FILE\" + whenever sqlerror exit sql.sqlcode; + set echo off + set heading on + SET UNDERLINE '=' + set feedback off + set linesize 150 + + -- Switch to the specified container + ALTER SESSION SET CONTAINER = $container_name; + + -- Print the container name for clarity + PROMPT Version information for container: $container_name; + + -- APEX version + PROMPT APEX Version:; + SELECT version_no AS version FROM apex_release; + + -- ORDS version + PROMPT ORDS Version:; + SELECT 'ORDS is ' || version AS \"Version of ORDS\" + FROM ords_metadata.ords_schema_version; + + exit; +EOF + " +} + +# Run the Oracle version query +echo "Gathering Oracle Database version..." >> "$OUTPUT_FILE" +get_oracle_version + +# Get a list of all PDBs, excluding PDB$SEED, and write to a temporary file +su - oracle -s /bin/bash -c " + sqlplus -s / as sysdba <<'EOF' + set heading off + set feedback off + set pagesize 0 + spool $PDB_LIST_FILE + SELECT NAME FROM v\$pdbs WHERE NAME NOT IN ('PDB\$SEED'); + spool off + exit; +EOF +" + +# Verify that the PDB_LIST_FILE was created and contains data +if [[ -f "$PDB_LIST_FILE" && -s "$PDB_LIST_FILE" ]]; then + # Read each valid PDB name from the temporary file + while IFS= read -r PDB; do + PDB=$(echo "$PDB" | xargs) # Trim any leading/trailing whitespace + if [[ -n "$PDB" ]]; then + echo "Gathering APEX and ORDS versions for PDB: $PDB..." >> "$OUTPUT_FILE" + get_apex_ords_versions "$PDB" + fi + done < "$PDB_LIST_FILE" +else + echo "No PDBs found or could not access v\$pdbs view" >> "$OUTPUT_FILE" +fi + +# Clean up the temporary file +rm -f "$PDB_LIST_FILE"