本机开发环境:Pi 网关、Pi-Switch 与 Incus 隔离容器
本文适用于需要在本机同时满足“模型统一路由”与“开发环境隔离”的场景。你会学到:本机 Arch + Incus + Pi + Pi-Switch 网关的整体拓扑、各组件职责与真实配置、以及从宿主机到容器内可复现的验证路径。
本文基于 2026-08-31 本机实测整理,敏感值已脱敏(API Key、真实域名以占位符展示),以本机
incus list/~/.pi/agent/settings.json/~/.pi-switch/config.json实测为准。
适用场景
- 你在裸机上跑
pi、opencode等 AI 编码代理,担心依赖污染宿主机,或多个项目需要独立文件系统/网络 - 模型 Key 分散在多个上游(OpenAI、DeepSeek、私有网关),需要在本地统一入口做模型名路由、故障转移与用量统计
- 需要一套“宿主机可写、容器内也可写”的共享目录,且不踩
nobody 65534/chown: Invalid argument的坑
整体架构
flowchart LR
subgraph Host["宿主机 Arch Linux 7.1.10-zen1"]
PI["pi 0.84.4\n~/.pi/agent"]
PS["Pi-Switch 网关\n127.0.0.1:43112\nproviderPrefix=pi-switch"]
INCUS["Incus 7.3 / LXC 7.0.0\ndir 存储 / nftables"]
end
subgraph Container["Incus 容器 arch\n10.10.10.85"]
CPI["pi 0.84.4\ndefaultProvider=pi-switch"]
end
UP["上游模型\nhttps://opencode.ai/zen/go/v1\nDeepSeek / Kimi / GLM"]
PROJ["共享目录\n/home/shial/Project ↔ /home/arch/Project\nshift=true"]
CPI -->|hostproxy 127.0.0.1:43112| PS --> UP
PI -.->|宿主机直连| PS
INCUS --- PROJ
Container --- PROJ
| 组件 | 职责 | 本机形态 |
|---|---|---|
| pi | Agent 运行时、会话/扩展/技能管理 | fnm + npm 全局安装 @earendil-works/pi-coding-agent,配置集中在 ~/.pi/agent/ |
| Pi-Switch | Provider 管理 + 模型名网关(路由/转换/故障转移) | 本地 Rust 网关 0.0.0.0:43112,WebUI 127.0.0.1:43110,providerPrefix=pi-switch 写入 ~/.pi/agent/models.json |
| Pi 网关 | 协议转换与统一端点 | 由 Pi-Switch 承担:openai-responses API、无状态按 profile/model 路由、SSE 透传、断路器 |
| Incus | 非特权容器隔离 | 3 容器(arch RUNNING,其余模板 STOPPED),shift=true 共享目录,hostproxy 透出网关端口到容器 |
数据流:容器内 pi --provider pi-switch --model oc/muse-spark-1.2-contributor → 127.0.0.1:43112(hostproxy)→ Pi-Switch 按模型名选 profile oc → https://opencode.ai/zen/go/v1/responses → 上游。
主机基座
本机为 Arch Linux(7.1.10-zen1-1-zen),Incus 7.3 + LXC 7.0.0,storage: dir,firewall: nftables,api_extensions_count: 541。
Node 与 pi:
node -v # v24.16.0(由 fnm 管理,路径 ~/.local/share/fnm)
pi --version # 0.84.4
pi 通过 npm install -g --ignore-scripts @earendil-works/pi-coding-agent 安装,可执行文件在 ~/.local/share/fnm/node-versions/v24.16.0/installation/bin/pi,切 Node 版本后需在对应版本重装。
~/.pi/agent/ 核心布局(本机实测):
~/.pi/agent/
├── settings.json # 全局设置(defaultProvider、theme、packages)
├── models.json # 由 Pi-Switch 网关发布写入
├── sessions/ # 会话 JSONL,按工作目录分树
├── state/workspace-history
├── extensions/、skills/ # 本地扩展与技能
└── auth.json # 凭证,勿提交
宿主机 ~/.pi/agent/settings.json 关键项(脱敏):
{
"defaultProvider": "opencode-go",
"defaultModel": "muse-spark-1.2-contributor",
"defaultThinkingLevel": "xhigh",
"theme": "front-end-delight",
"packages": ["../../Project/pi-switch", "npm:pi-mcp-adapter", "..."]
}
宿主机
defaultProvider为opencode-go,但已通过本地包../../Project/pi-switch接入 Pi-Switch;容器内~/.pi/agent/settings.json则直接defaultProvider: pi-switch。
Pi 网关(由 Pi-Switch 提供)
本机没有独立的 AxonHub 实例,Pi 网关即 Pi-Switch 的本地代理。这是本机与早期 ai/axonhub-* 文档(Docker + SQLite 8090 方案)的差异:AxonHub 仍可作为可选网关,但当前日常链路为 Pi-Switch。
Pi-Switch 本质是轻量 profile 切换器 + 模型名网关(Rust 核心 + TUI/CLI/WebUI 三端同源):
- Provider 管理:CRUD、多上游
upstreams[]、模型暴露expose到 pi、Responses 透传/转换 - 网关:独立进程,无状态按
profile/model路由,SSE 流式、User-Agent 伪装、OpenAI ↔ Anthropic、Responses ↔ Chat Completions 互转、故障转移与断路器 - 发布模型:显式
发布到 Pi才会写入~/.pi/agent/models.json,非自动同步
本机 ~/.pi-switch/config.json(脱敏后节选):
{
"current": "oc",
"profiles": {
"oc": {
"api": "openai-responses",
"baseUrl": "https://opencode.ai/zen/go/v1",
"apiKey": "sk-***",
"exposedModels": ["muse-spark-1.2-contributor", "mimo-v2.5", "hy3", "kimi-k2.5", "glm-5.2"],
"models": ["deepseek-v4-flash", "mimo-v2.5", "...共 30+ 个"]
}
},
"settings": {
"providerPrefix": "pi-switch",
"gatewayApi": "openai-responses",
"proxy": { "host": "0.0.0.0", "port": 43112, "failover": ["oc"] },
"web": { "host": "127.0.0.1", "port": 43110 }
}
}
本机运行态:
pi-switch proxy status
# Proxy daemon is running (PID 526553)
# Listen: http://0.0.0.0:43112
# Failover: oc
pi-switch provider list
# * oc [p1] api: openai-responses baseUrl: https://opencode.ai/zen/go/v1
# models: deepseek-v4-flash, mimo-v2.5, hy3, kimi-k2.5, glm-5.2, ...
pi-switch config show | head -n 20
参考:Pi-Switch GitHub 与本地 ~/Project/pi-switch/README.md、CONTEXT.md。
Pi-Switch
Pi-Switch 在 pi 侧以本地包 ../../Project/pi-switch 形式安装,并通过 extensions/index.ts 注入会话归因头(x-conversation-id / x-opencode-session),用于网关侧的 requests.log 聚合与 WebUI 统计。
- 配置入口:
pi-switch tui(推荐)或pi-switch webui start --daemon(浏览器http://127.0.0.1:43110) - 诊断:
pi-switch doctor检查config.json/models.json/ 结构完整性 - 统计:每次代理请求追加写入
~/.pi-switch/requests.log(JSONL),WebUI 按today/last24h/last7d/custom聚合四维度 token(input/output/cached/reasoning)与缓存命中率 - 断路器:
failureThreshold: 3、cooldownSeconds: 60,半开探测恢复
宿主机与容器协同:宿主机网关监听 0.0.0.0:43112,Incus 通过 hostproxy 设备将该端口透入容器,容器内 127.0.0.1:43112 即宿主机网关,无需额外网络打洞。
Incus 隔离环境
容器清单(2026-08-31 实测,脱敏)
+-----------------+---------+--------------------+------+-----------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+-----------------+---------+--------------------+------+-----------+-----------+
| arch | RUNNING | 10.10.10.85 (eth0) | | CONTAINER | 0 |
| debian-template | STOPPED | | | CONTAINER | 0 |
| ubuntu-template | STOPPED | | | CONTAINER | 0 |
+-----------------+---------+--------------------+------+-----------+-----------+
incus info:server_version: 7.3、driver: lxc | qemu 7.0.0 | 11.1.0、kernel: 7.1.10-zen1-1-zen。
容器 arch 规格:limits.cpu: 8、limits.memory: 16GiB、security.nesting: true、security.protection.shift: true。
非特权映射与共享目录
容器为非特权,volatile.idmap.current:
[{"Isuid":true,"Hostid":165536,"Nsid":0,"Maprange":65536},
{"Isgid":true,"Hostid":165536,"Nsid":0,"Maprange":65536}]
容器内:
cat /proc/self/uid_map
# 0 165536 65536
含义:容器 UID 0 → 宿主机 165536,容器 1000 → 宿主机 166536,以此类推。此前排障沉淀见 Incus 非特权容器 UID/GID 映射与共享目录排障。
本机当前采用 shift=true(Incus shiftfs)而非手动 chown 166536:175536:
devices:
Project:
path: /home/arch/Project
source: /home/shial/Project
shift: "true"
type: disk
hostproxy:
bind: container
connect: tcp:127.0.0.1:43112
listen: tcp:127.0.0.1:43112
type: proxy
效果:
- 宿主机
id shial:uid=1000 gid=1000 groups=...175536(project_shared),目录drwxr-xr-x shial:shial /home/shial/Project - 容器内
ls -ln /home/arch/Project显示1000:1000,nobody 65534不再出现,Invalid argument不再触发 - 宿主机已保留
project:x:166536:175536与project_shared:x:175536:shial供回退到手动对齐方案时使用;但shift=true下无需sudo chown -R 166536:175536
流水:宿主机 /home/shial/Project --shiftfs--> 容器 /home/arch/Project,双向读写即时可见。
端到端工作流
以容器内发起一次 pi 请求为例(全程可复现):
# 1. 宿主机:确认网关
pi-switch proxy status
# Listen: http://0.0.0.0:43112
# 2. 宿主机:确认 Incus
incus list
incus config show arch | grep -A2 hostproxy
# 3. 容器内:确认影子映射
incus exec arch -- bash -c "cat /proc/self/uid_map; id"
# 4. 容器内:经网关发起请求(模型名含 profile 前缀)
incus exec arch -- bash -c "pi --provider pi-switch --model oc/muse-spark-1.2-contributor -p 'hello via pi-switch'"
# 5. 宿主机:查看聚合
tail -n 1 ~/.pi-switch/requests.log | python3 -m json.tool
# 含 conversationId、model、provider、promptTokens、costTotal
# 6. 宿主机:WebUI 统计
# 浏览器打开 http://127.0.0.1:43110 → Stats 页按 today 聚合
~/.pi-switch/requests.log 示例(脱敏,单行 JSONL):
{"conversationId":"01a05616-4078-7e11-9972-2f1af750b73b","model":"muse-spark-1.2-contributor","provider":"oc","promptTokens":125856,"completionTokens":107,"ok":true,"status":200}
排障与常见问题
| 现象 | 排查 |
|---|---|
nobody / 65534 | 容器内 cat /proc/self/uid_map,确认 0 165536 65536;若用手动方案则检查 Host UID = 165536 + Container UID 是否对齐,参考 排障记录 |
chown: Invalid argument | 同上,宿主机 UID 未落在映射范围;或 shift=false 时未 chown -R 166536:175536 |
| 宿主机改不动容器写入的文件 | id shial 是否含 project_shared,ls -ld /home/shial/Project 是否 g+rwX 且 g+s(手动方案) |
pi-switch proxy status 未运行 | pi-switch proxy start --daemon,查 ~/.pi-switch/proxy.log |
容器内 curl 127.0.0.1:43112 不通 | incus config show arch 是否含 hostproxy,宿主机 `ss -tlnp |
| 模型 401 / 429 | pi-switch config show 中 baseUrl/apiKey 脱敏核对,requests.log 看 status/error,触发断路器时等待 60s 冷却 |
验证与自查
pi --version输出0.84.4,pi list与~/.pi/agent/settings.json的packages一致pi-switch proxy status显示running且Listen: http://0.0.0.0:43112incus list中arch RUNNING 10.10.10.85,incus exec arch -- cat /proc/self/uid_map为0 165536 65536- 宿主机
id shial含project_shared,ls -ld /home/shial/Project可写 - 容器内
ls -ln /home/arch/Project非65534,touch /home/arch/Project/.test && ls /home/shial/Project/.test双向可见 - 容器内
pi --provider pi-switch --model oc/muse-spark-1.2-contributor -p "hello"成功返回,并在~/.pi-switch/requests.log留痕 hugo --quiet --ignoreCache构建零错误,本文在http://localhost:1313/ops/dev-env-pi-gateway-incus/渲染正常,Mermaid 可见
参考
- Pi 官网 与 Pi GitHub
- Pi-Switch GitHub(本地
~/Project/pi-switch/README.md、CONTEXT.md) - Incus 官方文档 与 Arch Wiki - Incus
- AxonHub GitHub(本机未启用,仅作网关选型参考)
- 本站:Pi 本机安装与使用、Incus 非特权容器 UID/GID 映射排障