如何把 Odysseus Cookbook 接入远程服务器下载并服务模型(SSH 密钥配置)
Odysseus 的 Cookbook 负责模型的硬件适配推荐、下载与服务。当本机 GPU 显存不够或干脆没有 GPU 时,可以把一台远程 GPU 服务器接入 Cookbook:在 Odysseus 里生成一个专用 SSH 密钥,把公钥装到远程服务器的 authorized_keys,之后模型下载(后台 tmux 会话)和模型服务(llama.cpp / vLLM / SGLang 等)都跑在远程机器上,Odysseus 自动把服务注册为 OpenAI 兼容端点。本文按 docs/setup.md 和 Cookbook 路由/前端代码中的实际实现,走通"添加服务器 → 生成密钥 → 安装公钥 → 验证连接 → 在远程下载并服务模型"这条完整路径。
前提条件
- Odysseus 已在运行(Docker Compose 或原生安装),并且你使用 admin 账户登录。SSH 密钥相关接口(
/api/cookbook/ssh-key、/api/cookbook/test-ssh)在 routes/cookbook_routes.py 中均由require_admin守卫,非管理员不可操作。 - 远程服务器为 Linux/POSIX 环境并已安装
tmux:Cookbook 的后台下载和服务依赖 tmux 会话,发起远程下载前会先在远程探测tmux,缺失时直接报"missing binary"错误而不是静默继续。 - 如果远程 SSH 不走 22 端口,服务器条目里有独立的端口字段,后续验证命令和
known_hosts条目都会按[host]:port处理,无需手工转换。
第一步:在 Cookbook 的服务器列表中登记远程主机
打开 Cookbook → Settings → Servers,为远程服务器建一个条目:
- 主机字段填
user@host格式(例如root@gpu-node-01);前端会校验必须是普通 SSH 目标加可选的纯数字端口,含非法字符时输入框会提示Use a plain SSH target like user@host and an optional numeric port.; - 端口字段留空表示默认 22,非标准端口填数字即可。
注意前端代码明确要求带 @(校验 user@host),只填裸 host 时连接测试面板会直接提示 Enter user@host to test。
第二步:生成 Odysseus 专用 SSH 密钥
在服务器条目对应的密钥面板里点击生成(按钮文案为 Generate key),前端调用 POST /api/cookbook/ssh-key。服务端执行的是:
ssh-keygen -t ed25519 -N "" -C odysseus-cookbook -f <key_path>
即生成一把无密码短语的 ed25519 密钥,注释为 odysseus-cookbook,私钥权限 0600、公钥 0644。密钥路径取决于部署方式(见 routes/cookbook_routes.py 中 _cookbook_ssh_dir 的逻辑):
| 部署方式 | 密钥目录 | 私钥/公钥 |
|---|---|---|
| Docker | 容器内 /app/.ssh,由宿主机 ${APP_DATA_DIR:-./data}/ssh 挂载而来 |
./data/ssh/id_ed25519 / ./data/ssh/id_ed25519.pub |
| 原生安装(非容器) | 当前用户主目录 ~/.ssh |
~/.ssh/id_ed25519 / ~/.ssh/id_ed25519.pub |
docker-compose.yml 中对该挂载的注释写得很直接:"Cookbook remote-server SSH identity. Odysseus can generate a key here; add the shown public key to each remote server's authorized_keys." 生成成功后面板会给出公钥内容;GET /api/cookbook/ssh-key 返回 {configured, public_key},configured 为 true 表示已有可用公钥。
第三步:把公钥装到远程服务器
docs/setup.md 给出两条路径,UI 里还有一条自动生成的粘贴命令。
方式 A:执行 UI 生成的一行 ssh 命令(推荐,免登录远程)
主机与公钥就绪后,密钥面板的输入框会生成形如下面的命令({host}、{port}、公钥值均由 UI 自动填入,直接点复制按钮取完整命令,在 Odysseus 所在机器上执行;{port} 仅在非 22 端口时出现):
ssh -o StrictHostKeyChecking=accept-new {port} {host} 'KEY={public_key} && mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && (grep -qxF "$KEY" ~/.ssh/authorized_keys || printf "%s\n" "$KEY" >> ~/.ssh/authorized_keys) && chmod 600 ~/.ssh/authorized_keys'
该命令在远程侧创建/修正 ~/.ssh(权限 700)与 authorized_keys(权限 600),grep -qxF 保证同一公钥重复执行不会产生重复行。
方式 B:手工追加公钥
把面板里显示的公钥内容复制后,登录远程服务器追加进 ~/.ssh/authorized_keys,即 docs/setup.md 中 "add the public key to the remote server's ~/.ssh/authorized_keys" 描述的默认做法。
方式 C:从宿主机用 ssh-copy-id
docs/setup.md 给出的宿主机命令(Docker 布局,密钥在 data/ssh):
ssh-copy-id -i data/ssh/id_ed25519.pub user@server
user@server 替换为你的实际登录用户名与主机名。原生安装时对应的是 ~/.ssh/id_ed25519.pub。
第四步:验证 SSH 连通性
回到服务器条目,点击测试按钮(状态点显示 Testing SSH…)。前端调用 POST /api/cookbook/test-ssh,传入 {host, ssh_port},服务端用 5 秒连接超时、8 秒总超时对该目标执行 echo ok,返回 stdout/stderr/exit_code。前端的判定逻辑(见 static/js/cookbook-hwfit.js):
- 成功:
exit_code === 0且 stdout 以ok开头,状态点变绿并显示Connected · {N} ms; - 失败:状态点变红,显示
Failed · {stderr 或 exit code 摘要},鼠标悬停可看完整错误(截断到 240 字符)。
超时场景下接口返回 exit_code: 124、stderr 为 SSH test timed out。测试通过后,提示文案会建议你用 Cookbook 的 Dependencies 面板检查该服务器上的 tmux / HuggingFace 环境。
第五步:在远程服务器上下载并服务模型
SSH 验证通过后,在 Cookbook 的下载/服务界面把目标服务器选为你刚登记的这台:
- 下载:发起下载时,Odysseus 把本次的 runner 脚本 scp 到远程(临时文件
.{session_id}_run.sh,执行完即删),再在远程创建 tmux 会话后台执行,日志经 SSE 推回界面;返回体形如{"ok": true, "session_id": "...", "remote": "user@host"},remote为local时表示本机下载。若模型需要 HuggingFace 访问,下载 runner 会在目标服务器上实际检查 HF token 是否已到达该服务器,避免密钥配了但下载仍 401 的情况。 - 服务:远程 serve 同样通过 SSH 在远程 tmux 中拉起 llama-server / vLLM / SGLang / Ollama 等进程。命令中若能解析出
--port(llama.cpp 默认 8080,扩散服务默认 8100),Cookbook 会自动注册一个指向该服务器/v1的 OpenAI 兼容端点,并探测/v1/models自动发现模型 id;在服务器真正就绪前该端点会置灰,服务起来后即可在模型选择器中使用。
已知边界与排查提示
- 远程缺 tmux:下载/服务请求会直接失败并报缺失
tmux,先在远程安装 tmux 再重试;这也是发起前探测的目的。 - 主机密钥变更:如果远程服务器的 host key 发生变化,Cookbook 会自动通过
ssh-keyscan刷新其自己的known_hosts(条目按[host]:port和 host 两种形式维护),无需手工跑ssh-keygen;刷新失败(超时或取不到密钥)会原样报出错误。 - 密钥位置与保密:
data/ssh/(或原生部署的~/.ssh/)里的私钥等价于远程服务器的登录凭据,属于 docs/setup.md 安全一节要求保持在 Git 和共享目录之外的data/数据范围。 - Docker 挂载:Docker 部署下公钥必须写到宿主机
data/ssh目录对应的挂载卷中才能被容器读到,直接往容器内写而不经过挂载卷的密钥在容器重建后会丢失。 - 平台差异:远程为 Windows 时下载/服务走 PowerShell runner(
Start-Process后台执行),不要求远程 tmux;本文主路径面向 Linux 远程。
完成后,远程服务器的模型下载与服务全部经由这一把 odysseus-cookbook 密钥的 SSH 通道运行;如果之后需要接入多机,按第一到第四步对每台服务器重复登记即可,服务器列表中只配置一台远程服务器时 Cookbook 下载界面会自动默认选中它。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00