Sandbar channels raise and resolve alerts through its API, with the API key sealed at rest and never returned to the browser. Resources and sites can be tagged and filtered by tag; sites can be renamed. The secretbox package now comes from Reef v0.1.9 instead of a private copy. 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/reef/dotenv"
|
|
"git.veillax.com/Veillax/reef/secretbox"
|
|
"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())
|
|
}
|