在AIOps领域飞速发展的今天,Agent Skills凭借轻量化、高定制性的优势,逐渐替代传统MCP方案,成为智能运维的核心工具。而OpenClaw作为开源AI助手的佼佼者,不仅支持多平台对接与本地部署,更提供了极简的Skills定制能力——无需复杂编码,通过自然语言交互即可生成专属功能模块。本文以企业高频需求的“远程Linux服务器巡检”为例,详细拆解OpenClaw Skills的开发全流程,涵盖需求定义、交互配置、文件解析、测试优化等核心环节,搭配可直接复用的代码模板与阿里云快速部署方案,确保零基础用户也能快速上手。
一、核心认知:OpenClaw Skills的价值与优势
(一)Skills是什么?
OpenClaw Skills是可扩展的功能模块,本质是“指令集+执行脚本+配置文件”的组合,能够让AI助手具备特定场景能力(如服务器巡检、文件处理、数据统计等)。每个Skill独立存储在~/.openclaw/workspaces/skills/目录下,支持按需启用、修改与扩展,是OpenClaw实现“个性化功能”的核心载体。
(二)与传统MCP方案的对比优势
| 特性 | OpenClaw Skills | 传统MCP方案 |
|---|---|---|
| 开发门槛 | 低,自然语言交互生成,无需专业编码 | 高,需手动编写配置文件与脚本 |
| 灵活性 | 极强,支持快速修改与扩展 | 弱,修改需重新部署 |
| 轻量化 | 独立模块,资源占用小 | 重量级,依赖复杂依赖 |
| 集成度 | 与OpenClaw生态深度融合,支持多平台触发 | 需额外适配对接,集成成本高 |
| 安全性 | 支持命令白名单,默认只读权限 | 权限管控复杂,易出现安全风险 |
对于服务器巡检、日常办公自动化等场景,OpenClaw Skills的“低代码+高灵活”特性能够大幅降低开发成本,实现“需求提出→技能生成→落地使用”的快速闭环。
(三)巡检Skill的核心价值
远程服务器巡检是企业运维的基础需求,传统人工巡检存在效率低、易遗漏、无法实时响应的问题。通过OpenClaw巡检Skill,可实现:
- 自动化执行:支持定时/按需触发,无需人工登录服务器;
- 全面覆盖:涵盖硬件资源、系统服务、异常日志、安全事件等核心维度;
- 智能分析:自动筛选异常信息,生成结构化报告,突出重点风险;
- 多端同步:巡检结果可同步至飞书、Telegram等平台,方便团队协作。
二、开发前准备:环境与工具清单
(一)环境要求
| 项目 | 具体要求 | 备注 |
|---|---|---|
| OpenClaw版本 | ≥2026.2.9 | 确保支持Skills动态生成功能 |
| 操作系统 | Linux/macOS/Windows(WSL2) | 本文以Linux(Ubuntu 22.04)为例 |
| Node.js | ≥22.x | Skills执行脚本依赖Node.js环境 |
| 网络 | 本地与远程服务器网络互通 | 需开放SSH端口(默认22) |
| 权限 | 本地用户具备OpenClaw配置目录读写权限 | 远程服务器需创建只读巡检账号 |
(二)必备工具
- 终端工具:Linux/macOS自带终端、Windows PowerShell;
- 文本编辑器:Nano、Vim、VS Code(用于修改Skill配置文件);
- SSH工具:用于测试远程服务器连接;
- 浏览器:Chrome、Edge(用于查看巡检报告与OpenClaw Web控制台)。
(三)核心凭证与信息
- 远程服务器信息:IP地址、SSH端口(默认22)、巡检账号用户名;
- 密钥对:用于SSH免密登录,需提前在本地生成并上传公钥至远程服务器;
- 巡检清单:明确需要检测的维度(如硬件资源、系统服务、异常日志等)。
(四)阿里云一键部署OpenClaw步骤流程
第一步:访问阿里云OpenClaw一键部署专题页面,找到并点击【一键购买并部署】。
阿里云OpenClaw一键部署专题页面:https://www.aliyun.com/activity/ecs/clawdbot


第二步:选购阿里云轻量应用服务器,配置参考如下:
- 镜像:OpenClaw(Moltbot)镜像(已经购买服务器的用户可以重置系统重新选择镜像)
- 实例:内存必须2GiB及以上。
- 地域:默认美国(弗吉尼亚),目前中国内地域(除香港)的轻量应用服务器,联网搜索功能受限。
- 时长:根据自己的需求及预算选择。



第三步:访问阿里云百炼大模型控制台,找到密钥管理,单击创建API-Key。
前往轻量应用服务器控制台,找到安装好OpenClaw的实例,进入「应用详情」放行18789端口、配置百炼API-Key、执行命令,生成访问OpenClaw的Token。
- 端口放通:需要放通对应端口的防火墙,单击一键放通即可。
- 配置百炼API-Key,单击一键配置,输入百炼的API-Key。单击执行命令,写入API-Key。
- 配置OpenClaw:单击执行命令,生成访问OpenClaw的Token。
- 访问控制页面:单击打开网站页面可进入OpenClaw对话页面。
三、阿里云OpenClaw部署简单步骤(小白速通版)
若尚未部署OpenClaw,可直接使用阿里云一键部署方案,快速搭建开发环境:
- 注册并登录阿里云账号,完成实名认证;
- 访问阿里云OpenClaw一键部署专题页面,点击“一键购买并部署”;
- 配置服务器参数:
- 实例规格:2核4GB内存+40GB ESSD云盘+5Mbps带宽;
- 镜像:选择“OpenClaw开发版”(内置Node.js与Skills开发依赖);
- 地域:推荐中国香港/新加坡(免备案,网络访问无限制);
- 支付完成后,等待实例状态变为“运行中”,记录服务器公网IP;
- 远程登录服务器,执行以下命令初始化:
# 启动OpenClaw服务 sudo systemctl start openclaw # 验证服务状态(显示active即为成功) sudo systemctl status openclaw # 查看OpenClaw版本 openclaw --version - 执行
openclaw dashboard获取Web控制台链接,完成部署。
四、巡检Skill开发全流程:从需求到落地
(一)Step1:定义需求,与OpenClaw交互配置
OpenClaw支持通过自然语言提出需求,AI会自动引导补充关键信息,无需手动编写基础配置。
1. 发起需求指令
打开OpenClaw Web控制台或对接的聊天平台(如飞书、Telegram),发送需求指令:
请帮我创建一个远程登录Linux机器并且做巡检的Skills,要求如下:
1. 登录方式:SSH密钥认证,禁止密码登录;
2. 巡检范围:
- 硬件资源:CPU、内存、磁盘、网络使用情况;
- 系统服务:运行状态、异常日志(近24小时);
- 安全事件:SSH异常登录、防火墙告警;
3. 输出结果:生成结构化Markdown报告,突出异常信息与处理建议;
4. 安全要求:仅执行只读命令,禁止修改服务器配置。
2. 响应AI的交互提问
OpenClaw会逐步引导补充关键信息,按实际情况回复即可:
- 提问1:请提供远程服务器的IP地址、SSH端口、巡检账号用户名?
回复:IP:192.168.124.16,端口:22,用户名:inspector; - 提问2:是否需要我帮你生成SSH密钥对?
回复:是,请生成并提供公钥使用方法; - 提问3:是否需要指定重点监控的系统服务?
回复:需要,重点监控sshd、mongod、docker、firewalld服务。
3. 生成Skill基础结构
OpenClaw会自动在~/.openclaw/workspaces/skills/目录下创建ssh-inspector文件夹,核心文件结构如下:
ssh-inspector/
├── references/ # 配置文件目录
│ ├── targets.yaml # 目标服务器配置
│ └── checks.yaml # 巡检命令白名单
├── scripts/ # 执行脚本目录
│ └── inspect.mjs # 核心巡检脚本
└── SKILL.md # Skill说明文档(大脑)
(二)Step2:解析核心文件,理解Skill工作原理
1. SKILL.md:Skill的“说明书”
该文件定义了Skill的名称、功能描述、使用场景、安全规则等核心信息,是OpenClaw识别和调用Skill的依据,内容如下(可直接复用修改):
---
name: ssh-inspector
description: Remote SSH login and read-only server inspection (health checks) by running a fixed allowlist of commands on configured targets, collecting outputs, and producing a Markdown report. Use when the user asks to SSH into servers, run remote commands for 巡检/healthcheck/巡查, gather system metrics (cpu/mem/disk/load), or generate inspection reports.
---
# ssh-inspector
Run read-only inspections against remote Linux hosts over SSH using a dedicated key.
## Safety (default deny)
- Only run commands that are defined in `references/checks.yaml`.
- Do not run any command that changes state (no package installs, no config edits, no restarts).
- Prefer non-interactive SSH: `BatchMode=yes`, explicit `ConnectTimeout`, and a per-command timeout.
## Config
- Targets: `references/targets.yaml`
- Allowed checks (command allowlist): `references/checks.yaml`
If a target is missing, ask the user for: host/IP, port, user, and which SSH key to use.
## How To Run
Use the bundled script:
- `node scripts/inspect.mjs --target bogon --checks basic`
It prints a Markdown report to stdout.
## Extending
- Add a new target in `references/targets.yaml`.
- Add or adjust checks in `references/checks.yaml`.
- Keep checks small and composable; parsing is best-effort.
2. references/targets.yaml:目标服务器配置
存储远程服务器的连接信息,支持配置多个目标,格式如下:
targets:
bogon: # 目标名称,自定义
host: 192.168.124.16 # 服务器IP
port: 22 # SSH端口
user: inspector # 巡检账号
keyPath: ~/.ssh/openclaw_inspect # 本地私钥路径
services: # 重点监控的服务
- sshd
- mongod
- docker
- firewalld
3. references/checks.yaml:巡检命令白名单
定义允许执行的巡检命令,按功能分组管理,支持动态扩展,格式如下:
checks:
basic: # 基础巡检组
description: 硬件资源与系统基础信息
commands:
- id: basic_identity
cmd: whoami; hostname; uname -r; date -Is
timeoutSec: 5
- id: basic_uptime
cmd: uptime
timeoutSec: 5
- id: hw_cpu
cmd: "(command -v mpstat >/dev/null 2>&1 && mpstat -P ALL 1 3 | sed -n '1,160p') || (top -b -n1 | sed -n '1,25p') || true"
timeoutSec: 15
- id: hw_mem
cmd: "free -h; echo; cat /proc/meminfo | egrep -i '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree):' || true"
timeoutSec: 10
- id: hw_disk_fs
cmd: "df -hT | sed -n '1,25p'"
timeoutSec: 10
services: # 服务巡检组(动态生成命令)
description: 重点服务运行状态与日志
daily: # 日常巡检组(整合基础+服务+安全)
description: 完整日常巡检(含资源、服务、日志、安全)
4. scripts/inspect.mjs:核心执行脚本
该脚本是Skill的“手脚”,负责读取配置、建立SSH连接、执行命令、生成报告,完整代码如下(可直接复制使用):
#!/usr/bin/env node
/*
Read-only SSH inspector.
- Loads targets from references/targets.yaml
- Loads allowlisted checks from references/checks.yaml
- Runs each command over SSH (non-interactive), captures stdout/stderr
- Prints a Markdown report
This script intentionally does not support arbitrary remote commands.
*/
import {
execFile } from 'node:child_process';
import {
readFile } from 'node:fs/promises';
import {
fileURLToPath } from 'node:url';
import {
dirname, join } from 'node:path';
// 显示使用说明
function usage() {
console.log(`Usage:
node scripts/inspect.mjs --target <name> --checks <group>
Options:
--target Target name in references/targets.yaml
--checks Check group in references/checks.yaml (default: basic)
`);
}
// 解析命令行参数
function parseArgs(argv) {
const args = {
checks: 'basic' };
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--help' || a === '-h') args.help = true;
else if (a === '--target') args.target = argv[++i];
else if (a === '--checks') args.checks = argv[++i];
else {
throw new Error(`Unknown arg: ${
a}`);
}
}
return args;
}
// 简易YAML解析器(无需依赖第三方库)
function parseSimpleYaml(text) {
const lines = text.replace(/\r\n/g, '\n').split('\n');
const root = {
};
const stack = [{
indent: -1, obj: root }];
for (let idx = 0; idx < lines.length; idx++) {
const raw = lines[idx];
const line = raw.replace(/\t/g, ' ');
if (!line.trim() || line.trim().startsWith('#')) continue;
const indent = line.match(/^ */)[0].length;
while (stack.length && indent <= stack[stack.length - 1].indent) stack.pop();
const parent = stack[stack.length - 1].obj;
const trimmed = line.trim();
if (trimmed.startsWith('- ')) {
if (!Array.isArray(parent)) throw new Error('YAML list item in non-list');
parent.push(stripQuotes(trimmed.slice(2).trim()));
continue;
}
const [k, ...rest] = trimmed.split(':');
const key = k.trim();
const value = rest.join(':').trim();
if (value === '') {
let j = idx + 1;
let next = null;
while (j < lines.length) {
const nl = lines[j].replace(/\t/g, ' ');
const nt = nl.trim();
if (nt && !nt.startsWith('#')) {
next = {
indent: nl.match(/^ */)[0].length, trimmed: nt };
break;
}
j++;
}
const isList = next && next.indent > indent && next.trimmed.startsWith('- ');
const container = isList ? [] : {
};
parent[key] = container;
stack.push({
indent, obj: container });
} else {
parent[key] = stripQuotes(value);
}
}
return root;
}
// 去除字符串引号
function stripQuotes(s) {
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
return s.slice(1, -1);
}
if (/^\d+$/.test(s)) return Number(s);
return s;
}
// 异步执行命令
function execFileP(cmd, args, {
timeoutMs } = {
}) {
return new Promise((resolve) => {
execFile(cmd, args, {
timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
resolve({
error, stdout: stdout ?? '', stderr: stderr ?? '' });
});
});
}
// Markdown转义
function mdEscape(s) {
return s.replace(/`/g, '\\`');
}
// 获取当前ISO格式时间
function nowIso() {
return new Date().toISOString();
}
// 构建服务巡检命令
function buildServiceCommands(services) {
const out = [];
const uniq = [...new Set((services || []).map((s) => String(s).trim()).filter(Boolean))];
for (const name of uniq) {
out.push({
id: `svc_${
name}_status`,
cmd: `systemctl status ${
name} --no-pager | sed -n '1,40p'`,
timeoutSec: 12,
});
out.push({
id: `svc_${
name}_errors`,
cmd: `journalctl -u ${
name} -p err..alert -n 80 --no-pager || true`,
timeoutSec: 15,
});
out.push({
id: `svc_${
name}_recent`,
cmd: `journalctl -u ${
name} -n 120 --no-pager | egrep -i 'warn|warning|error|failed|fail|critical|crit|alert|panic|segfault|oom|killed process|timeout|timed out|refused|denied|unreachable|reset|broken pipe|i/o error|corrupt|read-only|no space|disk quota|throttl|backoff|rate limit|too many|conntrack|dropped' | tail -n 60 || true`,
timeoutSec: 15,
});
}
if (out.length === 0) {
out.push({
id: 'services_config',
cmd: "echo 'No services configured for this target. Add targets.<name>.services in references/targets.yaml'",
timeoutSec: 3,
});
}
return out;
}
// 构建日常巡检命令(整合基础+服务+安全)
function buildDailyCommands(t) {
const base = [
{
id: 'basic_identity', cmd: 'whoami; hostname; uname -r; date -Is', timeoutSec: 5 },
{
id: 'basic_uptime', cmd: 'uptime', timeoutSec: 5 },
{
id: 'basic_os', cmd: "cat /etc/os-release | sed -n '1,12p'", timeoutSec: 5 },
{
id: 'hw_cpu', cmd: "(command -v mpstat >/dev/null 2>&1 && mpstat -P ALL 1 3 | sed -n '1,160p') || (top -b -n1 | sed -n '1,25p') || true", timeoutSec: 15 },
{
id: 'hw_mem', cmd: "free -h; echo; cat /proc/meminfo | egrep -i '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab):' || true", timeoutSec: 10 },
{
id: 'hw_disk_fs', cmd: "df -hT | sed -n '1,25p'", timeoutSec: 10 },
{
id: 'hw_disk_io', cmd: "(command -v iostat >/dev/null 2>&1 && iostat -x 1 3 | sed -n '1,120p') || true", timeoutSec: 18 },
{
id: 'hw_net_overview', cmd: "ss -s | sed -n '1,80p'", timeoutSec: 10 },
{
id: 'logs_journal_err_24h', cmd: 'journalctl -p err..alert -S -24h --no-pager | tail -n 200 || true', timeoutSec: 20 },
{
id: 'logs_dmesg_key', cmd: "dmesg -T 2>/dev/null | egrep -i 'error|fail|oom|killed process|segfault|panic|xfs|ext4|nvme|reset|link down|call trace' | tail -n 200 || true", timeoutSec: 12 },
{
id: 'sec_last_failed', cmd: "lastb -n 50 2>/dev/null | sed -n '1,60p' || true", timeoutSec: 12 },
{
id: 'sec_sshd_suspicious_24h', cmd: "journalctl -u sshd -S -24h --no-pager | egrep -i 'failed password|invalid user|authentication failure|maximum authentication attempts|POSSIBLE BREAK-IN ATTEMPT|Did not receive identification string|Connection closed by authenticating user|error: kex_exchange_identification' | tail -n 200 || true", timeoutSec: 20 },
{
id: 'systemd_failed_units', cmd: 'systemctl --failed --no-pager || true', timeoutSec: 10 },
{
id: 'systemd_recent_errors', cmd: 'journalctl -p err..alert -n 80 --no-pager || true', timeoutSec: 15 },
];
const svc = buildServiceCommands(t?.services ?? []);
return base.concat(svc);
}
// 解析checks.yaml(专用解析器)
function parseChecksYaml(text) {
const lines = text.replace(/\r\n/g, '\n').split('\n');
const out = {
checks: {
} };
let curGroup = null;
let inCommands = false;
let curCmd = null;
const kv = (s) => {
const i = s.indexOf(':');
if (i === -1) return null;
return [s.slice(0, i).trim(), s.slice(i + 1).trim()];
};
for (let raw of lines) {
const line = raw.replace(/\t/g, ' ');
const t = line.trim();
if (!t || t.startsWith('#')) continue;
if (t === 'checks:') continue;
if (/^[a-zA-Z0-9_-]+:$/.test(t) && line.startsWith(' ') && !line.startsWith(' ')) {
curGroup = t.slice(0, -1);
out.checks[curGroup] = {
commands: [] };
inCommands = false;
curCmd = null;
continue;
}
if (!curGroup) continue;
if (t === 'commands:') {
inCommands = true;
curCmd = null;
continue;
}
if (inCommands && t.startsWith('- ')) {
curCmd = {
};
out.checks[curGroup].commands.push(curCmd);
const rest = t.slice(2);
const pair = kv(rest);
if (pair) curCmd[pair[0]] = stripQuotes(pair[1]);
continue;
}
const pair = kv(t);
if (!pair) continue;
if (!inCommands) {
out.checks[curGroup][pair[0]] = stripQuotes(pair[1]);
} else if (curCmd) {
curCmd[pair[0]] = stripQuotes(pair[1]);
}
}
return out;
}
// 命令字符串转义
function shellQuote(s) {
return `'${String(s).replace(/'/g, `'"'"'`)}'`;
}
// 生成Markdown报告
function renderReport({
target, host, user, checks, start, results }) {
let md = '';
md += `# SSH Inspection Report\n\n`;
md += `- Target: \`${
mdEscape(target)}\`\n`;
md += `- Host: \`${
mdEscape(host)}\`\n`;
md += `- User: \`${
mdEscape(user)}\`\n`;
md += `- Checks: \`${
mdEscape(checks)}\`\n`;
md += `- Started: \`${
mdEscape(start)}\`\n\n`;
for (const r of results) {
md += `## ${
mdEscape(r.id)}\n\n`;
md += `Command: \`${
mdEscape(r.cmd)}\`\n\n`;
md += `Status: ${
r.ok ? 'OK' : 'FAIL'} (timeout ${
r.timeoutSec}s)\n\n`;
if (r.stdout.trim()) {
md += `STDOUT:\n\n\`\`\`\n${
r.stdout.trim()}\n\`\`\`\n\n`;
}
if (r.stderr.trim()) {
md += `STDERR:\n\n\`\`\`\n${
r.stderr.trim()}\n\`\`\`\n\n`;
}
}
return md;
}
// 主函数
async function main() {
const args = parseArgs(process.argv);
if (args.help || !args.target) {
usage();
if (!args.target) process.exitCode = 2;
return;
}
const here = dirname(fileURLToPath(import.meta.url));
const skillDir = dirname(here);
const targetsPath = join(skillDir, 'references', 'targets.yaml');
const checksPath = join(skillDir, 'references', 'checks.yaml');
// 读取配置文件
const targetsText = await readFile(targetsPath, 'utf-8');
const checksText = await readFile(checksPath, 'utf-8');
// 解析配置
const targets = parseSimpleYaml(targetsText);
const t = targets.targets?.[args.target];
if (!t) throw new Error(`Unknown target: ${
args.target}`);
const checks = parseChecksYaml(checksText);
const group = checks.checks?.[args.checks];
if (!group) throw new Error(`Unknown checks group: ${
args.checks}`);
// 动态生成命令组
if (args.checks === 'services') {
group.commands = buildServiceCommands(t.services ?? []);
}
if (args.checks === 'daily') {
group.commands = buildDailyCommands(t);
}
// SSH连接基础参数
const sshBase = [
'-i', String(t.keyPath),
'-p', String(t.port ?? 22),
'-o', 'BatchMode=yes',
'-o', 'StrictHostKeyChecking=accept-new',
'-o', 'ConnectTimeout=8',
];
const dest = `${
t.user}@${
t.host}`;
const start = nowIso();
const results = [];
// 执行巡检命令
for (const c of group.commands) {
const timeoutMs = Number(c.timeoutSec ?? 10) * 1000;
const remote = `bash -lc ${
shellQuote(c.cmd)}`;
const {
error, stdout, stderr } = await execFileP('ssh', [...sshBase, dest, remote], {
timeoutMs });
results.push({
id: c.id, cmd: c.cmd, timeoutSec: c.timeoutSec ?? 10, ok: !error, code: error?.code ?? 0, stdout, stderr });
}
// 生成并输出报告
const report = renderReport({
target: args.target, host: t.host, user: t.user, checks: args.checks, start, results });
try {
process.stdout.write(report);
} catch (e) {
if (e?.code !== 'EPIPE') throw e;
}
}
// 执行主函数
main().catch((err) => {
console.error(err?.stack || String(err));
process.exitCode = 1;
});
(三)Step3:配置SSH密钥认证,确保连接安全
巡检Skill采用SSH密钥认证方式登录远程服务器,避免密码泄露风险,配置步骤如下:
1. 本地生成密钥对
# 生成SSH密钥对(无需设置密码)
ssh-keygen -t rsa -b 4096 -f ~/.ssh/openclaw_inspect
# 查看公钥内容
cat ~/.ssh/openclaw_inspect.pub
2. 上传公钥至远程服务器
# 替换为远程服务器IP和用户名
ssh-copy-id -i ~/.ssh/openclaw_inspect.pub inspector@192.168.124.16
# 测试免密登录(成功登录即为配置完成)
ssh -i ~/.ssh/openclaw_inspect inspector@192.168.124.16
3. 配置远程服务器权限(安全加固)
为巡检账号配置只读权限,限制命令执行范围:
# 远程服务器执行,创建巡检账号(若已存在可跳过)
sudo useradd -m inspector
# 设置账号权限,禁止sudo
sudo usermod -L inspector # 锁定密码登录
sudo chmod 700 /home/inspector
sudo chmod 600 /home/inspector/.ssh/authorized_keys
# 限制可执行命令(可选,通过rbash实现)
sudo usermod -s /bin/rbash inspector
(四)Step4:测试Skill,验证巡检功能
1. 手动执行巡检脚本
# 进入Skill脚本目录
cd ~/.openclaw/workspaces/skills/ssh-inspector/scripts
# 执行基础巡检(检查硬件资源)
node inspect.mjs --target bogon --checks basic
# 执行服务巡检(检查重点服务)
node inspect.mjs --target bogon --checks services
# 执行完整巡检(基础+服务+安全+日志)
node inspect.mjs --target bogon --checks daily
2. 查看巡检报告
执行后会在终端输出Markdown格式报告,包含以下核心模块:
- 基础信息:服务器身份、系统版本、运行时间;
- 硬件资源:CPU、内存、磁盘、网络使用情况;
- 系统服务:重点服务运行状态、异常日志;
- 安全事件:SSH异常登录、防火墙告警、内核错误;
- 系统稳定性:systemd失败单元、最近24小时异常日志。
3. 示例报告核心内容(异常信息提取)
# SSH Inspection Report
- Target: bogon
- Host: 192.168.124.16
- User: inspector
- Checks: daily
- Started: 2026-02-12T06:04:19.133Z
## systemd_failed_units
Command: systemctl --failed --no-pager || true
Status: FAIL (timeout 10s)
STDOUT:
UNIT LOAD ACTIVE SUB DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
LOAD = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB = The low-level unit activation state, values depend on unit type.
## logs_journal_err_24h
Command: journalctl -p err..alert -S -24h --no-pager | tail -n 200 || true
STDOUT:
Feb 12 10:00:00 bogon setroubleshoot[12345]: SELinux is preventing /usr/bin/mongod from write access on the file /var/lib/mongo/journal/j._0.
...(共132条类似记录)
(五)Step5:优化Skill,提升实用性
1. 异常信息过滤与优先级排序
默认报告包含所有执行结果,可修改inspect.mjs的renderReport函数,添加异常过滤逻辑,突出重点风险:
// 新增异常关键词列表
const errorKeywords = ['failed', 'error', 'alert', 'critical', 'SELinux is preventing', 'WARNING', 'panic', 'segfault', 'oom'];
// 修改renderReport函数,添加异常标记
function renderReport({
target, host, user, checks, start, results }) {
let md = '';
md += `# SSH Inspection Report\n\n`;
md += `- Target: \`${
mdEscape(target)}\`\n`;
md += `- Host: \`${
mdEscape(host)}\`\n`;
md += `- User: \`${
mdEscape(user)}\`\n`;
md += `- Checks: \`${
mdEscape(checks)}\`\n`;
md += `- Started: \`${
mdEscape(start)}\`\n`;
// 统计异常项
const errorItems = results.filter(r => !r.ok || errorKeywords.some(key => r.stdout.includes(key) || r.stderr.includes(key)));
md += `- 异常项数量: ${
errorItems.length}\n\n`;
// 优先显示异常项
if (errorItems.length > 0) {
md += `## 重点异常(优先处理)\n\n`;
for (const r of errorItems) {
md += `### ⚠️ ${
mdEscape(r.id)}\n\n`;
md += `Command: \`${
mdEscape(r.cmd)}\`\n\n`;
md += `Status: ${
r.ok ? 'OK(含异常信息)' : 'FAIL'}\n\n`;
if (r.stdout.trim()) {
md += `异常信息:\n\n\`\`\`\n${
r.stdout.trim()}\n\`\`\`\n\n`;
}
}
}
// 显示正常项(折叠)
md += `## 正常巡检项(点击展开)\n\n<details><summary>查看所有巡检结果</summary>\n\n`;
for (const r of results.filter(r => !errorItems.includes(r))) {
md += `### ${
mdEscape(r.id)}\n\n`;
md += `Command: \`${
mdEscape(r.cmd)}\`\n\n`;
md += `Status: OK\n\n`;
}
md += `</details>\n`;
return md;
}
2. 配置定时巡检
通过OpenClaw的Cron Jobs功能,设置定时自动巡检:
# 进入OpenClaw配置
openclaw configure --section cron
# 添加每日14:00巡检任务
# 格式:分钟 小时 日 月 周 命令
0 14 * * * node ~/.openclaw/workspaces/skills/ssh-inspector/scripts/inspect.mjs --target bogon --checks daily > /tmp/openclaw_inspect_report_$(date +%Y%m%d).md
3. 多平台同步报告
将巡检报告自动发送至飞书/Telegram,需先完成OpenClaw与对应平台的对接,再修改定时任务:
# 飞书同步(替换为你的飞书机器人Webhook)
0 14 * * * node ~/.openclaw/workspaces/skills/ssh-inspector/scripts/inspect.mjs --target bogon --checks daily | curl -X POST -H "Content-Type: application/json" -d '{"text":"'"$(cat -)"'"}' https://open.feishu.cn/open-apis/bot/v2/hook/xxx
五、Skills扩展与维护:打造可复用生态
(一)添加新的巡检维度
若需增加数据库巡检、应用性能检测等功能,可修改checks.yaml添加新命令组:
checks:
database: # 数据库巡检组
description: MongoDB数据库状态检测
commands:
- id: mongo_status
cmd: "mongosh --eval 'db.runCommand({ serverStatus: 1 }).ok' || true"
timeoutSec: 20
- id: mongo_connections
cmd: "mongosh --eval 'db.runCommand({ serverStatus: 1 }).connections' || true"
timeoutSec: 20
(二)多目标服务器管理
在targets.yaml中添加多个服务器配置,支持批量巡检:
targets:
bogon: # 服务器1
host: 192.168.124.16
port: 22
user: inspector
keyPath: ~/.ssh/openclaw_inspect
services: [sshd, mongod, docker]
server2: # 服务器2
host: 192.168.124.17
port: 22
user: inspector
keyPath: ~/.ssh/openclaw_inspect
services: [nginx, mysql, redis]
执行批量巡检:
# 循环执行多目标巡检
for target in bogon server2; do
node ~/.openclaw/workspaces/skills/ssh-inspector/scripts/inspect.mjs --target $target --checks daily > /tmp/inspect_$target_$(date +%Y%m%d).md
done
(三)版本控制与备份
为Skills配置Git版本控制,避免修改错误导致功能失效:
# 初始化Git仓库
cd ~/.openclaw/workspaces/skills/ssh-inspector
git init
# 创建.gitignore文件
cat <<EOF> .gitignore
node_modules/
*.log
tmp/
EOF
# 首次提交
git add .
git commit -m "initial commit: 服务器巡检Skill基础版本"
六、常见问题排查
(一)SSH连接失败
- 表现:执行脚本提示“Connection timed out”或“Permission denied”;
- 原因:服务器IP/端口错误、密钥路径配置错误、远程服务器防火墙拦截;
- 解决方案:
- 验证服务器连通性:
ping 192.168.124.16; - 检查SSH端口:
telnet 192.168.124.16 22; - 验证密钥路径:确保
targets.yaml中keyPath指向正确的私钥文件; - 检查防火墙:远程服务器执行
sudo ufw allow 22开放SSH端口。
- 验证服务器连通性:
(二)命令执行超时
- 表现:报告中显示“Status: FAIL (timeout 10s)”;
- 原因:命令执行时间过长,超过默认超时时间;
- 解决方案:
- 在
checks.yaml中为对应命令增加timeoutSec参数(如改为30秒); - 简化复杂命令,拆分多个小命令执行。
- 在
(三)报告无异常信息但实际存在问题
- 表现:服务器存在异常,但报告未显示;
- 原因:异常关键词未包含在过滤列表中;
- 解决方案:
- 查看完整执行结果:
node inspect.mjs --target bogon --checks daily > full_report.md; - 分析异常信息特征,添加到
errorKeywords列表中。
- 查看完整执行结果:
七、总结
OpenClaw Skills的低代码开发模式,让非专业开发者也能快速打造专属AI功能模块。本文以远程服务器巡检为例,从需求定义、交互配置、文件解析、测试优化等维度,完整呈现了Skills的开发流程,核心亮点在于:
- 自然语言驱动:无需手动编写基础配置,AI自动引导补充关键信息;
- 安全可控:采用命令白名单与只读权限,避免服务器配置被修改;
- 高度可扩展:支持添加新巡检维度、多目标管理、多平台同步;
- 实用导向:优化后的报告突出异常信息,支持定时执行与自动同步。
搭配阿里云一键部署方案,用户可快速搭建开发环境,实现“需求提出→技能生成→落地使用”的闭环。除了服务器巡检,OpenClaw Skills还可应用于文件批量处理、数据统计分析、日常办公自动化等多个场景,通过简单的配置与扩展,即可打造全方位的AI助手生态。随着OpenClaw社区的持续发展,Skills的功能与模板将不断丰富,值得持续关注与探索。