CI / test (push) Successful in 4m48s
Saving settings replaced the whole Settings value, which would have dropped SelfEdgeID and made the next start create a second self edge with a new token. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
95 lines
3.3 KiB
Go
95 lines
3.3 KiB
Go
// 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())
|
|
}
|