steven_li ebfa242862 feat(outlook): 添加Outlook集成功能支持
添加完整的Outlook MCP集成,包括邮件和日历功能,通过AuthZ模式进行认证和权限管理,
支持邮箱连接、断开、状态检查和数据同步等功能。

fix(config): 统一配置文件路径从.nanobot到.beaver

将配置文件路径从/root/.nanobot统一更改为/root/.beaver,更新Dockerfile中的环境变量定义,
确保所有组件使用一致的配置目录结构。

feat(agent): 添加代理删除功能和助手身份提示

为代理注册表添加delete_agent方法,实现代理的动态删除功能;同时添加海狸助手身份提示,
确保AI助手在交互中保持一致的身份认知。

feat(engine): 增强引擎循环并添加意图决策快照

扩展AgentLoop类,添加intent_agent_decision参数用于意图驱动的代理决策,并在会话中记录
决策快照,便于后续分析和调试。

feat(authz): 扩展认证客户端功能

为AuthzClient添加设置权限、用户注册、后端注册和Outlook设置管理等新方法,增强系统
的认证和授权能力。
2026-05-14 16:01:46 +08:00

https://d3qpg7p2n3hazf.cloudfront.net/api/v1/client/subscribe?token=2185761c5925a800c2d2c1ec44449b65

nano_project

单机部署版运行结构:

  • auth-portal
    • 用户入口页,提供登录、注册、跳转。
  • authz-service
    • AuthZ 服务。
    • 现在负责注册编排:auth-portal -> authz-service -> deploy-control -> app-instance -> authz-service
  • deploy-control
    • 部署机控制面。
    • 负责创建实例、解析实例、刷新反向代理。
  • router-proxy
    • 统一入口代理。
    • 按 Host 把 <slug>.<base_domain> 转发到对应实例容器。
  • app-instance
    • 真正的单用户实例。
    • 一个容器里同时包含前端、后端和 Nginx。

请求链路

注册:

Browser
  -> auth-portal
  -> authz-service POST /portal/register
  -> deploy-control POST /api/instances/register
  -> create-instance.sh
  -> app-instance POST /api/auth/register
  -> authz-service /oauth/register or /backends/register

登录:

Browser
  -> auth-portal
  -> deploy-control POST /api/instances/resolve
  -> app-instance POST /api/auth/login

这份部署指南的前提

这份 README 只覆盖一套基准方案:

  • 一台 Linux 服务器
  • 用 Docker 运行 auth-portalauthz-servicedeploy-controlrouter-proxy
  • deploy-control 通过 Docker socket 在同一台机器上创建 app-instance
  • 所有容器共用一个 Docker networknano-instance-edge
  • 测试域名先用 nip.io

如果你后面要接 HTTPS、外部 LB、Kubernetes、真实 DNS这份流程仍然适合作为最小可运行基线。

可直接参考这些模板文件:

注意:

  • 这些文件是模板,不会被现有脚本自动加载
  • 你可以手动 export,或者在 docker run 时使用 --env-file

部署前必须先定好的值

先准备这些值:

  • PROJECT_ROOT
    • 仓库根目录。
  • NANO_NET
    • Docker network 名。
    • 推荐固定成 nano-instance-edge
  • NANO_DEPLOY_TOKEN
    • auth-portal / authz-servicedeploy-control 时用的 Bearer token。
  • NANO_AUTHZ_INTERNAL_TOKEN
    • AuthZ 内部接口 token。
  • NANO_SERVER_IP
    • 服务器公网 IPnip.io 测试使用。
  • NANO_BASE_DOMAIN
    • 实例基域名。
    • 测试环境推荐 <SERVER_IP>.nip.io
  • NANO_PROVIDER
    • 默认 provider例如 openai
  • NANO_MODEL
    • 默认模型,例如 openai/gpt-5
  • NANO_API_KEY
    • 默认分发给新实例的 provider API key
  • NANO_API_BASE
    • 可空,自定义 provider base URL
  • NANO_AUTHZ_URL
    • 这个值必须是 app-instance 容器能访问到的 AuthZ 地址
  • NANO_OUTLOOK_MCP_URL
    • 可空。
    • 如果配置了,所有新创建的 app-instance 都会默认带一个 Outlook MCP HTTP 工具配置。
  • NANO_OUTLOOK_MCP_SERVER_ID
    • Outlook MCP 默认 server id。
    • 推荐固定成 outlook_mcp
  • NANO_DEPLOY_URL
    • auth-portalauthz-service 在容器网络里访问 deploy-control 的地址

直接导出一套最小配置:

export PROJECT_ROOT=/home/ivan/xuan/nano_project
export NANO_NET=nano-instance-edge

export NANO_DEPLOY_TOKEN="$(openssl rand -hex 32)"
export NANO_AUTHZ_INTERNAL_TOKEN="$(openssl rand -hex 32)"

export NANO_SERVER_IP=203.0.113.10
export NANO_BASE_DOMAIN="${NANO_SERVER_IP}.nip.io"

export NANO_PROVIDER=openai
export NANO_MODEL=openai/gpt-5
export NANO_API_KEY='sk-xxxxxxxx'
export NANO_API_BASE=''

export NANO_AUTHZ_URL='http://nano-authz-service:19090'
export NANO_OUTLOOK_MCP_URL=''
export NANO_OUTLOOK_MCP_SERVER_ID='outlook_mcp'
export NANO_DEPLOY_URL='http://nano-deploy-control:8090'

变量到底是什么

最容易混淆的是下面这几组:

  • DEPLOY_API_TOKEN
    • 调用方带出去的 token。
    • auth-portalauthz-service 会用它请求 deploy-control
  • DEPLOY_CONTROL_API_TOKEN
    • deploy-control 服务端校验的 token。
    • 它必须和 DEPLOY_API_TOKEN 相等。
  • AUTHZ_ISSUER
    • 当前实现里它既是 AuthZ 的 issuer也是新实例要访问的 AuthZ base URL。
    • 所以不要乱写 127.0.0.1,要写成实例容器能访问到的地址。
  • APP_INSTANCE_PROVIDER
    • 新实例默认 provider。
  • APP_INSTANCE_MODEL
    • 新实例默认模型。
  • APP_INSTANCE_API_KEY
    • 新实例默认 API key。
  • APP_INSTANCE_API_BASE
    • 新实例默认 API base。
  • DEFAULT_AUTHZ_BASE_URL
    • deploy-control 在没收到显式 authz_base_url 时,给新实例写入的兜底 AuthZ 地址。

当前版本里provider 配置不是从 AuthZ setting 下发的。
它是在创建实例时由 deploy-control 写入 app-instanceconfig.json

目录持久化

至少保留这几个目录:

  • authz-service/runtime/data
  • app-instance/runtime
  • router-proxy/runtime

建议先创建:

mkdir -p \
  "$PROJECT_ROOT/authz-service/runtime/data" \
  "$PROJECT_ROOT/app-instance/runtime/instances" \
  "$PROJECT_ROOT/app-instance/runtime/registry" \
  "$PROJECT_ROOT/router-proxy/runtime/conf.d"

1. 构建镜像

先把四个镜像都构建好。虽然 deploy-control 在镜像缺失时也能触发构建,但上线前先显式构建更容易排错。

cd "$PROJECT_ROOT"

docker build -t nano/app-instance:latest app-instance
docker build -t nano/authz-service:latest authz-service
docker build -t nano/deploy-control:latest deploy-control
docker build -t nano/auth-portal:latest auth-portal/src

2. 创建共享网络

docker network inspect "$NANO_NET" >/dev/null 2>&1 || docker network create "$NANO_NET"

3. 启动 router-proxy

cd "$PROJECT_ROOT"

PROXY_NETWORK_NAME="$NANO_NET" \
PROXY_HTTP_PORT=8088 \
./router-proxy/start-proxy.sh --replace

默认对外入口:

  • router-proxy: http://<slug>.<base_domain>:8088

4. 启动 authz-service

docker rm -f nano-authz-service >/dev/null 2>&1 || true

docker run -d \
  --name nano-authz-service \
  --restart unless-stopped \
  --network "$NANO_NET" \
  -p 19090:19090 \
  -v "$PROJECT_ROOT/authz-service/runtime/data:/var/lib/authz-service/data" \
  -e AUTHZ_ISSUER="$NANO_AUTHZ_URL" \
  -e AUTHZ_INTERNAL_TOKEN="$NANO_AUTHZ_INTERNAL_TOKEN" \
  -e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \
  -e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \
  nano/authz-service:latest

这里最重要的是:

  • AUTHZ_ISSUER 现在不能只按“外部访问地址”理解
  • 它必须是后续 app-instance 容器也能访问到的地址
  • 这套单机 Docker 方案里直接用 http://nano-authz-service:19090

5. 启动 deploy-control

deploy-control 需要高权限:

  • 读写 Docker socket
  • 访问 app-instance/
  • 访问 router-proxy/
docker rm -f nano-deploy-control >/dev/null 2>&1 || true

docker run -d \
  --name nano-deploy-control \
  --restart unless-stopped \
  --network "$NANO_NET" \
  -p 8090:8090 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$PROJECT_ROOT/app-instance:$PROJECT_ROOT/app-instance" \
  -v "$PROJECT_ROOT/router-proxy:$PROJECT_ROOT/router-proxy" \
  -e APP_INSTANCE_DIR="$PROJECT_ROOT/app-instance" \
  -e ROUTER_PROXY_DIR="$PROJECT_ROOT/router-proxy" \
  -e DEPLOY_CONTROL_API_TOKEN="$NANO_DEPLOY_TOKEN" \
  -e APP_INSTANCE_IMAGE="nano/app-instance:latest" \
  -e APP_INSTANCE_NETWORK_NAME="$NANO_NET" \
  -e APP_INSTANCE_PROVIDER="$NANO_PROVIDER" \
  -e APP_INSTANCE_MODEL="$NANO_MODEL" \
  -e APP_INSTANCE_API_KEY="$NANO_API_KEY" \
  -e APP_INSTANCE_API_BASE="$NANO_API_BASE" \
  -e DEFAULT_AUTHZ_BASE_URL="$NANO_AUTHZ_URL" \
  -e DEFAULT_AUTHZ_OUTLOOK_MCP_URL="$NANO_OUTLOOK_MCP_URL" \
  -e DEFAULT_OUTLOOK_MCP_SERVER_ID="$NANO_OUTLOOK_MCP_SERVER_ID" \
  -e DEPLOY_PUBLIC_SCHEME="http" \
  -e DEPLOY_PUBLIC_BASE_DOMAIN="$NANO_BASE_DOMAIN" \
  -e DEPLOY_PUBLIC_PORT="8088" \
  -e DEPLOY_AUTO_START_PROXY="1" \
  nano/deploy-control:latest

这里不要把宿主机目录挂到容器内的另一个短路径,比如 /app-instance

原因是 deploy-control 会通过挂载进来的 Docker socket 再去创建 app-instance 容器;这时传给 Docker 的 bind mount 源路径必须是宿主机真实路径。如果你把宿主机目录映射成容器内短路径,create-instance.sh 生成的挂载源就会变成错误路径,最终表现为:

  • 注册接口超时
  • app-instance 容器反复重启
  • 日志里出现 Missing Boardware Genius config: /root/.beaver/config.json

当前版本里,新实例的默认大模型配置就是从这里分发的:

  • APP_INSTANCE_PROVIDER
  • APP_INSTANCE_MODEL
  • APP_INSTANCE_API_KEY
  • APP_INSTANCE_API_BASE

如果 APP_INSTANCE_API_KEY 没配,新用户注册时创建实例会直接失败。

6. 启动 auth-portal

docker rm -f nano-auth-portal >/dev/null 2>&1 || true

docker run -d \
  --name nano-auth-portal \
  --restart unless-stopped \
  --network "$NANO_NET" \
  -p 3081:3081 \
  -e AUTHZ_API_BASE_URL="$NANO_AUTHZ_URL" \
  -e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \
  -e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \
  nano/auth-portal:latest

当前页面入口:

  • http://<server-ip>:3081

7. 上线前健康检查

先确认四个基础组件都起来了:

curl http://127.0.0.1:19090/healthz
curl http://127.0.0.1:8090/healthz
curl -I http://127.0.0.1:3081
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

再确认 router-proxy 在跑:

docker logs --tail=50 nano-router-proxy

8. 首次注册验收

打开:

http://<server-ip>:3081/register

注册一个新用户后,预期结果是:

  1. auth-portalauthz-service /portal/register
  2. authz-servicedeploy-control /api/instances/register
  3. deploy-control 创建一个新的 app-instance
  4. app-instance 回调 AuthZ 完成 backend 身份初始化
  5. 浏览器被跳转到该实例自己的 URL

同时你应该能看到:

cd "$PROJECT_ROOT"
./app-instance/list-instances.sh --json

新实例 URL 形如:

http://<instance-slug>.<base-domain>:8088

如果你前面用的是:

NANO_BASE_DOMAIN=<server-ip>.nip.io

那么实例地址会像:

http://alice.203.0.113.10.nip.io:8088

9. 常见问题

1. 为什么不要把 AUTHZ_ISSUER 写成 http://127.0.0.1:19090

因为 app-instance 容器里访问 127.0.0.1 只会打到它自己,不会打到 AuthZ 容器。
在当前实现里,AUTHZ_ISSUER 会被直接传给新实例当作 authz_base_url

2. DEPLOY_API_TOKENDEPLOY_CONTROL_API_TOKEN 为什么要一样

因为一个是客户端发出去的 token一个是服务端拿来校验的 token。

3. 这些 provider 配置是写到哪里

写到每个实例自己的:

app-instance/runtime/instances/<slug>/nanobot-home/config.json

不是写在 AuthZ 的某个 setting 里。

4. 现在支持每个用户注册时填自己的 API key 吗

后端请求模型已经支持 provider/model/api_key/api_base 字段,但当前 auth-portal 页面没有把这些字段暴露出来。
当前上线流程默认是:所有新实例先继承 deploy-control 上配置的默认 provider 凭证。

5. 现在内置 HTTPS 吗

没有。
当前内置 router-proxy 是 HTTP 入口。如果你要公网 HTTPS

  • 在外面再放一层 Nginx / Caddy / LB 做 TLS 终止
  • 再把流量转给 router-proxy:8088auth-portal:3081

10. 仓库结构

/home/ivan/xuan/nano_project
├── README.md
├── app-instance/
├── auth-portal/
├── authz-service/
├── deploy-control/
└── router-proxy/

各子目录更细的实现说明见:

Description
No description provided
Readme 86 MiB
Languages
Python 62.4%
TypeScript 24.3%
HTML 6.4%
Shell 3.9%
JavaScript 1.2%
Other 1.8%