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, ) }