乐于分享
好东西不私藏

深入深出openclaw:gateway 服务进程的启动逻辑

深入深出openclaw:gateway 服务进程的启动逻辑

OpenClaw一个起到“承上启下”桥梁作用的组件就是它的gateway进程。前面我们已经看到过gateway启动后,我们能打开对应的网关配置页面。gateway进程的主要主要是用于接收来自手机客户端例如飞书,企业微信,钉钉等应用发过来的命令,然后将对应的命令转发给agent模块进行处理,然后再把处理结果返还给手机客户端。本节我们看看openclaw是如何在不同系统上加载gateway进程,并且将其作为系统常驻服务进程的。

openclaw主要运行在三个主流操作系统上,分别为macos, linux和windows,它先为三个系统的服务进程启动设置了接口定义,在 src/daemon/service.ts 文件中为服务进程的启动,停止等操作提供了统一的抽象接口,对应代码如下:

export type GatewayService = {
label: string;
  loadedText: string;
  notLoadedText: string;
  stage: (args: GatewayServiceStageArgs) =>Promise<void>;
  install: (args: GatewayServiceInstallArgs) =>Promise<void>;
  uninstall: (args: GatewayServiceManageArgs) =>Promise<void>;
  stop: (args: GatewayServiceControlArgs) =>Promise<void>;
  restart: (args: GatewayServiceControlArgs) =>Promise<GatewayServiceRestartResult>;
  isLoaded: (args: GatewayServiceEnvArgs) =>Promise<boolean>;
  readCommand: (env: GatewayServiceEnv) =>Promise<GatewayServiceCommandConfig | null>;
  readRuntime: (env: GatewayServiceEnv) =>Promise<GatewayServiceRuntime>;
};

上面接口定义中,install对应在给定操作系统上安装gateway进程,uninstall对应卸载gateway进程,restart对应重启gateway进程,我们看看针对不同操作系统,openclaw如何初始化上面接口对应的实例,在同一个代码文件,也就是service.ts里有一个关键的常量定义GATEWAY_SERVICE_REGISTRY,它的内容如下:

const GATEWAY_SERVICE_REGISTRY: Record<SupportedGatewayServicePlatform, GatewayService> = {
darwin: {
label"LaunchAgent",
loadedText"loaded",
notLoadedText"not loaded",
stage: ignoreServiceWriteResult(stageLaunchAgent),
install: ignoreServiceWriteResult(installLaunchAgent),
uninstall: uninstallLaunchAgent,
stop: stopLaunchAgent,
restart: restartLaunchAgent,
isLoaded: isLaunchAgentLoaded,
readCommand: readLaunchAgentProgramArguments,
readRuntime: readLaunchAgentRuntime,
  },
linux: {
label"systemd",
loadedText"enabled",
notLoadedText"disabled",
stage: ignoreServiceWriteResult(stageSystemdService),
install: ignoreServiceWriteResult(installSystemdService),
uninstall: uninstallSystemdService,
stop: stopSystemdService,
restart: restartSystemdService,
isLoaded: isSystemdServiceEnabled,
readCommand: readSystemdServiceExecStart,
readRuntime: readSystemdServiceRuntime,
  },
win32: {
label"Scheduled Task",
loadedText"registered",
notLoadedText"missing",
stage: ignoreServiceWriteResult(stageScheduledTask),
install: ignoreServiceWriteResult(installScheduledTask),
uninstall: uninstallScheduledTask,
stop: stopScheduledTask,
restart: restartScheduledTask,
isLoaded: isScheduledTaskInstalled,
readCommand: readScheduledTaskCommand,
readRuntime: readScheduledTaskRuntime,
  },
};

由于openclaw是通过nodejs开发的,因此openclaw的代码需要通过Nodejs的进程进行加载执行。当nodejs在对应的操作系统上运行时,它首先获取当前操作系统的信息,如果当前操作系统是macos,那么它使用“darwin”来获取GatewayService实例,如果是linux,那么通过”linux”来获取对应实例,如果是windows,你妈使用”win32″来获取对应实例。由于不同操作系统设置常驻服务进程的逻辑和步骤完全不同,因此openclaw在抽象出服务进程的对应接口后,需要针对不同操作系统执行不同的服务进程设置流程。

尽管不同系统设置常驻服务进程的流程不同,但是他们都遵循相同的模式:创建常驻服务进程配置文件->通知操作系统每次启动时读取配置文件->根据配置文件的设置启动nodejs进程->读取并运行openclaw编译好的dist目录下的entry.js文件代码->执行gateway模块代码。根据这个模式,openclaw如果运行在macos上,它就会创建一个xml文件,里面配置好如何启动nodejs进程的设置。如果运行在Linux上,就会创建unit文件,然后通知systemd模块在系统启动时读取unit文件然后启动nodejs去执行openclaw对应的gateway模块代码;如果运行在windows上,那么创建cmd脚本文件,然后使用schtasks服务运行对应的cmd脚步,cmd脚步将启动nodejs进程,并让nodejs进程执行openclaw对应的dist/entry.js,然后执行对应的gateway服务进程模块

由于我使用windows系统,因此我们着重看在windows上openclaw启动gateway服务进程的具体流程。从前面给出针对windows平台的GatewayService接口实例看到,在执行install接口时,针对windows平台,install接口实际对应函数installScheduledTask,同时可以看到所有关于windows平台对应的GatewayService接口函数实现都来自schtasks.js,所以我们进入到这个文件。

从schtasks.js中对应的installScheduledTask函数实现如下:

exportasyncfunctioninstallScheduledTask(
  args: GatewayServiceInstallArgs,
): Promise<
{ scriptPath: string }> {
const staged = await writeScheduledTaskScript(args);
await activateScheduledTask({
env: args.env,
stdout: args.stdout,
scriptPath: staged.scriptPath,
description: staged.taskDescription,
  });
return { scriptPath: staged.scriptPath };
}

asyncfunctionwriteScheduledTaskScript({
  env,
  programArguments,
  workingDirectory,
  environment,
  description,
}: Omit<GatewayServiceInstallArgs, "stdout">
): Promise<
{
scriptPath: string;
  taskDescription: string;
}> {
await assertSchtasksAvailable();
const scriptPath = resolveTaskScriptPath(env);
await fs.mkdir(path.dirname(scriptPath), { recursivetrue });
const taskDescription = resolveGatewayServiceDescription({ env, environment, description });
const script = buildTaskScript({
description: taskDescription,
    programArguments,
    workingDirectory,
    environment,
  });
await fs.writeFile(scriptPath, script, "utf8");
return { scriptPath, taskDescription };
}

它首先调用writeScheduledTaskScript,该函数首先使用resolveTaskScriptPath获取cmd脚本文件应该存放的路径,通常情况下是openclaw的安装路径,然后使用buildTastScript创建cmd脚本的内容,最后使用fs.writeFile写下cmd脚本,我们修改上面代码做个调试,我们打印出scriptPath和script对应内容,在执行完writeFile后直接退出程序而不是继续往下走:

asyncfunctionwriteScheduledTaskScript({
  env,
  programArguments,
  workingDirectory,
  environment,
  description,
}: Omit<GatewayServiceInstallArgs, "stdout">
): Promise<
{
scriptPath: string;
  taskDescription: string;
}> {
 ....
  console.log("scriptPath:", scriptPath);
console.log("taskLaunchPath:", taskLaunchPath);
console.log("taskDescription:", taskDescription);
  process.exit(0);
return { scriptPath, taskLaunchPath, taskDescription };
}

然后执行如下命令:

pnpm openclaw onboard --install-daemon

上面命令启动openclaw命令后,进入gateway进程的启动流程,然后会执行到writeScheduledTaskScript函数,等执行完三条console.log输出信息后退出,此时在console上看到类似下面的输出:

taskLaunchPath: C:\Users\OseasyVM\.openclaw\gateway.cmd
taskDescription: OpenClaw Gateway (v2026.5.20)

根据taskLaunchPath的路径,我们打开对应的gateway.cmd查看其内容:

@echo off
rem OpenClaw Gateway (v2026.5.20)
set "HOME=C:\Users\OseasyVM"
set "TMPDIR=C:\Users\OseasyVM\AppData\Local\Temp"
set "OPENCLAW_GATEWAY_PORT=18789"
set "OPENCLAW_SYSTEMD_UNIT=openclaw-gateway.service"
set "OPENCLAW_WINDOWS_TASK_NAME=OpenClaw Gateway"
set "OPENCLAW_SERVICE_MARKER=openclaw"
set "OPENCLAW_SERVICE_KIND=gateway"
set "OPENCLAW_SERVICE_VERSION=2026.5.20"
"C:\Program Files\nodejs\node.exe" C:\Users\OseasyVM\Documents\openclaw\dist\index.js gateway --port 18789

从上面脚本看到,他运行时会先设定一系列环境变量,然后启动node程序,让他执行我们编译后的dist\index.js,这是openclaw的入口文件,gateway表示执行gateway进程部分的代码,并绑定端口18789.现在我们去掉上面的调试输出代码,然后重新启动gateway:

openclaw onboard --install-daemon

相应的设置能”skip”就”skip”,先启动gateway进程再说。接下来的一系列配置可以根据自己的需要配置,我是选择了飞书,搜索选择了kimi,另外说一句,我在设置飞书为通讯app的时候,需要在飞书开发者后台创建一个应用,这个应用设置为机器人功能,因为我们需要通过gateway发送消息给飞书。要想实现网关与飞书的通讯,我们首先需要安装飞书差距,使用如下命令:

openclaw plugins install clawhub:@openclaw-cn/feishu

但是上面的插件存在问题,那就是它自己缺失了一个关键组件叫zod,首先我们先通过全局方式对其进行安装: “”” npm install -g zod “”” 完成上面安装后,我们需要针对飞书机器人应用进行相应配置,要想与openclaw网关应用通讯,我们需要确保机器人在事件配置上使用长连接来接收事件:

上面的配置告诉飞书:“把用户发送给机器人的消息推送过来”。然后需要开通相应权限,在左侧“权限管理”点击后,在右边的权限名称了搜索如下权限:

1.im:message

2.im:message.p2p_msg

3.im:message.group_at_msg

4.contact:contact.base:readonly :

然后进入“事件与回调”,选择订阅方式为“长连接”:

点击右下角的“添加事件”,在弹出的页面输入事件:im.message.receive_v1,然后选择添加这个事件:

然后点击上方的“创建版本”,让飞书管理员审核通过,通常情况下这个管理员都是你自己。接下来我们需要启动openclaw网关应用,使用管理员权限打开powershell,然后输入如下命令:

# 使用 cmd /c 来执行命令(会临时切换到 CMD 环境)
cmd /c "pnpm openclaw gateway install"

运行上面命令如果成功,你会得到如下输出:

OpenClaw 2026.5.20 (bde07dd) — I run on caffeine, JSON5, and the audacity of"it worked on my machine."


Installed Scheduled Task: OpenClaw Gateway
Task script: C:\Users\OseasyVM\.openclaw\gateway.cmd

看到上面输出说明网关服务安装成功,接下来我们需要启动网关服务,使用在powershell中执行如下命令: “”” cmd /c “pnpm openclaw gateway start” “”” 然后在你创建的飞书应用中随便输入一条消息,你会得到相应回复如下: “”” OpenClaw: access not configured. Your Feishu user id: XXXXXXXXXX Pairing code: XXXX Ask the bot owner to approve with: openclaw pairing approve feishu XXXX openclaw pairing approve feishu XXXX “”” 看到上面回复后说明网关服务启动成功,同时也跟你配置的飞书机器人配对成功。