Start the Go rewrite: jsjson, awg-ctrl port, differential bench
ci / check (push) Successful in 6m6s
ci / check (push) Successful in 6m6s
- internal/jsjson: JSON.stringify-compatible encoder and order-preserving decoder, tested against fixtures produced by Node itself - internal/ctrl: awg-ctrl in Go (params, vpn:// keys, users, peers, sync, 3.1 upgrade, CAKE shaping, metrics, monitor job, Express-compatible HTTP layer) - internal/iauth, internal/logx: internal Ed25519 tokens and logging - tools/diffbench: Docker bench with stubbed awg/ip/tc/nft that runs one scenario against the TS and Go implementations for comparison Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package ctrl
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Установка и удаление встроенного мониторинга (Prometheus) по кнопке в панели. Действие
|
||||
// одно из двух и без параметров: awg-ctrl запускает ту же команду, что оператор набрал бы
|
||||
// руками (`awg-ctrl monitor install|remove`, root), так что код один. Панель сама пакетов
|
||||
// не ставит и не удаляет — она только просит об этом здесь.
|
||||
|
||||
type monitorJob struct {
|
||||
mu sync.Mutex
|
||||
active bool
|
||||
action string
|
||||
exit *int
|
||||
}
|
||||
|
||||
var reANSI = regexp.MustCompile("\x1b\\[[0-9;]*[A-Za-z]")
|
||||
|
||||
func (s *Server) logTail() string {
|
||||
b, err := os.ReadFile(s.cliLog)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
t := string(b)
|
||||
if len(t) > 4000 { // последние 4000 символов (в JS — UTF-16 единиц; для лога разницы нет)
|
||||
t = t[len(t)-4000:]
|
||||
}
|
||||
return strings.ReplaceAll(reANSI.ReplaceAllString(t, ""), "\r", "\n")
|
||||
}
|
||||
|
||||
// startMonitorJob запускает CLI в фоне; возвращает 409, если уже идёт.
|
||||
func (s *Server) startMonitorJob(action string) error {
|
||||
j := &s.monitor
|
||||
j.mu.Lock()
|
||||
if j.active {
|
||||
j.mu.Unlock()
|
||||
return httpErr(409, "Операция уже идёт")
|
||||
}
|
||||
if _, err := os.Stat(s.cliPath); err != nil {
|
||||
j.mu.Unlock()
|
||||
return httpErr(501, "CLI не найден ("+s.cliPath+"): выполните на сервере awg-ctrl monitor "+action)
|
||||
}
|
||||
os.MkdirAll(filepath.Dir(s.cliLog), 0o755)
|
||||
f, err := os.OpenFile(s.cliLog, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
j.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(s.cliPath, "monitor", action)
|
||||
cmd.Stdout, cmd.Stderr = f, f
|
||||
j.active, j.action, j.exit = true, action, nil
|
||||
if err := cmd.Start(); err != nil {
|
||||
s.log.Error("monitor: не запустился", "action", action, "error", err.Error())
|
||||
code := 127
|
||||
j.active, j.exit = false, &code
|
||||
f.Close()
|
||||
j.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
j.mu.Unlock()
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
code := 0
|
||||
if err != nil {
|
||||
code = 1
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
code = ee.ExitCode()
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
s.log.Info("monitor: завершён", "action", action, "code", code)
|
||||
j.mu.Lock()
|
||||
j.active, j.exit = false, &code
|
||||
j.mu.Unlock()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
type jobView struct {
|
||||
Action *string `json:"action"`
|
||||
Running bool `json:"running"`
|
||||
Exit *int `json:"exit"`
|
||||
Log string `json:"log"`
|
||||
}
|
||||
|
||||
func (s *Server) monitorView() jobView {
|
||||
j := &s.monitor
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
v := jobView{Running: j.active, Exit: j.exit}
|
||||
if j.action != "" {
|
||||
a := j.action
|
||||
v.Action = &a
|
||||
v.Log = s.logTail()
|
||||
}
|
||||
return v
|
||||
}
|
||||
Reference in New Issue
Block a user