Algernon 1.17.12 日常运维实操指南

Algernon 1.17.12 日常运维实操全流程实测:巡检、systemd 启停与信号行为(SIGHUP 重开日志、SIGTERM 退出码 1)、logrotate 轮转、备份恢复演练、1.17.11 到 1.17.12 升级回滚与四个排障实录。

AlgernonAlgernonGoWeb 服务器2026/10/24

Algernon 1.17.12 日常运维实操指南

目标:面向已按 《Ubuntu 24.04 安装 Algernon 1.17.12 指南》完成部署的运维人员,给出一套以实测为准的日常运维实操手册——覆盖日常巡检、服务启停与信号行为、日志管理与轮转、备份与恢复演练、版本升级与回滚,以及本文实测过程中沉淀的四个排障实录。本文所有命令均在 Ubuntu 24.04.5 LTS(x86_64)服务器上针对 Algernon 1.17.12 真实执行,输出为实测回显(已略去终端配色转义符)。

一、环境信息

本文实操基于以下环境,所有回显均可按此环境复现:

  • 操作系统:Ubuntu 24.04.5 LTS(x86_64),主机名 algernon-server;
  • Algernon:1.17.12,静态二进制位于 /usr/local/bin/algernon;
  • 服务托管:systemd 单元 algernon.service,专用系统用户 algernon 运行;
  • 站点目录:/srv/algernon;数据库:Bolt 单文件 /var/lib/algernon/algernon.db;
  • 日志:应用日志 /var/log/algernon/app.log,访问日志 /var/log/algernon/access.log;
  • 监听::3000(HTTP);
  • 备份目录:/var/backups/algernon。

本文示例的 algernon.service 在安装篇单元的基础上做了两处运维改造:ExecStart 接入了 --boltdb、--log、--accesslog 三个参数(让数据库路径与日志落盘位置固定,参数含义见 《Algernon 1.17.12 配置参数详解》第三章),并新增 SuccessExitStatus=1(作用见第三章 3.4 节的排障实录)。当前生效的完整单元文件如下(systemctl cat algernon.service 实测):

[Unit]
Description=Algernon web server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=algernon
Group=algernon
WorkingDirectory=/srv/algernon
ExecStart=/usr/local/bin/algernon --httponly --addr=:3000 --noninteractive --boltdb=/var/lib/algernon/algernon.db --log /var/log/algernon/app.log --accesslog /var/log/algernon/access.log /srv/algernon
Restart=on-failure
RestartSec=3
SuccessExitStatus=1
StateDirectory=algernon

[Install]
WantedBy=multi-user.target

💡 说明:Restart=on-failure 表示主进程以非零退出码或被信号杀死时自动重启,RestartSec=3 表示重启间隔 3 秒。这两个键与本文第三章的「手动 kill 陷阱」「stop 后状态 failed」两个现象直接相关,读懂它们才能理解后文的现象。

二、日常巡检

日常巡检的目标是用一组固定命令在 1 分钟内回答四个问题:服务活着吗?端口在听吗?站点能打开吗?资源与数据文件正常吗?以下命令逐条给出实测回显。

2.1 服务状态巡检

先看 systemd 对服务的判定,is-active 给出最简结论,status 给出细节:

systemctl is-active algernon.service
active
systemctl status algernon.service --no-pager -l
● algernon.service - Algernon web server
     Loaded: loaded (/etc/systemd/system/algernon.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-10-24 04:49:22 UTC; 2s ago
   Main PID: 10620 (algernon)
      Tasks: 11 (limit: 94371)
     Memory: 11.3M (peak: 12.0M)
        CPU: 25ms
     CGroup: /system.slice/algernon.service
             └─10620 /usr/local/bin/algernon --httponly --addr=:3000 --noninteractive --boltdb=/var/lib/algernon/algernon.db --log /var/log/algernon/app.log --accesslog /var/log/algernon/access.log /srv/algernon

status 输出里的 Memory 是 systemd 的 cgroup 记账值(启动初期约 11 MB),而 ps 看到的 RSS 会随缓存增长(见 2.4 节实测约 35 MB),两个数字口径不同,巡检时任选其一保持口径一致即可。Active: 行的 active (running) 与 Main PID 是判断服务存活的核心字段。

再确认开机自启是否在位(重装或迁移服务器后容易漏配):

systemctl is-enabled algernon.service
enabled

2.2 进程与端口巡检

确认主进程参数与监听端口,用 ss 检查 3000 端口确实由 algernon 进程持有:

ss -lntp | grep :3000
LISTEN 0      4096               *:3000             *:*    users:(("algernon",pid=11115,fd=9))

回显显示 *:3000 处于 LISTEN 状态,持有者是 algernon 进程。如果此命令无输出而服务 is-active 仍为 active,说明进程刚启动尚未完成监听或启动即报错,转入第七章 7.1 节排查。

查看主进程的完整启动参数(确认线上跑的到底是哪套参数,避免「改了配置没生效」的误判):

ps -o pid,user,rss,etime,args -p $(systemctl show -p MainPID --value algernon.service) | tail -1
  11115 algernon 35988       00:11 /usr/local/bin/algernon --httponly --addr=:3000 --noninteractive --boltdb=/var/lib/algernon/algernon.db --log /var/log/algernon/app.log --accesslog /var/log/algernon/access.log /srv/algernon

args 列完整还原了当前生效的启动参数,与单元文件 ExecStart 一致即说明配置已生效。

2.3 站点可用性探测

端口在听不等于页面能出,补一发 HTTP 探测拿状态码:

curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
200

200 即站点可用。建议把 2.1 节的 is-active、本节的 curl 探测与 2.4 节的数据库文件检查组合成一个巡检脚本,接入 cron 或监控系统的探针。

2.4 数据文件与资源巡检

Algernon 的持久化状态只有两个落盘点:站点目录与 Bolt 数据库文件。巡检时确认它们存在、属主正确、大小无异常:

ls -la /var/lib/algernon/algernon.db
du -sh /srv/algernon /var/lib/algernon
-rw------- 1 algernon algernon 32768 Oct 24 04:57 /var/lib/algernon/algernon.db
104K    /srv/algernon
28K     /var/lib/algernon

两个要点:一是数据库文件属主必须是 algernon:algernon,权限 600,属主错乱会直接导致服务起不来(实测见 7.1 节);二是 Bolt 库大小按 4 KB 页对齐增长,32768 字节即 8 页,属于轻量使用场景的正常水位,若发现库文件异常膨胀可结合应用日志排查写入逻辑。

三、服务启停与配置重载

本章先给出 systemd 的标准启停操作,再通过三个实测现象讲清 Algernon 的信号行为——包括一个容易踩的「手动 kill 杀不死」陷阱和一处「stop 后状态 failed」的反直觉表现,最后给出修复后的干净停机流程。

3.1 标准启停与重启

日常启停一律走 systemctl。重启与启动的实测:

systemctl start algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
active
200
systemctl restart algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
active
200

restart 会完整停掉旧进程再拉起新进程,适合参数变更后使用;它会造成秒级中断,线上高可用场景应安排在流量低谷执行。

但首次执行 systemctl stop 时出现了一个反直觉现象——服务停了,状态却不是 inactive 而是 failed:

systemctl stop algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 2 http://localhost:3000/
failed
000

端口确已释放(000 表示连接被拒绝),服务也确实停了,但 is-active 给出 failed,且日志里出现「Deactivated successfully」的矛盾组合。根因与解法在 3.3 节实测信号行为后于 3.4 节给出。

3.2 陷阱:手动 kill 主 PID 会被自动拉起

因为单元配置了 Restart=on-failure,直接对主进程发 SIGTERM 不会停掉服务,反而触发自动重启。实测验证:

MAINPID=$(systemctl show -p MainPID --value algernon.service)
echo $MAINPID
kill -TERM $MAINPID
sleep 4
systemctl is-active algernon.service
systemctl show -p MainPID --value algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
10332
active
10579
200

时序解读:主进程 10332 收到 SIGTERM 后以退出码 1 结束(信号行为实测见 3.3 节),systemd 将其判为「失败退出」,Restart=on-failure 随即生效,3 秒后拉起新进程 10579,站点探测仍为 200。现象上就是「kill 了又活了」。

正确的停止方式只有一个:systemctl stop。systemd 的 stop 操作属于主动管理行为,不会触发 Restart=;若确需模拟「崩溃后自动拉起」的故障演练,手动 kill 反而是正确手段。

3.3 信号行为实测:SIGTERM/SIGINT 退出码 1、SIGHUP 重开日志

Algernon 对三种常用信号的行为,用一个临时独立进程逐个实测。先准备一个最小站点并以非交互模式启动:

mkdir -p /root/sigtest
echo "Hello SIGTERM test" > /root/sigtest/index.html
cd /root/sigtest
algernon -s -t --nodb -n . :3304 >/tmp/sigterm_final.log 2>&1 &

其中 -s 生产模式、-t 仅 HTTP、--nodb 关闭数据库、-n 非交互(脱离终端运行必备)、:3304 避开主服务端口。SIGTERM 实测:

APID=$!
sleep 2
kill -0 $APID && echo alive
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3304/
kill -TERM $APID
wait $APID
echo "exit_code=$?"
sleep 1
curl -s -o /dev/null -w "%{http_code}\n" --max-time 2 http://localhost:3304/
tail -2 /tmp/sigterm_final.log
alive
200
exit_code=1
000
time="2026-10-24T05:02:51Z" level=info msg="Serving HTTP on http://localhost:3304/"
time="2026-10-24T05:02:53Z" level=fatal msg=Interrupted

SIGINT(Ctrl+C 同款信号)实测:

algernon -s -t --nodb -n . :3305 >/tmp/sigint.log 2>&1 &
BPID=$!
sleep 2
kill -INT $BPID
wait $BPID
echo "exit_code=$?"
exit_code=1

两组实测结论一致:SIGTERM 与 SIGINT 都让 Algernon 以退出码 1 结束,日志末尾留下 level=fatal msg=Interrupted,端口随即释放(000)。这就是 3.1 节 stop 后状态显示 failed 的根因——systemd 发出 SIGTERM,Algernon 以非零码退出,systemd 把这次退出记为失败。 Algernon 1.17.12 没有「优雅排空后再退出」的握手流程,SIGTERM 即直接退出,中断窗口为毫秒级,靠 Restart=on-failure 与负载均衡兜底即可。

SIGHUP 的行为完全不同:它不会退出进程,而是让运行中的服务重新打开日志文件(详见 4.4 节的完整实测)。

3.4 修复:加 SuccessExitStatus=1 让 stop 干净落地

systemd 提供 SuccessExitStatus= 键,把指定的退出码声明为「成功退出」。在单元文件 [Service] 段追加一行,把 Algernon 的 SIGTERM 退出码 1 纳入成功范围:

SuccessExitStatus=1

修改单元文件后按固定流程生效:重载单元定义 → 重启服务 → 验证 stop 行为:

systemctl daemon-reload
systemctl restart algernon.service
sleep 2
systemctl stop algernon.service
sleep 1
systemctl is-active algernon.service
systemctl start algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
inactive
active
200

修复后 systemctl stop 的落地状态从 failed 变为 inactive,不再在监控里产生误报。若此前已出现过 failed 状态,可用 systemctl reset-failed algernon.service 清掉历史失败记录。本文环境信息中的单元文件即包含这一行。

四、日志管理

本章覆盖 Algernon 运维中的三类日志:落盘的应用日志与访问日志、journal 收纳的运行日志,以及 logrotate 自动轮转。目标是不丢日志、不被日志撑爆磁盘、轮转后服务无感。

4.1 文件日志的落盘与格式

单元文件的 ExecStart 里接入了两个日志参数:--log /var/log/algernon/app.log 收应用日志,--accesslog /var/log/algernon/access.log 收访问日志。两者的格式不同,实测文件内容如下。

应用日志为 JSON 行格式,每行一条事件(实测节选):

tail -3 /var/log/algernon/app.log
{"level":"info","msg":"Serving HTTP on http://localhost:3000/","time":"2026-10-24T04:32:13Z"}
{"level":"error","msg":"Interrupted","time":"2026-10-24T04:36:01Z"}
{"level":"info","msg":"Serving HTTP on http://localhost:3000/","time":"2026-10-24T04:36:01Z"}

level 字段取值为 info、warning、error、fatal,做告警时按 error 及以上过滤即可。JSON 行格式可直接被 Filebeat、Promtail 等采集器解析。

访问日志为 Apache CLF(Common Log Format)格式(实测节选):

tail -2 /var/log/algernon/access.log
::1 -  [24/Oct/2026:04:30:41 +0000] "GET / HTTP/1.1" 200 251 "" "curl/8.5.0"
::1 -  [24/Oct/2026:04:31:54 +0000] "GET / HTTP/1.1" 200 251 "" "curl/8.5.0"

除 CLF 外,Algernon 还支持 --ncsa 参数输出不含 Referer/User-Agent 的精简 NCSA 格式,两格式实测对比(同一批请求):

::1 - - [23/Oct/2026:19:44:38 +0000] "GET / HTTP/1.1" 200 76 "" "curl/8.5.0"     ← accesslog(CLF)
::1 - - [23/Oct/2026:19:44:38 +0000] "GET / HTTP/1.1" 200 76                     ← ncsa(精简)

💡 技术拓展:Web 访问日志的常见格式不止一种。主流选项对比——

格式内容典型用途
CLF(Common)IP、时间、请求行、状态码、字节数最基础的吞吐统计
CLF + Referer/UA(Combined)在 CLF 上追加来源页与客户端标识来源分析、爬虫识别
NCSA 精简仅 IP、时间、请求行、状态码、字节数高流量下压缩日志体积
JSON Lines全字段结构化直接进 ELK/Loki 等采集管道

Algernon 的 --accesslog 输出 Combined 风格 CLF、--ncsa 输出精简 NCSA,应用日志 --log 则是 JSON 行;本文环境三路并行,访问分析用 access.log,告警用 app.log。

4.2 journalctl 查看运行日志

即便接入了文件日志,systemd journal 仍完整记录服务的标准输出(启动横幅、启动参数等)。常用查询实测:

journalctl -u algernon.service --no-pager -n 4 -o short-precise
Oct 24 04:57:00.442722 algernon-server algernon[11115]: Large file timeout:        10 sec
Oct 24 04:57:00.442722 algernon-server algernon[11115]: Listening for:                HTTP  localhost  3000  plain
Oct 24 04:57:10.235678 algernon-server systemd[1]: algernon.service: Sent signal SIGHUP to main process 11115 (algernon) on client request.
Oct 24 04:57:10.241373 algernon-server systemd[1]: algernon.service: Sent signal SIGHUP to main process 11115 (algernon) on client request.

排查「某时间点发生了什么」时按时间窗口过滤:

journalctl -u algernon.service --no-pager --since "-15min" | grep -c "algernon"
410

--since "-15min" 表示最近 15 分钟,配合 grep -c 快速估算日志量;实时跟踪把 --no-pager 换成 -f 即可。journal 与文件日志是并行的两路,journal 里的 Sent signal SIGHUP to main process 11115 (algernon) on client request 正是 4.3 节 logrotate 轮转钩子发信号的痕迹。

4.3 logrotate 日志轮转配置与强制轮转实测

访问日志持续增长,必须配轮转。新建 /etc/logrotate.d/algernon:

/var/log/algernon/*.log {
    daily
    rotate 7
    missingok
    notifempty
    compress
    delaycompress
    create 0640 algernon algernon
    postrotate
        systemctl kill -s HUP algernon.service >/dev/null 2>&1 || true
    endscript
}

逐项说明:daily 每天轮转一次;rotate 7 保留 7 份归档;missingok/notifempty 允许日志缺失或为空时跳过;compress+delaycompress 归档压缩(最近一份延迟到下轮再压,避免读到半截文件);create 0640 algernon algernon 轮转后按原属主属组重建空文件,权限 0640 与服务运行用户匹配;postrotate 钩子在轮转后向服务发 SIGHUP,通知 Algernon 重开日志文件句柄——没有这一步,服务会继续往已被改名的旧文件里写。

用 --force 立即触发一次轮转做验证。轮转前:

ls -la /var/log/algernon/
-rw-r----- 1 algernon algernon 1240 Oct 24 04:57 access.log
-rw-r--r-- 1 algernon algernon  162 Oct 24 04:25 access.log.1
-rw-r----- 1 algernon algernon 4032 Oct 24 04:57 app.log
-rw-r--r-- 1 algernon algernon  190 Oct 24 04:25 app.log.1

执行强制轮转并产生新请求:

logrotate --force /etc/logrotate.d/algernon
sleep 1
curl -s -o /dev/null http://localhost:3000/
sleep 1
ls -la /var/log/algernon/
-rw-r----- 1 algernon algernon   77 Oct 24 04:57 access.log
-rw-r----- 1 algernon algernon 1240 Oct 24 04:57 access.log.1
-rw-r--r-- 1 algernon algernon  109 Oct 24 04:25 access.log.2.gz
-rw-r----- 1 algernon algernon   96 Oct 24 04:57 app.log
-rw-r----- 1 algernon algernon 4128 Oct 24 04:57 app.log.1
-rw-r--r-- 1 algernon algernon  151 Oct 24 04:25 app.log.2.gz

轮转后的归档文件内容抽查(原 access.log 已改名 access.log.1,1240 字节原样保留):

head -1 /var/log/algernon/access.log.1
::1 -  [24/Oct/2026:04:47:37 +0000] "GET / HTTP/1.1" 200 251 "" "curl/8.5.0"

结果解读:原日志整体归档为 .1,更早的归档被压缩为 .2.gz,新的 access.log 从 77 字节起步——说明轮转后服务已经把新请求写进了新文件,postrotate 的 SIGHUP 生效,全程无需重启服务。

4.4 SIGHUP 重开日志验证

4.3 节的轮转链路依赖「SIGHUP → 重开日志文件」这一行为,这里单独把该行为验证一遍:手工模拟 logrotate 的「改名 + 重建」动作,发 SIGHUP 前后各打一个请求,确认写入位置的变化。

curl -s -o /dev/null http://localhost:3000/
mv /var/log/algernon/access.log /var/log/algernon/access.log.pretest
touch /var/log/algernon/access.log
chown algernon:algernon /var/log/algernon/access.log
chmod 0640 /var/log/algernon/access.log
systemctl kill -s HUP algernon.service
sleep 1
curl -s -o /dev/null http://localhost:3000/
sleep 1
tail -1 /var/log/algernon/access.log.pretest
tail -1 /var/log/algernon/access.log
systemctl is-active algernon.service
rm -f /var/log/algernon/access.log.pretest
::1 -  [24/Oct/2026:04:47:36 +0000] "GET / HTTP/1.1" 200 251 "" "curl/8.5.0"
::1 -  [24/Oct/2026:04:47:37 +0000] "GET / HTTP/1.1" 200 251 "" "curl/8.5.0"
active

时序解读:04:47:36 的请求发生在改名之后、SIGHUP 之前,落入了被改名的旧文件——说明此时服务仍持有旧文件句柄;04:47:37 的请求发生在 SIGHUP 之后,落入了新建的 access.log——说明服务已重新打开新文件。整个过程服务保持 active,没有重启。结论:SIGHUP 的作用是重开日志文件句柄,进程不退出、服务不中断,这正是它配合 logrotate 使用的价值。

五、备份与恢复

Algernon 的状态面很小,备份成本极低;本章给出完整的冷备流程与一次真实的「数据库损坏 → 恢复」演练。

5.1 备份对象盘点

需要备份的只有三样(大小实测见 2.4 节):

  • /srv/algernon/:站点目录(页面、样式、脚本、上传文件全在这里);
  • /var/lib/algernon/algernon.db:Bolt 数据库单文件(Lua 应用通过 Set 等接口写入的结构化数据);
  • /etc/systemd/system/algernon.service:服务单元(含全部启动参数,恢复时省去回忆成本)。

日志文件不建议纳入常规备份(可用日志归档系统采集),备份集保持最小可用。

5.2 冷备实操

停机备份能保证 Bolt 单文件的强一致性(运行中复制可能取到事务中间态)。三步备份,逐条执行:

mkdir -p /var/backups/algernon
systemctl stop algernon.service
sleep 1
cp -a /var/lib/algernon/algernon.db /var/backups/algernon/algernon.db.$(date +%Y%m%d)
tar -czf /var/backups/algernon/algernon-www.$(date +%Y%m%d).tar.gz -C /srv algernon
cp -a /etc/systemd/system/algernon.service /var/backups/algernon/algernon.service.$(date +%Y%m%d)
ls -la /var/backups/algernon/
systemctl start algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
total 44
drwxr-xr-x 2 root     root      4096 Oct 24 05:01 .
drwxr-xr-x 1 root     root      4096 Oct 24 05:01 ..
-rw-r--r-- 1 root     root      5803 Oct 24 05:01 algernon-www.20261024.tar.gz
-rw------- 1 algernon algernon 32768 Oct 24 04:57 algernon.db.20261024
-rw-r--r-- 1 root     root       493 Oct 24 04:49 algernon.service.20261024
active
200

验证结果:备份目录出现三件套——数据库快照(32768 字节,属主 algernon 原样保留,cp -a 的功劳)、站点压缩包(5803 字节)、单元文件副本;服务恢复 active、站点 200。date +%Y%m%d 让每日备份天然按日期滚动,配合 cron 即可实现每日冷备,历史备份按保留策略定期清理。

5.3 恢复演练:数据库损坏场景

备份的价值要在恢复里验证。先制造一次真实损坏:把数据库文件截断到 100 字节(Bolt 库要求 4 KB 页对齐,截断即损坏):

systemctl stop algernon.service
sleep 1
truncate -s 100 /var/lib/algernon/algernon.db
ls -la /var/lib/algernon/algernon.db
systemctl start algernon.service
sleep 3
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
tail -1 /var/log/algernon/app.log
-rw------- 1 algernon algernon 100 Oct 24 05:01 /var/lib/algernon/algernon.db
inactive
000
{"level":"error","msg":"Could not use Bolt as database backend: invalid database","time":"2026-10-24T05:01:55Z"}

损坏症状完整呈现:服务起不来(inactive)、站点不可达(000),app.log 里留下 Could not use Bolt as database backend: invalid database。注意 truncate -s 100 保留了文件属主,所以报错是 invalid database(内容损坏);若属主也错乱,报错会是 permission denied,两种症状的排查见 7.1 节。

执行恢复:停服 → 用备份覆盖数据库 → 启动 → 验证:

systemctl stop algernon.service 2>/dev/null
sleep 1
cp -a /var/backups/algernon/algernon.db.20261024 /var/lib/algernon/algernon.db
systemctl start algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
curl -s --max-time 3 http://localhost:3000/ | head -1
active
200
<!doctype html><html><head><title>index.md</title></head><link href="style.gcss" rel="stylesheet" type="text/css"><body><h1 id="markdown-页面">Markdown 页面</h1>

站点恢复如初。站点目录的恢复同理:tar -xzf /var/backups/algernon/algernon-www.20261024.tar.gz -C /srv 解包后补一条 chown -R algernon:algernon /srv/algernon 即可(实测同一演练中执行过,恢复后子页面状态码 200)。

⚠️ 注意:恢复操作要「先停服再覆盖文件」。Bolt 打开文件时持有写锁,服务运行中覆盖数据库文件轻则写入失败、重则两边数据都不完整。

六、版本升级与回滚

Algernon 是单二进制程序,升级的本质就是「换二进制 + 重启」,数据库与站点目录完全不动。本章用 1.17.11 → 1.17.12 的真实版本变更做全流程演练(含回滚),方法对未来任意版本升级同样适用。

6.1 查询最新版本与下载安装包

先查官方最新发布版本:

curl -s https://api.github.com/repos/xyproto/algernon/releases/latest | grep -m1 "tag_name"
  "tag_name": "v1.17.12",

官方为 Linux x86_64 提供静态编译包,命名规律为 algernon-<版本>-linux_x86_64_static.tar.xz,下载地址固定为 https://github.com/xyproto/algernon/releases/download/v<版本>/<包名>。下载目标版本包并算校验和(本文以 1.17.11 与 1.17.12 两个包为例,实际升级时替换为目标版本号):

mkdir -p /root/upgrade && cd /root/upgrade
curl -sSL -o algernon-1.17.12-linux_x86_64_static.tar.xz https://github.com/xyproto/algernon/releases/download/v1.17.12/algernon-1.17.12-linux_x86_64_static.tar.xz
sha256sum algernon-1.17.11-linux_x86_64_static.tar.xz algernon-1.17.12-linux_x86_64_static.tar.xz
73075b76ccbc5bf9d383e95ca9987cd66a1bf46f37dfeaa00ec0ad5714871abf  algernon-1.17.11-linux_x86_64_static.tar.xz
be2b11a20aa9bd731678076e1f6eb594cf45f8c08e65e13bd2e34c84e8077815  algernon-1.17.12-linux_x86_64_static.tar.xz

⚠️ 注意:国内网络下载 GitHub 发布件容易中途断流(实测直连报 curl: (56) Failure when receiving data from the peer),可改走代理:curl -sSL -x http://<代理地址>:<端口> -o <包名> <URL>。下载完成后先 sha256sum 核对再动手,避免半截包混入升级流程。

6.2 原地升级实操(1.17.11 → 1.17.12)

为了演示真实的版本变化,先把服务器置于 1.17.11(安装方法与 1.17.12 完全一致):

cd /root/upgrade
tar -xf algernon-1.17.11-linux_x86_64_static.tar.xz
find . -maxdepth 2 -name algernon -type f
./algernon-1.17.11-linux_x86_64_static/algernon
systemctl stop algernon.service
cp algernon-1.17.11-linux_x86_64_static/algernon /usr/local/bin/algernon
chmod +x /usr/local/bin/algernon
algernon --version
systemctl start algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
Algernon 1.17.11
active
200

当前线上版本 1.17.11,服务正常。现在执行升级,固定五步:备份旧二进制 → 停服 → 替换二进制 → 启动 → 验证:

cp /usr/local/bin/algernon /root/upgrade/algernon-1.17.11.bak
cd /root/upgrade
rm -rf algernon-1.17.12-linux_x86_64_static
tar -xf algernon-1.17.12-linux_x86_64_static.tar.xz
systemctl stop algernon.service
sleep 1
cp algernon-1.17.12-linux_x86_64_static/algernon /usr/local/bin/algernon
chmod +x /usr/local/bin/algernon
algernon --version
systemctl start algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
ls -la /usr/local/bin/algernon
journalctl -u algernon.service --no-pager --since "-30s" -o cat | grep "1.17.12" | head -1
Algernon 1.17.12
active
200
-rwxr-x--x 1 root root 45756576 Oct 24 04:56 /usr/local/bin/algernon
        1.17.12

升级完成的四重验证全部通过:--version 报告新版本 1.17.12、服务 active、站点 200、journal 启动横幅显示 1.17.12。整个中断窗口只有 stop 到 start 之间的约 1 秒。升级不触碰 /srv/algernon 与 /var/lib/algernon/algernon.db,升级后站点数据原样可用(200 即含数据页面的响应)。

6.3 回滚实操

新版本出现兼容性问题时,用 6.2 节第一步留下的备份副本原路回滚:

systemctl stop algernon.service
cp /root/upgrade/algernon-1.17.11.bak /usr/local/bin/algernon
chmod +x /usr/local/bin/algernon
algernon --version
systemctl start algernon.service
sleep 2
systemctl is-active algernon.service
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://localhost:3000/
Algernon 1.17.11
active
200

回滚完成,站点恢复正常。确认稳定后再按 6.2 节流程重新升级(本文实测即回滚到 1.17.11 后再次升回 1.17.12,两次版本切换均一次通过)。

七、故障排查手册

本章收录本文实测过程中出现的四类故障,每条按「现象 → 定位 → 解决」组织,现象与回显均为实测原文。

7.1 服务起不来:Could not use Bolt as database backend

现象:systemctl start 后 is-active 返回 inactive,站点探测 000,且 Restart=on-failure 不会反复拉起。

定位:先看应用日志最后一行(journalctl -u algernon.service -n 20 只能看到启动横幅,真正的报错在 --log 指向的文件里):

tail -1 /var/log/algernon/app.log
{"level":"error","msg":"Could not use Bolt as database backend: invalid database","time":"2026-10-24T05:01:55Z"}

冒号后的原因分两种:invalid database 为文件内容损坏(如 5.3 节的截断);open /var/lib/algernon/algernon.db: permission denied 为属主或权限错乱(常见于手工以 root 重建过该文件,属主变成 root 而 Bolt 需要以运行用户读写)。

解决:内容损坏走 5.3 节恢复流程(备份覆盖);权限错乱执行 chown algernon:algernon /var/lib/algernon/algernon.db 后重新 systemctl start。

⚠️ 注意:这类启动失败不会触发 Restart=on-failure 的循环重启——Algernon 打不开数据库时记录错误即退出,退出码为 0,systemd 视为「正常退出」而不重启。因此数据库类故障必须靠巡检或外部探针发现,不能指望 systemd 自动兜底。

7.2 systemctl stop 后状态显示 failed

现象:systemctl stop 后 is-active 返回 failed 而不是 inactive(见 3.1 节实测)。

定位:Algernon 收到 SIGTERM 后以退出码 1 结束(3.3 节实测),systemd 把非零退出码记为失败。

解决:单元文件 [Service] 段加 SuccessExitStatus=1 并 systemctl daemon-reload(完整流程与修复后验证见 3.4 节),stop 即回落到干净的 inactive。

7.3 手动 kill 主进程后服务「杀不死」

现象:对主进程执行 kill <PID>,几秒后进程换了个 PID 又活了(见 3.2 节实测,Main PID 从 10332 变为 10579)。

定位:这是 Restart=on-failure 的设计行为——Algernon 以退出码 1 结束被 systemd 判为失败退出,随即自动重启。

解决:停服务一律用 systemctl stop(配合 7.2 的 SuccessExitStatus=1 后干净落地)。需要做「进程崩溃自愈」演练时,手动 kill 才是正确工具。

7.4 数据库文件被部分覆盖,服务却仍能启动

现象:排查磁盘/写入异常时发现数据库文件头部被随机数据覆盖过一段(本文实测用 dd conv=notrunc 覆盖前 100 字节模拟),预期服务起不来,实际 is-active 仍是 active 且页面照常返回 200。

定位:Bolt 数据库在文件头部维护两份元数据页(page 0 与 page 1 互为备份),一份损坏时自动启用另一份,因此仅覆盖前 100 字节不足以让库失效;这与 5.3 节把文件截断到 100 字节(两份元数据都没了)形成对照。

解决:不要因为「还能启动」就认定数据无损——被覆盖区间的数据页内容已经丢失。发现此类痕迹后,仍应安排停机窗口从备份恢复(5.3 节流程),并排查覆盖来源(磁盘故障、误写脚本等)。

八、常见问题(FAQ)

Q1:SIGHUP 能重载配置吗? 不能把 SIGHUP 当配置重载用。实测确认的 SIGHUP 效果只有一项:重开日志文件句柄(4.4 节),进程不退出、服务不中断。修改启动参数的正确流程是改单元文件 → systemctl daemon-reload → systemctl restart(3.4 节实测);修改 serverconf.lua 后重启服务生效。

Q2:怎么确认线上服务实际生效的启动参数? 用 ps -o args -p $(systemctl show -p MainPID --value algernon.service) 直接看主进程命令行(2.2 节实测回显),以它为准而不是以「我记得改过什么」为准。

Q3:轮转日志期间会不会丢请求日志? 不会。logrotate 的 postrotate 钩子发出 SIGHUP 后,服务把新请求写入新建的日志文件(4.3/4.4 节实测:SIGHUP 前的请求留在旧归档、之后的请求进新文件),两条边界清晰,无丢失窗口。

Q4:升级版本会丢站点数据吗? 不会。升级只替换 /usr/local/bin/algernon 二进制,站点目录与 Bolt 数据库不在升级动作的触碰范围内(6.2 节实测升级后站点 200、数据页面原样渲染)。稳妥起见,升级前仍建议按 5.2 节做一次冷备——回滚有 6.3 节的二进制备份兜底,数据有冷备兜底。

Q5:服务停止后为什么还要手动 reset-failed? 若未配置 SuccessExitStatus=1(或修复前)执行过 stop,systemd 会保留 failed 状态记录,systemctl list-units --failed 里会一直挂着。执行 systemctl reset-failed algernon.service 清除即可;配置修复后新产生的停机不会再进入 failed。

九、参考链接