CI / go-test (push) Waiting to run
CI / postgres-durable-first (push) Waiting to run
CI / codegen (push) Waiting to run
CI / govulncheck (push) Waiting to run
CI / race (push) Waiting to run
CI / fuzz-smoke (push) Waiting to run
CI / golangci (push) Waiting to run
CI / frontend (push) Waiting to run
Docs CI / build (push) Waiting to run
Docs Deploy (GitHub Pages) / build (push) Waiting to run
Docs Deploy (GitHub Pages) / deploy (push) Blocked by required conditions
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (amd64) (push) Waiting to run
Release 3X-UI / Publish rolling dev release (push) Blocked by required conditions
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
- 0: sing-box gen overrides fragment listen/port from the inbound row (empty listen -> "::"), Update/Add recompute the stored fragment; non-loopback E2E against the real core binary. - 1: agent fetches the sing-box core from the Gitea release when missing, self-checks it, and only then latches the switch. - 2: node list shows the sing-box core version (heartbeat field, persisted on the node row), xray version as fallback. - 3: public-page fallback returns 404 to anonymous visitors when the page is disabled. - 4: settings restart hint bar removed, dead i18n keys cleaned. - 5: two PUT calls missing JSON headers fixed (audit: only those two), batch probe-task creation endpoint + UI. - 6: cf-probe retirement card and coverage pipeline removed. - 7: generated vless links no longer carry spx (panel export + sub); parser still accepts spx; golden fixtures updated. - probe batch: dedicated probe settings tab (public-page settings, tasks, per-node meta), retention_days editable, per-node traffic calibration + stat-interface pinning, runtime metrics split into a lightweight fast report (default 3s, 1-60s adjustable). - 8: sing-box core processes start with GOMEMLIMIT=32MiB, GOGC=50. - 9: dead /getTwoFactorEnable docs entries removed (frontend call and contract entry already gone at base; verified by grep).
94 lines
2.8 KiB
Go
94 lines
2.8 KiB
Go
package agent
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// V2.0.1 ⑧: the sing-box core process is launched with
|
|
// GOMEMLIMIT=32MiB and GOGC=50 in its environment — on the first
|
|
// start and on every re-apply, since all launches go through
|
|
// singboxProcess.start. The agent's own env is not modified, and
|
|
// values the agent was itself started with are replaced (never
|
|
// duplicated) for the child.
|
|
|
|
func countEnv(env []string, want string) int {
|
|
n := 0
|
|
for _, kv := range env {
|
|
if kv == want {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func TestSingboxCoreEnv(t *testing.T) {
|
|
t.Setenv("GOMEMLIMIT", "999MiB")
|
|
t.Setenv("GOGC", "off")
|
|
env := singboxCoreEnv()
|
|
if n := countEnv(env, "GOMEMLIMIT=32MiB"); n != 1 {
|
|
t.Errorf("GOMEMLIMIT=32MiB appears %d times, want exactly 1 (%v)", n, env)
|
|
}
|
|
if n := countEnv(env, "GOGC=50"); n != 1 {
|
|
t.Errorf("GOGC=50 appears %d times, want exactly 1", n)
|
|
}
|
|
for _, kv := range env {
|
|
if kv == "GOMEMLIMIT=999MiB" || kv == "GOGC=off" {
|
|
t.Errorf("agent's own %q leaked into the core env", kv)
|
|
}
|
|
}
|
|
// The agent process env itself is untouched.
|
|
if got := os.Getenv("GOMEMLIMIT"); got != "999MiB" {
|
|
t.Errorf("agent env GOMEMLIMIT = %q, want the untouched 999MiB", got)
|
|
}
|
|
}
|
|
|
|
// TestSingboxProcessStartCarriesCoreEnv starts a stand-in core
|
|
// through the real launch path and reads back the environment the
|
|
// child actually received (the script dumps its own env on start).
|
|
func TestSingboxProcessStartCarriesCoreEnv(t *testing.T) {
|
|
dir := t.TempDir()
|
|
dump := filepath.Join(dir, "child-env.txt")
|
|
binPath := filepath.Join(dir, "sing-box")
|
|
script := "#!/bin/sh\nenv > '" + dump + "'\nexec sleep 3600\n"
|
|
if err := os.WriteFile(binPath, []byte(script), 0o755); err != nil {
|
|
t.Fatalf("write fake core: %v", err)
|
|
}
|
|
cfgPath := filepath.Join(dir, "cfg.json")
|
|
if err := os.WriteFile(cfgPath, []byte("{}"), 0o600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
t.Setenv("GOMEMLIMIT", "999MiB")
|
|
p := &singboxProcess{}
|
|
if err := p.start(binPath, cfgPath, filepath.Join(dir, "sing-box.log")); err != nil {
|
|
t.Fatalf("start: %v", err)
|
|
}
|
|
defer p.stop()
|
|
|
|
var childEnv []string
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if raw, err := os.ReadFile(dump); err == nil && len(raw) > 0 {
|
|
childEnv = strings.Split(strings.TrimSpace(string(raw)), "\n")
|
|
break
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
if childEnv == nil {
|
|
t.Fatal("fake core never dumped its environment")
|
|
}
|
|
if n := countEnv(childEnv, "GOMEMLIMIT=32MiB"); n != 1 {
|
|
t.Errorf("child env GOMEMLIMIT=32MiB x%d, want 1 (%v)", n, childEnv)
|
|
}
|
|
if n := countEnv(childEnv, "GOGC=50"); n != 1 {
|
|
t.Errorf("child env GOGC=50 x%d, want 1", n)
|
|
}
|
|
if n := countEnv(childEnv, "GOMEMLIMIT=999MiB"); n != 0 {
|
|
t.Errorf("child inherited the agent's GOMEMLIMIT=999MiB (%v)", childEnv)
|
|
}
|
|
}
|