120 lines
3.9 KiB
Go
120 lines
3.9 KiB
Go
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)
|
|
}
|
|
}
|