1
INI Config Format
anti edited this page 2026-04-18 06:04:39 -04:00

INI Config Format

DECNET deployments are described by an INI file passed via --config. The file is parsed by decnet/ini_loader.py (structural validation in decnet/models.py::validate_ini_string) and converted into an IniConfig of DeckySpec and CustomServiceSpec objects.

This page is an exhaustive reference. Every key documented here is accepted by the parser. Keys not listed are ignored (or cause a validation error for typed fields).

Related pages:


Section types

A DECNET INI file contains four kinds of sections:

  1. [general] — fleet-wide network settings (optional, but recommended).
  2. [<decky-name>] — one decky (or a group of deckies if amount=N).
  3. [<decky-name>.<service>] — persona/override config for one service on one decky (or group).
  4. [custom-<name>] — a bring-your-own service definition. See Custom services.

Section names must match ^[A-Za-z0-9\-_.]+$ (enforced on the decky name).

How the parser distinguishes a service subsection from a decky

A section with a dot in its name is treated as a per-service subsection if and only if the last dotted segment is a known service name registered in decnet.services.registry.all_services(). Otherwise the whole section name is treated as a decky name.

Example: [decky-01.ssh] → subsection (because ssh is a registered service). [decky.webmail] → decky section (because webmail is not a registered service).


[general] section

Fleet-wide settings. All keys optional.

Key Type Meaning
net string LAN CIDR for the decoy network, e.g. 192.168.1.0/24. Maps to IniConfig.subnet.
gw string LAN gateway IP. Maps to IniConfig.gateway.
interface string Host interface to bind MACVLAN/IPVLAN to, e.g. eth0, wlp6s0.

Note: log_target is not an INI key. It is a CLI flag (--log-target HOST:PORT). You may leave a commented #log_target= reminder in your INI, but the parser will not read it.

Example:

[general]
net       = 192.168.1.0/24
gw        = 192.168.1.1
interface = eth0

Decky sections

Each non-general, non-custom-* section that is not a service subsection defines a decky (or a group). The section name becomes the decky's name.

Key Type Default Meaning
ip string auto Static IP inside the subnet. Forbidden when amount>1 — raises ValueError.
services csv list None Comma-separated service slugs. If omitted, falls back to the --randomize-services pool or the archetype's service list.
archetype string None Archetype slug (e.g. linux-server, windows-workstation). Archetypes pre-seed services, distro, and nmap_os. See Archetypes.
amount int 1 Number of deckies to spawn from this section. Must be 1..100. When >1, the section acts as a template and deckies are named <section>-01, <section>-02, ….
nmap_os string linux TCP/IP stack family for fingerprint spoofing. Accepted: linux, windows, embedded, bsd, cisco. Also accepted as nmap-os (alias).
mutate_interval int global Auto-rotation interval in minutes (>=1). Also accepted as mutate-interval (alias).

amount expansion rules

[corp-workstations]
archetype = windows-workstation
amount    = 5

Expands to corp-workstations-01corp-workstations-05. Any [corp-workstations.<service>] subsection propagates to all five expanded deckies (see Resolution order below).

Setting ip= together with amount>1 is rejected — one IP cannot be shared.


Per-service subsections [<decky>.<service>]

Persona overrides for a single service on one decky (or a group). The last dotted segment must be a registered service slug — otherwise the subsection is ignored.

Every key inside such a subsection is passed through verbatim as a dict into DeckySpec.service_config[<service>] and then consumed by the service plugin's compose_fragment(..., service_cfg=...). The available keys are therefore service-specific — see Service personas for the full per-service key list.

Examples (excerpt from development/test-full.ini):

[decky-webmail.http]
server_header = Apache/2.4.54 (Debian)
response_code = 200
fake_app      = wordpress

[decky-webmail.smtp]
smtp_banner = 220 mail.corp.local ESMTP Postfix (Debian/GNU)
smtp_mta    = mail.corp.local

[decky-ldapdc.ssh]
ssh_version    = OpenSSH_8.9p1 Ubuntu-3ubuntu0.6
kernel_version = 5.15.0-91-generic
users          = root:toor,admin:admin123,svc_backup:backup2024

Resolution order

When the runtime configuration for a decky's service is built, values are layered in this order (later wins):

  1. Service defaults — hard-coded inside the service plugin.
  2. Archetype — if the decky's section had archetype=…, the archetype's default services/distro/nmap_os apply.
  3. Per-decky keysip, services, nmap_os, mutate_interval from the decky section override archetype defaults.
  4. Per-service subsection — keys in [<decky>.<service>] override the service's built-in defaults. For a group (amount>N), the subsection [<group>.<service>] is copied into every expanded decky.

Full realistic example

A 10-decky heterogeneous fleet, mixing an archetype pool with role-themed boxes. This is a trimmed version of development/test-full.ini.

# full.ini — 10-decky heterogeneous fleet
#
#   sudo decnet deploy --config full.ini --interface eth0 \
#        --log-target 192.168.1.200:5140

[general]
net       = 192.168.1.0/24
gw        = 192.168.1.1
interface = eth0

# ── Archetype pool: 5 Windows workstations ───────────────────────────
[windows-workstation]
archetype = windows-workstation
amount    = 5

# ── Web / Mail stack ─────────────────────────────────────────────────
[decky-webmail]
ip       = 192.168.1.110
services = http, smtp, imap, pop3
nmap_os  = linux

[decky-webmail.http]
server_header = Apache/2.4.54 (Debian)
fake_app      = wordpress

[decky-webmail.smtp]
smtp_banner = 220 mail.corp.local ESMTP Postfix (Debian/GNU)

# ── Windows file server ──────────────────────────────────────────────
[decky-fileserv]
ip       = 192.168.1.111
services = smb, ftp, tftp
nmap_os  = windows

[decky-fileserv.smb]
workgroup   = CORP
server_name = FILESERV01
os_version  = Windows Server 2019

# ── Postgres / Mongo / Elastic ───────────────────────────────────────
[decky-dbsrv02]
ip       = 192.168.1.113
services = postgres, mongodb, elasticsearch
nmap_os  = linux

[decky-dbsrv02.postgres]
pg_version = 14.5

[decky-dbsrv02.elasticsearch]
es_version   = 8.4.3
cluster_name = prod-search

# ── IoT / SCADA (embedded TCP stack) ─────────────────────────────────
[decky-iot]
ip       = 192.168.1.117
services = mqtt, snmp, conpot
nmap_os  = embedded
mutate_interval = 60

[decky-iot.snmp]
snmp_community = public
sys_descr      = Linux router 5.4.0 #1 SMP x86_64

# ── Legacy admin box ─────────────────────────────────────────────────
[decky-legacy]
ip       = 192.168.1.119
services = telnet, vnc, ssh
nmap_os  = bsd

[decky-legacy.ssh]
ssh_version    = OpenSSH_7.4p1 Debian-10+deb9u7
kernel_version = 4.9.0-19-amd64
users          = root:root,admin:password,pi:raspberry

# ── Bring-your-own service (see Custom-Services) ─────────────────────
[custom-weirdapp]
binary = myorg/weirdapp:1.2.3
exec   = /opt/weirdapp/run --listen 0.0.0.0:9000
ports  = 9000

Deploy with:

sudo decnet deploy --config full.ini --interface eth0 \
     --log-target 192.168.1.200:5140

Validation and errors

  • Empty INI (no sections): "The provided INI content must contain at least one section".
  • Total payload > 512 KB: rejected.
  • At least one decky section is mandatory (IniConfig.at_least_one_decky).
  • amount must be a positive integer <=100.
  • ip= with amount>1 is rejected.
  • mutate_interval must be an integer.
  • DeckySpec and CustomServiceSpec both have extra="forbid" — unknown typed fields on the parser's output raise Pydantic errors.