Files
beaver_project/部署指南.md
steven_li bfa77204bf feat(server): 添加系统重启功能支持
添加后台任务支持用于处理系统重启请求,实现延迟进程终止机制,
允许系统安全地重启并在重启后自动恢复服务。

feat(frontend): 实现前端系统重启控制面板

在状态页面添加重启对话框组件,提供用户友好的重启确认界面,
包含重启状态监控和错误处理功能,确保用户可以安全重启系统。

docs(deployment): 更新部署指南添加URL协议要求说明

详细说明NANO_AUTHZ_URL和NANO_DEPLOY_URL环境变量必须包含
http://协议前缀的要求,添加常见错误示例和容器重建步骤,
帮助用户避免注册页面502错误问题。
2026-03-19 10:26:43 +08:00

17 KiB
Raw Blame History

nano_project 本机一步步部署指南

这份文档适合第一次在本机把整个项目跑起来的人,目标是:

  • 在一台 LinuxWSL2 Ubuntu 机器上
  • Docker 跑完整链路
  • 最后能在浏览器里注册账号,并自动创建你的专属实例

这套项目当前的推荐本机测试方式是:

  • auth-portal
  • authz-service
  • deploy-control
  • router-proxy
  • app-instance

全部一起跑。

如果你只单独跑某个前端页面,页面能打开,但注册、登录、创建实例这些核心能力不一定会通。


0. 先说前提

适合的环境

推荐:

  • Linux
  • WSL2 Ubuntu

不推荐直接按这份文档在纯 Windows 命令行里照抄,因为这里依赖:

  • Docker
  • Bash 脚本
  • Docker Socket 挂载
  • 宿主机目录挂载

你需要先装好的工具

  • docker
  • git
  • curl
  • openssl
  • python3

先检查:

docker --version
docker ps
python3 --version
openssl version
curl --version

如果 docker ps 报错,先把 Docker 启动起来。


1. 进入项目根目录

cd /home/ivan/xuan/nano_project

你执行完以后,建议顺手确认一下当前目录:

pwd

你应该看到:

/home/ivan/xuan/nano_project

2. 准备一套本机测试变量

为什么这里用 127.0.0.1.nip.io

因为这是最省事的本机测试域名方案。

它的作用是:

  • alice.127.0.0.1.nip.io
  • 自动解析到 127.0.0.1

这样 router-proxy 就能按子域名区分不同实例。

直接复制执行

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=127.0.0.1
export NANO_BASE_DOMAIN=127.0.0.1.nip.io

export NANO_PROVIDER=openai
export NANO_MODEL=openai/gpt-5
export NANO_API_KEY='把这里换成你自己的模型 API Key'
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'

这里每个变量大概是干什么的

  • PROJECT_ROOT
    • 仓库根目录
  • NANO_NET
    • 所有容器共用的 Docker 网络
  • NANO_DEPLOY_TOKEN
    • auth-portal / authz-servicedeploy-control 时的鉴权 token
  • NANO_AUTHZ_INTERNAL_TOKEN
    • AuthZ 内部接口 token
  • NANO_BASE_DOMAIN
    • 实例基础域名
  • NANO_PROVIDER
    • 新实例默认模型提供商
  • NANO_MODEL
    • 新实例默认模型
  • NANO_API_KEY
    • 新实例默认模型 API Key
  • NANO_API_BASE
    • 自定义模型网关地址,没有就留空
  • NANO_AUTHZ_URL
    • 容器网络内访问 AuthZ 的地址
  • NANO_DEPLOY_URL
    • 容器网络内访问 deploy-control 的地址
  • NANO_OUTLOOK_MCP_URL
    • 可选;如果你有独立 Outlook MCP 服务,可以在这里填
  • NANO_OUTLOOK_MCP_SERVER_ID
    • Outlook MCP 默认 server id当前推荐固定 outlook_mcp

一个特别重要的提醒

NANO_API_KEY 不能空着。

如果这里不填,新用户注册时虽然页面可能能走到一半,但自动创建 app-instance 时大概率失败,因为实例配置里需要 APP_INSTANCE_API_KEY

NANO_AUTHZ_URLNANO_DEPLOY_URL 也不能留空,而且必须带协议头。

正确写法:

http://nano-authz-service:19090
http://nano-deploy-control:8090

错误写法:

nano-authz-service:19090
nano-deploy-control:8090
172.19.207.13:19090
172.19.207.13:8090

如果这里漏了 http://,注册页很容易直接报:

502: Request URL is missing an 'http://' or 'https://' protocol.

还有一个很容易忽略的点:

  • 你在 shell 里重新 export NANO_DEPLOY_URL=...
  • 不会自动修改已经在运行中的 nano-authz-servicenano-auth-portal

也就是说:

  • 变量改对了
  • 但容器没重建

注册页还是会继续报同一个 502。

改完变量以后,至少要重建这些容器:

  • nano-authz-service
  • nano-auth-portal

3. 创建运行目录

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"

这一步的作用是给下面几个东西留持久化空间:

  • AuthZ 数据
  • 实例注册表
  • 每个用户实例的配置目录
  • router-proxy 生成出来的路由文件

4. 构建镜像

第一次构建会比较久,正常情况要等几分钟。

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

如果中间有某个镜像失败,不要继续往下跑,先把失败那一步修掉。

常见失败原因:

  • Docker 没启动
  • 网络拉镜像失败
  • 你的本机磁盘空间不够

5. 创建共享 Docker 网络

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

执行完后可确认:

docker network ls | grep "$NANO_NET"

应该能看到:

nano-instance-edge

6. 启动统一入口代理 router-proxy

cd "$PROJECT_ROOT"

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

启动后,统一入口走:

http://<你的实例slug>.127.0.0.1.nip.io:8088

例如:

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

7. 启动 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 这里不能写成:

http://127.0.0.1:19090

因为后面新创建的 app-instance 也是通过 Docker 网络去访问 AuthZ 的。

所以这里要写容器网络里可访问的地址:

http://nano-authz-service:19090

DEPLOY_API_BASE_URL 也一样,不能空,不能只写 host:port

这里如果传成空字符串,或者写成:

nano-deploy-control:8090

注册页会在 authz-service -> deploy-control 这一步直接报:

502: Request URL is missing an 'http://' or 'https://' protocol.

启动完可以立刻确认:

docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)='

8. 启动 deploy-control

这一步最容易配错的点是挂载目录。

一定要注意:

  • app-instancerouter-proxy 的宿主机路径,要按原路径挂进容器
  • 不能偷懒挂到容器里的另一个短路径比如 /app-instance
  • 同时要把 APP_INSTANCE_DIRROUTER_PROXY_DIR 也明确传进去

直接执行:

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

这一步在做什么

deploy-control 会负责:

  • 收到“创建实例”的请求
  • 调用 app-instance/create-instance.sh
  • 通过 Docker 创建对应用户实例
  • 刷新 router-proxy

9. 启动 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

这个页面就是用户看到的登录/注册入口。

虽然注册入口主要依赖 AUTHZ_API_BASE_URL,这里还是建议把 DEPLOY_API_BASE_URL 一起带上并确认非空,避免后面运行态调用 deploy-control 时再踩同一个坑。

启动完可以确认:

docker inspect nano-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)='

10. 做健康检查

先检查接口

curl http://127.0.0.1:19090/healthz
curl http://127.0.0.1:8090/healthz
curl -I http://127.0.0.1:3081

你应该大致看到:

  • authz-service 返回健康 JSON
  • deploy-control 返回健康 JSON
  • auth-portal 返回 HTTP/1.1 200 OK

再看容器状态

docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

你至少应该能看到这些容器:

  • nano-authz-service
  • nano-deploy-control
  • nano-auth-portal
  • nano-router-proxy

再看一下代理日志

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

如果这一步没有明显报错,就可以开始浏览器测试了。


11. 浏览器首次测试

打开:

http://127.0.0.1:3081/register

然后按顺序操作:

  1. 注册一个新账号
  2. 注册成功后,系统会自动创建一个你的专属实例
  3. 浏览器应该跳到你的实例地址

跳转目标一般长这样:

http://你的slug.127.0.0.1.nip.io:8088

例如:

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

12. 确认实例真的被创建出来了

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

你应该能看到类似:

  • instance_id
  • instance_slug
  • container_name
  • public_url

以及对应的 app-instance-<slug> 容器。

你还可以继续查:

docker ps --format 'table {{.Names}}\t{{.Status}}' | grep app-instance

13. 如果你只是想单独看前端页面

如果你只是想看 auth-portal 页面样子,不跑全链路,也可以单独启动它的前端开发模式:

cd /home/ivan/xuan/nano_project/auth-portal/src
npm install
npm run dev

然后打开:

http://127.0.0.1:3081

但是要注意:

  • 这只能看页面
  • 注册、登录、创建实例这些动作是否成功,仍然取决于 authz-servicedeploy-control 有没有另外启动

14. 一键排错命令

如果你感觉“不对劲”,先跑这几条:

docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

docker logs --tail=100 nano-authz-service
docker logs --tail=100 nano-deploy-control
docker logs --tail=100 nano-auth-portal
docker logs --tail=100 nano-router-proxy

curl http://127.0.0.1:19090/healthz
curl http://127.0.0.1:8090/healthz
curl -I http://127.0.0.1:3081

如果是实例创建失败,再加两条:

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

docker ps --format 'table {{.Names}}\t{{.Status}}' | grep app-instance

如果注册页弹出:

502: Request URL is missing an 'http://' or 'https://' protocol.

优先查这两条:

docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)='
docker inspect nano-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)='

重点看:

  • nano-authz-service 里的 DEPLOY_API_BASE_URL
  • nano-auth-portal 里的 AUTHZ_API_BASE_URL

它们都必须是完整 URL不能是空字符串也不能是裸 host:port

如果你已经改过 export NANO_DEPLOY_URL=...,但这里查出来还是空,说明你只是改了当前 shell 变量,没有把容器重建掉。

这时直接按下面重建:

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

export NANO_DEPLOY_TOKEN="$(docker inspect nano-deploy-control --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^DEPLOY_CONTROL_API_TOKEN=//p')"
export NANO_AUTHZ_INTERNAL_TOKEN="$(docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^AUTHZ_INTERNAL_TOKEN=//p')"

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

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

重建后再确认:

docker inspect nano-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)='
docker inspect nano-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)='

你必须看到:

DEPLOY_API_BASE_URL=http://nano-deploy-control:8090

15. 最常见的坑

1. API Key 没填

现象:

  • 注册页面提交后创建实例失败

原因:

  • APP_INSTANCE_API_KEY 没有有效值

2. Docker 没启动

现象:

  • deploy-control 无法创建实例
  • docker ps 本身就报错

3. AUTHZ_ISSUER 写成了 127.0.0.1

错误写法:

http://127.0.0.1:19090

正确写法:

http://nano-authz-service:19090

原因:

  • 新实例容器里访问不到宿主机自己的 127.0.0.1:19090

4. deploy-control 的路径挂载写错

错误思路:

  • 把宿主机的 app-instance 挂到容器里的 /app-instance

正确思路:

  • 宿主机原路径挂进去
  • 并设置:
    • APP_INSTANCE_DIR="$PROJECT_ROOT/app-instance"
    • ROUTER_PROXY_DIR="$PROJECT_ROOT/router-proxy"

5. NANO_DEPLOY_URLNANO_AUTHZ_URL 没带 http://

典型现象:

  • 注册页直接弹:
    • 502: Request URL is missing an 'http://' or 'https://' protocol.

原因:

  • authz-serviceDEPLOY_API_BASE_URL 去调 deploy-control
  • auth-portalAUTHZ_API_BASE_URL 去调 authz-service
  • 这些值如果是空,或者写成 nano-deploy-control:8090 这种不带协议的字符串,请求会直接失败
  • 就算你后来在 shell 里改对了,如果没重建相关容器,老的错误值仍然会继续生效

正确写法:

http://nano-authz-service:19090
http://nano-deploy-control:8090

6. nip.io 解析失败

如果实例跳转地址打不开,先试:

ping 127.0.0.1.nip.io

如果你本地网络把 nip.io 拦了,这套子域名测试方式就会失效。

7. 端口被占用

默认会用到这些端口:

  • 3081
  • 8090
  • 19090
  • 8088

你可以先查:

ss -ltnp | grep -E '3081|8090|19090|8088'

16. 如果你要重新来一遍

如果你只是想“重新部署这四个基础容器”,可以先停掉它们:

docker rm -f \
  nano-auth-portal \
  nano-authz-service \
  nano-deploy-control \
  nano-router-proxy 2>/dev/null || true

如果你还想把旧实例容器也一起清掉,再额外处理 app-instance-*

注意:

  • 不要在你还需要旧数据的时候乱删 runtime/
  • authz-service/runtime/dataapp-instance/runtime/instances 里都有持久化数据

17. 本机部署成功后的结果应该是什么

如果整个流程正常,最后你会得到:

  • 一个可以打开的注册页:
    • http://127.0.0.1:3081/register
  • 一个统一实例入口代理:
    • http://<slug>.127.0.0.1.nip.io:8088
  • 一个能自动创建用户专属容器的部署控制面
  • 一份实例注册表:
    • app-instance/runtime/registry/instances.json

18. 你下一步最建议做什么

第一次建议这样测:

  1. 用全新用户名注册一个测试账号
  2. 确认浏览器跳到了你的实例 URL
  3. 再执行 ./app-instance/list-instances.sh --json
  4. 确认注册表里真的有这条实例记录

如果你后面想要,我还可以继续补两份文档:

  • 服务器部署指南.md
    • 面向公网服务器、固定 IP、长期运行
  • 常见报错排查.md
    • 专门收集 502、超时、实例起不来、MCP 鉴权失败这类问题