// Command agent is the Linux client installed on machines without a public IP. // It runs as a root systemd service, dials the server over HTTP long-polling, // and opens a root PTY when an admin requests a terminal in the web panel. package main import ( "context" "log" "net" "net/url" "os" "os/signal" "strconv" "syscall" "github.com/x-grk/longpolling-remote-access/internal/agent" "github.com/x-grk/longpolling-remote-access/internal/tunnel" ) func main() { host := envOr("HOST", "https://REDACTED:8445") cfg := agent.Config{ Host: host, EnrollSecret: os.Getenv("ENROLL_SECRET"), Name: os.Getenv("AGENT_NAME"), StatePath: envOr("STATE_PATH", "/etc/lpagent/state.json"), TunnelAddr: tunnelAddr(host), // A locally set address wins over the one from the panel settings. TunnelPinned: os.Getenv("TUNNEL_ADDR") != "" || os.Getenv("TUNNEL_PORT") != "", } if cfg.EnrollSecret == "" { // Only required for the very first run (before enrollment). if _, err := os.Stat(cfg.StatePath); os.IsNotExist(err) { log.Fatal("ENROLL_SECRET is required for first enrollment") } } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() err := agent.New(cfg).Run(ctx) // Stopping (an update, a restart): hang the terminals up, so their shells // save their history instead of being killed. agent.CloseTerminals() if err != nil { log.Fatalf("agent: %v", err) } } func envOr(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } // tunnelAddr derives the direct-tunnel address (host:port) from the server URL. // It is only the starting point: the agent follows the host and port set in // the panel settings, unless TUNNEL_ADDR (the whole address) or TUNNEL_PORT // (just the port) pins it here. Set TUNNEL_ADDR=off to disable the tunnel. func tunnelAddr(hostURL string) string { if v := os.Getenv("TUNNEL_ADDR"); v != "" { if v == "off" { return "" } return v } u, err := url.Parse(hostURL) if err != nil || u.Hostname() == "" { return "" } return net.JoinHostPort(u.Hostname(), envOr("TUNNEL_PORT", strconv.Itoa(tunnel.DefaultPort))) }