Update scripts/openclaw-entrypoint.sh
Browse files- 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() {
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
run_step() {
|
| 52 |
-
local description="$1"
|
|
|
|
| 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
|
|
|
|
| 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
|
|
|
|
| 70 |
PY
|
| 71 |
fi
|
| 72 |
}
|
|
@@ -83,22 +95,41 @@ mkdir_state_dirs() {
|
|
| 83 |
|
| 84 |
fix_permissions() {
|
| 85 |
local path
|
| 86 |
-
|
| 87 |
-
[[
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 98 |
config_path = os.environ["OPENCLAW_CONFIG_PATH"]
|
| 99 |
try:
|
| 100 |
-
with open(config_path) as
|
| 101 |
-
cfg = json.load(
|
| 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" ]]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 133 |
from pathlib import Path
|
|
|
|
| 134 |
config_path = Path(os.environ["OPENCLAW_CONFIG_PATH"])
|
| 135 |
config = {}
|
| 136 |
if config_path.exists():
|
| 137 |
-
try:
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 148 |
-
|
| 149 |
-
if not
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
| 174 |
valid_auth_modes = {"none", "token", "password", "trusted-proxy"}
|
| 175 |
mode_env = (os.environ.get("OPENCLAW_GATEWAY_AUTH_MODE") or "").strip().lower()
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
mkdir -p "$(dirname "$backup_env_file")"
|
| 229 |
-
: > "$backup_env_file"
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
}
|
| 237 |
|
| 238 |
setup_backup_cron() {
|
| 239 |
-
[[ "$(id -u)" -ne 0 ]]
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/
|
| 248 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
}
|
| 254 |
|
| 255 |
restore_from_backup_on_startup() {
|
| 256 |
-
[[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
}
|
| 295 |
|
| 296 |
backup_on_shutdown() {
|
| 297 |
-
[[ "$OPENCLAW_SHUTDOWN_BACKUP_DONE" -eq 1 ]]
|
|
|
|
|
|
|
| 298 |
OPENCLAW_SHUTDOWN_BACKUP_DONE=1
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
echo "openclaw: running shutdown backup"
|
| 302 |
-
/usr/local/bin/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
}
|
| 304 |
|
| 305 |
run_gateway_with_shutdown_backup() {
|
| 306 |
local gateway_exit_code=0
|
| 307 |
local shutting_down=0
|
| 308 |
-
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
shutting_down=1
|
| 311 |
-
log_warn "received signal, preparing graceful shutdown"
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
}
|
| 317 |
-
|
| 318 |
-
trap '
|
| 319 |
-
trap '
|
|
|
|
|
|
|
| 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"
|
|
|
|
|
|
|
| 324 |
log_info "gateway process exited (pid=${OPENCLAW_CHILD_PID}, code=${gateway_exit_code})"
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
}
|
| 327 |
|
| 328 |
main() {
|
| 329 |
-
[[ "$#" -eq 0 ]]
|
| 330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ]]
|
|
|
|
|
|
|
|
|
|
| 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 "$@"
|
|
|
|
|
|
|
|
|