| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| |
| |
| _HF_SPACE_VARS=( |
| "OPENCLAW_BACKUP_DATASET_REPO" |
| "OPENCLAW_RESTORE_DATASET_REPO" |
| "OPENCLAW_BACKUP_ENABLED" |
| "OPENCLAW_BACKUP_NPM_ENABLED" |
| "OPENCLAW_RESTORE_NPM_ENABLED" |
| "OPENCLAW_VERSION" |
| "HF_TOKEN" |
| "HF_STORAGE_REPO" |
| ) |
| declare -A _PRESERVED_HF_VARS |
| for _var in "${_HF_SPACE_VARS[@]}"; do |
| if [[ -n "${!_var:-}" ]]; then |
| _PRESERVED_HF_VARS["$_var"]="${!_var}" |
| fi |
| done |
|
|
| |
| if [[ -f /etc/profile.d/openclaw-env.sh ]]; then |
| |
| source /etc/profile.d/openclaw-env.sh |
| fi |
|
|
| |
| for _var in "${!_PRESERVED_HF_VARS[@]}"; do |
| export "$_var"="${_PRESERVED_HF_VARS[$_var]}" |
| done |
| unset _HF_SPACE_VARS _PRESERVED_HF_VARS _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:-*/12 * * * *}" |
| 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_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" |
|
|
| |
| OPENCLAW_REQUIRED_SCRIPTS=( |
| "/usr/local/bin/openclaw-restore.sh" |
| "/usr/local/bin/openclaw-backup-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 "β STEP ${OPENCLAW_STEP_INDEX} FAILED: ${description} (exit code: $step_exit_code, duration: ${step_duration}s)" |
| log_error "Last command output may be found in logs above" |
| return $step_exit_code |
| fi |
| } |
|
|
| is_true() { |
| local value |
| value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" |
| [[ "$value" == "1" || "$value" == "true" || "$value" == "yes" || "$value" == "on" ]] |
| } |
|
|
| |
| preflight_checks() { |
| log_info "=== Running Pre-flight Checks ===" |
| local all_passed=true |
| local missing_scripts=() |
|
|
| |
| log_info "Checking external script dependencies..." |
| 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" |
| missing_scripts+=("$script (not executable)") |
| all_passed=false |
| else |
| log_warn " β Missing: $script" |
| missing_scripts+=("$script (not found)") |
| all_passed=false |
| fi |
| done |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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 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() { |
| mkdir -p "$OPENCLAW_STATE_DIR" "$OPENCLAW_WORKSPACE_DIR" |
| mkdir -p "$OPENCLAW_STATE_DIR/identity" |
| mkdir -p "$OPENCLAW_STATE_DIR/agents/main/agent" |
| mkdir -p "$OPENCLAW_STATE_DIR/agents/main/sessions" |
| mkdir -p "$OPENCLAW_STATE_DIR/logs" |
| mkdir -p "$(dirname "$OPENCLAW_STDOUT_LOG_PATH")" "$(dirname "$OPENCLAW_STDERR_LOG_PATH")" |
| touch "$OPENCLAW_STDOUT_LOG_PATH" "$OPENCLAW_STDERR_LOG_PATH" |
| } |
|
|
| fix_permissions() { |
| local path |
|
|
| if [[ "$(id -u)" -ne 0 ]]; then |
| return 0 |
| fi |
|
|
| if [[ -e "$OPENCLAW_HOME" ]]; then |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$OPENCLAW_HOME" |
| fi |
|
|
| 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 |
| if [[ -e "$path" ]]; then |
| chown -R "$OPENCLAW_USER:$OPENCLAW_GROUP" "$path" |
| fi |
| done |
|
|
| } |
|
|
| 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=( |
| |
| 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 |
| |
| OPENCLAW_RESTORE_DATASET_REPO |
| OPENCLAW_BACKUP_NPM_ENABLED |
| OPENCLAW_RESTORE_NPM_ENABLED |
| |
| OPENCLAW_BACKUP_ENCRYPTION_ENABLED |
| |
| |
| 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_ENV_DIR |
| OPENCLAW_BACKUP_ROOT_NPM_DIR |
| OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR |
| |
| OPENCLAW_STATE_DIR |
| OPENCLAW_HOME |
| OPENCLAW_WORKSPACE_DIR |
| OPENCLAW_CONFIG_PATH |
| |
| OPENCLAW_INCREMENTAL_BACKUP |
| OPENCLAW_INCREMENTAL_INTERVAL_MINUTES |
| |
| 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 |
| |
| OPENCLAW_FULL_BACKUP_INTERVAL_HOURS |
| OPENCLAW_MAX_INCREMENTAL_BACKUPS |
| |
| OPENCLAW_BACKUP_KEEP_COUNT |
| OPENCLAW_BACKUP_COMPRESSION_LEVEL |
| OPENCLAW_BACKUP_SPLIT_SIZE |
| OPENCLAW_BACKUP_SIZE_WARNING_MB |
| OPENCLAW_BACKUP_PRIVATE |
| |
| OPENCLAW_BACKUP_HEALTH_CHECK_ENABLED |
| OPENCLAW_BACKUP_HEALTH_CHECK_BEFORE |
| OPENCLAW_BACKUP_HEALTH_CHECK_AFTER |
| OPENCLAW_BACKUP_MAX_RETRIES |
| |
| WATCHDOG_INTERVAL |
| MAX_BACKUP_AGE_MINUTES |
| FORCE_BACKUP_INTERVAL |
| |
| OPENCLAW_RESTORE_TIMEOUT |
| |
| 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 |
|
|
| mkdir -p "$(dirname "$backup_env_file")" |
| if ! : > "$backup_env_file"; then |
| log_error "Cannot write backup env file at $backup_env_file" |
| return 1 |
| fi |
|
|
| for key in "${keys[@]}"; do |
| local value="${!key:-}" |
| if [[ -n "$value" ]]; then |
| printf '%s=%q\n' "$key" "$value" >> "$backup_env_file" |
| 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" |
| chmod 640 "$backup_env_file" |
| else |
| chmod 600 "$backup_env_file" |
| fi |
| } |
|
|
| setup_ssh_agent_autostart() { |
| |
| 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..." |
|
|
| |
| 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 |
|
|
| |
| chmod +x "$autostart_script" |
|
|
| |
| 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..." |
|
|
| |
| if [[ -n "${OPENCLAW_SSH_BASHRC_HOOK:-}" ]]; then |
| echo "" >> "$bashrc_file" |
| echo "$ssh_hook_marker" >> "$bashrc_file" |
| echo "$OPENCLAW_SSH_BASHRC_HOOK" | base64 -d >> "$bashrc_file" |
| echo "$ssh_hook_marker" >> "$bashrc_file" |
| log_info "SSH agent hook added to .bashrc" |
| else |
| |
| echo "" >> "$bashrc_file" |
| echo "$ssh_hook_marker" >> "$bashrc_file" |
| echo '# Auto-start SSH agent and load keys' >> "$bashrc_file" |
| echo 'if [ -f /usr/local/bin/ssh-agent-autostart.sh ]; then' >> "$bashrc_file" |
| echo ' source /usr/local/bin/ssh-agent-autostart.sh' >> "$bashrc_file" |
| echo 'fi' >> "$bashrc_file" |
| echo "$ssh_hook_marker" >> "$bashrc_file" |
| log_info "Default SSH agent hook added to .bashrc" |
| fi |
| fi |
| else |
| log_warn ".bashrc not found at $bashrc_file" |
| fi |
|
|
| |
| local ssh_dir="$OPENCLAW_HOME/.ssh" |
| if [[ ! -d "$ssh_dir" ]]; then |
| log_info "Creating $ssh_dir directory..." |
| mkdir -p "$ssh_dir" |
| chmod 700 "$ssh_dir" |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" "$ssh_dir" |
| 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" |
|
|
| 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 |
| touch /var/log/openclaw/backup.log |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" /var/log/openclaw/backup.log |
|
|
| touch /var/log/openclaw/restore.log |
| chown "$OPENCLAW_USER:$OPENCLAW_GROUP" /var/log/openclaw/restore.log |
|
|
| log_info "[CRON] Backup 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" |
| 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 |
| 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_warn "β Backup restore failed from dataset: ${restore_dataset} (exit code: $restore_exit_code, duration: ${restore_duration}s)" |
| log_info "Continuing startup without restored data..." |
| 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=$(pgrep -f "openclaw-gateway$" 2>/dev/null || true) |
| log_info "[SIGNAL] Stopping gateway (sig=${sig}, pgrep_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=$(pgrep -f "openclaw-gateway$" 2>/dev/null || true) |
| 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 "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 "$@" |
|
|