7706f59549c736a4d003c5e46cf5b8d0bd0d3792
界面预览
| 总览 | 登录 |
|---|---|
![]() |
![]() |
| 租户 | 任务 |
|---|---|
![]() |
![]() |
| AI 网关 | 通知设置 |
|---|---|
![]() |
![]() |
特性
- 多租户管理:多份 OCI API Key 配置集中管理,私钥/口令 AES-256-GCM 加密落库;分组、批量测活、账户类型与订阅信息识别
- 资源管理:实例(含创建/电源操作/更换公网 IP/IPv6/VNIC/引导卷)、VCN/子网/安全列表、块存储、限额与成本查询,多区域/多区间支持
- 抢机任务:cron 周期尝试创建实例直到成功,支持熔断与通知
- 网页控制台:实例串行控制台(xterm)与 VNC(noVNC),两跳 SSH 隧道
- AI 网关:OpenAI / Anthropic 兼容端点转发 OCI GenAI(对话/Responses/Embeddings),号池渠道加权负载均衡、熔断探测、密钥管理与用量日志
- 日志回传: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/*10MB(Caddy 用request_body按前缀分层)
环境变量
| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
DATA_KEY |
是 | 无 | 敏感字段加密主密钥(更换后已入库密文无法解密) |
JWT_SECRET |
是 | 无 | 登录令牌签名密钥 |
ADMIN_USERNAME |
否 | admin |
初始管理员用户名 |
ADMIN_PASSWORD |
首次启动是 | 无 | 仅在用户不存在时创建;已存在不重置 |
ADDR |
否 | :8080 |
HTTP 监听地址 |
DB_DRIVER |
否 | sqlite |
sqlite / mysql / postgres(后两者 experimental) |
DB_DSN |
外部库时是 | 无 | MySQL 需 parseTime=True;PostgreSQL 标准 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.html;spec 文件在 docs/swagger.json|yaml。
编码规范与项目约定见 AGENTS.md 与 .trellis/spec/。





