Files
summercms/modules/compass/README.md
Jakub Zych 5fb22c28d0 fix(11-04): keep earlier overrides when compass Persist saves
Persist rewrote overrides.yaml with only this process's runtime values,
so saving one key (for example websockets:generate-vapid-keys --update)
dropped every key persisted earlier. It now starts from the saved file
and lets runtime values win.
2026-09-30 13:45:13 +02:00

5.6 KiB

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"

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; party merges plugin defaults into it during activation.

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

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:

# 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

go test ./modules/compass/...

The tests use temporary directories and explicit environment lists and need no external services.