package main import ( "context" "errors" "flag" "fmt" "log" "net" "net/http" "os" "os/signal" "strconv" "strings" "syscall" "time" ) func envOr(name, fallback string) string { if value := strings.TrimSpace(os.Getenv(name)); value != "" { return value } return fallback } func envBool(name string, fallback bool) bool { raw := strings.TrimSpace(os.Getenv(name)) if raw == "" { return fallback } value, err := strconv.ParseBool(raw) if err != nil { return fallback } return value } func main() { hostname, _ := os.Hostname() cfg := config{} flag.StringVar(&cfg.Listen, "listen", envOr("RUSTPROXY_DOCKER_AGENT_LISTEN", "127.0.0.1:9876"), "listen address") flag.StringVar(&cfg.Token, "token", envOr("RUSTPROXY_DOCKER_AGENT_TOKEN", ""), "Bearer token (prefer environment variable)") flag.StringVar(&cfg.DockerSock, "docker-socket", envOr("RUSTPROXY_DOCKER_SOCKET", "/var/run/docker.sock"), "Docker Engine unix socket") flag.StringVar(&cfg.AdvertiseIP, "advertise-ip", envOr("RUSTPROXY_DOCKER_ADVERTISE_IP", ""), "optional preferred host IP; all usable host addresses are still reported") flag.StringVar(&cfg.PublicIPURL, "public-ip-url", envOr("RUSTPROXY_DOCKER_PUBLIC_IP_URL", "https://api.ipify.org"), "public IP lookup URL; empty disables") flag.StringVar(&cfg.Hostname, "hostname", envOr("RUSTPROXY_DOCKER_HOSTNAME", hostname), "reported host name") flag.BoolVar(&cfg.TLS, "tls", envBool("RUSTPROXY_DOCKER_AGENT_TLS", true), "serve HTTPS; enabled by default") flag.StringVar(&cfg.CertFile, "cert-file", envOr("RUSTPROXY_DOCKER_AGENT_CERT_FILE", "/var/lib/rustproxy-docker-agent/agent.crt"), "TLS certificate file") flag.StringVar(&cfg.KeyFile, "key-file", envOr("RUSTPROXY_DOCKER_AGENT_KEY_FILE", "/var/lib/rustproxy-docker-agent/agent.key"), "TLS private key file") initCert := flag.Bool("init-cert", false, "create the self-signed TLS certificate if missing and exit") printFingerprint := flag.Bool("print-fingerprint", false, "print SHA-256 certificate fingerprint and exit") flag.Parse() if *initCert { fingerprint, created, err := ensureSelfSignedCertificate(cfg.CertFile, cfg.KeyFile, cfg.Hostname) if err != nil { log.Fatal(err) } fmt.Printf("%s\n", fingerprint) if created { log.Printf("created self-signed certificate %s", cfg.CertFile) } return } if *printFingerprint { fingerprint, err := certificateFingerprint(cfg.CertFile) if err != nil { log.Fatal(err) } fmt.Printf("%s\n", fingerprint) return } if len(cfg.Token) < 24 { log.Fatal("RUSTPROXY_DOCKER_AGENT_TOKEN must contain at least 24 characters") } if _, err := os.Stat(cfg.DockerSock); err != nil { log.Fatalf("Docker socket unavailable: %v", err) } if _, _, err := net.SplitHostPort(cfg.Listen); err != nil { log.Fatalf("invalid listen address: %v", err) } if cfg.TLS { fingerprint, created, err := ensureSelfSignedCertificate(cfg.CertFile, cfg.KeyFile, cfg.Hostname) if err != nil { log.Fatalf("TLS certificate: %v", err) } cfg.Fingerprint = fingerprint if created { log.Printf("generated self-signed TLS certificate; SHA-256 %s", fingerprint) } } server := &http.Server{Addr: cfg.Listen, Handler: handler(cfg), ReadHeaderTimeout: 3 * time.Second, IdleTimeout: 30 * time.Second, MaxHeaderBytes: 16 << 10} ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() go func() { <-ctx.Done() shutdown, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = server.Shutdown(shutdown) }() scheme := "http" if cfg.TLS { scheme = "https" } log.Printf("RustProxy Docker Agent %s listening on %s://%s", version, scheme, cfg.Listen) if cfg.Fingerprint != "" { log.Printf("TLS certificate SHA-256: %s", cfg.Fingerprint) } var err error if cfg.TLS { err = server.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile) } else { err = server.ListenAndServe() } if err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) } }