Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 165 additions & 8 deletions scripts/start-services.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ while [ $# -gt 0 ]; do
done

PIDDIR="/tmp/nemoclaw-services-${SANDBOX_NAME}"
SANDBOX_STATE_FILE="$PIDDIR/sandbox.name"

GREEN='\033[0;32m'
RED='\033[0;31m'
Expand All @@ -56,8 +57,51 @@ fail() {
exit 1
}

# Validate identifiers to prevent shell injection in ProxyCommand
validate_name() {
case "$1" in
(*[!A-Za-z0-9._-]*|'') fail "Invalid identifier: '$1'" ;;
esac
}

# Resolve sandbox name: explicit flag > persisted state > auto-detect
resolve_sandbox() {
if [ "$SANDBOX_NAME" != "default" ]; then
printf '%s\n' "$SANDBOX_NAME"
return
fi
if [ -f "$SANDBOX_STATE_FILE" ]; then
cat "$SANDBOX_STATE_FILE"
return
fi
openshell sandbox list --names 2>/dev/null | head -1
}

is_running() {
local pidfile="$PIDDIR/$1.pid"
local name="$1"
local pidfile="$PIDDIR/$name.pid"

# For openclaw-gateway, probe inside the sandbox via SSH so the check
# is independent of the local port-forward tunnel state.
if [ "$name" = "openclaw-gateway" ]; then
if command -v openshell >/dev/null 2>&1; then
local sandbox gateway_name proxy_cmd
sandbox="$(resolve_sandbox)"
[ -n "$sandbox" ] || return 1
gateway_name="$(openshell gateway info 2>/dev/null | grep -oP 'Gateway:\s+\K\S+' || echo 'openshell')"
printf -v proxy_cmd 'openshell ssh-proxy --gateway-name %q --name %q' "$gateway_name" "$sandbox"
ssh -o "ProxyCommand=$proxy_cmd" \
-o StrictHostKeyChecking=accept-new \
-o UserKnownHostsFile="$PIDDIR/openshell-known_hosts" \
-o LogLevel=ERROR \
-o ConnectTimeout=5 \
sandbox@"openshell-$sandbox" \
"curl -sf --max-time 2 http://127.0.0.1:$DASHBOARD_PORT/ >/dev/null" 2>/dev/null
return $?
fi
return 1
fi

if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
return 0
fi
Expand All @@ -68,7 +112,11 @@ start_service() {
local name="$1"
shift
if is_running "$name"; then
info "$name already running (PID $(cat "$PIDDIR/$name.pid"))"
if [ "$name" = "openclaw-gateway" ]; then
info "$name already running (port $DASHBOARD_PORT healthy)"
else
info "$name already running (PID $(cat "$PIDDIR/$name.pid"))"
fi
return 0
fi
nohup "$@" >"$PIDDIR/$name.log" 2>&1 &
Expand Down Expand Up @@ -97,11 +145,17 @@ stop_service() {
show_status() {
mkdir -p "$PIDDIR"
echo ""
if is_running cloudflared; then
echo -e " ${GREEN}●${NC} cloudflared (PID $(cat "$PIDDIR/cloudflared.pid"))"
else
echo -e " ${RED}●${NC} cloudflared (stopped)"
fi
for svc in openclaw-gateway gateway-forward telegram-bridge cloudflared; do
if is_running "$svc"; then
if [ "$svc" = "openclaw-gateway" ]; then
echo -e " ${GREEN}●${NC} $svc (healthy on port $DASHBOARD_PORT)"
else
echo -e " ${GREEN}●${NC} $svc (PID $(cat "$PIDDIR/$svc.pid"))"
fi
else
echo -e " ${RED}●${NC} $svc (stopped)"
fi
done
echo ""

if [ -f "$PIDDIR/cloudflared.log" ]; then
Expand All @@ -116,12 +170,100 @@ show_status() {
do_stop() {
mkdir -p "$PIDDIR"
stop_service cloudflared
stop_service telegram-bridge
stop_service gateway-forward
stop_service openclaw-gateway
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Stop the gateway process inside the sandbox (the local PID is just
# the SSH wrapper; the actual process runs inside the sandbox).
if command -v openshell >/dev/null 2>&1; then
local sandbox gateway_name
sandbox="$(resolve_sandbox)"
gateway_name="$(openshell gateway info 2>/dev/null | grep -oP 'Gateway:\s+\K\S+' || echo 'openshell')"
if [ -n "$sandbox" ]; then
validate_name "$sandbox"
validate_name "$gateway_name"
local proxy_cmd
printf -v proxy_cmd 'openshell ssh-proxy --gateway-name %q --name %q' "$gateway_name" "$sandbox"
info "Stopping gateway inside sandbox '$sandbox'..."
ssh -o "ProxyCommand=$proxy_cmd" \
-o StrictHostKeyChecking=accept-new \
-o UserKnownHostsFile="$PIDDIR/openshell-known_hosts" \
-o LogLevel=ERROR \
-o ConnectTimeout=10 \
sandbox@"openshell-$sandbox" \
'openclaw gateway stop 2>/dev/null; true' 2>/dev/null || true
fi
fi
info "All services stopped."
}

do_start() {
mkdir -p "$PIDDIR"

# ── OpenClaw gateway inside sandbox ──────────────────────────────
# Start the OpenClaw gateway inside the sandbox and forward
# port 18789 to the host so external dashboards (e.g. Mission
# Control) can connect via WebSocket.
if command -v openshell >/dev/null 2>&1; then
local sandbox gateway_name
sandbox="$(resolve_sandbox)"
gateway_name="$(openshell gateway info 2>/dev/null | grep -oP 'Gateway:\s+\K\S+' || echo 'openshell')"
if [ -n "$sandbox" ]; then
validate_name "$sandbox"
validate_name "$gateway_name"
# Persist resolved sandbox so stop uses the same target
printf '%s\n' "$sandbox" > "$SANDBOX_STATE_FILE"
local gw_token="${OPENCLAW_GATEWAY_TOKEN:-$(head -c 24 /dev/urandom | xxd -p)}"
local proxy_cmd
printf -v proxy_cmd 'openshell ssh-proxy --gateway-name %q --name %q' "$gateway_name" "$sandbox"
local known_hosts_file="$PIDDIR/openshell-known_hosts"

# Start gateway inside sandbox (idempotent — skips if already running)
if ! is_running "openclaw-gateway"; then
info "Starting OpenClaw gateway inside sandbox '$sandbox'..."
# Write token to a temp file readable only by us, pipe it to ssh stdin
local token_file
token_file="$(mktemp "$PIDDIR/gw-token.XXXXXX")"
chmod 600 "$token_file"
printf '%s\n' "$gw_token" > "$token_file"
start_service openclaw-gateway \
sh -c "ssh -o 'ProxyCommand=$proxy_cmd' \
-o StrictHostKeyChecking=accept-new \
-o 'UserKnownHostsFile=$known_hosts_file' \
-o LogLevel=ERROR \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
sandbox@openshell-$sandbox \
'read -r token; export OPENCLAW_GATEWAY_TOKEN=\"\$token\"; exec openclaw gateway run' \
< '$token_file'; rm -f '$token_file'"
sleep 5
fi

# Forward port 18789 from sandbox to host (idempotent)
# Bind to loopback only; use cloudflared for external access
if ! is_running "gateway-forward"; then
info "Forwarding port $DASHBOARD_PORT from sandbox..."
start_service gateway-forward \
ssh -N -L "127.0.0.1:$DASHBOARD_PORT:127.0.0.1:$DASHBOARD_PORT" \
-o "ProxyCommand=$proxy_cmd" \
-o StrictHostKeyChecking=accept-new \
-o UserKnownHostsFile="$known_hosts_file" \
-o LogLevel=ERROR \
-o ServerAliveInterval=15 \
-o ServerAliveCountMax=3 \
sandbox@"openshell-$sandbox"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
sleep 3
fi
else
warn "No sandbox found. Gateway and port forwarding skipped."
fi
fi

# Telegram bridge (only if token provided)
if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then
SANDBOX_NAME="$SANDBOX_NAME" start_service telegram-bridge \
node "$REPO_DIR/scripts/telegram-bridge.js"
fi

# cloudflared tunnel
if command -v cloudflared >/dev/null 2>&1; then
start_service cloudflared \
Expand Down Expand Up @@ -158,7 +300,22 @@ do_start() {
printf " │ Public URL: %-40s│\n" "$tunnel_url"
fi

echo " │ Messaging: via OpenClaw native channels (if configured) │"
if is_running openclaw-gateway; then
printf " │ Gateway: %-40s│\n" "healthy (port $DASHBOARD_PORT)"
else
printf " │ Gateway: %-40s│\n" "offline"
fi

if is_running gateway-forward; then
printf " │ Port fwd: %-40s│\n" "$DASHBOARD_PORT → sandbox"
fi

if is_running telegram-bridge; then
echo " │ Telegram: bridge running │"
else
echo " │ Telegram: not started (no token) │"
fi

echo " │ │"
echo " │ Run 'openshell term' to monitor egress approvals │"
echo " └─────────────────────────────────────────────────────┘"
Expand Down