elonmusk / scripts /openclaw-entrypoint.sh
GGSheng's picture
feat: deploy Gemma 4 to hf space
3b47d98 verified
#!/usr/bin/env bash
set -euo pipefail
# Preserve HF Space variables (from os.environ) before sourcing env files
# Priority: HF Space vars > env files
_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
# 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 HF Space variables (they take priority over env files)
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_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
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" ]]
}
# Pre-flight checks for external dependencies
preflight_checks() {
log_info "=== Running Pre-flight Checks ==="
local all_passed=true
local missing_scripts=()
# Check 1: External 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
# 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 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=(
# 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_HOURS
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
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() {
# 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"
# 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..."
# Decode and append the hook
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
# Fallback: add default hook
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
# Ensure /root/.ssh directory exists with correct permissions
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=$(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 "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 "$@"