@hitechclaw/clawspark 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +378 -0
- package/clawspark +2715 -0
- package/configs/models.yaml +108 -0
- package/configs/skill-packs.yaml +44 -0
- package/configs/skills.yaml +37 -0
- package/install.sh +387 -0
- package/lib/common.sh +249 -0
- package/lib/detect-hardware.sh +156 -0
- package/lib/diagnose.sh +636 -0
- package/lib/render-diagram.sh +47 -0
- package/lib/sandbox-commands.sh +415 -0
- package/lib/secure.sh +244 -0
- package/lib/select-model.sh +442 -0
- package/lib/setup-browser.sh +138 -0
- package/lib/setup-dashboard.sh +228 -0
- package/lib/setup-inference.sh +128 -0
- package/lib/setup-mcp.sh +142 -0
- package/lib/setup-messaging.sh +242 -0
- package/lib/setup-models.sh +121 -0
- package/lib/setup-openclaw.sh +808 -0
- package/lib/setup-sandbox.sh +188 -0
- package/lib/setup-skills.sh +113 -0
- package/lib/setup-systemd.sh +224 -0
- package/lib/setup-tailscale.sh +188 -0
- package/lib/setup-voice.sh +101 -0
- package/lib/skill-audit.sh +449 -0
- package/lib/verify.sh +177 -0
- package/package.json +57 -0
- package/scripts/release.sh +133 -0
- package/uninstall.sh +161 -0
- package/v2/README.md +50 -0
- package/v2/configs/providers.yaml +79 -0
- package/v2/configs/skills.yaml +36 -0
- package/v2/install.sh +116 -0
- package/v2/lib/common.sh +285 -0
- package/v2/lib/detect-hardware.sh +119 -0
- package/v2/lib/select-runtime.sh +273 -0
- package/v2/lib/setup-extras.sh +95 -0
- package/v2/lib/setup-openclaw.sh +187 -0
- package/v2/lib/setup-provider.sh +131 -0
- package/v2/lib/verify.sh +133 -0
- package/web/index.html +1835 -0
- package/web/install.sh +387 -0
- package/web/logo-hero.svg +11 -0
- package/web/logo-icon.svg +12 -0
- package/web/logo.svg +17 -0
- package/web/vercel.json +8 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# lib/setup-sandbox.sh -- Optional Docker-based sandbox for safe code execution.
|
|
3
|
+
# Enables OpenClaw to run agent-generated code in an isolated container.
|
|
4
|
+
# If Docker is not installed, the installer continues without sandbox support.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
setup_sandbox() {
|
|
8
|
+
# Install Docker if not available
|
|
9
|
+
if ! check_command docker; then
|
|
10
|
+
log_info "Docker not found. Installing for sandboxed code execution..."
|
|
11
|
+
if check_command apt-get; then
|
|
12
|
+
# Linux: install via official Docker convenience script
|
|
13
|
+
(curl -fsSL https://get.docker.com | sh) >> "${CLAWSPARK_LOG}" 2>&1 &
|
|
14
|
+
spinner $! "Installing Docker..."
|
|
15
|
+
if check_command docker; then
|
|
16
|
+
# Add current user to docker group so we don't need sudo
|
|
17
|
+
sudo usermod -aG docker "${USER}" 2>> "${CLAWSPARK_LOG}" || true
|
|
18
|
+
# Start Docker daemon
|
|
19
|
+
sudo systemctl start docker 2>> "${CLAWSPARK_LOG}" || true
|
|
20
|
+
sudo systemctl enable docker 2>> "${CLAWSPARK_LOG}" || true
|
|
21
|
+
log_success "Docker installed."
|
|
22
|
+
else
|
|
23
|
+
log_warn "Docker installation failed -- sandbox will not be available."
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
elif check_command brew; then
|
|
27
|
+
log_info "Installing Docker via Homebrew..."
|
|
28
|
+
(brew install --cask docker) >> "${CLAWSPARK_LOG}" 2>&1 &
|
|
29
|
+
spinner $! "Installing Docker..."
|
|
30
|
+
if [[ -d "/Applications/Docker.app" ]]; then
|
|
31
|
+
log_success "Docker installed. Open Docker.app to start the daemon."
|
|
32
|
+
log_warn "Docker daemon not running yet -- sandbox will be available after starting Docker."
|
|
33
|
+
return 0
|
|
34
|
+
else
|
|
35
|
+
log_warn "Docker installation failed -- sandbox will not be available."
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
else
|
|
39
|
+
log_warn "No package manager found -- install Docker manually for sandbox support."
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Check if Docker daemon is running
|
|
45
|
+
if ! docker info &>/dev/null; then
|
|
46
|
+
# Try to start it
|
|
47
|
+
if check_command systemctl; then
|
|
48
|
+
sudo systemctl start docker 2>> "${CLAWSPARK_LOG}" || true
|
|
49
|
+
sleep 2
|
|
50
|
+
fi
|
|
51
|
+
if ! docker info &>/dev/null; then
|
|
52
|
+
log_warn "Docker is installed but daemon is not running -- sandbox skipped."
|
|
53
|
+
log_info "Start Docker and run: clawspark sandbox on"
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
log_info "Setting up Docker sandbox for safe code execution..."
|
|
59
|
+
|
|
60
|
+
# ── Create sandbox directory and Dockerfile ───────────────────────────────
|
|
61
|
+
local sandbox_dir="${CLAWSPARK_DIR}/sandbox"
|
|
62
|
+
mkdir -p "${sandbox_dir}"
|
|
63
|
+
|
|
64
|
+
cat > "${sandbox_dir}/Dockerfile" <<'DOCKERFILE'
|
|
65
|
+
FROM ubuntu:22.04
|
|
66
|
+
|
|
67
|
+
# Non-interactive apt
|
|
68
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
69
|
+
|
|
70
|
+
# Install common dev tools
|
|
71
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
72
|
+
python3 python3-pip python3-venv \
|
|
73
|
+
nodejs npm \
|
|
74
|
+
git curl wget jq \
|
|
75
|
+
build-essential \
|
|
76
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
77
|
+
|
|
78
|
+
# Create non-root user
|
|
79
|
+
RUN useradd -m -s /bin/bash sandbox
|
|
80
|
+
USER sandbox
|
|
81
|
+
WORKDIR /sandbox
|
|
82
|
+
|
|
83
|
+
# Set up Python virtual env
|
|
84
|
+
RUN python3 -m venv /sandbox/.venv
|
|
85
|
+
ENV PATH="/sandbox/.venv/bin:$PATH"
|
|
86
|
+
|
|
87
|
+
# Install common Python packages
|
|
88
|
+
RUN pip install --no-cache-dir \
|
|
89
|
+
requests flask fastapi uvicorn \
|
|
90
|
+
pandas numpy matplotlib \
|
|
91
|
+
beautifulsoup4 lxml
|
|
92
|
+
|
|
93
|
+
CMD ["/bin/bash"]
|
|
94
|
+
DOCKERFILE
|
|
95
|
+
|
|
96
|
+
# ── Seccomp profile (block dangerous syscalls) ────────────────────────────
|
|
97
|
+
cat > "${sandbox_dir}/seccomp-profile.json" <<'SECCOMP'
|
|
98
|
+
{
|
|
99
|
+
"defaultAction": "SCMP_ACT_ALLOW",
|
|
100
|
+
"syscalls": [
|
|
101
|
+
{
|
|
102
|
+
"names": [
|
|
103
|
+
"mount", "umount2", "pivot_root",
|
|
104
|
+
"swapon", "swapoff",
|
|
105
|
+
"reboot", "kexec_load", "kexec_file_load",
|
|
106
|
+
"init_module", "finit_module", "delete_module",
|
|
107
|
+
"acct", "settimeofday", "clock_settime",
|
|
108
|
+
"ptrace",
|
|
109
|
+
"add_key", "request_key", "keyctl",
|
|
110
|
+
"unshare", "setns"
|
|
111
|
+
],
|
|
112
|
+
"action": "SCMP_ACT_ERRNO",
|
|
113
|
+
"errnoRet": 1
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
SECCOMP
|
|
118
|
+
|
|
119
|
+
# ── Build the sandbox image ───────────────────────────────────────────────
|
|
120
|
+
log_info "Building sandbox image (this may take a minute)..."
|
|
121
|
+
(docker build -t clawspark-sandbox:latest "${sandbox_dir}") >> "${CLAWSPARK_LOG}" 2>&1 &
|
|
122
|
+
spinner $! "Building clawspark-sandbox image..."
|
|
123
|
+
|
|
124
|
+
if docker image inspect clawspark-sandbox:latest &>/dev/null; then
|
|
125
|
+
log_success "Sandbox image built: clawspark-sandbox:latest"
|
|
126
|
+
else
|
|
127
|
+
log_warn "Sandbox image build failed. Check ${CLAWSPARK_LOG}."
|
|
128
|
+
return 0
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# NOTE: We do NOT write sandbox config to openclaw.json during install.
|
|
132
|
+
# Setting agents.defaults.sandbox with network:"none" breaks the main agent's
|
|
133
|
+
# network access. The sandbox image and seccomp profile are ready for use via
|
|
134
|
+
# "clawspark sandbox on" which will enable it when the user explicitly wants it.
|
|
135
|
+
# The sandbox run.sh helper works standalone without any openclaw.json changes.
|
|
136
|
+
|
|
137
|
+
# Persist sandbox state as "off" (available but not active)
|
|
138
|
+
echo "false" > "${CLAWSPARK_DIR}/sandbox.state"
|
|
139
|
+
log_success "Sandbox image and seccomp profile ready."
|
|
140
|
+
log_info "Enable later with: clawspark sandbox on"
|
|
141
|
+
|
|
142
|
+
# ── Helper script for manual sandbox use ──────────────────────────────────
|
|
143
|
+
cat > "${sandbox_dir}/run.sh" <<'RUNSH'
|
|
144
|
+
#!/usr/bin/env bash
|
|
145
|
+
# run.sh -- Run a command inside the clawspark sandbox.
|
|
146
|
+
# Usage: ~/.clawspark/sandbox/run.sh <command>
|
|
147
|
+
# Example: ~/.clawspark/sandbox/run.sh python3 -c "print('hello')"
|
|
148
|
+
set -euo pipefail
|
|
149
|
+
|
|
150
|
+
SANDBOX_DIR="${HOME}/.clawspark/sandbox"
|
|
151
|
+
|
|
152
|
+
if ! command -v docker &>/dev/null; then
|
|
153
|
+
echo "Error: Docker is not installed." >&2
|
|
154
|
+
exit 1
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
if ! docker info &>/dev/null; then
|
|
158
|
+
echo "Error: Docker daemon is not running." >&2
|
|
159
|
+
exit 1
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
if ! docker image inspect clawspark-sandbox:latest &>/dev/null; then
|
|
163
|
+
echo "Error: Sandbox image not found. Run install.sh or rebuild with:" >&2
|
|
164
|
+
echo " docker build -t clawspark-sandbox:latest ${SANDBOX_DIR}" >&2
|
|
165
|
+
exit 1
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
docker run --rm -it \
|
|
169
|
+
--read-only \
|
|
170
|
+
--tmpfs /tmp:size=100m \
|
|
171
|
+
--tmpfs /sandbox/work:size=500m \
|
|
172
|
+
--network=none \
|
|
173
|
+
--cap-drop=ALL \
|
|
174
|
+
--security-opt=no-new-privileges \
|
|
175
|
+
--security-opt="seccomp=${SANDBOX_DIR}/seccomp-profile.json" \
|
|
176
|
+
--memory=1g \
|
|
177
|
+
--cpus=2 \
|
|
178
|
+
--pids-limit=200 \
|
|
179
|
+
-v "${PWD}:/sandbox/code:ro" \
|
|
180
|
+
clawspark-sandbox:latest \
|
|
181
|
+
"$@"
|
|
182
|
+
RUNSH
|
|
183
|
+
chmod +x "${sandbox_dir}/run.sh"
|
|
184
|
+
|
|
185
|
+
log_success "Sandbox setup complete."
|
|
186
|
+
log_info "Sub-agent code execution will run in isolated Docker containers."
|
|
187
|
+
log_info "Manual use: ~/.clawspark/sandbox/run.sh <command>"
|
|
188
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# lib/setup-skills.sh — Reads skills.yaml and installs each enabled skill via clawhub.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
setup_skills() {
|
|
6
|
+
log_info "Installing OpenClaw skills..."
|
|
7
|
+
hr
|
|
8
|
+
|
|
9
|
+
# ── Locate skills.yaml ──────────────────────────────────────────────────
|
|
10
|
+
local skills_file=""
|
|
11
|
+
local search_paths=(
|
|
12
|
+
"${CLAWSPARK_DIR}/skills.yaml"
|
|
13
|
+
"${SCRIPT_DIR:-/opt/clawspark}/configs/skills.yaml"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
for candidate in "${search_paths[@]}"; do
|
|
17
|
+
if [[ -f "${candidate}" ]]; then
|
|
18
|
+
skills_file="${candidate}"
|
|
19
|
+
break
|
|
20
|
+
fi
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
if [[ -z "${skills_file}" ]]; then
|
|
24
|
+
log_warn "No skills.yaml found — skipping skill installation."
|
|
25
|
+
return 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
log_info "Using skills config: ${skills_file}"
|
|
29
|
+
|
|
30
|
+
# Copy to CLAWSPARK_DIR if not already there
|
|
31
|
+
if [[ "${skills_file}" != "${CLAWSPARK_DIR}/skills.yaml" ]]; then
|
|
32
|
+
cp "${skills_file}" "${CLAWSPARK_DIR}/skills.yaml"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
local -a skills=()
|
|
36
|
+
while IFS= read -r slug; do
|
|
37
|
+
skills+=("${slug}")
|
|
38
|
+
done < <(_parse_enabled_skills "${skills_file}")
|
|
39
|
+
|
|
40
|
+
if [[ ${#skills[@]} -eq 0 ]]; then
|
|
41
|
+
log_warn "No skills found under 'enabled:' in ${skills_file}."
|
|
42
|
+
return 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
log_info "Found ${#skills[@]} skill(s) to install."
|
|
46
|
+
|
|
47
|
+
# ── Fix npm cache permissions (root-owned ~/.npm from sudo npm) ─────────
|
|
48
|
+
_fix_npm_cache_perms
|
|
49
|
+
|
|
50
|
+
# ── Install each skill ──────────────────────────────────────────────────
|
|
51
|
+
local installed=0
|
|
52
|
+
local failed=0
|
|
53
|
+
|
|
54
|
+
local skill_timeout=120 # 2 minutes max per skill
|
|
55
|
+
|
|
56
|
+
for skill in "${skills[@]}"; do
|
|
57
|
+
printf ' %s→%s Installing %s%s%s ... ' "${CYAN}" "${RESET}" "${BOLD}" "${skill}" "${RESET}"
|
|
58
|
+
if timeout "${skill_timeout}" npx --yes clawhub@latest install --force "${skill}" >> "${CLAWSPARK_LOG}" 2>&1; then
|
|
59
|
+
printf '%s✓%s\n' "${GREEN}" "${RESET}"
|
|
60
|
+
installed=$(( installed + 1 ))
|
|
61
|
+
else
|
|
62
|
+
local ec=$?
|
|
63
|
+
if [[ ${ec} -eq 124 ]]; then
|
|
64
|
+
printf '%s✗ (timed out after %ds, skipping)%s\n' "${YELLOW}" "${skill_timeout}" "${RESET}"
|
|
65
|
+
else
|
|
66
|
+
printf '%s✗ (failed, continuing)%s\n' "${YELLOW}" "${RESET}"
|
|
67
|
+
fi
|
|
68
|
+
log_warn "Skill '${skill}' failed to install — see ${CLAWSPARK_LOG}"
|
|
69
|
+
failed=$(( failed + 1 ))
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
printf '\n'
|
|
74
|
+
log_success "Skills installed: ${installed} succeeded, ${failed} failed."
|
|
75
|
+
|
|
76
|
+
# ── Install essential community skills (web search, research) ─────────
|
|
77
|
+
_install_community_skills
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ── Community skills that don't need API keys ─────────────────────────────
|
|
81
|
+
_install_community_skills() {
|
|
82
|
+
log_info "Installing community web search skills..."
|
|
83
|
+
local -a community_skills=(
|
|
84
|
+
"ddg-web-search"
|
|
85
|
+
"deep-research-pro"
|
|
86
|
+
"local-web-search-skill"
|
|
87
|
+
)
|
|
88
|
+
for skill in "${community_skills[@]}"; do
|
|
89
|
+
printf ' %s->%s Installing %s%s%s ... ' "${CYAN}" "${RESET}" "${BOLD}" "${skill}" "${RESET}"
|
|
90
|
+
if timeout 120 npx --yes clawhub@latest install --force "${skill}" >> "${CLAWSPARK_LOG}" 2>&1; then
|
|
91
|
+
printf '%sOK%s\n' "${GREEN}" "${RESET}"
|
|
92
|
+
else
|
|
93
|
+
printf '%sskipped%s\n' "${YELLOW}" "${RESET}"
|
|
94
|
+
fi
|
|
95
|
+
done
|
|
96
|
+
log_success "Community skills installed (web search, deep research)."
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ── Fix root-owned npm cache (common after sudo npm install -g) ───────────
|
|
100
|
+
_fix_npm_cache_perms() {
|
|
101
|
+
local npm_cache="${HOME}/.npm"
|
|
102
|
+
if [[ -d "${npm_cache}" ]]; then
|
|
103
|
+
# Check if any files are owned by root (uid 0)
|
|
104
|
+
local root_files
|
|
105
|
+
root_files=$(find "${npm_cache}" -maxdepth 2 -uid 0 2>/dev/null | head -1)
|
|
106
|
+
if [[ -n "${root_files}" ]]; then
|
|
107
|
+
log_info "Fixing npm cache permissions (root-owned files in ~/.npm)..."
|
|
108
|
+
sudo chown -R "$(id -u):$(id -g)" "${npm_cache}" 2>/dev/null || {
|
|
109
|
+
log_warn "Could not fix ~/.npm permissions. Skills may fail to install."
|
|
110
|
+
}
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# lib/setup-systemd.sh -- Creates systemd services for OpenClaw gateway,
|
|
3
|
+
# node host, and ClawMetry dashboard so they auto-start on boot.
|
|
4
|
+
# Skipped on platforms without systemd (macOS, containers).
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
setup_systemd_services() {
|
|
8
|
+
# Skip on non-systemd platforms (macOS, Docker, WSL1, etc.)
|
|
9
|
+
if ! check_command systemctl; then
|
|
10
|
+
log_info "systemd not available -- services will use PID-based management."
|
|
11
|
+
return 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Verify systemd is actually running (not just the binary present)
|
|
15
|
+
# is-system-running returns non-zero for "degraded" (common when minor
|
|
16
|
+
# services fail), so we must check the output text, not the exit code.
|
|
17
|
+
local sys_state
|
|
18
|
+
sys_state=$(systemctl is-system-running 2>/dev/null || true)
|
|
19
|
+
case "${sys_state}" in
|
|
20
|
+
running|degraded|starting|initializing)
|
|
21
|
+
log_info "systemd active (state: ${sys_state})."
|
|
22
|
+
;;
|
|
23
|
+
*)
|
|
24
|
+
log_info "systemd not active (state: ${sys_state}) -- skipping service creation."
|
|
25
|
+
return 0
|
|
26
|
+
;;
|
|
27
|
+
esac
|
|
28
|
+
|
|
29
|
+
log_info "Creating systemd services for auto-start on boot..."
|
|
30
|
+
|
|
31
|
+
local user_name
|
|
32
|
+
user_name=$(whoami)
|
|
33
|
+
local user_home="${HOME}"
|
|
34
|
+
|
|
35
|
+
# Find binary paths
|
|
36
|
+
local openclaw_bin
|
|
37
|
+
openclaw_bin=$(command -v openclaw 2>/dev/null || echo "")
|
|
38
|
+
if [[ -z "${openclaw_bin}" ]]; then
|
|
39
|
+
local npm_bin
|
|
40
|
+
npm_bin="$(npm config get prefix 2>/dev/null)/bin"
|
|
41
|
+
[[ -x "${npm_bin}/openclaw" ]] && openclaw_bin="${npm_bin}/openclaw"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if [[ -z "${openclaw_bin}" ]]; then
|
|
45
|
+
log_warn "openclaw binary not found -- skipping systemd setup."
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
local env_file="${user_home}/.openclaw/gateway.env"
|
|
50
|
+
|
|
51
|
+
# Compute a comprehensive PATH for systemd (it starts with minimal PATH)
|
|
52
|
+
# The env file also has PATH, but belt-and-suspenders is safer here
|
|
53
|
+
local npm_prefix_bin
|
|
54
|
+
npm_prefix_bin="$(npm config get prefix 2>/dev/null)/bin"
|
|
55
|
+
local svc_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
56
|
+
[[ -d "${npm_prefix_bin}" ]] && svc_path="${npm_prefix_bin}:${svc_path}"
|
|
57
|
+
[[ -d "${user_home}/.npm-global/bin" ]] && svc_path="${user_home}/.npm-global/bin:${svc_path}"
|
|
58
|
+
[[ -d "/snap/bin" ]] && svc_path="${svc_path}:/snap/bin"
|
|
59
|
+
|
|
60
|
+
# ── Gateway service ──────────────────────────────────────────────────────
|
|
61
|
+
sudo tee /etc/systemd/system/clawspark-gateway.service > /dev/null <<GWEOF
|
|
62
|
+
[Unit]
|
|
63
|
+
Description=clawspark OpenClaw Gateway
|
|
64
|
+
After=network.target ollama.service
|
|
65
|
+
Wants=ollama.service
|
|
66
|
+
|
|
67
|
+
[Service]
|
|
68
|
+
Type=simple
|
|
69
|
+
User=${user_name}
|
|
70
|
+
Environment=HOME=${user_home}
|
|
71
|
+
Environment=PATH=${svc_path}
|
|
72
|
+
EnvironmentFile=-${env_file}
|
|
73
|
+
ExecStart=${openclaw_bin} gateway run --bind loopback
|
|
74
|
+
Restart=on-failure
|
|
75
|
+
RestartSec=5
|
|
76
|
+
StandardOutput=append:${user_home}/.clawspark/gateway.log
|
|
77
|
+
StandardError=append:${user_home}/.clawspark/gateway.log
|
|
78
|
+
|
|
79
|
+
[Install]
|
|
80
|
+
WantedBy=multi-user.target
|
|
81
|
+
GWEOF
|
|
82
|
+
log_success "Created clawspark-gateway.service"
|
|
83
|
+
|
|
84
|
+
# ── Node host service ────────────────────────────────────────────────────
|
|
85
|
+
sudo tee /etc/systemd/system/clawspark-nodehost.service > /dev/null <<NHEOF
|
|
86
|
+
[Unit]
|
|
87
|
+
Description=clawspark OpenClaw Node Host
|
|
88
|
+
After=clawspark-gateway.service
|
|
89
|
+
Requires=clawspark-gateway.service
|
|
90
|
+
|
|
91
|
+
[Service]
|
|
92
|
+
Type=simple
|
|
93
|
+
User=${user_name}
|
|
94
|
+
Environment=HOME=${user_home}
|
|
95
|
+
Environment=PATH=${svc_path}
|
|
96
|
+
EnvironmentFile=-${env_file}
|
|
97
|
+
ExecStartPre=/bin/sleep 3
|
|
98
|
+
ExecStart=${openclaw_bin} node run --host 127.0.0.1 --port 18789
|
|
99
|
+
Restart=on-failure
|
|
100
|
+
RestartSec=5
|
|
101
|
+
StandardOutput=append:${user_home}/.clawspark/node.log
|
|
102
|
+
StandardError=append:${user_home}/.clawspark/node.log
|
|
103
|
+
|
|
104
|
+
[Install]
|
|
105
|
+
WantedBy=multi-user.target
|
|
106
|
+
NHEOF
|
|
107
|
+
log_success "Created clawspark-nodehost.service"
|
|
108
|
+
|
|
109
|
+
# ── Dashboard service ────────────────────────────────────────────────────
|
|
110
|
+
local clawmetry_bin=""
|
|
111
|
+
if command -v clawmetry &>/dev/null; then
|
|
112
|
+
clawmetry_bin=$(command -v clawmetry)
|
|
113
|
+
elif [[ -x "${user_home}/.local/bin/clawmetry" ]]; then
|
|
114
|
+
clawmetry_bin="${user_home}/.local/bin/clawmetry"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
if [[ -n "${clawmetry_bin}" ]]; then
|
|
118
|
+
sudo tee /etc/systemd/system/clawspark-dashboard.service > /dev/null <<DBEOF
|
|
119
|
+
[Unit]
|
|
120
|
+
Description=clawspark ClawMetry Dashboard
|
|
121
|
+
After=network.target
|
|
122
|
+
|
|
123
|
+
[Service]
|
|
124
|
+
Type=simple
|
|
125
|
+
User=${user_name}
|
|
126
|
+
Environment=HOME=${user_home}
|
|
127
|
+
ExecStart=${clawmetry_bin} --port 8900 --host 127.0.0.1
|
|
128
|
+
Restart=on-failure
|
|
129
|
+
RestartSec=5
|
|
130
|
+
StandardOutput=append:${user_home}/.clawspark/dashboard.log
|
|
131
|
+
StandardError=append:${user_home}/.clawspark/dashboard.log
|
|
132
|
+
|
|
133
|
+
[Install]
|
|
134
|
+
WantedBy=multi-user.target
|
|
135
|
+
DBEOF
|
|
136
|
+
log_success "Created clawspark-dashboard.service"
|
|
137
|
+
else
|
|
138
|
+
log_info "clawmetry binary not found -- dashboard service not created."
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# ── Enable all services ──────────────────────────────────────────────────
|
|
142
|
+
sudo systemctl daemon-reload
|
|
143
|
+
|
|
144
|
+
sudo systemctl enable clawspark-gateway.service >> "${CLAWSPARK_LOG}" 2>&1 || true
|
|
145
|
+
sudo systemctl enable clawspark-nodehost.service >> "${CLAWSPARK_LOG}" 2>&1 || true
|
|
146
|
+
if [[ -n "${clawmetry_bin}" ]]; then
|
|
147
|
+
sudo systemctl enable clawspark-dashboard.service >> "${CLAWSPARK_LOG}" 2>&1 || true
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
log_success "Systemd services enabled -- will auto-start on boot."
|
|
151
|
+
|
|
152
|
+
# ── Migrate running nohup processes to systemd ───────────────────────────
|
|
153
|
+
# Stop the nohup-started processes and let systemd take over.
|
|
154
|
+
# This ensures a clean state right now, not just after next reboot.
|
|
155
|
+
log_info "Migrating running services to systemd..."
|
|
156
|
+
|
|
157
|
+
# Helper: safely kill a PID from a pid file (validates numeric + process identity)
|
|
158
|
+
_safe_kill_pid() {
|
|
159
|
+
local pid_file="$1" name_hint="$2"
|
|
160
|
+
if [[ -f "${pid_file}" ]]; then
|
|
161
|
+
local pid
|
|
162
|
+
pid=$(cat "${pid_file}" 2>/dev/null || echo "")
|
|
163
|
+
if [[ -n "${pid}" ]] && [[ "${pid}" =~ ^[0-9]+$ ]]; then
|
|
164
|
+
# Verify the PID belongs to an openclaw/clawmetry process
|
|
165
|
+
local proc_cmd
|
|
166
|
+
proc_cmd=$(ps -p "${pid}" -o comm= 2>/dev/null || echo "")
|
|
167
|
+
if [[ "${proc_cmd}" == *"openclaw"* ]] || [[ "${proc_cmd}" == *"clawmetry"* ]] || [[ "${proc_cmd}" == *"python"* ]]; then
|
|
168
|
+
kill "${pid}" 2>/dev/null || true
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
rm -f "${pid_file}"
|
|
172
|
+
fi
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_safe_kill_pid "${CLAWSPARK_DIR}/gateway.pid" "gateway"
|
|
176
|
+
_safe_kill_pid "${CLAWSPARK_DIR}/node.pid" "node"
|
|
177
|
+
_safe_kill_pid "${CLAWSPARK_DIR}/dashboard.pid" "dashboard"
|
|
178
|
+
|
|
179
|
+
sleep 2
|
|
180
|
+
|
|
181
|
+
# Start via systemd
|
|
182
|
+
sudo systemctl start clawspark-gateway.service || {
|
|
183
|
+
log_warn "Gateway systemd start failed. Check: sudo journalctl -u clawspark-gateway"
|
|
184
|
+
}
|
|
185
|
+
sleep 3
|
|
186
|
+
sudo systemctl start clawspark-nodehost.service || {
|
|
187
|
+
log_warn "Node host systemd start failed. Check: sudo journalctl -u clawspark-nodehost"
|
|
188
|
+
}
|
|
189
|
+
if [[ -n "${clawmetry_bin}" ]]; then
|
|
190
|
+
sudo systemctl start clawspark-dashboard.service || {
|
|
191
|
+
log_warn "Dashboard systemd start failed. Check: sudo journalctl -u clawspark-dashboard"
|
|
192
|
+
}
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Verify
|
|
196
|
+
sleep 3
|
|
197
|
+
local all_ok=true
|
|
198
|
+
if sudo systemctl is-active --quiet clawspark-gateway.service; then
|
|
199
|
+
log_success "Gateway running via systemd."
|
|
200
|
+
else
|
|
201
|
+
log_warn "Gateway not running via systemd."
|
|
202
|
+
all_ok=false
|
|
203
|
+
fi
|
|
204
|
+
if sudo systemctl is-active --quiet clawspark-nodehost.service; then
|
|
205
|
+
log_success "Node host running via systemd."
|
|
206
|
+
else
|
|
207
|
+
log_warn "Node host not running via systemd."
|
|
208
|
+
all_ok=false
|
|
209
|
+
fi
|
|
210
|
+
if [[ -n "${clawmetry_bin}" ]]; then
|
|
211
|
+
if sudo systemctl is-active --quiet clawspark-dashboard.service; then
|
|
212
|
+
log_success "Dashboard running via systemd."
|
|
213
|
+
else
|
|
214
|
+
log_warn "Dashboard not running via systemd."
|
|
215
|
+
all_ok=false
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
if [[ "${all_ok}" == "true" ]]; then
|
|
220
|
+
log_success "All services running via systemd. They will auto-start on boot."
|
|
221
|
+
else
|
|
222
|
+
log_warn "Some services failed to start via systemd. Use 'clawspark restart' or check journalctl."
|
|
223
|
+
fi
|
|
224
|
+
}
|