Add API key authentication for proxy endpoints.
Support multiple API keys from config, env, and CLI, enforce auth on non-public endpoints, and pass keys through remote deploy verification. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
24
README.md
24
README.md
@@ -288,6 +288,30 @@ REMOTE_PASSWORD='your-password' \
|
|||||||
|
|
||||||
The script uploads `cmd/`, `internal/`, `vendor/`, `go.mod`, and `go.sum`, prepares a Docker build context on the server, then rebuilds the runtime image there with a multi-stage Docker build. The remote host needs `docker`, `tar`, and `curl`; it does not need a host Go installation, but it does need network access to pull the base Docker images if they are not already cached.
|
The script uploads `cmd/`, `internal/`, `vendor/`, `go.mod`, and `go.sum`, prepares a Docker build context on the server, then rebuilds the runtime image there with a multi-stage Docker build. The remote host needs `docker`, `tar`, and `curl`; it does not need a host Go installation, but it does need network access to pull the base Docker images if they are not already cached.
|
||||||
|
|
||||||
|
### API Authentication
|
||||||
|
|
||||||
|
Set one or more API keys with `LINGMA_PROXY_API_KEYS` or the JSON config field `api_keys`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LINGMA_PROXY_API_KEYS="key-one,key-two"
|
||||||
|
go run ./cmd/lingma-ipc-proxy --host 127.0.0.1 --port 8095
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"api_keys": ["key-one", "key-two"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The deployment script also passes through `LINGMA_PROXY_API_KEYS`, so the same key set can be enabled on the remote server during deploy.
|
||||||
|
|
||||||
|
When keys are configured, all API endpoints except `/`, `/health`, `/runtime/status`, and `/v1/runtime/status` require authentication. Clients can send either:
|
||||||
|
|
||||||
|
- `Authorization: Bearer <key>`
|
||||||
|
- `x-api-key: <key>`
|
||||||
|
|
||||||
|
This works well with OpenAI-compatible clients that already expose an API key field.
|
||||||
|
|
||||||
## Client Configuration
|
## Client Configuration
|
||||||
|
|
||||||
### Claude Code
|
### Claude Code
|
||||||
|
|||||||
@@ -363,6 +363,30 @@ REMOTE_PASSWORD='your-password' \
|
|||||||
|
|
||||||
脚本会上传 `cmd/`、`internal/`、`vendor/`、`go.mod` 和 `go.sum`,在服务器上准备 Docker 构建上下文,然后用多阶段 Docker build 重建运行时镜像。远端主机需要预装 `docker`、`tar`、`curl`;不需要额外安装宿主机 Go,但如果相关基础镜像未缓存,仍需要能拉取 Docker 基础镜像。
|
脚本会上传 `cmd/`、`internal/`、`vendor/`、`go.mod` 和 `go.sum`,在服务器上准备 Docker 构建上下文,然后用多阶段 Docker build 重建运行时镜像。远端主机需要预装 `docker`、`tar`、`curl`;不需要额外安装宿主机 Go,但如果相关基础镜像未缓存,仍需要能拉取 Docker 基础镜像。
|
||||||
|
|
||||||
|
### API 鉴权
|
||||||
|
|
||||||
|
可以通过环境变量 `LINGMA_PROXY_API_KEYS` 或 JSON 配置里的 `api_keys` 设置一个或多个 API Key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LINGMA_PROXY_API_KEYS="key-one,key-two"
|
||||||
|
go run ./cmd/lingma-ipc-proxy --host 127.0.0.1 --port 8095
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"api_keys": ["key-one", "key-two"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
部署脚本也会透传 `LINGMA_PROXY_API_KEYS`,所以可以在远端部署时一起启用同一组 key。
|
||||||
|
|
||||||
|
配置了 key 之后,除 `/`、`/health`、`/runtime/status`、`/v1/runtime/status` 之外的接口都会要求鉴权。客户端可以使用:
|
||||||
|
|
||||||
|
- `Authorization: Bearer <key>`
|
||||||
|
- `x-api-key: <key>`
|
||||||
|
|
||||||
|
这也兼容大多数 OpenAI 兼容客户端自带的 API Key 配置方式。
|
||||||
|
|
||||||
## 客户端配置
|
## 客户端配置
|
||||||
|
|
||||||
### Claude Code
|
### Claude Code
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type fileConfig struct {
|
|||||||
RemoteBaseURL string `json:"remote_base_url"`
|
RemoteBaseURL string `json:"remote_base_url"`
|
||||||
RemoteAuthFile string `json:"remote_auth_file"`
|
RemoteAuthFile string `json:"remote_auth_file"`
|
||||||
RemoteVersion string `json:"remote_version"`
|
RemoteVersion string `json:"remote_version"`
|
||||||
|
APIKeys []string `json:"api_keys"`
|
||||||
Cwd string `json:"cwd"`
|
Cwd string `json:"cwd"`
|
||||||
CurrentFilePath string `json:"current_file_path"`
|
CurrentFilePath string `json:"current_file_path"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
@@ -144,6 +145,7 @@ func loadConfig() (service.Config, string) {
|
|||||||
remoteBaseURL := flag.String("remote-base-url", cfg.RemoteBaseURL, "Remote Lingma API base URL")
|
remoteBaseURL := flag.String("remote-base-url", cfg.RemoteBaseURL, "Remote Lingma API base URL")
|
||||||
remoteAuthFile := flag.String("remote-auth-file", cfg.RemoteAuthFile, "Remote Lingma credentials.json path; empty reads ~/.lingma cache")
|
remoteAuthFile := flag.String("remote-auth-file", cfg.RemoteAuthFile, "Remote Lingma credentials.json path; empty reads ~/.lingma cache")
|
||||||
remoteVersion := flag.String("remote-version", cfg.RemoteVersion, "Remote Lingma cosy version")
|
remoteVersion := flag.String("remote-version", cfg.RemoteVersion, "Remote Lingma cosy version")
|
||||||
|
apiKeys := flag.String("api-keys", strings.Join(cfg.APIKeys, ","), "Comma-separated API keys accepted via Authorization Bearer or x-api-key")
|
||||||
cwd := flag.String("cwd", cfg.Cwd, "Working directory used when creating Lingma sessions")
|
cwd := flag.String("cwd", cfg.Cwd, "Working directory used when creating Lingma sessions")
|
||||||
currentFilePath := flag.String("current-file-path", cfg.CurrentFilePath, "Current file path sent through ACP meta")
|
currentFilePath := flag.String("current-file-path", cfg.CurrentFilePath, "Current file path sent through ACP meta")
|
||||||
mode := flag.String("mode", cfg.Mode, "Lingma ACP mode value")
|
mode := flag.String("mode", cfg.Mode, "Lingma ACP mode value")
|
||||||
@@ -181,6 +183,7 @@ func loadConfig() (service.Config, string) {
|
|||||||
cfg.RemoteBaseURL = strings.TrimSpace(*remoteBaseURL)
|
cfg.RemoteBaseURL = strings.TrimSpace(*remoteBaseURL)
|
||||||
cfg.RemoteAuthFile = strings.TrimSpace(*remoteAuthFile)
|
cfg.RemoteAuthFile = strings.TrimSpace(*remoteAuthFile)
|
||||||
cfg.RemoteVersion = strings.TrimSpace(*remoteVersion)
|
cfg.RemoteVersion = strings.TrimSpace(*remoteVersion)
|
||||||
|
cfg.APIKeys = splitCSV(*apiKeys)
|
||||||
cfg.Cwd = strings.TrimSpace(*cwd)
|
cfg.Cwd = strings.TrimSpace(*cwd)
|
||||||
cfg.CurrentFilePath = strings.TrimSpace(*currentFilePath)
|
cfg.CurrentFilePath = strings.TrimSpace(*currentFilePath)
|
||||||
cfg.Mode = strings.TrimSpace(*mode)
|
cfg.Mode = strings.TrimSpace(*mode)
|
||||||
@@ -268,6 +271,9 @@ func overlayFileConfig(dst *service.Config, src fileConfig) {
|
|||||||
if strings.TrimSpace(src.RemoteVersion) != "" {
|
if strings.TrimSpace(src.RemoteVersion) != "" {
|
||||||
dst.RemoteVersion = strings.TrimSpace(src.RemoteVersion)
|
dst.RemoteVersion = strings.TrimSpace(src.RemoteVersion)
|
||||||
}
|
}
|
||||||
|
if len(src.APIKeys) > 0 {
|
||||||
|
dst.APIKeys = cleanStringSlice(src.APIKeys)
|
||||||
|
}
|
||||||
if strings.TrimSpace(src.Cwd) != "" {
|
if strings.TrimSpace(src.Cwd) != "" {
|
||||||
dst.Cwd = strings.TrimSpace(src.Cwd)
|
dst.Cwd = strings.TrimSpace(src.Cwd)
|
||||||
}
|
}
|
||||||
@@ -361,6 +367,9 @@ func overlayEnvConfig(dst *service.Config) {
|
|||||||
if value := strings.TrimSpace(os.Getenv("LINGMA_REMOTE_VERSION")); value != "" {
|
if value := strings.TrimSpace(os.Getenv("LINGMA_REMOTE_VERSION")); value != "" {
|
||||||
dst.RemoteVersion = value
|
dst.RemoteVersion = value
|
||||||
}
|
}
|
||||||
|
if value := strings.TrimSpace(os.Getenv("LINGMA_PROXY_API_KEYS")); value != "" {
|
||||||
|
dst.APIKeys = splitCSV(value)
|
||||||
|
}
|
||||||
if value := strings.TrimSpace(os.Getenv("LINGMA_PROXY_CWD")); value != "" {
|
if value := strings.TrimSpace(os.Getenv("LINGMA_PROXY_CWD")); value != "" {
|
||||||
dst.Cwd = value
|
dst.Cwd = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
"pipe": "",
|
"pipe": "",
|
||||||
"websocket_url": "",
|
"websocket_url": "",
|
||||||
"remote_auth_file": "/secrets/credentials.json",
|
"remote_auth_file": "/secrets/credentials.json",
|
||||||
|
"api_keys": [
|
||||||
|
"replace-with-long-random-key-1",
|
||||||
|
"replace-with-long-random-key-2"
|
||||||
|
],
|
||||||
"lingma_bootstrap_enabled": true,
|
"lingma_bootstrap_enabled": true,
|
||||||
"lingma_source_type": "marketplace",
|
"lingma_source_type": "marketplace",
|
||||||
"lingma_vsix_url": "",
|
"lingma_vsix_url": "",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func NewServer(addr string, svc *service.Service) *Server {
|
|||||||
|
|
||||||
s.http = &http.Server{
|
s.http = &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: s.withRecorder(withCORS(mux)),
|
Handler: s.withRecorder(withCORS(s.withAuth(mux))),
|
||||||
ReadHeaderTimeout: 10 * time.Second,
|
ReadHeaderTimeout: 10 * time.Second,
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
@@ -1686,6 +1686,65 @@ func writeOpenAIError(w http.ResponseWriter, status int, kind string, message st
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPublicPath(path string) bool {
|
||||||
|
switch path {
|
||||||
|
case "/", "/health", "/runtime/status", "/v1/runtime/status":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAnthropicPath(path string) bool {
|
||||||
|
switch path {
|
||||||
|
case "/v1/messages", "/v1/messages/count_tokens":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractAPIKey(r *http.Request) string {
|
||||||
|
if value := strings.TrimSpace(r.Header.Get("x-api-key")); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
auth := strings.TrimSpace(r.Header.Get("Authorization"))
|
||||||
|
if auth == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(auth, " ", 2)
|
||||||
|
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) withAuth(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if isPublicPath(r.URL.Path) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys := s.svc.APIKeys()
|
||||||
|
if len(keys) == 0 {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
provided := extractAPIKey(r)
|
||||||
|
for _, key := range keys {
|
||||||
|
if provided == key {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isAnthropicPath(r.URL.Path) {
|
||||||
|
writeAnthropicError(w, http.StatusUnauthorized, "authentication_error", "invalid or missing API key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeOpenAIError(w, http.StatusUnauthorized, "authentication_error", "invalid or missing API key")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func streamingHeaders(w http.ResponseWriter) {
|
func streamingHeaders(w http.ResponseWriter) {
|
||||||
w.Header().Set("Content-Type", "text/event-stream")
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
|||||||
@@ -81,6 +81,83 @@ func TestCapabilitiesAdvertiseAgentCompatibility(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthAllowsPublicHealthWithoutAPIKey(t *testing.T) {
|
||||||
|
server := NewServer("", service.New(service.Config{
|
||||||
|
Model: "Qwen3-Coder",
|
||||||
|
Timeout: time.Second,
|
||||||
|
APIKeys: []string{"key-1", "key-2"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
server.http.Handler.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthRejectsProtectedEndpointWithoutAPIKey(t *testing.T) {
|
||||||
|
server := NewServer("", service.New(service.Config{
|
||||||
|
Model: "Qwen3-Coder",
|
||||||
|
Timeout: time.Second,
|
||||||
|
APIKeys: []string{"key-1", "key-2"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/capabilities", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
server.http.Handler.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(rec.Body.String(), "authentication_error") {
|
||||||
|
t.Fatalf("body = %s", rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthAcceptsBearerAndXAPIKey(t *testing.T) {
|
||||||
|
server := NewServer("", service.New(service.Config{
|
||||||
|
Model: "Qwen3-Coder",
|
||||||
|
Timeout: time.Second,
|
||||||
|
APIKeys: []string{"key-1", "key-2"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
bearerReq := httptest.NewRequest(http.MethodGet, "/capabilities", nil)
|
||||||
|
bearerReq.Header.Set("Authorization", "Bearer key-2")
|
||||||
|
bearerRec := httptest.NewRecorder()
|
||||||
|
server.http.Handler.ServeHTTP(bearerRec, bearerReq)
|
||||||
|
if bearerRec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("bearer status = %d body = %s", bearerRec.Code, bearerRec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
xReq := httptest.NewRequest(http.MethodGet, "/capabilities", nil)
|
||||||
|
xReq.Header.Set("x-api-key", "key-1")
|
||||||
|
xRec := httptest.NewRecorder()
|
||||||
|
server.http.Handler.ServeHTTP(xRec, xReq)
|
||||||
|
if xRec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("x-api-key status = %d body = %s", xRec.Code, xRec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnthropicAuthErrorShape(t *testing.T) {
|
||||||
|
server := NewServer("", service.New(service.Config{
|
||||||
|
Model: "Qwen3-Coder",
|
||||||
|
Timeout: time.Second,
|
||||||
|
APIKeys: []string{"key-1"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(`{"model":"kmodel","messages":[{"role":"user","content":"hello"}],"max_tokens":16}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
server.http.Handler.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(rec.Body.String(), `"type":"error"`) {
|
||||||
|
t.Fatalf("body = %s", rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestNormalizeOpenAIRequestRejectsMissingUserAndAssistantMessages(t *testing.T) {
|
func TestNormalizeOpenAIRequestRejectsMissingUserAndAssistantMessages(t *testing.T) {
|
||||||
req := openAIChatRequest{
|
req := openAIChatRequest{
|
||||||
Model: "test-model",
|
Model: "test-model",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ type Config struct {
|
|||||||
ShellType string
|
ShellType string
|
||||||
SessionMode SessionMode
|
SessionMode SessionMode
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
APIKeys []string
|
||||||
RemoteFallbackEnabled bool
|
RemoteFallbackEnabled bool
|
||||||
RemoteFallbackModels []string
|
RemoteFallbackModels []string
|
||||||
LingmaBootstrapEnabled bool
|
LingmaBootstrapEnabled bool
|
||||||
@@ -185,6 +186,7 @@ func New(cfg Config) *Service {
|
|||||||
cfg.Mode = "agent"
|
cfg.Mode = "agent"
|
||||||
}
|
}
|
||||||
cfg.Model = strings.TrimSpace(cfg.Model)
|
cfg.Model = strings.TrimSpace(cfg.Model)
|
||||||
|
cfg.APIKeys = cleanStringSlice(cfg.APIKeys)
|
||||||
if strings.TrimSpace(cfg.ShellType) == "" {
|
if strings.TrimSpace(cfg.ShellType) == "" {
|
||||||
cfg.ShellType = lingmaipc.DefaultShellType()
|
cfg.ShellType = lingmaipc.DefaultShellType()
|
||||||
}
|
}
|
||||||
@@ -247,6 +249,26 @@ func (s *Service) DefaultModel() string {
|
|||||||
return strings.TrimSpace(s.cfg.Model)
|
return strings.TrimSpace(s.cfg.Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) APIKeys() []string {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return append([]string(nil), s.cfg.APIKeys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanStringSlice(values []string) []string {
|
||||||
|
out := make([]string, 0, len(values))
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, value := range values {
|
||||||
|
item := strings.TrimSpace(value)
|
||||||
|
if item == "" || seen[item] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[item] = true
|
||||||
|
out = append(out, item)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) PrepareRuntime() error {
|
func (s *Service) PrepareRuntime() error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
cfg := s.cfg
|
cfg := s.cfg
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ Optional environment variables:
|
|||||||
LINGMA_MARKETPLACE_PUBLISHER=Alibaba-Cloud
|
LINGMA_MARKETPLACE_PUBLISHER=Alibaba-Cloud
|
||||||
LINGMA_MARKETPLACE_EXTENSION=tongyi-lingma
|
LINGMA_MARKETPLACE_EXTENSION=tongyi-lingma
|
||||||
LINGMA_PROXY_MODEL=org_auto
|
LINGMA_PROXY_MODEL=org_auto
|
||||||
|
LINGMA_PROXY_API_KEYS=
|
||||||
VERIFY_PUBLIC=false
|
VERIFY_PUBLIC=false
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -74,7 +75,9 @@ LINGMA_VSIX_URL="${LINGMA_VSIX_URL:-https://tongyi-code.oss-cn-hangzhou.aliyuncs
|
|||||||
LINGMA_MARKETPLACE_PUBLISHER="${LINGMA_MARKETPLACE_PUBLISHER:-Alibaba-Cloud}"
|
LINGMA_MARKETPLACE_PUBLISHER="${LINGMA_MARKETPLACE_PUBLISHER:-Alibaba-Cloud}"
|
||||||
LINGMA_MARKETPLACE_EXTENSION="${LINGMA_MARKETPLACE_EXTENSION:-tongyi-lingma}"
|
LINGMA_MARKETPLACE_EXTENSION="${LINGMA_MARKETPLACE_EXTENSION:-tongyi-lingma}"
|
||||||
LINGMA_PROXY_MODEL="${LINGMA_PROXY_MODEL:-org_auto}"
|
LINGMA_PROXY_MODEL="${LINGMA_PROXY_MODEL:-org_auto}"
|
||||||
|
LINGMA_PROXY_API_KEYS="${LINGMA_PROXY_API_KEYS:-}"
|
||||||
VERIFY_PUBLIC="${VERIFY_PUBLIC:-false}"
|
VERIFY_PUBLIC="${VERIFY_PUBLIC:-false}"
|
||||||
|
VERIFY_API_KEY="${LINGMA_PROXY_API_KEYS%%,*}"
|
||||||
|
|
||||||
if [ "$REMOTE_BUILD_DIR" = "$REMOTE_DIR" ]; then
|
if [ "$REMOTE_BUILD_DIR" = "$REMOTE_DIR" ]; then
|
||||||
echo "REMOTE_BUILD_DIR must be different from REMOTE_DIR" >&2
|
echo "REMOTE_BUILD_DIR must be different from REMOTE_DIR" >&2
|
||||||
@@ -151,6 +154,7 @@ LINGMA_SESSION_BUNDLE_FILE=/secrets/lingma-session.b64
|
|||||||
LINGMA_PROXY_BACKEND=remote
|
LINGMA_PROXY_BACKEND=remote
|
||||||
LINGMA_PROXY_SESSION_MODE=auto
|
LINGMA_PROXY_SESSION_MODE=auto
|
||||||
LINGMA_PROXY_MODEL=$LINGMA_PROXY_MODEL
|
LINGMA_PROXY_MODEL=$LINGMA_PROXY_MODEL
|
||||||
|
LINGMA_PROXY_API_KEYS=$LINGMA_PROXY_API_KEYS
|
||||||
LINGMA_PROXY_TIMEOUT_SECONDS=0
|
LINGMA_PROXY_TIMEOUT_SECONDS=0
|
||||||
LINGMA_REMOTE_FALLBACK_ENABLED=true
|
LINGMA_REMOTE_FALLBACK_ENABLED=true
|
||||||
EOF
|
EOF
|
||||||
@@ -183,7 +187,11 @@ echo "==> Waiting for remote health endpoint"
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "==> Remote models"
|
echo "==> Remote models"
|
||||||
"${SSH_BASE[@]}" "curl -fsS http://127.0.0.1:$REMOTE_PUBLIC_PORT/v1/models"
|
if [ -n "$VERIFY_API_KEY" ]; then
|
||||||
|
"${SSH_BASE[@]}" "curl -fsS -H 'Authorization: Bearer $VERIFY_API_KEY' http://127.0.0.1:$REMOTE_PUBLIC_PORT/v1/models"
|
||||||
|
else
|
||||||
|
"${SSH_BASE[@]}" "curl -fsS http://127.0.0.1:$REMOTE_PUBLIC_PORT/v1/models"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$VERIFY_PUBLIC" = "true" ]; then
|
if [ "$VERIFY_PUBLIC" = "true" ]; then
|
||||||
echo
|
echo
|
||||||
|
|||||||
Reference in New Issue
Block a user