中文 English

Docker 容器无限重启?只因 v3.7.0 少了一个 serve —— 思源笔记 CLI 破坏性变更修复实录

发布时间: 2026-07-02
Docker Siyuan 思源笔记 CLI 破坏性变更 docker-compose 故障排查 AI Agent

先说结论

把思源笔记从 v3.6.x 升级到 v3.7.0 后,Docker 容器陷入无限重启循环。docker logs 显示 Error: unknown flag: --accessAuthCode。根因是 v3.7.0 引入了 CLI 子命令架构,原来的顶级 flag 现在需要加一个 serve 子命令。修复只需在 docker-compose.yml 的 command 字段最前面加上 'serve',多 7 个字符,从无限重启到正常运行。

本文给出完整排查过程、三种操作系统下的一键修复脚本(Windows 11 / Ubuntu 26.04 / macOS 26),以及人工执行和 AI Agent 自动配置两种方法。所有脚本只调用 Docker CLI 和 SSH,不依赖第三方服务。

AI 生成封面:Docker 容器无限重启的排查与修复。

图 1:AI 生成封面。容器重启循环的修复,往往只需要一行改动。

一、问题背景:思源笔记与 Docker 部署

思源笔记(SiYuan) 是一款开源的个人知识管理系统,支持 Markdown 编辑、双向链接、数据同步和 Docker 部署。很多用户选择用 Docker 在服务器上运行思源,通过浏览器随时随地访问自己的笔记。

Docker 部署的典型配置如下:

version: "3.9"
services:
  main:
    image: b3log/siyuan:v3.7.0
    command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
    ports:
      - 6806:6806
    volumes:
      - /docker/siyuan_data:/siyuan/workspace
    restart: always
    environment:
      - PUID=1001
      - PGID=1002
      - SIYUAN_LANG=zh-CN

这个配置在 v3.6.x 及之前的版本中工作良好。然而,2026 年 6 月 30 日发布的 v3.7.0 是一个大版本更新,带来了全新的用户界面、内核插件系统和命令行接口(CLI)重构。正是这个 CLI 重构,导致了许多 Docker 用户的容器在升级后无法启动。

v3.7.0 带来了什么?

根据官方发布说明,v3.7.0 是一次全面的版本革新:

其中第四项 —— CLI 命令行接口,就是本文要讨论的"罪魁祸首"。它带来了更强大的自动化能力,但也引入了一个破坏性变更。

思源 v3.7.0 架构概览。

图 2:思源 Docker 部署架构。Portainer 管理 Stack,容器内 entrypoint.sh 启动 kernel,kernel 再通过 serve 子命令启动 HTTP 服务。

二、问题表现:容器无限重启循环

某天,我在 Portainer 中把思源笔记的镜像从 v3.6.1 升级到了 v3.7.0,点击"重新部署"后,发现容器状态不对劲:

$ docker ps --filter name=siyuan
NAMES          STATUS
siyuan-qiniu   Restarting (1) 8 seconds ago

容器不断经历 启动 → 退出(exit code 1)→ Docker 自动重启 → 又退出 的死循环。从外面看,端口映射存在但无法访问,浏览器打开对应地址一直转圈。

docker ps 显示容器无限重启。

图 3:真实终端截图 — docker ps 显示 siyuan 容器处于 Restarting 状态。

这就好比你家小区新换了一个门禁系统(升级镜像),结果门禁主机不断重启 —— 刚启动、闪一下屏、然后黑掉、再重启。你站在门口进不去,干着急。

用生活场景理解"容器重启循环"

打比方 —— 汽车启动故障

你换了新发动机(升级镜像),但点火方式不对。每次转动钥匙,发动机"咔"响一声就熄火了。车载电脑自动尝试重新点火(restart: always),于是你听到"咔-咔-咔"不断重复。要修好它,不能只拔电瓶,得看故障码 —— 对容器来说,故障码就是 docker logs

三、问题分析:从 docker logs 找到第一线索

容器出问题,第一反应不是重启、不是回滚、不是重装,而是 看日志

$ docker logs --tail 40 siyuan-qiniu
Error: unknown flag: --accessAuthCode
Usage:
  kernel [command]

Available Commands:
  asset       Manage assets
  attr        Manage block attributes
  block       Block operations
  bookmark    Manage bookmarks
  completion  Generate the autocompletion script
  serve       Start kernel HTTP server
  export      Export documents
  ...
Flags:
      --dry-run            dry run mode
  -h, --help               help for kernel
  -w, --workspace string   workspace path

真实错误日志。

图 4:真实截图 — docker logs 显示的 kernel CLI 错误信息。

思源笔记 v3.7.0 锁屏登录页面。

图 5:真实截图 — 修复后思源笔记 v3.7.0 的锁屏登录页面。输入 accessAuthCode 即可访问笔记。

日志非常清楚地告诉我们:--accessAuthCode 是一个未知的 flag。而且,kernel 列出了它认识的所有子命令serveassetblock 等),其中 serve 的描述正是 “Start kernel HTTP server”。

这个输出本身就是一个巨大的提示:kernel 期望的第一个参数是一个子命令,而不是 --workspace--accessAuthCode 这样的 flag。

排查链路

完整的排查链路如下:

  1. docker ps -a → 发现容器 Restarting
  2. docker logs → 看到 unknown flag: --accessAuthCode
  3. docker inspect → 确认 compose 配置路径和 command 字段
  4. 查阅官方 README → 发现 v3.7.0 起必须显式传入 serve 子命令
  5. 定位根因 → compose 文件中 command 缺少 serve

故障排查流程。

图 6:Docker 容器故障排查标准流程。docker logs 是第一线索,不要只看 docker ps。

四、问题根因:v3.7.0 CLI 子命令架构变更

打比方 —— 餐厅点餐系统升级

旧系统(v3.6.x 及之前):你走进餐厅,直接对服务员说"宫保鸡丁、米饭、可乐"。服务员知道你在点餐,就帮你下单了。

新系统(v3.7.0):餐厅装了新的 POS 系统。现在你必须先说"点餐",然后再说"宫保鸡丁、米饭、可乐"。如果你直接说"宫保鸡丁",系统会疑惑:“宫保鸡丁是什么操作?我只认识’点餐’、‘结账’、‘打包’这些操作。”

这里的"点餐"就是 serve 子命令,“宫保鸡丁"就是 --workspace--accessAuthCode 参数。

技术层面

在 v3.7.0 之前,思源的 kernel 入口直接接受顶级 flag:

kernel --workspace=/siyuan/workspace/ --accessAuthCode=xxx

v3.7.0 引入了完整的 CLI 子命令架构(这是 Issue #17674 的实现),kernel 现在需要一个子命令:

kernel serve --workspace=/siyuan/workspace/ --accessAuthCode=xxx

Docker 镜像的 ENTRYPOINT/opt/siyuan/entrypoint.sh,这个脚本负责设置 PUID/PGID 然后调用 kernel。但它本身不会自动加上 serve 子命令 —— 这个责任落在了 CMD 或 compose 的 command 字段上。

在 docker-compose.yml 中,原来的配置是:

command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']

这些参数直接传给了 entrypoint.sh,然后传给 kernel。kernel 把 --workspace 当成子命令名来解析,发现不认识,于是报错退出。Docker 的 restart: always 策略又把它拉起来,如此往复。

CLI 破坏性变更对比。

图 7:v3.6.x vs v3.7.0 CLI 参数结构对比。新增的 serve 子命令是唯一的区别。

注意:官方 README 第 216 行已经明确写了这个变更: “自 v3.7.0 起,必须显式传入 serve 子命令(例如 docker run b3log/siyuan serve --workspace=...)。”

但在升级已有部署时,很多人(包括我)不会重新读一遍 README,导致踩坑。

五、如何解决问题

5.1 修复原理

修复极其简单:在 compose 文件的 command 数组最前面加一个 'serve'

- command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
+ command: ['serve', '--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']

仅 7 个字符的改动,从无限重启到正常运行。

compose 文件修改 diff。

图 8:docker-compose.yml 修改内容 —— 只在 command 数组最前面加了 ‘serve’。

5.2 一键修复脚本

以下脚本适用于三种操作系统。它们通过 SSH 连接到 Docker 宿主机,自动检测思源容器、备份原配置、修补 command 字段、重建容器。

安全提醒:脚本会自动备份原 docker-compose.yml(带时间戳),不会覆盖数据卷,可安全回滚。

Windows 11(PowerShell)

# siyuan-fix.ps1 — 思源 v3.7.0 容器修复脚本
# 用法:.\siyuan-fix.ps1 -ServerHost <docker-host> -ServerUser root -StackName siyuan-note-qiniu
param(
    [Parameter(Mandatory=$true)][string]$ServerHost,
    [string]$ServerUser = "root",
    [string]$StackName = "siyuan-note-qiniu"
)

$ErrorActionPreference = "Stop"

Write-Host "=== 思源 v3.7.0 Docker 容器修复 ===" -ForegroundColor Cyan

# 1. 检测容器状态
Write-Host "[1/5] 检查容器状态..." -ForegroundColor Yellow
$status = ssh "${ServerUser}@${ServerHost}" "docker ps --filter name=$StackName --format '{{.Status}}'"
Write-Host "  容器状态: $status"

# 2. 查看日志确认问题
Write-Host "[2/5] 查看容器日志..." -ForegroundColor Yellow
$logs = ssh "${ServerUser}@${ServerHost}" "docker logs --tail 20 $($StackName)_main_1 2>&1 || docker logs --tail 20 siyuan-qiniu 2>&1 || docker logs --tail 20 \$(docker ps -a --filter name=siyuan --format '{{.Names}}') 2>&1"
if ($logs -match "unknown flag.*accessAuthCode") {
    Write-Host "  ✓ 确认为 v3.7.0 CLI 变更导致的问题" -ForegroundColor Green
} else {
    Write-Host "  ⚠ 错误信息与预期不符,请手动检查" -ForegroundColor Red
    Write-Host $logs
    exit 1
}

# 3. 定位 compose 文件
Write-Host "[3/5] 定位 docker-compose.yml..." -ForegroundColor Yellow
$composePath = ssh "${ServerUser}@${ServerHost}" "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null"
if (-not $composePath) {
    # 备用方案:搜索 Portainer 目录
    $composePath = ssh "${ServerUser}@${ServerHost}" "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1"
}
Write-Host "  Compose 文件: $composePath"

# 4. 备份并修复
Write-Host "[4/5] 备份并修复 compose 文件..." -ForegroundColor Yellow
$fixCmd = @"
cp '$composePath' '$composePath.bak.\$(date +%Y%m%d-%H%M%S)' && \
sed -i \"s|command: \\[\"--workspace|command: \\[\"serve\", \"--workspace|g\" '$composePath' && \
echo 'FIXED' && cat '$composePath' | grep -A 2 'command:'
"@
$result = ssh "${ServerUser}@${ServerHost}" $fixCmd
Write-Host $result
if ($result -match "FIXED") {
    Write-Host "  ✓ compose 文件已修复" -ForegroundColor Green
} else {
    Write-Host "  ⚠ 修复可能未生效,请手动检查" -ForegroundColor Red
    exit 1
}

# 5. 重建容器
Write-Host "[5/5] 重建容器..." -ForegroundColor Yellow
$composeDir = Split-Path $composePath -Parent
$projectName = "siyuan-note-qiniu"
$rebuildResult = ssh "${ServerUser}@${ServerHost}" "cd '$composeDir' && docker compose -p $projectName up -d --force-recreate 2>&1"
Write-Host $rebuildResult

# 验证
Start-Sleep -Seconds 5
$finalStatus = ssh "${ServerUser}@${ServerHost}" "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
Write-Host "`n=== 最终状态 ===" -ForegroundColor Cyan
Write-Host $finalStatus

$healthCheck = ssh "${ServerUser}@${ServerHost}" "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1 || echo 'port may differ'"
Write-Host "HTTP 状态码: $healthCheck (401 = 正常,锁屏密码生效)"
Write-Host "`n✅ 修复完成!" -ForegroundColor Green

Ubuntu 26.04 / Debian

#!/usr/bin/env bash
# siyuan-fix.sh — 思源 v3.7.0 容器修复脚本
# 用法: bash siyuan-fix.sh <docker-host> [ssh-user] [stack-name]
set -euo pipefail

SERVER_HOST="${1:?请指定 Docker 宿主机地址}"
SERVER_USER="${2:-root}"
STACK_NAME="${3:-siyuan-note-qiniu}"

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ssh_cmd() { ssh -o StrictHostKeyChecking=accept-new "${SERVER_USER}@${SERVER_HOST}" "$@"; }

echo -e "${CYAN}=== 思源 v3.7.0 Docker 容器修复 ===${NC}"

# 1. 检测容器状态
echo -e "${YELLOW}[1/5] 检查容器状态...${NC}"
ssh_cmd "docker ps -a --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"

# 2. 确认日志中的错误
echo -e "${YELLOW}[2/5] 确认错误日志...${NC}"
CONTAINER_NAME=$(ssh_cmd "docker ps -a --filter name=siyuan --format '{{.Names}}' | head -1")
LOGS=$(ssh_cmd "docker logs --tail 20 ${CONTAINER_NAME} 2>&1" || true)
if echo "$LOGS" | grep -q "unknown flag.*accessAuthCode"; then
    echo -e "${GREEN}  ✓ 确认为 v3.7.0 CLI 变更导致的问题${NC}"
else
    echo -e "${RED}  ⚠ 错误信息与预期不符,请手动检查${NC}"
    echo "$LOGS"
    exit 1
fi

# 3. 定位 compose 文件
echo -e "${YELLOW}[3/5] 定位 docker-compose.yml...${NC}"
COMPOSE_PATH=$(ssh_cmd "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null" || true)
if [[ -z "$COMPOSE_PATH" ]]; then
    COMPOSE_PATH=$(ssh_cmd "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1" || true)
fi
if [[ -z "$COMPOSE_PATH" ]]; then
    echo -e "${RED}无法定位 docker-compose.yml,请手动指定${NC}"
    exit 1
fi
echo "  Compose 文件: $COMPOSE_PATH"

# 4. 备份并修复
echo -e "${YELLOW}[4/5] 备份并修复 compose 文件...${NC}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ssh_cmd "cp '$COMPOSE_PATH' '$COMPOSE_PATH.bak.$TIMESTAMP'" && echo "  ✓ 已备份到 .bak.$TIMESTAMP"
ssh_cmd "sed -i \"s|command: \\\\[\\\"--workspace|command: \\\\[\\\"serve\\\", \\\"--workspace|g\" '$COMPOSE_PATH'"
echo -e "${GREEN}  ✓ compose 文件已修复${NC}"

# 5. 重建容器
echo -e "${YELLOW}[5/5] 重建容器...${NC}"
COMPOSE_DIR=$(dirname "$COMPOSE_PATH")
ssh_cmd "cd '$COMPOSE_DIR' && docker compose -p $STACK_NAME up -d --force-recreate 2>&1"

# 验证
sleep 5
echo -e "\n${CYAN}=== 最终状态 ===${NC}"
ssh_cmd "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
HTTP_CODE=$(ssh_cmd "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1" || echo "N/A")
echo -e "HTTP 状态码: ${HTTP_CODE} (401 = 正常,锁屏密码生效)"
echo -e "\n${GREEN}✅ 修复完成!${NC}"

macOS 26

#!/usr/bin/env bash
# siyuan-fix-mac.sh — 思源 v3.7.0 容器修复脚本 (macOS)
# 用法: bash siyuan-fix-mac.sh <docker-host> [ssh-user] [stack-name]
# 前置条件: brew install coreutils (提供 gsed)

set -euo pipefail

SERVER_HOST="${1:?请指定 Docker 宿主机地址}"
SERVER_USER="${2:-root}"
STACK_NAME="${3:-siyuan-note-qiniu}"

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ssh_cmd() { ssh -o StrictHostKeyChecking=accept-new "${SERVER_USER}@${SERVER_HOST}" "$@"; }

echo -e "${CYAN}=== 思源 v3.7.0 Docker 容器修复 (macOS) ===${NC}"

# 检查必要工具
if ! command -v ssh &>/dev/null; then echo -e "${RED}需要 SSH 客户端${NC}"; exit 1; fi

# 1. 检测
echo -e "${YELLOW}[1/5] 检查容器状态...${NC}"
ssh_cmd "docker ps -a --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"

# 2. 确认错误
echo -e "${YELLOW}[2/5] 确认错误日志...${NC}"
CONTAINER_NAME=$(ssh_cmd "docker ps -a --filter name=siyuan --format '{{.Names}}' | head -1")
LOGS=$(ssh_cmd "docker logs --tail 20 ${CONTAINER_NAME} 2>&1" || true)
if echo "$LOGS" | grep -q "unknown flag.*accessAuthCode"; then
    echo -e "${GREEN}  ✓ 确认为 v3.7.0 CLI 变更问题${NC}"
else
    echo -e "${RED}  ⚠ 错误信息不符${NC}"; echo "$LOGS"; exit 1
fi

# 3. 定位 compose
echo -e "${YELLOW}[3/5] 定位 compose 文件...${NC}"
COMPOSE_PATH=$(ssh_cmd "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null" || true)
[[ -z "$COMPOSE_PATH" ]] && COMPOSE_PATH=$(ssh_cmd "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1" || true)
[[ -z "$COMPOSE_PATH" ]] && { echo -e "${RED}无法定位 compose 文件${NC}"; exit 1; }
echo "  Compose: $COMPOSE_PATH"

# 4. 备份并修复 (macOS 用 gsed 或 python)
echo -e "${YELLOW}[4/5] 备份并修复...${NC}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ssh_cmd "cp '$COMPOSE_PATH' '$COMPOSE_PATH.bak.$TIMESTAMP'" && echo "  ✓ 已备份"
# 使用 python 做替换,避免 macOS sed 兼容问题
ssh_cmd "python3 -c \"
import re
p = '$COMPOSE_PATH'
c = open(p).read()
c = c.replace('command: [\\\"--workspace', 'command: [\\\"serve\\\", \\\"--workspace')
open(p, 'w').write(c)
print('FIXED')
\""
echo -e "${GREEN}  ✓ compose 文件已修复${NC}"

# 5. 重建
echo -e "${YELLOW}[5/5] 重建容器...${NC}"
COMPOSE_DIR=$(dirname "$COMPOSE_PATH")
ssh_cmd "cd '$COMPOSE_DIR' && docker compose -p $STACK_NAME up -d --force-recreate 2>&1"

sleep 5
echo -e "\n${CYAN}=== 最终状态 ===${NC}"
ssh_cmd "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
HTTP_CODE=$(ssh_cmd "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1" || echo "N/A")
echo -e "HTTP: ${HTTP_CODE} (401 = 正常)"
echo -e "\n${GREEN}✅ 修复完成!${NC}"

5.3 手动修复步骤(无需脚本)

如果你不想用脚本,手动修复也很简单:

# 1. SSH 到 Docker 宿主机
ssh root@<your-docker-host>

# 2. 找到 compose 文件(通常在 Portainer 数据目录下)
grep -rl "siyuan" /docker/portainer_data/compose/*/docker-compose.yml

# 3. 备份原文件
cp /path/to/docker-compose.yml /path/to/docker-compose.yml.bak.$(date +%Y%m%d-%H%M%S)

# 4. 修改 command 行(在数组最前面加上 'serve')
# 原: command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
# 改: command: ['serve', '--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']

# 可以用 sed 一键替换:
sed -i "s|command: \\[\"--workspace|command: \\[\"serve\", \"--workspace|g" /path/to/docker-compose.yml

# 5. 验证修改
grep "command:" /path/to/docker-compose.yml

# 6. 重建容器
cd $(dirname /path/to/docker-compose.yml)
docker compose -p siyuan-note-qiniu up -d --force-recreate

# 7. 验证
docker ps --filter name=siyuan
curl -o /dev/null -w "%{http_code}\n" http://127.0.0.1:6001/
# 返回 401 表示锁屏密码生效,服务正常!

docker compose up 成功输出。

图 9:真实终端截图 — 重建容器后 Status 变为 Up,curl 返回 401(锁屏密码生效)。

六、人工执行 vs AI Agent 自动配置

6.1 人工执行

人工修复这个问题的预估时间是 15-30 分钟

但实际经验是,大部分时间花在"理解这个错误是什么意思"上。unknown flag 这个提示对于不熟悉 CLI 子命令概念的人来说不够直观。

6.2 AI Agent 自动配置

如果让 AI Agent 来修复,流程大致如下:

给 Agent 的提示词:
---
我的思源笔记 Docker 容器一直在重启。请帮我排查和修复。

约束条件:
1. 不要执行大版本 OS 升级
2. 不要下载不明安装器
3. 不要输出真实内网 IP、完整计算机名、私有域名或密钥
4. 修改配置文件前先备份
5. 修复后验证容器状态和 HTTP 可达性
---

Agent 的执行流程:

  1. docker ps -a → 发现容器 Restarting
  2. docker logs → 看到 unknown flag: --accessAuthCode
  3. docker inspect → 确认 compose 配置和镜像版本
  4. 查阅官方 README → 发现 v3.7.0 的 serve 子命令要求
  5. 备份 → 修改 compose → 重建容器 → 验证

实测效果:从开始排查到修复完成,Agent 用时约 10 分钟。如果人工来做,考虑到搜索、阅读文档和理解 CLI 架构变更,通常需要 20-30 分钟。

AI Agent 修复流程。

图 10:AI Agent 远程修复 Docker 容器故障的完整流程。

6.3 给 Agent 的护栏

让 Agent 操作生产环境,以下护栏必不可少:

护栏 说明
备份优先 修改任何配置文件前必须先备份
不执行 OS 升级 Agent 只能操作容器层,不能动宿主系统
不泄露隐私 日志、配置、路径中不能包含真实 IP/域名/密钥
先诊断后动手 必须通过 logs/inspect 确认根因后再修改
验证闭环 修复后必须检查容器状态和 HTTP 可达性
可回滚 保留备份文件和回滚命令

七、修复后的验证

修复完成后,思源笔记的服务会正常运行。访问 http://<your-host>:<port>/(本例中端口为 6001),会看到思源的锁屏页面:

思源笔记锁屏登录页面。

图 11:真实截图 — 修复后思源笔记 v3.7.0 锁屏登录页面(大尺寸)。输入 accessAuthCode 即可进入笔记。

输入在 --accessAuthCode 中设置的密码即可进入笔记。如果看到 HTTP 401 响应(用 curl 测试),说明锁屏密码生效,服务完全正常。

$ curl -o /dev/null -w "%{http_code}\n" http://<host>:<port>/
401

HTTP 401 在这里不是错误 —— 它表示思源的访问控制正在工作,只有输入正确密码才能进入。

八、Q&A

Q1:为什么思源要在 v3.7.0 引入这个破坏性变更?

因为 v3.7.0 新增了完整的命令行接口(CLI),kernel 现在不仅是一个 HTTP 服务器,还是一个多功能的命令行工具。除了 serve(启动 HTTP 服务),还有 export(导出文档)、import(导入文件)、sync(同步数据)等子命令。把功能拆成子命令是 CLI 设计的标准做法,就像 git commitgit push 一样。

Q2:我的思源还是 v3.6.x,该不该升级?

v3.7.0 带来了全新 UI、内核插件系统和 AI 知识库,是非常值得升级的版本。升级前只需确认你的 docker-compose.yml 中 command 字段包含 serve 子命令即可。

Q3:如果我不止一个思源容器要修怎么办?

上面提供的一键脚本可以通过参数指定不同的 Stack 名称。如果你有多个思源实例,遍历每个 compose 目录执行修复即可。

Q4:Portainer 里怎么改?

在 Portainer 中找到对应的 Stack,点击 Editor,在 command 数组最前面加上 'serve',,然后点击 “Update the stack” 即可。Portainer 会自动重建容器。

Q5:修复后数据会不会丢?

不会。数据存储在 /siyuan/workspace(或你挂载的对应目录)中,修复只改了 compose 文件的 command 字段,不涉及数据卷。而且修复前会自动备份 compose 文件。

Q6:有没有比改 compose 更简单的方法?

如果你用 docker run 而不是 compose,只需在命令中加上 serve

docker run -d \
  -v /siyuan/workspace:/siyuan/workspace \
  -p 6806:6806 \
  b3log/siyuan \
  serve \
  --workspace=/siyuan/workspace/ \
  --accessAuthCode=xxx

Q7:其他 Docker 镜像也会有这种问题吗?

任何从"无子命令 CLI"升级到"有子命令 CLI"的软件都可能出现类似问题。升级前看一下 CHANGELOG 中是否有 “CLI”、“command”、“breaking change” 等关键词是个好习惯。

九、总结

这次排查的教训可以浓缩为三句话:

  1. 容器出问题,先看 docker logsdocker ps 只能告诉你"容器在重启”,docker logs 才能告诉你"为什么重启"。
  2. 升级前看一眼 CHANGELOG。v3.7.0 的 README 已经明确写了需要 serve 子命令,但升级已有部署时很容易漏掉。
  3. 修复前先备份cp file.yml file.yml.bak.$(date) 花不了 2 秒,但能在出问题时救命。

从技术角度看,这次问题的本质是 CLI 接口的破坏性变更。思源团队在 v3.7.0 做了正确的架构决策(引入子命令架构),也写了文档,但破坏性变更对已有部署的影响总是最大的。好在这个修复极其简单 —— 多写一个 serve,7 个字符。

从运维角度看,这个案例完美展示了 “日志优先"排查方法论的价值:不要猜、不要试、不要急着回滚 —— 先看日志,日志会告诉你一切。

如果你也遇到了类似问题,希望这篇文章能帮你少走 15 分钟的弯路。

参考资料