本页带你在本地从零跑通整套系统:六个后端服务 + Flutter 客户端。

前置条件

uv

Python 包与环境管理工具,每个后端服务都是独立的 uv 项目。

Python 3.11

后端运行时(由 uv 按服务管理)。

Flutter

客户端运行时,参考官方 quick install

ffmpeg

视频 / 音频转写前先提取音轨;只处理文字文章时不需要。

1. 获取代码并安装依赖

RakullApp 现在是单一大仓库(monorepo):全部源码(rakull_serverrakullapp_coredocs 等)都直接在仓库里,不再使用子模块。普通 clone 即可拿到所有代码。
git clone git@github.com:AgentEndeavour/RakullApp.git
cd RakullApp
各服务的标准 uv sync / uv run 会安装完整运行时;Agent Server 默认包含 OpenAI 与 Azure Speech SDK,无需额外 extra。转写还需要系统安装 ffmpeg:
# macOS;Linux 请用系统包管理器安装 ffmpeg
brew install ffmpeg
cd rakull_server/agent_server
uv sync
ffmpeg -version
cd ../../
各微服务仍保留独立的 remote 仓库(RakullServerRakullAgentServer 等),但它们现在是下游镜像:只在主仓 PR 合并后由同步脚本自动更新,日常开发不要直接克隆或修改它们。详见仓库模型

2. 一键启动所有后端

bash scripts/run_all_servers.sh
这个脚本会:
  1. 先准备本地 PostgreSQL,再初始化持库服务(幂等):scripts/init_all_dbs.sh 会自动调用 scripts/setup_postgres.sh(前置条件是本机有 Homebrew,本地开发不用 Docker),由它基于 postgresql@18 完成安装 / initdb / 启动,创建 rakull 角色和 rakull_datarakull_collection 两个库;随后 data_server 在这两个物理隔离的库上幂等建 schema——rakull_data 放文章 / 句子 / 媒体 / 反馈 / 每周推荐等内容,rakull_collection 专放收藏域,两库同进程持有但不跨库 JOINuser_manager 不受影响,仍建自己的 SQLite 认证库;管理员账号照旧直接写入 user_manager 的库。
  2. 用同一个 JWT_SECRET 启动六个服务,使签发的 token 在各服务间通用。
  3. 自动注入服务间地址(DATA_SERVER_URL 等)与同一个 CONTROLLER_ASSERTION_SECRET(study/collection 调 data_server 的内部服务断言密钥)。
  4. Ctrl-C 时优雅停止全部服务(每个服务独立进程组,不留孤儿进程)。
启动后的端口分布:
服务端口角色
user_manager8010鉴权 / 管理(面向客户端)
agent_server8011AI 计算(内部)
immersive_study_server8012控制器 / App Server(面向客户端)
collection_server8013收藏薄网关(面向客户端,无状态)
data_server8014持久化 / Model(内部)
manager_server8015Admin Server:管理后台 + 数据采集(面向管理端)
想改端口或密钥?用环境变量覆盖即可,例如 JWT_SECRET=my-secret USER_MANAGER_PORT=9010 bash scripts/run_all_servers.sh。数据库连接可用 DATA_DATABASE_URL / COLLECTION_DATABASE_URL(默认分别为 postgresql://rakull:rakull@localhost:5432/rakull_datapostgresql://rakull:rakull@localhost:5432/rakull_collection;首次使用可先跑 bash scripts/setup_postgres.shinit_all_dbs.sh 本来就会自动调用它)与 LOCAL_AUTH_DATABASE_PATHuser_manager 的 SQLite 认证库)覆盖;collection_server 不读任何数据库变量。CONTROLLER_ASSERTION_SECRET 本地缺失时脚本自动生成随机值,生产/预发环境必须显式提供且各服务一致。

手动安装 PostgreSQL(通常可跳过)

本地开发统一使用本机原生 PostgreSQL(Homebrew,不用 Docker)。正常情况下 run_all_servers.sh 会通过 scripts/setup_postgres.sh 自动完成安装 / initdb / 启动 / 建角色建库,你什么都不用装。想单独准备或排查时,可以手动执行:
brew install postgresql@18        # 已安装则跳过
bash scripts/setup_postgres.sh    # 幂等:启动集群、建 rakull 角色与两个库
psql -h localhost -U rakull -d rakull_data -c 'SELECT 1'   # 验证连通(默认密码 rakull)
脚本默认值:角色 rakull / 密码 rakull、库 rakull_datarakull_collection、连接串 postgresql://rakull:rakull@localhost:5432/...、主版本 PG_MAJOR=18。可用环境变量覆盖:PG_HOST / PG_PORT / PG_ROLE / PG_PASSWORD / PG_SUPERUSER / PG_MAJOR;业务侧用 DATA_DATABASE_URL / COLLECTION_DATABASE_URL 覆盖连接串。注意 user_manager 不使用 PostgreSQL,它仍维护自己的 SQLite 认证库 local_auth.db

从 PostgreSQL 16 升级到 18(一次性)

PostgreSQL 18 无法直接读取 16 的 data directory。setup_postgres.sh 发现端口仍被旧主版本集群占用时会直接报错退出,不会静默写旧库。先停掉后端服务(bash scripts/stop_all_servers.sh),再按本地数据是否需要保留二选一: A. 本地数据可丢弃——schema 会在下次 run_all_servers.sh 时自动重建:
# 停旧集群:brew services 启动的用第一条;pg_ctl 启动的用第二条
brew services stop postgresql@16 2>/dev/null || true
/opt/homebrew/opt/postgresql@16/bin/pg_ctl -D "$(brew --prefix)/var/postgresql@16" stop
brew install postgresql@18
bash scripts/setup_postgres.sh    # initdb 全新 18 集群并建角色 / 两库
B. 保留本地数据——用 pg_upgrade 原地升级整个集群(含 rakull_data / rakull_collection):
brew install postgresql@18
/opt/homebrew/opt/postgresql@16/bin/pg_ctl -D "$(brew --prefix)/var/postgresql@16" stop
NEW="$(brew --prefix)/var/postgresql@18"
/opt/homebrew/opt/postgresql@18/bin/initdb -D "$NEW" -E UTF8 --locale=C
/opt/homebrew/opt/postgresql@18/bin/pg_upgrade \
  -b /opt/homebrew/opt/postgresql@16/bin -B /opt/homebrew/opt/postgresql@18/bin \
  -d "$(brew --prefix)/var/postgresql@16" -D "$NEW" -U "$(id -un)"
bash scripts/setup_postgres.sh
升级后照常 bash scripts/run_all_servers.sh 即可;确认无误前不要删除旧 data directory($(brew --prefix)/var/postgresql@16)。仍需临时跑旧版本时,PG_MAJOR=16 bash scripts/setup_postgres.sh 即可。

从旧版 SQLite 迁移历史数据(一次性)

PostgreSQL 改造后运行时不再读取任何 SQLite 文件。如果你是从旧版升级、本地还留着历史数据,旧文件位于 data_server 的服务根目录(rakull_server/data_server/data.dbcollection.db,不是 data/ 子目录)。按下面步骤一次性搬入 PostgreSQL:
迁移期间必须停掉所有后端服务,避免迁移与运行中的进程同时写库。SQLite 原文件全程只读、不会被修改,确认 PostgreSQL 数据无误前不要删除它们。
# 1) 停掉全部后端
bash scripts/stop_all_servers.sh

# 2) 备份:旧 SQLite 原样复制,目标 PostgreSQL 用 pg_dump 各导一份(强烈建议)
mkdir -p ~/rakull_pg_backup && cp rakull_server/data_server/*.db ~/rakull_pg_backup/
pg_dump -h localhost -U rakull -Fc rakull_data       -f ~/rakull_pg_backup/rakull_data.dump
pg_dump -h localhost -U rakull -Fc rakull_collection -f ~/rakull_pg_backup/rakull_collection.dump

# 3) 执行迁移(目标库必须已经 bootstrap;跑过 run_all_servers.sh 即已完成)
cd rakull_server/data_server
uv run python scripts/migrate_sqlite_to_pg.py \
  --data-sqlite data.db \
  --collection-sqlite collection.db \
  --replace
脚本行为:以只读方式打开 SQLite,按 PostgreSQL schema 中实际存在的表 / 列逐列复制;身份列用 OVERRIDING SYSTEM VALUE 写入并在结尾把序列重置到 max(id);加载期间临时摘除外键、禁用用户触发器,结束后全部恢复;每个库在单事务内完成,任何一行失败都整体回滚。--replace 会先 TRUNCATE 目标表再重灌——首次 bootstrap 已写入情景 / 旅程种子行,因此本地迁移基本都要带它(种子行也包含在旧 SQLite 中)。 更老的仓库(收藏曾独立存放):在收藏域并入 data_server 之前,收藏住在另一个文件 rakull_server/collection_server/collection.db,与 data_server/collection.db 是两套独立数据、各自有一套 id。追加 --legacy-collection-sqlite 即可把它按并集合并进来(脚本会自动重排主键、把卡片指向新的收藏夹 id,两账号数据互不冲突):
uv run python scripts/migrate_sqlite_to_pg.py \
  --data-sqlite data.db \
  --collection-sqlite collection.db \
  --legacy-collection-sqlite ../collection_server/collection.db \
  --replace
完成后重新 bash scripts/run_all_servers.sh 启动,登录客户端确认文章与收藏可见,即可删除或归档旧 .db 文件。迁移脚本是一次性切换工具,不是长期兼容层;全新环境没有历史数据,直接跳过本小节。登录账号始终在 user_manager 的 SQLite 中,不参与本次迁移。

3. 在管理后台配置 AI、语音、COS 与用户

六个服务启动后,打开管理后台并以默认管理员 admin / admin123 登录:
open http://localhost:8015
  • AI APIs → Models:添加 OpenAI-compatible Endpoint 与 API Key,并创建至少一个 Model Binding。首个可用模型会自动成为默认模型;CallPlan 仅用于指定不同模型之间的默认与 Fallback。Endpoint 连接测试使用当前未保存表单;保存会先实测连接,只有成功才写入。
  • AI APIs → Speech & automation:配置 Whisper Key / Base URL / Model、Azure TTS Key / Region / Voice,运行真实测试,并设置文章自动 TTS 开关。Whisper 测试音频上限为 5 MB;Azure 测试会合成固定日文短句,可能产生少量费用。
  • COS storage:配置 COS 凭证、Bucket 与 Region,执行 Bucket、PUT、HEAD、GET、LIST、DELETE 六步探测,再管理 Rakull 对象。
  • Users / Requests:管理账号并审批待注册用户。
密钥只写不回显。在 AI APIs → Models 中,API Key 留空表示当前候选没有 Key,填写即覆盖;独立测试不保存,Endpoint 保存会先测试并只在成功后覆盖。在 COS storage 中,测试只使用当前表单而不保存;保存会覆盖当前配置,空凭证按清空处理。运行时设置对后续请求热生效,无需重启。部署环境变量和 YAML 仅作为高级回退,统一优先级为:后台运行时设置 > 环境变量 > config.local.yaml > config.yaml > 默认值
Whisper 与 Azure 按钮会向真实供应商发请求;候选配置无需先保存,但测试可能产生费用。错误消息会脱敏;语音配置保存不会自动测试或重试。
运行时设置保存在各服务的 gitignored .data/ 文件中,适合单 worker / 单副本。本地以外的多副本部署需要共享一致的持久化配置。详见 agent_server 配置对象存储

管理账号与用户审批

管理员账号在初始化时直接写入数据库user_managerapp/db/seed.py,幂等),默认 admin / admin123,可用环境变量 ROOT_ADMIN_USERNAME / ROOT_ADMIN_PASSWORD 覆盖。不再有“第一个注册用户自动成为 root”的行为 —— 所有注册都会进入待审批队列,由管理员批准。schema 里预置了三个邀请码:zywaizx 用管理员账号登录拿到 token:
curl -X POST http://localhost:8010/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"admin123"}'
返回中会带上 token,可直接用于后续请求:
curl http://localhost:8010/api/auth/me \
  -H "Authorization: Bearer <token>"
懒得手动复制 token?用脚本一步到位:TOKEN=$(scripts/admin_token.sh) —— 它会登录、用 /api/auth/me 校验,并把 token 打到标准输出(便于直接捕获复用),身份信息打到标准错误。
普通用户注册后处于待审批状态(不再直接返回 token),由管理员在管理后台审批:
curl -X POST http://localhost:8010/api/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"alice","password":"alice123","invite_code":"zy"}'
# -> {"message":"Registration submitted, awaiting admin approval"}

4. 运行 Flutter 客户端

rakullapp_core/ 目录下,通过 --dart-define 把三个面向客户端的服务地址传进去:
cd rakullapp_core
flutter pub get
flutter run -d chrome \
  --dart-define=USER_MANAGER_API_BASE=http://localhost:8010 \
  --dart-define=STUDY_API_BASE=http://localhost:8012 \
  --dart-define=COLLECTION_API_BASE=http://localhost:8013
客户端只直连这三个服务,永远不会直接访问内部的 agent_server / data_server

可选:把管理后台打包为 App

也可以把控制台打包成桌面 / 移动端 App:rakull_manager 是一个只做 WebView 的 Flutter 壳,指向上面的控制台地址。
cd rakull_manager
flutter create --project-name rakull_manager --platforms=android,ios,macos,windows,linux,web .
flutter pub get
flutter run --dart-define=MANAGER_CONSOLE_URL=http://localhost:8015
详见 Admin Server

下一步

架构总览

理解 MVC 分层与服务职责。

贡献流程

创建分支、测试并通过 PR 提交改动。