-
Notifications
You must be signed in to change notification settings - Fork 0
Command line
warden introduce itself and list the commands
warden --version print the version
warden serve run the registry
warden tui open the dashboard
warden run start a program on a port warden picked
warden env claim a port and print it as environment
warden register claim a port and print it
warden get print the address of one service
warden ls list every registered service
warden release give a port back
warden reap release ports held by something that is gone
warden heartbeat push a lease out before it runs out
warden pool show how much of the pool is in use
warden history what happened to a port, or to a service
warden events follow what happens, as it happens
warden webhook where events are posted, and whether it works
warden apply register everything a project's warden.toml asks for
warden export write the reverse-proxy configuration
warden ports show what is listening on this machine
warden kill stop whatever is holding a port
warden nodes list the wardens this one knows about
warden doctor check everything and say what is wrong
warden service start warden with the machine
warden setup answer a few questions, once
warden settings show what is in effect, and where it came from
warden update say whether a newer warden exists, and fetch it
Every command that talks to a warden takes --url / -u and --token, both of
which also read WARDEN_URL and WARDEN_TOKEN. Every command that prints a
table also takes --json.
Two flags run through the fleet, and they mean different things:
| Flag | On | Means |
|---|---|---|
--all |
ls, pool, ports, tui
|
Ask every warden, not just this one |
--node NAME |
register, release, heartbeat
|
Do it on that one warden, through this one |
warden kill reads the local machine and ignores --url entirely. warden ports
does too, unless --all is given, which needs a hub to ask.
--install-completion teaches your shell to complete every one of them; see
Installation.
Start a program on a port warden picked, hold the port while it runs, and give it back when it exits.
$ warden run -- npm run dev
shop-api -> 8000
VITE ready, listening on http://localhost:8000Everything after -- is the command. The child gets PORT, WARDEN_PORT,
WARDEN_HOST, WARDEN_ADDRESS and WARDEN_SERVICE added to its environment —
PORT because every framework already reads it.
| Flag | Meaning |
|---|---|
--name |
Register under this name, default the current directory's |
--kind, -k
|
What the service is, default service
|
--project |
Group services of one codebase |
--host |
Interface the process will bind to |
--preferred-port |
Wish for this port, take another if it is not free |
--require-port |
Insist on this port, and fail if it is not free |
--ttl |
Hold a lease this long, renewed while the process runs |
--anyway |
Start on a free port when no warden answers |
Three things it does that a shell wrapper gets wrong:
- The child's exit code becomes warden's, so nothing swallows a failure.
- The port is released whether the program exits, crashes or is interrupted.
- The registration carries the child's process id, which is what later tells a held port from an abandoned one. See warden reap.
With no warden reachable it refuses rather than picking a port itself, because
guessing at a port is the thing warden exists to stop. --anyway overrides
that, takes a free port from the pool, and says on standard error that nothing
is registered.
The same claim, printed instead of wrapped — for an IDE run configuration, a Makefile, or anything else that cannot be started by warden.
$ warden env shop-api --kind backend
PORT=8000
WARDEN_PORT=8000
WARDEN_HOST=127.0.0.1
WARDEN_ADDRESS=127.0.0.1:8000
WARDEN_SERVICE=shop-api| Flag | Meaning |
|---|---|
--kind, -k
|
What the service is, default service
|
--project, --host, --preferred-port, --require-port
|
As warden register
|
--export |
Prefix each line with export
|
--write .env |
Set those keys in a dotenv file, leaving every other line alone |
eval $(warden env shop-api --export)
warden env shop-api --write .env--write replaces each key where it already stands and appends the rest. A
.env is usually somebody else's file too, so nothing warden did not write is
touched.
Run the registry.
| Flag | Meaning |
|---|---|
--host |
Interface to listen on |
--port |
Port to listen on |
--pool 8000-8999 |
Range of ports to hand out |
--reserved 8080,9000-9010 |
Ports never handed out |
--database |
Path to the registry database |
--no-probe |
Do not test ports for existing listeners |
Fleet settings have no flags; they are environment only. See Configuration.
Claim a port. Prints the port and nothing else, so it drops into a script.
| Flag | Meaning |
|---|---|
--kind, -k
|
Required. What the service is |
--project |
Group services of one codebase |
--host |
Interface the service will bind to, default 127.0.0.1
|
--preferred-port |
Wish for this port, take another if it is not free |
--require-port |
Insist on this port, fail if it is not free |
--ttl |
Release the port again after this many seconds |
--pid |
Process id of the service |
--count |
Claim this many at once, named <name>-1 upwards |
--contiguous |
With --count, insist they run back to back |
--node |
Register on that warden in the fleet, through this one |
--preferred-port and --require-port cannot both be given.
A stack that needs four ports can ask four times and hope nothing takes one in between, or ask once:
$ warden register stack --kind backend --count 4
8800
8801
8802
8803They come back as stack-1 to stack-4, chosen and written under one
transaction, so either all four are held or none are. Asking again renews the
same four rather than shuffling a running stack onto different ports.
--contiguous insists they run back to back, for the tools that will not take
a scattered set. When no run is long enough it says so and writes nothing:
$ warden register row --kind backend --count 6 --contiguous
no run of 6 free ports in 8800-8809 on 127.0.0.1warden pool says it before it comes to that, whenever the two numbers differ:
8800-8809 5 allocated 5 free 0 reserved 4 in a row.
A group takes no --require-port: that has no sensible answer for four ports at
once, and a caller who needs one particular port needs one registration.
$ warden register shop-api --kind backend --project shop
8000Print one service's address.
$ warden get shop-api
127.0.0.1:8000Given as node/service, it asks that node through the hub:
$ warden get build-01/build-runner --url http://hub:7010
127.0.0.1:9000Exits 1 with no service registered as 'x' when it is not there.
List registered services.
| Flag | Meaning |
|---|---|
--project |
Only this project |
--kind |
Only this kind |
--holders |
Also say whether each holder is still there |
--stale |
Only the ones whose holder is gone |
--all |
Ask every warden in the fleet, not just this one |
$ warden ls --project shop
SERVICE KIND PROJECT ADDRESS PID
shop-api backend shop 127.0.0.1:8000 -
shop-web frontend shop 127.0.0.1:8001 ---all asks every warden in the fleet and adds a NODE column. Nodes that did
not answer are named on standard error, so a pipe still gets a clean table. See
Cluster.
--holders adds a HOLDER column saying whether whoever asked for each port is
still there:
$ warden ls --holders
SERVICE KIND ADDRESS PID HOLDER
shop-api backend 127.0.0.1:8000 14204 running
old-job worker 127.0.0.1:8001 9930 goneA holder is gone when the process it named no longer exists, or when nothing
is listening on its port. It costs one sweep of the machine's sockets, which is
why it is asked for rather than always done, and it cannot be combined with
--all: only the machine a service runs on can see whether it is still there.
A registration touched in the last 30 seconds is never called gone. A service that has registered and not yet bound its socket is starting, not dead.
Give a port back to the pool.
$ warden release shop-web
released shop-web--node build-01 releases it on that warden instead, through the one you asked.
Release the ports of services that are no longer there.
$ warden reap
release old-job? nothing is on 8001 and pid 9930 is gone [y/N]: y
released 1It asks per service and names why each one qualifies. --yes skips the
questions, for a machine that runs it on a schedule.
Nothing is ever reclaimed on its own. A service in the middle of a restart would lose its port to a timer, and a registry that takes ports back without being asked is worse than one holding a few dead rows.
Push a lease out before it runs out, for a service registered with a --ttl.
$ warden heartbeat ci-runner --ttl 600
127.0.0.1:9000| Flag | Meaning |
|---|---|
--ttl |
Seconds from now; without it, the lease it registered with |
--pid |
Update the recorded process id at the same time |
--node |
Do it on that warden in the fleet |
$ warden pool
8000-8999 2 allocated 997 free 1 reservedCounts only ports inside the pool. A service on a --require-port outside the
range is registered but not counted here.
--all reports every node and the fleet totals, with FREE coloured by how little
is left — the point of the view being to spot the machine about to run out:
$ warden pool --all --url http://hub:7010
NODE POOL HELD FREE RESERVED
build-01 9000-9099 4 96 1
hub 8000-8999 2 997 1
web-02 9000-9099 98 2 1The totals are a sum of what is left, never one pool the fleet shares: two nodes may hand out the same numbers on different machines.
What happened to a port, kept after it stopped being true.
$ warden history 8000
WHEN WHAT SERVICE KIND ADDRESS PID
7s ago released shop-api backend 127.0.0.1:8000 14204
2h ago registered shop-api backend 127.0.0.1:8000 14204| Argument | Meaning |
|---|---|
what |
A port, or a service name. Everything recent without one |
| Flag | Meaning |
|---|---|
--limit |
How many to show, default 20 |
Every registration, renewal, move, release and expiry is written down as it
happens, so warden history 8000 still answers for a service released weeks
ago. Rows are dropped past a cap, so a warden left running for a year does not
grow a database nobody asked for.
Follow what happens, as it happens. Runs until it is stopped.
$ warden events
09:41:02 registered shop-api 127.0.0.1:8600
09:41:11 renewed shop-api 127.0.0.1:8600
09:41:44 released shop-api 127.0.0.1:8600| Flag | Meaning |
|---|---|
--json |
One event per line, flushed as it arrives, so it pipes |
The stream carries every action — registered, renewed, moved, released,
expired — and filtering is the reader's business. GET /v1/events is the same
thing as server-sent events, behind the same token as every other read.
See Events and webhooks.
Where events are posted, and whether that is working.
$ warden webhook
address https://discord.com/api/webhooks/...
shape discord
events registered and released
delivered 7| Flag | Meaning |
|---|---|
--test |
Post one made-up event from this machine now |
Without --test the answer comes from the warden that is running. With it, the
post goes out from this machine using this machine's settings — which is what
warden setup has just written, and a different thing. If the two disagree, the
running warden has not been restarted.
The address is always shown cut back to its host. A webhook URL is a credential and its path is the half worth stealing.
Register everything a project's warden.toml asks for.
$ warden apply
SERVICE KIND ADDRESS WHAT
shop-api backend 127.0.0.1:8900 taken
shop-worker worker 127.0.0.1:8901 taken| Flag | Meaning |
|---|---|
--file, -f
|
Which manifest to read, default warden.toml here |
--env |
Also write the ports into this env file |
--release |
Give the project's ports back instead |
Running it twice changes nothing the second time: it renews, and never shuffles a running project onto different ports. See Projects.
Write the reverse-proxy configuration for what is registered.
$ warden export caddy --domain example.com| Argument | Meaning |
|---|---|
proxy |
caddy, nginx or traefik
|
| Flag | Meaning |
|---|---|
--domain |
Names become <service>.<domain>
|
--project, --kind
|
Narrow it down |
--all |
Take the whole fleet, each service at its own machine |
It prints and stops. See Reverse proxy.
What is listening on this machine. Needs no server.
| Flag | Meaning |
|---|---|
--port |
Only this port |
--udp / --no-udp
|
Include UDP sockets, on by default |
--all |
Ask every warden in the fleet, and add a NODE column |
The WARDEN column names the service when the port came from the registry, which needs a warden to be reachable. Without one the column is simply blank; the rest still works.
See Ports and processes.
Stop whatever is holding a port. Needs no server.
| Argument | Meaning |
|---|---|
target |
A port, or a process id with --pid
|
| Flag | Meaning |
|---|---|
--pid |
Read the number as a process id |
--force |
Kill it outright if it will not stop politely |
--yes, -y
|
Do not ask first |
List the wardens this one knows about.
| Flag | Meaning |
|---|---|
--forget NAME |
Remove a node that is not coming back |
$ warden nodes --url http://hub:7010
NODE URL POOL VERSION STATUS LAST SEEN
build-01 http://build-01:7010 9000-9099 0.1.0 online 4s agoThe dashboard. --interval sets the refresh in seconds, default 2. --all shows
the whole fleet instead of one warden.
| Key | Action |
|---|---|
↑ ↓ j k
|
Move |
tab |
Switch between services and ports |
n |
Step through one node at a time, with --all
|
r |
Reload now |
d |
Release the service, or stop the process |
q |
Quit |
The ports view works with no warden running at all: it falls back to reading the
machine directly, says so in the subtitle, and d stops a process the same way
warden kill does. The services view still needs a registry.
0 on success, 1 on any refusal, with the reason on standard error. The
message is the same one the API would return, so a script can act on it:
if ! PORT=$(warden register shop-api --kind backend 2>/dev/null); then
echo "no port available" >&2
exit 1
fiEverything worth checking, in one command, so "why is this not working" does not mean running four others and comparing what they said.
$ warden doctor
ok warden 0.1.0 answering at http://127.0.0.1:7010, role hub
ok settings from ~/.config/warden/warden.toml
warn listening on 0.0.0.0 with no token set - anyone who can reach this
machine can hand out and release ports
ok pool 8000-8999, 4 held, 995 free
warn 2 of 6 registrations held by something that is gone - warden reap
ok build-01 online, last seen 4s ago
note could not check for updates - github is unreachable| Level | Means |
|---|---|
ok |
Checked, nothing wrong |
note |
Worth knowing, or a check that could not run at all |
warn |
Works, but somebody should look at it |
fail |
Broken |
Exits 1 only when something failed, and 0 on warnings, so it drops into a
health check without an unset token being read as the machine being down.
Have this machine start warden serve at login.
$ warden service install
systemd user unit
~/.config/systemd/user/warden.service
[Unit]
Description=warden - hands out ports and says who holds them
...
systemctl --user daemon-reload
systemctl --user enable --now warden.service
Write it? [y/N]:| Command | Does |
|---|---|
warden service install |
Show what it would write, then write it |
warden service uninstall |
Take it away again |
warden service status |
Say whether it is installed, and whether it is up |
--yes on install and uninstall skips the question.
| System | What gets written |
|---|---|
| Linux | A systemd user unit in ~/.config/systemd/user
|
| macOS | A launchd agent in ~/Library/LaunchAgents
|
| Windows | A command in the Startup folder |
Always as the account that ran it, never as root or SYSTEM: a warden started by another user reads that user's settings and hands out ports from a registry nobody else can see.
On Windows it is a Startup command rather than a scheduled task, because
creating a task is refused outright on plenty of managed machines. That also
means there is no service manager to ask, so status says running when a
warden is answering on the configured port, whoever started it.
Asks the handful of questions that matter and writes the answers to the config file. It keeps any setting it did not ask about. See Configuration.
In a terminal it is one screen:
| Key | Action |
|---|---|
tab shift+tab
|
Move between fields |
space |
Toggle a switch or a tick box |
enter |
Open a menu, or pick from it |
pgup pgdn
|
Scroll without leaving the field you are in |
ctrl+t |
Post a test event to the webhook address on screen |
ctrl+s |
Save |
ctrl+q |
Leave without writing anything |
Questions that nothing has earned stay hidden: no token field until the warden
is reachable from elsewhere, no upstream address until it reports to one, no
webhook shape until events go anywhere, and no signing secret unless the shape
is json. A refusal names the field and puts the cursor in it.
It fits an 80 by 24 terminal; under 84 columns the labels move above the fields they name and nothing is cut off.
| Flag | Meaning |
|---|---|
--plain |
Ask one question at a time instead of a screenful |
Without a terminal - a script piping answers in, a job on a build machine - the
questions come one at a time on their own. So do they where TERM is unset or
dumb, which is what a cron job or a serial console gets; warden tui says so
and points at warden ls rather than ending in a traceback.
A warden that is already running keeps the settings it started with. It reads them once, at startup. Restart it to pick up a new webhook or pool.
Every setting, its value, and which source that value came from.
warden settings # the table
warden settings --json # the same as data
warden settings set pool_end 4199 # write one to the config file
warden settings unset probe # take it back outset types and checks the value before writing it, so the file never holds
something warden would refuse to start with, and it says so when the environment
will override what you just wrote anyway.
warden — nothing binds a port without asking ·
uv tool install warden-ports
Repository · Issues · Releases · PyPI · MIT
Getting started
While it runs
Several machines
Reference