From aca0674ecc05078029322fd6343378768f3bbc40 Mon Sep 17 00:00:00 2001 From: bigbes Date: Sun, 9 Aug 2026 00:20:56 +0300 Subject: [PATCH] instconf: the origin ladder and the one-pass config check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validateConfig kept its shape and lost its body. instconf.Require is the same decision — every missing key in one record, one exit, so an operator edits config.ini once instead of restarting per gap — and firstConfigured was this service's copy of the four-key API-origin ladder core-go panics at the end of. NeedAny is handed APIOriginKeys() rather than the four names written out, so the startup check and InternalAPIOrigin cannot drift apart the way two literal lists eventually do. --- cmd/comparesrht/main.go | 65 +++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 38 deletions(-) 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 @@ package main import ( - "fmt" + "errors" "log/slog" "os" "strings" @@ -37,6 +37,7 @@ import ( "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 @@ func main() { // 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") - } - - if len(missing) > 0 { + 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 + + // 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