mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
191 lines
5.9 KiB
Go
191 lines
5.9 KiB
Go
package management
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func (h *Handler) GetConfig(c *gin.Context) {
|
|
if h == nil || h.cfg == nil {
|
|
c.JSON(200, gin.H{})
|
|
return
|
|
}
|
|
cfgCopy := *h.cfg
|
|
c.JSON(200, &cfgCopy)
|
|
}
|
|
|
|
func (h *Handler) GetConfigYAML(c *gin.Context) {
|
|
data, err := os.ReadFile(h.configFilePath)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "read_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
var node yaml.Node
|
|
if err = yaml.Unmarshal(data, &node); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
c.Header("Content-Type", "application/yaml; charset=utf-8")
|
|
c.Header("Vary", "format, Accept")
|
|
enc := yaml.NewEncoder(c.Writer)
|
|
enc.SetIndent(2)
|
|
_ = enc.Encode(&node)
|
|
_ = enc.Close()
|
|
}
|
|
|
|
func WriteConfig(path string, data []byte) error {
|
|
data = config.NormalizeCommentIndentation(data)
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, errWrite := f.Write(data); errWrite != nil {
|
|
_ = f.Close()
|
|
return errWrite
|
|
}
|
|
if errSync := f.Sync(); errSync != nil {
|
|
_ = f.Close()
|
|
return errSync
|
|
}
|
|
return f.Close()
|
|
}
|
|
|
|
func (h *Handler) PutConfigYAML(c *gin.Context) {
|
|
body, err := io.ReadAll(c.Request.Body)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": "cannot read request body"})
|
|
return
|
|
}
|
|
var cfg config.Config
|
|
if err = yaml.Unmarshal(body, &cfg); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()})
|
|
return
|
|
}
|
|
// Validate config using LoadConfigOptional with optional=false to enforce parsing
|
|
tmpDir := filepath.Dir(h.configFilePath)
|
|
tmpFile, err := os.CreateTemp(tmpDir, "config-validate-*.yaml")
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
tempFile := tmpFile.Name()
|
|
if _, errWrite := tmpFile.Write(body); errWrite != nil {
|
|
_ = tmpFile.Close()
|
|
_ = os.Remove(tempFile)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errWrite.Error()})
|
|
return
|
|
}
|
|
if errClose := tmpFile.Close(); errClose != nil {
|
|
_ = os.Remove(tempFile)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errClose.Error()})
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = os.Remove(tempFile)
|
|
}()
|
|
_, err = config.LoadConfigOptional(tempFile, false)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()})
|
|
return
|
|
}
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if WriteConfig(h.configFilePath, body) != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": "failed to write config"})
|
|
return
|
|
}
|
|
// Reload into handler to keep memory in sync
|
|
newCfg, err := config.LoadConfig(h.configFilePath)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "reload_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
h.cfg = newCfg
|
|
c.JSON(http.StatusOK, gin.H{"ok": true, "changed": []string{"config"}})
|
|
}
|
|
|
|
// GetConfigFile returns the raw config.yaml file bytes without re-encoding.
|
|
// It preserves comments and original formatting/styles.
|
|
func (h *Handler) GetConfigFile(c *gin.Context) {
|
|
data, err := os.ReadFile(h.configFilePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not_found", "message": "config file not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "read_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
c.Header("Content-Type", "application/yaml; charset=utf-8")
|
|
c.Header("Cache-Control", "no-store")
|
|
c.Header("X-Content-Type-Options", "nosniff")
|
|
// Write raw bytes as-is
|
|
_, _ = c.Writer.Write(data)
|
|
}
|
|
|
|
// Debug
|
|
func (h *Handler) GetDebug(c *gin.Context) { c.JSON(200, gin.H{"debug": h.cfg.Debug}) }
|
|
func (h *Handler) PutDebug(c *gin.Context) { h.updateBoolField(c, func(v bool) { h.cfg.Debug = v }) }
|
|
|
|
// UsageStatisticsEnabled
|
|
func (h *Handler) GetUsageStatisticsEnabled(c *gin.Context) {
|
|
c.JSON(200, gin.H{"usage-statistics-enabled": h.cfg.UsageStatisticsEnabled})
|
|
}
|
|
func (h *Handler) PutUsageStatisticsEnabled(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.UsageStatisticsEnabled = v })
|
|
}
|
|
|
|
// UsageStatisticsEnabled
|
|
func (h *Handler) GetLoggingToFile(c *gin.Context) {
|
|
c.JSON(200, gin.H{"logging-to-file": h.cfg.LoggingToFile})
|
|
}
|
|
func (h *Handler) PutLoggingToFile(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.LoggingToFile = v })
|
|
}
|
|
|
|
// Request log
|
|
func (h *Handler) GetRequestLog(c *gin.Context) { c.JSON(200, gin.H{"request-log": h.cfg.RequestLog}) }
|
|
func (h *Handler) PutRequestLog(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.RequestLog = v })
|
|
}
|
|
|
|
// Websocket auth
|
|
func (h *Handler) GetWebsocketAuth(c *gin.Context) {
|
|
c.JSON(200, gin.H{"ws-auth": h.cfg.WebsocketAuth})
|
|
}
|
|
func (h *Handler) PutWebsocketAuth(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.WebsocketAuth = v })
|
|
}
|
|
|
|
// Request retry
|
|
func (h *Handler) GetRequestRetry(c *gin.Context) {
|
|
c.JSON(200, gin.H{"request-retry": h.cfg.RequestRetry})
|
|
}
|
|
func (h *Handler) PutRequestRetry(c *gin.Context) {
|
|
h.updateIntField(c, func(v int) { h.cfg.RequestRetry = v })
|
|
}
|
|
|
|
// Max retry interval
|
|
func (h *Handler) GetMaxRetryInterval(c *gin.Context) {
|
|
c.JSON(200, gin.H{"max-retry-interval": h.cfg.MaxRetryInterval})
|
|
}
|
|
func (h *Handler) PutMaxRetryInterval(c *gin.Context) {
|
|
h.updateIntField(c, func(v int) { h.cfg.MaxRetryInterval = v })
|
|
}
|
|
|
|
// Proxy URL
|
|
func (h *Handler) GetProxyURL(c *gin.Context) { c.JSON(200, gin.H{"proxy-url": h.cfg.ProxyURL}) }
|
|
func (h *Handler) PutProxyURL(c *gin.Context) {
|
|
h.updateStringField(c, func(v string) { h.cfg.ProxyURL = v })
|
|
}
|
|
func (h *Handler) DeleteProxyURL(c *gin.Context) {
|
|
h.cfg.ProxyURL = ""
|
|
h.persist(c)
|
|
}
|