194 lines
11 KiB
Bash
Executable File
194 lines
11 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
set -eu
|
|
|
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
PLATFORM_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)
|
|
OPS_GATEWAY_REPO="${OPS_GATEWAY_REPO:-$PLATFORM_ROOT/../../data/NODEDC_TASKMANAGER_CODEXAPI}"
|
|
ENGINE_REPO="${ENGINE_REPO:-$PLATFORM_ROOT/../NODEDC_ENGINE_INFRA}"
|
|
PLATFORM_ENV="${PLATFORM_ENV:-$PLATFORM_ROOT/infra/.env}"
|
|
|
|
passed=0
|
|
failed=0
|
|
warnings=0
|
|
|
|
section() {
|
|
printf '\n== %s ==\n' "$1"
|
|
}
|
|
|
|
pass() {
|
|
passed=$((passed + 1))
|
|
printf 'PASS %s\n' "$1"
|
|
}
|
|
|
|
fail() {
|
|
failed=$((failed + 1))
|
|
printf 'FAIL %s\n' "$1" >&2
|
|
}
|
|
|
|
warn() {
|
|
warnings=$((warnings + 1))
|
|
printf 'WARN %s\n' "$1" >&2
|
|
}
|
|
|
|
require_file() {
|
|
file="$1"
|
|
label="$2"
|
|
if [ -f "$file" ]; then
|
|
pass "$label"
|
|
else
|
|
fail "$label (missing file: $file)"
|
|
fi
|
|
}
|
|
|
|
require_contains() {
|
|
file="$1"
|
|
needle="$2"
|
|
label="$3"
|
|
if [ ! -f "$file" ]; then
|
|
fail "$label (missing file: $file)"
|
|
return 0
|
|
fi
|
|
if grep -Fq "$needle" "$file"; then
|
|
pass "$label"
|
|
else
|
|
fail "$label (missing: $needle)"
|
|
fi
|
|
}
|
|
|
|
require_not_contains() {
|
|
file="$1"
|
|
needle="$2"
|
|
label="$3"
|
|
if [ ! -f "$file" ]; then
|
|
fail "$label (missing file: $file)"
|
|
return 0
|
|
fi
|
|
if grep -Fq "$needle" "$file"; then
|
|
fail "$label (forbidden: $needle)"
|
|
else
|
|
pass "$label"
|
|
fi
|
|
}
|
|
|
|
read_env() {
|
|
file="$1"
|
|
key="$2"
|
|
if [ ! -f "$file" ]; then
|
|
return 0
|
|
fi
|
|
awk -F= -v key="$key" '
|
|
$0 ~ "^[[:space:]]*#" { next }
|
|
$1 == key {
|
|
value = substr($0, index($0, "=") + 1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
|
gsub(/^"|"$/, "", value)
|
|
print value
|
|
exit
|
|
}
|
|
' "$file"
|
|
}
|
|
|
|
require_env_value() {
|
|
file="$1"
|
|
key="$2"
|
|
expected="$3"
|
|
label="$4"
|
|
actual=$(read_env "$file" "$key" || true)
|
|
if [ "$actual" = "$expected" ]; then
|
|
pass "$label"
|
|
else
|
|
fail "$label (expected $key=$expected, got ${actual:-<missing>})"
|
|
fi
|
|
}
|
|
|
|
section "Required files"
|
|
require_file "$PLATFORM_ROOT/infra/.env.example" "Platform local env example exists"
|
|
require_file "$PLATFORM_ROOT/infra/scripts/check-local-environment-safety.mjs" "Local environment safety checker exists"
|
|
require_file "$PLATFORM_ROOT/infra/synology/.env.synology.example" "Platform Synology env example exists"
|
|
require_file "$PLATFORM_ROOT/infra/docker-compose.dev.yml" "Platform local compose exists"
|
|
require_file "$PLATFORM_ROOT/infra/synology/docker-compose.platform-http.yml" "Platform Synology compose exists"
|
|
require_file "$PLATFORM_ROOT/infra/synology/sync-ai-hub-relay.sh" "Synology AI Hub relay sync script exists"
|
|
require_file "$PLATFORM_ROOT/infra/synology/apply-ai-hub-relay.sh" "Synology AI Hub relay apply script exists"
|
|
require_file "$OPS_GATEWAY_REPO/.env.example" "Ops Gateway local env example exists"
|
|
require_file "$OPS_GATEWAY_REPO/.env.synology.example" "Ops Gateway Synology env example exists"
|
|
require_file "$OPS_GATEWAY_REPO/docker-compose.local.yml" "Ops Gateway local compose exists"
|
|
require_file "$OPS_GATEWAY_REPO/docker-compose.synology.yml" "Ops Gateway Synology compose exists"
|
|
require_file "$ENGINE_REPO/docker-compose.yml" "Engine compose exists"
|
|
|
|
section "Platform local contract"
|
|
require_env_value "$PLATFORM_ROOT/infra/.env.example" "NODEDC_ENV" "local" "Local Platform example declares NODEDC_ENV"
|
|
require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_OPS_ENTITLEMENT_URL" "http://host.docker.internal:4100/api/internal/v1/ai-workspace/entitlements" "Local Platform example points Ops entitlement to local Gateway"
|
|
require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ENABLED" "true" "Local Platform example enables Assistant action relay"
|
|
require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ID" "local-dev" "Local Platform example declares stable local Assistant action relay id"
|
|
require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_HUB_PUBLIC_URL" "wss://ai-hub.nodedc.ru/api/ai-workspace/hub" "Local Platform example keeps deployed AI Hub only as worker relay"
|
|
require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_HUB_INTERNAL_URL" "https://ai-hub.nodedc.ru" "Local Platform example has explicit relay control URL"
|
|
require_contains "$PLATFORM_ROOT/infra/scripts/init-dev-env.sh" "NODEDC_ENV=local" "Local env generator emits NODEDC_ENV"
|
|
require_contains "$PLATFORM_ROOT/infra/scripts/init-dev-env.sh" "AI_WORKSPACE_OPS_ENTITLEMENT_URL=http://host.docker.internal:4100/api/internal/v1/ai-workspace/entitlements" "Local env generator emits local Ops entitlement URL"
|
|
require_contains "$PLATFORM_ROOT/infra/scripts/init-dev-env.sh" "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ENABLED=true" "Local env generator enables Assistant action relay"
|
|
require_contains "$PLATFORM_ROOT/infra/scripts/init-dev-env.sh" "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ID=local-dev" "Local env generator emits stable Assistant action relay id"
|
|
require_contains "$PLATFORM_ROOT/infra/scripts/init-dev-env.sh" "AI_WORKSPACE_HUB_INTERNAL_URL=https://ai-hub.nodedc.ru" "Local env generator emits explicit relay control URL"
|
|
require_not_contains "$PLATFORM_ROOT/infra/docker-compose.dev.yml" "env_file:" "Local compose does not inject full .env into containers"
|
|
require_contains "$PLATFORM_ROOT/infra/docker-compose.dev.yml" 'NODEDC_INTERNAL_ACCESS_TOKEN: ${NODEDC_INTERNAL_ACCESS_TOKEN:-}' "Local compose explicitly passes internal token only to AI services"
|
|
require_contains "$PLATFORM_ROOT/infra/docker-compose.dev.yml" 'AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ENABLED: ${AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ENABLED:-true}' "Local compose explicitly passes Assistant action relay enabled flag"
|
|
require_contains "$PLATFORM_ROOT/infra/docker-compose.dev.yml" 'AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ID: ${AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ID:-local-dev}' "Local compose explicitly passes Assistant action relay id"
|
|
|
|
section "Local safety guardrail"
|
|
if node "$PLATFORM_ROOT/infra/scripts/check-local-environment-safety.mjs"; then
|
|
pass "Local environment safety checker passes"
|
|
else
|
|
fail "Local environment safety checker failed"
|
|
fi
|
|
|
|
section "Platform Synology contract"
|
|
require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "NODEDC_AI_WORKSPACE_ASSISTANT_URL" "http://ai-workspace-assistant:18082" "Synology Platform exposes Assistant to app services on compose network"
|
|
require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "AI_WORKSPACE_OPS_ENTITLEMENT_URL" "http://172.22.0.222:18190/api/internal/v1/ai-workspace/entitlements" "Synology Platform points Ops entitlement to Synology Gateway"
|
|
require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "AI_WORKSPACE_HUB_PUBLIC_URL" "wss://ai-hub.nodedc.ru/api/ai-workspace/hub" "Synology Platform worker-facing Hub URL is public relay"
|
|
require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "AI_WORKSPACE_HUB_INTERNAL_URL" "https://ai-hub.nodedc.ru" "Synology Platform backend Hub URL is explicit"
|
|
require_contains "$PLATFORM_ROOT/infra/synology/verify-current-runtime.sh" 'AI_WORKSPACE_OPS_ENTITLEMENT_URL" = "http://172.22.0.222:18190/api/internal/v1/ai-workspace/entitlements"' "Synology verify checks Ops entitlement URL"
|
|
require_contains "$PLATFORM_ROOT/infra/synology/sync-ai-hub-relay.sh" 'services/ai-workspace-hub/' "Hub-only sync copies AI Workspace Hub source"
|
|
require_contains "$PLATFORM_ROOT/infra/synology/apply-ai-hub-relay.sh" 'up -d --no-deps --force-recreate ai-workspace-hub' "Hub-only apply recreates only AI Workspace Hub without dependencies"
|
|
require_contains "$PLATFORM_ROOT/infra/synology/apply-ai-hub-relay.sh" '/api/ai-workspace/hub/v1/assistant-relays/' "Hub-only apply verifies Assistant relay endpoint"
|
|
require_not_contains "$PLATFORM_ROOT/infra/synology/apply-ai-hub-relay.sh" ' launcher' "Hub-only apply does not target Launcher"
|
|
require_not_contains "$PLATFORM_ROOT/infra/synology/apply-ai-hub-relay.sh" 'authentik' "Hub-only apply does not target Authentik"
|
|
|
|
section "Ops Gateway contract"
|
|
require_env_value "$OPS_GATEWAY_REPO/.env.example" "NODEDC_AGENT_GATEWAY_PUBLIC_URL" "https://ops-agents.nodedc.ru" "Ops Gateway local example keeps worker-facing MCP URL explicit"
|
|
require_env_value "$OPS_GATEWAY_REPO/.env.example" "NODEDC_TASKER_INTERNAL_URL" "http://task.local.nodedc" "Ops Gateway local example points downstream to local Tasker"
|
|
require_env_value "$OPS_GATEWAY_REPO/.env.synology.example" "NODEDC_AGENT_GATEWAY_PUBLIC_URL" "https://ops-agents.nodedc.ru" "Ops Gateway Synology public MCP URL is public relay"
|
|
require_env_value "$OPS_GATEWAY_REPO/.env.synology.example" "NODEDC_TASKER_INTERNAL_URL" "http://172.22.0.222:18090" "Ops Gateway Synology downstream points to Synology Tasker"
|
|
require_contains "$OPS_GATEWAY_REPO/docker-compose.synology.yml" '${HOST_BIND:-172.22.0.222}:${HOST_PORT:-18190}:${PORT:-4100}' "Ops Gateway Synology compose binds expected internal port"
|
|
require_contains "$OPS_GATEWAY_REPO/README.md" "The response uses the AI Workspace adapter contract" "Ops Gateway README documents AI Workspace adapter contract"
|
|
|
|
section "Engine contract"
|
|
require_contains "$ENGINE_REPO/docker-compose.yml" 'NODEDC_AI_WORKSPACE_ASSISTANT_URL: ${NODEDC_AI_WORKSPACE_ASSISTANT_URL:-http://ai-workspace-assistant:18082}' "Engine compose accepts configurable Assistant URL"
|
|
require_contains "$ENGINE_REPO/docker-compose.yml" 'NDC_AI_WORKSPACE_HUB_URL: ${NDC_AI_WORKSPACE_HUB_URL:-wss://ai-hub.nodedc.ru/api/ai-workspace/hub}' "Engine compose accepts configurable worker-facing Hub URL"
|
|
require_contains "$ENGINE_REPO/docker-compose.yml" 'NDC_AI_WORKSPACE_HUB_HTTP_URL: ${NDC_AI_WORKSPACE_HUB_HTTP_URL:-https://ai-hub.nodedc.ru}' "Engine compose accepts configurable Hub HTTP URL"
|
|
require_contains "$ENGINE_REPO/nodedc-source/server/aiWorkspace/assistantRegistryClient.js" "'X-NODEDC-User-Role': role" "Engine forwards user role to Assistant"
|
|
require_contains "$ENGINE_REPO/nodedc-source/server/aiWorkspace/assistantRegistryClient.js" "'X-NODEDC-User-Groups': groups.join(',')" "Engine forwards user groups to Assistant"
|
|
|
|
section "Current local env warnings"
|
|
if [ -f "$PLATFORM_ENV" ]; then
|
|
local_ops_entitlement=$(read_env "$PLATFORM_ENV" AI_WORKSPACE_OPS_ENTITLEMENT_URL || true)
|
|
if [ -z "$local_ops_entitlement" ]; then
|
|
warn "Current Platform env has no AI_WORKSPACE_OPS_ENTITLEMENT_URL; current Mac remains hybrid/contract-only until env is deliberately updated."
|
|
else
|
|
pass "Current Platform env has AI_WORKSPACE_OPS_ENTITLEMENT_URL"
|
|
fi
|
|
local_action_relay_enabled=$(read_env "$PLATFORM_ENV" AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ENABLED || true)
|
|
local_action_relay_id=$(read_env "$PLATFORM_ENV" AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ID || true)
|
|
if [ "$local_action_relay_enabled" = "true" ] && [ -n "$local_action_relay_id" ]; then
|
|
pass "Current Platform env has Assistant action relay enabled"
|
|
else
|
|
warn "Current Platform env has no enabled Assistant action relay; remote worker action calls may route incorrectly."
|
|
fi
|
|
else
|
|
warn "Current Platform env not found: $PLATFORM_ENV"
|
|
fi
|
|
|
|
section "Summary"
|
|
printf 'passed=%s failed=%s warnings=%s\n' "$passed" "$failed" "$warnings"
|
|
|
|
if [ "$failed" -ne 0 ]; then
|
|
exit 1
|
|
fi
|