LeafWiki 二进制与 Compose 部署实战
LeafWiki v0.14.2 双形态部署实测:二进制直跑与 systemd 服务化、Compose 编排(8141 端口/10.89.61.0/24 网段)、首启日志与 schema 迁移、建页/编辑/检索/权限/乐观锁/resync 功能闭环、5 项排障实录、反代 HTTPS、快照备份恢复与资源占用。
LeafWiki 二进制与 Compose 部署实战
实测版本 v0.14.2,实测日期 2026-11-01。资源规划:宿主 Web 端口 8141,自定义网段 10.89.61.0/24(服务实例固定地址 10.89.61.10)。本文两种形态均为同一台 Ubuntu 26.04.1 LTS 服务器上的实测记录。
一、部署形态选择
LeafWiki 官方提供五条安装路径(详见《LeafWiki 介绍与选型指南》第四章):单二进制、Linux 安装脚本(systemd 服务)、Compose 编排、Kubernetes、源码构建。本文实测两条主线:
- B1 二进制直跑:产品定位形态,
leafwiki单个静态链接 ELF(48,427,168 字节),适合裸机/虚机最小运维; - B2 Compose 编排:官方推荐的声明式形态,适合已有编排栈管理习惯的服务器。
两条路径产物等价:二进制与官方发布包内 /app/leafwiki 完全同源(实测从官方发布包提取后直跑验证;生产环境请从 GitHub Releases 下载 leafwiki-v0.14.2-linux-amd64 及 .sha256 校验,本实验机外网受限无法直连 Releases 下载,此为如实记录的取证边界)。
二、环境基线
| 项 | 值 |
|---|---|
| 宿主 OS | Ubuntu 26.04.1 LTS(grep PRETTY /etc/os-release) |
| 内核/架构 | 7.0.0-38-generic x86_64(uname -rm) |
| 编排引擎 | Docker version 29.9.2, build a82e528 |
| 实验目录 | /opt/data/quzuo/tmp/leafwiki/ |
| 端口 | 宿主 8141 → 服务 8080 |
| 网段 | 10.89.61.0/24,自定义网络 leafwiki-net |
三、B1:二进制直跑
3.1 获取与校验
官方二进制发布物命名 leafwiki-<version>-<os>-<arch>(linux-amd64/arm64/386、darwin-amd64/arm64、windows-amd64),发布页附 .sha256 文件:
sha256sum leafwiki-<version>-<os>-<arch>
cat leafwiki-<version>-<os>-<arch>.sha256
mv leafwiki-<version>-<os>-<arch> leafwiki
chmod +x leafwiki
实测二进制为静态链接 Go ELF,无 glibc 依赖:
$ file leafwiki
leafwiki: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=..., stripped
3.2 首次启动
./bin/leafwiki --jwt-secret=bin-test-secret-0123456789abcdef --admin-password=q1w2e3r4 --allow-insecure --host=127.0.0.1 --port=8099 --data-dir ./bindata
实测启动日志(bin.log 逐行照抄):
time=2026-11-01T22:11:34.915+08:00 level=WARN source=/app/cmd/leafwiki/main.go:209 msg="allow-insecure enabled. Auth cookies may be transmitted over plain HTTP (INSECURE)."
time=2026-11-01T22:11:34.956+08:00 level=INFO source=/app/internal/core/tree/tree_service.go:63 msg="Checking schema version..." component=TreeService
time=2026-11-01T22:11:34.963+08:00 level=INFO source=/app/internal/wiki/wiki_boot.go:214 msg="search indexing started" component=Wiki
time=2026-11-01T22:11:34.963+08:00 level=INFO source=/app/internal/wiki/wiki_boot.go:285 msg="Welcome page already exists, skipping creation" component=Wiki
time=2026-11-01T22:11:34.963+08:00 level=INFO source=/app/cmd/leafwiki/main.go:464 msg="Starting LeafWiki" address=127.0.0.1:8099 data_dir=./bindata
要点:
- 默认只绑
127.0.0.1:8080,暴露到网络需显式--host=0.0.0.0; - 明文 HTTP 必须
--allow-insecure,否则登录 Cookie 不工作; - 首次启动自动创建 admin 用户(用户名默认
admin,口令取--admin-password,至少 8 字符)并写入Welcome to LeafWiki初始页; - 数据目录自动完成 schema 迁移(0→5)并初始化检索索引。
验证服务与登录:
$ curl -s -o /dev/null -w 'code=%{http_code}\n' http://127.0.0.1:8099/
code=200
$ curl -s -X POST http://127.0.0.1:8099/api/auth/login -H 'Content-Type: application/json' -d '{"identifier":"admin","password":"q1w2e3r4"}'
{"accessTokenExpiresAt":1793543196,"message":"Login successful","user":{"id":"c1e2T2qvR","username":"admin","email":"admin@localhost","role":"admin","totpEnabled":false,"mustSetPassword":false}}
3.3 数据目录结构(两种形态通用)
首次运行后 ./bindata 实测布局:
bindata
bindata/tags.db-shm
bindata/avatars
bindata/search.db-shm
bindata/sessions.db-shm
bindata/tags.db-wal
bindata/tags.db
bindata/favorites.db-wal
bindata/schema.json
bindata/usersettings.db-wal
bindata/properties.db
bindata/search.db
另有 users.db、links.db、usersettings.db 等 SQLite 库(WAL 模式),页面正文在 root/ 子目录按树形层级落为 .md 文件,修订历史在 .leafwiki/revisions/<页面ID>/,快照 ZIP 在 snapshots/。元数据在 SQLite、正文在文件系统是理解 LeafWiki 持久化的关键:备份必须整目录打包,只拷 *.md 会丢权限/标签/修订。
3.4 systemd 服务化(生产形态)
官方 Linux 安装脚本(install.sh --non-interactive --env-file ./.env)即装成 systemd 服务;手工单元等价写法:
# /etc/systemd/system/leafwiki.service
[Unit]
Description=LeafWiki
After=network.target
[Service]
ExecStart=/opt/leafwiki/leafwiki --data-dir=/var/lib/leafwiki
EnvironmentFile=/etc/leafwiki/.env
User=leafwiki
Restart=on-failure
[Install]
WantedBy=multi-user.target
注意:交互模式安装脚本会把环境变量明文写入 /etc/leafwiki/.env,需收紧文件权限(官方 README 安全提示)。32 位 x86 Linux 仅二进制路径可用(无 CGO 构建兼容 musl),安装脚本与升级脚本不适用于该架构。
四、B2:Compose 编排部署
4.1 编排文件(实测全文)
# docker-compose.yml
name: leafwiki-0.14.2-stack
services:
leafwiki:
image: ghcr.io/perber/leafwiki:v0.14.2
container_name: leafwiki-0.14.2-web
restart: unless-stopped
ports:
- "8141:8080"
environment:
- LEAFWIKI_JWT_SECRET=s9-l34fw1k1-jwt-secret-2026-long-enough
- LEAFWIKI_ADMIN_PASSWORD=q1w2e3r4
- LEAFWIKI_ALLOW_INSECURE=true
- LEAFWIKI_ENABLE_REVISION=true
- LEAFWIKI_LOG_LEVEL=info
volumes:
- ./data:/app/data
networks:
leafwiki-net:
ipv4_address: 10.89.61.10
networks:
leafwiki-net:
driver: bridge
ipam:
config:
- subnet: 10.89.61.0/24
4.2 启动与首启日志
$ docker compose up -d
Network leafwiki-0142-stack_leafwiki-net Created
Container leafwiki-0.14.2-web Created
Container leafwiki-0.14.2-web Started
$ docker compose ps
leafwiki-0.14.2-web 0.0.0.0:8141->8080/tcp, [::]:8141->8080/tcp Up 3 seconds
首启日志逐行照抄(含官方发布包入口脚本行为):
[entrypoint] Starting entrypoint script...
[entrypoint] No --host argument or LEAFWIKI_HOST environment variable found. Setting LEAFWIKI_HOST to '0.0.0.0'
[entrypoint] Executing /app/leafwiki
time=2026-11-01T14:07:24.430Z level=WARN source=/app/cmd/leafwiki/main.go:209 msg="allow-insecure enabled. Auth cookies may be transmitted over plain HTTP (INSECURE)."
time=2026-11-01T14:07:24.498Z level=INFO source=/app/internal/core/auth/user_service.go:146 msg="user created" component=UserService userID=H_sIThqDR role=admin
time=2026-11-01T14:07:24.503Z level=WARN source=/app/internal/core/auth/session_manager.go:46 msg="JWT secret is too short; a minimum of 32 characters is strongly recommended" component=SessionManager length=27
time=2026-11-01T14:07:24.544Z level=INFO source=/app/internal/core/tree/tree_service.go:63 msg="Checking schema version..." component=TreeService
time=2026-11-01T14:07:24.544Z level=INFO msg="Schema file not found, assuming version 0"
time=2026-11-01T14:07:24.544Z level=INFO source=/app/internal/core/tree/tree_service.go:106 msg="Migrating schema" component=TreeService fromVersion=0 toVersion=5
time=2026-11-01T14:07:24.544Z level=INFO msg="Saving schema version 1 to data/schema.json"
...(schema 2/3/4/5 逐版保存)
time=2026-11-01T14:07:24.568Z level=INFO source=/app/internal/wiki/wiki_boot.go:214 msg="search indexing started" component=Wiki
time=2026-11-01T14:07:24.571Z level=INFO source=/app/internal/wiki/wiki_boot.go:224 msg="search indexing completed" component=Wiki
time=2026-11-01T14:07:24.572Z level=INFO source=/app/internal/core/tree/tree_mutations.go:337 msg="updating node content" component=TreeService nodeID=89sSohqvR
time=2026-11-01T14:07:24.578Z level=INFO source=/app/cmd/leafwiki/main.go:464 msg="Starting LeafWiki" address=0.0.0.0:8080 data_dir=./data
入口脚本行为:未提供 --host/LEAFWIKI_HOST 时自动置 0.0.0.0(编排形态需要对外监听,与二进制默认 127.0.0.1 相反)。官方发布包默认以 root 运行,README 给出非特权写法:user: 1000:1000 + 数据目录对该用户可写。
五、部署后基础功能实测(两种形态一致)
以 8141 实例、示例账号 admin / q1w2e3r4 走通核心闭环(API 全链路细节见《LeafWiki REST API 实测》):
- 建页:
POST /api/pages传{title,slug,parentId,kind},返回新页RRpOT2qvR,初始内容# Deploy Verify\n; - 编辑:
PUT /api/pages/{id}传{version,title,slug,content,tags,properties}全字段,保存成功并返回新 version; - 磁盘同步:编辑后
data/root/deploy-verify.md实测更新,文件头带leafwiki_id/leafwiki_title/leafwiki_created_at/leafwiki_updated_at/leafwiki_creator_id/leafwiki_last_author_id与tags前置元数据,正文即 Markdown 原文; - 检索:
GET /api/search?q=callout命中并高亮,返回tags字段;GET /api/tags实测[{"tag":"s9","count":1},{"tag":"verify","count":1}]; - 权限:
POST /api/users建 viewer 角色用户viewer01,其登录后可读页面,但建页请求实测返回:
HTTP/1.1 403 Forbidden
{"error":"Editor or Admin role required"}
- 乐观锁:携带过期 version 的
PUT实测返回page_version_conflict(「Page was changed by another request」),并发编辑保护生效; - 外部编辑:直接追加磁盘
.md内容后POST /api/admin/resync返回{"ok":true},随后 API 读取页面内容已包含外部段落(「EXTERNAL」校验通过)。
六、排障实录
| # | 现象 | 根因与处置 |
|---|---|---|
| 1 | 首启日志 WARN:JWT secret is too short ... length=27 | 实验密钥 27 字符 < 建议 32。更换 ≥32 字符密钥重建后告警消失。服务仍可运行,但生产必须满足长度建议 |
| 2 | PUT /api/pages/{id} 返回 page_invalid_request | 只传 {content,version} 不够;实测必须全字段 {version,title,slug,content,tags,properties}。从发布包内嵌前端产物反查出 payload 结构后通过 |
| 3 | 二进制实例启动即退:Failed to start server ... bind: address already in use | 前一次测试进程仍占用 8099。pkill -f 'bin/leafwiki' 清理后重启成功。同一数据目录不允许双实例并发写 |
| 4 | 直接编辑磁盘 .md 后 API 仍读旧内容 | LeafWiki 以 SQLite 元数据+内存索引为准,外部修改需触发 POST /api/admin/resync(管理端「外部编辑与重同步」文档行为一致) |
| 5 | 明文 HTTP 登录后 Cookie 不下发 | 必须显式 --allow-insecure(或环境变量 LEAFWIKI_ALLOW_INSECURE=true);HTTPS 反代场景去掉该参数并确保转发 X-Forwarded-Proto: https |
七、反向代理与 HTTPS 要点
- 子路径挂载:
--base-path=/wiki(v0.8.2+),反代保留前缀转发; - 反代认证:
--enable-http-remote-user+--http-remote-user-header-name(默认Remote-User)+--trusted-proxy-ips(必填可信来源,防伪造头);可配--enable-http-remote-user-auto-create自动开户并指定默认角色(不得为 admin); - SSO 跳转:
--login-url/--logout-url替换内置登录页; - 生产 HTTPS:去掉
--allow-insecure,反代需透传X-Forwarded-Proto: https。
八、备份与恢复
- 自动快照:默认开启(
--snapshot,间隔 24h,保留 10 份),实测data/snapshots/生成snapshot-20261101-140724.zip(3,287 字节,含 SQLite 全库)与同名.json清单; - 离线恢复:停服后执行,实测通过:
$ ./bin/leafwiki --data-dir ./bindata restore-snapshot bindata/snapshots/snapshot-20261101-141121.zip
Snapshot restored successfully. Start the server normally to pick up the restored data.
- 冷备份:停服后整目录
cp -r data/(官方口径);开启--snapshot时管理界面 Settings → Full Backup 还支持在线恢复(恢复换文件期间写入返回 503); - Git 备份(实验性,v0.11.3):
--git-backup --git-backup-remote=<url>定时推送root/与assets/到远端仓库,默认间隔 60m。
九、升级
- 二进制/systemd:官方
update.sh仅适用于安装脚本部署;手工路径为下载新版本二进制 → 停服 → 替换 → 起服(schema 自动迁移,实测 0→5 逐版落schema.json); - Compose:改
image标签 →docker compose up -d,数据卷不动; - 升级前建议先手动触发一次快照(管理界面或等待定时快照),v0.x 大版本间行为变化需核对 Release Notes。
十、资源占用与结论
稳定运行(含检索索引、1 页面 2 用户)实测:
$ docker stats leafwiki-0.14.2-web --no-stream
leafwiki-0.14.2-web CPU=0.00% MEM=10.08MiB / 15.07GiB PIDS=11
$ du -sh data
1000K data
约 10 MiB 常驻内存、空载零 CPU、数据目录 1 MB 内——与「单二进制 + SQLite」定位高度一致,树莓派级硬件即可承载小团队 Wiki。两条部署主线全部实测通过,无功能性阻塞;B1 的取证边界(Releases 直连下载在本实验机不可达,改用官方发布包同源二进制验证)已如实记录。
延伸阅读:《LeafWiki 配置参数详解》(全量 CLI/环境变量逐项)、《LeafWiki REST API 实测》(curl 全链路实录)。