EN
  • English
  • Deutsch
  • Polski
  • Italiano
  • Español

Declarative configuration

Warden’s configuration is declarative: you describe the state you want, not the steps to get there. A single config.toml is enough. As the setup grows you can split it across files, the same way a NixOS system is split across modules. Warden loads them, merges them in memory, and runs that picture. The files on disk stay as you wrote them.

Why the file is the policy: Project principles. What those entities do at query time: Filtering process.

File and folder structure

Start with one file. Split a directory out when a file gets hard to read or to share. The tree below is a suggestion, not a requirement. Name files for what is in them, so git diff devices/iot.toml tells the story.

tree /etc/purge-warden
/etc/purge-warden/ ├── config.toml # master file — globals + includes ├── secrets.toml # tokens, mode 0600 (never shown) ├── devices/ │ ├── famiglia.toml # household laptops, phones, tablets │ └── iot.toml # cameras, smart bulbs, thermostat ├── profiles/ │ ├── kids.toml │ ├── adults.toml │ └── iot-only.toml ├── groups/ │ └── parents.toml ├── subnets/ │ └── vlans.toml # guest VLAN, IoT VLAN, main LAN ├── schedules/ │ └── bedtime.toml ├── blocklists/ │ └── purge-catalog.toml └── rules/ └── household-overrides.toml

The master file

The master config.toml declares which fragments to load:

/etc/purge-warden/config.toml
toml
schema_version = 1

includes = [
  "devices/*.toml",
  "profiles/*.toml",
  "groups/*.toml",
  "subnets/*.toml",
  "schedules/*.toml",
  "blocklists/*.toml",
  "rules/*.toml",
]

[server]
listen = "0.0.0.0:53"
allow_from = ["10.0.0.0/8", "192.168.0.0/16"]

[upstream]
servers = ["9.9.9.9", "1.1.1.1"]

The servers values are examples — pick your own. Warden ships no default upstream.

On load, Warden expands every glob, sorts the matches, and reads each fragment. A fragment uses the same TOML schema as the master: devices/family.toml is just [[devices]] entries.

  • A glob that matches zero files is fine.
  • A file listed by name in includes and missing from disk is a hard error.

Merge rules

Merge happens in memory on every boot, every hot reload, and every CLI write. Disk files are not rewritten. Warden never silently picks a winner.

The rules key off the TOML shape, not the file name. A [[devices]] row in rules/misc.toml loads the same way as one in devices/. Convention is still useful: singletons in the master, arrays in devices/*.toml, profiles in profiles/*.toml.

StructureWhere you see itAcross files
Singletonschema_version, includes, [server], [upstream], [cache], [security], [anti_bypass], [ip_blocklists], [local_dns], [tracking], [socket], [api]Exactly one file may define it. These stay in the master.
Array of tables[[devices]], [[groups]], [[subnets]], [[schedules]], [[blocklists]], [[admin_rules]]Concatenate. Every id must be unique. Split these.
Named map[profiles.<id>]Merge by key. The same id in two files is a conflict. Split these. Profiles are not [[profiles]].
warden reload
error: duplicate device id "alice-laptop" first defined at devices/famiglia.toml:12 also at devices/legacy.toml:34 hint: rename one, or merge the two entries

A phantom device that depends on file order is worse than a load that fails on the exact line. Errors name (file, line) because a provenance map rides next to the merged config.

The CLI writes the same files

Every warden command that changes configuration writes back into this tree:

warden
$ warden device add --id "alice-laptop" --mac "AA:BB:CC:..." \ --profile kids --into devices/famiglia.toml

--into picks the fragment. Omit it and Warden looks next to the master (devices/ for warden device add, groups/ for warden group add). One file in that directory → that file. Several → Warden refuses and asks. None → the entry goes into config.toml.

The CLI is a typing aid. The files remain what you diff, commit, and back up (tar /etc/purge-warden/).

Hot reload — full re-read, atomic swap

Editing a file does not apply it. Validate, then reload:

warden config lint && sudo systemctl reload purge-warden

warden reload does the same via the daemon’s IPC socket.

On reload, Warden re-reads every file, validates the merge, and swaps the running config only if the new picture is valid. If validation fails, the previous config keeps running. The DNS handler never sees a half-applied state.

A change that binds an OS resource still needs a restart: [server].listen or [socket].path. Everything else — devices, profiles, lists, rules, schedules — is a reload.

↑↓ to navigate · Enter to open · Esc to close See all results