Skip to content

Keksclan/goConfy

Repository files navigation

goConfy

goConfy Maskottchen

Maskottchen gezeichnet von @theanc1entmagusbride – Vielen Dank! 🎨

Go Reference Go Report Card Go Version License: MIT CI

goConfy is a strongly typed, strict, and secure YAML configuration loader for Go.

It provides a complete configuration pipeline: YAML parsing → environment macro expansion → dotenv loading → profile-based overrides → strict decoding → normalization → validation → secret redaction.

Features

  • YAML parsing via gopkg.in/yaml.v3
  • Macros: {ENV:KEY:default} and {FILE:/path:default} — exact-match, secure, no shell injection
  • Dotenv support: load .env files without mutating os.Environ()
  • Profile-based overrides: dev, staging, prod merged into base config
  • Strict decoding: rejects unknown YAML keys (catches typos)
  • Typed structs: decode into int, bool, string, time.Duration, nested structs
  • Normalization hook: Normalize() called after decode
  • Validation hook: Validate() called after normalization
  • Secret redaction: secret:"true" tag, redaction-by-convention, and dot-path redaction
  • Optional tooling module: goconfygen (CLI) and goconfytui (TUI) are available separately in ./tools

Installation

go get github.com/keksclan/goConfy@latest

Requires Go 1.22+.

This installs the core runtime loader only.

Optional generator/TUI tooling lives in a separate module at ./tools. See docs/GENERATOR.md and docs/INSTALL_TOOLS.md.

Versioning & Compatibility

Wir folgen SemVer.

  • v0.x.x: Experimentelle Phase. Breaking Changes an der API sind jederzeit möglich.
  • v1.x.x: Stabile API. Wir garantieren Rückwärtskompatibilität innerhalb einer Major-Version.

Weitere Informationen zum Release-Prozess finden Sie in RELEASE.md.

Quickstart

1. Define a Config Struct

package main

import (
    "fmt"
    "log"

    goconfy "github.com/keksclan/goConfy"
    "github.com/keksclan/goConfy/types"
)

type Config struct {
    Host    string         `yaml:"host"`
    Port    int            `yaml:"port"`
    Timeout types.Duration `yaml:"timeout"`
    DB      struct {
        DSN      string `yaml:"dsn"`
        Password string `yaml:"password" secret:"true"`
    } `yaml:"db"`
}

func main() {
    cfg, err := goconfy.Load[Config](
        goconfy.WithFile("config.yml"),
    )
    if err != nil {
        log.Fatalf("failed to load config: %v", err)
    }

    fmt.Printf("Host: %s, Port: %d\n", cfg.Host, cfg.Port)

    // Safe logging — secrets are replaced with [REDACTED]
    json, _ := goconfy.DumpRedactedJSON(cfg)
    fmt.Println(json)
}

2. Create config.yml

host: localhost
port: "{ENV:APP_PORT:8080}"
timeout: 30s
db:
  dsn: "{ENV:DB_DSN:postgres://localhost:5432/mydb}"
  password: "{ENV:DB_PASSWORD:}"

3. Create .env (Optional)

APP_PORT=3000
DB_DSN=postgres://db.prod:5432/mydb
DB_PASSWORD=supersecret

4. Run

go run main.go

Output:

Host: localhost, Port: 8080
{
  "db": {
    "dsn": "postgres://localhost:5432/mydb",
    "password": "[REDACTED]"
  },
  "host": "localhost",
  "port": 8080,
  "timeout": "30s"
}

Macros

goConfy uses exact-match environment and file macros:

{ENV:KEY}               → look up environment variable KEY, error if missing
{ENV:KEY:default}       → look up environment variable KEY, use "default" if missing
{FILE:/path/to/file}    → read entire file content (trimmed)
{FILE:/path:default}    → read file content, use "default" if file missing or unreadable

Rules

  • The macro must be the entire YAML value — no inline macros
  • Environment Macros: Key names must be uppercase + digits + underscores: [A-Z0-9_]+
  • File Macros: The path is read directly from disk; content is trimmed of leading/trailing whitespace
  • No recursive expansion — the resolved value is never re-scanned
  • Security: No shell-style ${VAR} or $(cmd) — by design (see Security Model). All lookups use direct syscalls (os.Getenv or os.ReadFile), ensuring no shell injection is possible.

Examples

# ✅ Correct — entire value is a macro
port: "{ENV:PORT:8080}"
db_url: "{FILE:/run/secrets/db_url}"
fallback_cert: "{FILE:/etc/ssl/cert.pem:NONE}"

# ✅ Missing values
# For ENV: error if missing and no default
# For FILE: error if missing/unreadable and no default
host: "{ENV:HOST:localhost}"

# ❌ Will NOT expand — macro is embedded in a string
url: "http://{ENV:HOST}:8080"

# ❌ Will NOT expand — shell-style
port: "${PORT}"

Pitfalls

  • No inline macros: "prefix-{ENV:KEY}-suffix" is NOT expanded. Use separate fields or compose in your application code.
  • No quoting inside defaults: {ENV:KEY:"value"} — the quotes become part of the default. Use {ENV:KEY:value} instead.

Dotenv

Load a .env file as an additional variable source for macro expansion. The file is parsed into memory and never injected into os.Environ().

Basic Usage

cfg, err := goconfy.Load[Config](
    goconfy.WithFile("config.yml"),
    goconfy.WithDotEnvFile(".env"),          // enables dotenv + sets path
)

Precedence

By default, OS environment wins over .env values:

goconfy.WithDotEnvOSPrecedence(true)   // default — OS env wins
goconfy.WithDotEnvOSPrecedence(false)  // .env wins over OS env

Optional Missing File

By default, a missing .env file causes an error:

goconfy.WithDotEnvOptional(true)  // silently ignore missing .env

Supported .env Format

# Comments are ignored
KEY=value
export ANOTHER_KEY=value
QUOTED="double quoted with \n escapes"
LITERAL='single quoted, no escapes'
Syntax Result
KEY=value value (trimmed)
KEY="" empty string
KEY=' ' two spaces (preserved)
KEY="a#b" a#b (hash is literal in quotes)
KEY=a #comment a (inline comment stripped)

Profiles

Profiles allow environment-specific overrides within a single YAML file.

Usage

host: localhost
port: 8080
profiles:
  prod:
    host: 0.0.0.0
    port: 443
cfg, err := goconfy.Load[Config](
    goconfy.WithFile("config.yml"),
    goconfy.WithEnableProfiles(true),
)

Set the active profile:

export APP_PROFILE=prod

Or explicitly:

goconfy.WithProfile("prod")

Merge Rules

  • Scalars: profile value replaces base
  • Mappings: merged recursively (only overridden keys change)
  • Sequences: profile replaces entire list
  • The profiles key is removed before decoding

See docs/PROFILES.md for full details.

Redaction

Secret Tag

type Config struct {
    Password string `yaml:"password" secret:"true"`
}

cfg := Config{Password: "secret123"}
redacted := goconfy.Redacted(cfg)
// password → "[REDACTED]"

Dot-Path Redaction

redacted := goconfy.Redacted(cfg, goconfy.WithRedactPaths([]string{"db.password"}))

DumpRedactedJSON

json, err := goconfy.DumpRedactedJSON(cfg)
// Returns JSON with all secrets replaced by "[REDACTED]"

Hooks

Normalize()

If your config struct implements Normalize(), it is called after decoding:

func (c *Config) Normalize() {
    if c.Host == "" {
        c.Host = "localhost"
    }
    c.LogLevel = strings.ToLower(c.LogLevel)
}

Validate()

If your config struct implements Validate() error, it is called after normalization:

func (c *Config) Validate() error {
    if c.Port < 1 || c.Port > 65535 {
        return fmt.Errorf("invalid port: %d", c.Port)
    }
    return nil
}

Optional Tools (separate module)

goconfygen (CLI) and goconfytui (TUI) are intentionally split from the core runtime.

Install directly:

go install github.com/keksclan/goConfy/tools/cmd/goconfygen@latest
go install github.com/keksclan/goConfy/tools/cmd/goconfytui@latest

Or build locally from this repository:

mkdir -p tools/bin
(cd tools && go build -o bin/goconfygen ./cmd/goconfygen)
(cd tools && go build -o bin/goconfytui ./cmd/goconfytui)

Provider registry import path for generator tooling:

import "github.com/keksclan/goConfy/tools/generator/registry"

See docs/GENERATOR.md for tool usage and docs/CLI.md for full CLI flags.

Examples

examples/basic

Demonstrates YAML loading with macros and strict decoding:

go run ./examples/basic

examples/dotenv

Demonstrates .env file integration with typed decoding:

go run ./examples/dotenv

tools/examples/generator

Demonstrates the registry provider and goconfygen usage:

# See tools/examples/generator/README.md for details
mkdir -p tools/bin
(cd tools && go build -o bin/goconfygen ./cmd/goconfygen)

tools/examples/tui

Sample config and .env for exploring the TUI:

(cd tools && go run ./cmd/goconfytui)
# Then open examples/tui/config.yml and examples/tui/.env
# See tools/examples/tui/README.md for a full walkthrough

Documentation

Document Description
Getting Started Full tutorial from zero to config
Generator & TUI Optional tooling module (goconfygen, goconfytui)
CLI Reference All goconfygen commands, flags, examples
Install Tools Core-only vs tools installation and build commands
Config Tags All supported struct tags
Profiles Profile behavior and merge semantics
Security Model Security design decisions

Compatibility

  • Go 1.22+ required
  • Core dependency: gopkg.in/yaml.v3
  • Tool dependencies (only in tools module): bubbletea, lipgloss, bubbles

Used By

If you use goConfy in your project or company infrastructure, feel free to add yourself to the list:

→ See USED_BY.md

Attribution is optional but always appreciated.

License

This project is licensed under the MIT License. Attribution is optional but always appreciated.

See the LICENSE file for the full text.

About

**goConfy** is a lightweight Go configuration library that loads configuration from YAML files and environment variables in a clean and predictable way. It focuses on minimal dependencies, high performance, and simple struct-based configuration loading while following Go best practices. The library is designed for modern Go services and integrates

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors