Core config & arguments
The framework configures itself through the same machinery it gives your
services: the core owns a config struct, and its values follow the usual
precedence — defaults < config files < environment < arguments. This page
lists what the core processes, which values are deliberately locked out of
config files, and how to remove core features from a binary entirely.
Reserved ids#
Two service ids are reserved — registering under either is a startup
error: core, the framework's own configuration, and introspection,
the core-registered Introspector. In config files the
core's own values nest under its id:
{ "core": { "disable": ["sqlite"], "override": ["sqlite=mysql"] } }
Environment names, as always, derive from the applet id, not from
core: for an applet srv the core's --config argument reads
SRV_CONFIG.
What the core processes#
| Value | Argument | Environment | Config file | Purpose |
|---|---|---|---|---|
| config | --config, -c | derived (<APPLET>_CONFIG) | never | explicit config file; replaces the location search |
| help | --help, -h | never | never | print the full argument schema with current effective values, exit 0 |
| write-config | --write-config | never | never | write the merged configuration, exit 0 |
| disable | --disable | derived | yes | remove service ids from the closure |
| enable | --enable | derived | yes | force service ids into the closure |
| override | --override | derived | yes | remap dependency ids (old=new) |
disable, enable and override are list-valued: repeat the argument to
append (--disable a --disable b), comma-separate the environment value,
use arrays in files.
The core also dogfoods its own metadata layer, so tooling can treat
these values sensibly: --config declares HintFile, and
--disable/--enable declare HintServiceID — completable from the
Introspector. --override takes from=to pairs, which no honest hint
describes, so it declares none. See
Your config struct.
Run-scoped values and why config files refuse them#
help and write-config are argument-only. A config file or an
inherited environment variable setting them would be a persistent denial
of service — every run printing help, or writing a config and exiting.
config is settable by argument or environment (<APPLET>_CONFIG is a
legitimate deployment pattern) but never by a config file — a file
choosing the next file is redirection, not configuration.
Enforcement is loud in both directions:
- a config file attempting to set any of the three (
help,write-config,config) is a startup error, never a silent skip; - all three are excluded from
--write-configoutput, so a generated config can never smuggle them back in.
The same semantics are available to your own services: tag a field
dump:"-" and it becomes run-scoped — excluded from generated configs and
refused from config files.
`--write-config` and `--config` together#
--write-config short-circuits the run: the pipeline stops right after
the merge — the configuration is written and the process exits 0 without
Configured, Start or Run. Where it writes depends on --config:
$ mytool --write-config # no target: merged config as JSON to stdout $ mytool --write-config -c /etc/mytool/config.yaml # target: written in the format the extension names $ mytool --port 9090 --write-config -c local.json # arguments participate in the merge as usual
The output format follows the target's extension via a format provider —
.json natively, .yaml with the provider linked in. If no registered
provider claims the target's extension, startup fails and says so.
The target is input and output:
- An existing target is loaded first as the explicit config, then
rewritten — which makes
--write-configa one-command normalizer: point it at a hand-edited or differently-formatted file and it comes back canonical (this is also the way to convert JSON ⇄ YAML). - A missing target is only created. And here lies the one deliberate
exception to
--configsemantics: in a normal run a non-empty--configreplaces the standard location search entirely, but in--write-configmode a missing target does not — the locations are loaded and merged as usual, so the newly written file captures the configuration the binary would actually be running with.
The output is also minimal: empty values — and the sections they would leave empty — are omitted.
Permissions are conservative: a newly created file gets mode 0600; an
existing target's permissions are the operator's prior decision and are
left untouched — normalizing a file must not silently revoke a
deliberately granted group read.
Recomposing services#
Operators can recompose a binary without recompiling:
$ mytool --disable sqlite --enable mysql --override sqlite=mysql
disableremoves services from the closure even if required. Disabling the dispatched applet itself is a startup error, as is listing the same id in bothenableanddisable.enablepulls services in — with their transitive dependencies — even if nothing requires them.overrideremaps ids: wherever a dependency namessqlite,mysqlis resolved instead. The substitute must satisfy the dependency's field type, checked at resolve time. An override key may name an unregistered id, and an override matching no dependency is legal — but every unused key is logged as a warning, so a typo never silently does nothing.- A dependency marked
;optionaltolerates a disabled target, never an unknown one: an id that names no registered service is a startup error even on an optional field.
Limits#
Config files are size-capped: the default cap is 1 MiB, checked with
stat before the file is ever opened. The cap is a build-time
property of the binary:
fw.MaxConfigSize(64 << 10) // tighter than the default
Suppressing core features#
A hardened or embedded binary can remove pieces of the core's surface
entirely — build-time, from main() or an init() before Main:
func main() { fw.Suppress(fw.FeatureConfigFile, fw.FeatureOverride) fw.Main() }
Suppressible: FeatureConfigFile (--config), FeatureWriteConfig,
FeatureDisable, FeatureEnable, FeatureOverride, FeatureHelp.
A suppressed feature vanishes: its argument becomes unknown (a
strict-parse error), its environment variable is never consulted, and its
key appearing in a config file's core section is a loud startup error —
operators learn it is not honored instead of wondering why it is ignored.
The counterpart for default-off features is fw.Enable. There is
currently one: FeatureSCMDebug unlocks the Windows-only --scm-debug
argument, which runs the Windows service code path in a console for
testing. It is argument-only by construction, absent from --help, and
without the opt-in the token is rejected as an unknown argument — as it is
on every non-Windows platform.
Misusing the build-time API is itself a collected startup violation, never
silently ignored: suppressing a default-off feature, enabling a default-on
one, unknown features, or a non-positive MaxConfigSize all fail startup
with every problem reported at once.
Related: the full rules live in the design spec; the API surface is in the reference.