// Hammerhead is the control plane: web UI + API that tells blacktip and bamboo what to do. package main import ( "context" "flag" "fmt" "log" "net" "net/http" "os" "strconv" "time" "git.veillax.com/Veillax/hammerhead" "git.veillax.com/Veillax/hammerhead/internal/secretbox" "git.veillax.com/Veillax/reef/dotenv" "git.veillax.com/Veillax/reef/selfupdate" ) // version is set at build time (deploy/build.sh). var version = "dev" // localAddr turns a listen address into one this machine can dial: ":8080" and "0.0.0.0:8080" // become "127.0.0.1:8080". func localAddr(listen string) string { host, port, err := net.SplitHostPort(listen) if err != nil { return listen } if host == "" || host == "0.0.0.0" || host == "::" { host = "127.0.0.1" } return net.JoinHostPort(host, port) } func env(k, def string) string { if v := os.Getenv(k); v != "" { return v } return def } func main() { // secrets come from a .env file (or the real environment), read before flags so flags can default to them envFile := dotenv.ArgPath(os.Args[1:], ".env") if used, err := dotenv.Load(envFile); err != nil { log.Fatalf("read %s: %v", envFile, err) } else if used != "" { log.Printf("loaded settings from %s", used) } listen := flag.String("listen", env("HAMMERHEAD_LISTEN", ":8080"), "address for the web UI and API") data := flag.String("data", env("HAMMERHEAD_DATA", "./hammerhead-data"), "directory for the database") url := flag.String("url", env("HAMMERHEAD_URL", ""), "public URL people reach hammerhead at (used for login redirects and SSO); can also be set in Settings") flag.String("env", ".env", "file to read secrets and settings from") genkey := flag.Bool("genkey", false, "print a new random HAMMERHEAD_MASTER_KEY and exit") autoUpdate := flag.Bool("auto-update", os.Getenv("HAMMERHEAD_AUTO_UPDATE") == "1", "check for new releases every few hours and update and restart by itself (Linux and macOS)") showVersion := flag.Bool("version", false, "print the version and exit") flag.Parse() if *showVersion { os.Stdout.WriteString("hammerhead " + version + "\n") return } if *genkey { fmt.Println(secretbox.GenerateKey()) return } days, _ := strconv.Atoi(os.Getenv("HAMMERHEAD_LOG_DAYS")) srv, err := hammerhead.New(*data, hammerhead.Options{ AdminEmail: os.Getenv("HAMMERHEAD_ADMIN_EMAIL"), AdminPassword: os.Getenv("HAMMERHEAD_ADMIN_PASSWORD"), DashboardURL: *url, MasterKey: os.Getenv("HAMMERHEAD_MASTER_KEY"), MetricsToken: os.Getenv("HAMMERHEAD_METRICS_TOKEN"), LogRetentionDays: days, SelfDomain: os.Getenv("HAMMERHEAD_SELF_DOMAIN"), SelfTarget: localAddr(*listen), SelfTunnel: os.Getenv("HAMMERHEAD_SELF_TUNNEL"), Version: version, AutoUpdate: *autoUpdate, }) if err != nil { log.Fatal(err) } defer srv.Close() if *autoUpdate { go selfupdate.Run(context.Background(), selfupdate.Config{Server: "https://git.veillax.com", Repo: "Veillax/hammerhead", App: "hammerhead", Current: version, Logf: log.Printf, Restart: func(exe string) error { srv.Close(); return selfupdate.Restart(exe) }}) } log.Printf("hammerhead %s listening on %s (open it in a browser to finish setup)", version, *listen) hs := &http.Server{Addr: *listen, Handler: srv.Handler(), ReadHeaderTimeout: 10 * time.Second} log.Fatal(hs.ListenAndServe()) }