Files
summercms/modules/compass/README.md
Jakub Zych 602e0412f3 docs: add a Role in the framework section to every module README
Each module README now says where the module sits in the dependency
graph, why it is its own module, and lists use cases for application
developers.
2026-10-11 00:58:17 +02:00

125 lines
6.2 KiB
Markdown

# compass
Layered YAML configuration with per-environment directories, `SUMMER_` environment overrides, embedded plugin defaults and dot-path access.
`import "git.golem15.com/golem15/summercms/modules/compass"`
**Name:** Points you the right way; configuration tells the application how to behave in each environment.
## Overview
`compass` is the SummerCMS counterpart of WinterCMS's `config/*.php` files, per-environment config directories, `.env` support and `Config::get('app.name')`. It merges several layers into one tree built on koanf, and every key is read with a dot path such as `app.name`. Plugin defaults live under the bare plugin ID (`acme.blog.posts_per_page`) instead of WinterCMS's `acme.blog::posts_per_page`. The generated `main` of an application calls `compass.Load("config")` and hands the result to [backpack](../backpack/README.md); [party](../party/README.md) merges plugin defaults into it during activation.
## Role in the framework
A dependency-free foundation module that `backpack` holds and most service modules read from. It is separate so configuration loading can run before the application exists, and so any package can read settings without depending on the container.
Use cases:
- Keep shared settings in a base file and override them per environment.
- Override any key from the environment, for example in containers and CI.
- Ship plugin defaults embedded in the plugin, so the plugin works with no host configuration.
## Features
- A fixed layer order, lowest to highest precedence:
1. Plugin defaults added with `compass.Config.MergePlugin`: a plugin's `config.yaml` becomes `<plugin id>.<key>`, any other `<name>.yaml` becomes `<plugin id>.<name>.<key>`.
2. `<dir>/*.yaml` (and `*.yml`): each file is a section named after the file, so `config/app.yaml` provides `app.*`. Files load in sorted order.
3. `<dir>/env/<environment>/*.yaml`: per-environment sections with the same naming.
4. `SUMMER_` environment variables, including values from a `.env` file (see Configuration).
5. `<dir>/env/<environment>/overrides.yaml`, the file written by `compass.Config.Persist`.
6. In-memory values stored with `compass.Config.Set`.
- Typed getters with zero-value defaults: `compass.Config.String`, `compass.Config.Int`, `compass.Config.Bool`, plus `compass.Config.Lookup` and `compass.Config.Has` to tell a missing key from a zero value.
- `compass.Config.LoadSection` unmarshals a whole subtree into a struct using `koanf` struct tags.
- Runtime overrides: `compass.Config.Set` changes a value in memory, `compass.Config.Persist` saves all runtime values atomically to the environment's `overrides.yaml`, keeping the keys already saved there and replacing the saved value of any key set at runtime (directory mode 0700, file mode 0600, refusing any path outside the config directory), and `compass.Config.Reload` rereads every source and discards unsaved runtime values.
- `compass.Config.Environment` reports the active environment name, which must consist of letters, digits, `-` and `_`.
- Safe for concurrent reads and writes.
## Usage
```go
package blog
import (
"git.golem15.com/golem15/summercms/modules/compass"
)
type mailSettings struct {
Host string `koanf:"host"`
Port int `koanf:"port"`
}
func loadConfig() (*compass.Config, error) {
cfg, err := compass.Open(compass.Options{Dir: "config", Env: "development"})
if err != nil {
return nil, err
}
name := cfg.String("app.name")
perPage := cfg.Int("acme.blog.posts_per_page")
_, _ = name, perPage
var mail mailSettings
if err := cfg.LoadSection("mail", &mail); err != nil {
return nil, err
}
// Store a runtime override and write it to config/env/development/overrides.yaml.
if err := cfg.Set("acme.blog.posts_per_page", 25); err != nil {
return nil, err
}
return cfg, cfg.Persist()
}
```
A matching configuration directory:
```yaml
# config/app.yaml
name: Acme
url: http://localhost:8080
# config/env/development/app.yaml
debug: true
```
## API reference
| Identifier | Description |
|------------|-------------|
| `compass.Config` | The merged configuration tree with dot-path access. |
| `compass.Load` | Opens a config directory, taking the environment from `SUMMER_ENV`. |
| `compass.Open` | Opens configuration with explicit `compass.Options`. |
| `compass.Options` | Config directory, environment name and the environment variable list to read (defaults to the process environment). |
| `compass.Config.String` / `compass.Config.Int` / `compass.Config.Bool` | Typed getters that return the zero value for a missing key. |
| `compass.Config.Lookup` / `compass.Config.Has` | Raw value lookup and existence check. |
| `compass.Config.LoadSection` | Unmarshals a subtree into a struct with `koanf` tags. |
| `compass.Config.MergePlugin` | Adds a plugin's embedded default configuration under its plugin ID. |
| `compass.Config.Set` / `compass.Config.Persist` / `compass.Config.Reload` | Runtime overrides, saving them to disk, and rebuilding from disk. |
| `compass.Config.Environment` | Returns the active environment name. |
## Configuration
`compass` reads the process environment (or `compass.Options.Environ` when it is set):
| Variable | Default | Effect |
|----------|---------|--------|
| `SUMMER_ENV` | `production` | Selects the environment directory `config/env/<name>/`. An explicit `compass.Options.Env` wins over it. |
| `SUMMER_<SECTION>__<KEY>` | none | Overrides a config key: the prefix is removed, `__` separates path segments and the name is lower-cased, so `SUMMER_DATABASE__DSN` sets `database.dsn` and `SUMMER_ADMIN__JWT__SECRET` sets `admin.jwt.secret`. |
A `.env` file in the parent directory of the config directory (next to `config/`) supplies `KEY=VALUE` lines, with optional `export` prefixes and quotes, for variables that are not already set in the real environment. It never modifies the process environment.
## Dependencies
- SummerCMS modules: none.
- Third-party: `github.com/knadh/koanf/v2` with its `providers/file`, `providers/env/v2`, `providers/confmap` and `parsers/yaml` packages.
- Standard library: `fmt`, `io/fs`, `os`, `path/filepath`, `sort`, `strings`, `sync`, `unicode`.
## Testing
```sh
go test ./modules/compass/...
```
The tests use temporary directories and explicit environment lists and need no external services.