diff --git a/.gitignore b/.gitignore
index 934c2b9842..828d595a94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@ ansible/host_vars/*
**/vendor_files
.vscode/
*.swp
+ansible/machine_audit/backend/collectedInfo
+ansible/machine_audit/backend/.env
diff --git a/ansible/machine_audit/backend/getNodeList.py b/ansible/machine_audit/backend/getNodeList.py
new file mode 100755
index 0000000000..312737253f
--- /dev/null
+++ b/ansible/machine_audit/backend/getNodeList.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+
+# Some portions generated by IBM Bob
+
+import sys
+import jenkins
+from pathlib import Path
+
+def getIP(nodeConfig):
+ find1 = nodeConfig.find("") + 6
+ find2 = nodeConfig.find("")
+ if find1 > 5 and find2 > -1:
+ ip = nodeConfig[find1:find2]
+ return ip
+ else:
+ return None
+
+def getNodePort(nodeConfig):
+ find1 = nodeConfig.find("") + 6
+ find2 = nodeConfig.find("")
+ if find1 > 5 and find2 > -1:
+ port = nodeConfig[find1:find2]
+ return port
+ else:
+ return None
+
+def createServer(username, password, url):
+ try:
+ server = jenkins.Jenkins(url=url, username=username,
+ password=password)
+ return server
+
+ except Exception as e:
+ print("Error: Could not connect to Jenkins server")
+ return sys.exit(1)
+
+def getNodeList(url, username, password):
+
+ try:
+ # Create collectedInfo directory if it doesn't exist
+ script_dir = Path(__file__).parent
+ collected_info_dir = script_dir / "collectedInfo"
+ collected_info_dir.mkdir(exist_ok=True)
+
+ # Define machine.list file path
+ machine_list_path = collected_info_dir / "machine.list"
+
+ # Remove existing file if it exists
+ if machine_list_path.exists():
+ machine_list_path.unlink()
+
+ # Connect to Jenkins
+ server = createServer(username, password, url)
+ nodes = server.get_nodes()
+
+ # Create new machine.list file and write node information
+ with open(machine_list_path, 'w') as f:
+ for node in nodes:
+ if "build" in node["name"] or "test" in node["name"] or "dockerhost" in node["name"]:
+ config = server.get_node_config(node["name"])
+ ip = getIP(config)
+ port = getNodePort(config)
+ f.write(f"{node['name']} {ip} {port}\n")
+
+ print(f"Machine list saved to: {machine_list_path}")
+
+ except jenkins.JenkinsException as e:
+ print(f"Jenkins error: {e}")
+ return None
+ except Exception as e:
+ print(f"Error: {e}")
+ return None
+
+if __name__ == "__main__":
+ url, username, password = sys.argv[1:4]
+ getNodeList(url, username, password)
diff --git a/ansible/machine_audit/backend/main.py b/ansible/machine_audit/backend/main.py
new file mode 100755
index 0000000000..31f17acbfe
--- /dev/null
+++ b/ansible/machine_audit/backend/main.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+
+# Some portions generated by IBM Bob
+
+import sys
+import subprocess
+from pathlib import Path
+from getNodeList import getNodeList
+
+# ANSI color codes
+GREEN = '\033[92m'
+RED = '\033[91m'
+CYAN = '\033[96m'
+RESET = '\033[0m'
+
+UNIX_LOG_PATH="/var/log/machine_info.json"
+WINDOWS_LOG_PATH="c:\\machine_info.json"
+
+def scp_machine_info(node_name, ip, port, collected_info_dir, log_path):
+
+ if ip is None or ip == "None" or port is None or port == "None":
+ print(f" {CYAN}[SKIP]{RESET} {node_name}: IP or port is None")
+ return False
+
+ local_filename = f"{node_name}_machine_info.json"
+ local_filepath = collected_info_dir / local_filename
+
+ # Use nagios user to SCP the file
+ remote_location = f"nagios@{ip}:{log_path}"
+ scp_command = [
+ "scp",
+ "-P", port,
+ "-o", "StrictHostKeyChecking=no",
+ "-o", "ConnectTimeout=5",
+ "-o", "NumberOfPasswordPrompts=0",
+ remote_location,
+ str(local_filepath)
+ ]
+
+ print(f" [INFO] Copying from {node_name} ({ip}:{port})...")
+
+ try:
+ result = subprocess.run(
+ scp_command,
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+
+ if result.returncode == 0:
+ print(f" {GREEN}[SUCCESS]{RESET} File saved to {local_filename}")
+ return True
+ else:
+ print(f" {RED}[ERROR]{RESET} SCP failed for {node_name}: {result.stderr.strip()}")
+ return False
+
+ except subprocess.TimeoutExpired:
+ print(f" {RED}[ERROR]{RESET} SCP timeout for {node_name}")
+ return False
+ except Exception as e:
+ print(f" {RED}[ERROR]{RESET} Exception for {node_name}: {e}")
+ return False
+
+
+def main():
+
+ # Jenkins credentials from command line
+ url, username, password = sys.argv[1:4]
+
+ # Generate machine list from Jenkins
+ print("\nFetching node list from Jenkins...")
+ getNodeList(url, username, password)
+
+ script_dir = Path(__file__).parent
+ collected_info_dir = script_dir / "collectedInfo"
+ machine_list_path = collected_info_dir / "machine.list"
+
+ if not machine_list_path.exists():
+ print(f"Error: machine.list not found at {machine_list_path}")
+ return 1
+
+ # Remove any existing *_machine_info.json files from collectedInfo directory
+ print("\nCleaning up old machine_info.json files...")
+ for old_file in collected_info_dir.glob("*_machine_info.json"):
+ old_file.unlink()
+ print(f" Removed: {old_file.name}")
+
+ # Read machine list and collect files
+ print("\nCollecting machine_info.json from each machine:")
+ successful = 0
+ failed = 0
+
+ with open(machine_list_path, 'r') as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+
+ parts = line.split()
+ if len(parts) >= 3:
+ node_name = parts[0]
+ ip = parts[1]
+ port = parts[2]
+
+ # SCP only from non windows machines for now
+ if scp_machine_info(node_name, ip, port, collected_info_dir, UNIX_LOG_PATH):
+ successful += 1
+ else:
+ failed += 1
+
+ # Print summary
+ print(f"\n{'='*60}")
+ print(f"Collection Summary:")
+ print(f" Successful: {successful}")
+ print(f" Failed: {failed}")
+ print(f"{'='*60}")
+
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())