news 2026/10/13 7:17:36

从零手搓Linux进程守护脚本:bash+systemd实现自动重启与日志轮转

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零手搓Linux进程守护脚本:bash+systemd实现自动重启与日志轮转

手里跑着一个叫openclaw的开源服务,用来处理内部定时任务和数据同步,最头疼的不是功能开发,而是怎么让它一直活着。直接nohup挂后台,机器一重启就断,断了可能一整天没人管;用supervisor又得装一堆Python依赖,配置还绕;系统自带systemd虽然能守护,但日志控制、单实例保护这些细节还是差点意思。最后我决定从0手搓一个自动启动神器,把开机自启、崩溃重启、日志轮转、单实例锁全部包进去。这篇文章就是完整的手搓过程,包括设计思路、代码实现、systemd接入,以及我在实际环境里踩过的一堆坑。

1. 为什么不用现成工具,非要手搓一个启动神器

1.1 现成方案的三座大山:依赖太重、行为不明、排错像黑盒

先说最常见的几种“现成方案”,它们各有各的别扭。

第一类是直接裸奔,比如nohup加&。这种方式启动openclaw最简单,但没有任何守护能力。进程一崩就永远消失了,你只能靠人工登录服务器查看。而且nohup产生的nohup.out文件会无限增长,几个月不看能把磁盘塞满,然后殃及同机其他服务。我早期就吃过这个亏,一个日志文件涨到几十GB,最后是删库重启才救回来。

第二类是用通用进程管理器,比如supervisor或pm2。supervisor的优点是功能全,自动重启、日志分流都有,但它的安装依赖在Python环境里经常出幺蛾子,多环境之间版本冲突能让人崩溃。pm2是Node生态的,如果想管理一个非Node进程,它虽然也能干,但多了一个Node运行时不说,pm2自己的守护行为还得养一个PM2 Root进程,一旦它挂了,管理的子进程全部遭殃。

第三类是直接用systemd的简单守护参数,比如Restart=always。这其实是很多生产环境的做法,但一旦你需要在启动前做环境检查、启动后做健康探测、崩溃时做告警,systemd自带的机制就不够灵活了。而且Restart=always遇到“进程启动后又立刻退出”的情况,systemd会严格按照间隔疯狂重拉,有时候会陷入重启风暴,把日志刷得飞起。

说到底,这三座大山的本质问题是:你无法在进程退出那一刻,真正看到“为什么退出”;你也无法在不额外引入新框架的情况下,把启动前的检查和启动后的探活串起来。排错就像对着黑盒猜。

1.2 手搓的核心优势:每一行逻辑都自己掌控

我最后选择手搓,理由其实很朴素:openclaw的运行环境是一个内网服务器,上面只有bash、coreutils和systemd,我不想为了一个守护进程再引入Python体系或Node体系。用bash脚本写一个启动管理外壳,再把systemd当作“开机触发器和最外层看门狗”,整体依赖为零,逻辑全在自己手里。

手搓带来的直接好处有三个:

  • 启动行为可控。我能指定“先检查配置文件是否存在、端口是否被占用、PID文件是否残留、上次异常退出状态”,再决定是否拉起进程。这些预检逻辑用systemd直接写非常别扭,但在脚本里就是几行if判断。

  • 退出信息可观测。每次进程退出,我都会在日志里记录退出码、时间戳、当前运行时长。下次启动前可以读取上次退出码,判断是正常关闭还是崩溃。这是现成工具很难给你的细节。

  • 实验成本低。我可以在本地反复改脚本,加入健康检查、内存监控、自动降级,随时重载生效,不用去翻各种工具的自定义规则。

所以,如果你也有一个必须长跑的守护进程,我建议你试着手搓一次。即便最后你不用它做生产,光是把这套逻辑想明白,对进程管理的理解也会上一个台阶。

1.3 这个启动神器到底应该包含什么

动手之前,我先列了一张功能清单,明确“神器”应该做到什么。没有需求直接写代码,后面一定会返工。

我的最终需求是六条:

  • 开机自启:机器重启后不用人工干预,openclaw自动拉起来。
  • 崩溃重启:进程意外退出后,能自动重新拉起,最多连续尝试N次,避免无限重启。
  • 单实例锁:同一时间只能有一个openclaw实例在跑,防止重复启动导致任务重复执行。
  • 日志可管:标准输出和标准错误分文件记录,并且按大小自动轮转,防止磁盘爆掉。
  • 状态可查:随时能检查进程是否活着、运行了多久、当前是不是健康。
  • 优雅停止:执行stop时给进程发送终止信号并等待,而不是直接kill -9。

后面的脚本和systemd配置,全部围绕这六条需求展开。你会发现,每一条需求都对应一个具体的代码机制,而不是一句空话。

2. 开工之前:openclaw的启动原理与自启核心拆解

2.1 openclaw进程到底是怎么跑起来的

先搞清楚openclaw本身是个什么东西。它本质上是一个长期驻留的命令行进程,启动命令大概是openclaw --config /etc/openclaw/config.yaml。启动后它会读取配置,连接内部的消息队列或数据库,然后开始监听任务。这个进程的特点是:前台运行,不主动退出,日志打到标准输出和标准错误。

这就意味着,启动它的最简单方式就是直接执行二进制文件,保持前台进程。而要把这样一个前台进程变成“可控服务”,外部需要一个管理壳来包住它:负责启动它、观察它、在它死后拉起它、在需要停止时通知它退出。

所以,我的“启动神器”不是替代openclaw,而是openclaw外面的一层包装。这层包装在Unix世界里有一个经典叫法——守护进程包装器,我用bash实现。

2.2 自启的本质就三件事:开机拉起、崩溃拉起、日志可查

想通之后,自启这件事可以抽象成三个核心动作。

第一件事是开机拉起。操作系统启动完成后,需要有一个东西能把这个脚本跑起来。在大多数Linux发行版上,这就是systemd单元的职责。我把systemd配置成一个服务,在multi-user.target之后启动我的脚本。

第二件事是崩溃拉起。进程自己不会复活,需要外部不断观察它的存活状态。我在脚本里用了一个while循环,循环内启动openclaw,启动后等待进程退出。如果退出,判断是不是“预期退出”(比如收到stop命令),如果是就跳出循环;否则记录退出码,延迟几秒再启动下一轮。这样就从机制上保证了崩溃后能自动拉起。

第三件事是日志可查。进程的stdout/stderr不应该直接丢到黑洞,也不应该无限累积。我在脚本里把输出按时间戳重定向到指定目录,并写入当前实例ID,这样后排查问题能精确到某一次崩溃。配合日志轮转,磁盘安全也有保障。

想清楚这三件事,剩下的实现都是细节。

2.3 选型:脚本 + systemd + 文件锁的组合拳

技术选型上,我没有用任何现成的守护工具,而是组合了三样基础能力:

  • bash脚本:承载启动、停止、状态检查、崩溃拉起逻辑。
  • systemd:承载开机启动和外部看护,同时提供一定的重启兜底。
  • flock文件锁:保证单实例,避免并发启动引起的混乱。

为什么加文件锁?因为systemd在某些情况下会重复启动脚本,或者脚本被人手动执行多次。如果没有锁,就会拉起多个openclaw进程。多个实例同时处理同一个任务队列,轻则任务重复执行,重则数据错乱。使用flock加锁之后,后出现的进程会直接退出,保证了唯一性。

这套组合的优势是只依赖Linux原生组件,任何发行版都跑得起来,也和具体的开源生态无关。

3. 手搓启动管理脚本:从设计到落地

3.1 脚本骨架:启动、停止、重启、状态

先设计脚本的对外接口。我希望它像systemd一样支持四个子命令,用起来很顺手:

  • ./openclaw-ctl.sh start:启动openclaw(若已在运行则跳过)。
  • ./openclaw-ctl.sh stop:优雅停止openclaw。
  • ./openclaw-ctl.sh restart:重启openclaw。
  • ./openclaw-ctl.sh status:查看运行状态。

脚本内部用函数封装四个动作,最外层再用case匹配参数。这个骨架特别适合扩展,以后想加reload、monitor,只需要在case里加分支就行。

3.2 关键机制一:PID文件与单实例锁

先讲PID文件和文件锁。PID文件路径我放在/var/run/openclaw.pid,但要注意:直接写PID文件有一个经典毛病——如果上一次进程被kill -9杀掉,PID文件可能残留。下一次启动时,检测到PID文件存在就误以为进程在跑,直接拒绝启动,这就坑了。

所以我每次检测PID文件时,都会用kill -0 $PID去探测对应进程是否真实存在。如果PID不存在,说明是残留PID文件,就清理掉再继续启动。

为了更保险,我还用了flock -n 9的方式给整个脚本的启动段加锁。锁串在文件描述符9上,锁文件路径是/var/run/openclaw.lock。-n表示非阻塞,获取不到锁就直接失败,避免两个终端同时执行start命令。

双保险的好处是,既能防止外部误操作,也能防止同进程内部的竞争条件。

3.3 关键机制二:崩溃自愈与优雅退出

核心部分来了。启动函数里有一个无限循环,逻辑如下:

while true; do start_openclaw wait $OPENCLAW_PID exit_code=$? log "openclaw exited with code $exit_code after ${runtime} seconds" if [ -f "$STOP_FLAG" ]; then log "stop flag detected, exiting" break fi # 连续崩溃次数过多则止步 if [ $crash_count -ge $MAX_CRASH_COUNT ]; then log "too many crashes, give up" break fi sleep $RESTART_DELAY done

这里有一个很关键的设计:使用STOP_FLAG文件。当执行stop命令时,脚本先创建一个标记文件,再向openclaw发送TERM信号。openclaw收到信号后会优雅退出,随后wait返回。此时循环检测到stop标记,就认为是预期退出,不再拉起。这个方案比单纯判断退出码更可靠,因为很多程序无论何种退出都会返回0,你必须用外部标记判断意图。

崩溃自愈的逻辑在循环中自然实现。每次非预期退出,我记录退出码并累计崩溃次数。如果短时间连续崩溃超过最大值,说明openclaw或配置有严重问题,此时继续重启只会刷日志和消耗CPU,不如停下来等人处理。我会在日志中打一条ERROR级别记录,告警也会在这里触发。

3.4 关键机制三:日志轮转与磁盘保护

日志管理我参考了logrotate思路,但没有额外安装,而是写了一个简单的按大小轮转函数。每次写入前检查日志文件大小,超过50MB就把当前日志重命名为带时间戳的备份,再重新写新日志。

roll_log() { local logfile="$1" if [ -f "$logfile" ] && [ $(stat -c%s "$logfile") -gt $MAX_LOG_SIZE ]; then mv "$logfile" "$logfile.$(date +%Y%m%d%H%M%S)" log "log rotated: $logfile" fi }

注意轮转动作必须在启动子进程之前检查,并且重定向打开新文件后再启动openclaw,避免文件描述符指向已删除的文件。保留最近5个轮转备份,更早的用find删除。这个机制我用生产环境跑了大半年,磁盘再也没有因为日志爆过。

3.5 脚本完整实现

下面是我实际用的脚本,删除了业务相关细节,只保留通用逻辑。你可以直接复制后替换掉openclaw路径和参数就能用。

#!/usr/bin/env bash # openclaw-ctl.sh - 从0手搓的openclaw自动启动神器 # 用法: ./openclaw-ctl.sh {start|stop|restart|status} set -u # ---------- 配置区 ---------- OPENCLAW_BIN="/usr/local/bin/openclaw" OPENCLAW_CONFIG="/etc/openclaw/config.yaml" OPENCLAW_ARGS="--config $OPENCLAW_CONFIG" RUN_DIR="/var/run/openclaw" PID_FILE="$RUN_DIR/openclaw.pid" LOCK_FILE="$RUN_DIR/openclaw.lock" LOG_DIR="/var/log/openclaw" STDOUT_LOG="$LOG_DIR/stdout.log" STDERR_LOG="$LOG_DIR/stderr.log" STOP_FLAG="$RUN_DIR/stop.flag" MAX_CRASH_COUNT=3 RESTART_DELAY=3 MAX_LOG_SIZE=$((50 * 1024 * 1024)) # --------------------------- mkdir -p "$RUN_DIR" "$LOG_DIR" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_DIR/manage.log" } check_running() { if [ -f "$PID_FILE" ]; then local pid pid=$(cat "$PID_FILE") if kill -0 "$pid" 2>/dev/null; then return 0 else rm -f "$PID_FILE" log "stale pid file removed" fi fi return 1 } start_openclaw() { roll_log "$STDOUT_LOG" roll_log "$STDERR_LOG" log "starting openclaw..." $OPENCLAW_BIN $OPENCLAW_ARGS >>"$STDOUT_LOG" 2>>"$STDERR_LOG" & local pid=$! echo "$pid" > "$PID_FILE" log "openclaw started, pid=$pid" } roll_log() { local logfile="$1" if [ -f "$logfile" ] && [ "$(stat -c%s "$logfile")" -gt "$MAX_LOG_SIZE" ]; then mv "$logfile" "$logfile.$(date +%Y%m%d%H%M%S)" # 只保留最近5份 ls -1t "$logfile".* 2>/dev/null | tail -n +6 | xargs -r rm -f log "log rotated: $logfile" fi } start() { # 单实例锁 exec 9>"$LOCK_FILE" if ! flock -n 9; then log "another instance is running, exit" exit 1 fi if check_running; then log "openclaw already running, skip start" exit 0 fi rm -f "$STOP_FLAG" local crash_count=0 while true; do start_openclaw wait $(cat "$PID_FILE") local exit_code=$? rm -f "$PID_FILE" log "openclaw exited, code=$exit_code" if [ -f "$STOP_FLAG" ]; then log "stop flag found, exiting gracefully" break fi crash_count=$((crash_count + 1)) log "crash count: $crash_count/$MAX_CRASH_COUNT" if [ "$crash_count" -ge "$MAX_CRASH_COUNT" ]; then log "reached max crash count, give up" # 这里可以触发告警 break fi sleep "$RESTART_DELAY" done exit 0 } stop() { touch "$STOP_FLAG" if check_running; then local pid pid=$(cat "$PID_FILE") log "sending SIGTERM to pid=$pid" kill -TERM "$pid" # 最多等待15秒 for _ in $(seq 1 15); do if ! kill -0 "$pid" 2>/dev/null; then break fi sleep 1 done if kill -0 "$pid" 2>/dev/null; then log "process still alive after timeout, sending SIGKILL" kill -KILL "$pid" fi rm -f "$PID_FILE" log "openclaw stopped" else log "openclaw not running" fi rm -f "$STOP_FLAG" } status() { if check_running; then local pid pid=$(cat "$PID_FILE") local runtime runtime=$(ps -o etimes= -p "$pid" | tr -d ' ') log "openclaw is running, pid=$pid, uptime=${runtime}s" exit 0 else log "openclaw is not running" exit 1 fi } restart() { stop sleep 1 start } case "${1:-}" in start) start ;; stop) stop ;; restart) restart ;; status) status ;; *) echo "usage: $0 {start|stop|restart|status}" >&2; exit 1 ;; esac

这里我用了wait $(cat "$PID_FILE")去等待openclaw进程退出。注意,如果预先知道$!,也可以直接wait $!。但把$!记到PID文件后再读取是为了统一接口,方便status读取。有一点要注意:$PID_FILE里的pid就是子进程PID,不是后台子shell的PID,所以wait能等到它。

另一个细节是,脚本里set -u而不是set -e,因为这里很多检查都是基于返回值做分支的,set -e会让kill -0失败时直接退出。新手经常会在这里被误导。

4. 接入系统自启:systemd服务配置实战

4.1 为什么选systemd而不是rc.local

在Linux上做开机自启,有几种常见姿势:rc.local、crontab @reboot、systemd。rc.local在很多新发行版已经默认不生效了,需要额外开启服务;crontab @reboot虽然能触发,但它在网络和文件系统还没准备好时会直接跑,容易失败。systemd是现在的事实标准,它精确控制依赖顺序,还能在进程死掉后兜底重启。

所以我把手搓的bash脚本包装成一个systemd服务,让systemd在开机后启动这个脚本,再用脚本内部的循环做精细的崩溃管理。两个层级和明确分工:systemd管开机触发,脚本管进程生命周期。

4.2 服务文件的完整写法与每一行参数解释

在/etc/systemd/system/openclaw.service里写入下面内容:

[Unit] Description=OpenClaw Auto Start Service After=network-online.target Wants=network-online.target [Service] Type=simple User=openclaw Group=openclaw WorkingDirectory=/home/openclaw ExecStart=/opt/openclaw/openclaw-ctl.sh start ExecStop=/opt/openclaw/openclaw-ctl.sh stop ExecReload=/opt/openclaw/openclaw-ctl.sh restart Restart=on-failure RestartSec=5s EnvironmentFile=/etc/openclaw/openclaw.env StandardOutput=journal StandardError=journal TimeoutStopSec=20s [Install] WantedBy=multi-user.target

逐行解释几个关键参数。

Type=simple:表示ExecStart启动的进程就是主进程,不fork。因为我的bash脚本start函数里的循环是在前台跑的,整个脚本生命周期等于openclaw守护生命周期。如果使用Type=forking,systemd会认为脚本会自己fork到后台,导致无法跟踪子进程。这里必须分清。

ExecStart=/opt/openclaw/openclaw-ctl.sh start:脚本启动后进入while循环,阻塞在前台。systemd认为这个服务还在运行。这正好符合我们对“守护”的预期。

ExecStop=/opt/openclaw/openclaw-ctl.sh stop:停止时执行脚本的stop函数,实现优雅关闭。

Restart=on-failure:这是第二层守护。正常stop时systemd不会重启,非正常退出时会重启。即使我的脚本内部逻辑有bug导致整个脚本退出,systemd也会再把它拉起来。

EnvironmentFile:非常关键。很多程序在bash里手动启动没问题,一到systemd就报错,就是因为少了运行所需的环境变量。我把openclaw需要的JAVA_HOME、PATH、LD_LIBRARY_PATH等放到一个env文件里,systemd启动前自动读取。

4.3 启用服务并踩坑实录

写完service文件后,执行:

sudo systemctl daemon-reload sudo systemctl enable openclaw.service sudo systemctl start openclaw.service

这里我踩过的第一个坑:直接enable后没有daemon-reload,导致systemd还在用旧配置。所以任何时候改了service文件,必须先reload。

第二个坑是文件权限。因为服务用User=openclaw运行,脚本和数据目录必须对openclaw用户可读写。如果不小心用root手工跑过一次脚本,生成的/var/run/openclaw.pid和日志文件归属root,openclaw用户无法删除,就会导致启动失败。我后来在脚本里加了权限检查和自动修正。

第三个坑是TimeoutStartSec。默认的90秒对于一般程序足够了,但有一次配置文件写错,openclaw启动时反复重试,卡在预检阶段,systemd直接判定启动超时,服务进入failed状态。日志里只看到“start operation timed out”,根本不知道是配置问题。后来我把预检逻辑移到脚本内部,并将预检失败直接给非零退出码,这样systemd会记录更明确的失败原因。

5. 常见问题与排查技巧实录

5.1 开机自启失效的三大原因

第一,没有执行enable。systemctl start只在当前会话有效,重启后不会自动启动。很多人只start不enable,然后说“开机自启怎么没用”,其实是自己漏了enable。

第二,依赖网络的服务过早启动。如果openclaw需要连接外部数据库或消息队列,而After只写了network.target,在网卡启动但DHCP还没拿到地址时就会启动失败。我改成After=network-online.target并加上Wants=network-online.target才稳定。

第三,系统时间或锁文件异常。如果上一个实例没有正常退出,PID文件残留,新实例通过kill -0判断后发现PID不存在,会清理后启动。但如果PID恰好被其他进程复用,就会错误地认为openclaw在运行,拒绝启动。这种鬼情况概率低但真实存在,我后来在PID文件里加上了启动时间戳,并校验进程名来规避。

5.2 进程反复重启的诡异现象

现象是日志里快速刷出了一堆“openclaw started”和“openclaw exited”,看状态一直处于restarting。

排查后发现两个原因:

一个原因是openclaw的配置文件指向了不存在的目录,启动即退出,退出码非0。而我的脚本里没有设置最大崩溃次数和延迟,导致无限快速循环。后来我加了MAX_CRASH_COUNT和RESTART_DELAY解决。

第二个原因是文件锁与崩溃循环的交互。由于脚本的整个start阶段持有文件锁,openclaw崩溃时锁还持有,如果此时手动执行stop,stop尝试创建STOP_FLAG,但因为while循环还在崩溃重试的sleep中,所以需要等重试次数耗尽或stop信号进入下一个循环检测。我在后续版本改进了stop逻辑:stop时先写入STOP_FLAG,再向当前PID发TERM,TERM传给了等待中的循环,循环通过wait拿到退出,再检测到STOP_FLAG退出。这样stop在3秒内生效。

5.3 日志文件疯狂膨胀

磁盘告警是最容易忽视的。即使我加了50MB轮转,如果openclaw在死循环中疯狂打印错误,每秒钟可能写几十MB,轮转跟不上。后来我在roll_log函数里加了一个限制:如果一小时内轮转次数超过10次,就暂时抑制stdout日志输出,只保留stderr。

更本质的预防是限制日志量。openclaw自身有日志级别配置,把debug级别关掉,生产环境用info级别就足够了。

如果你的程序没法按级别控制日志,我再给你一个暴力办法:写个cron任务每小时检查日志目录总大小,超过阈值就清空最老的日志。虽然粗暴,但能兜底。

5.4 环境变量丢失导致启动失败

用systemd跑脚本时,bash无法继承你手动登录shell里的环境变量。这是很多自动化场景翻车的原因。

症状是脚本手工跑start一切正常,用systemd跑却报“找不到动态库”或“找不到命令”。

解决办法就是在service文件里加一行Environment=或者在/etc/openclaw/openclaw.env里写明所有变量。建议把所有环境变量集中到一个文件,方便统一管理。

另一个细节:如果openclaw用了systemd的StandardOutput=journal,在脚本里又重定向了>> $STDOUT_LOG,那么日志会被复制两份。我后来在脚本里删除了内部重定向,直接让systemd收集到journal,再用journalctl -u openclaw查看。这样排错更统一。

5.5 实用排错命令速查表

下面是我排查问题时最常用的命令,整理成表格方便你参考。

场景命令
查看服务状态systemctl status openclaw.service
查看主进程PIDsystemctl show -p MainPID openclaw.service
查看服务日志journalctl -u openclaw.service -f
查看上次启动的日志journalctl -u openclaw.service -n 200
检查脚本语法bash -n openclaw-ctl.sh
追踪进程退出码echo $?或者在脚本中加入日志
检查PID文件cat /var/run/openclaw/openclaw.pid
检查文件锁状态flock -n /var/run/openclaw/openclaw.lock -c "echo unlocked"
检查开机自启systemctl is-enabled openclaw.service
查看所有相关进程pgrep -a openclaw

用这个表,大多数问题都能在十分钟内定位。

6. 升级与扩展:让启动神器更聪明

6.1 健康检查与探活

有了基本守护之后,我开始往脚本里加“活体检测”。光看进程存活其实是不够的——如果openclaw进程还活着,但内部线程死锁,任务已经完全不处理了,那它跟死了也没区别。

我给openclaw加了一个HTTP健康端点,比如/healthz,脚本每隔30秒请求一次,如果连续3次超时,就认为服务不健康,执行restart。这个逻辑可以放在一个独立函数里,用cron定时调用,或者在start循环里开一个后台子进程周期检测。

实现起来不复杂:

health_check() { local url="${HEALTH_URL:-http://127.0.0.1:8080/healthz}" if curl -sf -m 5 "$url" >/dev/null; then return 0 else return 1 fi }

我把健康检查接入到主循环里:每次拉起openclaw后,等待5秒,做一次健康检查,如果检查失败就计入连续崩溃,超过阈值则放弃。这样从“进程活着”升级到“服务可用”。

6.2 告警通知

生产环境出了事,最好第一时间通知人。我不想引入复杂的监控系统,就在脚本的“达到最大崩溃次数”分支里加了一个告警函数。

发送方式我用最简单的curl POST,打给内部的通知服务(比如企业微信机器人或钉钉机器人),标题和内容就是本次崩溃的退出码、连续次数、时间、PID文件状态。如果你连curl都不想依赖,还可以用mail命令发邮件,但内网可能没有邮件服务。

我的建议是:先保证外壳日志里能完整记录,然后接一条webhook通知,这样最少也能做到“挂了立刻知道”。如果你有监控系统,也可以让监控系统直接盯systemd服务状态,省掉脚本里的告警。

6.3 定时自检与内存回收

最后一个扩展是定时任务。openclaw跑久了,偶尔会因为第三方库内存泄漏导致RSS持续上涨,最后把整台机器拖垮。我写了一个独立脚本openclaw-memcheck.sh,通过crontab每30分钟执行一次,检查进程RSS占用,如果超过设定阈值(比如2GB),就执行openclaw-ctl.sh restart。

#!/usr/bin/env bash # 检查openclaw内存,超过阈值则重启 MAX_RSS_MB=${MAX_RSS_MB:-2048} pid=$(cat /var/run/openclaw/openclaw.pid 2>/dev/null) if [ -z "$pid" ]; then exit 0 fi rss_kb=$(ps -o rss= -p "$pid" | tr -d ' ') rss_mb=$((rss_kb / 1024)) if [ "$rss_mb" -gt "$MAX_RSS_MB" ]; then echo "$(date) restart openclaw due to high memory: ${rss_mb}MB" >> /var/log/openclaw/manage.log /opt/openclaw/openclaw-ctl.sh restart fi

这个方法很土,但有效。而且因为是重启级的干预,不会产生累积错误。如果你不想定时,也可以把内存检查放进后台循环,每隔5分钟跑一次,逻辑类似。

手搓这套东西花了大概一天时间,之后一直在稳定跑。我现在最大的体会是:别迷信现成工具,也别小看bash脚本。当你亲手把PID文件、退出码、文件锁、状态标志都理顺之后,再去看任何复杂的进程管理器,脑子里都是透明的。最后分享一个细节:修改脚本后不要忘了用bash -n做语法检查,我至少有一次因为少了then关键字导致systemd服务起不来,排查了半天才发现是脚本语法错误。希望这套方案能给你一些启发,你完全可以根据自己的场景改出更适合的启动神器。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 7:17:07

PdfiumViewer在WinForms中的PDF渲染集成与性能优化实践

简介:PdfiumViewer 是一款基于 C# 构建的 PDF 查看控件,面向需要在 .NET 桌面应用中快速集成文档展示能力的开发者。它通过封装 Google 开源的 Pdfium 渲染引擎,支持 Windows、Linux 与 macOS 跨平台运行,并实现了缩放、平移、搜索…

作者头像 李华
网站建设 2026/10/13 7:17:03

Fabric Java链码开发:联盟链落地的最小可行切口

1. 为什么毕业设计选“Fabric链码开发”不是跟风,而是踩准了联盟链落地的最小切口最近帮几个高校的某导师带毕设,翻了二十多份开题报告,发现一个有意思的现象:超过七成学生写“区块链”,但真正能说清自己在链上“干了什…

作者头像 李华
网站建设 2026/10/13 7:12:53

Codex CLI 接入第三方 API 完整指南:config.toml 配置与 401/404 排查

如果你以为把 Codex CLI 接到第三方 API 服务只是改改base_url的事,那我得先给你泼盆冷水——这事的常见结局是:一脸自信地改完 config.toml,然后贴一张 401 或 404 的报错截图去群里求助。我第一次接公司内部模型网关的时候,也抱…

作者头像 李华
网站建设 2026/10/13 7:10:11

给Agent建网站:用结构化skills消除AI幻觉,提升AI可靠性

如果你最近在做 Agent 类的应用,大概率见过同一个尴尬场景:让模型去查一份文档,它语气肯定地回了一段根本不存在的内容。我做了一个给 Agent 用的网站,把关键流程和数据结构拆成 skills 放进去,核心目标就一句话——让…

作者头像 李华
网站建设 2026/10/13 7:08:45

Python制作数据分析透视表的方法详解

前言 透视表(pivot table)是把"长表"转成"交叉表"的经典操作: 用一列做行标签、一列做列标签、一列做取值,交叉的格子里放聚合结果。 Excel 里的透视表人人会点,pandas 里对应的是 pivot_table。 …

作者头像 李华
网站建设 2026/10/13 7:08:30

工业互联网技术体系拆解:三层架构、避坑与落地实战

简介:面向工业互联网与智能制造初学者的一份体系化PPT课件,定位为工业互联网技术体系的入门与概览材料。课件以制造技术、信息技术、融合技术为主线,系统梳理了工业App低代码开发、安全技术、工业大数据处理与管理、数字孪生、感知与工业视觉…

作者头像 李华