diff --git a/.vscode/launch.json b/.vscode/launch.json index eaea56a..fc3ac20 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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", diff --git a/access/auto_config.sh b/access/auto_config.sh index fe01a27..0172886 100644 --- a/access/auto_config.sh +++ b/access/auto_config.sh @@ -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 diff --git a/openclaw_ctl b/openclaw_ctl index 12825e4..27f6a9f 100644 --- a/openclaw_ctl +++ b/openclaw_ctl @@ -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 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 "/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 " 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 diff --git a/suite_services.py b/suite_services.py index cd7b1d9..99086ed 100644 --- a/suite_services.py +++ b/suite_services.py @@ -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: