- docs/backend: admin controllers, forms, lists and filters, relation manager, users and permissions, settings, partials and widgets, admin SPA - docs/services: storage, outbound HTTP, realtime, Web Push, search, parity testing and the Frontend and AJAX (not provided) page - Examples for cabana (with testdata/docs YAML), fetchguard, lighthouse and its centrifugo driver, flare, beachcomber and typesense, tide; lighthouse and beachcomber TestDocs* regions run on their Postgres harnesses - concept map rows link their guide pages and the not-provided rows the Frontend and AJAX page; index lists Backend, Database and Services - TestDocsRequiredPages asserts the D-08 section order
98 lines
4.5 KiB
Markdown
98 lines
4.5 KiB
Markdown
---
|
|
title: Configuration
|
|
description: Read layered configuration with compass, from plugin defaults through per-environment files and SUMMER_ variables to runtime overrides saved to disk.
|
|
section: services
|
|
order: 10
|
|
---
|
|
# Configuration
|
|
|
|
WinterCMS reads configuration with `Config::get('app.name')` from `config/*.php`, per-environment directories and `.env`. SummerCMS reads it from a `compass.Config` built by [compass](../../modules/compass/README.md), with the same dot paths. [Setup, Configuration](../setup/configuration.md) lists the keys an application sets; this page covers how the layers merge and how code reads and changes them.
|
|
|
|
## Layers
|
|
|
|
`compass.Config` merges its sources in a fixed order. Each layer overrides the ones before it:
|
|
|
|
1. Plugin defaults, merged with `compass.Config.MergePlugin` when the plugin is activated. A plugin's `config/config.yaml` becomes `<plugin id>.<key>`, so `acme.blog.posts_per_page` is the WinterCMS `acme.blog::posts_per_page`. Any other `config/<name>.yaml` becomes `<plugin id>.<name>.<key>`.
|
|
2. `config/*.yaml`. Each file is a section named after the file, so `config/app.yaml` provides `app.*`.
|
|
3. `config/env/<environment>/*.yaml`, the per-environment sections.
|
|
4. `SUMMER_` environment variables and the `.env` file next to `config/`. `SUMMER_MAIL__DRIVER` sets `mail.driver`: the prefix is removed, `__` separates the path segments and the name is lower-cased. A `.env` value applies only when the real environment does not set the same variable.
|
|
5. `config/env/<environment>/overrides.yaml`, written by `compass.Config.Persist`.
|
|
6. Values set in memory with `compass.Config.Set`.
|
|
|
|
The environment comes from `SUMMER_ENV` and defaults to `production`. Its name may contain only letters, digits, `-` and `_`.
|
|
|
|
## Reading values
|
|
|
|
The typed getters `compass.Config.String`, `compass.Config.Int` and `compass.Config.Bool` return the zero value for a missing key. Use `compass.Config.Lookup` or `compass.Config.Has` when a missing key must be told apart from a zero value, and `compass.Config.LoadSection` to read a whole section into a struct with `koanf` tags:
|
|
|
|
```go src=modules/compass/example_test.go#ExampleOpen
|
|
dir, err := os.MkdirTemp("", "acme-config")
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
if err := writeConfig(dir); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
// Environ stands in for the process environment (nil reads os.Environ).
|
|
cfg, err := compass.Open(compass.Options{
|
|
Dir: dir,
|
|
Env: "development",
|
|
Environ: []string{"SUMMER_MAIL__DRIVER=smtp"},
|
|
})
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
// A plugin's embedded config/config.yaml becomes its defaults.
|
|
plugin := fstest.MapFS{"config/config.yaml": {Data: []byte("posts_per_page: 10\n")}}
|
|
if err := cfg.MergePlugin("acme.blog", plugin); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
fmt.Println(cfg.String("app.name"), cfg.Bool("app.debug"), cfg.Int("acme.blog.posts_per_page"))
|
|
var mail mailSettings
|
|
if err := cfg.LoadSection("mail", &mail); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
fmt.Println(mail.Driver, mail.From)
|
|
_, found := cfg.Lookup("app.timezone")
|
|
fmt.Println(cfg.Environment(), found)
|
|
|
|
// A runtime override, saved to env/development/overrides.yaml.
|
|
if err := cfg.Set("acme.blog.posts_per_page", 25); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
if err := cfg.Persist(); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
saved, _ := os.ReadFile(filepath.Join(dir, "env", "development", "overrides.yaml"))
|
|
fmt.Print(string(saved))
|
|
// Output:
|
|
// Acme true 10
|
|
// smtp blog@example.com
|
|
// development false
|
|
// acme:
|
|
// blog:
|
|
// posts_per_page: 25
|
|
```
|
|
|
|
The application opens its configuration once with `compass.Load("config")` in the generated `main` and hands it to the `backpack.App`. Plugins read it through `app.Config` and never open their own.
|
|
|
|
## Changing values at runtime
|
|
|
|
`compass.Config.Set` changes a value in memory. `compass.Config.Persist` saves every value set at runtime to `config/env/<environment>/overrides.yaml`, keeping the keys already saved there. It replaces the file atomically, creates it readable only by its owner and refuses any path outside the config directory. `compass.Config.Reload` rereads every source and discards values that were set but not persisted.
|
|
|
|
Values that admins edit in the backend are not configuration: settings pages store them in a database row; see [Settings](../backend/settings.md).
|
|
|
|
> [!WARNING]
|
|
> `overrides.yaml` is written by the running application. Keep it out of version control, and keep secrets in environment variables rather than in values the application persists.
|