File size: 3,966 Bytes
b6ecafa
 
 
 
 
 
 
c4d8984
b6ecafa
c4d8984
b6ecafa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4840982
b6ecafa
 
 
4840982
 
 
 
 
b6ecafa
 
 
 
303c344
 
 
 
 
 
 
 
 
 
 
 
b6ecafa
 
 
 
 
c4d8984
 
 
 
 
b6ecafa
 
 
c4d8984
 
 
b6ecafa
 
c4d8984
 
 
 
 
 
 
b6ecafa
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import fs from 'node:fs'
import { config } from '@/lib/config'
import { logger } from '@/lib/logger'

interface OpenClawGatewayConfig {
  gateway?: {
    auth?: {
      mode?: 'token' | 'password'
      token?: string
      password?: string
    }
    port?: number
    controlUi?: {
      allowedOrigins?: string[]
    }
  }
}

function readOpenClawConfig(): OpenClawGatewayConfig | null {
  const configPath = config.openclawConfigPath
  if (!configPath || !fs.existsSync(configPath)) return null
  try {
    const raw = fs.readFileSync(configPath, 'utf8')
    return JSON.parse(raw) as OpenClawGatewayConfig
  } catch {
    return null
  }
}

export function registerMcAsDashboard(mcUrl: string): { registered: boolean; alreadySet: boolean } {
  const configPath = config.openclawConfigPath
  if (!configPath || !fs.existsSync(configPath)) {
    return { registered: false, alreadySet: false }
  }

  try {
    const raw = fs.readFileSync(configPath, 'utf8')
    const parsed = JSON.parse(raw) as Record<string, any>

    // Ensure nested structure
    if (!parsed.gateway) parsed.gateway = {}
    if (!parsed.gateway.controlUi) parsed.gateway.controlUi = {}

    const origin = new URL(mcUrl).origin
    const origins: string[] = parsed.gateway.controlUi.allowedOrigins || []
    const alreadyInOrigins = origins.includes(origin)

    if (alreadyInOrigins) {
      return { registered: false, alreadySet: true }
    }

    // Add MC origin to allowedOrigins only — do NOT touch dangerouslyDisableDeviceAuth.
    // MC authenticates via gateway token, but forcing device auth off is a security
    // downgrade that the operator should control, not Mission Control.
    origins.push(origin)
    parsed.gateway.controlUi.allowedOrigins = origins

    fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + '\n')
    logger.info({ origin }, 'Registered MC origin in gateway config')
    return { registered: true, alreadySet: false }
  } catch (err: any) {
    // Read-only filesystem (e.g. Docker read_only: true, or intentional mount) —
    // treat as a non-fatal skip rather than an error.
    if (err?.code === 'EROFS' || err?.code === 'EACCES' || err?.code === 'EPERM') {
      logger.warn(
        { err, configPath },
        'Gateway config is read-only — skipping MC origin registration. ' +
        'To enable auto-registration, mount openclaw.json with write access or ' +
        'add the MC origin to gateway.controlUi.allowedOrigins manually.',
      )
      return { registered: false, alreadySet: false }
    }
    logger.error({ err }, 'Failed to register MC in gateway config')
    return { registered: false, alreadySet: false }
  }
}

/**
 * Returns the gateway auth credential (token or password) for Bearer/WS auth.
 * Env overrides: OPENCLAW_GATEWAY_TOKEN, GATEWAY_TOKEN, OPENCLAW_GATEWAY_PASSWORD, GATEWAY_PASSWORD.
 * From config: uses gateway.auth.token when mode is "token", gateway.auth.password when mode is "password".
 */
export function getDetectedGatewayToken(): string {
  const envToken = (process.env.OPENCLAW_GATEWAY_TOKEN || process.env.GATEWAY_TOKEN || '').trim()
  if (envToken) return envToken
  
  const envPassword = (process.env.OPENCLAW_GATEWAY_PASSWORD || process.env.GATEWAY_PASSWORD || '').trim()
  if (envPassword) return envPassword

  const parsed = readOpenClawConfig()
  const auth = parsed?.gateway?.auth
  const mode = auth?.mode === 'password' ? 'password' : 'token'
  const credential =
    mode === 'password'
      ? String(auth?.password ?? '').trim()
      : String(auth?.token ?? '').trim()
  return credential
}

export function getDetectedGatewayPort(): number | null {
  const envPort = Number(process.env.OPENCLAW_GATEWAY_PORT || process.env.GATEWAY_PORT || '')
  if (Number.isFinite(envPort) && envPort > 0) return envPort

  const parsed = readOpenClawConfig()
  const cfgPort = Number(parsed?.gateway?.port || 0)
  return Number.isFinite(cfgPort) && cfgPort > 0 ? cfgPort : null
}