mirror of
				https://github.com/9technologygroup/patchmon.net.git
				synced 2025-10-31 20:13:50 +00:00 
			
		
		
		
	Merge pull request #242 from PatchMon/release/1-3-1
Agent update initiation from server
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1430,6 +1430,69 @@ router.patch( | ||||
| 	}, | ||||
| ); | ||||
|  | ||||
| // Force agent update for specific host | ||||
| router.post( | ||||
| 	"/:hostId/force-agent-update", | ||||
| 	authenticateToken, | ||||
| 	requireManageHosts, | ||||
| 	async (req, res) => { | ||||
| 		try { | ||||
| 			const { hostId } = req.params; | ||||
|  | ||||
| 			// Get host to verify it exists | ||||
| 			const host = await prisma.hosts.findUnique({ | ||||
| 				where: { id: hostId }, | ||||
| 			}); | ||||
|  | ||||
| 			if (!host) { | ||||
| 				return res.status(404).json({ error: "Host not found" }); | ||||
| 			} | ||||
|  | ||||
| 			// Get queue manager | ||||
| 			const { QUEUE_NAMES } = require("../services/automation"); | ||||
| 			const queueManager = req.app.locals.queueManager; | ||||
|  | ||||
| 			if (!queueManager) { | ||||
| 				return res.status(500).json({ | ||||
| 					error: "Queue manager not available", | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			// Get the agent-commands queue | ||||
| 			const queue = queueManager.queues[QUEUE_NAMES.AGENT_COMMANDS]; | ||||
|  | ||||
| 			// Add job to queue | ||||
| 			await queue.add( | ||||
| 				"update_agent", | ||||
| 				{ | ||||
| 					api_id: host.api_id, | ||||
| 					type: "update_agent", | ||||
| 				}, | ||||
| 				{ | ||||
| 					attempts: 3, | ||||
| 					backoff: { | ||||
| 						type: "exponential", | ||||
| 						delay: 2000, | ||||
| 					}, | ||||
| 				}, | ||||
| 			); | ||||
|  | ||||
| 			res.json({ | ||||
| 				success: true, | ||||
| 				message: "Agent update queued successfully", | ||||
| 				host: { | ||||
| 					id: host.id, | ||||
| 					friendlyName: host.friendly_name, | ||||
| 					apiId: host.api_id, | ||||
| 				}, | ||||
| 			}); | ||||
| 		} catch (error) { | ||||
| 			console.error("Force agent update error:", error); | ||||
| 			res.status(500).json({ error: "Failed to force agent update" }); | ||||
| 		} | ||||
| 	}, | ||||
| ); | ||||
|  | ||||
| // Serve the installation script (requires API authentication) | ||||
| router.get("/install", async (req, res) => { | ||||
| 	try { | ||||
|   | ||||
| @@ -176,6 +176,15 @@ function pushSettingsUpdate(apiId, newInterval) { | ||||
| 	); | ||||
| } | ||||
|  | ||||
| function pushUpdateAgent(apiId) { | ||||
| 	const ws = apiIdToSocket.get(apiId); | ||||
| 	safeSend(ws, JSON.stringify({ type: "update_agent" })); | ||||
| } | ||||
|  | ||||
| function getConnectionByApiId(apiId) { | ||||
| 	return apiIdToSocket.get(apiId); | ||||
| } | ||||
|  | ||||
| function pushUpdateNotification(apiId, updateInfo) { | ||||
| 	const ws = apiIdToSocket.get(apiId); | ||||
| 	if (ws && ws.readyState === WebSocket.OPEN) { | ||||
| @@ -330,10 +339,12 @@ module.exports = { | ||||
| 	broadcastSettingsUpdate, | ||||
| 	pushReportNow, | ||||
| 	pushSettingsUpdate, | ||||
| 	pushUpdateAgent, | ||||
| 	pushUpdateNotification, | ||||
| 	pushUpdateNotificationToAll, | ||||
| 	// Expose read-only view of connected agents | ||||
| 	getConnectedApiIds: () => Array.from(apiIdToSocket.keys()), | ||||
| 	getConnectionByApiId, | ||||
| 	isConnected: (apiId) => { | ||||
| 		const ws = apiIdToSocket.get(apiId); | ||||
| 		return !!ws && ws.readyState === WebSocket.OPEN; | ||||
|   | ||||
| @@ -190,6 +190,19 @@ class QueueManager { | ||||
| 					// For settings update, we need additional data | ||||
| 					const { update_interval } = job.data; | ||||
| 					agentWs.pushSettingsUpdate(api_id, update_interval); | ||||
| 				} else if (type === "update_agent") { | ||||
| 					// Force agent to update by sending WebSocket command | ||||
| 					const ws = agentWs.getConnectionByApiId(api_id); | ||||
| 					if (ws && ws.readyState === 1) { | ||||
| 						// WebSocket.OPEN | ||||
| 						agentWs.pushUpdateAgent(api_id); | ||||
| 						console.log(`✅ Update command sent to agent ${api_id}`); | ||||
| 					} else { | ||||
| 						console.error(`❌ Agent ${api_id} is not connected`); | ||||
| 						throw new Error( | ||||
| 							`Agent ${api_id} is not connected. Cannot send update command.`, | ||||
| 						); | ||||
| 					} | ||||
| 				} else { | ||||
| 					console.error(`Unknown agent command type: ${type}`); | ||||
| 				} | ||||
|   | ||||
| @@ -187,6 +187,16 @@ const HostDetail = () => { | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	// Force agent update mutation | ||||
| 	const forceAgentUpdateMutation = useMutation({ | ||||
| 		mutationFn: () => | ||||
| 			adminHostsAPI.forceAgentUpdate(hostId).then((res) => res.data), | ||||
| 		onSuccess: () => { | ||||
| 			queryClient.invalidateQueries(["host", hostId]); | ||||
| 			queryClient.invalidateQueries(["hosts"]); | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	const updateFriendlyNameMutation = useMutation({ | ||||
| 		mutationFn: (friendlyName) => | ||||
| 			adminHostsAPI | ||||
| @@ -703,6 +713,29 @@ const HostDetail = () => { | ||||
| 											/> | ||||
| 										</button> | ||||
| 									</div> | ||||
|  | ||||
| 									<div> | ||||
| 										<p className="text-xs text-secondary-500 dark:text-secondary-300 mb-1.5"> | ||||
| 											Force Update | ||||
| 										</p> | ||||
| 										<button | ||||
| 											type="button" | ||||
| 											onClick={() => forceAgentUpdateMutation.mutate()} | ||||
| 											disabled={forceAgentUpdateMutation.isPending} | ||||
| 											className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-primary-600 dark:text-primary-400 bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-md hover:bg-primary-100 dark:hover:bg-primary-900/40 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" | ||||
| 										> | ||||
| 											<RefreshCw | ||||
| 												className={`h-3 w-3 ${ | ||||
| 													forceAgentUpdateMutation.isPending | ||||
| 														? "animate-spin" | ||||
| 														: "" | ||||
| 												}`} | ||||
| 											/> | ||||
| 											{forceAgentUpdateMutation.isPending | ||||
| 												? "Updating..." | ||||
| 												: "Update Now"} | ||||
| 										</button> | ||||
| 									</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						)} | ||||
|   | ||||
| @@ -95,6 +95,7 @@ export const adminHostsAPI = { | ||||
| 		api.put("/hosts/bulk/groups", { hostIds, groupIds }), | ||||
| 	toggleAutoUpdate: (hostId, autoUpdate) => | ||||
| 		api.patch(`/hosts/${hostId}/auto-update`, { auto_update: autoUpdate }), | ||||
| 	forceAgentUpdate: (hostId) => api.post(`/hosts/${hostId}/force-agent-update`), | ||||
| 	updateFriendlyName: (hostId, friendlyName) => | ||||
| 		api.patch(`/hosts/${hostId}/friendly-name`, { | ||||
| 			friendly_name: friendlyName, | ||||
|   | ||||
							
								
								
									
										54
									
								
								setup.sh
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								setup.sh
									
									
									
									
									
								
							| @@ -1797,7 +1797,12 @@ create_agent_version() { | ||||
|         cp "$APP_DIR/agents/patchmon-agent.sh" "$APP_DIR/backend/" | ||||
|          | ||||
|         print_status "Agent version management removed - using file-based approach" | ||||
| # Ensure we close the conditional and the function properly | ||||
|     fi | ||||
|      | ||||
|     # Make agent binaries executable | ||||
|     if [ -d "$APP_DIR/agents" ]; then | ||||
|         chmod +x "$APP_DIR/agents/patchmon-agent-linux-"* 2>/dev/null || true | ||||
|         print_status "Agent binaries made executable" | ||||
|     fi | ||||
|  | ||||
|     return 0 | ||||
| @@ -2703,6 +2708,13 @@ update_env_file() { | ||||
|     : ${TFA_MAX_REMEMBER_SESSIONS:=5} | ||||
|     : ${TFA_SUSPICIOUS_ACTIVITY_THRESHOLD:=3} | ||||
|      | ||||
|     # Prisma Connection Pool | ||||
|     : ${DB_CONNECTION_LIMIT:=30} | ||||
|     : ${DB_POOL_TIMEOUT:=20} | ||||
|     : ${DB_CONNECT_TIMEOUT:=10} | ||||
|     : ${DB_IDLE_TIMEOUT:=300} | ||||
|     : ${DB_MAX_LIFETIME:=1800} | ||||
|      | ||||
|     # Track which variables were added | ||||
|     local added_vars=() | ||||
|      | ||||
| @@ -2764,6 +2776,21 @@ update_env_file() { | ||||
|     if ! grep -q "^TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=" "$env_file"; then | ||||
|         added_vars+=("TFA_SUSPICIOUS_ACTIVITY_THRESHOLD") | ||||
|     fi | ||||
|     if ! grep -q "^DB_CONNECTION_LIMIT=" "$env_file"; then | ||||
|         added_vars+=("DB_CONNECTION_LIMIT") | ||||
|     fi | ||||
|     if ! grep -q "^DB_POOL_TIMEOUT=" "$env_file"; then | ||||
|         added_vars+=("DB_POOL_TIMEOUT") | ||||
|     fi | ||||
|     if ! grep -q "^DB_CONNECT_TIMEOUT=" "$env_file"; then | ||||
|         added_vars+=("DB_CONNECT_TIMEOUT") | ||||
|     fi | ||||
|     if ! grep -q "^DB_IDLE_TIMEOUT=" "$env_file"; then | ||||
|         added_vars+=("DB_IDLE_TIMEOUT") | ||||
|     fi | ||||
|     if ! grep -q "^DB_MAX_LIFETIME=" "$env_file"; then | ||||
|         added_vars+=("DB_MAX_LIFETIME") | ||||
|     fi | ||||
|      | ||||
|     # If there are missing variables, add them | ||||
|     if [ ${#added_vars[@]} -gt 0 ]; then | ||||
| @@ -2849,6 +2876,25 @@ EOF | ||||
|             echo "TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=$TFA_SUSPICIOUS_ACTIVITY_THRESHOLD" >> "$env_file" | ||||
|         fi | ||||
|          | ||||
|         # Add Prisma connection pool config if missing | ||||
|         if printf '%s\n' "${added_vars[@]}" | grep -q "DB_CONNECTION_LIMIT"; then | ||||
|             echo "" >> "$env_file" | ||||
|             echo "# Database Connection Pool Configuration (Prisma)" >> "$env_file" | ||||
|             echo "DB_CONNECTION_LIMIT=$DB_CONNECTION_LIMIT" >> "$env_file" | ||||
|         fi | ||||
|         if printf '%s\n' "${added_vars[@]}" | grep -q "DB_POOL_TIMEOUT"; then | ||||
|             echo "DB_POOL_TIMEOUT=$DB_POOL_TIMEOUT" >> "$env_file" | ||||
|         fi | ||||
|         if printf '%s\n' "${added_vars[@]}" | grep -q "DB_CONNECT_TIMEOUT"; then | ||||
|             echo "DB_CONNECT_TIMEOUT=$DB_CONNECT_TIMEOUT" >> "$env_file" | ||||
|         fi | ||||
|         if printf '%s\n' "${added_vars[@]}" | grep -q "DB_IDLE_TIMEOUT"; then | ||||
|             echo "DB_IDLE_TIMEOUT=$DB_IDLE_TIMEOUT" >> "$env_file" | ||||
|         fi | ||||
|         if printf '%s\n' "${added_vars[@]}" | grep -q "DB_MAX_LIFETIME"; then | ||||
|             echo "DB_MAX_LIFETIME=$DB_MAX_LIFETIME" >> "$env_file" | ||||
|         fi | ||||
|          | ||||
|         print_status ".env file updated with ${#added_vars[@]} new variable(s)" | ||||
|         print_info "Added variables: ${added_vars[*]}" | ||||
|     else | ||||
| @@ -3054,6 +3100,12 @@ update_installation() { | ||||
|     print_info "Building frontend..." | ||||
|     npm run build | ||||
|      | ||||
|     # Make agent binaries executable | ||||
|     if [ -d "$instance_dir/agents" ]; then | ||||
|         chmod +x "$instance_dir/agents/patchmon-agent-linux-"* 2>/dev/null || true | ||||
|         print_status "Agent binaries made executable" | ||||
|     fi | ||||
|      | ||||
|     # Run database migrations with self-healing | ||||
|     print_info "Running database migrations..." | ||||
|     cd "$instance_dir/backend" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user