本指南使用 Alibaba Cloud Linux 3,不使用 Docker。六个 Python 服务由 systemd 管理,Caddy 提供公网 HTTPS,媒体和数据库快照沿用腾讯 COS。data_server 的两个数据库可以使用本机 PostgreSQL 集群或阿里云 RDS;推送到配置分支的更新可由 GitHub Webhook 自动部署。两种部署形态与自动更新链路的总体设计见部署形态与自动更新链路。后端支持 x86_64 和 ARM64;本指南的服务器端 Flutter Web 构建只支持 x86_64。安装脚本会拒绝其他系统或不受支持的架构,避免误用系统包和 SDK。
域名到底映射到哪个端口?
DNS 的 A 记录只把域名解析成 IP,不指定端口。访问 https://admin.dev.rakull.com 时,浏览器默认连接服务器的 443 端口。Caddy 根据请求域名选择后端,将它转发到同机的 http://127.0.0.1:8015。用户不需要写 :8015。
公网到 Caddy 使用 HTTPS;同机服务之间使用 HTTP。80 端口用于 HTTP 跳转和证书验证;443 提供 HTTPS。证书申请和续期由 Caddy 自动 HTTPS 管理。
1. 准备 DNS 与服务器网络
在 rakull.com 的 DNS 控制台添加以下 A 记录,记录值全部填写服务器公网 IPv4:
| 主机记录 | 完整域名 | Caddy 的本机目标 |
|---|
dev | dev.rakull.com | /var/www/rakull-dev/current |
auth.dev | auth.dev.rakull.com | 127.0.0.1:8010 |
study.dev | study.dev.rakull.com | 127.0.0.1:8012 |
collection.dev | collection.dev.rakull.com | 127.0.0.1:8013 |
admin.dev | admin.dev.rakull.com | 127.0.0.1:8015 |
deploy.dev | deploy.dev.rakull.com | 127.0.0.1:8016(仅启用 Webhook 自动部署时) |
第六条记录仅用于 GitHub Webhook 自动部署;不启用该功能时可不配置。不要配置指向其他主机的 AAAA 记录。为各域名单独申请证书,不依赖通配符证书或 DNS API 密钥。
阿里云安全组开放入站 TCP 80、443;SSH 端口只允许运维来源。8010–8016 不开放公网。若系统运行 firewalld,在服务器执行:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
仅在 firewalld 已启用时执行这些命令,不要为了网站部署改变现有 SSH 规则。公网网站需满足所在地域的域名接入要求后再进行上线验证。
在本地检查解析(将 SERVER_IP 替换为真实 IP):
for domain in dev.rakull.com auth.dev.rakull.com study.dev.rakull.com collection.dev.rakull.com admin.dev.rakull.com; do
dig +short A "$domain"
done
ssh YOUR_SSH_USER@SERVER_IP
2. 安装服务器运行环境
以下命令在服务器执行。仓库为私有时,先配置只读 Git 访问凭据,不要把令牌写进命令 URL。检出包含部署脚本的已审核提交,后续示例用 REVIEWED_COMMIT_SHA 表示其完整 40 位 SHA。
cat /etc/os-release
uname -m
sudo dnf install -y git
sudo git clone https://github.com/AgentEndeavour/RakullApp.git /opt/rakull-dev
cd /opt/rakull-dev
sudo git checkout --detach REVIEWED_COMMIT_SHA
sudo bash scripts/deployment/install.sh # 本机 PostgreSQL 集群
sudo bash scripts/deployment/install.sh --database-backend rds # 或:使用 RDS
安装脚本安装编译依赖、Azure Speech SDK 所需的 alsa-lib、固定版本 uv 0.12.12 和 Caddy 2.11.4、Python 3.11,并创建 rakull-dev、rakull-deploy、caddy 三个系统账号,同时安装 rakull-deploy 的 sudoers 片段与 Webhook 接收服务所需的状态目录。默认 local 后端安装 postgresql-server,仅当 /var/lib/pgsql/data/PG_VERSION 不存在时执行 postgresql-setup --initdb,再 systemctl enable --now postgresql;只装集群,不建角色或业务库。--database-backend rds 时只从 PGDG 镜像安装 PostgreSQL 18 客户端(psql、pg_dump),不安装、不初始化本机集群;已存在的 /etc/rakull-dev/deployment.env 中 DATA_DATABASE_BACKEND 会作为重跑脚本时的默认值。Caddy 下载包核对官方 SHA-512;脚本不自动开放防火墙、不启动网站。安装需要访问官方发行源;失败后先解决网络问题再重试。
| 内容 | 实际位置 |
|---|
| 代码和各服务虚拟环境 | /opt/rakull-dev |
| 数据库、上传文件、管理台运行配置 | /var/lib/rakull-dev |
| 域名配置 | /etc/rakull-dev/deployment.env |
| 部署密钥 | /etc/rakull-dev/secrets.env |
| 生成的公共/服务配置 | /etc/rakull-dev/runtime.env、<service>.env |
| HTTPS 配置 | /etc/caddy/Caddyfile |
| Web 发布版本 | /var/www/rakull-dev |
| systemd 服务 | /etc/systemd/system/rakull-dev-*.service |
| Webhook 密钥、锁与部署状态 | /var/lib/rakull-deploy(webhook.secret、deploy.lock、deploy-status.json) |
| 自动部署日志 | /var/log/rakull-deploy/deploy-<SHA前12位>.log |
| Python、Flutter SDK 与 Web 构建产物 | /opt/rakull-runtime(python、flutter、web-build) |
编辑域名和密钥,密钥文件使用 root:rakull-dev、0640 权限。文件格式为 KEY=value,包含空格的值需加引号;不支持 shell 命令或变量展开。Alibaba Cloud Linux 部署模板还在 deployment.env 中提供以下国内下载源:
UV_DEFAULT_INDEX=https://mirrors.aliyun.com/pypi/simple/
FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
PUB_HOSTED_URL=https://pub.flutter-io.cn
UV_DEFAULT_INDEX 用于 Python 包;后两项分别用于 Flutter SDK/构建产物和 Dart、Flutter 包,来自 Flutter 中国网络环境官方说明。可替换为其他公开 HTTPS 镜像;UV_DEFAULT_INDEX 留空时使用锁文件原始下载地址。不要在这些 URL 中写用户名、密码或令牌。
sudoedit /etc/rakull-dev/deployment.env
openssl rand -hex 32
openssl rand -hex 32
sudoedit /etc/rakull-dev/secrets.env
sudo ./scripts/rakull_dev.sh prepare
sudo ./scripts/rakull_dev.sh configure
两个随机值分别填入 JWT_SECRET、CONTROLLER_ASSERTION_SECRET,不能相同;secrets.env 还必须设置 DATA_DATABASE_PASSWORD。设置管理员用户名和强密码,填写当前媒体所在桶的 COS 凭据、桶名和地域。prepare 为六个服务执行锁定的完整运行时依赖安装;agent_server 的默认依赖已包含 Azure Speech 与 OpenAI,无需 extra,并在结束前验证两个 SDK 与系统 ALSA 库可加载。配置镜像时,它先从各服务的 uv.lock 导出带哈希的生产依赖,通过镜像预安装,再运行 uv sync --frozen 校验最终环境。这个流程不会改写锁文件,镜像返回的文件也必须匹配已提交的哈希。旧服务器若已有 deployment.env,需要按需手动添加上述三个镜像变量;安装脚本不会覆盖现有配置。configure 校验五个业务域名互不相同、必填密钥齐备且 JWT_SECRET 与 CONTROLLER_ASSERTION_SECRET 为两个不同的 32 字符以上随机值,然后生成 Caddy、systemd、CORS 和数据路径,最后用 caddy validate 与 systemd-analyze verify 校验生成结果。PostgreSQL 部分按 DATA_DATABASE_BACKEND 分叉:local 时以 postgres 系统用户经 psql(密码走 stdin,不进 argv)幂等 CREATE/ALTER ROLE rakull LOGIN PASSWORD 为 DATA_DATABASE_PASSWORD,创建属主为 rakull 的 rakull_data、rakull_collection 两个库;rds 时不在本机建库,而是先 TCP 连通 DATA_DATABASE_HOST:DATA_DATABASE_PORT,再以应用账号对两个库各执行一次 SELECT 1,失败则提示先运行 init-rds。使用 Caddy 专用服务 rakull-dev-caddy,不要同时启动另一个占用 80/443 的 Web 服务。
这是公网开发版,所以使用 RAKULL_ENV=production 启用安全检查;数据库和域名仍与正式版分开。默认保留原始 API 路径和流式代理。
RDS 托管数据库初始化(仅 rds 后端)
DATA_DATABASE_BACKEND 在 deployment.env 中取值 local(默认)或 rds。使用 RDS 时,deployment.env 还需设置 DATA_DATABASE_HOST 与 DATA_DATABASE_PORT,secrets.env 中设置 DATA_DATABASE_USER(默认 rakull)与 DATA_DATABASE_PASSWORD。rds 模式只安装客户端:pg_dump 对更新的主版本服务器会直接失败,因此客户端必须与 RDS 主版本匹配,安装脚本固定装 PGDG 的 PostgreSQL 18(/usr/pgsql-18/bin),不初始化本机集群。
首次接入 RDS 需要一个高权账号一次性创建应用角色与两个业务库。这个账号的密码不写入任何文件、不进命令行参数:默认通过交互式输入读取并经临时 PGPASSFILE(0600,用完即删)传递;自动化场景可用 --super-password-file 指定仅 root 可读的 0600 单文件,脚本会校验权限,用后应删除该文件。
cd /opt/rakull-dev
sudo bash scripts/deployment/install.sh --database-backend rds
sudoedit /etc/rakull-dev/deployment.env # DATA_DATABASE_BACKEND=rds、HOST、PORT
sudoedit /etc/rakull-dev/secrets.env # DATA_DATABASE_USER、DATA_DATABASE_PASSWORD
sudo ./scripts/rakull_dev.sh init-rds # 交互输入高权账号与密码;或加 --super-user/--super-password-file
sudo ./scripts/rakull_dev.sh configure
sudo ./scripts/rakull_dev.sh prepare
sudo ./scripts/rakull_dev.sh migrate
init-rds 幂等创建/更新 rakull 角色并创建属主为该角色的 rakull_data、rakull_collection,结束时打印 RDS 服务端版本。它要求 secrets.env 已设置非占位密码,且角色名符合 PostgreSQL 标识符规则。高权账号只在这一步使用,之后的日常运行只持应用账号密码。RDS 网络侧需允许 ECS 内网地址访问 5432;configure 的连通性与 SELECT 1 校验可同时验证账号、密码与白名单。
3. 在原开发机上传数据库到 COS(不要在新服务器执行)
本节只在仍持有原始数据库的开发机执行,不是在刚安装好的 /opt/rakull-dev 新服务器执行。“本地”指原开发机,而不是当前所在目录。若服务器执行 verify --source server --channel bootstrap --latest 已返回 "ok": true,说明可用 bootstrap 快照已经存在,请整节跳过并直接进入第 4 节。
媒体已在 COS 不等于数据库已备份。 快照覆盖业务、认证与收藏三类库,按引擎分别处理:data_server 的 rakull_data、rakull_collection 用 pg_dump -Fc 导成自定义格式(data.pgdump / collection.pgdump),user_manager 的 local_auth.db、user.db、rakull.db 仍用 SQLite Backup API,并顺带检查同目录的额外 SQLite 文件;清单 manifest 用 engine 字段(postgresql / sqlite)标注每个库。user_manager 若配置了外部或内存 SQLite URL 不在覆盖范围、会明确报错,必须另行备份。不要在备份期间运行其他写数据库的脚本。
退出服务器 SSH,或另开原开发机终端,然后在原开发机的仓库根目录停止本地服务,确保各服务已有虚拟环境。scripts/stop_all_servers.sh 是本地开发栈脚本并依赖 lsof;服务器停止 systemd 服务应使用 sudo ./scripts/rakull_dev.sh stop。脚本读取本地 data_server 的两个 PostgreSQL 连接、user_manager 的 SQLite 库路径和 COS 管理台配置,不复制凭据。下文的 /safe/path 是路径占位符,必须替换为原开发机上真实存在且只有操作者可读写的目录,不能原样粘贴。
bash scripts/stop_all_servers.sh
./scripts/db_snapshot.sh audit-media --source local --output /safe/path/cos-audit.json
audit-media 会分页比较当前 PostgreSQL 内容库 rakull_data(media_objects)、COS 对象和两个快照频道中的历史数据库,报告库内 BYTEA、数据库引用但 COS 缺失的对象、生成媒体孤儿、临时对象及受保护的未跟踪上传。只有 ready_for_snapshot=true 才进入 push。若报告中的缺失对象全部是可重新生成的固定回复音频,可先保留回滚库并重置其缓存状态,再通过正常情景音频接口重新生成:
./scripts/db_snapshot.sh repair-missing-fixed --source local \
--report /safe/path/cos-audit.json \
--rollback /safe/path/data-before-fixed-audio-repair.pgdump --apply
# 启动本地服务,通过正常的情景回复变体音频 POST 接口重新生成后,再次 audit-media。
其他缺失类型不会被自动修复。确认引用缺失为零后,以预演、实际迁移、再次审计的顺序把库内本地 BYTEA 清空到 COS;迁移清单不含密钥:
cd rakull_server/data_server
.venv/bin/python scripts/migrate_to_cos.py --dry-run \
--manifest /safe/path/cos-migration-dry-run.json
.venv/bin/python scripts/migrate_to_cos.py \
--manifest /safe/path/cos-migration-applied.json
cd ../..
./scripts/db_snapshot.sh audit-media --source local \
--output /safe/path/cos-audit-ready.json
预演或实际迁移出现平票、缺失源或校验错误时立即停止并检查清单,不要绕过。ready_for_snapshot=true 后上传、列出、验证,再拉到一个新的隔离目录:
./scripts/db_snapshot.sh push --source local --channel bootstrap
./scripts/db_snapshot.sh list --source local --channel bootstrap
./scripts/db_snapshot.sh verify --source local --channel bootstrap --latest
./scripts/db_snapshot.sh pull --source local --channel bootstrap --latest \
--output /safe/path/rakull-bootstrap
详见对象存储指南。自定义端口时保留相同的 *_PORT 环境变量;也要停止自定义启动的其他写入进程。data_server 两库用 pg_dump -Fc 导成自定义格式副本,user_manager 各库用 SQLite Backup API 获取包含 WAL 已提交数据的单文件副本;之后统一检查完整性、媒体引用和 SHA-256。存在本地上传文件或缺失的远程媒体时拒绝发布,不能直接删除未确认用途的本地文件。
COS 桶必须保持私有,不配置允许匿名读取的桶策略;快照对象显式使用私有 ACL。COS 目录为 backups/bootstrap/<snapshot-id>/。每个数据库上传后回读校验,最后发布 manifest.json 完成标记和 latest.json。失败的上传不会替代上一份成功快照。首次迁移完成后保留输出的快照 ID。
list 即使频道为空也会返回 snapshot_count: 0,并列出不完整目录或悬空 latest。verify 在临时目录下载并验证后立即丢弃;pull 将同一份已验证内容原子写入指定的新目录,若目录非空则拒绝覆盖。拉取只用于离线核验,不会替换任何运行数据库。
必须在成功上传并拉回验证快照之后再清理。清理后再次审计,保留两份报告和 .prune.json 结果:
./scripts/db_snapshot.sh audit-media --source local \
--output /safe/path/cos-audit-pre-prune.json
./scripts/db_snapshot.sh prune-media --source local \
--report /safe/path/cos-audit-pre-prune.json \
--temporary-older-than-days 7 --apply
./scripts/db_snapshot.sh audit-media --source local \
--output /safe/path/cos-audit-final.json
4. 回到新服务器恢复与配置迁移
切换回新服务器的 /opt/rakull-dev。首次恢复必须在初始化/启动服务之前;先用服务器配置列出并独立验证 bootstrap 快照,只有 verify 返回 "ok": true 才继续恢复:
cd /opt/rakull-dev
sudo ./scripts/rakull_dev.sh stop
sudo ./scripts/db_snapshot.sh list --source server --channel bootstrap
sudo ./scripts/db_snapshot.sh verify --source server --channel bootstrap --latest
sudo ./scripts/db_snapshot.sh restore --target server --channel bootstrap --latest
sudo ./scripts/rakull_dev.sh migrate
sudo ./scripts/rakull_dev.sh set-admin-password
恢复会先下载、验证,再按引擎还原:data_server 两库用 pg_restore --clean --if-exists 整库还原(不是合并),user_manager 的 SQLite 库整文件替换;完成后保持服务停止。已有数据库时默认拒绝覆盖;只有明确要替换时才增加 --replace,原数据库保存在 /var/lib/rakull-dev/rollback/。迁移前的旧 SQLite 版 data_server 快照仍可 list / verify,但 restore 会被拒绝,并提示先在 rakull_server/data_server 下运行 scripts/migrate_sqlite_to_pg.py 完成一次性迁移。
ROOT_ADMIN_PASSWORD 只用于新账号播种,不会自动修改恢复库中的已有密码。set-admin-password 显式更新配置指定的已有 root 账号密码。启动前会拒绝仍使用 admin123 的 root 账号;若有多个这种账号,逐一指定用户名并修改。
数据库包不含运行配置和密钥。除 secrets.env 外,按需通过 SSH 安全复制以下本地文件,或在管理台重新配置:
| 本地来源 | 服务器目的位置 |
|---|
rakull_server/agent_server/.data/model-routing.json | /var/lib/rakull-dev/agent_server/model-routing.json |
rakull_server/agent_server/.data/speech-config.json | /var/lib/rakull-dev/agent_server/speech-config.json |
rakull_server/data_server/.data/cos-config.json | /var/lib/rakull-dev/data_server/cos-config.json |
这些文件可能含密钥,不能提交到 Git 或放到 Web 发布目录。服务器文件归 rakull-dev:rakull-dev,权限 0600。COS 运行配置优先于环境变量;使用环境配置时无需复制 COS JSON。恢复要求媒体桶名和地域与快照一致。若管理台以后修改了 COS 桶,还需同步 Agent 的 TRANSCRIBE_SOURCE_HOSTS;初始配置会自动允许当前 COS 桶域名。
5. 在服务器构建 Web 并启动 HTTPS
以下步骤全部在 x86_64 新服务器执行,不需要把配置或 build/web 在开发机与服务器之间来回复制。先确认 /opt 所在文件系统至少有 8 GiB 可用空间,并建议构建时至少有 4 GiB 可用内存与交换空间之和。Flutter 3.47.1 Linux SDK 下载包约 1.5 GB,解压、缓存和 Web 构建还需要额外空间。
cd /opt/rakull-dev
df -h /opt
sudo bash scripts/deployment/install_flutter.sh
sudo ./scripts/rakull_dev.sh build-web
sudo ./scripts/rakull_dev.sh publish-web
sudo ./scripts/rakull_dev.sh start
sudo ./scripts/rakull_dev.sh status
install_flutter.sh 固定安装 Flutter 3.47.1,从 CFUG 国内镜像断点续传,并用固定 SHA-256 校验完整下载包;已正确安装时可安全重跑。下载包保留在 /var/cache/rakull-dev/,SDK 安装到 /opt/rakull-runtime/flutter/。脚本拒绝替换来源不明或版本不同的现有 SDK。
build-web 直接读取 /etc/rakull-dev/deployment.env,自动生成三个公开 API 的编译参数,在 /opt/rakull-runtime/build/ 的隔离源码副本中通过国内源执行 flutter pub get 和 release Web 构建。这样既能处理 Flutter 3.47.1 自带 SDK 包与旧锁文件之间的解析变化,也不会修改服务器检出的 pubspec.lock;成功产物写入 /opt/rakull-runtime/web-build,域名配置不会复制到发布目录。publish-web 默认发布该产物,原子切换静态文件软链接并保留旧版本。start 启动六个服务并检查健康接口,再启用开机启动和 HTTPS。DNS 与网络就绪后 Caddy 自动申请证书。打开 https://dev.rakull.com 和 https://admin.dev.rakull.com。
构建命令关闭仅用于兼容性提示的 Wasm dry run,以减少低配服务器上的并发内存占用;同时禁用 Web 运行时 CDN,把 CanvasKit 等引擎资源随站点发布,避免国内浏览器因无法访问 gstatic.com 而白屏。若 dart2js 以 exit code -9 退出,先用 journalctl -k 确认 OOM,再停止后端并增加交换空间后重试;不要把 record-use 或 root 提示误判为编译错误。
完整的无上传流程目前只支持 x86_64,因为 Flutter 3.47.1 官方 Linux SDK 没有本指南可校验的 ARM64 发行包。后端和 Caddy 仍支持 ARM64,但不要在 ARM64 机器上绕过安装脚本使用未知 SDK。
GitHub Webhook 自动部署
自动部署在机上由一个仅监听 127.0.0.1:8016 的接收服务(scripts/deployment/webhook_receiver.py,systemd 单元 rakull-dev-webhook,运行用户 rakull-deploy)经 Caddy 的 deploy 域名对外提供服务。它通过 HMAC-SHA256 校验 GitHub 请求,再以 sudoers 白名单中的唯一命令触发 remote-update,接收服务本身不持有 root 权限。总体链路与安全边界见部署形态与自动更新链路。
一次性配置步骤:
-
确认
deploy.dev.rakull.com 的 A 记录已指向本机,且 80/443 可达。
-
在
/etc/rakull-dev/deployment.env 设置 DEPLOY_WEBHOOK_DOMAIN=deploy.dev.rakull.com 与 DEPLOY_WEBHOOK_REF=dev。后者接受裸分支名或 refs/heads/<name>,开发环境固定为 dev;非配置分支的 push 会被确认接收但忽略。
-
放置共享密钥,属主与权限必须为
rakull-deploy:rakull-deploy、0600(放在接收服务自己的状态目录,因为该用户无权穿越 /etc/rakull-dev):
sudo install -m 0600 -o rakull-deploy -g rakull-deploy /dev/null \
/var/lib/rakull-deploy/webhook.secret
sudo openssl rand -hex 32 -out /var/lib/rakull-deploy/webhook.secret
-
重新生成配置并重启,使 Caddy 站点与带
--deploy-ref 参数的 systemd 单元生效;start/restart 会在密钥存在时自动 enable 并启动接收服务:
cd /opt/rakull-dev
sudo ./scripts/rakull_dev.sh configure
sudo ./scripts/rakull_dev.sh restart
-
在 GitHub 仓库 Settings → Webhooks 添加:Payload URL 为
https://deploy.dev.rakull.com/webhooks/github,Content type 选择 application/json,Secret 填入第 3 步同值,事件选择 Let me select individual events 后仅勾选 Pings 与 Pushes,勾选 Active。
配置完成后在 Recent Deliveries 确认 ping 返回 200;向 dev 分支推送一次,事件应返回 202 并在后台开始部署。请求处理的完整判定(401/409/413 等)见总览页的响应表。
部署过程的查看与人工介入:
sudo ./scripts/rakull_dev.sh deploy-status # 读取最近一次部署状态
curl -s https://deploy.dev.rakull.com/status # 同上,不经 SSH(无鉴权,仅状态摘要)
curl -s https://deploy.dev.rakull.com/healthz
sudo journalctl -u rakull-dev-webhook -n 100 --no-pager
sudo ls -l /var/log/rakull-deploy/ # deploy-<SHA前12位>.log
需要手动重放时,执行与接收服务完全相同的入口:sudo ./scripts/rakull_dev.sh remote-update <完整40位SHA>。修改触发分支后必须重新 configure 并重启 rakull-dev-webhook,因为分支值固化在单元参数中。
修改域名:具体在哪里改?
- 在 DNS 控制台创建新域名的 A 记录,指向服务器公网 IP。
- 服务器编辑
/etc/rakull-dev/deployment.env 的五个 *_DOMAIN 值。
- 执行下列命令,重新生成 Caddy 和 CORS 配置,再使用新域名重新构建、发布 Web,最后重启服务:
cd /opt/rakull-dev
sudoedit /etc/rakull-dev/deployment.env
sudo ./scripts/rakull_dev.sh configure
sudo ./scripts/rakull_dev.sh build-web
sudo ./scripts/rakull_dev.sh publish-web
sudo ./scripts/rakull_dev.sh restart
- 验证新域名的证书、登录、管理台、收藏和媒体读取;切换完成后再删除旧 DNS 记录。
不要直接修改生成的 runtime.env 或 Caddyfile 来换域名,否则下次 configure 会覆盖。Flutter API 地址是编译期配置,换 API 域名必须重新构建前端;Python 业务代码不需要改。本地启动继续使用 localhost。Webhook 的 deploy 域名由独立的 DEPLOY_WEBHOOK_DOMAIN 控制,必须与五个业务域名不同;修改它只需 configure 后重启服务,不涉及 Web 重新构建。
日常备份、更新与回滚
以下命令在服务器执行:
cd /opt/rakull-dev
sudo git fetch origin
REVIEWED_COMMIT_SHA="$(sudo git rev-parse origin/main)"
sudo ./scripts/rakull_dev.sh update "$REVIEWED_COMMIT_SHA"
sudo ./scripts/rakull_dev.sh build-web
sudo ./scripts/rakull_dev.sh publish-web
sudo ./scripts/rakull_dev.sh status
sudo journalctl -u rakull-dev-immersive_study_server -n 100 --no-pager
sudo journalctl -u rakull-dev-caddy -n 100 --no-pager
Webhook 自动部署调用的是同一条端到端入口 remote-update,手动更新 dev 分支内容时直接执行它即可(无需再分步 build/publish):
cd /opt/rakull-dev
sudo git fetch origin
REVIEWED_COMMIT_SHA="$(sudo git rev-parse origin/dev)"
sudo ./scripts/rakull_dev.sh remote-update "$REVIEWED_COMMIT_SHA"
sudo ./scripts/rakull_dev.sh deploy-status
remote-update 依次执行 update 的全部步骤、build-web 与 publish-web,并把 running/success/failed 状态写入 deploy-status.json。update 只更新后端,适用于仅改动服务代码或需要分步控制时,Web 需随后手动构建发布。两者在 prepare 之后、migrate 之前都会以 rakull-dev 用户从 COS active 槽拉取数据资产(机制见数据资产仓库与 COS 双槽发布):COS 未配置时跳过并继续;COS 已配置但拉取失败则中止部署,避免空目录或语法卡缺失的状态上线。首次手工装机且不经过 remote-update 时,可用 sudo ./scripts/rakull_dev.sh _fetch-assets 手动刷新。
update 自动在切换代码前向 development 频道备份数据库,因此不需要先手动重复 push。备份会短暂停止六个服务创建多库快照,再恢复原本运行的服务并上传。COS 备份失败时,更新会在检出目标提交和迁移数据库之前终止;修复 COS 后重试,不要绕过备份。一次只执行一个部署/备份操作,不要让定时任务与更新并发。默认不设置定时停服,不自动删除历史快照。两个频道隔离:本地只能上传 bootstrap,服务器只能上传 development。
首次升级到包含本修复的版本时,旧版 prepare 或 Flutter 命令可能留下受跟踪的生成文件。只需一次性检查并恢复列出的生成目录/文件,再重试更新:
sudo git status --short
sudo git restore -- \
rakull_server/agent_server/rakull_agent_server.egg-info \
rakull_server/immersive_study_server/rakull_immersive_study_server.egg-info \
rakullapp_core/linux/flutter/generated_plugins.cmake \
rakullapp_core/windows/flutter/generated_plugins.cmake
新版本不再跟踪 Python *.egg-info,Web 构建也只在隔离源码副本中运行。更新检查只阻止受跟踪文件的修改,不会被服务器上的无关未跟踪运行文件卡住。
媒体清理必须使用最新审计报告,默认只允许生成媒体孤儿和超过七个完整日的临时对象。命令执行前会重新检查数据库指纹、桶、历史快照引用和对象状态;没有 --apply 不会删除:
sudo ./scripts/db_snapshot.sh audit-media --source server \
--output /var/lib/rakull-dev/cos-audit.json
sudo ./scripts/db_snapshot.sh prune-media --source server \
--report /var/lib/rakull-dev/cos-audit.json \
--temporary-older-than-days 7 --apply
untracked、unknown、external、tracked_unused、使用中及仅被历史快照引用的对象始终受保护。删除结果写入同目录的 .prune.json,发生部分失败时保留已完成记录并返回失败。
服务器清理会在最终重检和删除期间短暂停止六个服务,结束后只恢复原本处于运行状态的服务。
update 要求受跟踪文件无修改并接收完整提交 SHA,先备份,再停服、检出目标提交、安装依赖、拉取 active 数据资产、迁移、恢复原服务并检查健康。备份失败不会切换代码或迁移数据库;切换代码后的步骤失败则保持后端停止,以免用不完整状态继续提供服务。随后在服务器从同一提交执行 build-web 和 publish-web(或直接使用包含这两步的 remote-update)。更新不会删除持久数据,但数据库迁移可能改变结构,所以回滚要同时匹配代码和数据库。
恢复指定快照(按引擎整库还原,不是合并;data_server 走 pg_restore --clean --if-exists):
sudo ./scripts/rakull_dev.sh stop
sudo git checkout --detach OLD_COMMIT_SHA
sudo ./scripts/rakull_dev.sh prepare
sudo ./scripts/db_snapshot.sh restore --target server --channel development --snapshot SNAPSHOT_ID --replace
sudo ./scripts/rakull_dev.sh migrate
sudo ./scripts/rakull_dev.sh build-web
sudo ./scripts/rakull_dev.sh publish-web
sudo ./scripts/rakull_dev.sh start
选择与快照 git_commit 对应的旧代码,并在服务器重新构建、发布该提交对应的 Web。若文件替换中途失败,服务保持停止;持久的 restore-in-progress 标记也会阻止重启机器后 systemd 自动启动,成功重新恢复后才清除。不要通过删除标记跳过恢复。rollback/<id>/restore.json 记录原路径和替换状态,完整的旧库已在替换前保存。优先重新恢复已验证的云端快照;不要在部分恢复的数据库上直接启动服务。
验收与排查
curl -I https://dev.rakull.com 检查前端;对 auth、study、collection、admin 域名请求 /health。
- 验证登录、收藏、管理台、文章分析、上传、媒体播放;流式接口还需在支持流的客户端确认逐步到达。
ss -lntp 确认 8010–8016 只监听 127.0.0.1。
- 证书失败:检查 DNS、错误 AAAA、80/443 安全组和防火墙,以及 Caddy 日志。
- 502:检查对应
rakull-dev-<service> 日志和本机 /health。
- 登录失败:核对共享 JWT、恢复的管理员密码;修改密钥后旧登录可能需要重新登录。
- CORS 或访问 localhost:重新生成配置、重启后端,并使用正确域名参数重新构建 Web。
- 媒体失败:核对 COS 运行配置优先级、桶权限和 Agent 下载域名白名单。
- SELinux 拒绝:检查审计日志和文件标签,按实际拒绝配置权限,不通过关闭 SELinux 绕过。
- Webhook 未触发部署:在 GitHub 的 Recent Deliveries 查看响应码——401 为密钥不一致或缺失、413 为请求体超过 1 MiB、409 为已有部署在执行;ping、删除分支和非配置分支返回 200 属正常忽略。
- 接收服务异常:
journalctl -u rakull-dev-webhook;密钥必须存在于 /var/lib/rakull-deploy/webhook.secret 且权限为 0600,否则 start/restart 不会启用该单元。
- 自动部署失败:
deploy-status 或 /status 显示 failed 及错误摘要,完整输出在 /var/log/rakull-deploy/deploy-<SHA前12位>.log;失败后服务保持停止,修复后对同一 SHA 重放 remote-update,或按上文步骤恢复快照,不要直接启动。
- RDS 连接失败:
configure 会提示先运行 init-rds;检查 RDS 白名单是否放行 ECS 内网地址、DATA_DATABASE_HOST/PORT 是否正确,以及 /usr/pgsql-18/bin 客户端是否已安装。
仓库测试和配置校验不代表服务器已部署。公网 IP、SSH、DNS 和外部模型未就绪时,线上 HTTPS、真实模型和媒体验收仍需在目标服务器完成。