From 623e258a2aba78fe486e3e750d5c3c7e33595ed4 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Fri, 14 Nov 2025 18:57:03 +0100 Subject: [PATCH] Add API endpoint to retrieve sanitized server configuration --- docs/docs.go | 276 ++++++++++++++++++++++++++++++++++ docs/swagger.json | 276 ++++++++++++++++++++++++++++++++++ docs/swagger.yaml | 190 +++++++++++++++++++++++ pkg/config/config.go | 17 +++ pkg/server/handlers_system.go | 17 +++ pkg/server/routes.go | 4 +- 6 files changed, 779 insertions(+), 1 deletion(-) diff --git a/docs/docs.go b/docs/docs.go index f46ac36..6b1e42d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -256,6 +256,34 @@ const docTemplate = `{ } } }, + "/api/v1/config": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns the current server configuration (sanitized)", + "tags": [ + "System" + ], + "summary": "Get server configuration", + "responses": { + "200": { + "description": "Sanitized configuration", + "schema": { + "$ref": "#/definitions/config.AppConfig" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/api/v1/instances": { "get": { "security": [ @@ -1475,6 +1503,247 @@ const docTemplate = `{ } }, "definitions": { + "config.AppConfig": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/definitions/config.AuthConfig" + }, + "backends": { + "$ref": "#/definitions/config.BackendConfig" + }, + "buildTime": { + "type": "string" + }, + "commitHash": { + "type": "string" + }, + "instances": { + "$ref": "#/definitions/config.InstancesConfig" + }, + "localNode": { + "type": "string" + }, + "nodes": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/config.NodeConfig" + } + }, + "server": { + "$ref": "#/definitions/config.ServerConfig" + }, + "version": { + "type": "string" + } + } + }, + "config.AuthConfig": { + "type": "object", + "properties": { + "inferenceKeys": { + "description": "List of keys for OpenAI compatible inference endpoints", + "type": "array", + "items": { + "type": "string" + } + }, + "managementKeys": { + "description": "List of keys for management endpoints", + "type": "array", + "items": { + "type": "string" + } + }, + "requireInferenceAuth": { + "description": "Require authentication for OpenAI compatible inference endpoints", + "type": "boolean" + }, + "requireManagementAuth": { + "description": "Require authentication for management endpoints", + "type": "boolean" + } + } + }, + "config.BackendConfig": { + "type": "object", + "properties": { + "llamaCpp": { + "$ref": "#/definitions/config.BackendSettings" + }, + "mlx": { + "$ref": "#/definitions/config.BackendSettings" + }, + "vllm": { + "$ref": "#/definitions/config.BackendSettings" + } + } + }, + "config.BackendSettings": { + "type": "object", + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "type": "string" + }, + "docker": { + "$ref": "#/definitions/config.DockerSettings" + }, + "environment": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "responseHeaders": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "config.DockerSettings": { + "type": "object", + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "boolean" + }, + "environment": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "image": { + "type": "string" + } + } + }, + "config.InstancesConfig": { + "type": "object", + "properties": { + "autoCreateDirs": { + "description": "Automatically create the data directory if it doesn't exist", + "type": "boolean" + }, + "dataDir": { + "description": "Directory where all llamactl data will be stored (instances.json, logs, etc.)", + "type": "string" + }, + "defaultAutoRestart": { + "description": "Default auto-restart setting for new instances", + "type": "boolean" + }, + "defaultMaxRestarts": { + "description": "Default max restarts for new instances", + "type": "integer" + }, + "defaultOnDemandStart": { + "description": "Default on-demand start setting for new instances", + "type": "boolean" + }, + "defaultRestartDelay": { + "description": "Default restart delay for new instances (in seconds)", + "type": "integer" + }, + "enableLRUEviction": { + "description": "Enable LRU eviction for instance logs", + "type": "boolean" + }, + "instancesDir": { + "description": "Instance config directory override", + "type": "string" + }, + "logsDir": { + "description": "Logs directory override", + "type": "string" + }, + "maxInstances": { + "description": "Maximum number of instances that can be created", + "type": "integer" + }, + "maxRunningInstances": { + "description": "Maximum number of instances that can be running at the same time", + "type": "integer" + }, + "onDemandStartTimeout": { + "description": "How long to wait for an instance to start on demand (in seconds)", + "type": "integer" + }, + "portRange": { + "description": "Port range for instances (e.g., 8000,9000)", + "type": "array", + "items": { + "type": "integer" + } + }, + "timeoutCheckInterval": { + "description": "Interval for checking instance timeouts (in minutes)", + "type": "integer" + } + } + }, + "config.NodeConfig": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "apikey": { + "type": "string" + } + } + }, + "config.ServerConfig": { + "type": "object", + "properties": { + "allowedHeaders": { + "description": "Allowed headers for CORS (e.g., \"Accept\", \"Authorization\", \"Content-Type\", \"X-CSRF-Token\")", + "type": "array", + "items": { + "type": "string" + } + }, + "allowedOrigins": { + "description": "Allowed origins for CORS (e.g., \"http://localhost:3000\")", + "type": "array", + "items": { + "type": "string" + } + }, + "enableSwagger": { + "description": "Enable Swagger UI for API documentation", + "type": "boolean" + }, + "host": { + "description": "Server host to bind to", + "type": "string" + }, + "port": { + "description": "Server port to bind to", + "type": "integer" + }, + "responseHeaders": { + "description": "Response headers to send with responses", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "instance.Instance": { "type": "object", "properties": { @@ -1494,6 +1763,13 @@ const docTemplate = `{ "description": "Auto restart", "type": "boolean" }, + "command_override": { + "type": "string" + }, + "docker_enabled": { + "description": "Execution context overrides", + "type": "boolean" + }, "environment": { "description": "Environment variables", "type": "object", diff --git a/docs/swagger.json b/docs/swagger.json index 26f9662..cb9d95a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -249,6 +249,34 @@ } } }, + "/api/v1/config": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns the current server configuration (sanitized)", + "tags": [ + "System" + ], + "summary": "Get server configuration", + "responses": { + "200": { + "description": "Sanitized configuration", + "schema": { + "$ref": "#/definitions/config.AppConfig" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/api/v1/instances": { "get": { "security": [ @@ -1468,6 +1496,247 @@ } }, "definitions": { + "config.AppConfig": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/definitions/config.AuthConfig" + }, + "backends": { + "$ref": "#/definitions/config.BackendConfig" + }, + "buildTime": { + "type": "string" + }, + "commitHash": { + "type": "string" + }, + "instances": { + "$ref": "#/definitions/config.InstancesConfig" + }, + "localNode": { + "type": "string" + }, + "nodes": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/config.NodeConfig" + } + }, + "server": { + "$ref": "#/definitions/config.ServerConfig" + }, + "version": { + "type": "string" + } + } + }, + "config.AuthConfig": { + "type": "object", + "properties": { + "inferenceKeys": { + "description": "List of keys for OpenAI compatible inference endpoints", + "type": "array", + "items": { + "type": "string" + } + }, + "managementKeys": { + "description": "List of keys for management endpoints", + "type": "array", + "items": { + "type": "string" + } + }, + "requireInferenceAuth": { + "description": "Require authentication for OpenAI compatible inference endpoints", + "type": "boolean" + }, + "requireManagementAuth": { + "description": "Require authentication for management endpoints", + "type": "boolean" + } + } + }, + "config.BackendConfig": { + "type": "object", + "properties": { + "llamaCpp": { + "$ref": "#/definitions/config.BackendSettings" + }, + "mlx": { + "$ref": "#/definitions/config.BackendSettings" + }, + "vllm": { + "$ref": "#/definitions/config.BackendSettings" + } + } + }, + "config.BackendSettings": { + "type": "object", + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "type": "string" + }, + "docker": { + "$ref": "#/definitions/config.DockerSettings" + }, + "environment": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "responseHeaders": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "config.DockerSettings": { + "type": "object", + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "boolean" + }, + "environment": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "image": { + "type": "string" + } + } + }, + "config.InstancesConfig": { + "type": "object", + "properties": { + "autoCreateDirs": { + "description": "Automatically create the data directory if it doesn't exist", + "type": "boolean" + }, + "dataDir": { + "description": "Directory where all llamactl data will be stored (instances.json, logs, etc.)", + "type": "string" + }, + "defaultAutoRestart": { + "description": "Default auto-restart setting for new instances", + "type": "boolean" + }, + "defaultMaxRestarts": { + "description": "Default max restarts for new instances", + "type": "integer" + }, + "defaultOnDemandStart": { + "description": "Default on-demand start setting for new instances", + "type": "boolean" + }, + "defaultRestartDelay": { + "description": "Default restart delay for new instances (in seconds)", + "type": "integer" + }, + "enableLRUEviction": { + "description": "Enable LRU eviction for instance logs", + "type": "boolean" + }, + "instancesDir": { + "description": "Instance config directory override", + "type": "string" + }, + "logsDir": { + "description": "Logs directory override", + "type": "string" + }, + "maxInstances": { + "description": "Maximum number of instances that can be created", + "type": "integer" + }, + "maxRunningInstances": { + "description": "Maximum number of instances that can be running at the same time", + "type": "integer" + }, + "onDemandStartTimeout": { + "description": "How long to wait for an instance to start on demand (in seconds)", + "type": "integer" + }, + "portRange": { + "description": "Port range for instances (e.g., 8000,9000)", + "type": "array", + "items": { + "type": "integer" + } + }, + "timeoutCheckInterval": { + "description": "Interval for checking instance timeouts (in minutes)", + "type": "integer" + } + } + }, + "config.NodeConfig": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "apikey": { + "type": "string" + } + } + }, + "config.ServerConfig": { + "type": "object", + "properties": { + "allowedHeaders": { + "description": "Allowed headers for CORS (e.g., \"Accept\", \"Authorization\", \"Content-Type\", \"X-CSRF-Token\")", + "type": "array", + "items": { + "type": "string" + } + }, + "allowedOrigins": { + "description": "Allowed origins for CORS (e.g., \"http://localhost:3000\")", + "type": "array", + "items": { + "type": "string" + } + }, + "enableSwagger": { + "description": "Enable Swagger UI for API documentation", + "type": "boolean" + }, + "host": { + "description": "Server host to bind to", + "type": "string" + }, + "port": { + "description": "Server port to bind to", + "type": "integer" + }, + "responseHeaders": { + "description": "Response headers to send with responses", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "instance.Instance": { "type": "object", "properties": { @@ -1487,6 +1756,13 @@ "description": "Auto restart", "type": "boolean" }, + "command_override": { + "type": "string" + }, + "docker_enabled": { + "description": "Execution context overrides", + "type": "boolean" + }, "environment": { "description": "Environment variables", "type": "object", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 7506036..033b391 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,173 @@ basePath: /api/v1 definitions: + config.AppConfig: + properties: + auth: + $ref: '#/definitions/config.AuthConfig' + backends: + $ref: '#/definitions/config.BackendConfig' + buildTime: + type: string + commitHash: + type: string + instances: + $ref: '#/definitions/config.InstancesConfig' + localNode: + type: string + nodes: + additionalProperties: + $ref: '#/definitions/config.NodeConfig' + type: object + server: + $ref: '#/definitions/config.ServerConfig' + version: + type: string + type: object + config.AuthConfig: + properties: + inferenceKeys: + description: List of keys for OpenAI compatible inference endpoints + items: + type: string + type: array + managementKeys: + description: List of keys for management endpoints + items: + type: string + type: array + requireInferenceAuth: + description: Require authentication for OpenAI compatible inference endpoints + type: boolean + requireManagementAuth: + description: Require authentication for management endpoints + type: boolean + type: object + config.BackendConfig: + properties: + llamaCpp: + $ref: '#/definitions/config.BackendSettings' + mlx: + $ref: '#/definitions/config.BackendSettings' + vllm: + $ref: '#/definitions/config.BackendSettings' + type: object + config.BackendSettings: + properties: + args: + items: + type: string + type: array + command: + type: string + docker: + $ref: '#/definitions/config.DockerSettings' + environment: + additionalProperties: + type: string + type: object + responseHeaders: + additionalProperties: + type: string + type: object + type: object + config.DockerSettings: + properties: + args: + items: + type: string + type: array + enabled: + type: boolean + environment: + additionalProperties: + type: string + type: object + image: + type: string + type: object + config.InstancesConfig: + properties: + autoCreateDirs: + description: Automatically create the data directory if it doesn't exist + type: boolean + dataDir: + description: Directory where all llamactl data will be stored (instances.json, + logs, etc.) + type: string + defaultAutoRestart: + description: Default auto-restart setting for new instances + type: boolean + defaultMaxRestarts: + description: Default max restarts for new instances + type: integer + defaultOnDemandStart: + description: Default on-demand start setting for new instances + type: boolean + defaultRestartDelay: + description: Default restart delay for new instances (in seconds) + type: integer + enableLRUEviction: + description: Enable LRU eviction for instance logs + type: boolean + instancesDir: + description: Instance config directory override + type: string + logsDir: + description: Logs directory override + type: string + maxInstances: + description: Maximum number of instances that can be created + type: integer + maxRunningInstances: + description: Maximum number of instances that can be running at the same time + type: integer + onDemandStartTimeout: + description: How long to wait for an instance to start on demand (in seconds) + type: integer + portRange: + description: Port range for instances (e.g., 8000,9000) + items: + type: integer + type: array + timeoutCheckInterval: + description: Interval for checking instance timeouts (in minutes) + type: integer + type: object + config.NodeConfig: + properties: + address: + type: string + apikey: + type: string + type: object + config.ServerConfig: + properties: + allowedHeaders: + description: Allowed headers for CORS (e.g., "Accept", "Authorization", "Content-Type", + "X-CSRF-Token") + items: + type: string + type: array + allowedOrigins: + description: Allowed origins for CORS (e.g., "http://localhost:3000") + items: + type: string + type: array + enableSwagger: + description: Enable Swagger UI for API documentation + type: boolean + host: + description: Server host to bind to + type: string + port: + description: Server port to bind to + type: integer + responseHeaders: + additionalProperties: + type: string + description: Response headers to send with responses + type: object + type: object instance.Instance: properties: created: @@ -13,6 +181,11 @@ definitions: auto_restart: description: Auto restart type: boolean + command_override: + type: string + docker_enabled: + description: Execution context overrides + type: boolean environment: additionalProperties: type: string @@ -216,6 +389,23 @@ paths: summary: Parse vllm serve command tags: - Backends + /api/v1/config: + get: + description: Returns the current server configuration (sanitized) + responses: + "200": + description: Sanitized configuration + schema: + $ref: '#/definitions/config.AppConfig' + "500": + description: Internal Server Error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Get server configuration + tags: + - System /api/v1/instances: get: description: Returns a list of all instances managed by the server diff --git a/pkg/config/config.go b/pkg/config/config.go index 6df9e42..c8ac108 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -610,3 +610,20 @@ func getDefaultConfigLocations() []string { return locations } + +// SanitizedCopy returns a copy of the AppConfig with sensitive information removed +func (cfg *AppConfig) SanitizedCopy() AppConfig { + // Create a copy of the config + sanitized := *cfg + + // Clear sensitive information + sanitized.Auth.InferenceKeys = []string{} + sanitized.Auth.ManagementKeys = []string{} + + for nodeName, node := range sanitized.Nodes { + node.APIKey = "" + sanitized.Nodes[nodeName] = node + } + + return sanitized +} diff --git a/pkg/server/handlers_system.go b/pkg/server/handlers_system.go index 46410f3..c53947c 100644 --- a/pkg/server/handlers_system.go +++ b/pkg/server/handlers_system.go @@ -20,3 +20,20 @@ func (h *Handler) VersionHandler() http.HandlerFunc { writeText(w, http.StatusOK, versionInfo) } } + +// ConfigHandler godoc +// @Summary Get server configuration +// @Description Returns the current server configuration (sanitized) +// @Tags System +// @Security ApiKeyAuth +// @Produces application/json +// @Success 200 {object} config.AppConfig "Sanitized configuration" +// @Failure 500 {string} string "Internal Server Error" +// @Router /api/v1/config [get] +func (h *Handler) ConfigHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Return a sanitized copy of the configuration + sanitizedConfig := h.cfg.SanitizedCopy() + writeJSON(w, http.StatusOK, sanitizedConfig) + } +} diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 618dbc0..b159968 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -42,7 +42,9 @@ func SetupRouter(handler *Handler) *chi.Mux { r.Use(authMiddleware.AuthMiddleware(KeyTypeManagement)) } - r.Get("/version", handler.VersionHandler()) // Get server version + r.Get("/version", handler.VersionHandler()) + + r.Get("/config", handler.ConfigHandler()) // Backend-specific endpoints r.Route("/backends", func(r chi.Router) {