Initialize
Turn a bare binary into a running scaffold — system user, directories, and a first config.toml
What this step does
warden init provisions everything a fresh install needs before it can start: the purge-warden system user, the state directories, and a first config.toml. It requires root, and refuses outright if it isn’t: warden init requires root. Run with sudo.
If you installed with the bundle installer, you do not run warden init yourself. The installer runs it for you with the answers it found (your LAN range and your upstream resolver), then creates the token and starts the service. Read this page when you build by hand, script an install, or want to know what the installer wrote.
init seeds three blocklist subscriptions and turns filtering on immediately. A list’s base defaults to deny, and the profile that init writes has no override, so it inherits base.
Run the real binary as root. The warden command on your PATH runs as the purge-warden user and cannot do this:
sudo /usr/local/libexec/purge-warden/warden initInteractive mode
With no --yes, init asks four questions in this order. Each accepts a bare Enter for its default:
| # | Prompt | Default (Enter) |
|---|---|---|
| 1 | Upstream resolver | Detected from this machine’s /etc/resolv.conf / resolvectl status, plus any entries from an upstreams.toml menu next to the config. Type 0 to enter an address by hand. |
| 2 | default_profile for unmapped sources | default — type none to leave unmapped clients REFUSED instead. |
| 3 | Blocklist subscriptions | The three defaults — see The three seeded lists. |
| 4 | Allowed client networks ([server].allow_from) | RFC 1918 + loopback: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8. |
Nothing writes upstreams.toml for you — create it by hand next to the config if you want a curated menu instead of the free-typed entry.
Non-interactive mode (--yes)
Scripted installs skip every prompt:
sudo /usr/local/libexec/purge-warden/warden init --yes--yes accepts the baked-in defaults for the profile (default), the three lists, and allow_from. The upstream resolver still has to come from somewhere. With no --upstream flag, --yes adopts whatever this machine’s own resolver configuration already points at, and prints what it chose so an install transcript isn’t silent about it:
upstream: adopting 192.168.1.1:53 (detected on this machine)
init detects what this machine already uses and offers it (interactively, as menu entry 1; under --yes, adopted automatically), and refuses rather than inventing a value if nothing usable is found. See Troubleshooting for both refusal messages.What it writes
A fresh config.toml looks like this, with its comments trimmed. The real file also carries a commented [api] block and commented [[devices]] and [[subnets]] examples; see What init does not write below.
schema_version = 5
[server]
listen = "0.0.0.0:53"
allow_from = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"]
log_level = "info"
default_profile = "default"
[upstream]
mode = "plain"
servers = ["192.168.1.1:53"]
timeout_ms = 5000
[lists]
sources = []
update_interval_secs = 43200
[[blocklists]]
id = "security-malicious"
display_name = "Security: malicious"
url = "https://lists.purge.cc/malicious.txt"
[[blocklists]]
id = "privacy-ads"
display_name = "Privacy: ads"
url = "https://lists.purge.cc/ads.txt"
[[blocklists]]
id = "privacy-tracking"
display_name = "Privacy: tracking"
url = "https://lists.purge.cc/tracking.txt"
[profiles.default]
display_name = "Default household profile"
[cache]
max_entries = 10000
max_ttl_secs = 3600
min_ttl_secs = 60
negative_ttl_secs = 300
[socket]
path = "/run/purge-warden/control.sock"
[backup]
dir = "/var/lib/purge-warden/backups"
[node]
id = "<a random UUID>"
name = "<this machine's host name>"The file is written with mode 0640.
Three things to know:
[backup] diris added only when the config is the standard/etc/purge-warden/config.toml. Archives go to/var/lib/purge-warden/backups, with the rest of Warden’s state. For any other config path, init writes no[backup]block and archives go to abackups/directory next to the config.[node]gives this machine a stable identity.idis a random UUID and never changes.nameis a display name. By default it is the host name; set another with--node-name <name>. The name is independent of the OS host name.[lists].sourcesstays empty on purpose. Subscriptions live as[[blocklists]]entries, not as slugs in that older channel.
The three seeded lists
--yes, or a bare Enter at the subscription prompt, subscribes these three catalog slugs. The id an entry renders as is the slug with / replaced by -:
| Catalog slug | Entry id | URL |
|---|---|---|
security/malicious | security-malicious | https://lists.purge.cc/malicious.txt |
privacy/ads | privacy-ads | https://lists.purge.cc/ads.txt |
privacy/tracking | privacy-tracking | https://lists.purge.cc/tracking.txt |
Override the set with --lists <csv> of catalog slugs. Raw URLs are refused (raw URLs are not accepted by warden init). Add a URL-backed source after init with warden blocklist add <id> --url <url>. Browse the full catalog with warden lists catalog.
Filtering starts the moment the daemon starts, for all three: each [[blocklists]] row’s base defaults to deny, and [profiles.default] carries no override, so it inherits base.
The default-profile question
--yes hardcodes default_profile = "default". Interactively, a bare Enter also accepts default. Typing none writes no [profiles.default] block at all and leaves default_profile commented out in [server]. A source that matches no device and no subnet is then answered REFUSED, not filtered by some fallback profile.
What init does not write
Only commented examples — no live entries:
- No
[[devices]], no[[groups]], no[[subnets]]. - No
[[custom_lists]]and nopacks/directory on disk.
The commented [[devices]] example in the file still shows the fields a device can have. Uncomment and adapt it to pin a source to a profile.
CLI reference
These flags go after init.
| Flag | Argument | Meaning |
|---|---|---|
--yes | (bool) | Skip every prompt, accepting the baked-in defaults for profile, lists and allow_from. |
--lists | <slug>[,...] | Comma-separated catalog slugs to subscribe, overriding the three defaults. |
--force | (bool) | Overwrite an existing config at the target path; see Re-running init. |
--upstream | <addr:port>[,...] | Comma-separated upstream resolvers, plain DNS. Wins over both detection and the prompt. |
--upstream-catalog | <path> | Path to the upstreams.toml menu file. Defaults to upstreams.toml next to the target config. |
--allow-from | <cidr>[,...] | Comma-separated CIDRs for [server].allow_from. Must be non-empty. |
--listen | <addr:port> | Bind address for [server].listen. Defaults to 0.0.0.0:53. |
--node-name | <name> | Display name for this node, written to [node].name. Defaults to the host name. |
--install-manpages | (bool) | Also generate manpages into /usr/local/share/man/man1/. |
--man-dir | <path> | Target directory for --install-manpages. |
--config <path> is a root-level flag. It goes before init, not after it. It moves the whole layout; see What it creates.
What it creates
With no --config, warden init provisions the standard layout:
| What | Where |
|---|---|
| System user | purge-warden — no login shell, no home directory |
| Master config | /etc/purge-warden/config.toml, mode 0640 — written only if missing, or if you pass --force |
| Config directory | /etc/purge-warden/, mode 0750 |
| State root | /var/lib/purge-warden/, mode 0750 |
| Downloaded lists | /var/lib/purge-warden/lists/ |
| Stats snapshots | /var/lib/purge-warden/data/ |
| Control socket + PID | /run/purge-warden/ |
Pass --config <path> and every one of those moves with it. A config under /etc/<name>/ keeps its mutable state under /var/lib/<name>/. Any other config location keeps its state beside the config.
A host that already has a config in /var/lib
Older releases kept the config at /var/lib/purge-warden/config.toml. If that file exists, warden init with no --config refuses, even with --force:
legacy config already exists: /var/lib/purge-warden/config.toml. Refusing to create /etc/purge-warden/config.toml and shadow existing policy. Keep using the existing master; Warden does not move it. To explicitly reinitialize the legacy master, run `warden --config /var/lib/purge-warden/config.toml init --force`.
A second config under /etc would hide your real policy. Keep using the existing file. To move it to /etc on purpose, use warden config relocate; see Upgrade.
Re-running init (--force)
Without --force, init refuses before touching anything if the target config already exists:
config already exists: /etc/purge-warden/config.toml. Pass --force to overwrite (the existing file will be copied with a .pre-init-<ts> suffix).
With --force, the old file is first saved beside the new one as config.toml.pre-init-<UTC timestamp>, for example config.toml.pre-init-20260731T140502Z, and init prints saved previous config to <path>. That copy covers the config file only. It does not cover your Custom List packs or other included files. Run warden config backup first; see Backup & restore.
Cluster nodes
init no longer scaffolds a cluster secondary. The old --cluster-secondary and --peer flags are retired and hidden. They only print legacy secondary scaffolding is retired; initialize a standalone node, then use cluster join with an invitation. Initialize a node as a normal standalone node, then join it to a primary with an invitation: warden cluster join --peer <primary https origin> --invitation-file <file>.
After init
init writes the config but creates no IPC token. Every mutating or admin command refuses without one. The command prints the next steps itself, followed, when [server].listen binds a port below 1024, by a note on setcap cap_net_bind_service:
If you installed with the bundle installer, it does steps 2 and 3 for you, through the systemd unit.
If you start the daemon by hand and [server].listen binds a port below 1024 (the default, 0.0.0.0:53, does), the daemon needs CAP_NET_BIND_SERVICE to bind it:
sudo setcap cap_net_bind_service=+ep /usr/local/libexec/purge-warden/wardenOr run the daemon from the systemd unit, which grants the same capability with AmbientCapabilities= and needs no setcap step. See systemd service.
Next: Import lists.
In the TUI
warden init is a root CLI command — there is no TUI equivalent, and nothing in the TUI runs it for you. Once it has run and the daemon is started, warden dashboard opens the TUI, and the Configuration section is where the rest of setup continues. 5 opens it on its first leaf, Labels; g e jumps straight to its Settings leaf.
Troubleshooting
no upstream resolver configured — init --yes refuses outright. Nothing usable was found on this machine. Pass one explicitly:
sudo /usr/local/libexec/purge-warden/warden init --yes --upstream 192.0.2.53:53the only resolver(s) this machine uses are warden itself (…) — a different refusal, same fix. This is the steady state when a working install is re-initialized: the network already points at warden, so adopting the detected address would make warden query itself. Pass --upstream with a resolver outside this host.
config already exists: … Expected — see Re-running init. Add --force, or point --config at a fresh path if you meant to keep the old one.
legacy config already exists: … Expected on a host upgraded from an earlier release. See A host that already has a config in /var/lib.
--lists rejects an entry with “raw URLs are not accepted”. --lists takes catalog slugs only. Subscribe by slug, then add a custom URL source afterwards with warden blocklist add <id> --url <url>.
A mutating command fails after init with a “no token” or authentication error. Expected — init never creates one. Run warden token generate as shown in After init.
For anything not listed here, warden config lint names the problem and the line it is on.
See also
- Import lists — the next step in the base path.
- Basic configuration — where the config lives, and how to validate and reload it after editing.
- Backup & restore —
warden config backup, before a--forcere-init. - CLI reference — config-path discovery order and the rest of the
warden configsurface.