76 lines
2.2 KiB
Go
76 lines
2.2 KiB
Go
// 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"
|
|
"strings"
|
|
"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,
|
|
Fallback: strings.TrimRight(os.Getenv("FALLBACK_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)))
|
|
}
|