feat(bridge): implement unified bridge entrypoints and routing
This commit is contained in:
parent
861816738b
commit
40fc458072
24
.github/workflows/pipeline.yml
vendored
24
.github/workflows/pipeline.yml
vendored
@ -21,7 +21,7 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
internal_service_token:
|
ai_workspace_auth_token:
|
||||||
description: "Optional ACP auth token for deploy"
|
description: "Optional ACP auth token for deploy"
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
@ -64,11 +64,11 @@ jobs:
|
|||||||
jwtGithubAudience: vault
|
jwtGithubAudience: vault
|
||||||
ignoreNotFound: true
|
ignoreNotFound: true
|
||||||
secrets: |
|
secrets: |
|
||||||
kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN | INTERNAL_SERVICE_TOKEN
|
kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN
|
||||||
|
|
||||||
- name: Export bridge auth token
|
- name: Export bridge auth token
|
||||||
if: ${{ steps.vault.outcome == 'success' }}
|
if: ${{ steps.vault.outcome == 'success' }}
|
||||||
run: echo "BRIDGE_AUTH_TOKEN=${{ steps.vault.outputs.INTERNAL_SERVICE_TOKEN }}" >> "$GITHUB_ENV"
|
run: echo "AI_WORKSPACE_AUTH_TOKEN=${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Probe current production bridge
|
- name: Probe current production bridge
|
||||||
id: production_state
|
id: production_state
|
||||||
@ -220,7 +220,7 @@ jobs:
|
|||||||
jwtGithubAudience: vault
|
jwtGithubAudience: vault
|
||||||
ignoreNotFound: true
|
ignoreNotFound: true
|
||||||
secrets: |
|
secrets: |
|
||||||
kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN | INTERNAL_SERVICE_TOKEN ;
|
kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN ;
|
||||||
kv/data/github-actions/xworkmate-bridge WORKSPACE_REPO_TOKEN | WORKSPACE_REPO_TOKEN ;
|
kv/data/github-actions/xworkmate-bridge WORKSPACE_REPO_TOKEN | WORKSPACE_REPO_TOKEN ;
|
||||||
kv/data/github-actions/xworkmate-bridge SINGLE_NODE_VPS_SSH_PRIVATE_KEY | SINGLE_NODE_VPS_SSH_PRIVATE_KEY ;
|
kv/data/github-actions/xworkmate-bridge SINGLE_NODE_VPS_SSH_PRIVATE_KEY | SINGLE_NODE_VPS_SSH_PRIVATE_KEY ;
|
||||||
kv/data/github-actions/xworkmate-bridge SINGLE_NODE_VPS_SSH_PRIVATE_KEY_B64 | SINGLE_NODE_VPS_SSH_PRIVATE_KEY_B64 ;
|
kv/data/github-actions/xworkmate-bridge SINGLE_NODE_VPS_SSH_PRIVATE_KEY_B64 | SINGLE_NODE_VPS_SSH_PRIVATE_KEY_B64 ;
|
||||||
@ -229,20 +229,20 @@ jobs:
|
|||||||
- name: Export deploy secrets
|
- name: Export deploy secrets
|
||||||
run: |
|
run: |
|
||||||
{
|
{
|
||||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ inputs.internal_service_token }}" ]]; then
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ inputs.ai_workspace_auth_token }}" ]]; then
|
||||||
echo "BRIDGE_AUTH_TOKEN=${{ inputs.internal_service_token }}"
|
echo "AI_WORKSPACE_AUTH_TOKEN=${{ inputs.ai_workspace_auth_token }}"
|
||||||
else
|
else
|
||||||
echo "BRIDGE_AUTH_TOKEN=${{ steps.vault.outputs.INTERNAL_SERVICE_TOKEN }}"
|
echo "AI_WORKSPACE_AUTH_TOKEN=${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}"
|
||||||
fi
|
fi
|
||||||
} >> "$GITHUB_ENV"
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Validate deploy secrets
|
- name: Validate deploy secrets
|
||||||
run: |
|
run: |
|
||||||
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
if [[ -z "${AI_WORKSPACE_AUTH_TOKEN}" ]]; then
|
||||||
echo "::error::BRIDGE_AUTH_TOKEN is empty. Provide it via the workflow_dispatch input, or ensure kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN is readable from Vault."
|
echo "::error::AI_WORKSPACE_AUTH_TOKEN is empty. Provide it via the workflow_dispatch input, or ensure kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN is readable from Vault."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "BRIDGE_AUTH_TOKEN length=${#BRIDGE_AUTH_TOKEN}"
|
echo "AI_WORKSPACE_AUTH_TOKEN length=${#AI_WORKSPACE_AUTH_TOKEN}"
|
||||||
|
|
||||||
- name: Checkout playbooks repository
|
- name: Checkout playbooks repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@ -384,10 +384,10 @@ jobs:
|
|||||||
jwtGithubAudience: vault
|
jwtGithubAudience: vault
|
||||||
ignoreNotFound: true
|
ignoreNotFound: true
|
||||||
secrets: |
|
secrets: |
|
||||||
kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN | INTERNAL_SERVICE_TOKEN
|
kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN
|
||||||
|
|
||||||
- name: Export bridge auth token
|
- name: Export bridge auth token
|
||||||
run: echo "BRIDGE_AUTH_TOKEN=${{ steps.vault.outputs.INTERNAL_SERVICE_TOKEN }}" >> "$GITHUB_ENV"
|
run: echo "AI_WORKSPACE_AUTH_TOKEN=${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Validate deployed endpoints
|
- name: Validate deployed endpoints
|
||||||
run: bash ./scripts/github-actions/validate-deploy.sh "$(git rev-parse --short HEAD)" "${BRIDGE_SERVER_URL}"
|
run: bash ./scripts/github-actions/validate-deploy.sh "$(git rev-parse --short HEAD)" "${BRIDGE_SERVER_URL}"
|
||||||
|
|||||||
@ -94,7 +94,7 @@ Optional GitHub secrets:
|
|||||||
|
|
||||||
Optional workflow input:
|
Optional workflow input:
|
||||||
|
|
||||||
- `internal_service_token`: manual dispatch input that is forwarded to Ansible as `INTERNAL_SERVICE_TOKEN`
|
- `ai_workspace_auth_token`: manual dispatch input that is forwarded as `AI_WORKSPACE_AUTH_TOKEN`
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,8 @@
|
|||||||
|
|
||||||
环境变量:
|
环境变量:
|
||||||
|
|
||||||
- `BRIDGE_AUTH_TOKEN`
|
- `AI_WORKSPACE_AUTH_TOKEN`:主共享 token,用于 bridge 入站鉴权、上游 provider 转发、OpenClaw Gateway 重签发与任务转发 fallback。
|
||||||
|
- `BRIDGE_AUTH_TOKEN`:旧主 token。没有 `AI_WORKSPACE_AUTH_TOKEN` 时继续生效并参与上游转发,用于存量租户兼容,直到 `AI_WORKSPACE_AUTH_TOKEN` 完成彻底替代后下线。
|
||||||
- `BRIDGE_REVIEW_AUTH_TOKEN`(可选):Apple review / beta 工测专用临时 token。清空该环境变量并重启/reload bridge 即可单独关停,不影响主 token。
|
- `BRIDGE_REVIEW_AUTH_TOKEN`(可选):Apple review / beta 工测专用临时 token。清空该环境变量并重启/reload bridge 即可单独关停,不影响主 token。
|
||||||
- `ACP_ALLOWED_ORIGINS`
|
- `ACP_ALLOWED_ORIGINS`
|
||||||
|
|
||||||
@ -48,9 +49,9 @@
|
|||||||
- `/acp` 与 `/acp/rpc` 都做 origin allowlist 校验
|
- `/acp` 与 `/acp/rpc` 都做 origin allowlist 校验
|
||||||
- 空 `Origin` 默认允许
|
- 空 `Origin` 默认允许
|
||||||
- `/api/ping`、`/acp`、`/acp/rpc` 在任一 bridge token 非空时都要求 bearer header
|
- `/api/ping`、`/acp`、`/acp/rpc` 在任一 bridge token 非空时都要求 bearer header
|
||||||
- `BRIDGE_AUTH_TOKEN` 与 `BRIDGE_REVIEW_AUTH_TOKEN` 都为空时默认放行
|
- `AI_WORKSPACE_AUTH_TOKEN`、`BRIDGE_AUTH_TOKEN` 与 `BRIDGE_REVIEW_AUTH_TOKEN` 都为空时默认放行
|
||||||
- token 非空时,接受裸 token 或 `Bearer <token>`
|
- token 非空时,接受裸 token 或 `Bearer <token>`
|
||||||
- 线上 Caddy 入口必须与 bridge origin 保持同一 token set:主 `BRIDGE_AUTH_TOKEN` 与可选 `BRIDGE_REVIEW_AUTH_TOKEN` 都应放行;无 token 仍返回 `401`
|
- 线上 Caddy 入口必须与 bridge origin 保持同一 token set:主 `AI_WORKSPACE_AUTH_TOKEN`、兼容 `BRIDGE_AUTH_TOKEN` 与可选 `BRIDGE_REVIEW_AUTH_TOKEN` 都应放行;无 token 仍返回 `401`
|
||||||
- `xworkmate-app` 生产 Origin 固定为 `https://xworkmate.svc.plus`
|
- `xworkmate-app` 生产 Origin 固定为 `https://xworkmate.svc.plus`
|
||||||
|
|
||||||
## 3.1 Lightweight Distributed Task Forwarding
|
## 3.1 Lightweight Distributed Task Forwarding
|
||||||
@ -139,7 +140,7 @@ distributed:
|
|||||||
- `bridge_endpoint` 是 peer bridge base URL,bridge 会按当前请求路径拼接 `/acp/rpc` 或 `/gateway/openclaw`
|
- `bridge_endpoint` 是 peer bridge base URL,bridge 会按当前请求路径拼接 `/acp/rpc` 或 `/gateway/openclaw`
|
||||||
- 同步消息不能走公网;`bridge_endpoint` 必须是 loopback、private、link-local 这类本机或 VPN 内网地址,用于 WireGuard over VLESS 等隧道已经提供加密的场景
|
- 同步消息不能走公网;`bridge_endpoint` 必须是 loopback、private、link-local 这类本机或 VPN 内网地址,用于 WireGuard over VLESS 等隧道已经提供加密的场景
|
||||||
- 只要求本机网络能路由到 endpoint;bridge 不依赖 config center 或额外注册中心
|
- 只要求本机网络能路由到 endpoint;bridge 不依赖 config center 或额外注册中心
|
||||||
- `task_forward_token` 为空时复用本机 `BRIDGE_AUTH_TOKEN`
|
- `task_forward_token` 为空时复用本机 `AI_WORKSPACE_AUTH_TOKEN`;未配置时兼容复用 `BRIDGE_AUTH_TOKEN`
|
||||||
- 转发请求会带 `X-XWorkmate-Bridge-Forwarded: 1`
|
- 转发请求会带 `X-XWorkmate-Bridge-Forwarded: 1`
|
||||||
- `X-XWorkmate-Forward-Source` 是源节点,`X-XWorkmate-Forward-Target` 是最终目标节点
|
- `X-XWorkmate-Forward-Source` 是源节点,`X-XWorkmate-Forward-Target` 是最终目标节点
|
||||||
- `X-XWorkmate-Forward-Hop` 逐跳递增,超过 `forwarding.hop_limit` 时拒绝转发,避免循环
|
- `X-XWorkmate-Forward-Hop` 逐跳递增,超过 `forwarding.hop_limit` 时拒绝转发,避免循环
|
||||||
@ -157,7 +158,7 @@ distributed:
|
|||||||
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
|
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
|
||||||
BRIDGE_WS_URL=wss://xworkmate-bridge.svc.plus/acp
|
BRIDGE_WS_URL=wss://xworkmate-bridge.svc.plus/acp
|
||||||
BRIDGE_HTTP_RPC_URL=https://xworkmate-bridge.svc.plus/acp/rpc
|
BRIDGE_HTTP_RPC_URL=https://xworkmate-bridge.svc.plus/acp/rpc
|
||||||
Authorization: Bearer $BRIDGE_AUTH_TOKEN
|
Authorization: Bearer $AI_WORKSPACE_AUTH_TOKEN
|
||||||
Origin: https://xworkmate.svc.plus
|
Origin: https://xworkmate.svc.plus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,7 @@ Gateway access remains bridge-owned via JSON-RPC methods:
|
|||||||
|
|
||||||
Upstream authentication is unified for both ACP and gateway routes:
|
Upstream authentication is unified for both ACP and gateway routes:
|
||||||
|
|
||||||
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
|
- `Authorization: Bearer $AI_WORKSPACE_AUTH_TOKEN`
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
|
|||||||
@ -109,17 +109,45 @@ func resolveURL(yamlVal string, envKeys ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bridgeUpstreamAuthorizationHeader() string {
|
func bridgeUpstreamAuthorizationHeader() string {
|
||||||
token := bridgeSharedAuthToken()
|
token := bridgePublicAuthToken()
|
||||||
if token != "" && !strings.HasPrefix(strings.ToLower(token), "bearer ") {
|
if token != "" && !strings.HasPrefix(strings.ToLower(token), "bearer ") {
|
||||||
return "Bearer " + token
|
return "Bearer " + token
|
||||||
}
|
}
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
func bridgeSharedAuthToken() string {
|
func bridgePublicAuthToken() string {
|
||||||
|
if token := strings.TrimSpace(os.Getenv("AI_WORKSPACE_AUTH_TOKEN")); token != "" {
|
||||||
|
return token
|
||||||
|
}
|
||||||
return strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""))
|
return strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bridgeSharedAuthToken() string {
|
||||||
|
return bridgePublicAuthToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func bridgeInboundAuthTokens() []string {
|
||||||
|
var tokens []string
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
for _, token := range []string{
|
||||||
|
os.Getenv("AI_WORKSPACE_AUTH_TOKEN"),
|
||||||
|
os.Getenv("BRIDGE_AUTH_TOKEN"),
|
||||||
|
os.Getenv("BRIDGE_REVIEW_AUTH_TOKEN"),
|
||||||
|
} {
|
||||||
|
trimmed := strings.TrimSpace(token)
|
||||||
|
if trimmed == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[trimmed]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[trimmed] = struct{}{}
|
||||||
|
tokens = append(tokens, trimmed)
|
||||||
|
}
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
func resolveDistributedTaskForwardToken(config *BridgeConfig) string {
|
func resolveDistributedTaskForwardToken(config *BridgeConfig) string {
|
||||||
if token := strings.TrimSpace(os.Getenv("XWORKMATE_BRIDGE_TASK_FORWARD_TOKEN")); token != "" {
|
if token := strings.TrimSpace(os.Getenv("XWORKMATE_BRIDGE_TASK_FORWARD_TOKEN")); token != "" {
|
||||||
return token
|
return token
|
||||||
|
|||||||
@ -93,6 +93,19 @@ func handleGatewayConnect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := server.gateway.Connect(request, notify)
|
result := server.gateway.Connect(request, notify)
|
||||||
|
if usesBridgeIdentity && shouldRetryOpenClawGatewayWithSharedToken(result) {
|
||||||
|
clearBridgeGatewayDeviceToken()
|
||||||
|
request.Auth.DeviceToken = ""
|
||||||
|
request.HasDeviceToken = false
|
||||||
|
request.Auth.Token = bridgeSharedAuthToken()
|
||||||
|
request.HasSharedAuth = strings.TrimSpace(request.Auth.Token) != ""
|
||||||
|
if request.HasSharedAuth {
|
||||||
|
request.ConnectAuthMode = "shared-token"
|
||||||
|
request.ConnectAuthFields = []string{"token"}
|
||||||
|
request.ConnectAuthSources = []string{"bridge:repair"}
|
||||||
|
result = server.gateway.Connect(request, notify)
|
||||||
|
}
|
||||||
|
}
|
||||||
if result.OK && usesBridgeIdentity {
|
if result.OK && usesBridgeIdentity {
|
||||||
saveBridgeGatewayDeviceToken(result.ReturnedDeviceToken)
|
saveBridgeGatewayDeviceToken(result.ReturnedDeviceToken)
|
||||||
}
|
}
|
||||||
@ -285,6 +298,19 @@ func ensureProductionGatewayConnected(
|
|||||||
request.HasDeviceToken = deviceToken != ""
|
request.HasDeviceToken = deviceToken != ""
|
||||||
request.ReportedRemoteAddress = resolveGatewayReportedRemoteAddress(server, request)
|
request.ReportedRemoteAddress = resolveGatewayReportedRemoteAddress(server, request)
|
||||||
result := server.gateway.Connect(request, notify)
|
result := server.gateway.Connect(request, notify)
|
||||||
|
if shouldRetryOpenClawGatewayWithSharedToken(result) {
|
||||||
|
clearBridgeGatewayDeviceToken()
|
||||||
|
request.Auth.DeviceToken = ""
|
||||||
|
request.HasDeviceToken = false
|
||||||
|
request.Auth.Token = bridgeSharedAuthToken()
|
||||||
|
request.HasSharedAuth = strings.TrimSpace(request.Auth.Token) != ""
|
||||||
|
if request.HasSharedAuth {
|
||||||
|
request.ConnectAuthMode = "shared-token"
|
||||||
|
request.ConnectAuthFields = []string{"token"}
|
||||||
|
request.ConnectAuthSources = []string{"bridge:repair"}
|
||||||
|
result = server.gateway.Connect(request, notify)
|
||||||
|
}
|
||||||
|
}
|
||||||
if result.OK {
|
if result.OK {
|
||||||
saveBridgeGatewayDeviceToken(result.ReturnedDeviceToken)
|
saveBridgeGatewayDeviceToken(result.ReturnedDeviceToken)
|
||||||
return nil
|
return nil
|
||||||
@ -297,6 +323,21 @@ func ensureProductionGatewayConnected(
|
|||||||
return &shared.RPCError{Code: -32002, Message: "GATEWAY_CONNECT_FAILED: " + message}
|
return &shared.RPCError{Code: -32002, Message: "GATEWAY_CONNECT_FAILED: " + message}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldRetryOpenClawGatewayWithSharedToken(result gatewayruntime.ConnectResult) bool {
|
||||||
|
if result.OK || strings.TrimSpace(bridgeSharedAuthToken()) == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
code := strings.ToUpper(strings.TrimSpace(shared.StringArg(result.Error, "code", "")))
|
||||||
|
message := strings.ToLower(strings.TrimSpace(shared.StringArg(result.Error, "message", "")))
|
||||||
|
details := shared.AsMap(result.Error["details"])
|
||||||
|
detailCode := strings.ToUpper(strings.TrimSpace(shared.StringArg(details, "code", "")))
|
||||||
|
return detailCode == "AUTH_DEVICE_TOKEN_MISMATCH" ||
|
||||||
|
detailCode == "PAIRING_REQUIRED" ||
|
||||||
|
code == "NOT_PAIRED" ||
|
||||||
|
strings.Contains(message, "device token mismatch") ||
|
||||||
|
strings.Contains(message, "rotate/reissue device token")
|
||||||
|
}
|
||||||
|
|
||||||
func configureProductionOpenClawGatewayRuntime(manager *gatewayruntime.Manager) {
|
func configureProductionOpenClawGatewayRuntime(manager *gatewayruntime.Manager) {
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -138,6 +138,20 @@ func saveBridgeGatewayDeviceToken(deviceToken string) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearBridgeGatewayDeviceToken() {
|
||||||
|
bridgeGatewayIdentity.Lock()
|
||||||
|
defer bridgeGatewayIdentity.Unlock()
|
||||||
|
if strings.TrimSpace(bridgeGatewayIdentity.value.DeviceID) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bridgeGatewayIdentity.deviceToken = ""
|
||||||
|
_ = persistBridgeGatewayIdentity(
|
||||||
|
bridgeGatewayIdentityPath(),
|
||||||
|
bridgeGatewayIdentity.value,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func persistBridgeGatewayIdentity(
|
func persistBridgeGatewayIdentity(
|
||||||
path string,
|
path string,
|
||||||
identity gatewayruntime.DeviceIdentity,
|
identity gatewayruntime.DeviceIdentity,
|
||||||
|
|||||||
@ -144,6 +144,26 @@ func TestBridgeGatewayIdentityPersistsReturnedDeviceToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBridgeGatewayIdentityClearsStoredDeviceToken(t *testing.T) {
|
||||||
|
identityPath := filepath.Join(t.TempDir(), "openclaw-device.json")
|
||||||
|
t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", identityPath)
|
||||||
|
resetBridgeGatewayIdentityForTest()
|
||||||
|
t.Cleanup(resetBridgeGatewayIdentityForTest)
|
||||||
|
|
||||||
|
identity := newBridgeGatewayIdentity()
|
||||||
|
saveBridgeGatewayDeviceToken("device-token-2")
|
||||||
|
clearBridgeGatewayDeviceToken()
|
||||||
|
resetBridgeGatewayIdentityForTest()
|
||||||
|
|
||||||
|
reloaded, token := bridgeGatewayOpenClawCredentials()
|
||||||
|
if reloaded.DeviceID != identity.DeviceID {
|
||||||
|
t.Fatalf("reloaded identity = %q, want %q", reloaded.DeviceID, identity.DeviceID)
|
||||||
|
}
|
||||||
|
if token != "" {
|
||||||
|
t.Fatalf("device token should be cleared, got %q", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func resetBridgeGatewayIdentityForTest() {
|
func resetBridgeGatewayIdentityForTest() {
|
||||||
bridgeGatewayIdentity.Lock()
|
bridgeGatewayIdentity.Lock()
|
||||||
defer bridgeGatewayIdentity.Unlock()
|
defer bridgeGatewayIdentity.Unlock()
|
||||||
|
|||||||
@ -108,3 +108,117 @@ func TestSystemLogsConnectsProductionGatewayForStatus(t *testing.T) {
|
|||||||
t.Fatalf("expected connected status to reuse gateway session, got %d connect attempts", got)
|
t.Fatalf("expected connected status to reuse gateway session, got %d connect attempts", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProductionGatewayReconnectsWithSharedTokenAfterDeviceTokenMismatch(t *testing.T) {
|
||||||
|
gateway := newAcpFakeOpenClawGateway(t)
|
||||||
|
defer gateway.Close()
|
||||||
|
gateway.rejectDeviceTokenOnce.Store(true)
|
||||||
|
|
||||||
|
identityPath := filepath.Join(t.TempDir(), "openclaw-device.json")
|
||||||
|
t.Setenv("GATEWAY_RPC_URL", gateway.URL())
|
||||||
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
||||||
|
t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml"))
|
||||||
|
t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", identityPath)
|
||||||
|
resetBridgeGatewayIdentityForTest()
|
||||||
|
t.Cleanup(resetBridgeGatewayIdentityForTest)
|
||||||
|
|
||||||
|
_ = newBridgeGatewayIdentity()
|
||||||
|
saveBridgeGatewayDeviceToken("stale-device-token")
|
||||||
|
server := NewServer()
|
||||||
|
|
||||||
|
result, rpcErr := server.handleRequest(
|
||||||
|
shared.RPCRequest{
|
||||||
|
ID: "status",
|
||||||
|
Method: "system.logs",
|
||||||
|
Params: map[string]any{},
|
||||||
|
},
|
||||||
|
func(map[string]any) {},
|
||||||
|
)
|
||||||
|
if rpcErr != nil {
|
||||||
|
t.Fatalf("system.logs returned rpc error: %#v", rpcErr)
|
||||||
|
}
|
||||||
|
if got := result["gatewayStatus"]; got != "connected" {
|
||||||
|
t.Fatalf("expected gatewayStatus connected after repair, got %#v", result)
|
||||||
|
}
|
||||||
|
if got := gateway.ConnectCount(); got != 2 {
|
||||||
|
t.Fatalf("expected stale device token retry with shared token, got %d connects", got)
|
||||||
|
}
|
||||||
|
resetBridgeGatewayIdentityForTest()
|
||||||
|
_, token := bridgeGatewayOpenClawCredentials()
|
||||||
|
if token != "device-token-1" {
|
||||||
|
t.Fatalf("expected repaired device token to be persisted, got %q", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProductionGatewayReconnectPrefersAIWorkspaceToken(t *testing.T) {
|
||||||
|
gateway := newAcpFakeOpenClawGateway(t)
|
||||||
|
defer gateway.Close()
|
||||||
|
gateway.rejectDeviceTokenOnce.Store(true)
|
||||||
|
|
||||||
|
t.Setenv("GATEWAY_RPC_URL", gateway.URL())
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-token")
|
||||||
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
||||||
|
t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml"))
|
||||||
|
t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", filepath.Join(t.TempDir(), "openclaw-device.json"))
|
||||||
|
resetBridgeGatewayIdentityForTest()
|
||||||
|
t.Cleanup(resetBridgeGatewayIdentityForTest)
|
||||||
|
|
||||||
|
_ = newBridgeGatewayIdentity()
|
||||||
|
saveBridgeGatewayDeviceToken("stale-device-token")
|
||||||
|
server := NewServer()
|
||||||
|
|
||||||
|
result, rpcErr := server.handleRequest(
|
||||||
|
shared.RPCRequest{
|
||||||
|
ID: "status",
|
||||||
|
Method: "system.logs",
|
||||||
|
Params: map[string]any{},
|
||||||
|
},
|
||||||
|
func(map[string]any) {},
|
||||||
|
)
|
||||||
|
if rpcErr != nil {
|
||||||
|
t.Fatalf("system.logs returned rpc error: %#v", rpcErr)
|
||||||
|
}
|
||||||
|
if got := result["gatewayStatus"]; got != "connected" {
|
||||||
|
t.Fatalf("expected gatewayStatus connected after AI workspace token repair, got %#v", result)
|
||||||
|
}
|
||||||
|
if got := gateway.ConnectCount(); got != 2 {
|
||||||
|
t.Fatalf("expected stale device token retry with AI workspace token, got %d connects", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProductionGatewayDoesNotUseInternalServiceTokenFallback(t *testing.T) {
|
||||||
|
gateway := newAcpFakeOpenClawGateway(t)
|
||||||
|
defer gateway.Close()
|
||||||
|
gateway.rejectDeviceTokenOnce.Store(true)
|
||||||
|
|
||||||
|
t.Setenv("GATEWAY_RPC_URL", gateway.URL())
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
|
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||||
|
t.Setenv("INTERNAL_SERVICE_TOKEN", "bridge-test-token")
|
||||||
|
t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml"))
|
||||||
|
t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", filepath.Join(t.TempDir(), "openclaw-device.json"))
|
||||||
|
resetBridgeGatewayIdentityForTest()
|
||||||
|
t.Cleanup(resetBridgeGatewayIdentityForTest)
|
||||||
|
|
||||||
|
_ = newBridgeGatewayIdentity()
|
||||||
|
saveBridgeGatewayDeviceToken("stale-device-token")
|
||||||
|
server := NewServer()
|
||||||
|
|
||||||
|
result, rpcErr := server.handleRequest(
|
||||||
|
shared.RPCRequest{
|
||||||
|
ID: "status",
|
||||||
|
Method: "system.logs",
|
||||||
|
Params: map[string]any{},
|
||||||
|
},
|
||||||
|
func(map[string]any) {},
|
||||||
|
)
|
||||||
|
if rpcErr != nil {
|
||||||
|
t.Fatalf("system.logs returned rpc error: %#v", rpcErr)
|
||||||
|
}
|
||||||
|
if got := result["gatewayStatus"]; got != "disconnected" {
|
||||||
|
t.Fatalf("expected gatewayStatus disconnected without AI workspace token, got %#v", result)
|
||||||
|
}
|
||||||
|
if got := gateway.ConnectCount(); got != 1 {
|
||||||
|
t.Fatalf("expected no retry with internal token, got %d connects", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -95,8 +95,8 @@ func TestCapabilitiesExposeBuiltInProductionProviderCatalog(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProductionProviderCatalogFallsBackToBridgeAuthToken(t *testing.T) {
|
func TestProductionProviderCatalogFallsBackToBridgeAuthToken(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token")
|
||||||
t.Setenv("INTERNAL_SERVICE_TOKEN", "")
|
|
||||||
|
|
||||||
_, catalog, _ := newProductionProviderCatalog()
|
_, catalog, _ := newProductionProviderCatalog()
|
||||||
p, ok := catalog["codex"]
|
p, ok := catalog["codex"]
|
||||||
@ -109,9 +109,24 @@ func TestProductionProviderCatalogFallsBackToBridgeAuthToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProductionProviderCatalogPrefersAIWorkspaceAuthToken(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-token")
|
||||||
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token")
|
||||||
|
|
||||||
|
_, catalog, _ := newProductionProviderCatalog()
|
||||||
|
p, ok := catalog["codex"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("missing codex")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := p.AuthorizationHeader; got != "Bearer ai-workspace-token" {
|
||||||
|
t.Fatalf("expected AI workspace bearer header, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProductionProviderCatalogPrefersDedicatedBridgeAuthToken(t *testing.T) {
|
func TestProductionProviderCatalogPrefersDedicatedBridgeAuthToken(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "dedicated-token")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "dedicated-token")
|
||||||
t.Setenv("INTERNAL_SERVICE_TOKEN", "legacy-token")
|
|
||||||
|
|
||||||
_, catalog, _ := newProductionProviderCatalog()
|
_, catalog, _ := newProductionProviderCatalog()
|
||||||
p, ok := catalog["codex"]
|
p, ok := catalog["codex"]
|
||||||
@ -125,6 +140,7 @@ func TestProductionProviderCatalogPrefersDedicatedBridgeAuthToken(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProductionProviderCatalogIgnoresInternalServiceToken(t *testing.T) {
|
func TestProductionProviderCatalogIgnoresInternalServiceToken(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||||
t.Setenv("INTERNAL_SERVICE_TOKEN", "legacy-token")
|
t.Setenv("INTERNAL_SERVICE_TOKEN", "legacy-token")
|
||||||
|
|
||||||
|
|||||||
@ -3056,6 +3056,7 @@ type acpFakeOpenClawGateway struct {
|
|||||||
artifactCount atomic.Int32
|
artifactCount atomic.Int32
|
||||||
artifactReadCount atomic.Int32
|
artifactReadCount atomic.Int32
|
||||||
artifactReadFailures atomic.Int32
|
artifactReadFailures atomic.Int32
|
||||||
|
rejectDeviceTokenOnce atomic.Bool
|
||||||
closeNextChatSend atomic.Bool
|
closeNextChatSend atomic.Bool
|
||||||
alwaysCloseChatSend atomic.Bool
|
alwaysCloseChatSend atomic.Bool
|
||||||
agentWaitDelayMs atomic.Int64
|
agentWaitDelayMs atomic.Int64
|
||||||
@ -3141,7 +3142,23 @@ func newAcpFakeOpenClawGateway(t *testing.T) *acpFakeOpenClawGateway {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got, want := shared.StringArg(shared.AsMap(params["auth"]), "token", ""), os.Getenv("BRIDGE_AUTH_TOKEN"); got != want {
|
auth := shared.AsMap(params["auth"])
|
||||||
|
if fake.rejectDeviceTokenOnce.Swap(false) && strings.TrimSpace(shared.StringArg(auth, "deviceToken", "")) != "" {
|
||||||
|
_ = conn.WriteJSON(map[string]any{
|
||||||
|
"type": "res",
|
||||||
|
"id": id,
|
||||||
|
"ok": false,
|
||||||
|
"error": map[string]any{
|
||||||
|
"code": "INVALID_REQUEST",
|
||||||
|
"message": "unauthorized: device token mismatch (rotate/reissue device token)",
|
||||||
|
"details": map[string]any{
|
||||||
|
"code": "AUTH_DEVICE_TOKEN_MISMATCH",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got, want := shared.StringArg(auth, "token", ""), bridgeSharedAuthToken(); got != want {
|
||||||
_ = conn.WriteJSON(map[string]any{
|
_ = conn.WriteJSON(map[string]any{
|
||||||
"type": "res",
|
"type": "res",
|
||||||
"id": id,
|
"id": id,
|
||||||
|
|||||||
@ -43,13 +43,20 @@ func newHTTPServer(addr string, handler http.Handler) *http.Server {
|
|||||||
|
|
||||||
func NewServer() *Server {
|
func NewServer() *Server {
|
||||||
config := loadBridgeConfig()
|
config := loadBridgeConfig()
|
||||||
|
authTokens := bridgeInboundAuthTokens()
|
||||||
|
authToken := ""
|
||||||
|
authExtraTokens := []string(nil)
|
||||||
|
if len(authTokens) > 0 {
|
||||||
|
authToken = authTokens[0]
|
||||||
|
authExtraTokens = authTokens[1:]
|
||||||
|
}
|
||||||
s := &Server{
|
s := &Server{
|
||||||
sessions: make(map[string]*session),
|
sessions: make(map[string]*session),
|
||||||
config: config,
|
config: config,
|
||||||
allowedOrigins: shared.ParseAllowedOrigins(shared.EnvOrDefault("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus,http://localhost:*,http://127.0.0.1:*")),
|
allowedOrigins: shared.ParseAllowedOrigins(shared.EnvOrDefault("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus,http://localhost:*,http://127.0.0.1:*")),
|
||||||
authService: service.NewStaticTokenAuthService(
|
authService: service.NewStaticTokenAuthService(
|
||||||
shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""),
|
authToken,
|
||||||
shared.EnvOrDefault("BRIDGE_REVIEW_AUTH_TOKEN", ""),
|
authExtraTokens...,
|
||||||
),
|
),
|
||||||
openClawGate: newOpenClawGatewayAdmissionGate(config),
|
openClawGate: newOpenClawGatewayAdmissionGate(config),
|
||||||
taskRouter: newDistributedTaskRouter(distributedTaskRouterConfig{
|
taskRouter: newDistributedTaskRouter(distributedTaskRouterConfig{
|
||||||
|
|||||||
@ -875,6 +875,7 @@ func (w *panicSSEWriter) Write(payload []byte) (int, error) {
|
|||||||
func (w *panicSSEWriter) WriteHeader(int) {}
|
func (w *panicSSEWriter) WriteHeader(int) {}
|
||||||
|
|
||||||
func TestHTTPHandlerPingRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) {
|
func TestHTTPHandlerPingRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
||||||
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
||||||
server := NewServer()
|
server := NewServer()
|
||||||
@ -889,7 +890,27 @@ func TestHTTPHandlerPingRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPHandlerPingAcceptsAIWorkspaceBearerAuthorization(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-test-token")
|
||||||
|
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||||
|
t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "")
|
||||||
|
t.Setenv("INTERNAL_SERVICE_TOKEN", "")
|
||||||
|
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
||||||
|
server := NewServer()
|
||||||
|
handler := server.Handler()
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/api/ping", nil)
|
||||||
|
request.Header.Set("Authorization", "Bearer ai-workspace-test-token")
|
||||||
|
handler.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200 for AI workspace token, got %d", recorder.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHTTPHandlerPingAllowsReviewBearerAuthorizationWhenConfigured(t *testing.T) {
|
func TestHTTPHandlerPingAllowsReviewBearerAuthorizationWhenConfigured(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-test-token")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
||||||
t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "review-bridge-test-token")
|
t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "review-bridge-test-token")
|
||||||
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
||||||
@ -950,8 +971,10 @@ func TestHandleRPCAllowsPreflightForConfiguredOrigin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleRPCAllowsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
|
func TestHandleRPCAllowsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "")
|
t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "")
|
||||||
|
t.Setenv("INTERNAL_SERVICE_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
||||||
server := NewServer()
|
server := NewServer()
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
@ -970,6 +993,7 @@ func TestHandleRPCAllowsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) {
|
func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
||||||
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
||||||
server := NewServer()
|
server := NewServer()
|
||||||
@ -990,6 +1014,7 @@ func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *te
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleRPCCapabilitiesRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) {
|
func TestHandleRPCCapabilitiesRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
||||||
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
||||||
server := NewServer()
|
server := NewServer()
|
||||||
@ -1009,6 +1034,7 @@ func TestHandleRPCCapabilitiesRequiresBearerAuthorizationWhenBridgeAuthTokenConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleRPCAllowsReviewBearerAuthorizationWhenConfigured(t *testing.T) {
|
func TestHandleRPCAllowsReviewBearerAuthorizationWhenConfigured(t *testing.T) {
|
||||||
|
t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-test-token")
|
||||||
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token")
|
||||||
t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "review-bridge-test-token")
|
t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "review-bridge-test-token")
|
||||||
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml")
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
||||||
BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}"
|
BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}"
|
||||||
|
|
||||||
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
||||||
echo "Error: BRIDGE_AUTH_TOKEN is required" >&2
|
echo "Error: AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
||||||
BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}"
|
BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}"
|
||||||
|
|
||||||
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
||||||
echo "Error: BRIDGE_AUTH_TOKEN is required" >&2
|
echo "Error: AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
||||||
BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}"
|
BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}"
|
||||||
HERMES_RPC_URL="${HERMES_RPC_URL:-${BRIDGE_SERVER_URL%/}/acp/rpc}"
|
HERMES_RPC_URL="${HERMES_RPC_URL:-${BRIDGE_SERVER_URL%/}/acp/rpc}"
|
||||||
|
|
||||||
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
||||||
echo "Error: BRIDGE_AUTH_TOKEN is required" >&2
|
echo "Error: AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -57,15 +57,22 @@ resolve_token_from_unit() {
|
|||||||
|
|
||||||
REMOTE_SYSTEM_SERVICE_UNIT_CONTENT="$(ssh -o BatchMode=yes "${SYSTEM_MIGRATION_USER}@${TARGET_HOST}" "cat '${SYSTEM_SERVICE_UNIT_PATH}' 2>/dev/null || true" 2>/dev/null || true)"
|
REMOTE_SYSTEM_SERVICE_UNIT_CONTENT="$(ssh -o BatchMode=yes "${SYSTEM_MIGRATION_USER}@${TARGET_HOST}" "cat '${SYSTEM_SERVICE_UNIT_PATH}' 2>/dev/null || true" 2>/dev/null || true)"
|
||||||
|
|
||||||
if [[ -z "${BRIDGE_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" ]]; then
|
if [[ -z "${AI_WORKSPACE_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" ]]; then
|
||||||
|
AI_WORKSPACE_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "AI_WORKSPACE_AUTH_TOKEN")"
|
||||||
|
if [[ -n "${AI_WORKSPACE_AUTH_TOKEN}" ]]; then
|
||||||
|
echo "recovered AI_WORKSPACE_AUTH_TOKEN from ${SYSTEM_SERVICE_UNIT_PATH} on ${TARGET_HOST}" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${AI_WORKSPACE_AUTH_TOKEN:-}" && -z "${BRIDGE_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" ]]; then
|
||||||
BRIDGE_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "BRIDGE_AUTH_TOKEN")"
|
BRIDGE_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "BRIDGE_AUTH_TOKEN")"
|
||||||
if [[ -n "${BRIDGE_AUTH_TOKEN}" ]]; then
|
if [[ -n "${BRIDGE_AUTH_TOKEN}" ]]; then
|
||||||
echo "recovered BRIDGE_AUTH_TOKEN from ${SYSTEM_SERVICE_UNIT_PATH} on ${TARGET_HOST}" >&2
|
echo "recovered BRIDGE_AUTH_TOKEN from ${SYSTEM_SERVICE_UNIT_PATH} on ${TARGET_HOST}" >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${BRIDGE_AUTH_TOKEN:-}" ]]; then
|
if [[ -z "${AI_WORKSPACE_AUTH_TOKEN:-}" && -z "${BRIDGE_AUTH_TOKEN:-}" ]]; then
|
||||||
echo "::error::BRIDGE_AUTH_TOKEN is required: pass it via env, -e xworkmate_bridge_auth_token=, or keep the existing system service unit at ${SYSTEM_SERVICE_UNIT_PATH}" >&2
|
echo "::error::AI_WORKSPACE_AUTH_TOKEN is required: pass it via env, -e ai_workspace_auth_token=, or keep AI_WORKSPACE_AUTH_TOKEN/BRIDGE_AUTH_TOKEN in the existing system service unit at ${SYSTEM_SERVICE_UNIT_PATH}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -73,7 +80,12 @@ if [[ -z "${BRIDGE_REVIEW_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CON
|
|||||||
BRIDGE_REVIEW_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "BRIDGE_REVIEW_AUTH_TOKEN")"
|
BRIDGE_REVIEW_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "BRIDGE_REVIEW_AUTH_TOKEN")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AUTH_TOKEN_LINE="Environment=\"BRIDGE_AUTH_TOKEN=$(escape_systemd_env "${BRIDGE_AUTH_TOKEN}")\""
|
AUTH_TOKEN_LINE=""
|
||||||
|
if [[ -n "${AI_WORKSPACE_AUTH_TOKEN:-}" ]]; then
|
||||||
|
AUTH_TOKEN_LINE="Environment=\"AI_WORKSPACE_AUTH_TOKEN=$(escape_systemd_env "${AI_WORKSPACE_AUTH_TOKEN}")\""
|
||||||
|
else
|
||||||
|
AUTH_TOKEN_LINE="Environment=\"BRIDGE_AUTH_TOKEN=$(escape_systemd_env "${BRIDGE_AUTH_TOKEN}")\""
|
||||||
|
fi
|
||||||
|
|
||||||
REVIEW_TOKEN_LINE=""
|
REVIEW_TOKEN_LINE=""
|
||||||
if [[ -n "${BRIDGE_REVIEW_AUTH_TOKEN:-}" ]]; then
|
if [[ -n "${BRIDGE_REVIEW_AUTH_TOKEN:-}" ]]; then
|
||||||
@ -127,7 +139,7 @@ existing_env="$(
|
|||||||
systemctl --user show -p Environment --value "${SERVICE_NAME}" 2>/dev/null || true
|
systemctl --user show -p Environment --value "${SERVICE_NAME}" 2>/dev/null || true
|
||||||
systemctl show -p Environment --value "${SYSTEM_SERVICE_NAME}" 2>/dev/null || true
|
systemctl show -p Environment --value "${SYSTEM_SERVICE_NAME}" 2>/dev/null || true
|
||||||
if [[ -f "${SYSTEM_SERVICE_UNIT_PATH}" ]]; then
|
if [[ -f "${SYSTEM_SERVICE_UNIT_PATH}" ]]; then
|
||||||
sed -n 's/^Environment="\(BRIDGE_AUTH_TOKEN=[^"]*\|BRIDGE_REVIEW_AUTH_TOKEN=[^"]*\)"$/\1/p' "${SYSTEM_SERVICE_UNIT_PATH}"
|
sed -n 's/^Environment="\(AI_WORKSPACE_AUTH_TOKEN=[^"]*\|BRIDGE_AUTH_TOKEN=[^"]*\|BRIDGE_REVIEW_AUTH_TOKEN=[^"]*\)"$/\1/p' "${SYSTEM_SERVICE_UNIT_PATH}"
|
||||||
fi
|
fi
|
||||||
} | sed '/^$/d' | head -n 1
|
} | sed '/^$/d' | head -n 1
|
||||||
)"
|
)"
|
||||||
@ -146,7 +158,7 @@ for line in lines:
|
|||||||
|
|
||||||
for item in shlex.split(os.environ.get("EXISTING_ENV", "")):
|
for item in shlex.split(os.environ.get("EXISTING_ENV", "")):
|
||||||
key, sep, value = item.partition("=")
|
key, sep, value = item.partition("=")
|
||||||
if sep and key in {"BRIDGE_AUTH_TOKEN", "BRIDGE_REVIEW_AUTH_TOKEN"} and key not in present:
|
if sep and key in {"AI_WORKSPACE_AUTH_TOKEN", "BRIDGE_AUTH_TOKEN", "BRIDGE_REVIEW_AUTH_TOKEN"} and key not in present:
|
||||||
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
||||||
lines.append(f'Environment="{key}={escaped}"')
|
lines.append(f'Environment="{key}={escaped}"')
|
||||||
present.add(key)
|
present.add(key)
|
||||||
|
|||||||
@ -20,6 +20,6 @@ if [[ "${RUN_APPLY}" != "true" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
ANSIBLE_CONFIG="${PWD}/ansible.cfg" \
|
ANSIBLE_CONFIG="${PWD}/ansible.cfg" \
|
||||||
BRIDGE_AUTH_TOKEN="${INTERNAL_SERVICE_TOKEN:-}" \
|
BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" \
|
||||||
BRIDGE_REVIEW_AUTH_TOKEN="${BRIDGE_REVIEW_AUTH_TOKEN:-}" \
|
BRIDGE_REVIEW_AUTH_TOKEN="${BRIDGE_REVIEW_AUTH_TOKEN:-}" \
|
||||||
"${args[@]}"
|
"${args[@]}"
|
||||||
|
|||||||
@ -31,8 +31,9 @@ curl_args=(
|
|||||||
--max-time 20
|
--max-time 20
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ -n "${BRIDGE_AUTH_TOKEN:-}" ]]; then
|
AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}"
|
||||||
curl_args+=(-H "Authorization: Bearer ${BRIDGE_AUTH_TOKEN}")
|
if [[ -n "${AUTH_TOKEN}" ]]; then
|
||||||
|
curl_args+=(-H "Authorization: Bearer ${AUTH_TOKEN}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for ((attempt = 1; attempt <= attempts; attempt += 1)); do
|
for ((attempt = 1; attempt <= attempts; attempt += 1)); do
|
||||||
@ -83,4 +84,3 @@ print(f"production_tag={deployed_tag}")
|
|||||||
print(f"production_commit={deployed_commit}")
|
print(f"production_commit={deployed_commit}")
|
||||||
print(f"production_version={deployed_version}")
|
print(f"production_version={deployed_version}")
|
||||||
PY
|
PY
|
||||||
|
|
||||||
|
|||||||
@ -149,7 +149,7 @@ run_deploy() {
|
|||||||
|
|
||||||
run_env_token_case() {
|
run_env_token_case() {
|
||||||
local tmp_dir
|
local tmp_dir
|
||||||
tmp_dir="$(setup_test_env "case: BRIDGE_AUTH_TOKEN env var drives the unit file")"
|
tmp_dir="$(setup_test_env "case: AI_WORKSPACE_AUTH_TOKEN env var drives the unit file")"
|
||||||
|
|
||||||
local log_file="${tmp_dir}/deploy.log"
|
local log_file="${tmp_dir}/deploy.log"
|
||||||
local unit_file="${tmp_dir}/remote/home/ubuntu/.config/systemd/user/xworkmate-bridge.service"
|
local unit_file="${tmp_dir}/remote/home/ubuntu/.config/systemd/user/xworkmate-bridge.service"
|
||||||
@ -160,7 +160,7 @@ run_env_token_case() {
|
|||||||
BRIDGE_CONFIG_PATH="${tmp_dir}/remote/opt/cloud-neutral/xworkmate-bridge/config.yaml" \
|
BRIDGE_CONFIG_PATH="${tmp_dir}/remote/opt/cloud-neutral/xworkmate-bridge/config.yaml" \
|
||||||
USER_SYSTEMD_DIR="${tmp_dir}/remote/home/ubuntu/.config/systemd/user" \
|
USER_SYSTEMD_DIR="${tmp_dir}/remote/home/ubuntu/.config/systemd/user" \
|
||||||
DEPLOY_NATIVE_SKIP_PROC_CHECK=true \
|
DEPLOY_NATIVE_SKIP_PROC_CHECK=true \
|
||||||
BRIDGE_AUTH_TOKEN="test-token"
|
AI_WORKSPACE_AUTH_TOKEN="test-token"
|
||||||
|
|
||||||
local log_output
|
local log_output
|
||||||
log_output="$(cat "${log_file}")"
|
log_output="$(cat "${log_file}")"
|
||||||
@ -168,7 +168,7 @@ run_env_token_case() {
|
|||||||
assert_contains "${log_output}" "scp ubuntu@example.test:"
|
assert_contains "${log_output}" "scp ubuntu@example.test:"
|
||||||
assert_contains "${log_output}" "ssh ubuntu@example.test"
|
assert_contains "${log_output}" "ssh ubuntu@example.test"
|
||||||
assert_contains "${log_output}" "systemctl --user restart xworkmate-bridge.service"
|
assert_contains "${log_output}" "systemctl --user restart xworkmate-bridge.service"
|
||||||
assert_file_contains "${unit_file}" 'Environment="BRIDGE_AUTH_TOKEN=test-token"'
|
assert_file_contains "${unit_file}" 'Environment="AI_WORKSPACE_AUTH_TOKEN=test-token"'
|
||||||
assert_file_contains "${unit_file}" "WantedBy=default.target"
|
assert_file_contains "${unit_file}" "WantedBy=default.target"
|
||||||
|
|
||||||
rm -rf "${tmp_dir}"
|
rm -rf "${tmp_dir}"
|
||||||
@ -176,7 +176,7 @@ run_env_token_case() {
|
|||||||
|
|
||||||
run_unit_fallback_case() {
|
run_unit_fallback_case() {
|
||||||
local tmp_dir
|
local tmp_dir
|
||||||
tmp_dir="$(setup_test_env "case: BRIDGE_AUTH_TOKEN recovered from system service unit file")"
|
tmp_dir="$(setup_test_env "case: AI_WORKSPACE_AUTH_TOKEN recovered from system service unit file")"
|
||||||
|
|
||||||
local system_unit_dir="${tmp_dir}/remote/etc/systemd/system"
|
local system_unit_dir="${tmp_dir}/remote/etc/systemd/system"
|
||||||
mkdir -p "${system_unit_dir}"
|
mkdir -p "${system_unit_dir}"
|
||||||
@ -185,7 +185,7 @@ run_unit_fallback_case() {
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Stale system service
|
Description=Stale system service
|
||||||
[Service]
|
[Service]
|
||||||
Environment="BRIDGE_AUTH_TOKEN=recovered-from-systemd"
|
Environment="AI_WORKSPACE_AUTH_TOKEN=recovered-from-systemd"
|
||||||
Environment="BRIDGE_REVIEW_AUTH_TOKEN=recovered-review-token"
|
Environment="BRIDGE_REVIEW_AUTH_TOKEN=recovered-review-token"
|
||||||
ExecStart=/bin/true
|
ExecStart=/bin/true
|
||||||
EOF
|
EOF
|
||||||
@ -201,7 +201,7 @@ EOF
|
|||||||
SYSTEM_SERVICE_UNIT_PATH="${system_unit_file}" \
|
SYSTEM_SERVICE_UNIT_PATH="${system_unit_file}" \
|
||||||
DEPLOY_NATIVE_SKIP_PROC_CHECK=true
|
DEPLOY_NATIVE_SKIP_PROC_CHECK=true
|
||||||
|
|
||||||
assert_file_contains "${unit_file}" 'Environment="BRIDGE_AUTH_TOKEN=recovered-from-systemd"'
|
assert_file_contains "${unit_file}" 'Environment="AI_WORKSPACE_AUTH_TOKEN=recovered-from-systemd"'
|
||||||
assert_file_contains "${unit_file}" 'Environment="BRIDGE_REVIEW_AUTH_TOKEN=recovered-review-token"'
|
assert_file_contains "${unit_file}" 'Environment="BRIDGE_REVIEW_AUTH_TOKEN=recovered-review-token"'
|
||||||
|
|
||||||
rm -rf "${tmp_dir}"
|
rm -rf "${tmp_dir}"
|
||||||
@ -209,7 +209,7 @@ EOF
|
|||||||
|
|
||||||
run_fail_fast_case() {
|
run_fail_fast_case() {
|
||||||
local tmp_dir
|
local tmp_dir
|
||||||
tmp_dir="$(setup_test_env "case: missing BRIDGE_AUTH_TOKEN fails fast with clear error")"
|
tmp_dir="$(setup_test_env "case: missing AI_WORKSPACE_AUTH_TOKEN fails fast with clear error")"
|
||||||
|
|
||||||
local log_file="${tmp_dir}/deploy.log"
|
local log_file="${tmp_dir}/deploy.log"
|
||||||
local stderr_file="${tmp_dir}/deploy.stderr"
|
local stderr_file="${tmp_dir}/deploy.stderr"
|
||||||
@ -226,9 +226,9 @@ run_fail_fast_case() {
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ "${exit_code}" == "0" ]]; then
|
if [[ "${exit_code}" == "0" ]]; then
|
||||||
fail "expected deploy to fail when BRIDGE_AUTH_TOKEN is empty and no system service unit exists"
|
fail "expected deploy to fail when AI_WORKSPACE_AUTH_TOKEN is empty and no system service unit exists"
|
||||||
fi
|
fi
|
||||||
assert_contains "$(cat "${stderr_file}")" "BRIDGE_AUTH_TOKEN is required"
|
assert_contains "$(cat "${stderr_file}")" "AI_WORKSPACE_AUTH_TOKEN is required"
|
||||||
|
|
||||||
rm -rf "${tmp_dir}"
|
rm -rf "${tmp_dir}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,11 @@ fi
|
|||||||
|
|
||||||
BASE_URL="$(normalize_url "${BRIDGE_SERVER_URL:-${2:-https://xworkmate-bridge.svc.plus}}")"
|
BASE_URL="$(normalize_url "${BRIDGE_SERVER_URL:-${2:-https://xworkmate-bridge.svc.plus}}")"
|
||||||
RPC_URL="${BASE_URL%/}/acp/rpc"
|
RPC_URL="${BASE_URL%/}/acp/rpc"
|
||||||
AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:?BRIDGE_AUTH_TOKEN is required}"
|
AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}"
|
||||||
|
if [[ -z "${AUTH_TOKEN}" ]]; then
|
||||||
|
echo "AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
fast_http_curl_common=(
|
fast_http_curl_common=(
|
||||||
--silent
|
--silent
|
||||||
|
|||||||
@ -2,14 +2,14 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BASE_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
BASE_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
||||||
AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}"
|
AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}"
|
||||||
REQUEST_ORIGIN="${OPENCLAW_SMOKE_ORIGIN:-https://xworkmate.svc.plus}"
|
REQUEST_ORIGIN="${OPENCLAW_SMOKE_ORIGIN:-https://xworkmate.svc.plus}"
|
||||||
RPC_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_RPC_TIMEOUT_SECONDS:-180}"
|
RPC_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_RPC_TIMEOUT_SECONDS:-180}"
|
||||||
POLL_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_POLL_TIMEOUT_SECONDS:-120}"
|
POLL_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_POLL_TIMEOUT_SECONDS:-120}"
|
||||||
POLL_INTERVAL_SECONDS="${OPENCLAW_SMOKE_POLL_INTERVAL_SECONDS:-2}"
|
POLL_INTERVAL_SECONDS="${OPENCLAW_SMOKE_POLL_INTERVAL_SECONDS:-2}"
|
||||||
|
|
||||||
if [[ -z "${AUTH_TOKEN}" ]]; then
|
if [[ -z "${AUTH_TOKEN}" ]]; then
|
||||||
echo "BRIDGE_AUTH_TOKEN is required" >&2
|
echo "AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BASE_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
BASE_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
||||||
AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}"
|
AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}"
|
||||||
HTTP_TIMEOUT_SECONDS="${HTTP_TIMEOUT_SECONDS:-30}"
|
HTTP_TIMEOUT_SECONDS="${HTTP_TIMEOUT_SECONDS:-30}"
|
||||||
RPC_TIMEOUT_SECONDS="${RPC_TIMEOUT_SECONDS:-90}"
|
RPC_TIMEOUT_SECONDS="${RPC_TIMEOUT_SECONDS:-90}"
|
||||||
|
|
||||||
if [[ -z "${AUTH_TOKEN}" ]]; then
|
if [[ -z "${AUTH_TOKEN}" ]]; then
|
||||||
echo "BRIDGE_AUTH_TOKEN is required" >&2
|
echo "AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user