Skip to content

Configuration

vxnsin edited this page Sep 7, 2026 · 13 revisions

Configuration

Run warden setup once, and warden settings whenever you want to see or change what it wrote. Nothing below has to be done by hand.

$ warden setup
Ports to hand out [8000-8999]: 4000-4099
Ports never to hand out: 4200
Port warden itself listens on [7010]:
Reachable from other machines? [y/N]:
Does this warden report to another one? [y/N]:
Allow stopping processes over the API? [y/N]:

Enter accepts every default, so it is six keystrokes if you only came for the pool.

Where it writes

System Path
Windows %LOCALAPPDATA%\warden\warden.toml
Linux ~/.config/warden/warden.toml
macOS ~/Library/Application Support/warden/warden.toml

WARDEN_CONFIG points somewhere else, which is how two wardens on one machine keep out of each other's way.

Seeing and changing what is in effect

warden settings opens the same screen warden setup uses, over what is already written down. Naming a part goes straight there:

warden settings            # the screen, on the first tab
warden settings embed      # what each event looks like in a chat window
warden settings firewall   # which backend, and how long before it rolls back

The parts are ports, reach, fleet, events, embed, firewall and riskctrl+left and ctrl+right move between them. Leaving with ctrl+s writes over the file rather than replacing it, so a setting the screen never asks about survives.

Without a terminal — a script, a build machine — it prints the table instead, and --plain or warden settings list asks for that on purpose:

$ warden settings --plain
SETTING     VALUE       FROM
host        127.0.0.1   default
port        7010        config file
pool_start  4000        config file
pool_end    4099        config file
token       set         environment

The FROM column is the point of the table. "Why is the pool 4000-4099 when the file says otherwise" is the question that costs an afternoon, and this answers it. Secrets show as set rather than being printed.

warden settings set pool_end 4199
warden settings unset probe        # back to its default
warden settings --json             # for a script

A value is checked before it is written, so the file never holds something warden would refuse to start with. Setting one that the environment overrides says so, rather than looking like it worked.

Where a value can come from

The first of these with an answer wins:

  1. a flag on warden serve
  2. an environment variable — WARDEN_ and the setting's name in capitals
  3. a .env file in the directory the process starts in
  4. the config file
  5. the default

The environment is what a container or a systemd unit will use, and the file is for a machine somebody sits at. Both work together, and warden settings says which one you are actually getting.

# .env, beside the process
WARDEN_POOL_START=4000
WARDEN_TOKEN=a-long-random-string

Setting names below are the file's keys. As an environment variable each takes the WARDEN_ prefix in capitals: pool_start becomes WARDEN_POOL_START.

The registry itself

Setting Flag Default Meaning
host --host 127.0.0.1 Interface it listens on
port --port 7010 Port it listens on
database --database platform data directory SQLite file holding everything

The database defaults to %LOCALAPPDATA%\warden\registry.db on Windows, ~/.local/share/warden/registry.db on Linux and ~/Library/Application Support/warden/registry.db on macOS. It is created on first use, along with any missing parent directories.

The pool

Setting Flag Default Meaning
pool_start --pool 8000-8999 8000 First port that may be handed out
pool_end --pool 8000-8999 8999 Last port that may be handed out
reserved --reserved empty Ports never handed out
probe --no-probe true Test a port for an existing listener before handing it out

reserved takes single ports and ranges, separated by commas: 8080,8443,9000-9010.

The registry's own port is added to the reserved set automatically when it falls inside the pool. It lives on the same machine, so it could never be handed out anyway.

--no-probe turns off the bind test. Only worth it if something in your environment makes binding expensive; you lose the ability to notice ports that were taken outside the registry.

Access

Setting Default Meaning
token empty One token that reaches everywhere, including the firewall
tokens empty Named tokens, each reaching only as far as it says
cluster_token empty The secret wardens use with each other: announcing, and reading
allow_kill false Let the API stop processes
allow_remote_update false Let a caller ask this warden to update itself
update_command empty What updating means on this machine
update_check true Ask GitHub whether a newer release exists
update_repo vxnsin/warden Which repository to ask about
update_interval 21600 Seconds between checks, at least 300

Empty means no check at all, which is fine while warden listens on loopback. Set token before binding to anything else — warden setup asks for one as soon as you say the machine should be reachable.

allow_kill is off on purpose. A warden reachable from the network would otherwise let anyone holding the token end processes on that machine, a far bigger thing to hand out than a port number. warden kill on the command line is unaffected; it acts locally and never asks the API.

Tokens that reach only as far as they say

One token opens everything: it registers a service, it writes a firewall rule, it applies a ruleset across a fleet. That was fine while the API only handed out ports inside a pool. Handing a deploy script that token now hands it the firewall of every machine it can reach.

So a token can be given a name and a reach:

tokens = [
  { name = "deploy",  scope = "registry", secret = "..." },
  { name = "grafana", scope = "read",     secret = "..." },
  { name = "walls",   scope = "firewall", secret = "..." },
]

or, where the environment is what configures the machine:

WARDEN_TOKENS="deploy:registry:s3cret, grafana:read:hunter2"

name:scope:secret, and the secret is everything after the second colon — so a secret with colons in it needs no escaping.

Scope Reaches
read Anything that only looks: services, pool, history, rules, the fleet views
registry Registering, renewing, releasing, stopping a process — and reading
firewall Writing, closing, applying, confirming and restoring rules — and reading
all Everything. What a single token has always meant

Reading is the floor: a token that may change a thing may also look at it, and never needs a second token to see what it just did.

A refusal says which token and how far it reaches, because "403" on its own has sent more than one person looking for the wrong problem:

the token 'deploy' may only registry, and this is a firewall thing to do

Nothing changes for a machine that has not asked for this. A single token is still a token that reaches everywhere, and a machine with neither still asks for nothing on loopback, exactly as before.

Which token asked

Named tokens are also the first time warden can say who. Every change records the name of the token it arrived with, and warden history shows it:

WHEN     WHAT        SERVICE   KIND     ADDRESS         PID   ASKED BY
2m ago   registered  shop-api  backend  10.4.0.7:8000   4242  deploy
5m ago   released    docs      docs     10.4.0.7:8002   -     at the machine

"at the machine" is somebody running warden release on the machine itself. There is no token at a keyboard, so there is nobody to name — and the column only appears where at least one row has a name.

Events going out

Setting Default Meaning
webhook empty Address events are posted to; must be http or https
webhook_format json json, discord, slack or teams
webhook_events all but renewed Which actions are worth posting
webhook_secret empty Key the posted body is signed with, for json
webhook_colours empty node.stale=#e5544b, one event at a time
webhook_titles empty node.stale=has stopped answering, the words after the subject
webhook_icons empty node.stale=!, what stands in front of it; a single - means none

warden settings embed edits the last three against a preview of the message, which is easier than writing an inline table by hand.

webhook_events takes a whole scope (firewall.*), a full name (node.stale), or the bare name a port event had in 0.2.0 (registered), in any mix. A name that is none of those is refused by name rather than quietly ignored, and warden events --known prints the whole list. port.renewed is left out by default: a channel told about every heartbeat is a channel people mute within the week.

The webhook applies to this warden only. In a fleet, every node reports its own events, so every node you want to hear from needs the setting.

See Events and webhooks.

The firewall

Setting Default Meaning
firewall_backend the machine's own nftables, iptables, pf or windows
firewall_rollback 60 Seconds to wait for a confirmation; 0 turns it off
firewall_from_registry false Let the registry's ports be opened at all
firewall_allow_from empty Networks a rule from the registry may open to, either family
allow_remote_firewall false Let a caller over the API change the rules

firewall_from_registry and firewall_allow_from are off and empty on purpose. Nothing declared is nothing allowed: a registry that could open ports out of the box would be a way around the firewall rather than a firewall.

allow_remote_firewall is a separate decision about who may ask. Reading the rules over the API is what a token already allows; changing them needs this, and warden doctor fails a warden that has it set, listens beyond loopback and asks for no token. See Firewall.

The fleet

Setting Default Meaning
node the machine name This warden's name in the fleet
upstream empty Hub to report to; empty means this one is a hub
advertise from host and port Address the hub should use to reach it
node_ttl 90 Seconds a node's entry stays fresh (10 to 86400)
require_https false Refuse to register or send a token to a plain HTTP node

The machine name is lowercased and stripped of anything a service name would not accept, so BUILD-01.office.lan becomes build-01.office.lan rather than refusing to start.

A node reports three times per node_ttl, never more often than every five seconds.

See Cluster for what these do together.

For clients, not the server

Variable Flag Default Meaning
WARDEN_URL --url, -u http://127.0.0.1:7010 Which warden the CLI and the Python client talk to
WARDEN_TOKEN --token empty Token to send with each request

WARDEN_URL is what points warden ls at a warden on another machine. Note that warden ports and warden kill ignore it: they read the local machine directly.

Limits worth knowing

Thing Rule
Service name lowercase letters, digits, . _ -, starting with a letter or digit, up to 64 characters
Kind lowercase letters, digits and -, up to 32 characters
Project same shape as a service name
ttl on a registration 1 second to 24 hours
node_ttl 10 seconds to 24 hours
Ports 1 to 65535, and pool_start may not exceed pool_end

A setting outside its range stops warden at startup with a message naming the setting, rather than being quietly clamped. warden settings set refuses it before it ever reaches the file.

Clone this wiki locally