把 DeepSeek Harness (dsh) 装进 Android 手机:Termux 实战全记录
一台 Root 过的安卓手机,能跑起 DeepSeek 官方的 AI Agent 运行时吗?
答案是:能,但过程足够写一篇万字踩坑实录。本文记录从零到dsh web跑通的完整过程,所有坑都标了根因和解法,照抄即可。
一、为什么要在手机上跑 dsh
dsh(DeepSeek Harness)是 DeepSeek 开源的 Agent 运行时框架——它把 LLM、工具调用、文件系统、沙箱、Web 界面打包成一个可自托管的智能体平台,本地运行,数据不出设备。
PC 上装 dsh 很简单(npm install -g @deepseek-ai/dsh 即可),但手机上没有官方 App。想在手机上跑,唯一的路是 Termux——Android 上的终端模拟器,本质是一个独立的 Linux 用户态环境。
本文设备:一加 12(PJX110),Android 16(API 36),KernelSU root,Termux 0.118.3.70,node v24.18.0。
前置要求:手机必须 Root(KernelSU / Magisk 均可),且能通过
adb连接电脑调试。
二、环境初始化
2.1 安装 Termux
- 从 GitHub Releases 下载最新 APK(
termux-app),arm64 设备选arm64-v8a包 - 直连 GitHub 慢的话走
gh-proxy.com镜像 - 安装后打开,执行
pkg update && pkg upgrade
2.2 安装基础工具链
pkg install nodejs-lts git cmake make clang python binutils
踩坑 1:
pkg/apt拒绝以 root 运行
Termux 的包管理器不允许 root 执行。如果通过su在 root 身份下操作,要切换回 Termux 应用 uid(示例中为 10493):> su 10493 -c 'pkg install cmake' > ``` > > 用 `ls -ld /data/data/com.termux` 可以查到你的 Termux uid。 ### 2.3 adb 连接与 shell 电脑端:bash
adb devices -l # 确认设备在线
adb shell # 进入设备 shell
之后所有操作要么在 Termux App 里直接做,要么 `adb shell` 里 `su -c '...'` 做。
---
## 三、安装 dsh
### 3.1 npm 全局安装
bash
npm install -g @deepseek-ai/dsh
> **踩坑 2:`--ignore-scripts` 只是暂时的**
> 如果原生模块(见第四节)编译报错,可以先 `npm install -g @deepseek-ai/dsh --ignore-scripts` 装上 JS 主体,之后再逐个补编译原生模块。`dsh --version` 能输出版本号即主体安装成功。
### 3.2 shebang 修复(Termux 特有的坑)
npm 生成的 bin 文件 shebang 是 `#!/usr/bin/env node`,但 **Termux 里没有 `/usr/bin/env`**。需要改成:
bash
sed -i ‘1s|^#!/usr/bin/env node$|#!/data/data/com.termux/files/usr/bin/env node|’
/data/data/com.termux/files/usr/lib/node_modules/@deepseek-ai/dsh/lib/bin.js
> Termux 的一切都在 `/data/data/com.termux/files/usr/`(即 `$PREFIX`),不是 `/usr`。
### 3.3 ESM bin 符号链接
**踩坑 3:npm 会把 ESM 包的 bin 文件"复制"到 `$PREFIX/bin`,导致模块解析失败(`ERR_MODULE_NOT_FOUND`)。** 正确做法是符号链接:
bash
rm $PREFIX/bin/dsh
ln -sf $PREFIX/lib/node_modules/@deepseek-ai/dsh/lib/bin.js $PREFIX/bin/dsh
---
## 四、原生模块编译(最硬核的部分)
dsh 依赖三个原生模块,Android 上都没有预编译包,全部要本地编译。**Termux 的 clang 默认 target 是 `aarch64-unknown-linux-android24`,这是后面一系列坑的总根源。**
### 4.1 node-pty(PTY 支持)
bash
cd $PREFIX/lib/nodemodules/@deepseek-ai/dsh/nodemodules/node-pty
CPLUSINCLUDEPATH=$PREFIX/include/node
node $PREFIX/lib/nodemodules/npm/nodemodules/node-gyp/bin/node-gyp.js rebuild
- node-gyp 随 npm 自带,路径如上
- **必须**加 `CPLUS_INCLUDE_PATH=$PREFIX/include/node`,否则找不到 `node_api.h`
- 产物生成在 `build/Release/pty.node`
### 4.2 koffi(FFI 沙箱模块)
koffi 用 CNoke + CMake 构建,安装时 `cnoke.cjs` 会跑 CMake。逐个击破:
**第一步:补 spawn.h**
Android bionic 的 `spawn.h` 不在 Termux 头文件里,从 AOSP bionic 仓库下载补进 `$PREFIX/include/`:
bash
从 android.googlesource.com/platform/bionic 拉取 libc/include/spawn.h
curl -o $PREFIX/include/spawn.h <bionic-spawn.h-URL>
**第二步:编译目标必须是 android36**
**踩坑 4(关键):** Termux clang 默认 `android24`,导致 bionic 头文件里 `__BIONIC_AVAILABILITY_GUARD(28)` 把 `posix_spawn` 等 API 28+ 的声明全部隐藏,编译直接报 `use of undeclared identifier 'posix_spawn'`。
修复:编辑 koffi 的 `build/koffi/android_arm64/v24.18.0_native/Release/CMakeFiles/koffi.dir/flags.make`,在 `CXX_DEFINES` / `CXX_FLAGS` / `ASM_*` 前加上:
–target=aarch64-linux-android36
同时建议把 `$PREFIX/include/spawn.h` 里的 `__BIONIC_AVAILABILITY_GUARD(28/34)` 两个块改为 `#if 1`,并删除 `__INTRODUCED_IN(28/34)` 后缀,彻底绕开 availability 检查(手机就是 API 36,函数全都有)。
**第三步:编译**
bash
cd …/koffi/build/koffi/androidarm64/v24.18.0native/Release
make -j4
**第四步:产物归位**
**踩坑 5:** 编译产物在 `Release/Output/koffi.node`,但 koffi 加载器找的是 `build/koffi/android_arm64/koffi.node`:
bash
cp Release/Output/koffi.node ../../
### 4.3 sharp / libvips(图片处理)—— 放弃编译,重写实现
**踩坑 6:sharp 依赖 libvips ≥ 8.18.3,Termux 仓库虽有 8.18.5,但递归依赖 115 个包,且 libvips 本身也是 C++ 大工程,在 Android 上编译风险极高。**
`dsh-attachment-local` 插件用 sharp 做图片格式/尺寸校验。**既然只用到校验,就用"魔数嗅探"重写**——解析 PNG/JPEG/WebP/GIF 的文件头拿格式和尺寸,不引入任何原生依赖:
- PNG:前 8 字节魔数 + IHDR 里的宽高(偏移 16/20,大端)
- JPEG:扫描 SOF 段(`0xFF 0xC0~0xCF`),读高(偏移 5/6)、宽(偏移 7/8)
- WebP:RIFF/WEBP 头 + VP8 / VP8L / VP8X 各自的尺寸字段
- GIF:`GIF87a/GIF89a` + 偏移 6/8 的宽高(小端)
修改文件:
`$PREFIX/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-attachment-local/lib/index.js`
删除 `import sharp from "sharp"`,把校验逻辑换成上面的嗅探实现,**存储层(内容寻址、sha256 校验、目录 fsync)一行不动**。改完 `node --check` 验证语法。
> 备份:改之前先 `cp index.js index.js.bak`。
---
## 五、两个"隐藏"的启动坑
### 5.1 HMR 插件强制创建
**踩坑 7:** 你以为 `cordis.patch.yml` 里 `disabled: true` 能禁掉 HMR?不行——`profile-boot` 里有一段硬逻辑:
js
if (ctx.get(“hmr”) === void 0) {
…
await ctx.loader.create({ name: “@deepseek-ai/cordis-plugin-hmr”, … });
}
HMR 构造器要求 `--expose-internals`,否则直接抛错。而 `NODE_OPTIONS=--expose-internals` 又不被允许。
**解法:直接改 shebang,让所有启动都带 flag:**
bash
sed -i ‘1s|^#!/data/data/com.termux/files/usr/bin/env node$|#!/data/data/com.termux/files/usr/bin/env -S node –expose-internals|’
$PREFIX/lib/node_modules/@deepseek-ai/dsh/lib/bin.js
> Termux 的 env 是 GNU coreutils 9.11,支持 `-S` 拆分参数,放心用。
### 5.2 Android 网络凭据机制:必须 root 启动
**踩坑 8(最后的拦路虎):** dsh web 启动后端口在监听(`ss -tlnp` 能看到 `127.0.0.1:3080 LISTEN`),但**任何连接都超时**——curl 卡到超时、nc 连不上、本机 fetch 也超时。
根因:OPPO/ColorOS 的 BPF 网络过滤器(`prog_oplus-netd_skfilter_*`)按 uid 放行网络。**非系统 uid(Termux 的 10493)进程即使 listen 成功,入站连接也会被静默丢弃;root 进程则完全放行。**
解法:**用 root 启动 dsh web**:
bash
su -c ‘export PATH=$PREFIX/bin:$PATH HOME=$HOME LDLIBRARYPATH=$PREFIX/lib; dsh web’
启动后 curl 立刻 `HTTP 200`(0.04s 响应)。
---
## 六、日常使用
手机端(Termux 里),`~/start-dsh.sh`:
bash
#!/data/data/com.termux/files/usr/bin/bash
export PREFIX=/data/data/com.termux/files/usr
export PATH=$PREFIX/bin:$PATH
export HOME=/data/data/com.termux/files/home
export LDLIBRARYPATH=$PREFIX/lib
echo “[dsh] 正在以 root 启动 web 服务…”
su -c “exec dsh web” # KernelSU 会弹授权
“`
- 手机浏览器访问
http://127.0.0.1:3080 - Mac 上访问:
adb forward tcp:3080 tcp:3080,然后浏览器打开http://127.0.0.1:3080
页面标题 “DeepSeek Harness”,右上角配置 DeepSeek API Key 即可开始对话。
七、踩坑速查表
| # | 现象 | 根因 | 解法 |
| – | ——————————————– | ————————————— | ———————————————– |
| 1 | Cannot run 'pkg' as root | Termux 禁 root 装包 | su 10493 -c 'pkg install ...' |
| 2 | dsh: inaccessible or not found | shebang 指向不存在的 /usr/bin/env | 改 shebang 为 $PREFIX/bin/env |
| 3 | ERR_MODULE_NOT_FOUND | npm 复制了 ESM bin | 删除复制文件,改符号链接 |
| 4 | use of undeclared identifier 'posix_spawn' | clang 默认 android24,API 28+ 声明被 guard 隐藏 | --target=aarch64-linux-android36 + 放开 spawn.h |
| 5 | Cannot find the native Koffi module | 产物在 Output/ 子目录 | 复制到 build/koffi/android_arm64/koffi.node |
| 6 | sharp 加载失败 | libvips 无法在 Termux 编译 | 魔数嗅探重写 attachment-local |
| 7 | --expose-internals is required | profile-boot 强制创建 HMR | shebang 改 env -S node --expose-internals |
| 8 | 端口在听但连不上 | Android 按 uid 过滤网络,非 root 进程入站被丢 | 用 root 启动 dsh web |
|---|
八、一些感想
在手机上跑桌面级 Agent 运行时,本质上是在和三个世界打架:Termux 的”准 Linux”环境、Android bionic 的 API 约束、厂商 ROM 的安全策略。每一个坑单独看都不难,但它们叠在一起就是十几个小时。
但跑通的那一刻——HTTP 200, 12109 bytes, 0.04s——很值。一个完全离线的、跑在你自己手机上的 AI 智能体,这种感觉和用云端 API 完全不一样。
祝折腾愉快。有问题欢迎在评论区交流。







暂无评论内容