把Deepseek harness 装入安卓手机

把 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 完全不一样。

祝折腾愉快。有问题欢迎在评论区交流。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容