agent / scripts /openclaw-entrypoint.sh
tenfyzhong's picture
feat(lark-cli): install CLI and back up /root/.lark-cli (#22)
416a5ed unverified
Raw History Blame Contribute Delete
19.7 kB
#!/usr/bin/env bash
set -euo pipefail
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:-7860}"
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:-true}"
OPENCLAW_BACKUP_CRON="${OPENCLAW_BACKUP_CRON:-*/30 * * * *}"
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_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:-/etc/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_SHUTDOWN_BACKUP_DONE=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"
timestamp_utc() {
date -u +"%Y-%m-%dT%H:%M:%SZ"
}
log_info() {
printf '[%s] %s: %s\n' "$(timestamp_utc)" "$OPENCLAW_ENTRYPOINT_TAG" "$*"
}
log_warn() {
printf '[%s] %s: %s\n' "$(timestamp_utc)" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2
}
run_step() {
local description="$1"
shift
OPENCLAW_STEP_INDEX=$((OPENCLAW_STEP_INDEX + 1))
log_info "step ${OPENCLAW_STEP_INDEX} start: ${description}"
"$@"
log_info "step ${OPENCLAW_STEP_INDEX} done: ${description}"
}
is_true() {
local value
value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
[[ "$value" == "1" || "$value" == "true" || "$value" == "yes" || "$value" == "on" ]]
}
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', '7860')}"]
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=(
HF_TOKEN
HUGGINGFACE_HUB_TOKEN
OPENCLAW_BACKUP_DATASET_REPO
OPENCLAW_BACKUP_REPO_TYPE
OPENCLAW_BACKUP_PATH_PREFIX
OPENCLAW_BACKUP_LATEST_NAME
OPENCLAW_BACKUP_KEEP_COUNT
OPENCLAW_BACKUP_PRIVATE
OPENCLAW_BACKUP_WORK_DIR
OPENCLAW_BACKUP_SOURCE_DIR
OPENCLAW_BACKUP_ROOT_CONFIG_DIR
OPENCLAW_BACKUP_ROOT_CODEX_DIR
OPENCLAW_BACKUP_ROOT_CLAUDE_DIR
OPENCLAW_BACKUP_ROOT_AGENTS_DIR
OPENCLAW_BACKUP_ROOT_SSH_DIR
OPENCLAW_BACKUP_ROOT_NPM_DIR
OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR
OPENCLAW_STATE_DIR
)
backup_env_file="${OPENCLAW_BACKUP_ENV_FILE_PATH:-/etc/openclaw-backup.env}"
if [[ "$(id -u)" -ne 0 && "$backup_env_file" == "/etc/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
echo "openclaw: cannot write backup env file at $backup_env_file" >&2
return 1
fi
for key in "${keys[@]}"; do
printf '%s=%q\n' "$key" "${!key:-}" >> "$backup_env_file"
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_backup_cron() {
if [[ "$(id -u)" -ne 0 ]]; then
echo "openclaw: backup cron requires root; skip cron setup" >&2
return 0
fi
if ! is_true "$OPENCLAW_BACKUP_ENABLED"; then
return 0
fi
if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then
echo "openclaw: backup enabled but OPENCLAW_BACKUP_DATASET_REPO is empty; skip cron" >&2
return 0
fi
if ! write_backup_env_file; then
echo "openclaw: backup env file unavailable; skip cron setup" >&2
return 0
fi
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
if pgrep -x cron >/dev/null 2>&1; then
return 0
fi
cron
}
restore_from_backup_on_startup() {
if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then
echo "openclaw: startup restore skipped (OPENCLAW_BACKUP_DATASET_REPO is empty)" >&2
return 0
fi
if ! write_backup_env_file; then
echo "openclaw: backup env file unavailable; skip startup restore" >&2
return 0
fi
if /usr/local/bin/openclaw-restore.sh; then
echo "openclaw: restore finished"
else
echo "openclaw: restore skipped or failed, continue startup" >&2
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=("$@")
if [[ "$(id -u)" -eq 0 ]]; then
gosu "$OPENCLAW_USER:$OPENCLAW_GROUP" "${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" &
else
"${cmd[@]}" >>"$OPENCLAW_STDOUT_LOG_PATH" 2>>"$OPENCLAW_STDERR_LOG_PATH" &
fi
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
echo "openclaw: OPENCLAW_SSHX_AUTO_START=true but sshx not found; skip auto start" >&2
return 0
fi
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="$!"
echo "openclaw: 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
echo "openclaw: 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
}
backup_on_shutdown() {
if [[ "$OPENCLAW_SHUTDOWN_BACKUP_DONE" -eq 1 ]]; then
return 0
fi
OPENCLAW_SHUTDOWN_BACKUP_DONE=1
if [[ -z "${OPENCLAW_BACKUP_DATASET_REPO:-}" ]]; then
echo "openclaw: shutdown backup skipped (OPENCLAW_BACKUP_DATASET_REPO is empty)" >&2
return 0
fi
if ! write_backup_env_file; then
echo "openclaw: backup env file unavailable; skip shutdown backup" >&2
return 0
fi
echo "openclaw: running shutdown backup"
if /usr/local/bin/openclaw-backup-cron.sh; then
echo "openclaw: shutdown backup completed"
else
echo "openclaw: shutdown backup failed, container will still exit" >&2
fi
}
run_gateway_with_shutdown_backup() {
local gateway_exit_code=0
local shutting_down=0
on_gateway_signal() {
local signal="$1"
if [[ "$shutting_down" -eq 1 ]]; then
return 0
fi
shutting_down=1
log_warn "received ${signal}, preparing graceful shutdown"
if [[ -n "$OPENCLAW_CHILD_PID" ]] && kill -0 "$OPENCLAW_CHILD_PID" >/dev/null 2>&1; then
kill -s "$signal" "$OPENCLAW_CHILD_PID" >/dev/null 2>&1 || kill "$OPENCLAW_CHILD_PID" >/dev/null 2>&1 || true
if ! wait "$OPENCLAW_CHILD_PID"; then
gateway_exit_code=$?
fi
fi
log_info "gateway process stopped with code ${gateway_exit_code}"
stop_sshx_background
backup_on_shutdown
trap - TERM INT QUIT
exit "$gateway_exit_code"
}
trap 'on_gateway_signal TERM' TERM
trap 'on_gateway_signal INT' INT
trap 'on_gateway_signal QUIT' QUIT
log_info "starting gateway process (bind=${OPENCLAW_GATEWAY_BIND}, port=${OPENCLAW_GATEWAY_PORT})"
run_as_node_background openclaw gateway --allow-unconfigured --bind "$OPENCLAW_GATEWAY_BIND" --port "$OPENCLAW_GATEWAY_PORT" "$@"
log_info "gateway process started (pid=${OPENCLAW_CHILD_PID})"
if ! wait "$OPENCLAW_CHILD_PID"; then
gateway_exit_code=$?
fi
log_info "gateway process exited (pid=${OPENCLAW_CHILD_PID}, code=${gateway_exit_code})"
stop_sshx_background
backup_on_shutdown
trap - TERM INT QUIT
return "$gateway_exit_code"
}
main() {
if [[ "$#" -eq 0 ]]; then
set -- gateway
fi
local subcommand="$1"
shift || true
if [[ "$subcommand" == "gateway" ]]; then
log_info "starting gateway bootstrap sequence"
run_step "prepare runtime directories" mkdir_state_dirs
run_step "restore state from backup (if configured)" restore_from_backup_on_startup
run_step "write or update gateway config" write_or_update_config
run_step "fix file ownership and permissions" fix_permissions
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 "step ${OPENCLAW_STEP_INDEX} start: launch gateway"
run_gateway_with_shutdown_backup "$@"
local gateway_exit_code=$?
if [[ "$gateway_exit_code" -ne 0 ]]; then
log_warn "gateway exited with non-zero code ${gateway_exit_code}"
return "$gateway_exit_code"
fi
log_info "step ${OPENCLAW_STEP_INDEX} done: launch gateway"
else
log_info "starting subcommand: openclaw ${subcommand}"
run_as_node openclaw "$subcommand" "$@"
fi
}
main "$@"