diff --git a/.gitea/scripts/deploy-nexus.sh b/.gitea/scripts/deploy-nexus.sh index 721f715..25008bb 100755 --- a/.gitea/scripts/deploy-nexus.sh +++ b/.gitea/scripts/deploy-nexus.sh @@ -61,7 +61,7 @@ JWT_ISSUER=nexus JWT_AUDIENCE=nexus-web BOOTSTRAP_OWNER_EMAIL=vmbao62@hotmail.de BOOTSTRAP_OWNER_PASSWORD=${ENV_BOOTSTRAP_OWNER_PASSWORD:-} -OPENCLAW_BASE_URL=http://host.docker.internal:18789 +OPENCLAW_BASE_URL=http://openclaw-gateway:18789 OPENCLAW_REQUIRED_VERSION=2026.7.1 OPENCLAW_GATEWAY_TOKEN=${ENV_OPENCLAW_TOKEN:-} OPENCLAW_GATEWAY_PASSWORD= @@ -141,11 +141,17 @@ for container in nexus-api-1 nexus-web-1; do done echo "Checking live health" +health_is_healthy() { + health_body="$(curl -fsS --max-time 10 "$BASE_URL/health")" || return 1 + printf '%s' "$health_body" | + grep -Eq '^[[:space:]]*\{[[:space:]]*"status"[[:space:]]*:[[:space:]]*"Healthy"' +} + retry=0 while [ "$retry" -lt 6 ]; do retry=$((retry + 1)) - if curl -fsS --max-time 10 "$BASE_URL/health" >/dev/null; then - echo "Health check passed" + if health_is_healthy; then + echo "Health check passed with Healthy runtime and database state" break fi if [ "$retry" -eq 6 ]; then @@ -170,6 +176,16 @@ check() { fi } +check_health() { + if health_is_healthy; then + printf '%-28s HTTP 200 (Healthy)\n' "Health" + pass=$((pass + 1)) + else + printf '%-28s not healthy\n' "Health" + fail=$((fail + 1)) + fi +} + check_post() { path="$1" expected="$2" @@ -184,7 +200,7 @@ check_post() { } check "/dashboard" "200" "Dashboard" -check "/health" "200" "Health" +check_health check "/api/v1/operations/snapshot" "401" "Operations auth" check_post "/api/v1/chat" "401" "Chat auth" diff --git a/.gitea/workflows/rollback.yaml b/.gitea/workflows/rollback.yaml index 8f82e96..e3ad95d 100644 --- a/.gitea/workflows/rollback.yaml +++ b/.gitea/workflows/rollback.yaml @@ -114,7 +114,7 @@ jobs: JWT_AUDIENCE=nexus-web BOOTSTRAP_OWNER_EMAIL=vmbao62@hotmail.de BOOTSTRAP_OWNER_PASSWORD=${ENV_BOOTSTRAP_OWNER_PASSWORD:-} - OPENCLAW_BASE_URL=http://host.docker.internal:18789 + OPENCLAW_BASE_URL=http://openclaw-gateway:18789 OPENCLAW_GATEWAY_TOKEN=${ENV_OPENCLAW_TOKEN} OPENCLAW_GATEWAY_PASSWORD= EOF @@ -188,9 +188,10 @@ jobs: WAIT=1 while [ $RETRY -lt $MAX ]; do RETRY=$((RETRY + 1)) - if curl -sf --max-time 10 https://nexus.noveria.net/health; then + HEALTH_BODY=$(curl -sf --max-time 10 https://nexus.noveria.net/health || true) + if printf '%s' "$HEALTH_BODY" | grep -Eq '^[[:space:]]*\{[[:space:]]*"status"[[:space:]]*:[[:space:]]*"Healthy"'; then echo "" - echo "✅ Health check passed (attempt $RETRY/$MAX)" + echo "✅ Health check passed with Healthy runtime and database state (attempt $RETRY/$MAX)" exit 0 fi echo "⏳ Attempt $RETRY/$MAX failed, waiting ${WAIT}s..." @@ -225,8 +226,21 @@ jobs: fi } + check_health() { + local body + body=$(curl -sf --max-time 10 "${BASE}/health" || true) + printf " %-25s" "Health API:" + if printf '%s' "$body" | grep -Eq '^[[:space:]]*\{[[:space:]]*"status"[[:space:]]*:[[:space:]]*"Healthy"'; then + echo " HTTP 200 Healthy ✅" + PASS=$((PASS + 1)) + else + echo " not healthy ❌" + FAIL=$((FAIL + 1)) + fi + } + check "/dashboard" "Dashboard" 200 - check "/health" "Health API" 200 + check_health check "/api/v1/operations/snapshot" "Operations API (auth)" 401 echo "" diff --git a/VERSION b/VERSION index 56c76c3..b873c1f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.58 +0.2.59 diff --git a/backend-tests/OpenClawRuntimeTests.cs b/backend-tests/OpenClawRuntimeTests.cs new file mode 100644 index 0000000..2bc0533 --- /dev/null +++ b/backend-tests/OpenClawRuntimeTests.cs @@ -0,0 +1,46 @@ +using System.Net; +using Microsoft.Extensions.Configuration; +using Nexus.Api.Data; +using Nexus.Api.Integrations; +using Xunit; + +namespace Nexus.Api.Tests; + +public sealed class OpenClawRuntimeTests +{ + [Fact] + public async Task GetStatusAsync_BlankPasswordFallsBackToConfiguredToken() + { + string? authorization = null; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Integrations:OpenClaw:Password"] = string.Empty, + ["Integrations:OpenClaw:Token"] = "gateway-token" + }) + .Build(); + var client = new HttpClient(new StubHandler(request => + { + authorization = request.Headers.Authorization?.ToString(); + return new HttpResponseMessage(HttpStatusCode.OK); + })) + { + BaseAddress = new Uri("http://openclaw-gateway:18789") + }; + var runtime = new OpenClawRuntime(client, configuration); + + var status = await runtime.GetStatusAsync(CancellationToken.None); + + Assert.Equal(OperationalStatus.Online, status.Status); + Assert.Equal("Bearer gateway-token", authorization); + } + + private sealed class StubHandler( + Func responder) : HttpMessageHandler + { + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + => Task.FromResult(responder(request)); + } +} diff --git a/backend/Integrations/OpenClawRuntime.cs b/backend/Integrations/OpenClawRuntime.cs index f359cd9..18a5c82 100644 --- a/backend/Integrations/OpenClawRuntime.cs +++ b/backend/Integrations/OpenClawRuntime.cs @@ -32,8 +32,9 @@ public sealed class OpenClawRuntime(HttpClient client, IConfiguration configurat private void ApplyAuthorization(HttpRequestMessage request) { - var credential = configuration["Integrations:OpenClaw:Password"] - ?? configuration["Integrations:OpenClaw:Token"]; + var credential = configuration["Integrations:OpenClaw:Password"]; + if (string.IsNullOrWhiteSpace(credential)) + credential = configuration["Integrations:OpenClaw:Token"]; if (!string.IsNullOrWhiteSpace(credential)) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", credential); } diff --git a/compose.yaml b/compose.yaml index ac342b6..aa963c7 100644 --- a/compose.yaml +++ b/compose.yaml @@ -55,7 +55,7 @@ services: ForwardedHeaders__ForwardLimit: ${FORWARDED_HEADERS_FORWARD_LIMIT:-1} ForwardedHeaders__KnownProxies__0: ${FORWARDED_HEADERS_KNOWN_PROXY:-} ForwardedHeaders__KnownNetworks__0: ${FORWARDED_HEADERS_KNOWN_NETWORK:-} - Integrations__OpenClaw__BaseUrl: ${OPENCLAW_BASE_URL:-http://host.docker.internal:18789} + Integrations__OpenClaw__BaseUrl: ${OPENCLAW_BASE_URL:-http://openclaw-gateway:18789} Integrations__OpenClaw__RequiredVersion: ${OPENCLAW_REQUIRED_VERSION:-2026.7.1} Integrations__OpenClaw__Token: ${OPENCLAW_GATEWAY_TOKEN:-} Integrations__OpenClaw__Password: ${OPENCLAW_GATEWAY_PASSWORD:-}