diff --git a/cmd/comparesrht/main.go b/cmd/comparesrht/main.go index e7eca64380ea590ce7bc0b63260ee71991500b1e..6d59b58f43d5647339d1bb2199796a18d91a2997 100644 --- a/cmd/comparesrht/main.go +++ b/cmd/comparesrht/main.go @@ -25,7 +25,7 @@ // panic on the first request. package main import ( - "fmt" + "errors" "log/slog" "os" "strings" @@ -37,6 +37,7 @@ "github.com/vaughan0/go-ini" "go.bigb.es/auxilia/scribe" "sourcecraft.dev/bigbes/sr-ht-core/config" coreserver "sourcecraft.dev/bigbes/sr-ht-core/server" + "sourcecraft.dev/bigbes/sr-ht-ecore/instconf" "sourcecraft.dev/bigbes/sr-ht-ecore/logging" "sourcecraft.dev/bigbes/sr-ht-ecore/login" @@ -173,49 +174,37 @@ // origin candidate is configured, and before crypto.InitCrypto (invoked by // server.New), which would otherwise fatal with a terse message on a missing // network-key or webhook key. func validateConfig(conf ini.File) string { - var missing []string - require := func(section, key string) { - if v, ok := conf.Get(section, key); !ok || strings.TrimSpace(v) == "" { - missing = append(missing, fmt.Sprintf("[%s] %s", section, key)) - } - } - - require("sr.ht", "network-key") // crypto: unified-login cookie fernet key - require("webhooks", "private-key") // crypto: webhook signing key (shared) - require("git.sr.ht", "repos") // bare repository root on disk - require("meta.sr.ht", "origin") // login/logout links in the nav - require("compare.sr.ht", "origin") // our own external origin - - // git.sr.ht needs at least one internal API origin candidate; without it - // config.GetAPI panics on the first authorization request. - apiOrigin := firstConfigured(conf, "git.sr.ht", - "api-internal-origin", "internal-origin", "api-origin", "origin") - if apiOrigin == "" { - missing = append(missing, - "[git.sr.ht] one of api-internal-origin, internal-origin, api-origin, origin") - } + err := instconf.Require(conf, + instconf.Need("sr.ht", "network-key"), // crypto: unified-login cookie fernet key + instconf.Need("webhooks", "private-key"), // crypto: webhook signing key (shared) + instconf.Need("git.sr.ht", "repos"), // bare repository root on disk + instconf.Need("meta.sr.ht", "origin"), // login/logout links in the nav + instconf.Need(service, "origin"), // our own external origin - if len(missing) > 0 { + // git.sr.ht needs at least one internal API origin candidate; without + // it config.GetAPI panics on the first authorization request. The + // ladder is asked for by name rather than written out, so this check + // and the lookup below cannot come to disagree about it. + instconf.NeedAny("git.sr.ht", instconf.APIOriginKeys()...), + ) + if err != nil { // One record and one exit, deliberately: an operator fixing a config // wants every gap in front of them at once, and a line per missing key - // is a restart per missing key. The keys go in as a slice attribute - // rather than a joined string so the structured sinks keep them as a + // is a restart per missing key. Strings() keeps them a slice attribute + // rather than a joined string, so the structured sinks keep them as a // list — the tint handler still renders them on one line. - slog.Error("incomplete configuration", "missing", missing) + var missing *instconf.MissingKeysError + if errors.As(err, &missing) { + slog.Error("incomplete configuration", "missing", missing.Strings()) + } else { + slog.Error("incomplete configuration", scribe.Err(err)) + } os.Exit(1) } - return apiOrigin -} -// firstConfigured returns the value of the first present, non-empty key in -// section, or "" if none are set. -func firstConfigured(conf ini.File, section string, keys ...string) string { - for _, k := range keys { - if v, ok := conf.Get(section, k); ok && strings.TrimSpace(v) != "" { - return v - } - } - return "" + // Cannot report false: NeedAny above walked the same ladder. + apiOrigin, _ := instconf.InternalAPIOrigin(conf, "git.sr.ht") + return apiOrigin } // resolveBind reconstructs the primary bind address server.New will use, for