wangdefa 489cb49cb3
CI / test (push) Successful in 31s
Release / release (push) Successful in 54s
AI 网关切换 OpenAI 兼容面并移除 chat 端点,新增模型黑白名单
2026-07-12 17:48:28 +08:00

OCI Portal logo

OCI Portal

Oracle Cloud Infrastructure 多租户管理面板

Go License Docker

本仓库为后端;前端工程见 oci-portal-dash(构建产物嵌入本服务成单文件)

界面预览

总览 登录
总览 登录
租户 任务
租户 任务
AI 网关 通知设置
AI 网关 通知设置

特性

  • 多租户管理:多份 OCI API Key 配置集中管理,私钥/口令 AES-256-GCM 加密落库;分组、批量测活、账户类型与订阅信息识别
  • 资源管理:实例(含创建/电源操作/更换公网 IP/IPv6/VNIC/引导卷)、VCN/子网/安全列表、块存储、限额与成本查询,多区域/多区间支持
  • 抢机任务:cron 周期尝试创建实例直到成功,支持熔断与通知
  • 网页控制台:实例串行控制台(xterm)与 VNC(noVNC),两跳 SSH 隧道
  • AI 网关OpenAI Responses / Anthropic Messages / Embeddings 兼容端点转发 OCI GenAI,号池渠道加权负载均衡、熔断探测、密钥管理与用量日志;对话统一走 OCI OpenAI 兼容面(xai / meta / openai 厂商模型,流式事件与推理增量原样保真),支持 xAI Grok 服务端工具 web_search / x_search(格式同 xAI 官方 Agent Tools,仅非流式)——兼容面属实测可用但未见 Oracle 文档承诺的能力,行为可能随上游调整
  • 日志回传OCI Audit 事件经 Connector Hub → Notifications HTTPS 订阅回传入库,一键创建链路;自定义告警规则(事件类型/来源 IP 白名单/资源/频率阈值)命中即推送
  • 租户治理IAM 用户/MFA/API Key 管理、密码策略、身份提供商(SAML)、通知收件人;多 Identity Domain 租户可按域切换管理
  • 安全JWT + bcrypt(凭据变更旧令牌立即失效,可一键撤销全部会话)、TOTP 两步验证、OIDC/GitHub 外部登录、登录锁定、IP 限速、真实 IP 头可配、请求体/超时防护、系统操作审计
  • 通知:模板化推送,五渠道并存(Telegram / Webhook / ntfy / Bark / SMTP),Webhook 可对接飞书、钉钉、Slack、企业微信机器人

快速开始

二进制运行

从 Release 下载对应架构的二进制后:

DATA_KEY=$(openssl rand -hex 32) JWT_SECRET=$(openssl rand -hex 32) ADMIN_PASSWORD=<初始密码> ./oci-portal-server

访问 http://localhost:8080,用 admin 与初始密码登录。

Docker Compose

# 镜像以 distroless nonroot(uid 65532)运行,数据卷需可写,否则 SQLite 报 unable to open database file
mkdir -p ./data && sudo chown 65532:65532 ./data
DATA_KEY=$(openssl rand -hex 32) JWT_SECRET=$(openssl rand -hex 32) ADMIN_PASSWORD=<初始密码> docker compose up -d

默认只监听 127.0.0.1:18888;公网访问须置于 TLS 反向代理之后,见下文「反向代理」。

源码构建(单文件,含前端)

# 1. 获取前端产物:从前端仓库 Release 下载 dist.zip(或本地 npm run build 后拷入)
curl -fL -o dist.zip https://github.com/wangdefaa/oci-portal-dash/releases/latest/download/dist.zip
rm -rf internal/webui/dist && mkdir -p internal/webui/dist && unzip -q dist.zip -d internal/webui/dist
# 2. 编译(免 CGO,可交叉编译;-X 两项注入「设置·关于」页的版本与构建时间,可省略,省略则显示 dev)
CGO_ENABLED=0 go build -trimpath \
  -ldflags "-s -w -X oci-portal/internal/api.buildVersion=v0.0.1 -X oci-portal/internal/api.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -o bin/oci-portal-server ./cmd/server

注意:上述解压会覆盖仓库占位文件 internal/webui/dist/index.html,提交代码前勿把真实产物带入版本库。

反向代理(公网部署必读)

面板自身只提供 HTTP,管理员口令、JWT 与租户 API 私钥都会经明文承载。除本机试用外,应让面板仅监听回环地址(compose 示例已默认 127.0.0.1:18888),由支持 TLS 的反向代理对外提供 HTTPS。

Caddy 最小示例(整站反代,自动申请并续期 Let's Encrypt 证书,WebSocket 自动透传):

portal.example.com {
	reverse_proxy 127.0.0.1:18888
}

nginx 示例(证书自备;Web Console 的 WebSocket 升级与长超时必须显式配置,否则串行终端连不上或空闲即断):

# http 块内:按请求是否升级为 WebSocket 决定 Connection 头
map $http_upgrade $connection_upgrade {
    default upgrade;
    ""      close;
}

server {
    listen 443 ssl;
    server_name portal.example.com;

    ssl_certificate     /etc/nginx/certs/portal.example.com.crt;
    ssl_certificate_key /etc/nginx/certs/portal.example.com.key;

    # 放行到最大入口(AI 网关 10MB);各入口更细的上限由后端自身执行
    client_max_body_size 10m;

    location / {
        proxy_pass http://127.0.0.1:18888;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # WebSocket 空闲与 AI 流式响应都需要长读超时(默认 60s 会断)
        proxy_read_timeout 1h;
        proxy_send_timeout 1h;
    }
}

Traefik 示例(已运行 Traefik 的 Docker 环境,面板容器加 label 接入即可,WebSocket 透明支持):

services:
  oci-portal:
    image: ghcr.io/wangdefaa/oci-portal:latest
    # ……环境变量与数据卷同上文 compose 示例;与 Traefik 同一 docker 网络,
    # Traefik 经容器网络直达 8080,无需(也不应)对外映射 ports
    labels:
      - traefik.enable=true
      - traefik.http.routers.oci-portal.rule=Host(`portal.example.com`)
      - traefik.http.routers.oci-portal.entrypoints=websecure
      - traefik.http.routers.oci-portal.tls.certresolver=le   # 换成你的 certResolver 名
      - traefik.http.services.oci-portal.loadbalancer.server.port=8080

若前端静态文件由反代直接伺服(不走内嵌页面),则按前缀代理,缺一不可:

前缀 内容 何时需要
/api/* 面板 REST、Web Console WebSocket、日志回传 webhook 始终
/ai/* AI 网关(OpenAI / Claude 兼容端点,独立密钥鉴权) 启用 AI 网关时
其余路径 前端 SPA 静态文件(404 回退 index.html 静态分离形态
  • 反代默认追加的 X-Forwarded-For 用于还原真实客户端 IP,系统日志留痕、登录锁定与 IP 限速都依赖它
  • 请求体上限建议与后端一致:/api/* 1MB、/ai/* 10MBCaddy 用 request_body 按前缀分层)

AI 网关接口

对外提供 OpenAI / Anthropic 兼容端点,转发 OCI GenAI On-Demand 推理。鉴权用面板创建的 AI 密钥,Authorization: Bearer sk-...x-api-key: sk-... 双头均可;密钥可绑定渠道分组实现路由隔离,也可配置模型白名单(白名单外调用 404,模型列表只返回交集)。请求体上限 10MB。

端点 协议 流式
POST /ai/v1/responses OpenAI Responses(无状态子集) SSE 支持(服务端工具除外)
POST /ai/v1/messages Anthropic Messages SSE 支持
POST /ai/v1/embeddings OpenAI Embeddings
GET /ai/v1/models OpenAI 模型列表(来自渠道模型目录)

对话端点统一转发 OCI OpenAI 兼容面(/actions/v1/responses),因此仅提供该面支持的 xai. / meta. / openai. 前缀模型;google / cohere 对话模型不在兼容面供给(上游 400),不再提供(cohere embed 模型不受影响)。兼容面属实测可用但无 Oracle 文档承诺的能力,行为可能随上游调整。Chat Completions/ai/v1/chat/completions)端点已移除,请迁移到 Responses 或 Messages。

以下各表对照网关行为: 转发上游;⚠️ 接受但忽略(静默丢弃,不影响请求); 拒绝(400,不发上游)。

Responses 字段

请求体除下列例外原样直通上游(未列字段一并转发,效果由上游决定):

字段 支持 说明
modelinput input 接受 string 或 item 数组
stream SSE,上游原生事件流(含 gpt-oss 推理增量);与服务端工具互斥
reasoning.effort 直达上游不限档位,见下「推理力度」
store ⚠️ 网关无状态,强制改写为 false
previous_response_idconversationbackground 网关不保存历史,请求需自带完整上下文
web_search / x_search / function 之外的工具类型 file_searchcode_interpretermcp

服务端工具tools 支持 {"type":"web_search"}{"type":"x_search"}xAI Grok 官方 Agent Tools 格式,可与 function 混用),响应保留 web_search_call 输出项与引用;仅非流式。响应 usage.input_tokens_details.cached_tokens 透传缓存命中量。

Anthropic Messages 字段

网关把 Messages 请求转换为 Responses 请求送上游,响应(含流式事件序列)转回 Anthropic 形态:

字段 支持 说明
modelmax_tokensmessages max_tokens 必填;content 块支持 text / image / tool_use / tool_result,其余块类型 400
system string 或块数组,映射为 instructions
temperaturetop_p
stream SSE,标准 Anthropic 事件序列(message_startcontent_block_*message_deltamessage_stop
toolstool_choice 映射为 Responses function 工具;tool_choice 支持 auto / any / none / tool
output_config.effort 直达上游不限档位,见下「推理力度」
stop_sequencestop_kmetadatathinkingservice_tier ⚠️ Responses 面无对应物,静默忽略

模型的思考输出(reasoning)不转为 thinking 块,静默丢弃;usage.cache_read_input_tokens 透传缓存命中量。

Embeddings 字段

字段 支持 说明
modelinput input 接受 string 或数组
dimensions
encoding_formatuser ⚠️ 输出恒为 float 数组

推理力度(effort

两个对话协议各以原生字段控制推理深度,取值直达上游不做档位校验:

协议 字段
Responses reasoning.effort
Anthropic Messages output_config.effort(转小写后透传)

是否支持、可用档位与实际效果由模型决定(2026-07 兼容面实测):

模型 支持档位 实测行为
xai.grok-4.3 none / minimal / low / medium / high 全部生效:none 完全关闭推理,默认 lowhigh 推理量最大
openai.gpt-oss-* minimal / low / medium / high none 被上游 400(Harmony 格式不支持);思考计入 completion tokens
xai.grok-3-mini(-fast) 名义 low / high 各档均被接受,但 none 并不关闭推理(仍产生思考)
meta.llama-* 接受任意档 非推理模型,参数无实际效果
xai.grok-4.20-multi-agent* low / medium / high / xhigh effort 控制并行 agent 数(4 / 16)而非推理深度
其余 grokgrok-3grok-4grok-4-fast-*grok-4-1-fast-*grok-code-fast-1grok-4.20-* 单/双型号全系) 不支持 携带即被上游 400does not support parameter reasoningEffort

不携带该字段时网关不下发,行为由模型默认档位决定。

通用行为

  • 未知模型 404;无可用渠道 503;单次请求最多尝试 3 个渠道(429/5xx/网络错误自动换渠道并计入熔断,模型级 400/404 换渠道不计熔断,其余 4xx 原样透传);流式建立后绑定渠道,中断不重试
  • 调用计量(渠道、token 用量、缓存命中、时延、重试数)写入面板「调用日志」;密钥可按需开启限时内容抓取

环境变量

变量 必填 默认值 说明
DATA_KEY 敏感字段加密主密钥(更换后已入库密文无法解密)
JWT_SECRET 登录令牌签名密钥
ADMIN_USERNAME admin 初始管理员用户名
ADMIN_PASSWORD 首次启动是 仅在用户不存在时创建;已存在不重置
ADDR :8080 HTTP 监听地址
DB_DRIVER sqlite sqlite / mysql / postgres(后两者 experimental
DB_DSN 外部库时是 MySQL 需 parseTime=TruePostgreSQL 标准 DSN
DB_PATH oci-portal.db SQLite 文件路径
PUBLIC_URL 面板公网基址,日志回传一键创建链路用
HTTPS_PROXY 出站代理(如 Telegram 通知走代理)
TZ 系统 cron 表达式解释时区(容器内建议显式设置,二进制已嵌 tzdata)
SWAGGER 1 时开放 /swagger/index.html API 文档(生产建议按需临时开启)
GIN_MODE release debug / release,影响日志与调试输出

开发

go test ./...            # 全量测试
go vet ./... && gofmt -l .
go tool swag init -g cmd/server/main.go -o docs --parseInternal --parseDependency   # 接口注释变更后重新生成 OpenAPI

API 文档:全部接口带 swaggo 注释,SWAGGER=1 启动后访问 /swagger/index.htmlspec 文件在 docs/swagger.json|yaml

编码规范与项目约定见 AGENTS.md.trellis/spec/

License

MIT

S
Description
No description provided
Readme MIT
21 MiB
v0.8.3
Latest
2026-07-30 12:44:14 +08:00
Languages
Go 99.9%
Dockerfile 0.1%