Download scripts/openclaw-entrypoint.sh from SZFSPC/apple: direct link, hf CLI and curl.
- Browser
- Download file 46.7 kB
-
https://huggingface.co/spaces/SZFSPC/apple/resolve/main/scripts/openclaw-entrypoint.sh
- Command line
-
hf download hf://spaces/SZFSPC/apple/scripts/openclaw-entrypoint.sh
-
curl -L -o openclaw-entrypoint.sh https://huggingface.co/spaces/SZFSPC/apple/resolve/main/scripts/openclaw-entrypoint.sh
46.7 kB
| # ---- Force line-buffered stdout/stderr for docker logs ---- | |
| # In Docker/HF Space, stdout goes through a pipe and is FULLY buffered (4KB+). | |
| # If we exit 1 before the buffer fills, error output is LOST. | |
| # Re-exec ourselves through stdbuf -oL -eL to force line buffering. | |
| if [[ -z "${_OPENCLAW_UNBUFFERED:-}" ]]; then | |
| export _OPENCLAW_UNBUFFERED=1 | |
| if command -v stdbuf >/dev/null 2>&1; then | |
| exec stdbuf -oL -eL "$0" "$@" | |
| fi | |
| fi | |
| set -euo pipefail | |
| # Preserve all existing environment variables (from HF Space / os.environ) | |
| # before sourcing env files, so HF Space always takes full priority. | |
| declare -A _PRESERVED_ENV | |
| while IFS='=' read -r _key _value; do | |
| _PRESERVED_ENV["$_key"]="$_value" | |
| done < <(env) | |
| unset _key _value | |
| # Load environment from save-env.sh if available | |
| if [[ -f /etc/profile.d/openclaw-env.sh ]]; then | |
| # shellcheck source=/dev/null | |
| source /etc/profile.d/openclaw-env.sh | |
| fi | |
| # Restore all previously existing variables (HF Space takes priority over env files) | |
| for _var in "${!_PRESERVED_ENV[@]}"; do | |
| export "$_var"="${_PRESERVED_ENV[$_var]}" | |
| done | |
| unset _PRESERVED_ENV _var | |
| OPENCLAW_USER="${OPENCLAW_USER:-root}" | |
| OPENCLAW_GROUP="${OPENCLAW_GROUP:-root}" | |
| OPENCLAW_HOME="${OPENCLAW_HOME:-/root}" | |
| OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-/root/.openclaw}" | |
| OPENCLAW_CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR}/openclaw.json}" | |
| OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-${OPENCLAW_STATE_DIR}/workspace}" | |
| OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}" | |
| OPENCLAW_GATEWAY_PORT="${OPENCLAW_GATEWAY_PORT:-18789}" | |
| OPENCLAW_INIT_GATEWAY_MODE="${OPENCLAW_INIT_GATEWAY_MODE:-local}" | |
| OPENCLAW_GATEWAY_AUTH_MODE="${OPENCLAW_GATEWAY_AUTH_MODE:-token}" | |
| OPENCLAW_LLM_PROVIDER="${OPENCLAW_LLM_PROVIDER:-thirdparty}" | |
| OPENCLAW_LLM_MODEL="${OPENCLAW_LLM_MODEL:-}" | |
| OPENCLAW_LLM_API="${OPENCLAW_LLM_API:-openai-completions}" | |
| OPENCLAW_LLM_BASE_URL_ENV="${OPENCLAW_LLM_BASE_URL_ENV:-OPENCLAW_LLM_BASE_URL}" | |
| OPENCLAW_LLM_API_KEY_ENV="${OPENCLAW_LLM_API_KEY_ENV:-OPENCLAW_LLM_API_KEY}" | |
| OPENCLAW_BACKUP_ENABLED="${OPENCLAW_BACKUP_ENABLED:-false}" | |
| OPENCLAW_BACKUP_NPM_ENABLED="${OPENCLAW_BACKUP_NPM_ENABLED:-true}" | |
| OPENCLAW_RESTORE_NPM_ENABLED="${OPENCLAW_RESTORE_NPM_ENABLED:-true}" | |
| OPENCLAW_BACKUP_CRON="${OPENCLAW_BACKUP_CRON:-*/20 * * * *}" | |
| OPENCLAW_PRUNE_ENABLED="${OPENCLAW_PRUNE_ENABLED:-true}" | |
| OPENCLAW_PRUNE_CRON="${OPENCLAW_PRUNE_CRON:-0 */8 * * *}" | |
| OPENCLAW_BACKUP_SOURCE_DIR="${OPENCLAW_BACKUP_SOURCE_DIR:-${OPENCLAW_STATE_DIR}}" | |
| OPENCLAW_BACKUP_ROOT_CONFIG_DIR="${OPENCLAW_BACKUP_ROOT_CONFIG_DIR:-/root/.config}" | |
| OPENCLAW_BACKUP_ROOT_CODEX_DIR="${OPENCLAW_BACKUP_ROOT_CODEX_DIR:-/root/.codex}" | |
| OPENCLAW_BACKUP_ROOT_CLAUDE_DIR="${OPENCLAW_BACKUP_ROOT_CLAUDE_DIR:-/root/.claude}" | |
| OPENCLAW_BACKUP_ROOT_CARGO_DIR="${OPENCLAW_BACKUP_ROOT_CARGO_DIR:-/root/.cargo}" | |
| OPENCLAW_BACKUP_ROOT_PIP_DIR="${OPENCLAW_BACKUP_ROOT_PIP_DIR:-/root/.pip}" | |
| OPENCLAW_BACKUP_ROOT_RUSTUP_DIR="${OPENCLAW_BACKUP_ROOT_RUSTUP_DIR:-/root/.rustup}" | |
| OPENCLAW_BACKUP_ROOT_AGENTS_DIR="${OPENCLAW_BACKUP_ROOT_AGENTS_DIR:-/root/.agents}" | |
| OPENCLAW_BACKUP_ROOT_SSH_DIR="${OPENCLAW_BACKUP_ROOT_SSH_DIR:-/root/.ssh}" | |
| OPENCLAW_BACKUP_ROOT_ENV_DIR="${OPENCLAW_BACKUP_ROOT_ENV_DIR:-/root/.env.d}" | |
| OPENCLAW_BACKUP_ROOT_NPM_DIR="${OPENCLAW_BACKUP_ROOT_NPM_DIR:-/root/.npm}" | |
| OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR="${OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR:-/root/.lark-cli}" | |
| OPENCLAW_BACKUP_ENV_FILE_PATH="${OPENCLAW_BACKUP_ENV_FILE_PATH:-/root/.env.d/openclaw-backup.env}" | |
| OPENCLAW_STDOUT_LOG_PATH="${OPENCLAW_STDOUT_LOG_PATH:-/var/log/openclaw/gateway.stdout.log}" | |
| OPENCLAW_STDERR_LOG_PATH="${OPENCLAW_STDERR_LOG_PATH:-/var/log/openclaw/gateway.stderr.log}" | |
| OPENCLAW_SSHX_AUTO_START="${OPENCLAW_SSHX_AUTO_START:-false}" | |
| OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH="${OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH:-false}" | |
| OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH="${OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH:-false}" | |
| OPENCLAW_CHILD_PID="" | |
| OPENCLAW_SSHX_PID="" | |
| OPENCLAW_NEED_CONFIG_UPDATE=0 | |
| export HOME="$OPENCLAW_HOME" | |
| export OPENCLAW_HOME | |
| export OPENCLAW_STATE_DIR | |
| export OPENCLAW_CONFIG_PATH | |
| export OPENCLAW_BACKUP_ENV_FILE_PATH | |
| OPENCLAW_STEP_INDEX=0 | |
| OPENCLAW_ENTRYPOINT_TAG="openclaw-entrypoint" | |
| # External script dependencies | |
| # Required: scripts without which the gateway cannot complete its core flow | |
| OPENCLAW_REQUIRED_SCRIPTS=( | |
| "/usr/local/bin/openclaw-restore.sh" | |
| ) | |
| # Optional: cron / helper scripts. Missing β WARNING only, gateway still starts | |
| OPENCLAW_OPTIONAL_SCRIPTS=( | |
| "/usr/local/bin/openclaw-backup-cron.sh" | |
| "/usr/local/bin/openclaw-prune-cron.sh" | |
| ) | |
| timestamp_utc() { | |
| date -u +"%Y-%m-%dT%H:%M:%SZ" | |
| } | |
| log_info() { | |
| printf '[%s] [INFO] [PID:%s] %s: %s\n' "$(timestamp_utc)" "$$" "$OPENCLAW_ENTRYPOINT_TAG" "$*" | |
| } | |
| log_warn() { | |
| printf '[%s] [WARN] [PID:%s] %s: %s\n' "$(timestamp_utc)" "$$" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2 | |
| } | |
| log_error() { | |
| printf '[%s] [ERROR] [PID:%s] %s: %s\n' "$(timestamp_utc)" "$$" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2 | |
| } | |
| log_debug() { | |
| if is_true "${OPENCLAW_DEBUG:-false}"; then | |
| printf '[%s] [DEBUG] [PID:%s] %s: %s\n' "$(timestamp_utc)" "$$" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2 | |
| fi | |
| } | |
| run_step() { | |
| local description="$1" | |
| shift | |
| OPENCLAW_STEP_INDEX=$((OPENCLAW_STEP_INDEX + 1)) | |
| log_info "ββββββββββββββββββββββββββββββββββββββββ" | |
| log_info "STEP ${OPENCLAW_STEP_INDEX}: ${description} [STARTING]" | |
| log_info "Command: $*" | |
| log_info "Working Dir: $(pwd)" | |
| log_info "User: $(id -u):$(id -g) ($(whoami))" | |
| log_info "ββββββββββββββββββββββββββββββββββββββββ" | |
| local step_start_time | |
| step_start_time=$(date +%s) | |
| if "$@"; then | |
| local step_end_time | |
| step_end_time=$(date +%s) | |
| local step_duration=$((step_end_time - step_start_time)) | |
| log_info "β STEP ${OPENCLAW_STEP_INDEX} COMPLETED: ${description} (${step_duration}s)" | |
| log_info "ββββββββββββββββββββββββββββββββββββββββ" | |
| return 0 | |
| else | |
| local step_exit_code=$? | |
| local step_end_time | |
| step_end_time=$(date +%s) | |
| local step_duration=$((step_end_time - step_start_time)) | |
| log_error "ββββββββββββββββββββββββββββββββββββββββ" | |
| log_error "β STEP ${OPENCLAW_STEP_INDEX} FAILED: ${description}" | |
| log_error " Exit code: ${step_exit_code}" | |
| log_error " Duration: ${step_duration}s" | |
| log_error " Command: $*" | |
| log_error " Working Dir: $(pwd)" | |
| log_error " User: $(id -u):$(id -g) ($(whoami))" | |
| log_error " Environment snapshot:" | |
| log_error " OPENCLAW_STATE_DIR=${OPENCLAW_STATE_DIR:-}" | |
| log_error " OPENCLAW_HOME=${OPENCLAW_HOME:-}" | |
| log_error " OPENCLAW_BACKUP_DATASET_REPO=${OPENCLAW_BACKUP_DATASET_REPO:-}" | |
| log_error " OPENCLAW_STDOUT_LOG_PATH=${OPENCLAW_STDOUT_LOG_PATH:-}" | |
| log_error " OPENCLAW_STDERR_LOG_PATH=${OPENCLAW_STDERR_LOG_PATH:-}" | |
| log_error "ββββββββββββββββββββββββββββββββββββββββ" | |
| log_error "[DIAGNOSTIC] Dumping gateway stdout/stderr before exit:" | |
| if [[ -f "${OPENCLAW_STDOUT_LOG_PATH:-}" ]]; then | |
| log_error " --- gateway stdout (last 50 lines) ---" | |
| tail -n 50 "${OPENCLAW_STDOUT_LOG_PATH}" 2>/dev/null || true | |
| fi | |
| if [[ -f "${OPENCLAW_STDERR_LOG_PATH:-}" ]]; then | |
| log_error " --- gateway stderr (last 50 lines) ---" | |
| tail -n 50 "${OPENCLAW_STDERR_LOG_PATH}" 2>/dev/null || true | |
| fi | |
| if [[ -f "/var/log/openclaw/restore.log" ]]; then | |
| log_error " --- restore.log (last 50 lines) ---" | |
| tail -n 50 "/var/log/openclaw/restore.log" 2>/dev/null || true | |
| fi | |
| log_error "[DIAGNOSTIC] About to exit with code 1 β docker logs should capture above" | |
| exit 1 | |
| fi | |
| } | |
| is_true() { | |
| local value | |
| value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" | |
| [[ "$value" == "1" || "$value" == "true" || "$value" == "yes" || "$value" == "on" ]] | |
| } | |
| # Pre-flight checks for external dependencies | |
| preflight_checks() { | |
| log_info "=== Running Pre-flight Checks ===" | |
| local all_passed=true | |
| local missing_scripts=() | |
| # Check 1a: REQUIRED external scripts | |
| # - missing β FATAL | |
| # - exists but not executable β auto chmod +x, continue | |
| log_info "Checking required external scripts..." | |
| for script in "${OPENCLAW_REQUIRED_SCRIPTS[@]}"; do | |
| if [[ -f "$script" && -x "$script" ]]; then | |
| log_debug " β Found: $script" | |
| elif [[ -f "$script" ]]; then | |
| log_warn " β Found but not executable: $script β auto chmod +x" | |
| chmod +x "$script" 2>/dev/null || true | |
| else | |
| log_error " β Missing required script: $script" | |
| missing_scripts+=("$script (not found)") | |
| all_passed=false | |
| fi | |
| done | |
| # Check 1b: OPTIONAL external scripts (cron / helpers) β never fail startup | |
| log_info "Checking optional external scripts (WARNING only)..." | |
| for script in "${OPENCLAW_OPTIONAL_SCRIPTS[@]}"; do | |
| if [[ -f "$script" && -x "$script" ]]; then | |
| log_debug " β Found (optional): $script" | |
| elif [[ -f "$script" ]]; then | |
| log_warn " β Found but not executable: $script β auto chmod +x" | |
| chmod +x "$script" 2>/dev/null || true | |
| else | |
| log_warn " β Optional script not found: $script (continuing startup)" | |
| fi | |
| done | |
| # Check 2: Required commands | |
| log_info "Checking required commands..." | |
| local required_commands=("python3" "openclaw") | |
| for cmd in "${required_commands[@]}"; do | |
| if command -v "$cmd" >/dev/null 2>&1; then | |
| log_debug " β Found: $cmd" | |
| else | |
| log_error " β Missing required command: $cmd" | |
| all_passed=false | |
| fi | |
| done | |
| # Check 3: Optional commands (with warnings) | |
| log_info "Checking optional commands..." | |
| local optional_commands=("gosu" "sshx" "openssl") | |
| for cmd in "${optional_commands[@]}"; do | |
| if command -v "$cmd" >/dev/null 2>&1; then | |
| log_debug " β Found: $cmd" | |
| else | |
| log_warn " β Optional command not found: $cmd" | |
| fi | |
| done | |
| # Check 4: Environment validation | |
| log_info "Checking environment configuration..." | |
| if [[ -n "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then | |
| log_info " β Backup dataset configured: $OPENCLAW_BACKUP_DATASET_REPO" | |
| else | |
| log_info " βΉ Backup dataset not configured (optional)" | |
| fi | |
| if [[ -n "${OPENCLAW_LLM_MODEL:-}" ]]; then | |
| log_info " β LLM model configured: $OPENCLAW_LLM_MODEL" | |
| else | |
| log_info " βΉ LLM model not configured (optional)" | |
| fi | |
| # Summary | |
| if is_true "$all_passed"; then | |
| log_info "=== Pre-flight Checks Passed ===" | |
| return 0 | |
| else | |
| log_error "=== Pre-flight Checks Failed ===" | |
| if [[ ${#missing_scripts[@]} -gt 0 ]]; then | |
| log_error "Missing required scripts:" | |
| printf '%s\n' "${missing_scripts[@]}" | while read -r line; do | |
| log_error " - $line" | |
| done | |
| fi | |
| return 1 | |
| fi | |
| } | |
| generate_gateway_token() { | |
| if command -v openssl >/dev/null 2>&1; then | |
| openssl rand -hex 32 | |
| else | |
| python3 - <<'PY' | |
| import secrets | |
| print(secrets.token_hex(32)) | |
| PY | |
| fi | |
| } | |
| mkdir_state_dirs() { | |
| log_info " Creating state directories..." | |
| log_info " OPENCLAW_STATE_DIR=$OPENCLAW_STATE_DIR" | |
| log_info " OPENCLAW_WORKSPACE_DIR=$OPENCLAW_WORKSPACE_DIR" | |
| log_info " OPENCLAW_STDOUT_LOG_PATH=$OPENCLAW_STDOUT_LOG_PATH" | |
| log_info " OPENCLAW_STDERR_LOG_PATH=$OPENCLAW_STDERR_LOG_PATH" | |
| mkdir -p "$OPENCLAW_STATE_DIR" "$OPENCLAW_WORKSPACE_DIR" || true | |
| mkdir -p "$OPENCLAW_STATE_DIR/identity" || true | |
| mkdir -p "$OPENCLAW_STATE_DIR/agents/main/agent" || true | |
| mkdir -p "$OPENCLAW_STATE_DIR/agents/main/sessions" || true | |
| mkdir -p "$OPENCLAW_STATE_DIR/logs" || true | |
| local stdout_dir stderr_dir | |
| stdout_dir=$(dirname "$OPENCLAW_STDOUT_LOG_PATH" 2>/dev/null || echo "/var/log/openclaw") | |
| stderr_dir=$(dirname "$OPENCLAW_STDERR_LOG_PATH" 2>/dev/null || echo "/var/log/openclaw") | |
| mkdir -p "$stdout_dir" "$stderr_dir" || true | |
| touch "$OPENCLAW_STDOUT_LOG_PATH" "$OPENCLAW_STDERR_LOG_PATH" || true | |
| log_info " State directories created successfully" | |
| } | |
| fix_permissions() { | |
| local path | |
| if [[ "$(id -u)" -ne 0 ]]; then | |
| log_info " Not running as root, skipping chown" | |
| return 0 | |
| fi | |
| log_info " Fixing ownership (user=$OPENCLAW_USER, group=$OPENCLAW_GROUP)..." | |
| if [[ -e "$OPENCLAW_HOME" ]]; then | |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$OPENCLAW_HOME" || true | |
| fi | |
| for path in \ | |
| "$OPENCLAW_STATE_DIR" \ | |
| "$OPENCLAW_WORKSPACE_DIR" \ | |
| "$OPENCLAW_STDOUT_LOG_PATH" \ | |
| "$OPENCLAW_STDERR_LOG_PATH" \ | |
| "$(dirname "$OPENCLAW_STDOUT_LOG_PATH" 2>/dev/null || echo "/var/log/openclaw")" \ | |
| "$(dirname "$OPENCLAW_STDERR_LOG_PATH" 2>/dev/null || echo "/var/log/openclaw")"; do | |
| if [[ -e "$path" ]]; then | |
| chown -R "$OPENCLAW_USER:$OPENCLAW_GROUP" "$path" || true | |
| fi | |
| done | |
| log_info " Permissions fixed" | |
| } | |
| write_or_update_config() { | |
| local existing_token | |
| existing_token="" | |
| if [[ -f "$OPENCLAW_CONFIG_PATH" ]]; then | |
| existing_token="$(python3 - <<'PY' | |
| import json | |
| import os | |
| config_path = os.environ["OPENCLAW_CONFIG_PATH"] | |
| try: | |
| with open(config_path, "r", encoding="utf-8") as fh: | |
| cfg = json.load(fh) | |
| token = cfg.get("gateway", {}).get("auth", {}).get("token", "") | |
| if isinstance(token, str): | |
| print(token.strip()) | |
| except Exception: | |
| pass | |
| PY | |
| )" | |
| fi | |
| if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then | |
| if [[ -n "$existing_token" ]]; then | |
| OPENCLAW_GATEWAY_TOKEN="$existing_token" | |
| else | |
| OPENCLAW_GATEWAY_TOKEN="$(generate_gateway_token)" | |
| fi | |
| fi | |
| export OPENCLAW_GATEWAY_TOKEN | |
| OPENCLAW_GATEWAY_PORT="$OPENCLAW_GATEWAY_PORT" \ | |
| OPENCLAW_GATEWAY_BIND="$OPENCLAW_GATEWAY_BIND" \ | |
| OPENCLAW_INIT_GATEWAY_MODE="$OPENCLAW_INIT_GATEWAY_MODE" \ | |
| OPENCLAW_CONFIG_PATH="$OPENCLAW_CONFIG_PATH" \ | |
| OPENCLAW_GATEWAY_TOKEN="$OPENCLAW_GATEWAY_TOKEN" \ | |
| OPENCLAW_GATEWAY_PASSWORD="${OPENCLAW_GATEWAY_PASSWORD:-}" \ | |
| OPENCLAW_GATEWAY_AUTH_MODE="$OPENCLAW_GATEWAY_AUTH_MODE" \ | |
| OPENCLAW_LLM_PROVIDER="$OPENCLAW_LLM_PROVIDER" \ | |
| OPENCLAW_LLM_MODEL="$OPENCLAW_LLM_MODEL" \ | |
| OPENCLAW_LLM_API="$OPENCLAW_LLM_API" \ | |
| OPENCLAW_LLM_BASE_URL_ENV="$OPENCLAW_LLM_BASE_URL_ENV" \ | |
| OPENCLAW_LLM_API_KEY_ENV="$OPENCLAW_LLM_API_KEY_ENV" \ | |
| OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_ALLOW_HOST_HEADER_ORIGIN_FALLBACK="${OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_ALLOW_HOST_HEADER_ORIGIN_FALLBACK:-true}" \ | |
| OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH="$OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH" \ | |
| OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH="$OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH" \ | |
| OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS="${OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS:-}" \ | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import sys | |
| from pathlib import Path | |
| config_path = Path(os.environ["OPENCLAW_CONFIG_PATH"]) | |
| config = {} | |
| if config_path.exists(): | |
| try: | |
| config = json.loads(config_path.read_text(encoding="utf-8")) | |
| except Exception: | |
| config = {} | |
| provider = os.environ.get("OPENCLAW_LLM_PROVIDER", "thirdparty").strip() or "thirdparty" | |
| model = (os.environ.get("OPENCLAW_LLM_MODEL") or "").strip() | |
| api = os.environ.get("OPENCLAW_LLM_API", "openai-completions").strip() or "openai-completions" | |
| base_env_name = os.environ.get("OPENCLAW_LLM_BASE_URL_ENV", "OPENCLAW_LLM_BASE_URL").strip() or "OPENCLAW_LLM_BASE_URL" | |
| key_env_name = os.environ.get("OPENCLAW_LLM_API_KEY_ENV", "OPENCLAW_LLM_API_KEY").strip() or "OPENCLAW_LLM_API_KEY" | |
| base_url_value = (os.environ.get(base_env_name) or "").strip() | |
| api_key_value = (os.environ.get(key_env_name) or "").strip() | |
| missing_llm_env = [] | |
| if not model: | |
| missing_llm_env.append("OPENCLAW_LLM_MODEL") | |
| if not base_url_value: | |
| missing_llm_env.append(base_env_name) | |
| if not api_key_value: | |
| missing_llm_env.append(key_env_name) | |
| custom_llm_ready = not missing_llm_env | |
| model_ref = f"{provider}/{model}" if custom_llm_ready else "" | |
| if missing_llm_env: | |
| print( | |
| "openclaw: skip custom LLM model configuration because missing: " | |
| + ", ".join(missing_llm_env), | |
| file=sys.stderr, | |
| ) | |
| sshx_auto_start = (os.environ.get("OPENCLAW_SSHX_AUTO_START") or "false").strip().lower() in { | |
| "1", | |
| "true", | |
| "yes", | |
| "on", | |
| } | |
| if not sshx_auto_start: | |
| print( | |
| "openclaw: if you want to configure LLM via sshx, consider setting OPENCLAW_SSHX_AUTO_START=true", | |
| file=sys.stderr, | |
| ) | |
| fallback_allowed_origins = [f"http://127.0.0.1:{os.environ.get('OPENCLAW_GATEWAY_PORT', '18789')}"] | |
| allowed_origins = fallback_allowed_origins | |
| raw_allowed_origins = (os.environ.get("OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS") or "").strip() | |
| if raw_allowed_origins: | |
| try: | |
| parsed = json.loads(raw_allowed_origins) | |
| if isinstance(parsed, list): | |
| normalized = [] | |
| for value in parsed: | |
| if isinstance(value, str): | |
| item = value.strip() | |
| if item: | |
| normalized.append(item) | |
| if normalized: | |
| allowed_origins = normalized | |
| except Exception: | |
| pass | |
| fallback_origin = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_ALLOW_HOST_HEADER_ORIGIN_FALLBACK") or "true").lower() in { | |
| "1", | |
| "true", | |
| "yes", | |
| "on", | |
| } | |
| allow_insecure_auth = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH") or "false").lower() in { | |
| "1", | |
| "true", | |
| "yes", | |
| "on", | |
| } | |
| disable_device_auth = (os.environ.get("OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH") or "false").lower() in { | |
| "1", | |
| "true", | |
| "yes", | |
| "on", | |
| } | |
| gateway = config.get("gateway") | |
| if not isinstance(gateway, dict): | |
| gateway = {} | |
| gateway["mode"] = os.environ.get("OPENCLAW_INIT_GATEWAY_MODE", "local") | |
| gateway["bind"] = os.environ.get("OPENCLAW_GATEWAY_BIND", "lan") | |
| auth = gateway.get("auth") | |
| if not isinstance(auth, dict): | |
| auth = {} | |
| token_value = (os.environ.get("OPENCLAW_GATEWAY_TOKEN") or "").strip() | |
| password_value = (os.environ.get("OPENCLAW_GATEWAY_PASSWORD") or "").strip() | |
| if token_value: | |
| auth["token"] = token_value | |
| if password_value: | |
| auth["password"] = password_value | |
| valid_auth_modes = {"none", "token", "password", "trusted-proxy"} | |
| mode_env = (os.environ.get("OPENCLAW_GATEWAY_AUTH_MODE") or "").strip().lower() | |
| existing_mode = auth.get("mode") | |
| if mode_env in valid_auth_modes: | |
| auth["mode"] = mode_env | |
| elif password_value and not token_value: | |
| auth["mode"] = "password" | |
| elif token_value and not password_value: | |
| auth["mode"] = "token" | |
| elif token_value and password_value: | |
| auth["mode"] = "token" | |
| elif isinstance(existing_mode, str) and existing_mode.strip().lower() in valid_auth_modes: | |
| auth["mode"] = existing_mode.strip().lower() | |
| else: | |
| auth["mode"] = "token" | |
| gateway["auth"] = auth | |
| gateway.setdefault("controlUi", {}) | |
| gateway["controlUi"]["allowedOrigins"] = allowed_origins | |
| gateway["controlUi"]["dangerouslyAllowHostHeaderOriginFallback"] = fallback_origin | |
| gateway["controlUi"]["allowInsecureAuth"] = allow_insecure_auth | |
| gateway["controlUi"]["dangerouslyDisableDeviceAuth"] = disable_device_auth | |
| config["gateway"] = gateway | |
| agents = config.get("agents") | |
| if not isinstance(agents, dict): | |
| agents = {} | |
| defaults = agents.get("defaults") | |
| if not isinstance(defaults, dict): | |
| defaults = {} | |
| if custom_llm_ready: | |
| defaults["model"] = {"primary": model_ref} | |
| allow_models = defaults.get("models") | |
| if not isinstance(allow_models, dict): | |
| allow_models = {} | |
| allow_models.setdefault(model_ref, {"alias": "Default"}) | |
| defaults["models"] = allow_models | |
| agents["defaults"] = defaults | |
| config["agents"] = agents | |
| if custom_llm_ready: | |
| models_cfg = config.get("models") | |
| if not isinstance(models_cfg, dict): | |
| models_cfg = {} | |
| models_cfg["mode"] = "merge" | |
| providers = models_cfg.get("providers") | |
| if not isinstance(providers, dict): | |
| providers = {} | |
| provider_cfg = providers.get(provider) | |
| if not isinstance(provider_cfg, dict): | |
| provider_cfg = {} | |
| provider_cfg["baseUrl"] = f"${{{base_env_name}}}" | |
| provider_cfg["apiKey"] = f"${{{key_env_name}}}" | |
| provider_cfg["api"] = api | |
| provider_cfg["models"] = [{"id": model, "name": model}] | |
| providers[provider] = provider_cfg | |
| models_cfg["providers"] = providers | |
| config["models"] = models_cfg | |
| config_path.parent.mkdir(parents=True, exist_ok=True) | |
| config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") | |
| os.chmod(config_path, 0o600) | |
| PY | |
| } | |
| write_backup_env_file() { | |
| local backup_env_file | |
| local keys=( | |
| # Backup core config | |
| OPENCLAW_BACKUP_DATASET_REPO | |
| OPENCLAW_BACKUP_REPO_TYPE | |
| OPENCLAW_BACKUP_PATH_PREFIX | |
| OPENCLAW_BACKUP_ENABLED | |
| OPENCLAW_BACKUP_CRON | |
| OPENCLAW_BACKUP_SOURCE_DIR | |
| OPENCLAW_BACKUP_WORK_DIR | |
| # Multi-dataset backup/restore support | |
| OPENCLAW_RESTORE_DATASET_REPO | |
| OPENCLAW_BACKUP_NPM_ENABLED | |
| OPENCLAW_RESTORE_NPM_ENABLED | |
| # Backup encryption (enabled flag only, password from env) | |
| OPENCLAW_BACKUP_ENCRYPTION_ENABLED | |
| # Note: Container restart/rebuild always triggers restore | |
| # Backup root directories | |
| OPENCLAW_BACKUP_ROOT_CONFIG_DIR | |
| OPENCLAW_BACKUP_ROOT_CODEX_DIR | |
| OPENCLAW_BACKUP_ROOT_CLAUDE_DIR | |
| OPENCLAW_BACKUP_ROOT_CARGO_DIR | |
| OPENCLAW_BACKUP_ROOT_PIP_DIR | |
| OPENCLAW_BACKUP_ROOT_RUSTUP_DIR | |
| OPENCLAW_BACKUP_ROOT_AGENTS_DIR | |
| OPENCLAW_BACKUP_ROOT_SSH_DIR | |
| OPENCLAW_BACKUP_ROOT_ENV_DIR | |
| OPENCLAW_BACKUP_ROOT_NPM_DIR | |
| OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR | |
| # State and workspace | |
| OPENCLAW_STATE_DIR | |
| OPENCLAW_HOME | |
| OPENCLAW_WORKSPACE_DIR | |
| OPENCLAW_CONFIG_PATH | |
| # Incremental backup | |
| OPENCLAW_INCREMENTAL_BACKUP | |
| OPENCLAW_INCREMENTAL_INTERVAL_MINUTES | |
| # Dynamic backup | |
| OPENCLAW_DYNAMIC_BACKUP | |
| OPENCLAW_DYNAMIC_SMALL_THRESHOLD_MB | |
| OPENCLAW_DYNAMIC_MEDIUM_THRESHOLD_MB | |
| OPENCLAW_DYNAMIC_HIGH_CHANGE_RATE | |
| OPENCLAW_DYNAMIC_LOW_CHANGE_RATE | |
| OPENCLAW_DYNAMIC_MIN_CHANGED_FILES | |
| OPENCLAW_DYNAMIC_MIN_CHANGED_SIZE_KB | |
| # Full backup strategy | |
| OPENCLAW_FULL_BACKUP_INTERVAL_MINUTES | |
| OPENCLAW_MAX_INCREMENTAL_BACKUPS | |
| # Backup retention and compression | |
| OPENCLAW_BACKUP_KEEP_COUNT | |
| OPENCLAW_BACKUP_COMPRESSION_LEVEL | |
| OPENCLAW_BACKUP_SPLIT_SIZE | |
| OPENCLAW_BACKUP_SIZE_WARNING_MB | |
| OPENCLAW_BACKUP_PRIVATE | |
| # Health check | |
| OPENCLAW_BACKUP_HEALTH_CHECK_ENABLED | |
| OPENCLAW_BACKUP_HEALTH_CHECK_BEFORE | |
| OPENCLAW_BACKUP_HEALTH_CHECK_AFTER | |
| OPENCLAW_BACKUP_MAX_RETRIES | |
| # Watchdog | |
| WATCHDOG_INTERVAL | |
| MAX_BACKUP_AGE_MINUTES | |
| FORCE_BACKUP_INTERVAL | |
| # Restore | |
| OPENCLAW_RESTORE_TIMEOUT | |
| # Extra | |
| OPENCLAW_BACKUP_EXTRA_DIRS | |
| OPENCLAW_BACKUP_EXTRA_FILES | |
| ) | |
| backup_env_file="${OPENCLAW_BACKUP_ENV_FILE_PATH:-/root/.env.d/openclaw-backup.env}" | |
| if [[ "$(id -u)" -ne 0 && "$backup_env_file" == "/root/.env.d/openclaw-backup.env" ]]; then | |
| backup_env_file="${OPENCLAW_STATE_DIR}/openclaw-backup.env" | |
| fi | |
| log_info " Backup env file: $backup_env_file" | |
| local env_dir | |
| env_dir=$(dirname "$backup_env_file" 2>/dev/null || echo "/root/.env.d") | |
| mkdir -p "$env_dir" || true | |
| if ! : > "$backup_env_file" 2>/dev/null; then | |
| log_error "Cannot write backup env file at $backup_env_file (continuing)" | |
| return 0 | |
| fi | |
| for key in "${keys[@]}"; do | |
| local value="${!key:-}" | |
| if [[ -n "$value" ]]; then | |
| printf '%s=%q\n' "$key" "$value" >> "$backup_env_file" || true | |
| fi | |
| done | |
| OPENCLAW_BACKUP_ENV_FILE_PATH="$backup_env_file" | |
| export OPENCLAW_BACKUP_ENV_FILE_PATH | |
| if [[ "$(id -u)" -eq 0 ]]; then | |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$backup_env_file" 2>/dev/null || true | |
| chmod 640 "$backup_env_file" 2>/dev/null || true | |
| else | |
| chmod 600 "$backup_env_file" 2>/dev/null || true | |
| fi | |
| log_info " Backup env file written successfully" | |
| } | |
| setup_ssh_agent_autostart() { | |
| # Check if SSH agent auto-start is enabled | |
| if ! is_true "${OPENCLAW_SSH_AGENT_AUTOSTART:-false}"; then | |
| log_info "SSH agent auto-start is disabled; skipping setup" | |
| return 0 | |
| fi | |
| log_info "Setting up SSH agent auto-start..." | |
| # Ensure the autostart script exists | |
| local autostart_script="/usr/local/bin/ssh-agent-autostart.sh" | |
| if [[ ! -f "$autostart_script" ]]; then | |
| log_warn "SSH agent autostart script not found at $autostart_script" | |
| return 0 | |
| fi | |
| # Make sure the script is executable | |
| chmod +x "$autostart_script" 2>/dev/null || true | |
| # Add SSH hook to .bashrc if not already present | |
| local bashrc_file="$OPENCLAW_HOME/.bashrc" | |
| local ssh_hook_marker="# OPENCLAW_SSH_AGENT_AUTOSTART" | |
| if [[ -f "$bashrc_file" ]]; then | |
| if grep -q "$ssh_hook_marker" "$bashrc_file" 2>/dev/null; then | |
| log_info "SSH agent hook already present in .bashrc" | |
| else | |
| log_info "Adding SSH agent hook to .bashrc..." | |
| { | |
| echo "" | |
| echo "$ssh_hook_marker" | |
| if [[ -n "${OPENCLAW_SSH_BASHRC_HOOK:-}" ]]; then | |
| echo "$OPENCLAW_SSH_BASHRC_HOOK" | base64 -d 2>/dev/null || true | |
| else | |
| echo '# Auto-start SSH agent and load keys' | |
| echo 'if [ -f /usr/local/bin/ssh-agent-autostart.sh ]; then' | |
| echo ' source /usr/local/bin/ssh-agent-autostart.sh' | |
| echo 'fi' | |
| fi | |
| echo "$ssh_hook_marker" | |
| } >> "$bashrc_file" 2>/dev/null || true | |
| log_info "SSH agent hook added to .bashrc" | |
| fi | |
| else | |
| log_warn ".bashrc not found at $bashrc_file (creating minimal one)" | |
| touch "$bashrc_file" 2>/dev/null || true | |
| fi | |
| # Ensure /root/.ssh directory exists with correct permissions | |
| local ssh_dir="$OPENCLAW_HOME/.ssh" | |
| mkdir -p "$ssh_dir" 2>/dev/null || true | |
| chmod 700 "$ssh_dir" 2>/dev/null || true | |
| if [[ "$(id -u)" -eq 0 ]]; then | |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$ssh_dir" 2>/dev/null || true | |
| fi | |
| log_info "SSH agent auto-start setup complete" | |
| log_info "Place your SSH private keys in $ssh_dir and they will be automatically loaded" | |
| } | |
| setup_backup_cron() { | |
| if [[ "$(id -u)" -ne 0 ]]; then | |
| log_warn "[CRON] Backup cron requires root; skipping cron setup" | |
| return 0 | |
| fi | |
| if ! is_true "$OPENCLAW_BACKUP_ENABLED"; then | |
| log_info "[CRON] Backup is disabled; skipping cron setup" | |
| return 0 | |
| fi | |
| if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then | |
| log_warn "[CRON] Backup enabled but OPENCLAW_BACKUP_DATASET_REPO is empty; skipping cron" | |
| return 0 | |
| fi | |
| if ! write_backup_env_file; then | |
| log_warn "[CRON] Backup env file unavailable; skipping cron setup" | |
| return 0 | |
| fi | |
| log_info "[CRON] Setting up backup cron job" | |
| log_info " Schedule: ${OPENCLAW_BACKUP_CRON}" | |
| log_info " User: ${OPENCLAW_USER}" | |
| log_info " Script: /usr/local/bin/openclaw-backup-cron.sh" | |
| log_info " Log: /var/log/openclaw/backup.log" | |
| mkdir -p /var/log/openclaw /etc/cron.d || true | |
| touch /var/log/openclaw/backup.log /var/log/openclaw/restore.log || true | |
| cat > /etc/cron.d/openclaw-backup <<EOFCRON | |
| SHELL=/bin/bash | |
| PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin | |
| OPENCLAW_BACKUP_ENV_FILE_PATH=${OPENCLAW_BACKUP_ENV_FILE_PATH} | |
| ${OPENCLAW_BACKUP_CRON} ${OPENCLAW_USER} /usr/local/bin/openclaw-backup-cron.sh >> /var/log/openclaw/backup.log 2>&1 | |
| EOFCRON | |
| chmod 0644 /etc/cron.d/openclaw-backup 2>/dev/null || true | |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" /var/log/openclaw/backup.log 2>/dev/null || true | |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" /var/log/openclaw/restore.log 2>/dev/null || true | |
| log_info "[CRON] Backup cron job registered (cron daemon is managed separately)" | |
| } | |
| setup_prune_cron() { | |
| if [[ "$(id -u)" -ne 0 ]]; then | |
| log_warn "[CRON] Prune cron requires root; skipping cron setup" | |
| return 0 | |
| fi | |
| if ! is_true "$OPENCLAW_PRUNE_ENABLED"; then | |
| log_info "[CRON] Prune is disabled; skipping cron setup" | |
| return 0 | |
| fi | |
| if ! is_true "$OPENCLAW_BACKUP_ENABLED"; then | |
| log_info "[CRON] Backup is disabled; skipping prune cron setup" | |
| return 0 | |
| fi | |
| if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then | |
| log_warn "[CRON] Backup dataset repo not set; skipping prune cron setup" | |
| return 0 | |
| fi | |
| # Prune shares the backup env file (written by write_backup_env_file) | |
| if ! write_backup_env_file; then | |
| log_warn "[CRON] Backup env file unavailable; skipping prune cron setup" | |
| return 0 | |
| fi | |
| # Block prune if last restore failed (same logic as watchdog) | |
| local restore_log="/var/log/openclaw/restore.log" | |
| if [[ -f "/root/dataset-restore-failed" ]]; then | |
| log_warn "[CRON] Previous restore failed; skipping prune cron setup until next successful restore" | |
| return 0 | |
| fi | |
| if [[ -f "$restore_log" ]]; then | |
| local last_restore | |
| last_restore=$(grep -E "Starting restore|Restore completed|Restore failed" "$restore_log" 2>/dev/null | tail -1) | |
| if [[ "$last_restore" == *"Restore failed"* ]]; then | |
| log_warn "[CRON] Previous restore failed; skipping prune cron setup until next successful restore" | |
| return 0 | |
| fi | |
| fi | |
| log_info "[CRON] Setting up prune cron job" | |
| log_info " Schedule: ${OPENCLAW_PRUNE_CRON}" | |
| log_info " User: ${OPENCLAW_USER}" | |
| log_info " Script: /usr/local/bin/openclaw-prune-cron.sh" | |
| log_info " Log: /var/log/openclaw/prune-cron.log" | |
| mkdir -p /var/log/openclaw /etc/cron.d || true | |
| touch /var/log/openclaw/prune-cron.log || true | |
| cat > /etc/cron.d/openclaw-prune <<EOFCRON | |
| SHELL=/bin/bash | |
| PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin | |
| OPENCLAW_BACKUP_ENV_FILE_PATH=${OPENCLAW_BACKUP_ENV_FILE_PATH} | |
| ${OPENCLAW_PRUNE_CRON} ${OPENCLAW_USER} /usr/local/bin/openclaw-prune-cron.sh >> /var/log/openclaw/prune-cron.log 2>&1 | |
| EOFCRON | |
| chmod 0644 /etc/cron.d/openclaw-prune 2>/dev/null || true | |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" /var/log/openclaw/prune-cron.log 2>/dev/null || true | |
| log_info "[CRON] Prune cron job registered (cron daemon is managed separately)" | |
| } | |
| restore_from_backup_on_startup() { | |
| log_info "=== Starting Backup Restore ===" | |
| log_info "Backup Enabled: ${OPENCLAW_BACKUP_ENABLED:-}" | |
| log_info "Backup NPM Enabled: ${OPENCLAW_BACKUP_NPM_ENABLED:-}" | |
| if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then | |
| log_info "Backup restore skipped: OPENCLAW_BACKUP_DATASET_REPO is not configured" | |
| return 0 | |
| fi | |
| local restore_dataset="${OPENCLAW_RESTORE_DATASET_REPO:-$OPENCLAW_BACKUP_DATASET_REPO}" | |
| log_info "Restore source: $restore_dataset" | |
| log_info "Backup target: $OPENCLAW_BACKUP_DATASET_REPO" | |
| log_info "Note: Restore is always performed on container restart/rebuild" | |
| if ! write_backup_env_file; then | |
| log_error "Failed to write backup environment file" | |
| log_warn "Backup restore skipped due to configuration error (continuing startup)" | |
| return 0 | |
| fi | |
| log_info "Backup environment file: $OPENCLAW_BACKUP_ENV_FILE_PATH" | |
| local restore_start_time | |
| restore_start_time=$(date +%s) | |
| log_info "Calling restore script: /usr/local/bin/openclaw-restore.sh" | |
| if /usr/local/bin/openclaw-restore.sh; then | |
| local restore_end_time | |
| restore_end_time=$(date +%s) | |
| local restore_duration=$((restore_end_time - restore_start_time)) | |
| log_info "β Backup restore completed successfully from dataset: ${restore_dataset} (${restore_duration}s)" | |
| if [[ -f "/tmp/openclaw-restore-skipped-no-backup" ]]; then | |
| log_info "Restore skipped due to no existing backup (new dataset), will generate new config" | |
| rm -f /tmp/openclaw-restore-skipped-no-backup || true | |
| OPENCLAW_NEED_CONFIG_UPDATE=1 | |
| fi | |
| else | |
| local restore_exit_code=$? | |
| local restore_end_time | |
| restore_end_time=$(date +%s) | |
| local restore_duration=$((restore_end_time - restore_start_time)) | |
| if [[ "$restore_exit_code" -eq 2 ]]; then | |
| log_info "Backup restore skipped: no dataset configured (${restore_duration}s)" | |
| else | |
| log_error "ββββββββββββββββββββββββββββββββββββββββ" | |
| log_error "β BACKUP RESTORE FAILED" | |
| log_error " Dataset: ${restore_dataset}" | |
| log_error " Exit code: ${restore_exit_code}" | |
| log_error " Duration: ${restore_duration}s" | |
| log_error "ββββββββββββββββββββββββββββββββββββββββ" | |
| log_error "[DIAGNOSTIC] Dumping restore-related logs:" | |
| if [[ -f "/var/log/openclaw/restore.log" ]]; then | |
| log_error " --- restore.log (last 80 lines) ---" | |
| tail -n 80 "/var/log/openclaw/restore.log" 2>/dev/null || true | |
| fi | |
| if [[ -f "/var/log/hf-entrypoint/openclaw-gateway-stdout.log" ]]; then | |
| log_error " --- gateway-stdout.log (last 30 lines) ---" | |
| tail -n 30 "/var/log/hf-entrypoint/openclaw-gateway-stdout.log" 2>/dev/null || true | |
| fi | |
| if [[ -f "/var/log/hf-entrypoint/openclaw-gateway-stderr.log" ]]; then | |
| log_error " --- gateway-stderr.log (last 30 lines) ---" | |
| tail -n 30 "/var/log/hf-entrypoint/openclaw-gateway-stderr.log" 2>/dev/null || true | |
| fi | |
| log_error "ββββββββββββββββββββββββββββββββββββββββ" | |
| log_info "Requesting Space restart to retry restore..." | |
| local space_id="${OPENCLAW_HF_SPACE_ID:-}" | |
| if [[ -n "$space_id" ]] && command -v hf >/dev/null 2>&1; then | |
| log_info "Calling hf spaces restart $space_id..." | |
| hf spaces restart "$space_id" 2>&1 || true | |
| sleep 5 | |
| else | |
| if [[ -z "$space_id" ]]; then | |
| log_error "OPENCLAW_HF_SPACE_ID is not set, cannot restart Space automatically" | |
| else | |
| log_error "hf CLI not available, cannot restart Space" | |
| fi | |
| log_error "Please manually restart the Space" | |
| fi | |
| log_error "[DIAGNOSTIC] About to exit with code 1 β all diagnostic output above was flushed line-by-line" | |
| exit 1 | |
| fi | |
| fi | |
| } | |
| run_as_node() { | |
| local cmd=("$@") | |
| if [[ "$(id -u)" -eq 0 ]]; then | |
| exec gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" | |
| fi | |
| exec "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" | |
| } | |
| run_as_node_background() { | |
| local cmd=("$@") | |
| log_info "[FORK] Preparing to run command in background" | |
| log_info " User: $(id -u):$(id -g) ($(whoami))" | |
| log_info " Target User: $OPENCLAW_USER:$OPENCLAW_GROUP" | |
| log_info " Command: ${cmd[*]}" | |
| log_info " Log stdout: $OPENCLAW_STDOUT_LOG_PATH" | |
| log_info " Log stderr: $OPENCLAW_STDERR_LOG_PATH" | |
| if [[ "$(id -u)" -eq 0 ]]; then | |
| if command -v gosu >/dev/null 2>&1; then | |
| log_info "[FORK] Using gosu: gosu $OPENCLAW_USER:$OPENCLAW_GROUP ${cmd[*]}" | |
| gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" & | |
| else | |
| log_warn "[FORK] gosu not found, running without gosu" | |
| "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" & | |
| fi | |
| else | |
| log_info "[FORK] Not root, running directly: ${cmd[*]}" | |
| "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" & | |
| fi | |
| OPENCLAW_CHILD_PID="$!" | |
| log_info "[FORK] Child process started with PID: ${OPENCLAW_CHILD_PID}" | |
| } | |
| start_sshx_background() { | |
| if ! is_true "$OPENCLAW_SSHX_AUTO_START"; then | |
| return 0 | |
| fi | |
| if ! command -v sshx >/dev/null 2>&1; then | |
| log_warn "[SSHX] OPENCLAW_SSHX_AUTO_START=true but sshx not found; skipping" | |
| return 0 | |
| fi | |
| log_info "[SSHX] Starting sshx background service..." | |
| if [[ "$(id -u)" -eq 0 ]]; then | |
| gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" sshx >/proc/1/fd/1 2>/proc/1/fd/2 & | |
| else | |
| sshx >/proc/1/fd/1 2>/proc/1/fd/2 & | |
| fi | |
| OPENCLAW_SSHX_PID="$!" | |
| log_info "[SSHX] sshx started in background (pid=$OPENCLAW_SSHX_PID)" | |
| } | |
| stop_sshx_background() { | |
| if [[ -n "$OPENCLAW_SSHX_PID" ]] && kill -0 "$OPENCLAW_SSHX_PID" >/dev/null 2>&1; then | |
| log_info "[SSHX] Stopping sshx background process (pid=$OPENCLAW_SSHX_PID)" | |
| kill "$OPENCLAW_SSHX_PID" >/dev/null 2>&1 || true | |
| wait "$OPENCLAW_SSHX_PID" >/dev/null 2>&1 || true | |
| fi | |
| } | |
| OPENCLAW_MANAGER_PID_FILE="/var/run/openclaw/manager.pid" | |
| OPENCLAW_SKIP_SHUTDOWN_BACKUP_FILE="/var/run/openclaw/.skip_shutdown_backup" | |
| save_pids() { | |
| mkdir -p "$(dirname "$OPENCLAW_MANAGER_PID_FILE")" | |
| echo "$$" > "$OPENCLAW_MANAGER_PID_FILE" | |
| chmod 644 "$OPENCLAW_MANAGER_PID_FILE" | |
| } | |
| clear_pids() { | |
| rm -f "$OPENCLAW_MANAGER_PID_FILE" | |
| } | |
| backup_on_shutdown() { | |
| if [[ -f "$OPENCLAW_SKIP_SHUTDOWN_BACKUP_FILE" ]]; then | |
| log_debug "Shutdown backup skipped via flag file" | |
| return 0 | |
| fi | |
| mkdir -p "$(dirname "$OPENCLAW_SKIP_SHUTDOWN_BACKUP_FILE")" | |
| touch "$OPENCLAW_SKIP_SHUTDOWN_BACKUP_FILE" | |
| log_info "=== Starting Shutdown Backup ===" | |
| if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then | |
| log_info "Shutdown backup skipped: OPENCLAW_BACKUP_DATASET_REPO is not configured" | |
| return 0 | |
| fi | |
| log_info "Backup dataset: $OPENCLAW_BACKUP_DATASET_REPO" | |
| if ! write_backup_env_file; then | |
| log_error "Failed to write backup environment file" | |
| log_warn "Shutdown backup skipped due to configuration error" | |
| return 0 | |
| fi | |
| log_debug "Backup environment file written: $OPENCLAW_BACKUP_ENV_FILE_PATH" | |
| local backup_start_time | |
| backup_start_time=$(date +%s) | |
| log_info "Executing shutdown backup..." | |
| if /usr/local/bin/openclaw-backup-cron.sh; then | |
| local backup_end_time | |
| backup_end_time=$(date +%s) | |
| local backup_duration=$((backup_end_time - backup_start_time)) | |
| log_info "β Shutdown backup completed successfully (${backup_duration}s)" | |
| else | |
| local backup_exit_code=$? | |
| log_error "β Shutdown backup failed (exit code: $backup_exit_code)" | |
| log_warn "Container will exit despite backup failure" | |
| fi | |
| } | |
| run_gateway_with_shutdown_backup() { | |
| log_info "ββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| log_info "Gateway Process Manager [PID:$$]" | |
| log_info "Parent PID: $PPID" | |
| log_info "Gateway Binary: $(command -v openclaw || echo 'NOT FOUND')" | |
| log_info "Gateway Config: ${OPENCLAW_CONFIG_PATH}" | |
| log_info "Gateway Log: stdout=${OPENCLAW_STDOUT_LOG_PATH}" | |
| log_info " stderr=${OPENCLAW_STDERR_LOG_PATH}" | |
| log_info "Gateway Port: ${OPENCLAW_GATEWAY_PORT}" | |
| log_info "Gateway Bind: ${OPENCLAW_GATEWAY_BIND}" | |
| log_info "Config Exists: $([ -f "$OPENCLAW_CONFIG_PATH" ] && echo 'yes' || echo 'no')" | |
| if [ -f "$OPENCLAW_CONFIG_PATH" ]; then | |
| log_info "Config Size: $(stat -c%s "$OPENCLAW_CONFIG_PATH" 2>/dev/null || echo 'unknown') bytes" | |
| fi | |
| log_info "ββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| local shutting_down=0 | |
| local need_restart=0 | |
| local gateway_exit_code=0 | |
| wait_for_gateway_ready() { | |
| local max_wait=60 | |
| local waited=0 | |
| log_info "[CHECKPOINT] Waiting for gateway to be ready..." | |
| log_info " Health URL: http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/health" | |
| log_info " Max Wait: ${max_wait}s" | |
| while [[ $waited -lt $max_wait ]]; do | |
| if curl -sf "http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/health" >/dev/null 2>&1; then | |
| log_info "[CHECKPOINT] Gateway is ready after ${waited}s" | |
| return 0 | |
| fi | |
| sleep 1 | |
| waited=$((waited + 1)) | |
| done | |
| log_warn "[CHECKPOINT] Gateway ready check timed out after ${max_wait}s, continuing anyway" | |
| return 1 | |
| } | |
| start_gateway() { | |
| log_info "[CHECKPOINT] Starting gateway process..." | |
| log_info " bind: ${OPENCLAW_GATEWAY_BIND}" | |
| log_info " port: ${OPENCLAW_GATEWAY_PORT}" | |
| log_info " config: ${OPENCLAW_CONFIG_PATH}" | |
| run_as_node_background openclaw gateway --allow-unconfigured --bind "$OPENCLAW_GATEWAY_BIND" --port "$OPENCLAW_GATEWAY_PORT" "$@" | |
| log_info "[FORK] Gateway child process PID: ${OPENCLAW_CHILD_PID}" | |
| log_info "[FORK] Command: openclaw gateway --allow-unconfigured --bind ${OPENCLAW_GATEWAY_BIND} --port ${OPENCLAW_GATEWAY_PORT}" | |
| save_pids | |
| } | |
| stop_gateway() { | |
| local sig="${1:-TERM}" | |
| local gateway_pid | |
| gateway_pid=$(lsof -ti :"${OPENCLAW_GATEWAY_PORT:-18789}" -sTCP:LISTEN 2>/dev/null | head -1 || true) | |
| if [[ -n "$gateway_pid" ]]; then | |
| local comm | |
| comm=$(ps -p "$gateway_pid" -o comm= 2>/dev/null || true) | |
| if [[ "$comm" != *"openclaw"* ]]; then | |
| gateway_pid="" | |
| fi | |
| fi | |
| log_info "[SIGNAL] Stopping gateway (sig=${sig}, pid=${gateway_pid:-none})" | |
| if [[ -n "$gateway_pid" ]]; then | |
| kill -s "$sig" "$gateway_pid" 2>/dev/null || true | |
| sleep 1 | |
| if kill -0 "$gateway_pid" 2>/dev/null; then | |
| log_info "[SIGNAL] Force killing gateway (sig=KILL)" | |
| kill -9 "$gateway_pid" 2>/dev/null || true | |
| fi | |
| fi | |
| } | |
| on_manager_signal() { | |
| local signal="$1" | |
| log_warn "[SIGNAL] Process manager received: ${signal}" | |
| case "$signal" in | |
| USR1) | |
| log_info "[SIGNAL] SIGUSR1: will restart gateway after current child exits" | |
| need_restart=1 | |
| stop_gateway TERM | |
| ;; | |
| TERM|INT|QUIT) | |
| if [[ "$shutting_down" -eq 1 ]]; then | |
| return 0 | |
| fi | |
| shutting_down=1 | |
| log_warn "[SIGNAL] Shutdown initiated, stopping gateway..." | |
| stop_gateway TERM | |
| stop_sshx_background | |
| backup_on_shutdown | |
| clear_pids | |
| rm -f "$OPENCLAW_MANAGER_PID_FILE" | |
| trap - TERM INT QUIT USR1 | |
| exit 0 | |
| ;; | |
| *) | |
| ;; | |
| esac | |
| } | |
| trap 'on_manager_signal USR1' USR1 | |
| trap 'on_manager_signal TERM' TERM | |
| trap 'on_manager_signal INT' INT | |
| trap 'on_manager_signal QUIT' QUIT | |
| start_gateway "$@" | |
| wait_for_gateway_ready | |
| while true; do | |
| local gateway_pid | |
| gateway_pid=$(lsof -ti :"${OPENCLAW_GATEWAY_PORT:-18789}" -sTCP:LISTEN 2>/dev/null | head -1 || true) | |
| if [[ -n "$gateway_pid" ]]; then | |
| local comm | |
| comm=$(ps -p "$gateway_pid" -o comm= 2>/dev/null || true) | |
| if [[ "$comm" != *"openclaw"* ]]; then | |
| gateway_pid="" | |
| fi | |
| fi | |
| if [[ -z "$gateway_pid" ]]; then | |
| log_info "[MONITOR] Gateway process not running" | |
| else | |
| log_info "[MONITOR] Waiting for gateway process (pid=${gateway_pid})..." | |
| while kill -0 "$gateway_pid" 2>/dev/null; do | |
| sleep 1 | |
| done | |
| fi | |
| gateway_exit_code=$? | |
| log_info "[MONITOR] Gateway process exited (exit_code=${gateway_exit_code})" | |
| if [[ "$shutting_down" -eq 1 ]]; then | |
| log_info "[MONITOR] Shutdown in progress, stopping services..." | |
| stop_sshx_background | |
| backup_on_shutdown | |
| clear_pids | |
| rm -f "$OPENCLAW_MANAGER_PID_FILE" | |
| trap - TERM INT QUIT USR1 | |
| exit 0 | |
| fi | |
| if [[ "$need_restart" -eq 1 ]]; then | |
| log_info "[MONITOR] Restart signal received, restarting gateway..." | |
| need_restart=0 | |
| start_gateway "$@" | |
| wait_for_gateway_ready | |
| else | |
| log_warn "[MONITOR] Gateway exited unexpectedly (exit_code=${gateway_exit_code})" | |
| log_info "[MONITOR] Waiting for restart signal (SIGUSR1)..." | |
| while true; do | |
| sleep 1 | |
| if [[ "$need_restart" -eq 1 ]]; then | |
| log_info "[MONITOR] Restart signal received" | |
| need_restart=0 | |
| break | |
| fi | |
| if [[ "$shutting_down" -eq 1 ]]; then | |
| log_info "[MONITOR] Shutdown signal received" | |
| stop_sshx_background | |
| backup_on_shutdown | |
| clear_pids | |
| rm -f "$OPENCLAW_MANAGER_PID_FILE" | |
| trap - TERM INT QUIT USR1 | |
| exit 0 | |
| fi | |
| done | |
| start_gateway "$@" | |
| wait_for_gateway_ready | |
| fi | |
| done | |
| } | |
| main() { | |
| log_info "ββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| log_info "OpenClaw Gateway Entrypoint [PID:$$]" | |
| log_info "ββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| log_info "Version: ${OPENCLAW_VERSION:-unknown}" | |
| log_info "User: $OPENCLAW_USER (UID: $(id -u))" | |
| log_info "Home: $OPENCLAW_HOME" | |
| log_info "State Dir: $OPENCLAW_STATE_DIR" | |
| log_info "Config Path: $OPENCLAW_CONFIG_PATH" | |
| log_info "Workspace Dir: $OPENCLAW_WORKSPACE_DIR" | |
| log_info "Entrypoint Args: $*" | |
| if ! preflight_checks; then | |
| log_error "Pre-flight checks failed. Aborting startup." | |
| exit 1 | |
| fi | |
| if [[ "$#" -eq 0 ]]; then | |
| set -- gateway | |
| fi | |
| local subcommand="$1" | |
| shift || true | |
| if [[ "$subcommand" == "gateway" ]]; then | |
| log_info "" | |
| log_info "ββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| log_info "Starting Gateway Bootstrap Sequence" | |
| log_info "ββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| save_pids | |
| run_step "prepare runtime directories" mkdir_state_dirs | |
| run_step "restore state from backup (if configured)" restore_from_backup_on_startup | |
| if [[ "$OPENCLAW_NEED_CONFIG_UPDATE" -eq 1 ]]; then | |
| log_info "Config update required: no previous backup found" | |
| else | |
| log_info "Config update: applying environment settings (preserving existing config)" | |
| fi | |
| run_step "write or update gateway config" write_or_update_config | |
| run_step "fix file ownership and permissions" fix_permissions | |
| run_step "setup SSH agent auto-start (if configured)" setup_ssh_agent_autostart | |
| run_step "setup backup cron (if enabled)" setup_backup_cron | |
| run_step "setup prune cron (if enabled)" setup_prune_cron | |
| run_step "post-restore openclaw operations" /usr/local/bin/openclaw-post-restore.sh | |
| run_step "start sshx background service (if enabled)" start_sshx_background | |
| OPENCLAW_STEP_INDEX=$((OPENCLAW_STEP_INDEX + 1)) | |
| log_info "ββββββββββββββββββββββββββββββββββββββββ" | |
| log_info "STEP ${OPENCLAW_STEP_INDEX}: launch gateway process manager" | |
| log_info "ββββββββββββββββββββββββββββββββββββββββ" | |
| run_gateway_with_shutdown_backup "$@" | |
| else | |
| log_info "Executing subcommand: openclaw ${subcommand} $*" | |
| run_as_node openclaw "$subcommand" "$@" | |
| fi | |
| } | |
| main "$@" | |