基于Tailscale构建自托管本地Markdown查看器,安全访问OpenClaw智能体日志
1. 项目概述与核心价值
如果你和我一样,深度使用 OpenClaw 这类本地优先的 AI 智能体框架,那你一定对那个不断膨胀的 .openclaw 目录又爱又恨。爱的是,它忠实地记录了所有智能体的思考过程、记忆日志、项目蓝图和配置文档,是数字大脑的“黑匣子”;恨的是,当你想在沙发上用手机快速回顾某个项目的决策过程,或者查看昨天智能体生成的代码草稿时,面对这个充满 .md 文件的本地目录,你只能望洋兴叹。把文件传到云端?这违背了本地优先、隐私至上的初衷。用 SSH 连回电脑在终端里 cat ?体验太差,而且对移动设备极不友好。 bowen0110/openclaw-viewer 就是为了解决这个“最后一公里”的痛点而生的:一个极简、自托管、专为手机浏览优化的本地文件查看器。
它的核心设计哲学非常明确: 你的文件,只留在你的设备上 。它不进行任何同步,不依赖任何云存储,不引入第三方服务。它仅仅是一个运行在你主机上的、内存占用极小的 Node.js 服务器,然后通过 Tailscale 构建的安全虚拟局域网,让你可以从任何联网的设备(尤其是手机)上,像浏览一个优雅的文档网站一样,访问你本地的 .openclaw 目录。无论是智能体的每日记忆日志( memory/ )、战争室产出的架构图说明( war_rooms/ ),还是各个智能体的灵魂文件 SOUL.md ,你都可以在一个响应式、暗色主题的界面中轻松查阅。对于任何重视工作流隐私、追求效率,并希望将 AI 智能体产出深度融入日常的开发者或研究者来说,这个工具都是一个不可或缺的“连接器”。
2. 核心设计思路与架构解析
2.1 为什么是“本地优先”与“自托管”?
在 AI 智能体工作流中,数据隐私和所有权是首要考量。OpenClaw 等框架将一切数据保存在本地,就是为了避免敏感的项目思路、未成形的创意或内部决策日志泄露到第三方服务器。因此,一个配套的查看工具必须继承这一原则。openclaw-viewer 选择自托管,意味着数据流完全可控:你的 Markdown 文件从本地磁盘被读取,在本地内存中被渲染成 HTML,再通过本地网络接口提供服务。整个生命周期中,文件字节从未离开你的物理设备或你完全掌控的 Tailscale 虚拟网络。这与将目录推送到 GitHub Gist、Notion 或任何云笔记形成了鲜明对比,后者总会引入数据托管和隐私政策的顾虑。
2.2 网络访问安全基石:Tailscale 的妙用
让本地服务能被外网安全访问,传统方案涉及端口转发、DDNS 和 SSL 证书,复杂且有安全风险。openclaw-viewer 巧妙地利用了 Tailscale 。Tailscale 基于 WireGuard,在你的所有设备间建立一个加密的 mesh VPN(他们称之为 tailnet)。一旦你的手机和电脑都登录了同一个 Tailscale 账户,它们就仿佛处于同一个安全的内部局域网,即使两者都在不同的公共 Wi-Fi 或蜂窝网络下。
注意 :使用 Tailscale 是该项目安全模型的核心。它替代了复杂的防火墙规则和暴露公网 IP 的风险。请确保你理解并正确配置了 Tailscale,这是服务能从手机访问的前提。
这样一来,查看器服务只需绑定到 localhost:3500 ,但在 Tailscale 网络内,你可以通过电脑的 Tailscale 内网 IP 或主机名(如 your-computer.tailnet-name.ts.net:3500 )直接访问。所有通信都是端到端加密的,无需配置 HTTPS(虽然 Tailscale 流量本身是加密的,但服务本身是 HTTP,在 tailnet 内是安全的)。
2.3 技术栈选型:轻量、专注与安全
项目技术栈的选择体现了“做一件事并做好”的理念:
- Express : 轻量级 Node.js Web 框架,足以处理文件浏览、读取和搜索的 API 需求。
- Marked : 高性能的 Markdown 解析器,将
.md文件快速转换为 HTML。 - highlight.js : 为 Markdown 中的代码块提供客户端语法高亮,提升代码阅读体验。
- DOMPurify : 关键的安全组件。在将 Markdown 渲染后的 HTML 发送到客户端前,进行严格的净化(Sanitization),防止潜在的 XSS 攻击。即使你无意中打开了一个包含恶意脚本的 Markdown 文件,它也能被有效过滤。
整个应用没有前端构建步骤(No Build Step),这意味着部署就是简单的 node server.js 。服务器内存占用约 33MB,几乎可以忽略不计,非常适合常驻后台运行。
3. 详细部署与配置指南
3.1 基础环境准备与快速启动
部署前,请确保完成以下两步:
- 安装 Node.js 18+ : 这是运行服务器的唯一硬性依赖。建议通过 nvm 管理 Node.js 版本,方便切换。
- 安装并配置 Tailscale :
- 在作为服务器的主机(你的开发机或常开电脑)上,安装 Tailscale 并登录。
- 在你的手机(iOS/Android)上,同样安装 Tailscale 应用,并使用同一账户登录。
- 确保两台设备在 Tailscale 管理后台(https://login.tailscale.com/admin/machines)都显示为 “Online” 状态。
完成上述准备后,部署查看器本身非常简单:
# 克隆仓库
git clone https://github.com/bowen0110/openclaw-viewer.git
cd openclaw-viewer
# 安装依赖
npm install
# 启动服务器(默认使用当前目录的上一级作为根目录)
npm start
启动后,控制台会显示 Server running on http://localhost:3500 。此时,在你的电脑浏览器访问这个地址,就能看到查看器的界面。
3.2 关键配置项详解
项目通过环境变量进行配置,灵活且易于集成到各种部署环境中。
| 环境变量 | 默认值 | 说明与实操建议 |
|---|---|---|
PORT |
3500 |
服务器监听的端口。如果 3500 被占用,可以修改为其他端口,如 8080 。 |
WORKSPACE_ROOT |
server.js 所在目录的父目录 |
这是最重要的配置 。它指定了服务器可以浏览的文件系统的根路径。 必须将其设置为你的 .openclaw 目录的父目录,或者直接指向 .openclaw 目录本身。 |
如何正确设置 WORKSPACE_ROOT :
假设你的 .openclaw 目录路径是 /Users/yourname/.openclaw 。
- 方法一(推荐) : 在启动命令前设置环境变量。
WORKSPACE_ROOT=/Users/yourname/.openclaw npm start - 方法二 : 创建
.env文件(项目根目录下)。
然后修改PORT=3500 WORKSPACE_ROOT=/Users/yourname/.openclawpackage.json中的start脚本,引入dotenv包来读取.env文件,或者使用npm命令npm run start:env并配置对应脚本。
实操心得 :我强烈建议将
WORKSPACE_ROOT明确设置为.openclaw的绝对路径。使用默认值(父目录)可能会意外暴露你不想共享的其他目录。安全第一。
3.3 从手机访问服务
这是体现项目价值的时刻。在电脑上启动服务后,你需要找到电脑在 Tailscale 网络中的地址。
-
在电脑终端执行:
tailscale status你会看到类似输出:
100.xx.xx.xx your-computer-name your-email@example.com linux -或者更友好的主机名:
your-computer-name.tailnet-name.ts.net。 -
在手机的 Tailscale App 中,确保状态为“已连接”。
-
打开手机浏览器,输入地址:
http://your-computer-name.tailnet-name.ts.net:3500。 -
你应该能看到和电脑浏览器里一样的文件浏览界面。大功告成!
常见问题排查 :如果手机无法访问,请依次检查:① 电脑防火墙是否允许了 3500 端口的入站连接(在 Tailscale 网络内,通常不需要额外设置);② 启动服务时是否指定了正确的
PORT;③ 手机 Tailscale 是否与电脑在同一 tailnet 中且在线。
4. 生产级持久化运行方案
我们不可能每次想用的时候都手动去启动服务。下面详细介绍如何在不同操作系统下将其设置为后台服务,实现开机自启、故障自动重启。
4.1 Linux / WSL2 方案(使用 systemd,推荐)
这是最健壮、管理最方便的方案。systemd 提供了完善的进程监控、日志收集和资源限制功能。
步骤一:创建 systemd 用户服务文件
将以下内容中的 /path/to/ 替换为你的实际路径。
/path/to/openclaw-viewer: 项目克隆的目录。/path/to/your/.openclaw: 你的.openclaw目录绝对路径。
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/openclaw-viewer.service << EOF
[Unit]
Description=OpenClaw Viewer Service
After=network.target tailscale.service
Wants=tailscale.service
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=$USER
WorkingDirectory=/path/to/openclaw-viewer
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
Environment=PORT=3500
Environment=WORKSPACE_ROOT=/path/to/your/.openclaw
ExecStart=/usr/bin/node /path/to/openclaw-viewer/server.js
Restart=on-failure
RestartSec=10
# 资源限制
MemoryMax=256M
CPUQuota=100%
TimeoutStartSec=15
WatchdogSec=120
# 日志重定向
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
EOF
关键配置解析 :
After=tailscale.service: 确保服务在 Tailscale 网络就绪后才启动,避免网络不可用。StartLimitIntervalSec和StartLimitBurst: 在 300 秒内最多重启 5 次,超过则永久停止,防止崩溃循环。MemoryMax=256M: 限制最大内存,防止内存泄漏导致系统问题。WatchdogSec=120: systemd 会监控服务,如果 120 秒内没有收到“心跳”(需要服务支持 watchdog,这里更多是进程存活监控),会认为服务僵死并重启。
步骤二:启用并启动服务
# 重新加载 systemd 配置
systemctl --user daemon-reload
# 启用开机自启
systemctl --user enable openclaw-viewer.service
# 立即启动服务
systemctl --user start openclaw-viewer.service
步骤三:允许用户服务在用户注销后继续运行
默认情况下,用户服务会在用户退出登录时停止。我们需要启用“linger”功能。
loginctl enable-linger $USER
步骤四:管理服务
# 查看服务状态
systemctl --user status openclaw-viewer.service
# 查看实时日志
journalctl --user -u openclaw-viewer.service -f
# 重启服务
systemctl --user restart openclaw-viewer.service
# 停止服务
systemctl --user stop openclaw-viewer.service
4.2 macOS 方案(使用 launchd)
macOS 使用 launchd 作为初始化系统。我们需要创建一个 plist 文件。
-
创建服务描述文件:
cat > ~/Library/LaunchAgents/com.user.openclawviewer.plist << EOF <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.user.openclawviewer</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/node</string> <string>/path/to/openclaw-viewer/server.js</string> </array> <key>EnvironmentVariables</key> <dict> <key>PORT</key> <string>3500</string> <key>WORKSPACE_ROOT</key> <string>/path/to/your/.openclaw</string> </dict> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>StandardOutPath</key> <string>/tmp/openclaw-viewer.log</string> <key>StandardErrorPath</key> <string>/tmp/openclaw-viewer.log</string> <key>WorkingDirectory</key> <string>/path/to/openclaw-viewer</string> </dict> </plist> EOF注意 :请确保
/usr/local/bin/node是你的 Node.js 正确路径,可以使用which node命令查看。 -
加载并启动服务:
launchctl load ~/Library/LaunchAgents/com.user.openclawviewer.plist launchctl start com.user.openclawviewer -
查看日志:
tail -f /tmp/openclaw-viewer.log
4.3 Windows 方案(使用 PM2,跨平台推荐)
对于 Windows,我推荐使用 PM2 ,这是一个功能强大的 Node.js 进程管理器,比 Windows 任务计划程序更易于管理。
-
全局安装 PM2:
npm install -g pm2 -
在项目目录下启动服务并设置环境变量:
# 进入项目目录 cd C:\path\to\openclaw-viewer # 用 PM2 启动进程,并命名 pm2 start server.js --name "openclaw-viewer" --env PORT=3500 --env WORKSPACE_ROOT="C:\Users\YourName\.openclaw" # 保存当前进程列表 pm2 save # 生成开机自启动脚本(会给出需要以管理员权限运行的命令) pm2 startup执行
pm2 startup后,它会输出一条命令(如pm2 startup针对特定系统的命令),你需要 以管理员身份打开 PowerShell 或 CMD ,执行那条命令。 -
常用 PM2 命令:
pm2 status # 查看所有进程状态 pm2 logs openclaw-viewer # 查看该服务的实时日志 pm2 restart openclaw-viewer # 重启服务 pm2 stop openclaw-viewer # 停止服务 pm2 delete openclaw-viewer # 删除服务
5. 功能深度使用与技巧
5.1 两种视图模式与高效浏览
打开查看器,你会看到两种主要的视图:
- 列表视图 :传统的文件列表,清晰展示当前目录下的文件和文件夹。
- 树状视图 :点击左上角的切换按钮,可以展开一个侧边栏式的完整目录树。这个视图对于快速跳转到深层嵌套的目录特别有用,尤其是在手机上,避免了多次点击返回。
实操技巧 :你的视图偏好(列表或树状)会被保存在浏览器的 localStorage 中。下次访问同一设备时,会自动恢复你喜欢的模式。
5.2 文件搜索与快速定位
顶部有一个搜索框。它执行的是 文件名搜索 ,而非文件内容搜索。输入关键词后,它会实时在所有目录中匹配包含该关键词的 .md 文件名,并显示最多 30 个结果。
注意 :目前搜索是大小写敏感的,且只针对 Markdown 文件。如果你需要全文搜索,可以考虑将
.openclaw目录用 Obsidian 打开,并利用其强大的搜索功能,但那就失去了手机便捷浏览和本地优先的纯粹性。这个查看器的搜索定位是“快速找到已知文件”。
5.3 阅读体验优化:暗色主题与代码高亮
界面采用 GitHub Dark 风格的暗色主题,长时间阅读不伤眼。对于 Markdown 中的代码块,前端通过 highlight.js 自动检测语言并进行语法高亮,这使得阅读智能体生成的代码片段、配置示例或日志中的数据结构变得非常舒适。
5.4 导航与历史记录
- 面包屑导航 :页面顶部的路径导航栏(面包屑)清晰显示了你的当前位置。你可以点击路径中的任何一级,快速跳转回上层目录。
- 浏览器历史 :应用完全支持浏览器的前进和后退按钮。你可以像浏览普通网页一样,在查看过的文件和目录间来回切换,交互非常自然。
6. 安全考量与最佳实践
6.1 内置安全机制
- 路径遍历防护 :服务器代码会严格校验请求的文件路径,防止用户通过构造类似
../../../etc/passwd的路径来访问系统敏感文件。这是通过path.relative和path.resolve函数确保请求路径不会逃逸出WORKSPACE_ROOT目录来实现的。 - HTML 净化 :使用
DOMPurify对 Markdown 渲染后的 HTML 进行消毒,移除所有可能执行的脚本(<script>)、事件处理器(如onclick)等危险元素,从根本上杜绝了通过 Markdown 文件进行 XSS 攻击的可能。 - Tailscale 网络隔离 :服务本身只监听
localhost。通过 Tailscale 访问,相当于在一个加密的私有局域网内访问,服务本身无需暴露在公网,极大减少了攻击面。
6.2 增强安全的最佳实践
尽管项目本身很安全,但遵循以下原则能让你的部署更稳固:
- 最小权限原则 :运行 Node.js 服务的系统用户,不应该有超出
WORKSPACE_ROOT目录的读写权限。在 Linux 上,可以考虑创建一个专用用户来运行此服务。 - 定期更新 :关注项目 GitHub 仓库的更新,及时拉取安全补丁或功能改进。
- 审计日志 :虽然查看器本身不记录访问日志,但你可以通过系统的日志工具(如
journalctl查看 systemd 服务的输出)来监控服务的运行状态。对于更高级的需求,可以考虑在 Express 服务器前加一个 Nginx 反向代理来记录访问日志。 - 谨慎设置
WORKSPACE_ROOT:再次强调,不要将其设置为/或你的家目录。精确指向你需要共享的目录。
7. 扩展用途:不止于 OpenClaw
这个查看器的本质是一个 通用的本地 Markdown 文件树服务器 。虽然它因 OpenClaw 而生,但其应用场景可以非常广泛:
- Obsidian 库手机查看器 :将
WORKSPACE_ROOT指向你的 Obsidian 仓库目录,就可以在手机上优雅地浏览你的笔记库。这对于快速查阅(而非编辑)特别方便。 - 文档项目预览 :如果你在本地编写项目文档(如
docs/目录),可以用它来在局域网内预览效果,比启动完整的文档服务器更轻量。 - 个人知识库 :管理你的 Zettelkasten(卡片盒笔记法)文件夹,实现跨设备阅读。
- 团队内部文档共享(小范围) :如果团队成员都加入了同一个 Tailscale 网络,你可以将此服务运行在一台内部服务器上,作为轻量级的内部文档站点。
它的简洁性和隐私性,使其成为任何需要安全、便捷地跨设备访问本地文本文件场景的绝佳解决方案。
8. 故障排除与常见问题
Q1: 服务启动成功,但手机访问时显示“无法连接”或“拒绝连接”。 A1: 这是最常见的问题。请按顺序排查: 1. 确认 Tailscale 连接 :在手机和电脑上分别打开 Tailscale App,确认两者都显示为“Connected”(绿色)。尝试在手机 Tailscale App 内 ping 一下电脑的主机名或 IP。 2. 确认服务端口 :在电脑上运行 netstat -an | grep 3500 (Linux/macOS)或 Get-NetTCPConnection -LocalPort 3500 (Windows PowerShell),查看是否有进程在监听 0.0.0.0:3500 或 :::3500 。如果只看到 127.0.0.1:3500 ,可能需要检查服务绑定地址的代码(本项目默认应绑定 0.0.0.0 )。 3. 检查防火墙 :临时关闭电脑的防火墙进行测试。如果关闭后能访问,则需要在防火墙规则中允许 3500 端口的入站连接(TCP)。
Q2: 树状视图或文件列表加载很慢。 A2: 如果你的 .openclaw 目录非常大(例如包含数万个文件),首次加载树状视图( /api/tree )可能会较慢,因为需要递归扫描所有目录。这是预期行为。建议: - 使用搜索功能直接定位文件,避免浏览超大目录。 - 考虑是否真的需要将所有历史文件都放在活动目录中,可以定期归档旧的日志和输出。
Q3: 某些 Markdown 文件渲染格式错乱。 A3: 这通常是由于 Markdown 内容包含非标准语法或复杂的 HTML 片段,与 marked 解析器的配置或 DOMPurify 的过滤规则有关。可以尝试: - 查看浏览器开发者控制台(Console)是否有 JS 错误。 - 对比在 Obsidian 或 VS Code 中的预览效果。如果只是简单的换行或列表问题,可能是原文件格式不够规范。
Q4: 如何修改界面样式或主题? A4: 项目的前端资源(CSS、JS)是静态的。你可以直接修改项目 public/ 目录下的 style.css 文件来自定义颜色、字体或布局。修改后需要重启 Node.js 服务生效。这是一个轻量级项目,自定义起来相对简单。
Q5: 能否支持图片预览或 PDF 查看? A5: 当前版本的核心定位是 Markdown 文本查看器,不支持内联图片预览或其他文件格式的直接渲染。对于图片,Markdown 中的 ![]() 语法会渲染成一个可点击的图片链接,点击后浏览器会尝试下载或打开图片文件(取决于手机浏览器的设置)。更复杂的文件预览属于功能扩展范畴,如果需要,可以 fork 项目自行开发。
更多推荐
所有评论(0)