Add /health endpoint to Java plugin and update health checks
Some checks are pending
Build Ghidra Plugin / build (push) Waiting to run
Some checks are pending
Build Ghidra Plugin / build (push) Waiting to run
New GET /health endpoint returns status, uptime, api_version, and loaded program without depending on program state. Lightweight enough for Docker HEALTHCHECK and monitoring probes. Python docker_health tool tries /health first, falls back to root endpoint for older plugin versions. Docker HEALTHCHECK updated to use /health instead of /.
This commit is contained in:
parent
14b2b575c8
commit
83949683ae
@ -147,6 +147,6 @@ ENV MCGHIDRA_MAXMEM=2G
|
|||||||
|
|
||||||
# Healthcheck
|
# Healthcheck
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
CMD curl -f http://localhost:${MCGHIDRA_PORT}/ || exit 1
|
CMD curl -f http://localhost:${MCGHIDRA_PORT}/health || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcghidra"
|
name = "mcghidra"
|
||||||
version = "2026.3.2.1"
|
version = "2026.3.6"
|
||||||
description = "Reverse engineering bridge: multi-instance Ghidra plugin with HATEOAS REST API and MCP server for decompilation, analysis & binary manipulation"
|
description = "Reverse engineering bridge: multi-instance Ghidra plugin with HATEOAS REST API and MCP server for decompilation, analysis & binary manipulation"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@ -52,6 +52,7 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
private HttpServer server;
|
private HttpServer server;
|
||||||
private int port;
|
private int port;
|
||||||
private boolean isBaseInstance = false;
|
private boolean isBaseInstance = false;
|
||||||
|
private long serverStartTimeMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for MCGhidra Plugin.
|
* Constructor for MCGhidra Plugin.
|
||||||
@ -109,6 +110,8 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
// Register Root Endpoint (should be last to include links to all other endpoints)
|
// Register Root Endpoint (should be last to include links to all other endpoints)
|
||||||
registerRootEndpoint(server);
|
registerRootEndpoint(server);
|
||||||
|
|
||||||
|
serverStartTimeMs = System.currentTimeMillis();
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
server.start();
|
server.start();
|
||||||
Msg.info(this, "MCGhidra HTTP server started on port " + port);
|
Msg.info(this, "MCGhidra HTTP server started on port " + port);
|
||||||
@ -184,6 +187,35 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Health endpoint — lightweight, no program dependency
|
||||||
|
server.createContext("/health", exchange -> {
|
||||||
|
try {
|
||||||
|
if ("GET".equals(exchange.getRequestMethod())) {
|
||||||
|
long uptimeMs = System.currentTimeMillis() - serverStartTimeMs;
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
|
||||||
|
Map<String, Object> healthData = new HashMap<>();
|
||||||
|
healthData.put("status", "up");
|
||||||
|
healthData.put("port", port);
|
||||||
|
healthData.put("api_version", ApiConstants.API_VERSION);
|
||||||
|
healthData.put("uptime_ms", uptimeMs);
|
||||||
|
healthData.put("program", program != null ? program.getName() : null);
|
||||||
|
|
||||||
|
ResponseBuilder builder = new ResponseBuilder(exchange, port)
|
||||||
|
.success(true)
|
||||||
|
.result(healthData)
|
||||||
|
.addLink("self", "/health")
|
||||||
|
.addLink("root", "/");
|
||||||
|
|
||||||
|
HttpUtil.sendJsonResponse(exchange, builder.build(), 200, port);
|
||||||
|
} else {
|
||||||
|
HttpUtil.sendErrorResponse(exchange, 405, "Method Not Allowed", "METHOD_NOT_ALLOWED", port);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Msg.error(this, "Error handling /health", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Info endpoint
|
// Info endpoint
|
||||||
server.createContext("/info", exchange -> {
|
server.createContext("/info", exchange -> {
|
||||||
try {
|
try {
|
||||||
@ -359,6 +391,7 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
.success(true)
|
.success(true)
|
||||||
.result(rootData)
|
.result(rootData)
|
||||||
.addLink("self", "/")
|
.addLink("self", "/")
|
||||||
|
.addLink("health", "/health")
|
||||||
.addLink("info", "/info")
|
.addLink("info", "/info")
|
||||||
.addLink("plugin-version", "/plugin-version")
|
.addLink("plugin-version", "/plugin-version")
|
||||||
.addLink("projects", "/projects")
|
.addLink("projects", "/projects")
|
||||||
|
|||||||
@ -889,19 +889,25 @@ class DockerMixin(MCGhidraMixinBase):
|
|||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
url = f"http://localhost:{port}/"
|
health_url = f"http://localhost:{port}/health"
|
||||||
|
root_url = f"http://localhost:{port}/"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(url)
|
# Try /health first (available in plugin v2.2+)
|
||||||
|
req = urllib.request.Request(health_url)
|
||||||
with urllib.request.urlopen(req, timeout=timeout) as response:
|
with urllib.request.urlopen(req, timeout=timeout) as response:
|
||||||
data = json_module.loads(response.read().decode())
|
data = json_module.loads(response.read().decode())
|
||||||
|
result = data.get("result", data)
|
||||||
return {
|
return {
|
||||||
"healthy": True,
|
"healthy": True,
|
||||||
"port": port,
|
"port": port,
|
||||||
"api_version": data.get("api_version"),
|
"api_version": result.get("api_version"),
|
||||||
"program": data.get("program"),
|
"program": result.get("program"),
|
||||||
"file": data.get("file"),
|
"uptime_ms": result.get("uptime_ms"),
|
||||||
}
|
}
|
||||||
|
except urllib.error.HTTPError:
|
||||||
|
# /health not available — fall back to root endpoint (older plugin)
|
||||||
|
pass
|
||||||
except urllib.error.URLError as e:
|
except urllib.error.URLError as e:
|
||||||
return {
|
return {
|
||||||
"healthy": False,
|
"healthy": False,
|
||||||
@ -916,6 +922,24 @@ class DockerMixin(MCGhidraMixinBase):
|
|||||||
"error": str(e),
|
"error": str(e),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Fallback: try root endpoint for older plugin versions
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(root_url)
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout) as response:
|
||||||
|
data = json_module.loads(response.read().decode())
|
||||||
|
return {
|
||||||
|
"healthy": True,
|
||||||
|
"port": port,
|
||||||
|
"api_version": data.get("api_version"),
|
||||||
|
"program": data.get("program"),
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"healthy": False,
|
||||||
|
"port": port,
|
||||||
|
"error": str(e),
|
||||||
|
}
|
||||||
|
|
||||||
@mcp_tool(
|
@mcp_tool(
|
||||||
name="docker_health",
|
name="docker_health",
|
||||||
description="Check if a MCGhidra container's API is responding",
|
description="Check if a MCGhidra container's API is responding",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user