mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
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.
250 lines
5.4 KiB
Bash
Executable File
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
|