diff options
Diffstat (limited to 'internal/engine/engine.go')
| -rw-r--r-- | internal/engine/engine.go | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/internal/engine/engine.go b/internal/engine/engine.go new file mode 100644 index 0000000..f71220f --- /dev/null +++ b/internal/engine/engine.go @@ -0,0 +1,134 @@ +package engine + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "sync" + + box "github.com/sagernet/sing-box" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/option" + + "vpnem/internal/config" + "vpnem/internal/models" +) + +type Engine struct { + mu sync.Mutex + instance *box.Box + cancel context.CancelFunc + running bool + configPath string + dataDir string +} + +func New(dataDir string) *Engine { + return &Engine{ + dataDir: dataDir, + configPath: filepath.Join(dataDir, "config.json"), + } +} + +func (e *Engine) Start(server models.Server, mode config.Mode, ruleSets []models.RuleSet, serverIPs []string) error { + return e.StartFull(server, mode, ruleSets, serverIPs, nil) +} + +func (e *Engine) StartFull(server models.Server, mode config.Mode, ruleSets []models.RuleSet, serverIPs []string, customBypass []string) error { + e.mu.Lock() + defer e.mu.Unlock() + + if e.running { + return fmt.Errorf("already running") + } + + cfg := config.BuildConfigFull(server, mode, ruleSets, serverIPs, customBypass) + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return fmt.Errorf("marshal config: %w", err) + } + + os.MkdirAll(e.dataDir, 0o755) + _ = os.WriteFile(e.configPath, data, 0o644) + log.Printf("engine: config saved (%d bytes)", len(data)) + + var opts option.Options + ctx := box.Context( + context.Background(), + include.InboundRegistry(), + include.OutboundRegistry(), + include.EndpointRegistry(), + ) + if err := opts.UnmarshalJSONContext(ctx, data); err != nil { + log.Printf("engine: parse FAILED: %v", err) + return fmt.Errorf("parse config: %w", err) + } + + boxCtx, cancel := context.WithCancel(ctx) + e.cancel = cancel + + instance, err := box.New(box.Options{ + Context: boxCtx, + Options: opts, + }) + if err != nil { + cancel() + log.Printf("engine: create FAILED: %v", err) + return fmt.Errorf("create sing-box: %w", err) + } + + if err := instance.Start(); err != nil { + instance.Close() + cancel() + log.Printf("engine: start FAILED: %v", err) + return fmt.Errorf("start sing-box: %w", err) + } + + e.instance = instance + e.running = true + log.Println("engine: started ok") + return nil +} + +func (e *Engine) Stop() error { + e.mu.Lock() + defer e.mu.Unlock() + + if !e.running { + return nil + } + + if e.instance != nil { + e.instance.Close() + e.instance = nil + } + if e.cancel != nil { + e.cancel() + } + e.running = false + log.Println("engine: stopped") + return nil +} + +func (e *Engine) Restart(server models.Server, mode config.Mode, ruleSets []models.RuleSet, serverIPs []string) error { + e.Stop() + return e.Start(server, mode, ruleSets, serverIPs) +} + +func (e *Engine) RestartFull(server models.Server, mode config.Mode, ruleSets []models.RuleSet, serverIPs []string, customBypass []string) error { + e.Stop() + return e.StartFull(server, mode, ruleSets, serverIPs, customBypass) +} + +func (e *Engine) IsRunning() bool { + e.mu.Lock() + defer e.mu.Unlock() + return e.running +} + +func (e *Engine) ConfigPath() string { + return e.configPath +} |
