// Command xui-agent is the standalone Linux build of the 3x-ui node // agent (agent/panel split, phase 1). Until the split, nodes ran the // panel binary under the "agent" subcommand, shipping the whole web // panel and the embedded frontend to every node. This entry links only // the agent's own dependency closure (internal/agent plus config, // logger, selfupdate, xray-api types and a few util leaves), so the node // artifact is a fraction of the panel binary while remaining a drop-in // replacement for it: // // - invocation is unchanged: service units run // "xui-agent agent [-config /etc/x-ui/agent.json]" (the bare // "-config" form is also accepted for manual runs); // - agent.json, the traffic baseline and the other state files keep // their names, locations and formats; // - the wire protocol with the master (register / heartbeat / // config / traffic / probe-report / update intent) is byte-for- // byte the internal/agent implementation the dual-mode binary ran. // // The agent lifecycle wiring below mirrors main.go's agent branch // exactly — the same internal/agent package does the work either way, // and cmd/xui-agent-windows is the Windows sibling of this entry. package main import ( "flag" "fmt" "log" "net/http" _ "net/http/pprof" "os" "os/signal" "path/filepath" "syscall" "github.com/mhsanaei/3x-ui/v3/internal/agent" "github.com/mhsanaei/3x-ui/v3/internal/config" "github.com/mhsanaei/3x-ui/v3/internal/logger" "github.com/mhsanaei/3x-ui/v3/internal/util/memtuning" "github.com/op/go-logging" ) func main() { args := os.Args[1:] // Version self-report: the agent's own display version (A-form), // from the same build-time displayVersion injection the panel // uses. Kept ahead of the subcommand so it works however the // binary was invoked. if len(args) > 0 { switch args[0] { case "-v", "--version", "version": fmt.Println(config.GetAgentDisplayVersion()) return } } // Service units (systemd / OpenRC / procd / nohup fallback in // install-agent.sh) invoke the binary with the historical "agent" // subcommand; consume it so those units keep working unchanged. if len(args) > 0 && args[0] == "agent" { args = args[1:] } else if len(args) > 0 && args[0][0] != '-' { fmt.Fprintf(os.Stderr, "xui-agent: unknown subcommand %q (this binary only runs the node agent)\n", args[0]) os.Exit(2) } runAgent(args) } // runAgent is main.go's agent branch, verbatim in behaviour: same // flags, same defaults, same shutdown wiring. func runAgent(args []string) { agentCmd := flag.NewFlagSet("agent", flag.ExitOnError) var configPath string agentCmd.StringVar(&configPath, "config", "/etc/x-ui/agent.json", "Path to agent config file") agentCmd.Parse(args) // Agent mode always uses the panel's directory layout for the // xray binary, geo files and config.json. Scoped to agent mode // only so the panel's own relative "bin" default is untouched. if os.Getenv("XUI_BIN_FOLDER") == "" { _ = os.Setenv("XUI_BIN_FOLDER", agent.DefaultBinFolder) } cfg, err := agent.LoadConfig(configPath) if err != nil { log.Fatalf("Failed to load agent config from %s: %v", configPath, err) } // The acknowledged traffic baseline persists next to the agent // config (its state home, e.g. /etc/x-ui), unless the config // names a path. if cfg.BaselinePath == "" { cfg.BaselinePath = filepath.Join(filepath.Dir(configPath), "agent-traffic-baseline.json") } // Initialize logger logger.InitLogger(logging.INFO) log.Printf("Starting 3x-ui agent (node: %s)", cfg.NodeName) // Agent memory profile (ITEM-P3 A1/M5): the same runtime tuning // family as the panel, with the agent's tighter budget, plus the // periodic release the panel runs through its job scheduler (the // agent has no scheduler), plus the panel's XUI_PPROF gate. for _, line := range memtuning.ApplyAgentMemoryTuning() { log.Println(line) } memtuning.StartPeriodicMemoryRelease() if os.Getenv("XUI_PPROF") == "true" { go func() { log.Println("pprof profiling server listening on 127.0.0.1:6060") if err := http.ListenAndServe("127.0.0.1:6060", nil); err != nil { log.Printf("pprof server stopped: %v", err) } }() } ag := agent.New(cfg) // Handle shutdown signals sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigCh log.Println("Shutting down agent...") ag.Stop() }() if err := ag.Run(); err != nil { log.Fatalf("Agent error: %v", err) } }