Repository navigation
feat(config)!: configure Fanout from the environment only - #305
Merged
Merged
Conversation
Use vendor credential and endpoint contracts with schema-generated public defaults so deployment configuration has explicit ownership. Keep secrets in runtime injection and exclude personal files from production launches. BREAKING CHANGE: Replace the generic AI key and base URL environment names and YAML keys with the selected provider's settings. OpenAI base URLs now include the API version path. Retired names have no aliases or fallback.
Review fixes for the provider environment names and dotenv layout. - AI chat is enabled only by FANOUT_AI_PROVIDER (YAML ai.provider), which now defaults to empty. A selected provider requires its API key, and only its base URL is validated. Inherited ANTHROPIC_* or OPENAI_* values from other tools no longer enable chat or block startup. - Provider values must be exactly anthropic or openai; no case or space normalization. config.AIBaseURL is the only source of endpoint defaults. - Generated .env and .env.production contain only environment-specific overrides (the listener address, declared with a local struct tag), so launcher-injected values no longer override --config YAML. .env.example lists every setting with an empty value and documents the default in comments. - The launcher forwards SIGINT, SIGTERM and SIGHUP on every delivery, classifies secrets from each block's generated header line only, and accepts CRLF inventories. - The docgen overwrite guard refuses quoted or escaped values. - The loader is the only check of accepted environment names. - Document the tracked .env migration and the AI configuration upgrade.
Fanout's settings are all flat, so a second configuration source added precedence bugs without adding expressiveness. - Remove the YAML file, the --config flag, fanout.example.yaml, fanout.docker.yaml and the koanf dependency. The loader resolves built-in defaults, then the process environment, from the schema tags. Environment names are unchanged; error messages name them directly. - Every secret also accepts <NAME>_FILE (both set fails, one trailing newline is stripped, an empty file fails, contents are never printed). Vendor variables are read only for the selected AI provider. - The binary listens on 127.0.0.1:7520 by default. The image sets FANOUT_ADDR=:7520 and FANOUT_DATA_DIR and bakes in no configuration file. - fanout healthcheck derives its probe from FANOUT_ADDR, supports TLS on any bind address, and never routes the local probe through a proxy. - Only the generated .env.example is tracked. just run loads an optional, ignored personal .env; inherited values win. - Docs describe environment and file-based secret delivery and carry one upgrade table for every breaking change on this branch.
- .env.example comments out every entry, so an env file copied from it sets nothing and cannot replace the image's FANOUT_ADDR with an empty value. - fanout healthcheck probes 127.0.0.1 for every wildcard bind, skips certificate verification only for derived IP-literal probes (zoned IPv6 included), verifies hostnames, and works out whether its URL is derived once, in argument parsing. It uses the exported DefaultAddr. - One provider table drives key, base URL and file resolution, validation, and error names. - The AI vendor-variable note appears only on the AI settings page. - Delete migration and upgrade guidance and tests that named removed settings; no legacy paths are supported. - Provider tests pass resolved base URLs and assert request paths.
…ames - The authentication guide shows the OIDC community-access example as environment assignments and no longer describes a configuration file. - The storage model, an OAuth comment, and the architecture and persistence diagrams name FANOUT_ROLLUP_SKIP_TO_LATEST, FANOUT_TRUSTED_PROXY_CIDRS, and FANOUT_DATA_DIR.
…tificate A derived HTTPS probe that dials an IP literal trusted any certificate, because a bind IP is rarely in the certificate's SAN list. It now trusts only the leaf in FANOUT_TLS_CERT_FILE and verifies it under that certificate's first DNS or IP SAN, so the probe confirms the listener presents the configured certificate without disabling verification. The local TLS test asserts tls.CertificateVerificationError, which both Linux (hostname mismatch) and macOS (unknown authority) return.
5 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Fanout is now configured from the environment only. Built-in defaults live in
internal/config/config.go, and the process environment overrides them. The YAML loader,--config,fanout.example.yaml,fanout.docker.yaml, and koanf are removed. All settings are flat, so a second configuration source added precedence rules without adding expressiveness.FANOUT_names. Vendor credentials and endpoints use the vendor's documented names:ANTHROPIC_API_KEY,ANTHROPIC_BASE_URL,OPENAI_API_KEY, andOPENAI_BASE_URL. UnknownFANOUT_names still fail at startup.FANOUT_AI_PROVIDERis empty by default, which disables chat. Set it toanthropicoropenai; that provider's key is then required. Fanout reads and validates only the selected provider's variables.<NAME>_FILE:FANOUT_AUTH_CODE_SECRET,FANOUT_OIDC_CLIENT_SECRET,FANOUT_SMTP_PASSWORD,FANOUT_METRICS_TOKEN,ANTHROPIC_API_KEY, andOPENAI_API_KEY. Setting both forms fails, one trailing newline is stripped, an empty file fails, and errors never print file contents.127.0.0.1:7520by default. The container image setsFANOUT_ADDR=:7520andFANOUT_DATA_DIR=/var/lib/fanout/dataand contains no configuration file.fanout healthcheckderives its URL fromFANOUT_ADDR, and uses HTTPS whenFANOUT_TLS_CERT_FILEis set. Wildcard binds probe127.0.0.1. Derived probes bypass proxies. HTTPS probes of an IP address trust only the certificate inFANOUT_TLS_CERT_FILE, verified under its own SAN; hostname probes verify normally. An explicit URL argument keeps default client behavior..env.example. Every entry is commented out, so copying it into--env-filesets nothing.just runloads an optional, ignored.envwith Node'sparseEnv, and inherited values win. The binary never reads dotenv files.llms.txt, and the generated settings reference describe the environment-only model.Breaking changes
There are no aliases or fallbacks.
--configare removed. The image ignores a file mounted at/etc/fanout/fanout.yaml; set the matching environment variables from the settings reference instead.FANOUT_AI_API_KEYandFANOUT_AI_BASE_URLare removed and fail at startup as unknown names. UseANTHROPIC_API_KEYandANTHROPIC_BASE_URL, orOPENAI_API_KEYandOPENAI_BASE_URL.FANOUT_AI_PROVIDERset to exactlyanthropicoropenai. An API key alone no longer enables chat.OPENAI_BASE_URLincludes/v1(defaulthttps://api.openai.com/v1), and Fanout appends/responses.ANTHROPIC_BASE_URLexcludes/v1(defaulthttps://api.anthropic.com), and Fanout appends/v1/messages.FANOUT_ADDR=:7520to accept remote connections; the container image already does.Verification
just checkjust test-racejust e2e_FILEsecrets, and provider gating