syw1516 commited on
Commit
37215eb
·
verified ·
1 Parent(s): ba3990a

Update scripts/openclaw-entrypoint.sh

Browse files
Files changed (1) hide show
  1. scripts/openclaw-entrypoint.sh +360 -96
scripts/openclaw-entrypoint.sh CHANGED
@@ -1,10 +1,11 @@
 
1
  #!/usr/bin/env bash
2
  set -euo pipefail
3
 
4
  OPENCLAW_USER="${OPENCLAW_USER:-root}"
5
  OPENCLAW_GROUP="${OPENCLAW_GROUP:-root}"
6
  OPENCLAW_HOME="${OPENCLAW_HOME:-/root}"
7
- OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-/root}"
8
  OPENCLAW_CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR}/openclaw.json}"
9
  OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-${OPENCLAW_STATE_DIR}/workspace}"
10
  OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
@@ -44,12 +45,21 @@ export OPENCLAW_BACKUP_ENV_FILE_PATH
44
  OPENCLAW_STEP_INDEX=0
45
  OPENCLAW_ENTRYPOINT_TAG="openclaw-entrypoint"
46
 
47
- timestamp_utc() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
48
- log_info() { printf '[%s] %s: %s\n' "$(timestamp_utc)" "$OPENCLAW_ENTRYPOINT_TAG" "$*"; }
49
- log_warn() { printf '[%s] %s: %s\n' "$(timestamp_utc)" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2; }
 
 
 
 
 
 
 
 
50
 
51
  run_step() {
52
- local description="$1"; shift
 
53
  OPENCLAW_STEP_INDEX=$((OPENCLAW_STEP_INDEX + 1))
54
  log_info "step ${OPENCLAW_STEP_INDEX} start: ${description}"
55
  "$@"
@@ -57,7 +67,8 @@ run_step() {
57
  }
58
 
59
  is_true() {
60
- local value; value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
 
61
  [[ "$value" == "1" || "$value" == "true" || "$value" == "yes" || "$value" == "on" ]]
62
  }
63
 
@@ -66,7 +77,8 @@ generate_gateway_token() {
66
  openssl rand -hex 32
67
  else
68
  python3 - <<'PY'
69
- import secrets; print(secrets.token_hex(32))
 
70
  PY
71
  fi
72
  }
@@ -83,22 +95,41 @@ mkdir_state_dirs() {
83
 
84
  fix_permissions() {
85
  local path
86
- [[ "$(id -u)" -ne 0 ]] && return 0
87
- [[ -e "$OPENCLAW_HOME" ]] && chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$OPENCLAW_HOME"
88
- for path in "$OPENCLAW_STATE_DIR" "$OPENCLAW_WORKSPACE_DIR" "$OPENCLAW_STDOUT_LOG_PATH" "$OPENCLAW_STDERR_LOG_PATH" "$(dirname "$OPENCLAW_STDOUT_LOG_PATH")" "$(dirname "$OPENCLAW_STDERR_LOG_PATH")"; do
89
- [[ -e "$path" ]] && chown -R "$OPENCLAW_USER:$OPENCLAW_GROUP" "$path"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  done
91
  }
92
 
93
  write_or_update_config() {
94
- local existing_token=""
 
 
95
  if [[ -f "$OPENCLAW_CONFIG_PATH" ]]; then
96
  existing_token="$(python3 - <<'PY'
97
- import json, os
 
 
98
  config_path = os.environ["OPENCLAW_CONFIG_PATH"]
99
  try:
100
- with open(config_path) as f:
101
- cfg = json.load(f)
102
  token = cfg.get("gateway", {}).get("auth", {}).get("token", "")
103
  if isinstance(token, str):
104
  print(token.strip())
@@ -107,8 +138,13 @@ except Exception:
107
  PY
108
  )"
109
  fi
 
110
  if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
111
- [[ -n "$existing_token" ]] && OPENCLAW_GATEWAY_TOKEN="$existing_token" || OPENCLAW_GATEWAY_TOKEN="$(generate_gateway_token)"
 
 
 
 
112
  fi
113
  export OPENCLAW_GATEWAY_TOKEN
114
 
@@ -129,13 +165,19 @@ PY
129
  OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH="$OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH" \
130
  OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS="${OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS:-}" \
131
  python3 - <<'PY'
132
- import json, os, sys
 
 
133
  from pathlib import Path
 
134
  config_path = Path(os.environ["OPENCLAW_CONFIG_PATH"])
135
  config = {}
136
  if config_path.exists():
137
- try: config = json.loads(config_path.read_text(encoding="utf-8"))
138
- except Exception: config = {}
 
 
 
139
  provider = os.environ.get("OPENCLAW_LLM_PROVIDER", "thirdparty").strip() or "thirdparty"
140
  model = (os.environ.get("OPENCLAW_LLM_MODEL") or "").strip()
141
  api = os.environ.get("OPENCLAW_LLM_API", "openai-completions").strip() or "openai-completions"
@@ -143,39 +185,104 @@ base_env_name = os.environ.get("OPENCLAW_LLM_BASE_URL_ENV", "OPENCLAW_LLM_BASE_U
143
  key_env_name = os.environ.get("OPENCLAW_LLM_API_KEY_ENV", "OPENCLAW_LLM_API_KEY").strip() or "OPENCLAW_LLM_API_KEY"
144
  base_url_value = (os.environ.get(base_env_name) or "").strip()
145
  api_key_value = (os.environ.get(key_env_name) or "").strip()
 
146
  missing_llm_env = []
147
- if not model: missing_llm_env.append("OPENCLAW_LLM_MODEL")
148
- if not base_url_value: missing_llm_env.append(base_env_name)
149
- if not api_key_value: missing_llm_env.append(key_env_name)
 
 
 
150
  custom_llm_ready = not missing_llm_env
151
  model_ref = f"{provider}/{model}" if custom_llm_ready else ""
 
152
  if missing_llm_env:
153
- print("openclaw: skip custom LLM model configuration because missing: " + ", ".join(missing_llm_env), file=sys.stderr)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  fallback_allowed_origins = [f"http://127.0.0.1:{os.environ.get('OPENCLAW_GATEWAY_PORT', '7860')}"]
155
  allowed_origins = fallback_allowed_origins
156
  raw_allowed_origins = (os.environ.get("OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS") or "").strip()
157
  if raw_allowed_origins:
158
  try:
159
  parsed = json.loads(raw_allowed_origins)
160
- if isinstance(parsed, list) and parsed:
161
- allowed_origins = [v.strip() for v in parsed if isinstance(v, str) and v.strip()]
162
- except Exception: pass
163
- fallback_origin = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_ALLOW_HOST_HEADER_ORIGIN_FALLBACK") or "true").lower() in {"1", "true", "yes", "on"}
164
- allow_insecure_auth = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH") or "false").lower() in {"1", "true", "yes", "on"}
165
- disable_device_auth = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH") or "false").lower() in {"1", "true", "yes", "on"}
166
- gateway = config.get("gateway", {})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  gateway["mode"] = os.environ.get("OPENCLAW_INIT_GATEWAY_MODE", "local")
168
  gateway["bind"] = os.environ.get("OPENCLAW_GATEWAY_BIND", "lan")
169
- auth = gateway.get("auth", {})
 
 
 
170
  token_value = (os.environ.get("OPENCLAW_GATEWAY_TOKEN") or "").strip()
171
  password_value = (os.environ.get("OPENCLAW_GATEWAY_PASSWORD") or "").strip()
172
- if token_value: auth["token"] = token_value
173
- if password_value: auth["password"] = password_value
 
 
 
174
  valid_auth_modes = {"none", "token", "password", "trusted-proxy"}
175
  mode_env = (os.environ.get("OPENCLAW_GATEWAY_AUTH_MODE") or "").strip().lower()
176
- if mode_env in valid_auth_modes: auth["mode"] = mode_env
177
- elif password_value and not token_value: auth["mode"] = "password"
178
- elif token_value: auth["mode"] = "token"
 
 
 
 
 
 
 
 
 
 
 
179
  gateway["auth"] = auth
180
  gateway.setdefault("controlUi", {})
181
  gateway["controlUi"]["allowedOrigins"] = allowed_origins
@@ -183,20 +290,34 @@ gateway["controlUi"]["dangerouslyAllowHostHeaderOriginFallback"] = fallback_orig
183
  gateway["controlUi"]["allowInsecureAuth"] = allow_insecure_auth
184
  gateway["controlUi"]["dangerouslyDisableDeviceAuth"] = disable_device_auth
185
  config["gateway"] = gateway
186
- agents = config.get("agents", {})
187
- defaults = agents.get("defaults", {})
 
 
 
 
 
188
  if custom_llm_ready:
189
  defaults["model"] = {"primary": model_ref}
190
- allow_models = defaults.get("models", {})
 
 
191
  allow_models.setdefault(model_ref, {"alias": "Default"})
192
  defaults["models"] = allow_models
193
  agents["defaults"] = defaults
194
  config["agents"] = agents
 
195
  if custom_llm_ready:
196
- models_cfg = config.get("models", {})
 
 
197
  models_cfg["mode"] = "merge"
198
- providers = models_cfg.get("providers", {})
199
- provider_cfg = providers.get(provider, {})
 
 
 
 
200
  provider_cfg["baseUrl"] = f"${{{base_env_name}}}"
201
  provider_cfg["apiKey"] = f"${{{key_env_name}}}"
202
  provider_cfg["api"] = api
@@ -204,18 +325,46 @@ if custom_llm_ready:
204
  providers[provider] = provider_cfg
205
  models_cfg["providers"] = providers
206
  config["models"] = models_cfg
 
 
207
  feishu_app_id = (os.environ.get("FEISHU_APP_ID") or "").strip()
208
  feishu_app_secret = (os.environ.get("FEISHU_APP_SECRET") or "").strip()
 
 
209
  if feishu_app_id and feishu_app_secret:
210
- channels_cfg = config.get("channels", {})
211
- feishu_cfg = channels_cfg.get("feishu", {})
212
- feishu_cfg.update({"enabled": True, "domain": (os.environ.get("FEISHU_DOMAIN") or "feishu").strip(), "connectionMode": "websocket", "defaultAccount": "default", "dmPolicy": "open", "groupPolicy": "open"})
213
- accounts = feishu_cfg.get("accounts", {})
214
- accounts["default"] = {"appId": feishu_app_id, "appSecret": feishu_app_secret}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  feishu_cfg["accounts"] = accounts
216
  channels_cfg["feishu"] = feishu_cfg
217
  config["channels"] = channels_cfg
 
218
  print(f"openclaw: configured feishu channel with app_id={feishu_app_id[:10]}...")
 
219
  config_path.parent.mkdir(parents=True, exist_ok=True)
220
  config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
221
  os.chmod(config_path, 0o600)
@@ -223,111 +372,227 @@ PY
223
  }
224
 
225
  write_backup_env_file() {
226
- local backup_env_file="/etc/openclaw-backup.env"
227
- [[ "$(id -u)" -ne 0 ]] && backup_env_file="${OPENCLAW_STATE_DIR}/openclaw-backup.env"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  mkdir -p "$(dirname "$backup_env_file")"
229
- : > "$backup_env_file"
230
- for key in HF_TOKEN HUGGINGFACE_HUB_TOKEN OPENCLAW_BACKUP_DATASET_REPO OPENCLAW_BACKUP_REPO_TYPE OPENCLAW_BACKUP_PATH_PREFIX OPENCLAW_BACKUP_LATEST_NAME OPENCLAW_BACKUP_KEEP_COUNT OPENCLAW_BACKUP_PRIVATE OPENCLAW_BACKUP_WORK_DIR OPENCLAW_BACKUP_SOURCE_DIR OPENCLAW_BACKUP_ROOT_CONFIG_DIR OPENCLAW_BACKUP_ROOT_CODEX_DIR OPENCLAW_BACKUP_ROOT_CLAUDE_DIR OPENCLAW_BACKUP_ROOT_AGENTS_DIR OPENCLAW_BACKUP_ROOT_SSH_DIR OPENCLAW_BACKUP_ROOT_NPM_DIR OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR OPENCLAW_STATE_DIR; do
 
 
 
 
231
  printf '%s=%q\n' "$key" "${!key:-}" >> "$backup_env_file"
232
  done
233
  OPENCLAW_BACKUP_ENV_FILE_PATH="$backup_env_file"
234
  export OPENCLAW_BACKUP_ENV_FILE_PATH
235
- [[ "$(id -u)" -eq 0 ]] && chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$backup_env_file" && chmod 640 "$backup_env_file" || chmod 600 "$backup_env_file"
 
 
 
 
 
 
236
  }
237
 
238
  setup_backup_cron() {
239
- [[ "$(id -u)" -ne 0 ]] && echo "openclaw: backup cron requires root; skip" >&2 && return 0
240
- is_true "$OPENCLAW_BACKUP_ENABLED" || return 0
241
- [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]] && echo "openclaw: backup enabled but OPENCLAW_BACKUP_DATASET_REPO is empty; skip cron" >&2 && return 0
242
- write_backup_env_file || { echo "openclaw: backup env file unavailable; skip cron" >&2; return 0; }
243
- cat > /etc/cron.d/openclaw-backup <<CRON
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  SHELL=/bin/bash
245
  PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
246
  OPENCLAW_BACKUP_ENV_FILE_PATH=${OPENCLAW_BACKUP_ENV_FILE_PATH}
247
- ${OPENCLAW_BACKUP_CRON} ${OPENCLAW_USER} /usr/local/bin/openclaw-backup-cron.sh >> /var/log/openclaw/backup.log 2>&1
248
- CRON
249
  chmod 0644 /etc/cron.d/openclaw-backup
250
  touch /var/log/openclaw/backup.log
251
  chown "$OPENCLAW_USER:$OPENCLAW_GROUP" /var/log/openclaw/backup.log
252
- pgrep -x cron >/dev/null 2>&1 || cron
 
 
 
 
 
253
  }
254
 
255
  restore_from_backup_on_startup() {
256
- [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]] && echo "openclaw: startup restore skipped (OPENCLAW_BACKUP_DATASET_REPO is empty)" >&2 && return 0
257
- write_backup_env_file || { echo "openclaw: backup env unavailable; skip restore" >&2; return 0; }
258
- /usr/local/bin/openclaw-restore.sh && echo "openclaw: restore finished" || echo "openclaw: restore skipped or failed, continue startup" >&2
 
 
 
 
 
 
 
 
 
 
 
259
  }
260
 
261
  run_as_node() {
262
  local cmd=("$@")
263
  if [[ "$(id -u)" -eq 0 ]]; then
264
- exec gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" "${cmd[@]}"
265
- else
266
- exec "${cmd[@]}"
267
  fi
 
268
  }
269
 
270
  run_as_node_background() {
271
  local cmd=("$@")
272
  if [[ "$(id -u)" -eq 0 ]]; then
273
- gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" "${cmd[@]}" &
274
  else
275
- "${cmd[@]}" &
276
  fi
277
  OPENCLAW_CHILD_PID="$!"
278
  }
279
 
280
  start_sshx_background() {
281
- is_true "$OPENCLAW_SSHX_AUTO_START" || return 0
282
- command -v sshx >/dev/null 2>&1 || { echo "openclaw: sshx not found; skip" >&2; return 0; }
 
 
 
 
 
 
 
283
  if [[ "$(id -u)" -eq 0 ]]; then
284
  gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" sshx >/proc/1/fd/1 2>/proc/1/fd/2 &
285
  else
286
  sshx >/proc/1/fd/1 2>/proc/1/fd/2 &
287
  fi
288
  OPENCLAW_SSHX_PID="$!"
289
- echo "openclaw: sshx started (pid=$OPENCLAW_SSHX_PID)"
290
  }
291
 
292
  stop_sshx_background() {
293
- [[ -n "$OPENCLAW_SSHX_PID" ]] && kill -0 "$OPENCLAW_SSHX_PID" 2>/dev/null && kill "$OPENCLAW_SSHX_PID" 2>/dev/null || true
 
 
 
 
294
  }
295
 
296
  backup_on_shutdown() {
297
- [[ "$OPENCLAW_SHUTDOWN_BACKUP_DONE" -eq 1 ]] && return 0
 
 
298
  OPENCLAW_SHUTDOWN_BACKUP_DONE=1
299
- [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]] && echo "openclaw: shutdown backup skipped (no repo)" >&2 && return 0
300
- write_backup_env_file || { echo "openclaw: env unavailable; skip shutdown backup" >&2; return 0; }
 
 
 
 
 
 
 
 
301
  echo "openclaw: running shutdown backup"
302
- /usr/local/bin/openclaw-backup-cron.sh && echo "openclaw: shutdown backup completed" || echo "openclaw: shutdown backup failed" >&2
 
 
 
 
303
  }
304
 
305
  run_gateway_with_shutdown_backup() {
306
  local gateway_exit_code=0
307
  local shutting_down=0
308
- on_signal() {
309
- [[ "$shutting_down" -eq 1 ]] && return 0
 
 
 
 
310
  shutting_down=1
311
- log_warn "received signal, preparing graceful shutdown"
312
- [[ -n "$OPENCLAW_CHILD_PID" ]] && kill -0 "$OPENCLAW_CHILD_PID" 2>/dev/null && kill "$OPENCLAW_CHILD_PID" 2>/dev/null || true
313
- wait "$OPENCLAW_CHILD_PID" 2>/dev/null; gateway_exit_code=$?
314
- log_info "gateway stopped with code ${gateway_exit_code}"
315
- stop_sshx_background; backup_on_shutdown; trap - TERM INT QUIT; exit "$gateway_exit_code"
 
 
 
 
 
 
 
 
 
316
  }
317
- trap 'on_signal TERM' TERM
318
- trap 'on_signal INT' INT
319
- trap 'on_signal QUIT' QUIT
 
 
320
  log_info "starting gateway process (bind=${OPENCLAW_GATEWAY_BIND}, port=${OPENCLAW_GATEWAY_PORT})"
321
- run_as_node_background openclaw gateway --allow-unconfigured --bind "$OPENCLAW_GATEWAY_BIND" --port "$OPENCLAW_GATEWAY_PORT" "$@"
322
  log_info "gateway process started (pid=${OPENCLAW_CHILD_PID})"
323
- wait "$OPENCLAW_CHILD_PID" 2>/dev/null; gateway_exit_code=$?
 
 
324
  log_info "gateway process exited (pid=${OPENCLAW_CHILD_PID}, code=${gateway_exit_code})"
325
- stop_sshx_background; backup_on_shutdown; trap - TERM INT QUIT; return "$gateway_exit_code"
 
 
 
 
326
  }
327
 
328
  main() {
329
- [[ "$#" -eq 0 ]] && set -- gateway
330
- local subcommand="$1"; shift || true
 
 
 
 
 
331
  if [[ "$subcommand" == "gateway" ]]; then
332
  log_info "starting gateway bootstrap sequence"
333
  run_step "prepare runtime directories" mkdir_state_dirs
@@ -340,9 +605,11 @@ main() {
340
  log_info "step ${OPENCLAW_STEP_INDEX} start: launch gateway"
341
  run_gateway_with_shutdown_backup "$@"
342
  local gateway_exit_code=$?
343
- [[ "$gateway_exit_code" -ne 0 ]] && log_warn "gateway exited with non-zero code ${gateway_exit_code}"
 
 
 
344
  log_info "step ${OPENCLAW_STEP_INDEX} done: launch gateway"
345
- return "$gateway_exit_code"
346
  else
347
  log_info "starting subcommand: openclaw ${subcommand}"
348
  run_as_node openclaw "$subcommand" "$@"
@@ -350,6 +617,3 @@ main() {
350
  }
351
 
352
  main "$@"
353
-
354
-
355
-
 
1
+
2
  #!/usr/bin/env bash
3
  set -euo pipefail
4
 
5
  OPENCLAW_USER="${OPENCLAW_USER:-root}"
6
  OPENCLAW_GROUP="${OPENCLAW_GROUP:-root}"
7
  OPENCLAW_HOME="${OPENCLAW_HOME:-/root}"
8
+ OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-/root/.openclaw}"
9
  OPENCLAW_CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR}/openclaw.json}"
10
  OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-${OPENCLAW_STATE_DIR}/workspace}"
11
  OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
 
45
  OPENCLAW_STEP_INDEX=0
46
  OPENCLAW_ENTRYPOINT_TAG="openclaw-entrypoint"
47
 
48
+ timestamp_utc() {
49
+ date -u +"%Y-%m-%dT%H:%M:%SZ"
50
+ }
51
+
52
+ log_info() {
53
+ printf '[%s] %s: %s\n' "$(timestamp_utc)" "$OPENCLAW_ENTRYPOINT_TAG" "$*"
54
+ }
55
+
56
+ log_warn() {
57
+ printf '[%s] %s: %s\n' "$(timestamp_utc)" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2
58
+ }
59
 
60
  run_step() {
61
+ local description="$1"
62
+ shift
63
  OPENCLAW_STEP_INDEX=$((OPENCLAW_STEP_INDEX + 1))
64
  log_info "step ${OPENCLAW_STEP_INDEX} start: ${description}"
65
  "$@"
 
67
  }
68
 
69
  is_true() {
70
+ local value
71
+ value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
72
  [[ "$value" == "1" || "$value" == "true" || "$value" == "yes" || "$value" == "on" ]]
73
  }
74
 
 
77
  openssl rand -hex 32
78
  else
79
  python3 - <<'PY'
80
+ import secrets
81
+ print(secrets.token_hex(32))
82
  PY
83
  fi
84
  }
 
95
 
96
  fix_permissions() {
97
  local path
98
+
99
+ if [[ "$(id -u)" -ne 0 ]]; then
100
+ return 0
101
+ fi
102
+
103
+ if [[ -e "$OPENCLAW_HOME" ]]; then
104
+ chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$OPENCLAW_HOME"
105
+ fi
106
+
107
+ for path in \
108
+ "$OPENCLAW_STATE_DIR" \
109
+ "$OPENCLAW_WORKSPACE_DIR" \
110
+ "$OPENCLAW_STDOUT_LOG_PATH" \
111
+ "$OPENCLAW_STDERR_LOG_PATH" \
112
+ "$(dirname "$OPENCLAW_STDOUT_LOG_PATH")" \
113
+ "$(dirname "$OPENCLAW_STDERR_LOG_PATH")"; do
114
+ if [[ -e "$path" ]]; then
115
+ chown -R "$OPENCLAW_USER:$OPENCLAW_GROUP" "$path"
116
+ fi
117
  done
118
  }
119
 
120
  write_or_update_config() {
121
+ local existing_token
122
+ existing_token=""
123
+
124
  if [[ -f "$OPENCLAW_CONFIG_PATH" ]]; then
125
  existing_token="$(python3 - <<'PY'
126
+ import json
127
+ import os
128
+
129
  config_path = os.environ["OPENCLAW_CONFIG_PATH"]
130
  try:
131
+ with open(config_path, "r", encoding="utf-8") as fh:
132
+ cfg = json.load(fh)
133
  token = cfg.get("gateway", {}).get("auth", {}).get("token", "")
134
  if isinstance(token, str):
135
  print(token.strip())
 
138
  PY
139
  )"
140
  fi
141
+
142
  if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
143
+ if [[ -n "$existing_token" ]]; then
144
+ OPENCLAW_GATEWAY_TOKEN="$existing_token"
145
+ else
146
+ OPENCLAW_GATEWAY_TOKEN="$(generate_gateway_token)"
147
+ fi
148
  fi
149
  export OPENCLAW_GATEWAY_TOKEN
150
 
 
165
  OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH="$OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH" \
166
  OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS="${OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS:-}" \
167
  python3 - <<'PY'
168
+ import json
169
+ import os
170
+ import sys
171
  from pathlib import Path
172
+
173
  config_path = Path(os.environ["OPENCLAW_CONFIG_PATH"])
174
  config = {}
175
  if config_path.exists():
176
+ try:
177
+ config = json.loads(config_path.read_text(encoding="utf-8"))
178
+ except Exception:
179
+ config = {}
180
+
181
  provider = os.environ.get("OPENCLAW_LLM_PROVIDER", "thirdparty").strip() or "thirdparty"
182
  model = (os.environ.get("OPENCLAW_LLM_MODEL") or "").strip()
183
  api = os.environ.get("OPENCLAW_LLM_API", "openai-completions").strip() or "openai-completions"
 
185
  key_env_name = os.environ.get("OPENCLAW_LLM_API_KEY_ENV", "OPENCLAW_LLM_API_KEY").strip() or "OPENCLAW_LLM_API_KEY"
186
  base_url_value = (os.environ.get(base_env_name) or "").strip()
187
  api_key_value = (os.environ.get(key_env_name) or "").strip()
188
+
189
  missing_llm_env = []
190
+ if not model:
191
+ missing_llm_env.append("OPENCLAW_LLM_MODEL")
192
+ if not base_url_value:
193
+ missing_llm_env.append(base_env_name)
194
+ if not api_key_value:
195
+ missing_llm_env.append(key_env_name)
196
  custom_llm_ready = not missing_llm_env
197
  model_ref = f"{provider}/{model}" if custom_llm_ready else ""
198
+
199
  if missing_llm_env:
200
+ print(
201
+ "openclaw: skip custom LLM model configuration because missing: "
202
+ + ", ".join(missing_llm_env),
203
+ file=sys.stderr,
204
+ )
205
+ sshx_auto_start = (os.environ.get("OPENCLAW_SSHX_AUTO_START") or "false").strip().lower() in {
206
+ "1",
207
+ "true",
208
+ "yes",
209
+ "on",
210
+ }
211
+ if not sshx_auto_start:
212
+ print(
213
+ "openclaw: if you want to configure LLM via sshx, consider setting OPENCLAW_SSHX_AUTO_START=true",
214
+ file=sys.stderr,
215
+ )
216
+
217
  fallback_allowed_origins = [f"http://127.0.0.1:{os.environ.get('OPENCLAW_GATEWAY_PORT', '7860')}"]
218
  allowed_origins = fallback_allowed_origins
219
  raw_allowed_origins = (os.environ.get("OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS") or "").strip()
220
  if raw_allowed_origins:
221
  try:
222
  parsed = json.loads(raw_allowed_origins)
223
+ if isinstance(parsed, list):
224
+ normalized = []
225
+ for value in parsed:
226
+ if isinstance(value, str):
227
+ item = value.strip()
228
+ if item:
229
+ normalized.append(item)
230
+ if normalized:
231
+ allowed_origins = normalized
232
+ except Exception:
233
+ pass
234
+
235
+ fallback_origin = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_ALLOW_HOST_HEADER_ORIGIN_FALLBACK") or "true").lower() in {
236
+ "1",
237
+ "true",
238
+ "yes",
239
+ "on",
240
+ }
241
+ allow_insecure_auth = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH") or "false").lower() in {
242
+ "1",
243
+ "true",
244
+ "yes",
245
+ "on",
246
+ }
247
+ disable_device_auth = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH") or "false").lower() in {
248
+ "1",
249
+ "true",
250
+ "yes",
251
+ "on",
252
+ }
253
+
254
+ gateway = config.get("gateway")
255
+ if not isinstance(gateway, dict):
256
+ gateway = {}
257
  gateway["mode"] = os.environ.get("OPENCLAW_INIT_GATEWAY_MODE", "local")
258
  gateway["bind"] = os.environ.get("OPENCLAW_GATEWAY_BIND", "lan")
259
+ auth = gateway.get("auth")
260
+ if not isinstance(auth, dict):
261
+ auth = {}
262
+
263
  token_value = (os.environ.get("OPENCLAW_GATEWAY_TOKEN") or "").strip()
264
  password_value = (os.environ.get("OPENCLAW_GATEWAY_PASSWORD") or "").strip()
265
+ if token_value:
266
+ auth["token"] = token_value
267
+ if password_value:
268
+ auth["password"] = password_value
269
+
270
  valid_auth_modes = {"none", "token", "password", "trusted-proxy"}
271
  mode_env = (os.environ.get("OPENCLAW_GATEWAY_AUTH_MODE") or "").strip().lower()
272
+ existing_mode = auth.get("mode")
273
+ if mode_env in valid_auth_modes:
274
+ auth["mode"] = mode_env
275
+ elif password_value and not token_value:
276
+ auth["mode"] = "password"
277
+ elif token_value and not password_value:
278
+ auth["mode"] = "token"
279
+ elif token_value and password_value:
280
+ auth["mode"] = "token"
281
+ elif isinstance(existing_mode, str) and existing_mode.strip().lower() in valid_auth_modes:
282
+ auth["mode"] = existing_mode.strip().lower()
283
+ else:
284
+ auth["mode"] = "token"
285
+
286
  gateway["auth"] = auth
287
  gateway.setdefault("controlUi", {})
288
  gateway["controlUi"]["allowedOrigins"] = allowed_origins
 
290
  gateway["controlUi"]["allowInsecureAuth"] = allow_insecure_auth
291
  gateway["controlUi"]["dangerouslyDisableDeviceAuth"] = disable_device_auth
292
  config["gateway"] = gateway
293
+
294
+ agents = config.get("agents")
295
+ if not isinstance(agents, dict):
296
+ agents = {}
297
+ defaults = agents.get("defaults")
298
+ if not isinstance(defaults, dict):
299
+ defaults = {}
300
  if custom_llm_ready:
301
  defaults["model"] = {"primary": model_ref}
302
+ allow_models = defaults.get("models")
303
+ if not isinstance(allow_models, dict):
304
+ allow_models = {}
305
  allow_models.setdefault(model_ref, {"alias": "Default"})
306
  defaults["models"] = allow_models
307
  agents["defaults"] = defaults
308
  config["agents"] = agents
309
+
310
  if custom_llm_ready:
311
+ models_cfg = config.get("models")
312
+ if not isinstance(models_cfg, dict):
313
+ models_cfg = {}
314
  models_cfg["mode"] = "merge"
315
+ providers = models_cfg.get("providers")
316
+ if not isinstance(providers, dict):
317
+ providers = {}
318
+ provider_cfg = providers.get(provider)
319
+ if not isinstance(provider_cfg, dict):
320
+ provider_cfg = {}
321
  provider_cfg["baseUrl"] = f"${{{base_env_name}}}"
322
  provider_cfg["apiKey"] = f"${{{key_env_name}}}"
323
  provider_cfg["api"] = api
 
325
  providers[provider] = provider_cfg
326
  models_cfg["providers"] = providers
327
  config["models"] = models_cfg
328
+
329
+ # ====== FEISHU CHANNEL CONFIGURATION ======
330
  feishu_app_id = (os.environ.get("FEISHU_APP_ID") or "").strip()
331
  feishu_app_secret = (os.environ.get("FEISHU_APP_SECRET") or "").strip()
332
+ feishu_domain = (os.environ.get("FEISHU_DOMAIN") or "feishu").strip()
333
+
334
  if feishu_app_id and feishu_app_secret:
335
+ channels_cfg = config.get("channels")
336
+ if not isinstance(channels_cfg, dict):
337
+ channels_cfg = {}
338
+
339
+ feishu_cfg = channels_cfg.get("feishu")
340
+ if not isinstance(feishu_cfg, dict):
341
+ feishu_cfg = {}
342
+
343
+ feishu_cfg["enabled"] = True
344
+ feishu_cfg["domain"] = feishu_domain
345
+ feishu_cfg["connectionMode"] = "websocket"
346
+ feishu_cfg["defaultAccount"] = "default"
347
+ feishu_cfg["dmPolicy"] = "open"
348
+ feishu_cfg["groupPolicy"] = "open"
349
+
350
+ accounts = feishu_cfg.get("accounts")
351
+ if not isinstance(accounts, dict):
352
+ accounts = {}
353
+
354
+ default_account = accounts.get("default")
355
+ if not isinstance(default_account, dict):
356
+ default_account = {}
357
+
358
+ default_account["appId"] = feishu_app_id
359
+ default_account["appSecret"] = feishu_app_secret
360
+
361
+ accounts["default"] = default_account
362
  feishu_cfg["accounts"] = accounts
363
  channels_cfg["feishu"] = feishu_cfg
364
  config["channels"] = channels_cfg
365
+
366
  print(f"openclaw: configured feishu channel with app_id={feishu_app_id[:10]}...")
367
+
368
  config_path.parent.mkdir(parents=True, exist_ok=True)
369
  config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
370
  os.chmod(config_path, 0o600)
 
372
  }
373
 
374
  write_backup_env_file() {
375
+ local backup_env_file
376
+ local keys=(
377
+ HF_TOKEN
378
+ HUGGINGFACE_HUB_TOKEN
379
+ OPENCLAW_BACKUP_DATASET_REPO
380
+ OPENCLAW_BACKUP_REPO_TYPE
381
+ OPENCLAW_BACKUP_PATH_PREFIX
382
+ OPENCLAW_BACKUP_LATEST_NAME
383
+ OPENCLAW_BACKUP_KEEP_COUNT
384
+ OPENCLAW_BACKUP_PRIVATE
385
+ OPENCLAW_BACKUP_WORK_DIR
386
+ OPENCLAW_BACKUP_SOURCE_DIR
387
+ OPENCLAW_BACKUP_ROOT_CONFIG_DIR
388
+ OPENCLAW_BACKUP_ROOT_CODEX_DIR
389
+ OPENCLAW_BACKUP_ROOT_CLAUDE_DIR
390
+ OPENCLAW_BACKUP_ROOT_AGENTS_DIR
391
+ OPENCLAW_BACKUP_ROOT_SSH_DIR
392
+ OPENCLAW_BACKUP_ROOT_NPM_DIR
393
+ OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR
394
+ OPENCLAW_STATE_DIR
395
+ )
396
+
397
+ backup_env_file="${OPENCLAW_BACKUP_ENV_FILE_PATH:-/etc/openclaw-backup.env}"
398
+ if [[ "$(id -u)" -ne 0 && "$backup_env_file" == "/etc/openclaw-backup.env" ]]; then
399
+ backup_env_file="${OPENCLAW_STATE_DIR}/openclaw-backup.env"
400
+ fi
401
+
402
  mkdir -p "$(dirname "$backup_env_file")"
403
+ if ! : > "$backup_env_file"; then
404
+ echo "openclaw: cannot write backup env file at $backup_env_file" >&2
405
+ return 1
406
+ fi
407
+
408
+ for key in "${keys[@]}"; do
409
  printf '%s=%q\n' "$key" "${!key:-}" >> "$backup_env_file"
410
  done
411
  OPENCLAW_BACKUP_ENV_FILE_PATH="$backup_env_file"
412
  export OPENCLAW_BACKUP_ENV_FILE_PATH
413
+
414
+ if [[ "$(id -u)" -eq 0 ]]; then
415
+ chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$backup_env_file"
416
+ chmod 640 "$backup_env_file"
417
+ else
418
+ chmod 600 "$backup_env_file"
419
+ fi
420
  }
421
 
422
  setup_backup_cron() {
423
+ if [[ "$(id -u)" -ne 0 ]]; then
424
+ echo "openclaw: backup cron requires root; skip cron setup" >&2
425
+ return 0
426
+ fi
427
+
428
+ if ! is_true "$OPENCLAW_BACKUP_ENABLED"; then
429
+ return 0
430
+ fi
431
+
432
+ if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then
433
+ echo "openclaw: backup enabled but OPENCLAW_BACKUP_DATASET_REPO is empty; skip cron" >&2
434
+ return 0
435
+ fi
436
+
437
+ if ! write_backup_env_file; then
438
+ echo "openclaw: backup env file unavailable; skip cron setup" >&2
439
+ return 0
440
+ fi
441
+
442
+ cat > /etc/cron.d/openclaw-backup <<EOFCRON
443
  SHELL=/bin/bash
444
  PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
445
  OPENCLAW_BACKUP_ENV_FILE_PATH=${OPENCLAW_BACKUP_ENV_FILE_PATH}
446
+ ${OPENCLAW_BACKUP_CRON} ${OPENCLAW_USER} python3 /usr/local/bin/backup_restore.py backup >> /var/log/openclaw/backup.log 2>&1
447
+ EOFCRON
448
  chmod 0644 /etc/cron.d/openclaw-backup
449
  touch /var/log/openclaw/backup.log
450
  chown "$OPENCLAW_USER:$OPENCLAW_GROUP" /var/log/openclaw/backup.log
451
+
452
+ if pgrep -x cron >/dev/null 2>&1; then
453
+ return 0
454
+ fi
455
+
456
+ cron
457
  }
458
 
459
  restore_from_backup_on_startup() {
460
+ if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then
461
+ echo "openclaw: startup restore skipped (OPENCLAW_BACKUP_DATASET_REPO is empty)" >&2
462
+ return 0
463
+ fi
464
+
465
+ if ! write_backup_env_file; then
466
+ echo "openclaw: backup env file unavailable; skip startup restore" >&2
467
+ return 0
468
+ fi
469
+ if python3 /usr/local/bin/backup_restore.py restore; then
470
+ echo "openclaw: restore finished"
471
+ else
472
+ echo "openclaw: restore skipped or failed, continue startup" >&2
473
+ fi
474
  }
475
 
476
  run_as_node() {
477
  local cmd=("$@")
478
  if [[ "$(id -u)" -eq 0 ]]; then
479
+ exec gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH"
 
 
480
  fi
481
+ exec "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH"
482
  }
483
 
484
  run_as_node_background() {
485
  local cmd=("$@")
486
  if [[ "$(id -u)" -eq 0 ]]; then
487
+ gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" &
488
  else
489
+ "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" &
490
  fi
491
  OPENCLAW_CHILD_PID="$!"
492
  }
493
 
494
  start_sshx_background() {
495
+ if ! is_true "$OPENCLAW_SSHX_AUTO_START"; then
496
+ return 0
497
+ fi
498
+
499
+ if ! command -v sshx >/dev/null 2>&1; then
500
+ echo "openclaw: OPENCLAW_SSHX_AUTO_START=true but sshx not found; skip auto start" >&2
501
+ return 0
502
+ fi
503
+
504
  if [[ "$(id -u)" -eq 0 ]]; then
505
  gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" sshx >/proc/1/fd/1 2>/proc/1/fd/2 &
506
  else
507
  sshx >/proc/1/fd/1 2>/proc/1/fd/2 &
508
  fi
509
  OPENCLAW_SSHX_PID="$!"
510
+ echo "openclaw: sshx started in background (pid=$OPENCLAW_SSHX_PID)"
511
  }
512
 
513
  stop_sshx_background() {
514
+ if [[ -n "$OPENCLAW_SSHX_PID" ]] && kill -0 "$OPENCLAW_SSHX_PID" >/dev/null 2>&1; then
515
+ echo "openclaw: stopping sshx background process (pid=$OPENCLAW_SSHX_PID)"
516
+ kill "$OPENCLAW_SSHX_PID" >/dev/null 2>&1 || true
517
+ wait "$OPENCLAW_SSHX_PID" >/dev/null 2>&1 || true
518
+ fi
519
  }
520
 
521
  backup_on_shutdown() {
522
+ if [[ "$OPENCLAW_SHUTDOWN_BACKUP_DONE" -eq 1 ]]; then
523
+ return 0
524
+ fi
525
  OPENCLAW_SHUTDOWN_BACKUP_DONE=1
526
+
527
+ if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then
528
+ echo "openclaw: shutdown backup skipped (OPENCLAW_BACKUP_DATASET_REPO is empty)" >&2
529
+ return 0
530
+ fi
531
+
532
+ if ! write_backup_env_file; then
533
+ echo "openclaw: backup env file unavailable; skip shutdown backup" >&2
534
+ return 0
535
+ fi
536
  echo "openclaw: running shutdown backup"
537
+ if python3 /usr/local/bin/backup_restore.py backup; then
538
+ echo "openclaw: shutdown backup completed"
539
+ else
540
+ echo "openclaw: shutdown backup failed, container will still exit" >&2
541
+ fi
542
  }
543
 
544
  run_gateway_with_shutdown_backup() {
545
  local gateway_exit_code=0
546
  local shutting_down=0
547
+
548
+ on_gateway_signal() {
549
+ local signal="$1"
550
+ if [[ "$shutting_down" -eq 1 ]]; then
551
+ return 0
552
+ fi
553
  shutting_down=1
554
+ log_warn "received ${signal}, preparing graceful shutdown"
555
+
556
+ if [[ -n "$OPENCLAW_CHILD_PID" ]] && kill -0 "$OPENCLAW_CHILD_PID" >/dev/null 2>&1; then
557
+ kill -s "$signal" "$OPENCLAW_CHILD_PID" >/dev/null 2>&1 || kill "$OPENCLAW_CHILD_PID" >/dev/null 2>&1 || true
558
+ if ! wait "$OPENCLAW_CHILD_PID"; then
559
+ gateway_exit_code=$?
560
+ fi
561
+ fi
562
+
563
+ log_info "gateway process stopped with code ${gateway_exit_code}"
564
+ stop_sshx_background
565
+ backup_on_shutdown
566
+ trap - TERM INT QUIT
567
+ exit "$gateway_exit_code"
568
  }
569
+
570
+ trap 'on_gateway_signal TERM' TERM
571
+ trap 'on_gateway_signal INT' INT
572
+ trap 'on_gateway_signal QUIT' QUIT
573
+
574
  log_info "starting gateway process (bind=${OPENCLAW_GATEWAY_BIND}, port=${OPENCLAW_GATEWAY_PORT})"
575
+ run_as_node_background openclaw gateway --skip-doctor --allow-unconfigured --bind "$OPENCLAW_GATEWAY_BIND" --port "$OPENCLAW_GATEWAY_PORT" "$@"
576
  log_info "gateway process started (pid=${OPENCLAW_CHILD_PID})"
577
+ if ! wait "$OPENCLAW_CHILD_PID"; then
578
+ gateway_exit_code=$?
579
+ fi
580
  log_info "gateway process exited (pid=${OPENCLAW_CHILD_PID}, code=${gateway_exit_code})"
581
+
582
+ stop_sshx_background
583
+ backup_on_shutdown
584
+ trap - TERM INT QUIT
585
+ return "$gateway_exit_code"
586
  }
587
 
588
  main() {
589
+ if [[ "$#" -eq 0 ]]; then
590
+ set -- gateway
591
+ fi
592
+
593
+ local subcommand="$1"
594
+ shift || true
595
+
596
  if [[ "$subcommand" == "gateway" ]]; then
597
  log_info "starting gateway bootstrap sequence"
598
  run_step "prepare runtime directories" mkdir_state_dirs
 
605
  log_info "step ${OPENCLAW_STEP_INDEX} start: launch gateway"
606
  run_gateway_with_shutdown_backup "$@"
607
  local gateway_exit_code=$?
608
+ if [[ "$gateway_exit_code" -ne 0 ]]; then
609
+ log_warn "gateway exited with non-zero code ${gateway_exit_code}"
610
+ return "$gateway_exit_code"
611
+ fi
612
  log_info "step ${OPENCLAW_STEP_INDEX} done: launch gateway"
 
613
  else
614
  log_info "starting subcommand: openclaw ${subcommand}"
615
  run_as_node openclaw "$subcommand" "$@"
 
617
  }
618
 
619
  main "$@"