feat(embedded): add Perses + VictoriaMetrics subprocess management with auto-download
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package embedded
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fujin/anthropic-proxy/internal/config"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Perses struct {
|
||||
cfg config.EmbeddedConfig
|
||||
proxyPort int
|
||||
cmd *exec.Cmd
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
func NewPerses(cfg config.EmbeddedConfig, proxyPort int) *Perses {
|
||||
return &Perses{cfg: cfg, proxyPort: proxyPort}
|
||||
}
|
||||
|
||||
func (p *Perses) Start() error {
|
||||
bin, err := ensureBinary("perses", p.cfg.PersesBinary, p.cfg.BinDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("perses: %w", err)
|
||||
}
|
||||
|
||||
p.tmpDir, err = os.MkdirTemp("", "perses-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp dir: %w", err)
|
||||
}
|
||||
|
||||
if err := p.writeServerConfig(); err != nil {
|
||||
return fmt.Errorf("write server config: %w", err)
|
||||
}
|
||||
if err := p.writeDatasourceProvision(); err != nil {
|
||||
return fmt.Errorf("write datasource provision: %w", err)
|
||||
}
|
||||
if err := p.writeDashboardProvision(); err != nil {
|
||||
return fmt.Errorf("write dashboard provision: %w", err)
|
||||
}
|
||||
|
||||
p.cmd = exec.Command(bin,
|
||||
"--config", filepath.Join(p.tmpDir, "config.yaml"),
|
||||
"-web.listen-address", fmt.Sprintf(":%d", p.cfg.Port),
|
||||
)
|
||||
p.cmd.Dir = filepath.Dir(bin)
|
||||
p.cmd.Stdout = &logWriter{level: "info", component: "perses"}
|
||||
p.cmd.Stderr = &logWriter{level: "error", component: "perses"}
|
||||
|
||||
if err := p.cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start perses: %w", err)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("binary", bin).
|
||||
Int("port", p.cfg.Port).
|
||||
Str("config", p.tmpDir).
|
||||
Msg("perses started")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Perses) Stop() {
|
||||
if p.cmd != nil && p.cmd.Process != nil {
|
||||
_ = p.cmd.Process.Kill()
|
||||
_ = p.cmd.Wait()
|
||||
}
|
||||
if p.tmpDir != "" {
|
||||
_ = os.RemoveAll(p.tmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Perses) Running() bool {
|
||||
return p.cmd != nil && p.cmd.Process != nil && p.cmd.ProcessState == nil
|
||||
}
|
||||
|
||||
func (p *Perses) writeServerConfig() error {
|
||||
provisionDir := filepath.Join(p.tmpDir, "provisions")
|
||||
if err := os.MkdirAll(filepath.Join(provisionDir, "datasources"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(provisionDir, "dashboards"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := fmt.Sprintf(`provisioning:
|
||||
interval: 1m
|
||||
folders:
|
||||
- %s
|
||||
database:
|
||||
file:
|
||||
folder: %s/data
|
||||
extension: json
|
||||
security:
|
||||
readonly: false
|
||||
enable_auth: false
|
||||
`, provisionDir, p.tmpDir)
|
||||
|
||||
return os.WriteFile(filepath.Join(p.tmpDir, "config.yaml"), []byte(cfg), 0o644)
|
||||
}
|
||||
|
||||
func (p *Perses) writeDatasourceProvision() error {
|
||||
ds := fmt.Sprintf(`kind: Datasource
|
||||
metadata:
|
||||
name: victoria-metrics
|
||||
project: anthropic-proxy
|
||||
spec:
|
||||
default: true
|
||||
plugin:
|
||||
kind: PrometheusDatasource
|
||||
spec:
|
||||
directUrl: http://localhost:%d
|
||||
`, p.cfg.VMPort)
|
||||
|
||||
return os.WriteFile(
|
||||
filepath.Join(p.tmpDir, "provisions", "datasources", "vm.yaml"),
|
||||
[]byte(ds), 0o644,
|
||||
)
|
||||
}
|
||||
|
||||
func (p *Perses) writeDashboardProvision() error {
|
||||
dashData, err := DashboardJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(
|
||||
filepath.Join(p.tmpDir, "provisions", "dashboards", "proxy.json"),
|
||||
dashData, 0o644,
|
||||
)
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
level string
|
||||
component string
|
||||
}
|
||||
|
||||
func (w *logWriter) Write(p []byte) (n int, err error) {
|
||||
msg := string(p)
|
||||
switch w.level {
|
||||
case "error":
|
||||
log.Error().Str("component", w.component).Msg(msg)
|
||||
default:
|
||||
log.Debug().Str("component", w.component).Msg(msg)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
Reference in New Issue
Block a user