Skip to content

support environment variables in ini config values - #492

Open
inqrphl wants to merge 6 commits into
mainfrom
configuration-environment-variables
Open

inqrphl wants to merge 6 commits into
mainfrom
configuration-environment-variables

Conversation

@inqrphl

@inqrphl inqrphl commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

AI summary of the changes, I verified the code myself.

Support environment variables in INI config values

Summary

This adds a new $ENV{NAME} syntax for referencing environment variables in
SNClient configuration values. It allows distributing the same configuration
files to many nodes and only changing the environment per node.

Example:

[/settings/default]
allowed hosts = 127.0.0.1, $ENV{SNCLIENT_ALLOWED_HOSTS}

[/settings/WEB/server]
password = $ENV{SNCLIENT_WEB_PASSWORD}

[/paths]
shared-path = $ENV{SNCLIENT_SHARED_PATH}

[/includes]
node = $ENV{SNCLIENT_NODE_INI}

Motivation

Configuration files are often distributed from a central place, while a few
values differ per node (hostnames, IPs, paths, credentials, include files).
Environment variables make it possible to keep a single configuration template
without generating per-node files.

Syntax and behavior

  • $ENV{NAME} is replaced with the value of the environment variable NAME.
  • $$ENV{NAME} escapes the syntax and produces a literal $ENV{NAME}.
  • An unset variable is a configuration error. snclient config check reports
    the variable name and the config file/line.
  • A variable that is set but empty expands to an empty string.
  • Values are expanded once. $ENV{...} inside an environment variable value is
    not expanded again.
  • Only config values are expanded. Section names and keys are not.
  • Works in normal values, quoted values, += appends, [/paths], and include
    paths.
  • The syntax is config-only. It is not added to check output templates or
    runtime macros, so environment values cannot be exposed through
    user-controlled check templates.

The exact form $ENV{...} is not used by any existing macro tokenizer
(${...}, $(...), %{...}, %(...), $...$, %...%, {{...}}), so
existing configurations are unaffected.

Implementation

  • Added a small non-recursive expander in pkg/snclient/config_env.go.
  • Expanded environment variables while parsing values in
    ConfigSection.SetRaw.
  • Kept the original placeholder in the raw config data, so installer/merge
    rewrites do not replace it with the node-specific value.
  • Expanded raw values during config load as well, so external script and alias
    command strings, which are built from the raw value, get the environment
    value.
  • Added explicit environment expansion for inline include paths and clear
    errors for empty include paths.
  • Propagated parse and load errors through ReadConfiguration, so missing
    variables fail snclient config check and daemon startup instead of being
    silently ignored.

Documentation

  • Added an "Environment Variables" section to
    docs/configuration/_index.md, including service environment caveats for
    systemd and Windows.
  • Added a changelog entry under next:.

Tests

Added unit tests covering:

  • normal, embedded, quoted, and multiple expansion
  • set-but-empty variables
  • missing variables and empty names
  • $$ENV{...} escaping
  • no recursive expansion
  • += appends
  • includes and include errors
  • [/paths] expansion through ReadConfiguration
  • raw expansion for external-script-style commands
  • on-demand macro resolution of env-expanded values
  • placeholder preservation in serialized config

Verified with:

make fmt
make golangci
make test
make citest

Also verified end-to-end with snclient config check:

  • with the variables set: OK - no configuration issues detected
  • with a variable unset: exit code 2 and
    environment variable "SNCLIENT_TEST_HOSTS" is not set

Notes

  • Environment variables are read from the process environment when the config
    is loaded. Changing service environment variables requires a service restart;
    a config reload alone does not pick them up.
  • No default-value syntax was added. If a variable can be absent on some nodes,
    the current behavior is to fail the config check explicitly rather than
    silently substituting an empty value.

{"multiple", "$ENV{SNCLIENT_TEST_ENV}/$ENV{SNCLIENT_TEST_ENV}", "value/value", ""},
{"quoted", `"$ENV{SNCLIENT_TEST_ENV}"`, `"value"`, ""},
{"empty", "[$ENV{SNCLIENT_TEST_ENV_EMPTY}]", "[]", ""},
{"spaces", "$ENV{ SNCLIENT_TEST_ENV }", "value", ""},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am not completely sure about this. The code strips the string inside the braces before looking up

{"spaces", "$ENV{ SNCLIENT_TEST_ENV }", "value", ""},
{"escape", "$$ENV{SNCLIENT_TEST_ENV}", "$ENV{SNCLIENT_TEST_ENV}", ""},
{"unclosed", "$ENV{SNCLIENT_TEST_ENV", "$ENV{SNCLIENT_TEST_ENV", ""},
{"nested", "$ENV{SNCLIENT_TEST_ENV_NESTED}", "$ENV{SNCLIENT_TEST_ENV}", ""},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

environment variables are resolved only one time, and that is intended.

@inqrphl
inqrphl marked this pull request as ready for review November 11, 2026 15:32
@inqrphl
inqrphl requested a review from sni November 11, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant