
用1Panel和Halo搭建自己的LobeChat 服务端数据库版本
本教程适用于拥有自己的域名并且通过Halo部署好了自己的个人博客,想实现在线LobeChat服务的场景
LobeChat官方给了两种部署方式:
客户端数据库的模式,该模式的优势是一行指令 / 一个按钮即可快捷完成部署,便于你快速上手与体验 LobeChat,但是数据均保留在用户本地,不会跨多端同步,也不支持文件上传、知识库等进阶功能。
服务端数据库的模式,部署稍复杂,支持多端用户,三方认证,知识库等,比客户端数据库模式功能强大得多
按照官方文档的要求:
一般来讲,想要完整的运行 LobeChat 数据库版本,你需要至少拥有如下四个服务
LobeChat 数据库版本自身
带有 PGVector 插件的 PostgreSQL 数据库
支持 S3 协议的对象存储服务
受 LobeChat 支持的 SSO 登录鉴权服务
开始部署前,喜欢此项目可以去点个星
安装 Casdoor 身份验证服务
从1panel商店中搜索casdoor,默认参数安装即可
在网站中用OpenResty创建一个反向代理,代理地址填为本地8000端口对应casdoor服务
通过反代域名打开casdoor管理平台,默认账号admin密码123,登录后记得修改下管理员的默认密码。点击用户管理组织,创建一个新的组织lobe-chat。密码类型选择bcrypt,其他参数按照个人喜好来。默认应用这里先空着,我们稍后创建应用后才能关联。
打开身份认证应用,新建一个应用为lobe-chat用于认证。组织选择刚才创建的lobe-chat,记录好客户端ID和客户端密钥。重定向url格式为:http(s)://你的lobechat域名/api/auth/callback/casdoor,关闭注册功能。由于我做了邮箱认证所以登录方式多选了一个验证码,如果只需要用户密码的话可以删除。关于邮箱认证可以参考casdoor的文档。
https://casdoor.org/zh/docs/provider/email/overview 另外lobechat提供一些还有一些不必需但是可以提高用户体验的字段,详情见:
https://lobehub.com/zh/docs/self-hosting/advanced/auth/next-auth/casdoor
注意下此处我们还没部署好minio,还有webhook,用户同步还没做。
安装MinIO
从1panel商店中搜索,安装默认参数安装。安装好后参数中会有用户密码,9001端口是webui的,9000是api的。同样的创建两个反代,webui配置完毕后就可以关闭了避免暴露在公网。
创建一个桶名字为lobechat,权限改为public。
然后创建access key,创建好后会自动下载一个json文件,保存好。
安装带有 PGVector 插件的 PostgreSQL 数据库
在1panel容器界面,点击镜像,拉取镜像pgvector/pgvector:pg17。如果拉取失败配置下docker镜像加速https://docker.1panel.live,此操作会重启docker服务。
启动容器,按照如下设置,ip可以不填会自动分配一个空闲ip。挂载目录:本机/data/1panel/apps/postgresql/my-postgre/data,容器:/var/lib/postgresql/data。我的1panel安装目录在/data,请自行修改。环境变量:POSTGRES_PASSWORD=自行定义
PGDATA=/var/lib/postgresql/data
添加postgresql,并创建lobechat数据库。
安装LobeChat 数据库版本
1panel容器界面,点击镜像,拉取镜像lobehub/lobe-chat-database:latest。
启动容器,部分参数如下。
重点说下环境变量,给大家一个模板参考:
#lobechat的域名,http(s)根据自己实际修改 APP_URL=https://chat.xxx.cn #加密秘钥可以不变 KEY_VAULTS_SECRET='dvy9O/6gMcy8k7Jnfbb1mI8he03efDQ6m0iknR8XppQ=' #postgresql数据库连接信息,postgres://用户名:密码@数据库地址(此处用容器名解析):端口号/数据库名称 DATABASE_URL=postgres://mylobehub:xxx@postgres:5432/mylobehub #加密秘钥可以不变 NEXT_AUTH_SECRET=3904039cd41ea1bdf6c93db0db96e250 #sso认证类型指定casdoor NEXT_AUTH_SSO_PROVIDERS=casdoor #在配置casdoor中获取的客户端id和客户端秘钥 AUTH_CASDOOR_ID=xxx AUTH_CASDOOR_SECRET=xxx #在配置casdoor中创建的反代域名 AUTH_CASDOOR_ISSUER=https://auth.xxx.cn #app_url拼接/api/auth生成 NEXTAUTH_URL=https://chat.xxx.cn/api/auth #casdoor上的webhook用于更新用户信息的秘钥 CASDOOR_WEBHOOK_SECRET=xxxx #开启认证debug NEXT_AUTH_DEBUG=1 #在配置minio时获取的access key S3_ACCESS_KEY_ID=xxx S3_SECRET_ACCESS_KEY=xxx #在配置minio中创建的反代域名(API接口) S3_ENDPOINT=https://s3.xxx.cn S3_BUCKET=lobechat #同api端点 S3_PUBLIC_DOMAIN=https://s3.xxxx.cn #必须,否则文件上传时请求地址会变为https://S3_BUCKET.s3.xxx.cn S3_ENABLE_PATH_STYLE=1 #上传的图片存储为base64格式 LLM_VISION_IMAGE_USE_BASE64=1 #lobechat有联网搜索,可以通过部署searxng容器实现,这里是指定searxng容器 SEARXNG_URL=http://searxng:8080
查看容器日志,如下就启动成功了。
[Database] Start to migration... ✅ database migration pass. ------------------------------------- ▲ Next.js 14.x.x - Local: https://localhost:3210 - Network: http://0.0.0.0:3210 ✓ Starting... ✓ Ready in 95ms
到此lobechat服务端数据库版本就部署完毕了,通过OpenResty反向代理配置,将lobechat、minio、casdoor内部端口和域名进行绑定就可以使用了。
小知识:可以申请一个域名后,通过泛域名*.domain解析到自己服务器。后续新建网站只需要将*替换,比如lobechat.domain.cn、minio.domain.cn等,同理ssl证书申请也是一样的。
还可以在halo中新建一个页面,将lobechat的链接放入网页中实现在线工具。参考:
效果如下:
后面扩展下,由于lobechat、casdoor、halo分别是三个不同的数据库,需要对数据库进行同步。
配置数据库触发器
我们将halo的数据库作为源数据库进行同步。
在halo数据库新建一张表users
CREATE TABLE users ( id CHAR(36) DEFAULT (UUID()) PRIMARY KEY COMMENT 'UUID 主键', name VARCHAR(255) NOT NULL UNIQUE COMMENT '用户名', email VARCHAR(255) NOT NULL COMMENT '邮箱', password VARCHAR(255) NOT NULL COMMENT '密码哈希', display_name VARCHAR(255) NULL COMMENT '显示名称', avatar VARCHAR(255) NULL COMMENT '头像路径', created_time DATETIME DEFAULT CURRENT_TIMESTAMP NULL COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
创建三个触发器,插入、更新、删除,用于从源表extensions表中同步用户到users。
BEGIN -- 仅处理有效用户数据 IF NEW.name LIKE '/registry/users/%' AND JSON_VALID(CONVERT(NEW.data USING latin1)) AND JSON_UNQUOTE(JSON_EXTRACT(CONVERT(NEW.data USING latin1), '$.spec.password')) IS NOT NULL THEN -- 解析 JSON 数据 SET @data_latin1 = CONVERT(NEW.data USING latin1); SET @name = JSON_UNQUOTE(JSON_EXTRACT(@data_latin1, '$.metadata.name')); SET @email = JSON_UNQUOTE(JSON_EXTRACT(@data_latin1, '$.spec.email')); -- 移除密码中的 {bcrypt} 前缀(假设前缀固定为 8 字符) SET @password = SUBSTRING( JSON_UNQUOTE(JSON_EXTRACT(@data_latin1, '$.spec.password')), 9 -- 从第 9 个字符开始截取(跳过 '{bcrypt}') ); SET @display_name = JSON_UNQUOTE(JSON_EXTRACT(CONVERT(NEW.data USING utf8mb4), '$.spec.displayName')); SET @avatar = JSON_UNQUOTE(JSON_EXTRACT(@data_latin1, '$.spec.avatar')); SET @created_time = CONCAT( SUBSTRING_INDEX( JSON_UNQUOTE(JSON_EXTRACT(@data_latin1, '$.metadata.creationTimestamp')), '.', 1 ), '+08:00' ); -- 插入或更新 users 表 INSERT INTO users (name, email, password, display_name, avatar, created_time) VALUES (@name, @email, @password, @display_name, @avatar, @created_time) ON DUPLICATE KEY UPDATE email = VALUES(email), password = VALUES(password), display_name = VALUES(display_name), avatar = VALUES(avatar), created_time = VALUES(created_time); END IF; END
BEGIN -- 仅处理路径为 '/registry/users/%' 的有效记录 IF NEW.name LIKE '/registry/users/%' AND JSON_VALID(CONVERT(NEW.data USING latin1)) AND JSON_UNQUOTE(JSON_EXTRACT(CONVERT(NEW.data USING latin1), '$.spec.password')) IS NOT NULL THEN -- 解析旧数据(用于处理主键变更) SET @old_data_latin1 = CONVERT(OLD.data USING latin1); SET @old_name = JSON_UNQUOTE(JSON_EXTRACT(@old_data_latin1, '$.metadata.name')); -- 解析新数据(移除密码前缀) SET @new_data_latin1 = CONVERT(NEW.data USING latin1); SET @new_name = JSON_UNQUOTE(JSON_EXTRACT(@new_data_latin1, '$.metadata.name')); SET @new_email = JSON_UNQUOTE(JSON_EXTRACT(@new_data_latin1, '$.spec.email')); -- 关键修改:移除 {bcrypt} 前缀 SET @new_password = SUBSTRING( JSON_UNQUOTE(JSON_EXTRACT(@new_data_latin1, '$.spec.password')), 9 -- 从第9个字符开始截取 ); SET @new_display_name = JSON_UNQUOTE(JSON_EXTRACT(CONVERT(NEW.data USING utf8mb4), '$.spec.displayName')); SET @new_avatar = JSON_UNQUOTE(JSON_EXTRACT(@new_data_latin1, '$.spec.avatar')); SET @new_created_time = CONCAT( SUBSTRING_INDEX( JSON_UNQUOTE(JSON_EXTRACT(@new_data_latin1, '$.metadata.creationTimestamp')), '.', 1 ), '+08:00' ); -- 如果用户名变更,先删除旧记录 IF @old_name != @new_name THEN DELETE FROM users WHERE name = @old_name; END IF; -- 插入或更新新记录(已处理密码前缀) INSERT INTO users (name, email, password, display_name, avatar, created_time) VALUES (@new_name, @new_email, @new_password, @new_display_name, @new_avatar, @new_created_time) ON DUPLICATE KEY UPDATE email = VALUES(email), password = VALUES(password), display_name = VALUES(display_name), avatar = VALUES(avatar), created_time = VALUES(created_time); END IF; END
BEGIN -- 仅处理路径为 '/registry/users/%' 的有效记录 IF OLD.name LIKE '/registry/users/%' AND JSON_VALID(CONVERT(OLD.data USING latin1)) AND JSON_UNQUOTE(JSON_EXTRACT(CONVERT(OLD.data USING latin1), '$.spec.password')) IS NOT NULL THEN -- 解析被删除记录的 name SET @data_latin1 = CONVERT(OLD.data USING latin1); SET @name = JSON_UNQUOTE(JSON_EXTRACT(@data_latin1, '$.metadata.name')); -- 删除 users 表中的对应记录 DELETE FROM users WHERE name = @name; END IF; END
安装dbsyncer
在1panel容器中点击仓库,添加仓库
点击镜像,拉取xhtb/dbsyncer:latest
启动容器,需要提前准备mysql数据库避免容器升级后配置文件消失,见手册:
https://gitee.com/ghi/dbsyncer/wikis/%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C/%E7%A4%BE%E5%8C%BA%E7%89%88%E5%AE%89%E8%A3%85 登录dbsyncer添加数据库连接,halo、casdoor、lobechat。自行修改账号密码和数据库名称。
配置驱动,halo→casdoor,只需要同步删除事件,新增和更新使用casdoor自带的同步功能。选择增量同步,表名保持一致。在高级设置中只启用删除。
回到casdoor,配置同步器,自行修改主机、用户、密码、数据库。
同步字段参数如下,其他保持一致,头像url为个人博客的地址,halo头像url为个人网站/upload/xxx,同步过来的avatat只存储/upload/xxx这部分。只一定要开启,避免反向同步。
点击webhook,目前只支持update-user这个事件,删除事件需要用到dbsyncer。casdoor和lobechat容器同在一个bridge,链接我直接使用容器名了,这里也可以使用lobechat的域名,casdoor-secret就是上文中lobechat环境变量中的webhook秘钥。事件选择update-user。
接下来配置halo→lobechat,同样只需要删除事件。
保存完毕后回到主页面,将驱动运行起来,用户同步就做好了。在halo上新建更新删除用户可以实时同步到casdoor和lobechat。