- POST /ai/v1/tts:xAI 官方 TTS 格式转换层(text/voice_id→input/voice),复用 Speech 编排,model 缺省 xai.grok-tts;实测 output_format/speed 透传生效 - 「过滤弃用模型」开关(GET/PUT /api/v1/ai-settings,settings 表持久化):开启后已宣布弃用(未退役)模型从列表与路由排除;同步入库与退役提醒不受影响 - AI 网关文档更名 docs/AI网关.md,补 tts 矩阵段与开关说明;README 引用同步 - CHANGELOG 0.5.0(0.4.0 已发版冻结);DASH_VERSION v0.5.0
274 lines
12 KiB
Markdown
274 lines
12 KiB
Markdown
<div align="center">
|
||
|
||
<img src="docs/assets/logo.svg" width="104" alt="OCI Portal logo">
|
||
|
||
# OCI Portal
|
||
|
||
**自托管的 OCI 多租户管理面板与 GenAI 兼容网关**
|
||
|
||

|
||

|
||

|
||

|
||
|
||
[快速开始](#快速开始) · [核心能力](#核心能力) · [生产部署](#生产部署) · [AI 网关](#ai-网关) · [开发](#开发)
|
||
|
||
</div>
|
||
|
||
OCI Portal 将多份 OCI API Key、云资源、自动化任务、审计事件和 OCI GenAI 渠道集中到一个管理界面。Vue 前端通过 `go:embed` 嵌入 Go 服务,Release 以单个二进制和多架构容器镜像交付。
|
||
|
||
本仓库为后端与发行仓库;前端源码位于 [oci-portal-dash](https://github.com/wangdefaa/oci-portal-dash)。
|
||
|
||
## 界面预览
|
||
|
||
| 总览 | 登录 |
|
||
| --- | --- |
|
||
|  |  |
|
||
|
||
| 租户 | 任务 |
|
||
| --- | --- |
|
||
|  |  |
|
||
|
||
| AI 网关 | 通知设置 |
|
||
| --- | --- |
|
||
|  |  |
|
||
|
||
## 核心能力
|
||
|
||
- **租户与区域**:集中管理多份 OCI API Key,支持分组、批量测活、账户画像、订阅区域缓存与区域切换;私钥和口令使用 AES-256-GCM 加密落库
|
||
- **计算、网络与存储**:实例创建与电源操作、公网 IP、IPv6、VNIC、串行控制台连接,VCN / 子网 / 安全列表,引导卷与块存储挂载,限额和成本查询
|
||
- **自动化任务**:抢机、租户测活、成本同步、AI 渠道探测四类 cron 任务,提供执行日志、重叠执行防护、熔断与结果通知
|
||
- **网页控制台**:浏览器内使用 xterm 串行终端和 noVNC,通过 OCI 控制台连接建立两跳 SSH 隧道
|
||
- **身份与审计**:IAM 用户、MFA、API Key、密码策略、SAML 身份提供商、通知收件人和多 Identity Domain 管理;OCI Audit 事件可经 Service Connector Hub 与 Notifications 回传,关键事件按类别通过「云端事件」通知推送
|
||
- **通知与安全**:Telegram、Webhook、ntfy、Bark、SMTP 五类渠道;JWT、bcrypt、TOTP、OIDC / GitHub 登录、登录锁定、IP 限速、会话撤销和系统操作审计
|
||
- **AI 网关**:提供 OpenAI Responses、Chat Completions、Embeddings 与 Anthropic Messages 兼容接口,支持渠道分组、加权路由、熔断探测、模型黑白名单、密钥管理和调用日志
|
||
|
||
## 运行形态
|
||
|
||
```text
|
||
浏览器 / API 客户端
|
||
│
|
||
▼
|
||
┌──────────────────── OCI Portal 单个 Go 进程 ────────────────────┐
|
||
│ Vue 3 静态资源(go:embed) │
|
||
│ /api/v1 → Gin → Service → OCI SDK │
|
||
│ /ai/v1 → 兼容转换与路由 → OCI Generative AI │
|
||
│ GORM → SQLite(默认)/ MySQL / PostgreSQL(experimental) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
默认推荐 SQLite 单实例部署。MySQL 和 PostgreSQL 适配仍属 experimental,不应据此推断服务支持多副本并发运行。
|
||
|
||
## 快速开始
|
||
|
||
### Docker Compose(推荐)
|
||
|
||
前置条件:Docker、Docker Compose v2、OpenSSL。
|
||
|
||
1. 克隆仓库并生成一份需要长期保存的 `.env`:
|
||
|
||
```bash
|
||
git clone https://github.com/wangdefaa/oci-portal.git
|
||
cd oci-portal
|
||
|
||
umask 077
|
||
printf 'DATA_KEY=%s\nJWT_SECRET=%s\nADMIN_PASSWORD=%s\n' \
|
||
"$(openssl rand -hex 32)" \
|
||
"$(openssl rand -hex 32)" \
|
||
"$(openssl rand -base64 24)" > .env
|
||
chmod 600 .env
|
||
```
|
||
|
||
2. 准备数据目录并启动:
|
||
|
||
```bash
|
||
mkdir -p data
|
||
|
||
# Linux bind mount 需要让镜像内的 nonroot 用户(uid 65532)可写。
|
||
sudo chown 65532:65532 data
|
||
|
||
docker compose up -d
|
||
docker compose ps
|
||
```
|
||
|
||
3. 查看初始管理员密码并登录:
|
||
|
||
```bash
|
||
grep '^ADMIN_PASSWORD=' .env
|
||
```
|
||
|
||
访问 `http://127.0.0.1:18888`,默认用户名为 `admin`。
|
||
|
||
> `DATA_KEY` 用于解密数据库中的 OCI 私钥、口令和渠道凭据。它只能生成一次并持续复用;丢失或更换后,已有密文无法恢复。请将 `.env` 与 `data/oci-portal.db` 一起备份。
|
||
|
||
### 二进制运行
|
||
|
||
Release 提供 Linux amd64 / arm64 二进制。以下以 amd64 为例;arm64 主机将文件名中的 `amd64` 替换为 `arm64`:
|
||
|
||
```bash
|
||
curl -fLO https://github.com/wangdefaa/oci-portal/releases/latest/download/oci-portal-server-linux-amd64
|
||
chmod +x oci-portal-server-linux-amd64
|
||
|
||
# 复用上文生成并妥善保存的 .env。
|
||
set -a
|
||
. ./.env
|
||
set +a
|
||
|
||
./oci-portal-server-linux-amd64
|
||
```
|
||
|
||
默认访问地址为 `http://localhost:8080`。`ADMIN_PASSWORD` 只在数据库中没有用户时创建初始管理员,后续启动不会用它重置密码。
|
||
|
||
### 源码构建
|
||
|
||
源码构建要求 Go 1.26.5、`curl`、`unzip` 和 `sha256sum`。仓库只保留前端占位页,编译完整单文件前应下载 `DASH_VERSION` 指定的前端产物并校验:
|
||
|
||
```bash
|
||
DASH_TAG="$(tr -d '\r\n' < DASH_VERSION)"
|
||
DASH_BASE="https://github.com/wangdefaa/oci-portal-dash/releases/download/${DASH_TAG}"
|
||
|
||
curl -fL -o dist.zip "${DASH_BASE}/dist.zip"
|
||
curl -fL -o dist.zip.sha256 "${DASH_BASE}/dist.zip.sha256"
|
||
sha256sum -c dist.zip.sha256
|
||
|
||
rm -rf internal/webui/dist
|
||
mkdir -p internal/webui/dist
|
||
unzip -q dist.zip -d internal/webui/dist
|
||
|
||
CGO_ENABLED=0 go build -trimpath -o bin/oci-portal-server ./cmd/server
|
||
```
|
||
|
||
本地构建显示 `dev` 版本;Release 工作流会注入正式版本和构建时间。`internal/webui/dist` 中的真实前端产物不应提交到仓库。
|
||
|
||
## 生产部署
|
||
|
||
服务本身只提供 HTTP。除本机试用外,应保持服务仅监听回环地址或容器内部网络,并由 TLS 反向代理提供 HTTPS;否则管理员密码、JWT、AI 密钥和租户凭据会经明文连接传输。
|
||
|
||
### Caddy
|
||
|
||
```caddyfile
|
||
portal.example.com {
|
||
reverse_proxy 127.0.0.1:18888
|
||
}
|
||
```
|
||
|
||
Caddy 会自动处理 WebSocket。使用 nginx、Traefik 或其他反向代理时,还需要满足:
|
||
|
||
- 为串行终端和 VNC 转发 WebSocket Upgrade 头
|
||
- 为 AI 流式响应和控制台连接设置较长的读写超时,建议 `1h`
|
||
- 整站请求体上限至少为 `10MB`;后端会继续限制面板 API 为 `1MB`、AI 网关为 `10MB`
|
||
- 将面板内「设置 → 安全 → 真实 IP 请求头」配置为反向代理实际写入的头;系统审计、登录锁定和 IP 限速依赖它
|
||
- 配置 `PUBLIC_URL` 或面板地址,供 OAuth 回调和 OCI 日志回传链路使用
|
||
|
||
<details>
|
||
<summary>nginx 最小示例</summary>
|
||
|
||
```nginx
|
||
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;
|
||
|
||
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;
|
||
proxy_read_timeout 1h;
|
||
proxy_send_timeout 1h;
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
若前端静态文件与后端分离部署,`/api/*` 和 `/ai/*` 必须代理到后端,其余路径由 SPA 静态服务处理并回退到 `index.html`。Traefik 通过容器网络连接时应直达容器端口 `8080`,无需暴露宿主机端口。
|
||
|
||
## AI 网关
|
||
|
||
面板内置 OpenAI / Anthropic 兼容的 GenAI 网关:独立密钥鉴权(`Authorization: Bearer sk-...` 或 `x-api-key`),支持渠道分组、加权路由、熔断探测、模型黑白名单、内容日志与调用日志。
|
||
|
||
| 端点 | 定位 | 流式 |
|
||
| --- | --- | --- |
|
||
| `POST /ai/v1/responses` | OpenAI Responses,无状态主接口(xAI 服务端工具 / MCP) | SSE |
|
||
| `POST /ai/v1/chat/completions` | OpenAI Chat Completions 兼容层 | SSE |
|
||
| `POST /ai/v1/messages` | Anthropic Messages 转换层 | SSE |
|
||
| `POST /ai/v1/embeddings` | OpenAI Embeddings | 否 |
|
||
| `POST /ai/v1/audio/speech` | 文本转语音(xAI Voice) | 否 |
|
||
| `POST /ai/v1/tts` | 文本转语音(xAI 官方格式) | 否 |
|
||
| `POST /ai/v1/rerank` | 文档重排(Cohere Rerank) | 否 |
|
||
| `POST /ai/v1/moderations` | 内容审核(OCI Guardrails) | 否 |
|
||
| `GET /ai/v1/models` | 当前密钥可见的模型列表 | 否 |
|
||
|
||
协议兼容边界、已知上游限制与逐字段兼容矩阵见 **[AI 网关文档](./docs/AI网关.md)**。
|
||
|
||
## API 与配置
|
||
|
||
### API 文档
|
||
|
||
| 路径 | 鉴权 | 用途 |
|
||
| --- | --- | --- |
|
||
| `/api/v1/*` | 登录返回的 JWT Bearer Token | 面板管理 API |
|
||
| `/ai/v1/*` | AI 网关密钥 | OpenAI / Anthropic 兼容接口 |
|
||
| `/api/v1/webhooks/oci-logs/:secret` | URL 中的独立回传密钥 | OCI Notifications 日志回传 |
|
||
|
||
OpenAPI 文件随仓库维护:[`docs/swagger.yaml`](docs/swagger.yaml) · [`docs/swagger.json`](docs/swagger.json)。
|
||
|
||
运行进程时设置 `SWAGGER=1` 可开放 `/swagger/index.html`。Swagger 默认关闭,生产环境建议仅在受控网络内按需开启。接口注释变更后必须重新生成 OpenAPI。
|
||
|
||
### 环境变量
|
||
|
||
| 变量 | 必填 | 默认值 | 说明 |
|
||
| --- | :---: | --- | --- |
|
||
| `DATA_KEY` | 是 | — | 敏感字段加密主密钥;必须持久保存,不能随意轮换 |
|
||
| `JWT_SECRET` | 是 | — | JWT 签名密钥;更换会使已有登录令牌失效 |
|
||
| `ADMIN_USERNAME` | 否 | `admin` | 初始管理员用户名 |
|
||
| `ADMIN_PASSWORD` | 首次启动 | — | 仅在数据库无用户时创建管理员,不会重置已有密码 |
|
||
| `ADDR` | 否 | `:8080` | HTTP 监听地址 |
|
||
| `DB_DRIVER` | 否 | `sqlite` | `sqlite` / `mysql` / `postgres`;后两者为 experimental |
|
||
| `DB_PATH` | SQLite | `oci-portal.db` | SQLite 文件路径 |
|
||
| `DB_DSN` | 外部数据库 | — | MySQL 需 `parseTime=True`;不要在日志或文档中暴露凭据 |
|
||
| `PUBLIC_URL` | 否 | — | 面板公网基址,作为 OAuth 回调和日志回传引导的回退值 |
|
||
| `TZ` | 否 | 系统时区 | cron 表达式的解释时区;容器示例使用 `Asia/Shanghai` |
|
||
| `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` | 否 | — | Go 标准出站代理变量;面板内显式代理配置优先用于对应业务 |
|
||
| `SWAGGER` | 否 | 关闭 | 设为 `1` 时开放 Swagger UI |
|
||
| `GIN_MODE` | 否 | `release` | `debug` / `release` |
|
||
|
||
## 升级与备份
|
||
|
||
- 升级前同时备份 `.env` 和数据库;SQLite Compose 部署的数据文件为 `data/oci-portal.db`
|
||
- 保持 `DATA_KEY` 不变;只恢复数据库而没有原密钥,敏感字段无法解密
|
||
- 服务启动时会自动执行数据库迁移;跨版本升级前先阅读 [CHANGELOG](CHANGELOG.md)
|
||
- Compose 部署使用 `docker compose pull && docker compose up -d` 更新镜像
|
||
- OCI API Key 应遵循最小权限原则;生产环境保持 Swagger 关闭并限制管理面访问来源
|
||
|
||
## 开发
|
||
|
||
```bash
|
||
gofmt -l .
|
||
go vet ./...
|
||
go test ./...
|
||
|
||
# Handler 注释变更后重新生成唯一的对外 API 文档。
|
||
go tool swag init -g cmd/server/main.go -o docs --parseInternal --parseDependency --overridesFile docs/.swaggo
|
||
```
|
||
|
||
- Go 后端规范与目录约定:[`AGENTS.md`](AGENTS.md) · [`.trellis/spec/backend/`](.trellis/spec/backend/)
|
||
- 前端开发与构建:[oci-portal-dash](https://github.com/wangdefaa/oci-portal-dash)
|
||
- 版本变更:[`CHANGELOG.md`](CHANGELOG.md)
|
||
|
||
## License
|
||
|
||
[MIT](LICENSE)
|