Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions config_embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
_ "embed"
)

//go:embed config.example.yaml
var configExampleYAML []byte

// GetConfigExampleYAML returns the embedded example config file
func GetConfigExampleYAML() []byte {
return configExampleYAML
}
18 changes: 12 additions & 6 deletions llama-swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ func main() {
currentPM.Shutdown()
newPM := proxy.New(conf)
newPM.SetVersion(date, commit, version)
newPM.SetConfigPath(*configPath)
newPM.SetConfigExample(GetConfigExampleYAML())
srv.Handler = newPM
fmt.Println("Configuration Reloaded")

Expand All @@ -114,20 +116,24 @@ func main() {
}
newPM := proxy.New(conf)
newPM.SetVersion(date, commit, version)
newPM.SetConfigPath(*configPath)
newPM.SetConfigExample(GetConfigExampleYAML())
srv.Handler = newPM
}
}

// load the initial proxy manager
reloadProxyManager()
debouncedReload := debounce(time.Second, reloadProxyManager)
if *watchConfig {
defer event.On(func(e proxy.ConfigFileChangedEvent) {
if e.ReloadingState == proxy.ReloadingStateStart {
debouncedReload()
}
})()

// Always listen for API-triggered config changes
defer event.On(func(e proxy.ConfigFileChangedEvent) {
if e.ReloadingState == proxy.ReloadingStateStart {
debouncedReload()
}
})()

if *watchConfig {
fmt.Println("Watching Configuration for changes")
go func() {
absConfigPath, err := filepath.Abs(*configPath)
Expand Down
18 changes: 18 additions & 0 deletions proxy/proxymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ type ProxyManager struct {
commit string
version string

// config file path for editing
configPath string

// embedded example config
configExample []byte

// peer proxy see: #296, #433
peerProxy *PeerProxy
}
Expand Down Expand Up @@ -966,3 +972,15 @@ func (pm *ProxyManager) SetVersion(buildDate string, commit string, version stri
pm.commit = commit
pm.version = version
}

func (pm *ProxyManager) SetConfigPath(configPath string) {
pm.Lock()
defer pm.Unlock()
pm.configPath = configPath
}

func (pm *ProxyManager) SetConfigExample(configExample []byte) {
pm.Lock()
defer pm.Unlock()
pm.configExample = configExample
}
67 changes: 67 additions & 0 deletions proxy/proxymanager_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sort"
"strings"

Expand All @@ -31,6 +33,9 @@ func addApiHandlers(pm *ProxyManager) {
apiGroup.GET("/events", pm.apiSendEvents)
apiGroup.GET("/metrics", pm.apiGetMetrics)
apiGroup.GET("/version", pm.apiGetVersion)
apiGroup.GET("/config/current", pm.apiGetCurrentConfig)
apiGroup.GET("/config/example", pm.apiGetExampleConfig)
apiGroup.POST("/config", pm.apiUpdateConfig)
}
}

Expand Down Expand Up @@ -250,3 +255,65 @@ func (pm *ProxyManager) apiGetVersion(c *gin.Context) {
"build_date": pm.buildDate,
})
}

func (pm *ProxyManager) apiGetCurrentConfig(c *gin.Context) {
pm.Lock()
configPath := pm.configPath
pm.Unlock()

if configPath == "" {
pm.sendErrorResponse(c, http.StatusNotFound, "Config file path not set")
return
}

data, err := os.ReadFile(configPath)
if err != nil {
pm.sendErrorResponse(c, http.StatusInternalServerError, fmt.Sprintf("Failed to read config file: %v", err))
return
}

c.Data(http.StatusOK, "text/yaml; charset=utf-8", data)
}

func (pm *ProxyManager) apiGetExampleConfig(c *gin.Context) {
pm.Lock()
data := pm.configExample
pm.Unlock()

if data == nil {
pm.sendErrorResponse(c, http.StatusInternalServerError, "Example config not available")
return
}

c.Data(http.StatusOK, "text/yaml; charset=utf-8", data)
}

func (pm *ProxyManager) apiUpdateConfig(c *gin.Context) {
pm.Lock()
configPath := pm.configPath
pm.Unlock()

if configPath == "" {
pm.sendErrorResponse(c, http.StatusBadRequest, "Config file path not set")
return
}

body, err := io.ReadAll(c.Request.Body)
if err != nil {
pm.sendErrorResponse(c, http.StatusBadRequest, fmt.Sprintf("Failed to read request body: %v", err))
return
}

// Write to config file
if err := os.WriteFile(configPath, body, 0644); err != nil {
pm.sendErrorResponse(c, http.StatusInternalServerError, fmt.Sprintf("Failed to write config file: %v", err))
return
}

// Trigger config reload event
event.Emit(ConfigFileChangedEvent{
ReloadingState: ReloadingStateStart,
})

c.JSON(http.StatusOK, gin.H{"message": "Config updated successfully. Reloading..."})
}
Loading