AFFiNE 0.27.4 运维命令与 GraphQL API 详解
AFFiNE 0.27.4 运维命令与 GraphQL API 实操:compose 生命周期与日志、/info 健康检查、GraphQL 登录态与查询、未认证仍返回 200 的陷阱、PostgreSQL/Redis 计数与容量、pg_dump 备份恢复、镜像内三个脚本用途,附四类故障排障命令组合。
AFFiNE 0.27.4 运维命令与 GraphQL API 详解
目标:把 AFFiNE 0.27.4 日常运维会用到的命令与接口一次讲全——服务生命周期、资源观测、GraphQL 查询、PostgreSQL/Redis 运维、备份恢复、镜像内脚本。文中命令与回显均来自 2026-10-25 在本文实例上的真实执行。
一、环境信息
- 操作系统:Ubuntu 22.04(x86_64)
- Docker Engine:29.9.2,Docker Compose 插件 v5.6.1
- AFFiNE:0.27.4(
allinone形态,Node.js v22.23.2) - PostgreSQL:16(
pgvector/pgvector:pg16) - Redis:7(实测 7.4.11)
- 部署目录:
/opt/data/quzuo/tmp/affine/deploy/ - Compose 项目名:
affine-v0274-stack - 访问地址:
http://192.168.146.128:8137 - 管理员:
admin@example.com/q1w2e3r4 - 部署篇参看 《AFFiNE 0.27.4 Docker Compose 部署指南》,配置项参看 《AFFiNE 0.27.4 配置参数详解》
二、前置条件
- 已在部署目录执行过一次
docker compose up -d,四个服务(postgres、redis、affine_migration、affine)就绪; - 宿主机能执行
docker与docker compose; - 已拿到管理员账号,用于 GraphQL 查询;
- 下文命令默认在部署目录执行;跨目录执行时用
-f指定编排文件。
三、命令体系总览
AFFiNE 没有独立的 CLI 二进制,运维入口分四类:
| 类别 | 入口 | 典型用途 |
|---|---|---|
| 编排层 | docker compose | 启停、日志、状态、配置解析 |
| 应用接口 | REST /info、/api/auth/*;GraphQL /graphql | 健康检查、登录、数据查询 |
| 数据库层 | psql、pg_dump、redis-cli | 计数、容量、备份 |
| 镜像内脚本 | node /app/scripts/* | 密钥初始化、配置 schema 生成 |
先认清服务拓扑。docker compose config --services 实测输出:
postgres
redis
affine_migration
affine
affine_migration 是一次性任务,执行完即退出,因此 ps 里通常看不到它处于 Up,这是正常状态,不是故障。
💡 技术拓展:为什么把迁移拆成独立服务?因为应用启动前必须先跑
self-host-predeploy.js(建表、生成private.key、执行 Prisma 迁移)。用depends_on: condition: service_completed_successfully把「迁移成功」作为应用启动的前置条件,可以避免应用先起来、表还没建好导致的报错循环。代价是迁移失败时应用会一直不启动——看到应用不起,先去查迁移服务的退出码与日志。
四、服务生命周期
4.1 查看状态
docker compose ps
实测输出:
NAME IMAGE COMMAND SERVICE STATUS PORTS
affine-0.27.4-postgres pgvector/pgvector:pg16 "docker-entrypoint.s…" postgres Up About an hour (healthy) 5432/tcp
affine-0.27.4-redis redis:7 "docker-entrypoint.s…" redis Up About an hour (healthy) 6379/tcp
affine-0.27.4-server ghcr.io/toeverything/affine:0.27.4 "docker-entrypoint.s…" affine Up 23 minutes 0.0.0.0:8137->3010/tcp
判读要点:
- PostgreSQL 与 Redis 有
(healthy),说明 healthcheck 通过;应用没有 healthcheck,只显示Up。 - 应用
Up的时长可以短于数据库,因为它是最后启动的。 - 端口列出现
0.0.0.0:8137->3010/tcp表示映射生效;只显示3010/tcp说明没映射到宿主机。
4.2 启动与重建
docker compose up -d # 按编排启动/更新
docker compose up -d --force-recreate affine # 只重建应用
docker compose restart affine # 仅重启,不重建
改 config.json 后用 restart affine 即可(配置在进程启动时读取);换镜像 tag 用 up -d(会重建);改编排里的环境变量用 up -d(会重建)。
4.3 日志
docker compose logs --tail=100 affine
docker compose logs -f affine
docker compose logs affine_migration
应用日志里最常出现的三类信息:启动时的配置加载、GraphQL 请求的 X-Request-Id、任务队列的失败重试。迁移服务日志只在启动阶段有价值,之后不再产生内容。
4.4 健康检查
应用没有内置 healthcheck,用 /info 代替:
curl -s -D - http://127.0.0.1:8137/info -o /tmp/ainfo.json -w "\nHTTP %{http_code}\n" | head -4
cat /tmp/ainfo.json
实测输出:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{"compatibility":"0.27.4","message":"AFFiNE 0.27.4 Server","type":"selfhosted","flavor":"allinone"}
compatibility 是客户端兼容版本,flavor: allinone 证明是单体形态。数据库宕机时该接口可能仍返回 200(它不查库),所以完整健康检查还要加一次登录或 GraphQL 查询。
五、资源观测
docker stats affine-0.27.4-server affine-0.27.4-postgres affine-0.27.4-redis --no-stream \
--format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.PIDs}}"
实测输出:
NAME CPU % MEM USAGE / LIMIT PIDS
affine-0.27.4-server 0.05% 163.5MiB / 23.42GiB 24
affine-0.27.4-postgres 3.92% 51.75MiB / 23.42GiB 15
affine-0.27.4-redis 0.15% 12.17MiB / 23.42GiB 6
空载基线:应用约 164 MiB、数据库约 52 MiB、Redis 约 12 MiB。官方最低要求 2 GB 内存,是因为文档合并峰值可到 1 GB;长期占用看这三个数,峰值看 docker stats 的实时刷新。
磁盘:
du -sh deploy/data/postgres deploy/data/storage
实测输出:
69M deploy/data/postgres
16K deploy/data/storage
注意区分两个概念:data/postgres 是磁盘目录占用(69 MB),而数据库逻辑大小是 14 MB——PostgreSQL 预分配 WAL 与页文件,目录远大于有效数据是正常的。
六、GraphQL API 入门
AFFiNE 的业务数据几乎全走 GraphQL,端点固定为 POST /graphql,请求体是标准 GraphQL 文档,响应是 Apollo 风格 JSON(data + errors)。
6.1 先登录拿 Cookie
GraphQL 本身不签发令牌,先走 REST 登录:
curl -s -c ai_cookies.txt -X POST http://127.0.0.1:8137/api/auth/sign-in \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"q1w2e3r4"}' \
-o /dev/null -w "login HTTP %{http_code}\n"
实测输出:
login HTTP 200
Cookie 罐里会有两个值:affine_user_id 与 affine_csrf_token。后续请求带 -b ai_cookies.txt。
登录失败返回 400 而不是 401:
curl -s -X POST http://127.0.0.1:8137/api/auth/sign-in \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"bad-pass"}' -w "\nHTTP %{http_code}\n"
实测输出:
{"status":400,"code":"Bad Request","type":"INVALID_INPUT","name":"WRONG_SIGN_IN_CREDENTIALS","message":"Wrong user email or password: admin@example.com","data":{"email":"admin@example.com"}}
HTTP 400
6.2 最小查询
curl -s -b ai_cookies.txt -X POST http://127.0.0.1:8137/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ __typename }"}'
实测输出:
{"data":{"__typename":"Query"}}
这条查询不需要认证,适合做连通性探测。
6.3 未认证的行为
curl -s -X POST http://127.0.0.1:8137/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ usersCount }"}' -w "\nHTTP %{http_code}\n"
实测输出:
{"errors":[{"message":"You must sign in first to access this resource.","locations":[{"line":1,"column":3}],"path":["usersCount"],"extensions":{"status":401,"code":"Unauthorized","type":"AUTHENTICATION_REQUIRED","name":"AUTHENTICATION_REQUIRED","message":"You must sign in first to access this resource."}}],"data":null}
HTTP 200
⚠️ HTTP 状态码仍是 200,401 藏在 errors[].extensions.status 里。写监控或脚本时必须解析响应体,只看状态码会把「未登录」当成「成功」。
6.4 查询语法错误的响应
curl -s -b ai_cookies.txt -X POST http://127.0.0.1:8137/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ nope }"}' -w "\nHTTP %{http_code}\n"
实测输出:
{"errors":[{"message":"Cannot query field \"nope\" on type \"Query\".","locations":[{"line":1,"column":3}],"extensions":{"status":400,"code":"Bad Request","type":"BAD_REQUEST","name":"GRAPHQL_BAD_REQUEST","message":"GraphQL bad request, code: GRAPHQL_VALIDATION_FAILED, Cannot query field \"nope\" on type \"Query\"."}}]}
HTTP 400
语法/字段错误是 400,鉴权失败是 200 外壳——两者要分开处理。
七、日常查询清单
7.1 当前用户、用户数、工作区
curl -s -b ai_cookies.txt -X POST http://127.0.0.1:8137/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ currentUser { id email name } usersCount workspaces { id memberCount public initialized createdAt } }"}'
实测输出:
{
"data": {
"currentUser": {
"id": "38ccef6f-61d0-4a00-ae0b-da659babdf06",
"email": "admin@example.com",
"name": "admin"
},
"usersCount": 1,
"workspaces": [
{
"id": "5eac931b-4b57-4684-9829-6f1cea8f4bd3",
"memberCount": 1,
"public": false,
"initialized": true,
"createdAt": "2026-10-25T04:20:56.918Z"
}
]
}
}
注意 WorkspaceType 没有 name 字段,直接查 name 会报校验失败——工作区名称存在文档里,不在工作区实体上。这是从「直觉字段名」出发最容易踩的坑。
7.2 生效配置与特性
curl -s -b ai_cookies.txt -X POST http://127.0.0.1:8137/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ serverConfig { name features } }"}'
实测输出:
{
"data": {
"serverConfig": {
"name": "AFFiNE 内网知识库",
"features": ["Comment", "Copilot", "LocalWorkspace", "CopilotEmbedding"]
}
}
}
features 是服务端实际开启的能力,排障「功能开关有没有生效」时看这里最快。完整生效配置用 appConfig(含私钥,注意脱敏)。
7.3 内省(调试用)
生产环境如果不希望暴露 schema,应关闭 GraphQL introspection。本文实例 appConfig.graphql.apolloDriverConfig.introspection 为 true,因此可以直接内省:
curl -s -X POST http://127.0.0.1:8137/graphql -H 'Content-Type: application/json' \
-d '{"query":"{ __schema { queryType { fields { name } } } }"}'
查询类型共 24 个字段,常用的是 currentUser、usersCount、workspaces、serverConfig、appConfig、adminWorkspaces、adminMailDeliveries。
八、数据库运维
8.1 进入 psql
docker exec -it affine-0.27.4-postgres psql -U affine -d affine
非交互执行用 -c:
docker exec affine-0.27.4-postgres psql -U affine -d affine \
-c "SELECT pg_size_pretty(pg_database_size('affine')) AS db_size, (SELECT count(*) FROM information_schema.tables WHERE table_schema='public') AS tables;"
实测输出:
db_size | tables
---------+--------
14 MB | 91
(1 row)
8.2 数据量核对
docker exec affine-0.27.4-postgres psql -U affine -d affine \
-c "SELECT (SELECT count(*) FROM users) AS users, (SELECT count(*) FROM workspaces) AS workspaces, (SELECT count(*) FROM workspace_members) AS members;"
实测输出:
users | workspaces | members
-------+------------+---------
1 | 1 | 1
(1 row)
这三个数与 GraphQL 的 usersCount、workspaces 长度、memberCount 完全对得上,是校验「接口数据与库内数据一致」的最快方法。
8.3 表容量
docker exec affine-0.27.4-postgres psql -U affine -d affine \
-c "SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 8;"
实测输出:
relname | size
-----------------------+--------
workspace_admin_stats | 128 kB
workspace_members | 128 kB
mail_deliveries | 120 kB
workspace_invitations | 96 kB
workspaces | 80 kB
users | 80 kB
_prisma_migrations | 72 kB
user_features | 64 kB
(8 rows)
91 张表是 Prisma 迁移的产物,_prisma_migrations 记录版本,不要手工删表。
8.4 备份
docker exec affine-0.27.4-postgres pg_dump -U affine -d affine -Fc > backup_affine_20261025.dump
ls -lh backup_affine_20261025.dump
实测输出:
-rw-r--r-- 1 root root 269K Oct 25 13:29 backup_affine_20261025.dump
-Fc 是自定义格式,压缩且支持选择性恢复。备份文件要落在宿主机持久目录,不要留在容器内。
恢复:
docker exec -i affine-0.27.4-postgres pg_restore -U affine -d affine --clean --if-exists < backup_affine_20261025.dump
--clean --if-exists 会先删旧对象再重建,适合整库回滚;只想补数据时去掉这两个参数并用 -t 表名 限定范围。
8.5 存储目录与配置目录也要备
数据库只存元数据,附件与头像在文件系统:
du -sh deploy/data/storage
ls deploy/config
实测输出:
16K deploy/data/storage
config.json private.key
完整备份 = pg_dump + data/storage 打包 + config/ 打包。三者缺一不可:少 storage 附件丢失,少 private.key 全员令牌失效,少 config.json 配置回退默认。
九、Redis 运维
docker exec affine-0.27.4-redis redis-cli INFO keyspace
实测输出:
# Keyspace
db0:keys=1,expires=0,avg_ttl=0,subexpiry=0
db4:keys=23,expires=8,avg_ttl=18774,subexpiry=0
DBSIZE 单看当前库(默认 db0)只有 1 个键,容易误判「Redis 没数据」;AFFiNE 的队列与限流数据在 db4:
docker exec affine-0.27.4-redis redis-cli -n 4 DBSIZE
实测输出:
22
键数会随任务执行浮动,INFO 与 DBSIZE 之间差一两个属正常。Redis 是缓存与队列,不作为持久数据源——清空 Redis 会丢排队任务与限流计数,但不丢文档;生产仍建议开启 AOF/RDB 持久化,减少重启后的任务丢失。
十、镜像内脚本
AFFiNE 镜像自带三个脚本,都在 /app/scripts:
| 脚本 | 用途 | 是否需手工执行 |
|---|---|---|
self-host-predeploy.js | 生成 private.key、执行部署前准备 | 否,由 affine_migration 服务自动跑 |
genconfig.ts | 从代码里的配置描述器生成 config.schema.json | 否,仅开发/发布用 |
docker-clean.mjs | 清理镜像构建产物 | 否,构建阶段用 |
确认预部署脚本确实在做事:
docker exec affine-0.27.4-server sh -c 'ls /root/.affine/config; node -v; ls /root/.affine/storage'
实测输出:
config.json
private.key
v22.23.2
avatars
private.key 不存在时,预部署脚本会在下次启动时自动生成一把新的 EC P-256 私钥(源码逻辑:文件已存在则跳过,不存在则写入)。所以「删了私钥」不是永久故障,但旧令牌会全部失效,且多副本会各生成各的——多副本部署必须共用同一份 config 目录。
十一、故障排查命令组合
11.1 应用起不来
docker compose ps
docker compose logs --tail=80 affine_migration
docker compose logs --tail=80 affine
先看迁移服务是否以 0 退出。迁移失败(数据库没就绪、迁移脚本报错)时应用会一直不启动,日志里能看到 depends_on 等待。
11.2 接口 500 / 502
curl -s -D - http://127.0.0.1:8137/info
docker compose logs --tail=50 affine | grep -i error
docker exec affine-0.27.4-postgres pg_isready -U affine -d affine
docker exec affine-0.27.4-redis redis-cli ping
pg_isready 返回 accepting connections、redis-cli ping 返回 PONG 才能排除依赖故障。
11.3 登录后立刻掉线
检查 config/private.key 是否在多次重建中被替换(例如把整个 config 目录删掉重建)。私钥变化会让已签发令牌全部失效,表现为所有用户被强制退出。用备份恢复私钥后重启应用即可,无需改密码。
11.4 磁盘增长过快
du -sh deploy/data/*
docker exec affine-0.27.4-postgres psql -U affine -d affine \
-c "SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 5;"
docker exec affine-0.27.4-postgres psql -U affine -d affine \
-c "SELECT pg_size_pretty(sum(size)) FROM pg_ls_waldir();"
先分清是数据表增长、WAL 增长还是 storage 附件增长。WAL 异常增长通常是长事务或复制槽未消费,需要单独排查。
十二、参考链接
- 站内:《AFFiNE 0.27.4 介绍与选型指南》、《AFFiNE 0.27.4 Docker Compose 部署指南》、《AFFiNE 0.27.4 配置参数详解》
- 官方自托管文档:
https://docs.affine.pro/docs/self-host-affine - 官方仓库:
https://github.com/toeverything/AFFiNE - 命令与回显采集日期:2026-10-25;实例版本 0.27.4