Files
NanoKVM-MIRROR/kvmapp/system/init.d/S96picoclaw
SiYue-ZO 64ce12cc0d feat(mcp): add authenticated remote control backend
Add an MCP Streamable HTTP server with token authentication, screenshot capture, keyboard, and mouse tools.

Introduce shared control-mode and input-control coordination so MCP, PicoClaw, and local HID paths serialize ownership safely.

Integrate PicoClaw gateway/runtime control handoff with PID-managed startup and focused unit coverage.
2026-07-29 15:16:06 +08:00

250 lines
5.4 KiB
Bash
Executable File

#!/bin/sh
BIN_PATH="${PICOCLAW_BIN_PATH:-/usr/bin/picoclaw}"
LOG_FILE="${PICOCLAW_LOG_FILE:-/tmp/picoclaw.log}"
BIN_NAME="$(basename "$BIN_PATH")"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
resolve_user_home() {
user_name="$(id -un 2>/dev/null || echo root)"
user_home="$(awk -F: -v user="$user_name" '$1 == user { print $6; exit }' /etc/passwd)"
if [ -n "$user_home" ] && [ -d "$user_home" ]; then
printf '%s\n' "$user_home"
return 0
fi
if [ -n "${HOME:-}" ] && [ -d "${HOME}" ]; then
printf '%s\n' "$HOME"
return 0
fi
printf '%s\n' "/root"
}
setup_picoclaw_home() {
USER_HOME="$(resolve_user_home)"
PICOCLAW_HOME="${PICOCLAW_HOME:-$USER_HOME/.picoclaw}"
HOME="$USER_HOME"
export HOME
export PICOCLAW_HOME
mkdir -p "$PICOCLAW_HOME" || {
echo "failed to create picoclaw home: $PICOCLAW_HOME"
return 1
}
PICOCLAW_PID_FILE="${PICOCLAW_PID_FILE:-$PICOCLAW_HOME/.picoclaw.pid}"
export PICOCLAW_PID_FILE
cd "$PICOCLAW_HOME" || {
echo "failed to enter picoclaw home: $PICOCLAW_HOME"
return 1
}
}
setup_runtime_env() {
[ -x "$BIN_PATH" ] || {
echo "picoclaw binary not found: $BIN_PATH"
return 1
}
setup_picoclaw_home || return 1
}
read_pid_file() {
[ -f "$PICOCLAW_PID_FILE" ] || return 1
pid="$(sed -n '1p' "$PICOCLAW_PID_FILE" 2>/dev/null | tr -d '[:space:]')"
case "$pid" in
''|*[!0-9]*)
return 1
;;
esac
printf '%s\n' "$pid"
}
is_gateway_pid() {
pid="$1"
[ -n "$pid" ] || return 1
[ -r "/proc/$pid/cmdline" ] || return 1
cmd_path="$(tr '\000' '\n' <"/proc/$pid/cmdline" 2>/dev/null | sed -n '1p')"
subcommand="$(tr '\000' '\n' <"/proc/$pid/cmdline" 2>/dev/null | sed -n '2p')"
[ "$(basename "$cmd_path")" = "$BIN_NAME" ] && [ "$subcommand" = "gateway" ] && return 0
return 1
}
find_gateway_pid() {
for proc_dir in /proc/[0-9]*; do
[ -d "$proc_dir" ] || continue
pid="${proc_dir#/proc/}"
if is_gateway_pid "$pid"; then
printf '%s\n' "$pid"
return 0
fi
done
return 1
}
cleanup_stale_pid_file() {
[ -f "$PICOCLAW_PID_FILE" ] || return 0
pid="$(read_pid_file 2>/dev/null)" || {
rm -f "$PICOCLAW_PID_FILE"
return 0
}
if ! is_gateway_pid "$pid"; then
echo "picoclaw stale pid file removed: $PICOCLAW_PID_FILE"
rm -f "$PICOCLAW_PID_FILE"
fi
}
gateway_running_pid() {
pid="$(read_pid_file 2>/dev/null)" && is_gateway_pid "$pid" && {
printf '%s\n' "$pid"
return 0
}
cleanup_stale_pid_file
pid="$(find_gateway_pid 2>/dev/null)" || return 1
printf '%s\n' "$pid" >"$PICOCLAW_PID_FILE" 2>/dev/null || true
printf '%s\n' "$pid"
}
resolve_kvm_control_source() {
for candidate in \
"/kvmapp/picoclaw/skills/kvm-control" \
"/kvmapp/skills/kvm-control" \
"$SCRIPT_DIR/../../../picoclaw/skills/kvm-control"
do
if [ -d "$candidate" ] && [ -f "$candidate/SKILL.md" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
return 1
}
sync_kvm_control_skill() {
source_dir="$(resolve_kvm_control_source)" || {
echo "kvm-control skill source not found"
return 1
}
skills_dir="$PICOCLAW_HOME/workspace/skills"
target_dir="$skills_dir/kvm-control"
mkdir -p "$skills_dir" || {
echo "failed to create skills directory: $skills_dir"
return 1
}
rm -rf "$target_dir" || {
echo "failed to remove old kvm-control skill: $target_dir"
return 1
}
cp -R "$source_dir" "$target_dir" || {
echo "failed to copy kvm-control skill to $target_dir"
return 1
}
}
sync_agent_profile() {
source_file="/kvmapp/picoclaw/AGENT_KVM.md"
target_file="$PICOCLAW_HOME/workspace/AGENT.md"
workspace_dir="$PICOCLAW_HOME/workspace"
[ -f "$source_file" ] || {
echo "agent profile source not found: $source_file"
return 1
}
mkdir -p "$workspace_dir" || {
echo "failed to create workspace directory: $workspace_dir"
return 1
}
cp -f "$source_file" "$target_file" || {
echo "failed to copy agent profile to $target_file"
return 1
}
}
start_service() {
setup_runtime_env || return 1
if pid="$(gateway_running_pid)"; then
echo "picoclaw gateway already running (PID: $pid)"
return 0
fi
if [ ! -f "$PICOCLAW_HOME/config.json" ]; then
echo "picoclaw config not found: $PICOCLAW_HOME/config.json"
return 1
fi
"$BIN_PATH" gateway >>"$LOG_FILE" 2>&1 &
gateway_pid="$!"
printf '%s\n' "$gateway_pid" >"$PICOCLAW_PID_FILE" || {
echo "failed to write picoclaw pid file: $PICOCLAW_PID_FILE"
return 1
}
echo "picoclaw gateway started (PID: $gateway_pid)"
}
onboard_service() {
setup_runtime_env || return 1
"$BIN_PATH" onboard >>"$LOG_FILE" 2>&1 || return 1
sync_kvm_control_skill || return 1
sync_agent_profile || return 1
}
stop_service() {
setup_picoclaw_home || return 1
pid="$(gateway_running_pid 2>/dev/null)" || {
cleanup_stale_pid_file
return 0
}
kill "$pid" 2>/dev/null || true
count=0
while is_gateway_pid "$pid"; do
count=$((count + 1))
if [ "$count" -ge 15 ]; then
echo "picoclaw gateway did not stop after TERM, killing PID: $pid"
kill -9 "$pid" 2>/dev/null || true
break
fi
sleep 1
done
rm -f "$PICOCLAW_PID_FILE"
}
case "$1" in
start)
start_service
;;
stop)
stop_service
echo "OK"
;;
onboard)
onboard_service
;;
restart)
stop_service
start_service
echo "OK"
;;
*)
echo "Usage: $0 {start|stop|onboard|restart}"
exit 1
;;
esac