OpenClaw refactor native setup with sandbox

This commit is contained in:
Trevor SANDY
2026-05-15 00:20:46 +02:00
parent 8c5ea514ba
commit 021dc34746
4 changed files with 306 additions and 108 deletions
+11
View File
@@ -20,6 +20,17 @@
"program": "${command:SelectScriptName}",
"args": []
},
{
"type": "bashdb",
"request": "launch",
"name": "Bash-Debug (openclaw_ctl)",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/openclaw_ctl",
"args": ["--gateway", "--sandbox"],
//"args": ["--configuration", "--sandbox"]
//"args": ["--onboarding", "--sandbox"]
//"args": ["--setenv", "--sandbox"]
},
{
"name": "PowerShell Launch Current File",
"type": "PowerShell",
+8 -7
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# Trevor SANDY
# Last Update April, 09 2026
# Last Update April, 28 2026
# Copyright (C) 2026 by Trevor SANDY
#
# Auto-configure, with user prompts, self-hosted AI-Suite with Caddy/Nginx proxy and
@@ -1874,12 +1874,13 @@ if [[ -f "$openclaw_compose_path" ]]; then
# Rebuild OpenClaw services
# shellcheck disable=SC2016
openclaw_service_yaml='
. as $root |
{
"name": "openclaw",
"services": (
$root.services
| to_entries
"name": "openclaw"
}
+
(
.services |= (
to_entries
| map(
.value = (
{
@@ -1897,7 +1898,7 @@ if [[ -f "$openclaw_compose_path" ]]; then
)
| from_entries
)
}
)
'
update_yaml_file "$openclaw_service_yaml" "$openclaw_compose_path"
fi
+201 -71
View File
@@ -1,12 +1,19 @@
#!/usr/bin/env bash
# Trevor SANDY
# Last Update April, 24 2026
# Last Update April, 30 2026
# Copyright (C) 2026 by Trevor SANDY
#
# Change Updates
# https://github.com/openclaw/openclaw/blob/main/scripts/docker/setup.sh
# 66f4b52 - 28/04/2026
set -euo pipefail
: "${APP_NAME:="AI-Suite"}"
: "${APP_NAME:='AI-Suite'}"
: "${DEBUG_ON:=false}"
: "${PLATFORM:='unknown'}"
: "${PRIMARY_ARG:=''}"
: "${ENV_MODE:=''}"
: "${SILENT:=0}"
# Reset BASH time counter
@@ -163,6 +170,11 @@ log_warning() { log WARNING "$*"; }
log_debug() { log DEBUG "$*"; }
log_info() { log INFO "$*"; }
fail() {
log_critical "$*"
exit 1
}
strip_sgr() {
local line sgr=$'\033'
while IFS= read -r line; do
@@ -204,56 +216,137 @@ finish_elapsed_time() {
printf '%s\n' "${INFO} ${GREEN}-------------------------------------------${END}"
}
completion=''
# shellcheck disable=SC2329
completion='Success!'
finish () {
local header="${END}✅ ${HEADER}"
local action="Set .env"
local status="Completed"
if [ "$completion" == "Success!" ]; then
:
elif [ "$completion" == "Partial!" ]; then
case "$PRIMARY_ARG" in
--setenv) : ;;
--onboarding) action="Onboarding" ;;
--configuration) action="Configuration" ;;
--gateway) action="Gateway" ;;
*) action="Unknown" ;;
esac
case "$completion" in
Success!) : ;;
Partial!)
header="${END}⚠️ ${HEADER}"
status="Finished"
else
;;
*)
header="${END}❌ ${SGR}${BOLD}${UNDERLINE}91m"
status="Terminated"
fi
log_info "${header}Configuration $status"
;;
esac
log_info "${header}OpenClaw - $action $status"
#-------------------------------------------
finish_elapsed_time
}
trap finish EXIT
########################################
# ENVIRONMENT
########################################
log_info "${HEADER}OpenClaw - Environment"
#-------------------------------------------
detect_arch() {
local -n _ref=$1
case $(uname -m) in
x86_64) _ref='amd64' ;;
aarch64 | arm64) _ref='arm64' ;;
armv7l) _ref='arm' ;;
i686 | i386) _ref='386' ;;
*) _ref='err' ;;
esac
}
detect_os() {
local -n _ref=$1
case $(uname | tr '[:upper:]' '[:lower:]') in
linux*) _ref='linux' ;;
darwin*) _ref='darwin' ;;
*) _ref='err' ;;
esac
}
is_wsl() {
case "$(uname -r)" in
*icrosoft*WSL2 | *icrosoft*wsl2) return ;;
*icrosoft) fail "Microsoft WSL1 is not supported. Use WSL2 with 'wsl --set-version <distro> 2'" ;;
*) return 1 ;;
esac
}
os=''
detect_os os
case "$os" in
linux*)
if is_wsl; then
PLATFORM="wsl"
else
PLATFORM="linux"
fi
;;
darwin*) PLATFORM="mac" ;;
err) fail "Unsupported platform." ;;
esac
arch=''
detect_arch arch
if [[ "$arch" == "err" ]]; then fail "Unsupported CPU architecture"; fi
log_info "${BODY}OS:${END} ${WHITE}$os"
log_info "${BODY}PLATFORM:${END} ${WHITE}$PLATFORM"
log_info "${BODY}ARCHITECTURE:${END} ${WHITE}$arch"
########################################
# ROOT / CONFIG
########################################
log_info "${HEADER}ROOT / CONFIG VARIABLES"
log_info "${HEADER}OpenClaw - Variables"
#-------------------------------------------
ROOT_DIR="$(pwd)"
OPENCLAW_DIR=""
log_info "${BODY}ROOT_DIR:${END} ${WHITE}$ROOT_DIR"
OPENCLAW_DIR="$(cd "$ROOT_DIR/openclaw" && pwd)"
case "$(basename "$ROOT_DIR")" in
openclaw)
OPENCLAW_DIR="${ROOT_DIR}"
ROOT_DIR="$(cd "$ROOT_DIR/../" && pwd)"
;;
ai-suite)
OPENCLAW_DIR="$(cd "$ROOT_DIR/openclaw" && pwd)"
;;
*) fail "Must be run from 'openclaw' or 'ai-suite'." ;;
esac
log_info "${BODY}OPENCLAW_DIR:${END} ${WHITE}$OPENCLAW_DIR"
# shellcheck disable=SC1091
source "$OPENCLAW_DIR/scripts/lib/docker-build.sh"
COMPOSE_FILE="$OPENCLAW_DIR/docker-compose.yml"
EXTRA_COMPOSE_FILE="$OPENCLAW_DIR/docker-compose.extra.yml"
SANDBOX_COMPOSE_FILE="$OPENCLAW_DIR/docker-compose.sandbox.yml"
PRIVATE_COMPOSE_FILE="$ROOT_DIR/docker-compose.override.private.yml"
PUBLIC_COMPOSE_FILE="$ROOT_DIR/docker-compose.override.public.yml"
#PRIVATE_COMPOSE_FILE="$ROOT_DIR/docker-compose.override.private.yml"
#PUBLIC_COMPOSE_FILE="$ROOT_DIR/docker-compose.override.public.yml"
IMAGE_NAME="${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:latest}"
EXTRA_MOUNTS="${OPENCLAW_EXTRA_MOUNTS:-}"
HOME_VOLUME_NAME="${OPENCLAW_HOME_VOLUME:-}"
DOCKER_SOCKET_PATH="${OPENCLAW_DOCKER_SOCKET:-}"
ENV_FILE="$OPENCLAW_DIR/.env"
BUILD="${BUILD:-}"
TIMEZONE="${OPENCLAW_TZ:-}"
PERMISSIONS="${PERMISSIONS:-}"
OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}"
OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME/.openclaw/workspace}"
HOME_DIR="$HOME"
[[ "$PLATFORM" == "wsl" ]] && \
HOME_DIR="$(wslpath "$(cmd.exe /c "<nul set /p=%USERPROFILE%" 2>/dev/null)")"
OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME_DIR/.openclaw}"
log_info "${BODY}OPENCLAW_CONFIG_DIR:${END} ${WHITE}$OPENCLAW_CONFIG_DIR"
OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME_DIR/.openclaw/workspace}"
log_info "${BODY}OPENCLAW_WORKSPACE_DIR:${END} ${WHITE}$OPENCLAW_WORKSPACE_DIR"
OPENCLAW_GATEWAY_PORT="${OPENCLAW_GATEWAY_PORT:-18789}"
OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
OPENCLAW_DOCKER_GID=""
OPENCLAW_INSTALL_DOCKER_CLI=""
@@ -262,18 +355,17 @@ OPENCLAW_INSTALL_DOCKER_CLI=""
# ARGUMENT PARSING (single primary arg)
########################################
PRIMARY_ARG=""
if [[ $# -gt 0 ]]; then
log_info "${header}Parse Arguments"
log_info "${HEADER}OpenClaw - Arguments"
#-------------------------------------------
PRIMARY_ARG="$1"
log_info "${BODY}Primary Argument:${END} ${WHITE}$PRIMARY_ARG"
shift || true
fi
ENV_MODE=""
case "$PRIMARY_ARG" in
--setenv) ENV_MODE="1" ;;
--onboarding|--configuration|--gateway) ;;
--onboarding|--configuration|--gateway) : ;;
*) echo -e "${ERROR} ${RED}Unknown primary argument.${END}" >&2 ;;
esac
@@ -282,17 +374,16 @@ SANDBOX_ENABLED="${OPENCLAW_SANDBOX:-0}"
ENVIRONMENT="${ENVIRONMENT:-private}"
while [ $# -gt 0 ]; do
case "$1" in
--build) BUILD="1" ;;
--debug) DEBUG_ON="true" ;;
--sandbox) OPENCLAW_SANDBOX="1" ;;
--environment)
shift
if [[ $# -eq 0 ]]; then
echo -e "${WARNING} ${YELLOW}--environment option requires 'public/private' argument.${END}"
else
ENVIRONMENT="$1"
fi
;;
# --environment)
# shift
# if [[ $# -eq 0 ]]; then
# echo -e "${WARNING} ${YELLOW}--environment option requires 'public/private' argument.${END}"
# else
# ENVIRONMENT="$1"
# fi
# ;;
*)
echo -e "${NOTICE} Extra argument ignored: ${WHITE}$1${END}" >&2
;;
@@ -304,11 +395,6 @@ done
# UTILITIES
########################################
fail() {
log_critical "$*"
exit 1
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
fail "Missing dependency: $1"
@@ -494,7 +580,9 @@ sync_gateway_config() {
fi
batch_json+="]"
run_prestart_cli config set --batch-json "$batch_json" >/dev/null
if ! run_prestart_cli config set --batch-json "$batch_json" >/dev/null; then
: #fail "Could not complete run configuration"
fi
log_info "${GREEN}Pinned gateway.mode=local and gateway.bind=${END}${WHITE}$OPENCLAW_GATEWAY_BIND for Docker setup."
if [[ -n "$allowed_origin_json" ]]; then
if [[ -z "$current_allowed_origins" || "$current_allowed_origins" == "null" || "$current_allowed_origins" == "[]" ]]; then
@@ -509,7 +597,7 @@ sync_gateway_config() {
is_env_mode() {
if is_truthy_value "$ENV_MODE"; then
log_info "${header}Query Mode"
log_info "${HEADER}OpenClaw - Set .env"
#-------------------------------------------
return 0
fi
@@ -533,6 +621,7 @@ upsert_env() {
for k in "${keys[@]}"; do
if [[ "$key" == "$k" ]]; then
printf '%s=%s\n' "$k" "${!k-}" >>"$tmp"
log_info "${BODY}$k:${END} ${WHITE}${!k-}"
seen="$seen$k "
replaced=true
break
@@ -547,21 +636,41 @@ upsert_env() {
for k in "${keys[@]}"; do
if [[ "$seen" != *" $k "* ]]; then
printf '%s=%s\n' "$k" "${!k-}" >>"$tmp"
log_info "${BODY}$k:${END} ${WHITE}${!k-}"
fi
done
mv "$tmp" "$file"
}
run_create_env() {
run_set_env() {
export OPENCLAW_CONFIG_DIR
export OPENCLAW_WORKSPACE_DIR
export OPENCLAW_DISABLE_BONJOUR="${OPENCLAW_DISABLE_BONJOUR:-}"
export OPENCLAW_DOCKER_APT_PACKAGES="${OPENCLAW_DOCKER_APT_PACKAGES:-}"
export OPENCLAW_EXTENSIONS="${OPENCLAW_EXTENSIONS:-}"
export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}"
export OPENCLAW_SANDBOX="$SANDBOX_ENABLED"
export OPENCLAW_DOCKER_SOCKET="$DOCKER_SOCKET_PATH"
export OPENCLAW_DOCKER_SETUP=1
export OPENCLAW_TZ="$TIMEZONE"
export COMPOSE_IGNORE_ORPHANS="${COMPOSE_IGNORE_ORPHANS:-'true'}"
export OTEL_EXPORTER_OTLP_ENDPOINT="${OTEL_EXPORTER_OTLP_ENDPOINT:-}"
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}"
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="${OTEL_EXPORTER_OTLP_METRICS_ENDPOINT:-}"
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="${OTEL_EXPORTER_OTLP_LOGS_ENDPOINT:-}"
export OTEL_EXPORTER_OTLP_PROTOCOL="${OTEL_EXPORTER_OTLP_PROTOCOL:-}"
export OTEL_SERVICE_NAME="${OTEL_SERVICE_NAME:-}"
export OTEL_SEMCONV_STABILITY_OPT_IN="${OTEL_SEMCONV_STABILITY_OPT_IN:-}"
export OPENCLAW_OTEL_PRELOADED="${OPENCLAW_OTEL_PRELOADED:-}"
upsert_env "$ENV_FILE" \
OPENCLAW_CONFIG_DIR \
OPENCLAW_WORKSPACE_DIR \
OPENCLAW_GATEWAY_PORT \
OPENCLAW_BRIDGE_PORT \
OPENCLAW_GATEWAY_BIND \
OPENCLAW_DISABLE_BONJOUR \
OPENCLAW_GATEWAY_TOKEN \
OPENCLAW_IMAGE \
OPENCLAW_EXTRA_MOUNTS \
OPENCLAW_HOME_VOLUME \
OPENCLAW_DOCKER_APT_PACKAGES \
@@ -571,7 +680,17 @@ run_create_env() {
OPENCLAW_DOCKER_GID \
OPENCLAW_INSTALL_DOCKER_CLI \
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS \
OPENCLAW_TZ
OPENCLAW_TZ \
COMPOSE_IGNORE_ORPHANS \
OTEL_EXPORTER_OTLP_ENDPOINT \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT \
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT \
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT \
OTEL_EXPORTER_OTLP_PROTOCOL \
OTEL_SERVICE_NAME \
OTEL_SEMCONV_STABILITY_OPT_IN \
OPENCLAW_OTEL_PRELOADED
completion='Success!'
}
########################################
@@ -581,7 +700,7 @@ run_create_env() {
# Dockerfile uses BuildKit-only syntax (RUN --mount=type=cache). Force
# BuildKit so hosts defaulting to the legacy builder do not fail.
run_docker_build() {
DOCKER_BUILDKIT=1 docker build "$@"
docker_build_exec "$@"
}
run_prestart_gateway() {
@@ -621,30 +740,44 @@ run_runtime_cli() {
}
run_onboarding() {
fix_permissions
if ! fix_permissions; then
log_error "Could not fix permissions for run onboarding."
fi
log_info ""
log_info "${BLUE}==>${END} ${GREEN}Onboarding (interactive)"
log_info "Docker setup pins Gateway mode to local."
log_info "Gateway runtime bind comes from OPENCLAW_GATEWAY_BIND (default: lan)."
log_info "Current runtime bind: $OPENCLAW_GATEWAY_BIND"
log_info "Gateway token: $OPENCLAW_GATEWAY_TOKEN"
log_info "Tailscale exposure: Off (use host-level tailnet/Tailscale setup separately)."
log_info "Install Gateway daemon: No (managed by Docker Compose)"
log_info "${GREEN}Docker setup pins Gateway mode to local."
log_info "${GREEN}Gateway runtime bind comes from OPENCLAW_GATEWAY_BIND (default: lan)."
log_info "${GREEN}Current runtime bind:${END} ${WHITE}$OPENCLAW_GATEWAY_BIND"
if is_truthy_value "$OPENCLAW_DISABLE_BONJOUR"; then
log_info "${GREEN}Bonjour/mDNS advertising:${END} ${WHITE}force disabled (OPENCLAW_DISABLE_BONJOUR=$OPENCLAW_DISABLE_BONJOUR)."
elif [[ -z "$OPENCLAW_DISABLE_BONJOUR" ]]; then
log_info "${GREEN}Bonjour/mDNS advertising:${END} ${WHITE}auto (disabled inside the Gateway container unless explicitly enabled)."
else
log_info "${GREEN}Bonjour/mDNS advertising:${END} ${WHITE}explicitly enabled (OPENCLAW_DISABLE_BONJOUR=$OPENCLAW_DISABLE_BONJOUR)."
fi
log_info "${GREEN}Gateway token:${END} ${WHITE}$OPENCLAW_GATEWAY_TOKEN"
log_info "${GREEN}Tailscale exposure:${END} ${WHITE}Off (use host-level tailnet/Tailscale setup separately)."
log_info "${GREEN}Install Gateway daemon:${END} ${WHITE}No (managed by Docker Compose)"
log_info ""
run_prestart_cli onboard --mode local --no-install-daemon
}
run_configuration() {
[[ -z "$PERMISSIONS" ]] && \
fix_permissions
if ! fix_permissions; then
log_error "Could not fix permissions for run configuration."
fi
log_info ""
log_info "${BLUE}==>${END} ${GREEN}Docker gateway defaults"
sync_gateway_config
completion='Success!'
}
run_gateway() {
[[ -z "$PERMISSIONS" ]] && \
fix_permissions
if ! fix_permissions; then
log_error "Could not fix permissions for run gateway."
fi
log_info ""
log_info "${BLUE}==>${END} ${GREEN}Provider setup (optional)"
log_info "${GREEN}WhatsApp (QR):"
@@ -654,17 +787,13 @@ run_gateway() {
log_info "${GREEN}Discord (bot token):"
log_info " ${WHITE}${COMPOSE_HINT} run --rm openclaw-cli channels add --channel discord --token <token>"
log_info "${GREEN}Docs:${END} ${WHITE}https://docs.openclaw.ai/channels"
log_info ""
log_info "${BLUE}==>${END} ${GREEN}Starting gateway"
[[ -n "$BUILD" ]] && \
COMPOSE_ARGS+=("--build")
docker compose "${COMPOSE_ARGS[@]}" up -d openclaw-gateway
if [[ -n "$SANDBOX_ENABLED" ]]; then
enable_sandbox
fi
log_info ""
log_info "${GREEN}Gateway running with host port mapping."
log_info "${GREEN}Access from tailnet devices via the host's tailnet IP."
@@ -675,10 +804,11 @@ run_gateway() {
log_info "${GREEN}Commands:"
log_info " ${WHITE}${COMPOSE_HINT} logs -f openclaw-gateway"
log_info " ${WHITE}${COMPOSE_HINT} exec openclaw-gateway node dist/index.js health --token \"$OPENCLAW_GATEWAY_TOKEN\""
completion='Success!'
}
########################################
# EXTRA COMPOSE BUILD
# EXTRA COMPOSE
########################################
write_extra_compose() {
@@ -887,15 +1017,17 @@ if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
EXISTING_CONFIG_TOKEN="$(read_config_gateway_token || true)"
if [[ -n "$EXISTING_CONFIG_TOKEN" ]]; then
OPENCLAW_GATEWAY_TOKEN="$EXISTING_CONFIG_TOKEN"
log_info "${GREEN}Reusing gateway token from $OPENCLAW_CONFIG_DIR/openclaw.json"
log_info "${BODY}Reusing gateway token from:${END} ${WHITE}$OPENCLAW_CONFIG_DIR/openclaw.json"
else
DOTENV_GATEWAY_TOKEN="$(read_env_gateway_token "$OPENCLAW_DIR/.env" || true)"
if [[ -n "$DOTENV_GATEWAY_TOKEN" ]]; then
OPENCLAW_GATEWAY_TOKEN="$DOTENV_GATEWAY_TOKEN"
log_info "${GREEN}Reusing gateway token from $OPENCLAW_DIR/.env"
log_info "${BODY}Reusing gateway token from:${END} ${WHITE}$OPENCLAW_DIR/.env"
elif command -v openssl >/dev/null 2>&1; then
log_info "${BODY}Generating gateway token from:${END} ${WHITE}openssl rand -hex 32"
OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)"
else
log_info "${BODY}Generating gateway token from:${END} ${WHITE}Python secrets.token_hex(32)"
OPENCLAW_GATEWAY_TOKEN="$(python3 - <<'PY'
import secrets
print(secrets.token_hex(32))
@@ -930,10 +1062,10 @@ if [[ -n "$SANDBOX_ENABLED" ]]; then
fi
if is_env_mode; then
run_create_env
run_set_env
exit 0
else
log_info "${header}Execution Mode"
log_info "${HEADER}OpenClaw - Execution Mode"
#-------------------------------------------
fi
@@ -975,14 +1107,12 @@ mkdir -p "$OPENCLAW_CONFIG_DIR/agents/main/agent"
mkdir -p "$OPENCLAW_CONFIG_DIR/agents/main/sessions"
COMPOSE_FILES=("$COMPOSE_FILE")
COMPOSE_ARGS=("-p" "ai-suite" "-f" "$COMPOSE_FILE")
if [[ "$ENVIRONMENT" == "public" ]]; then
COMPOSE_ARGS+=("-f" "$PUBLIC_COMPOSE_FILE")
COMPOSE_FILES+=("$PUBLIC_COMPOSE_FILE")
else
COMPOSE_ARGS+=("-f" "$PRIVATE_COMPOSE_FILE")
COMPOSE_FILES+=("$PRIVATE_COMPOSE_FILE")
fi
COMPOSE_ARGS=("-p" "ai-suite")
# if [[ "$ENVIRONMENT" == "public" ]]; then
# COMPOSE_FILES+=("$PUBLIC_COMPOSE_FILE")
# else
# COMPOSE_FILES+=("$PRIVATE_COMPOSE_FILE")
# fi
VALID_MOUNTS=()
if [[ -n "$EXTRA_MOUNTS" ]]; then
+86 -30
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
Trevor SANDY
Last Update April 17, 2026
Last Update April 29, 2026
Copyright (c) 2025-Present by Trevor SANDY
AI-Suite uses this script for the installation command that handles the AI-Suite
@@ -314,7 +314,7 @@ LSH.setFormatter(LSHF)
LSH.setLevel(logging.NOTSET)
def run_command(cmd, cwd=None):
def run_command(cmd, cwd=None, re_raise=None):
"""Run a shell command and print it."""
raw_msg = " ".join([log_run_cmd, " ".join(cmd)])
log.info(raw_msg, extra=LSHF.style(header=log_run_cmd, msg=" ".join(cmd)))
@@ -328,6 +328,8 @@ def run_command(cmd, cwd=None):
log.error(f"{result.stderr.strip()}")
except Exception as e:
log.error(f"Exception: {e}.")
if re_raise:
raise
def run_pkg_command(cmd):
"""Run a package shell command and print it."""
@@ -469,6 +471,13 @@ def is_root_user():
except (subprocess.CalledProcessError, ValueError):
return False
def to_wsl_path(path: pathlib.Path) -> str:
path_str = path.resolve().as_posix()
if path.drive:
drive = path.drive[0].lower()
return f"/mnt/{drive}{path_str[2:]}"
return path_str
def sudo_user():
if system == "Windows":
if not is_wsl2():
@@ -1578,6 +1587,7 @@ def clone_openclaw_repo():
git("sparse-checkout", "init", "--cone")
git("sparse-checkout", "set",
"scripts/docker/setup.sh",
"scripts/lib/docker-build.sh",
"scripts/clawdock",
"docs"
)
@@ -1682,6 +1692,10 @@ def prepare_openclaw_env(cwd):
home_dir = pathlib.Path.home()
config_dir = home_dir / ".openclaw"
workspace_dir = config_dir / "workspace"
if is_wsl2():
home_dir = to_wsl_path(home_dir)
config_dir = to_wsl_path(config_dir)
workspace_dir = to_wsl_path(workspace_dir)
gateway_port = 18789
bridge_port = 18790
gateway_bind = "lan"
@@ -1694,12 +1708,16 @@ def prepare_openclaw_env(cwd):
output_path=os.path.join(cwd, ".env")
add_home_dir = True
add_config_dir = True
add_config_path = True
add_workspace_dir = True
add_state_dir = True
add_gateway_port = True
add_bridge_port = True
add_gateway_bind = True
add_gateway_token = True
add_remote_image = True
add_gateway_password = True
add_openai_api_key = True
add_extra_mounts = True
add_home_volume = True
add_sandbox = True
@@ -1710,58 +1728,65 @@ def prepare_openclaw_env(cwd):
sandbox = False
update_env = False
log.info(f"Writing .env file to {output_path}...")
debug_style = LSHF.style(logging.WARNING)
try:
with open(example_path, "r", newline="\n") as f:
lines = f.readlines()
for line in lines:
modified_line = line.strip()
if modified_line.startswith("# OPENCLAW_HOME="):
add_home_dir = False
if modified_line.startswith("# OpenClaw .env example"):
modified_lines.append("# OpenClaw .env (from .env.example)\n")
elif modified_line.startswith("# 1) Copy this file to `.env`"):
modified_lines.append("# 1) Copied to `./openclaw/.env` (for local runs from repo)\n")
elif modified_line.startswith("OPENCLAW_HOME="):
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_home_dir = False
modified_lines.append(f"OPENCLAW_HOME={home_dir}\n")
else:
modified_lines.append(line)
elif modified_line.startswith("OPENCLAW_CONFIG_DIR="):
add_config_dir = False
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_config_dir = False
modified_lines.append(f"OPENCLAW_CONFIG_DIR={config_dir}\n")
else:
modified_lines.append(line)
elif modified_line.startswith("OPENCLAW_WORKSPACE_DIR=\n"):
add_workspace_dir = False
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_workspace_dir = False
modified_lines.append(f"OPENCLAW_WORKSPACE_DIR={workspace_dir}\n")
else:
modified_lines.append(line)
elif modified_line.startswith("OPENCLAW_GATEWAY_PORT="):
add_gateway_port = False
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_gateway_port = False
modified_lines.append(f"OPENCLAW_GATEWAY_PORT={gateway_port}\n")
else:
modified_lines.append(line)
elif modified_line.startswith("OPENCLAW_BRIDGE_PORT="):
add_bridge_port = False
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_bridge_port = False
modified_lines.append(f"OPENCLAW_BRIDGE_PORT={bridge_port}\n")
else:
modified_lines.append(line)
elif modified_line.startswith("OPENCLAW_GATEWAY_BIND="): # Foo
add_gateway_bind = False
elif modified_line.startswith("OPENCLAW_GATEWAY_BIND="):
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_gateway_bind = False
modified_lines.append(f"OPENCLAW_GATEWAY_BIND={gateway_bind}\n")
else:
modified_lines.append(line)
elif modified_line.startswith("OPENCLAW_GATEWAY_TOKEN="):
add_gateway_token = False
add_gateway_password = False
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_gateway_token = False
modified_lines.append(f"OPENCLAW_GATEWAY_TOKEN={gateway_token}\n")
log.debug(f"OPENCLAW_GATEWAY_TOKEN={elide(gateway_token)}", extra=debug_style)
else:
modified_lines.append(line)
elif modified_line.startswith("OPENCLAW_IMAGE="):
@@ -1771,15 +1796,19 @@ def prepare_openclaw_env(cwd):
modified_lines.append(f"OPENCLAW_IMAGE={openclaw_image}\n")
else:
modified_lines.append(line)
elif modified_line.startswith("# OPENCLAW_GATEWAY_PASSWORD="):
elif modified_line.startswith("OPENCLAW_GATEWAY_PASSWORD="):
add_gateway_token = False
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_gateway_password = False
modified_lines.append(f"OPENCLAW_GATEWAY_PASSWORD={gateway_password}\n")
log.debug(f"OPENCLAW_GATEWAY_PASSWORD={elide(gateway_password)}", extra=debug_style)
else:
modified_lines.append(line)
elif modified_line.startswith("# OPENAI_API_KEY="):
elif modified_line.startswith("OPENAI_API_KEY="):
key_value = modified_line.split('=', 1)
if len(key_value) == 2 and not key_value[1]:
add_openai_api_key = False
modified_lines.append(f"OPENAI_API_KEY={openapi_key}\n")
else:
modified_lines.append(line)
@@ -1824,12 +1853,15 @@ def prepare_openclaw_env(cwd):
if add_remote_image or default_paths:
lines = modified_lines
modified_lines = []
section_header = False
for line in lines:
modified_line = line.strip()
section_header = modified_line.startswith("# ----------------------------")
if modified_line.startswith("# ----------------------------"):
section_header = True
default_path_insert = modified_line.startswith("# Optional path overrides ")
auto_configure_settings = modified_line.startswith("# OPENCLAW_HOME=")
auto_configure_settings = modified_line.startswith("# Model provider API keys ")
if section_header and add_remote_image:
section_header = False
add_remote_image = False
modified_lines.append("# " + "-" * 77 + "\n")
modified_lines.append("# Prebuilt Image\n")
@@ -1841,19 +1873,35 @@ def prepare_openclaw_env(cwd):
elif default_paths and default_path_insert:
modified_lines.append("# Default mount paths.\n")
if add_config_dir:
add_config_path = False
modified_lines.append(f"OPENCLAW_CONFIG_DIR={config_dir}\n")
if add_workspace_dir:
add_state_dir = False
modified_lines.append(f"OPENCLAW_WORKSPACE_DIR={workspace_dir}\n")
elif add_gateway_password and modified_line.startswith("# OPENCLAW_GATEWAY_PASSWORD="):
add_gateway_password = False
modified_lines.append(f"OPENCLAW_GATEWAY_PASSWORD={gateway_password}\n")
log.debug(f"OPENCLAW_GATEWAY_PASSWORD={elide(gateway_password)}", extra=debug_style)
elif add_openai_api_key and modified_line.startswith("# OPENAI_API_KEY="):
add_openai_api_key = False
modified_lines.append(f"OPENAI_API_KEY={openapi_key}\n")
elif modified_line.startswith("# OPENCLAW_STATE_DIR="):
continue
if add_state_dir:
modified_lines.append(f"OPENCLAW_STATE_DIR={workspace_dir}\n")
else:
continue
elif modified_line.startswith("# OPENCLAW_CONFIG_PATH="):
continue
elif auto_configure_settings:
modified_lines.append(line)
modified_lines.append("\n")
if add_config_path:
modified_lines.append(f"OPENCLAW_CONFIG_PATH={config_dir}\n")
else:
continue
elif add_home_dir and modified_line.startswith("# OPENCLAW_HOME="):
add_home_dir = False
modified_lines.append(f"OPENCLAW_HOME={home_dir}\n")
elif section_header and auto_configure_settings:
section_header = False
modified_lines.append("# Auto-configure settings\n")
if add_home_dir:
modified_lines.append(f"OPENCLAW_HOME={home_dir}\n")
modified_lines.append("# " + "-" * 77 + "\n")
if add_gateway_port:
modified_lines.append(f"OPENCLAW_GATEWAY_PORT={gateway_port}\n")
if add_bridge_port:
@@ -1862,6 +1910,7 @@ def prepare_openclaw_env(cwd):
modified_lines.append(f"OPENCLAW_GATEWAY_BIND={gateway_bind}\n")
if add_gateway_token:
modified_lines.append(f"OPENCLAW_GATEWAY_TOKEN={gateway_token}\n")
log.debug(f"OPENCLAW_GATEWAY_TOKEN={elide(gateway_token)}", extra=debug_style)
if add_extra_mounts:
update_env = True
modified_lines.append("OPENCLAW_EXTRA_MOUNTS=\n")
@@ -1883,8 +1932,15 @@ def prepare_openclaw_env(cwd):
if add_timezone:
update_env = True
modified_lines.append("OPENCLAW_TZ=\n")
modified_lines.append("\n")
modified_lines.append("# " + "-" * 77 + "\n")
modified_lines.append(line)
else:
modified_lines.append(line)
modified_lines.append("\n")
modified_lines.append("# " + "-" * 77 + "\n")
modified_lines.append("# Additional settings\n")
modified_lines.append("# " + "-" * 77 + "\n")
with open(output_path, "w", newline="\n") as f:
f.writelines(modified_lines)
except Exception as e:
@@ -1907,8 +1963,6 @@ def prepare_openclaw_env(cwd):
f'{oc_script} --setenv {" ".join(cmd_args)}'
]
run_command(env_cmd)
log.info(f".env file created at {output_path}", extra=log_bright)
debug_style = LSHF.style(logging.WARNING)
log.debug(f"OPENCLAW_IMAGE={openclaw_image}", extra=debug_style)
log.debug(f"OPENCLAW_HOME={home_dir}", extra=debug_style)
log.debug(f"OPENCLAW_CONFIG_DIR={config_dir}", extra=debug_style)
@@ -1916,9 +1970,9 @@ def prepare_openclaw_env(cwd):
log.debug(f"OPENCLAW_GATEWAY_PORT={gateway_port}", extra=debug_style)
log.debug(f"OPENCLAW_BRIDGE_PORT={bridge_port}", extra=debug_style)
log.debug(f"OPENCLAW_GATEWAY_BIND={gateway_bind}", extra=debug_style)
log.debug(f"OPENCLAW_GATEWAY_TOKEN={elide(gateway_token)}", extra=debug_style)
log.debug(f"OPENCLAW_GATEWAY_PASSWORD={elide(gateway_password)}", extra=debug_style)
log.debug(f"OPENAI_API_KEY={openapi_key}", extra=debug_style)
log.info(f".env file created at {output_path}", extra=log_bright)
return True
def prepare_openclaw_config(cwd, env_vars):
@@ -2055,6 +2109,8 @@ def prepare_openclaw_config(cwd, env_vars):
backup_path = dst_path.with_name(f"openclaw.json.bak.{timestamp}")
log.info(f"Creating backup: {backup_path}")
shutil.copy(dst_path, backup_path)
dst_dir = pathlib.Path.home() / ".openclaw" / "workspace"
dst_dir.mkdir(parents=True, exist_ok=True)
try:
log.info(f"Writing config to {dst_path}")
@@ -2329,11 +2385,13 @@ def start_openclaw(
if system == 'Windows':
convert_line_endings(oc_script)
oc_script = oc_script.replace("\\", "/")
oc_script = "".join(["./", oc_script])
oc_prefix = "../" if cwd else "./"
oc_script = "".join([oc_prefix, oc_script])
if system == "Windows":
cmd = ["wsl", "-e"] + cmd
cmd_args = ["--sandbox"]
if build:
cmd_args.append("--build")
if onboard_store['b']:
# --- Onboarding + Configuration loop ---
onboarding_cmd = cmd + [
@@ -2363,8 +2421,6 @@ def start_openclaw(
# --- Gateway start ---
if environment == "public":
cmd_args.extend(["--environment", "public"])
if build:
cmd.append("--build")
gateway_cmd = cmd + [
f'{oc_script} --gateway {" ".join(cmd_args)}'
]
@@ -2420,7 +2476,7 @@ def _openclaw_run_with_retries(
attempts = 0
while attempts < max_attempts:
try:
run_command(cmd, cwd=cwd)
run_command(cmd, cwd=cwd, re_raise=True)
log.info(f"{action_name} completed successfully.")
return 0 # success → exit loop
except Exception as e: