feat(platform): 开发环境 - 新增 Docker 热更新并完善私网上游指引 (#122)

* feat(platform): 开发环境 - 新增 Docker 热更新开发配置

* docs(platform): 开发环境 - 记录 Fork 分支同步流程

* fix(platform): 开发环境 - 避让端口冲突并持久化构建缓存

* fix(platform): 模型渠道 - 改进私网上游配置指引

* docs(platform): 开发环境 - 本机升级流程仅保留在本地手册

* feat(security): 自定义渠道 - 允许白名单主机使用 HTTP 中转

---------

Co-authored-by: sqy941013 <sqy941013@users.noreply.github.com>
This commit is contained in:
JackFlynnandsqy941013 authored and GitHub committed 2026-08-04 16:22:27 +08:00
1 parent 50d8c2854a
commit 5e93fa7704
10 files changed
+173 -13

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
root = "."
tmp_dir = "/tmp/open-ai-canvas-air"
[build]
bin = "/tmp/open-ai-canvas-air/server"
cmd = "go build -o /tmp/open-ai-canvas-air/server ./cmd/server"
delay = 800
exclude_dir = ["data", "tmp", "vendor"]
exclude_regex = ["_test\\.go"]
include_ext = ["go", "mod", "sum"]
kill_delay = "500ms"
send_interrupt = true
stop_on_error = true
[log]
time = true
[misc]
clean_on_exit = true
+14
View File
@@ -0,0 +1,14 @@
ARG GO_IMAGE=golang:1.25-alpine
FROM ${GO_IMAGE}
ARG GOPROXY=https://proxy.golang.org,direct
ARG ALPINE_MIRROR
ENV GOPROXY=$GOPROXY
RUN if [ -n "$ALPINE_MIRROR" ]; then sed -i "s#https://dl-cdn.alpinelinux.org#${ALPINE_MIRROR}#g" /etc/apk/repositories; fi \
&& apk add --no-cache build-base git \
&& go install github.com/air-verse/air@v1.63.0
WORKDIR /workspace/backend
CMD ["air", "-c", ".air.toml"]
+8 -4
View File
@@ -62,8 +62,8 @@ func ValidateOutboundURL(rawURL string) (*url.URL, error) {
return parsed, nil
}
// 用户自定义渠道必须使用更严格的出口策略:只允许 HTTPS,不接受 URL 凭据,
// 仅部署者精确配置的主机可以路由到私网。
// 用户自定义渠道必须使用更严格的出口策略:不接受 URL 凭据,且仅部署者精确配置的
// 主机可以路由到私网或使用会明文传输 API Key 的 HTTP。
func ValidateCustomRelayURL(rawURL string) (*url.URL, error) {
if len(strings.TrimSpace(rawURL)) > 4096 {
return nil, BadAuthRequest("自定义渠道地址过长")
@@ -72,8 +72,12 @@ func ValidateCustomRelayURL(rawURL string) (*url.URL, error) {
if err != nil || parsed.Hostname() == "" || !parsed.IsAbs() {
return nil, BadAuthRequest("自定义渠道地址无效")
}
if parsed.Scheme != "https" {
return nil, BadAuthRequest("自定义渠道中转只支持 HTTPS")
scheme := strings.ToLower(parsed.Scheme)
if scheme != "https" && scheme != "http" {
return nil, BadAuthRequest("自定义渠道地址只支持 http/https")
}
if scheme == "http" && !allowedPrivateUpstreamHost(parsed.Hostname()) {
return nil, BadAuthRequest("自定义渠道 HTTP 仅允许访问已配置的可信上游主机")
}
if parsed.User != nil {
return nil, BadAuthRequest("自定义渠道地址不允许包含认证信息")
+9 -4
View File
@@ -105,18 +105,23 @@ func TestAllowedPrivateUpstreamHostUsesExactCaseInsensitiveMatch(t *testing.T) {
}
}
func TestValidateCustomRelayURLUsesHTTPSAndExactPrivateAllowlist(t *testing.T) {
func TestValidateCustomRelayURLAllowsHTTPOnlyForExactPrivateAllowlist(t *testing.T) {
t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true")
t.Setenv("CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS", "")
if _, err := ValidateCustomRelayURL("http://127.0.0.1:8080/v1/models"); err == nil {
t.Fatal("ValidateCustomRelayURL() should reject HTTP")
t.Fatal("ValidateCustomRelayURL() should reject HTTP without an exact host allowlist")
}
if _, err := ValidateCustomRelayURL("https://127.0.0.1:8080/v1/models"); err == nil {
t.Fatal("ValidateCustomRelayURL() should ignore the global private upstream override")
}
t.Setenv("CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS", "127.0.0.1")
if _, err := ValidateCustomRelayURL("https://127.0.0.1:8080/v1/models"); err != nil {
t.Fatalf("ValidateCustomRelayURL() error = %v", err)
for _, rawURL := range []string{
"http://127.0.0.1:8080/v1/models",
"https://127.0.0.1:8080/v1/models",
} {
if _, err := ValidateCustomRelayURL(rawURL); err != nil {
t.Fatalf("ValidateCustomRelayURL(%q) error = %v", rawURL, err)
}
}
}