a55951f315
- Replace Python-based sanitizer in deploy script with lightweight jq/alpine - Add sync-agents-sanitized.mjs for on-demand and watch-mode sync - Add AgentConfigPath to appsettings.json (explicit default) - Extend /health/live endpoint to report agent count from sanitized config - Document architecture in docs/agent-identity-architecture.md - No openclaw.json secrets ever reach Nexus API containers Verification: - curl /api/v1/agents → 9 agents, zero secrets in response - agents-sanitized.json contains only 'agents' key, no gateway/auth - All C# code paths read from agents-sanitized.json (AgentConfigPath) - Bridge controller resolves agent IDs via AgentService.GetAllowedAgentIdsAsync()
82 lines
2.8 KiB
C#
82 lines
2.8 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
using Nexus.Api.Integrations;
|
|
|
|
namespace Nexus.Api.Controllers;
|
|
|
|
[ApiController]
|
|
public class HealthController(IAgentRuntime runtime, HealthCheckService healthChecks) : ControllerBase
|
|
{
|
|
[AllowAnonymous]
|
|
[HttpGet("/health/live")]
|
|
public IResult Live()
|
|
{
|
|
var agentCount = 0;
|
|
try
|
|
{
|
|
var path = System.IO.Path.Combine(
|
|
System.IO.Path.GetDirectoryName(
|
|
System.Reflection.Assembly.GetExecutingAssembly().Location) ?? "/app",
|
|
"..");
|
|
var configPath = "/home/node/.openclaw/agents-sanitized.json";
|
|
if (System.IO.File.Exists(configPath))
|
|
{
|
|
var json = System.IO.File.ReadAllText(configPath);
|
|
using var doc = System.Text.Json.JsonDocument.Parse(json);
|
|
if (doc.RootElement.TryGetProperty("agents", out var agentsEl)
|
|
&& agentsEl.TryGetProperty("list", out var listEl))
|
|
agentCount = listEl.GetArrayLength();
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
return Results.Ok(new { status = "Healthy", timestamp = DateTimeOffset.UtcNow, agentCount });
|
|
}
|
|
|
|
[HttpGet("/health")]
|
|
public async Task<IResult> Get(CancellationToken ct)
|
|
{
|
|
var report = await healthChecks.CheckHealthAsync(ct);
|
|
|
|
string runtimeStatus;
|
|
string? runtimeDetail;
|
|
try
|
|
{
|
|
var status = await runtime.GetStatusAsync(ct);
|
|
runtimeStatus = status.Status.ToString();
|
|
runtimeDetail = status.Detail;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
runtimeStatus = "Offline";
|
|
runtimeDetail = ex.Message;
|
|
}
|
|
|
|
static IReadOnlyDictionary<string, object?> NormalizeData(IReadOnlyDictionary<string, object> source)
|
|
{
|
|
return source.ToDictionary(kvp => kvp.Key, kvp => (object?)kvp.Value);
|
|
}
|
|
|
|
var entries = report.Entries.ToDictionary(
|
|
e => e.Key,
|
|
e => new
|
|
{
|
|
status = e.Value.Status.ToString(),
|
|
description = e.Value.Description,
|
|
data = NormalizeData(e.Value.Data)
|
|
});
|
|
|
|
entries["runtime"] = new
|
|
{
|
|
status = runtimeStatus,
|
|
description = runtimeDetail,
|
|
data = new Dictionary<string, object?>() as IReadOnlyDictionary<string, object?>
|
|
};
|
|
|
|
var isHealthy = report.Status == HealthStatus.Healthy && runtimeStatus == "Online";
|
|
return isHealthy ? Results.Ok(new { status = "Healthy", checks = entries, timestamp = DateTimeOffset.UtcNow })
|
|
: Results.Ok(new { status = "Degraded", checks = entries, timestamp = DateTimeOffset.UtcNow });
|
|
}
|
|
}
|