# 交付手册(静态单二进制 + 本地语音转写 + Clonezilla 整盘克隆) > 面向交付工程师。目标:把一台配置好的原型机整盘克隆到客户同型号机器, > 客户拿到手即用,机器上**无源代码、无开发痕迹、无测试数据、无默认口令**。 --- ## 0. 交付物清单 | 文件 | 说明 | |------|------| | `eai_agentplatform-server` | 单二进制(CGO 关闭,静态链接,约 38MB,无任何运行时依赖) | | `eai_agentplatform.service` | systemd 单元文件 | | `eai_agentplatform.env` | 环境变量样例 | | `eai_agentplatform-asr.service` | 本地语音转写服务的 systemd 单元(**需要 Python,见下**) | | `asr.env` | 本地语音转写服务的环境变量样例 | | `clonezilla-cleanup.sh` | 克隆前清理脚本(DRY-RUN 默认) | | `data/eai_agentplatform.db` | SQLite 数据库(首启自动建表) | | `data/kb_data/` | 已审批素材目录 | | `assets/knowledge/source/` | 知识源 Markdown(待入库,由管理员审批) | | `assets/training/materials/` | 培训资料资产(课程种子、PDF、视频、脚本) | **不需要**:Go 运行时、Docker、MySQL、任何 npm 依赖。 > **平台本体确实不需要 Python**:`eai_agentplatform-server` 是静态单二进制, > `ldd` 输出 `not a dynamic executable`,这条一直成立。 > > **但语音转写是另一个进程,它需要。** 转写默认走本机(faster-whisper large-v3 + > pyannote 3.1),由一个独立的 Python 服务(`eai_agentplatform-asr.service`, > 监听 127.0.0.1:8090)提供。客户机器上因此会多出: > > | 组件 | 体积 | 说明 | > |---|---|---| > | `/opt/eai_agentplatform-asr/venv` | 约 9.4 GB | Python 环境(含 CUDA 版 PyTorch) | > | `/opt/eai_agentplatform-asr/models` | 约 4.1 GB | 模型权重,随整盘克隆带过去,不重新下载 | > > 合计约 **13.5 GB**,交付前请确认目标机磁盘够用。 > > 这**不是**「为了本地化而放弃轻交付」——恰恰相反:把转写留在本机,是为了不让 > 客户会议的录音被送到公网 ASR。此处与「单二进制交付」的出入是明知并接受的取舍, > 记在案。若某台客户机确实装不了 Python,可以把默认语音路由切到云端 > (后台 → AI 路由 → 默认语音路由),代价是**录音会离开本机**, > 界面在转写结果上会明确标注「音频已离开本机」。 --- ## 1. 目录布局(客户机器最终形态) ``` /opt/eai_agentplatform/ ├── eai_agentplatform-server # 单二进制 ├── .env # JWT 密钥等(交付前生成,勿提交源码库) ├── assets/ │ ├── knowledge/ │ │ └── source/ # 知识源 Markdown │ └── training/ │ └── materials/ # 培训资料资产 ├── data/ │ ├── eai_agentplatform.db # SQLite(首启自动建) │ ├── backups/ # 定期备份(服务自动维护,见第 7 节) │ └── kb_data/ # 素材 + 提取缓存 └── (前端静态资源由 Nginx 托管,见 ../docs/02_Architecture/部署文档.md) /opt/eai_agentplatform-asr/ # 本地语音转写(独立进程,独立单元) ├── serve.py # OpenAI 兼容 HTTP 端点(/v1/audio/transcriptions、/v1/models) ├── asr_core.py # 转写 + 说话人分离 │ # ↑ 这两个的**事实源在仓库** deploy/asr/,装机时由 │ # install_asr_local.sh 装过来;直接改 /opt 下那份 │ # 会在下次装机时被覆盖,且没人 review 得到 ├── asr.env # 环境变量(精度档位、离线开关) ├── venv/ # Python 环境(约 9.4 GB) ├── models/ # 模型权重(约 4.1 GB,实体目录) │ ├── Systran/faster-whisper-large-v3/ │ └── pyannote/{segmentation-3.0,wespeaker-voxceleb-resnet34-LM,speaker-diarization-3.1}/ └── cache/ # 运行时缓存(HF_HOME 指到这里) ``` > `serve.py` 按 `脚本所在目录/models` 找模型,所以 `models/` 必须是它的同级目录 > (符号链接也行,但交付机上建议放实体目录,少一层依赖)。 > 原型机上 `~/asr-poc/models` 是指向仓库 `external_download/` 的软链 —— 那是开发期的 > 组织方式,克隆到客户机之前必须把权重**拷成实体目录**,客户机上没有仓库。 --- ## 2. 交付前准备(原型机) ```bash # 2.1 构建静态二进制(开发机执行,产物拷贝到原型机) export PATH=$HOME/go-sdk/go/bin:$PATH CGO_ENABLED=0 GOPROXY=https://goproxy.cn,direct go build -o bin/eai_agentplatform-server ./cmd/server # 校验:file 输出应为 "statically linked",ldd 应为 "not a dynamic executable" # 2.2 拷贝到原型机 + 建账号 + 装 systemd 单元 sudo install -m 0755 eai_agentplatform-server /opt/eai_agentplatform/eai_agentplatform-server sudo useradd -r -s /usr/sbin/nologin eai_agentplatform sudo mkdir -p /opt/eai_agentplatform/data/kb_data /opt/eai_agentplatform/assets/knowledge/source /opt/eai_agentplatform/assets/training/materials sudo chown -R eai_agentplatform:eai_agentplatform /opt/eai_agentplatform sudo install -m 0644 deploy/eai_agentplatform.service /etc/systemd/system/eai_agentplatform.service # 2.3 生成 .env(含随机 JWT 密钥) sudo -u eai_agentplatform cp deploy/eai_agentplatform.env /opt/eai_agentplatform/.env NEW_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(48))") sudo sed -i "s|^JWT_SECRET=.*|JWT_SECRET=$NEW_SECRET|" /opt/eai_agentplatform/.env # 2.4 启动验证 sudo systemctl daemon-reload sudo systemctl enable --now eai_agentplatform curl http://127.0.0.1:8080/api/health # {"status":"ok",...} # 2.5 本地语音转写服务(ASR) # 整个 2.5 已经写成脚本,它会先打印计划再执行(--dry-run 只看计划): sudo bash deploy/install_asr_local.sh # # 它做的事(等价的手工命令如下,仅供理解,**别照着敲**): # 环境本身随原型机整盘克隆过来,这一步只是把它装到交付位置 + 起服务。 # venv 与模型都很大(9.4G + 4.1G),用 mv 而不是 cp —— 两台机器上各留一份没意义。 sudo mkdir -p /opt/eai_agentplatform-asr sudo install -m 0644 deploy/asr/{serve.py,asr_core.py} /opt/eai_agentplatform-asr/ sudo mv ~/asr-poc/venv /opt/eai_agentplatform-asr/venv sudo cp -aL ~/asr-poc/models /opt/eai_agentplatform-asr/models # -L 解开软链,客户机上没有仓库可指 sudo install -m 0644 deploy/asr.env /opt/eai_agentplatform-asr/asr.env sudo install -m 0644 deploy/eai_agentplatform-asr.service /etc/systemd/system/ sudo chown -R eai_agentplatform:eai_agentplatform /opt/eai_agentplatform-asr sudo mkdir -p /opt/eai_agentplatform-asr/cache && sudo chown eai_agentplatform:eai_agentplatform /opt/eai_agentplatform-asr/cache sudo systemctl daemon-reload sudo systemctl enable --now eai_agentplatform-asr # 2.6 验证:模型加载要几十秒,起来之前 /v1/models 是连不上的 systemctl is-active eai_agentplatform-asr # active curl -s http://127.0.0.1:8090/v1/models | head -c 200 # 再确认平台侧认它:后台 → AI 路由 → 语音转写,应显示「本地转写 · Whisper large-v3」 ``` > **转写模型与平台是强耦合的吗?** 不是。平台启动时读 `config/ai_config.json`, > `audio_route_local_whisper` 指向 `http://127.0.0.1:8090/v1`。ASR 服务没起来 > **不影响平台启动**:转写会自动回退到 `fallback_routes` 里的云端路由, > 并在转写产物的日志与界面警示区写明「已回退到云端,音频已离开本机」。 > 这正是让 ASR 单独成一个 systemd 单元(而不是塞进主进程)的原因。 --- ## 3. 克隆前清理(不可逆,务必先 DRY-RUN) ```bash sudo bash clonezilla-cleanup.sh # DRY-RUN,只列动作 sudo bash clonezilla-cleanup.sh --confirm # 确认后真实清理 ``` 脚本负责: - 删除所有 Go 源代码(`cmd/` `internal/` `go.mod` `go.sum`) - 清除 `.git` / 版本控制痕迹 - 清空日志与 shell 历史 - 删除本地 ASR 工作区里的**原型机数据**:`~/asr-poc/audio`(真实会议录音)、 `~/asr-poc/out`(完整逐字稿与说话人分离结果,含一份云端转写对照稿)、安装日志 脚本**不**负责(需手工确认,见下): ### 手工清单(缺一不可) | # | 动作 | 命令 | |---|------|------| | A | **重置管理员密码** | `sudo -u eai_agentplatform /opt/eai_agentplatform/eai_agentplatform-server -reset-admin '<强密码>'` | | B | **确认 JWT 密钥随机** | `grep -c '__CHANGE_ME__' /opt/eai_agentplatform/.env`(应为 0) | | C | **清空测试数据** | Web 后台删除演示用户/素材/考试记录,或用 sqlite3(见第 4 节) | | D | **删掉 ASR 工作区,只留一份 venv** | 装好 `/opt/eai_agentplatform-asr` 并确认服务能起后,`rm -rf ~/asr-poc`(清理脚本会给提示,不代删) | > 默认种子账号为 `admin / admin123`,**克隆前必须改密**(A 项),否则客户拿到默认口令。 --- ## 4. 清空测试数据的 SQL 参考(可选) ```bash # 若需彻底清库只保留结构 + admin: sqlite3 /opt/eai_agentplatform/data/eai_agentplatform.db <<'SQL' -- 先停服务 DELETE FROM exam_record; DELETE FROM knowledge_chunk; DELETE FROM knowledge_source; DELETE FROM media_file; DELETE FROM course; DELETE FROM product; DELETE FROM question; DELETE FROM exam_paper; DELETE FROM "user" WHERE username != 'admin'; DELETE FROM system_config WHERE config_key NOT IN ('company_intro','llm_base_url','llm_model','embed_model','llm_api_key'); SQL ``` > 表名以实际 schema 为准;无 sqlite3 时,用管理员 Web 界面逐项删除亦可。 --- ## 5. Clonezilla 整盘克隆 1. 原型机清理完成、复核 A/B/C 三项后关机。 2. U 盘启动 Clonezilla → `device-device`(整盘复制)。 3. 目标机为**同型号**机器,逐台克隆。 4. 客户机器首启:systemd 自动拉起服务;Nginx 反代 8080。 5. 交付验收:登录(新密码)→ 上传素材 → 审批 → 考试 → AI 问答(若接内网 LLM)。 --- ## 6. 交付红线(每次克隆前过一遍) - [ ] `ldd eai_agentplatform-server` 输出 `not a dynamic executable` - [ ] `find /opt/eai_agentplatform -name '*.go' -o -name 'go.mod'` 无结果 - [ ] `/opt/eai_agentplatform/.env` 无 `__CHANGE_ME__` - [ ] 管理员密码非 `admin123` - [ ] `.bash_history` 已清空、无 `.git` - [ ] 测试账号(zhangsan 等)、演示素材、演示考试记录已删除 - [ ] `data/backups/` 里没有**原型机自己的**数据备份(见第 7 节末) - [ ] `~/asr-poc/audio`、`~/asr-poc/out` 已删(真实录音与逐字稿) - [ ] 全盘只有一份 `venv`(在 `/opt/eai_agentplatform-asr`),`~/asr-poc` 已删 - [ ] `/opt/eai_agentplatform-asr/models` 是实体目录,不是指向仓库的软链 - [ ] `systemctl is-active eai_agentplatform-asr` 为 `active`,且转写可选到「本地转写」路由 --- ## 7. 数据备份与恢复 SQLite 是单文件、没有主从副本,文件坏一份就是全丢,所以服务自带定期备份,默认开着。 **怎么跑**:进程启动时先检查一次(距上次备份够久就补一份),之后每小时醒一次看是否到期。 不需要外部 cron / systemd timer,备份逻辑在二进制里。间隔和保留份数由 `.env` 控制: ``` BACKUP_ENABLED=true # 关掉就完全不备 BACKUP_DIR=data/backups # 必须在 data/ 下(ProtectSystem=strict 只放开这里) BACKUP_KEEP=7 # 保留最近几份,更旧的自动删 BACKUP_INTERVAL_HOURS=24 # 间隔 ``` **备份文件长这样**:`data/backups/eai_agentplatform-20260914-162401.db`。 用 `VACUUM INTO` 产出,不是 `cp` 主库 —— 服务边跑边写时 `cp` 可能拷到写了一半的页; `VACUUM INTO` 出来的是已压实、内部一致的完整副本,且不用停服。产出的文件会校验 SQLite 文件头, 不是真库就删掉并报错,不会留下「看着像备份的废物」。 **手工备一份**(升级、迁移、动数据之前留个手边的副本): ```bash sudo -u eai_agentplatform /opt/eai_agentplatform/eai_agentplatform-server -backup ``` **恢复**(会覆盖当前数据,先停服): ```bash sudo systemctl stop eai_agentplatform sudo -u eai_agentplatform cp data/backups/eai_agentplatform-20260914-162401.db data/eai_agentplatform.db sudo systemctl start eai_agentplatform ``` > 备份只在**本机 data/ 目录**里,防的是误删/误改/写坏,防不了整盘损坏。 > 要防整盘,得把 `data/backups/` 定期拷到机器之外(U 盘 / 内网文件服务器)。 **克隆前注意**:原型机的 `data/backups/` 里会有原型机自己的历史数据(测试账号、演示素材), 整盘克隆会原样带到客户机器上。清理时一并删掉该目录内容,让客户机器从干净状态开始。 恢复之后想核对内容,直接看文件时间戳和大小即可 —— 文件名里的时间就是备份时刻。