YAML/JSON Configuration Guide¶
This guide explains how to use YAML and JSON formats to define and manage OpenWRT configurations with wrtkit.
Table of Contents¶
- Overview
- Schema Generation
- Individual Section Configuration
- Complete Configuration
- API Reference
- Examples
Overview¶
All wrtkit configuration objects are built on Pydantic models, which means they support:
- Serialization: Convert Python objects to YAML/JSON
- Deserialization: Load configurations from YAML/JSON files
- Schema Generation: Generate JSON/YAML schemas for IDE autocomplete and validation
- Permissive Schema: Unknown fields are accepted for future UCI options
Schema Generation¶
Generate schemas to enable IDE autocomplete and validation:
JSON Schema¶
from wrtkit.config import UCIConfig
from wrtkit.network import NetworkInterface
import json
# Generate schema for a specific section type
schema = NetworkInterface.json_schema()
with open("network-interface-schema.json", "w") as f:
json.dump(schema, f, indent=2)
# Generate schema for complete configuration
full_schema = UCIConfig.json_schema()
with open("uci-config-schema.json", "w") as f:
json.dump(full_schema, f, indent=2)
YAML Schema¶
from wrtkit.config import UCIConfig
from wrtkit.network import NetworkInterface
# Generate YAML-formatted schema
schema_yaml = NetworkInterface.yaml_schema()
with open("network-interface-schema.yaml", "w") as f:
f.write(schema_yaml)
# Complete configuration schema in YAML
full_schema_yaml = UCIConfig.yaml_schema()
print(full_schema_yaml)
Individual Section Configuration¶
Load and save individual configuration sections (interfaces, radios, etc.):
Network Interface¶
YAML Format:
# lan-interface.yaml
device: br-lan
proto: static
ipaddr: 192.168.1.1
netmask: 255.255.255.0
gateway: 192.168.1.254
Usage:
from wrtkit.network import NetworkInterface
# Load from YAML file
interface = NetworkInterface.from_yaml_file("lan-interface.yaml", "lan")
# Or from YAML string
yaml_str = """
device: br-lan
proto: static
ipaddr: 192.168.1.1
"""
interface = NetworkInterface.from_yaml(yaml_str, "lan")
# Save to YAML
interface.to_yaml_file("output.yaml")
# Or get as string
yaml_output = interface.to_yaml()
print(yaml_output)
Wireless Access Point¶
YAML Format:
# wireless-ap.yaml
device: radio0
mode: ap
network: lan
ssid: MyWiFiNetwork
encryption: sae
key: MySecurePassword123!
ieee80211r: true
mobility_domain: 4f57
Usage:
from wrtkit.wireless import WirelessInterface
# Load from YAML
ap = WirelessInterface.from_yaml_file("wireless-ap.yaml", "default_radio0")
# Save to JSON
ap.to_json_file("wireless-ap.json")
JSON Format¶
All sections also support JSON:
from wrtkit.network import NetworkInterface
# Load from JSON file
interface = NetworkInterface.from_json_file("config.json", "lan")
# Save to JSON
interface.to_json_file("output.json", indent=2)
Complete Configuration¶
Full Configuration Structure¶
YAML Format:
network:
devices:
br_lan:
name: br-lan
type: bridge
stp: true
ports:
- lan1
- lan2
interfaces:
lan:
device: br-lan
proto: static
ipaddr: 192.168.1.1
netmask: 255.255.255.0
wireless:
radios:
radio0:
channel: 36
htmode: HE80
country: US
interfaces:
default_radio0:
device: radio0
mode: ap
network: lan
ssid: MyNetwork
encryption: sae
key: SecurePassword123!
dhcp:
sections:
lan:
interface: lan
start: 100
limit: 150
leasetime: 12h
firewall:
zones:
lan:
name: lan
input: ACCEPT
output: ACCEPT
forward: ACCEPT
network:
- lan
wan:
name: wan
input: REJECT
output: ACCEPT
forward: REJECT
masq: true
mtu_fix: true
network:
- wan
forwardings:
- src: lan
dest: wan
Loading Complete Configuration¶
from wrtkit.config import UCIConfig
# Load from YAML file
config = UCIConfig.from_yaml_file("router-config.yaml")
# Load from JSON file
config = UCIConfig.from_json_file("router-config.json")
# Verify loaded configuration
print(f"Network interfaces: {len(config.network.interfaces)}")
print(f"Wireless radios: {len(config.wireless.radios)}")
# Generate UCI commands
commands = config.get_all_commands()
for cmd in commands:
print(cmd.to_string())
Saving Complete Configuration¶
from wrtkit.config import UCIConfig
from wrtkit.network import NetworkInterface, NetworkDevice
# Build configuration programmatically
config = UCIConfig()
device = NetworkDevice("br_lan") \
.with_name("br-lan") \
.with_type("bridge") \
.with_ports(["lan1", "lan2"])
config.network.add_device(device)
interface = NetworkInterface("lan") \
.with_device("br-lan") \
.with_static_ip("192.168.1.1", "255.255.255.0")
config.network.add_interface(interface)
# Save to YAML
config.to_yaml_file("my-router-config.yaml")
# Save to JSON
config.to_json_file("my-router-config.json", indent=2)
# Get as string
yaml_str = config.to_yaml()
json_str = config.to_json()
API Reference¶
UCISection Methods¶
All configuration sections (NetworkInterface, WirelessRadio, etc.) inherit these methods:
Schema Generation¶
json_schema(title: str = None) -> Dict[str, Any]: Generate JSON Schemayaml_schema(title: str = None) -> str: Generate YAML-formatted schema
Serialization¶
to_dict(exclude_none=True, exclude_private=True) -> Dict: Convert to dictionaryto_json(exclude_none=True, exclude_private=True, indent=2) -> str: Convert to JSON stringto_yaml(exclude_none=True, exclude_private=True) -> str: Convert to YAML stringto_json_file(filename, exclude_none=True, exclude_private=True, indent=2): Save to JSON fileto_yaml_file(filename, exclude_none=True, exclude_private=True): Save to YAML file
Deserialization¶
from_dict(data: Dict, section_name: str) -> T: Create from dictionaryfrom_json(json_str: str, section_name: str) -> T: Create from JSON stringfrom_yaml(yaml_str: str, section_name: str) -> T: Create from YAML stringfrom_json_file(filename: str, section_name: str) -> T: Load from JSON filefrom_yaml_file(filename: str, section_name: str) -> T: Load from YAML file
UCIConfig Methods¶
Schema Generation¶
json_schema(title="UCI Configuration Schema") -> Dict[str, Any]: Generate JSON Schemayaml_schema(title="UCI Configuration Schema") -> str: Generate YAML-formatted schema
Serialization¶
to_dict(exclude_none=True) -> Dict: Convert to dictionaryto_json(indent=2, exclude_none=True) -> str: Convert to JSON stringto_yaml(exclude_none=True) -> str: Convert to YAML stringto_json_file(filename, indent=2, exclude_none=True): Save to JSON fileto_yaml_file(filename, exclude_none=True): Save to YAML file
Deserialization¶
from_dict(data: Dict) -> UCIConfig: Create from dictionaryfrom_json(json_str: str) -> UCIConfig: Create from JSON stringfrom_yaml(yaml_str: str) -> UCIConfig: Create from YAML stringfrom_json_file(filename: str) -> UCIConfig: Load from JSON filefrom_yaml_file(filename: str) -> UCIConfig: Load from YAML file
Examples¶
Example 1: Hybrid Approach¶
Mix programmatic and declarative configuration:
from wrtkit.config import UCIConfig
from wrtkit.network import NetworkInterface
# Load base configuration from YAML
config = UCIConfig.from_yaml_file("base-config.yaml")
# Add dynamic configuration programmatically
guest_net = NetworkInterface("guest") \
.with_device("lan1.100") \
.with_static_ip("192.168.100.1", "255.255.255.0")
config.network.add_interface(guest_net)
# Save merged configuration
config.to_yaml_file("final-config.yaml")
Example 2: Configuration Templates¶
Create reusable templates:
from wrtkit.wireless import WirelessInterface
# Load template
template = WirelessInterface.from_yaml_file("ap-template.yaml", "template")
# Customize for each radio
for i, radio in enumerate(["radio0", "radio1"]):
ap = template.model_copy(update={
"device": radio,
"ssid": f"MyNetwork-{i}"
})
# Use the customized AP config...
Example 3: Validation and Testing¶
from wrtkit.config import UCIConfig
# Load configuration
config = UCIConfig.from_yaml_file("router-config.yaml")
# Validate by generating commands
commands = config.get_all_commands()
assert len(commands) > 0
# Export for review
print(config.to_yaml())
# Save UCI script for manual review
config.save_to_file("review.sh")
Example 4: Version Control¶
YAML/JSON configurations work great with version control:
# Track your router configurations
git add router-configs/*.yaml
git commit -m "Update LAN IP address"
git diff HEAD~1 router-configs/production.yaml
Example 5: Configuration Migration¶
Convert between formats:
from wrtkit.config import UCIConfig
# Load from JSON
config = UCIConfig.from_json_file("old-config.json")
# Save to YAML for better readability
config.to_yaml_file("new-config.yaml")
Real-World Scenarios¶
Scenario 1: Multi-Site Deployment¶
Deploy the same configuration to multiple routers with site-specific changes:
Template (base-config.yaml):
network:
devices:
br_lan:
name: br-lan
type: bridge
ports:
- lan1
- lan2
wireless:
radios:
radio0:
channel: 36
htmode: HE80
country: US
Deployment Script:
from wrtkit import UCIConfig
from wrtkit.network import NetworkInterface
from wrtkit import SSHConnection
sites = [
{"name": "site1", "ip": "192.168.1.0", "router": "10.0.0.1"},
{"name": "site2", "ip": "192.168.2.0", "router": "10.0.0.2"},
{"name": "site3", "ip": "192.168.3.0", "router": "10.0.0.3"},
]
for site in sites:
# Load base template
config = UCIConfig.from_yaml_file("base-config.yaml")
# Customize for site
lan = NetworkInterface("lan") \
.with_device("br-lan") \
.with_static_ip(f"{site['ip'][:-1]}1", "255.255.255.0")
config.network.add_interface(lan)
# Save site-specific config
config.to_yaml_file(f"configs/{site['name']}.yaml")
# Deploy
with SSHConnection(site["router"], username="root", password="pass") as ssh:
config.apply(ssh)
print(f"✓ Deployed to {site['name']}")
Scenario 2: Configuration Testing¶
Test configurations before deployment:
from wrtkit import UCIConfig
import pytest
def test_lan_configuration():
"""Test that LAN is configured correctly."""
config = UCIConfig.from_yaml_file("production-router.yaml")
# Verify LAN interface exists
lan_interfaces = [i for i in config.network.interfaces if i._section == "lan"]
assert len(lan_interfaces) == 1
# Verify correct IP configuration
lan = lan_interfaces[0]
assert lan.proto == "static"
assert lan.ipaddr == "192.168.1.1"
assert lan.netmask == "255.255.255.0"
def test_wireless_security():
"""Test that wireless is properly secured."""
config = UCIConfig.from_yaml_file("production-router.yaml")
# All APs should use WPA3 or WPA2
for iface in config.wireless.interfaces:
if iface.mode == "ap":
assert iface.encryption in ["sae", "psk2"], \
f"AP {iface._section} uses weak encryption"
def test_firewall_rules():
"""Test firewall configuration."""
config = UCIConfig.from_yaml_file("production-router.yaml")
# WAN should reject incoming
wan_zones = [z for z in config.firewall.zones if z.name == "wan"]
assert len(wan_zones) == 1
assert wan_zones[0].input == "REJECT"
Scenario 3: Dynamic VLAN Generation¶
Generate VLAN configurations from a CSV or database:
vlans.csv:
vlan_id,name,subnet,dhcp_start,dhcp_limit
10,guest-wifi,192.168.10.0,50,100
20,iot,192.168.20.0,100,150
30,cameras,192.168.30.0,10,50
Script:
import csv
from wrtkit import UCIConfig
from wrtkit.network import NetworkDevice, NetworkInterface
from wrtkit.dhcp import DHCPSection
from wrtkit.firewall import FirewallZone
config = UCIConfig()
with open('vlans.csv', 'r') as f:
reader = csv.DictReader(f)
for idx, row in enumerate(reader):
vlan_id = int(row['vlan_id'])
name = row['name']
subnet = row['subnet']
gateway = f"{subnet.rsplit('.', 1)[0]}.1"
# VLAN device
device = NetworkDevice(f"vlan_{vlan_id}") \
.with_type("8021q") \
.with_ifname("lan1") \
.with_vid(vlan_id)
config.network.add_device(device)
# Interface
interface = NetworkInterface(name) \
.with_device(f"lan1.{vlan_id}") \
.with_static_ip(gateway, "255.255.255.0")
config.network.add_interface(interface)
# DHCP
dhcp = DHCPSection(name) \
.with_interface(name) \
.with_range(
int(row['dhcp_start']),
int(row['dhcp_limit']),
"12h"
)
config.dhcp.add_dhcp(dhcp)
# Firewall zone (isolated)
zone = FirewallZone(name) \
.with_name(name) \
.with_input("REJECT") \
.with_output("ACCEPT") \
.with_forward("REJECT") \
.with_network(name)
config.firewall.add_zone(zone)
# Save generated config
config.to_yaml_file("generated-vlans.yaml")
print(f"Generated configuration for {idx + 1} VLANs")
Scenario 4: Configuration Backup and Restore¶
Backup Script:
from wrtkit import UCIConfig, SSHConnection
from datetime import datetime
def backup_router(host, output_dir="backups"):
"""Backup router configuration to YAML."""
import os
os.makedirs(output_dir, exist_ok=True)
# Connect and retrieve current config
with SSHConnection(host, username="root", password="pass") as ssh:
# Create empty config to use diff functionality
config = UCIConfig()
# Get remote config via diff
diff = config.diff(ssh, show_remote_only=True)
# Extract remote commands and reconstruct config
# (In practice, you'd parse the remote config)
# For now, we'll document this is a limitation
# Alternative: Use SSH to dump config directly
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{output_dir}/router_{host}_{timestamp}.yaml"
print(f"✓ Backed up router {host} to {filename}")
# Restore from backup
def restore_router(backup_file, host):
"""Restore router configuration from YAML backup."""
config = UCIConfig.from_yaml_file(backup_file)
with SSHConnection(host, username="root", password="pass") as ssh:
diff = config.diff(ssh)
if not diff.is_empty():
print("Changes to apply:")
print(diff.to_tree())
if input("Apply? (yes/no): ") == "yes":
config.apply(ssh)
print("✓ Configuration restored")
Best Practices¶
- Use YAML for human-edited configs: YAML is more readable and supports comments
- Use JSON for programmatic access: JSON is better for APIs and automation
- Store schemas with configs: Include schema files in your repo for IDE support
- Validate before deployment: Load configs and generate UCI commands to verify
- Use version control: Track configuration changes over time
- Keep templates: Create reusable configuration templates for common setups
- Document custom fields: Since the schema is permissive, document any custom UCI options
- Test configurations: Write tests to verify config correctness
- Use hybrid approach: Combine YAML templates with Python for dynamic generation
- Backup regularly: Export configurations to YAML for disaster recovery
Permissive Schema¶
The configuration is permissive by default, accepting unknown fields:
# This works - custom UCI options are preserved
network:
interfaces:
lan:
device: br-lan
proto: static
ipaddr: 192.168.1.1
# Custom/future UCI option
custom_option: custom_value
experimental_feature: true
interface = NetworkInterface.from_yaml_file("config.yaml", "lan")
# Custom fields are accessible
data = interface.model_dump()
print(data["custom_option"]) # "custom_value"
This allows you to use future UCI options that aren't yet explicitly defined in wrtkit.
IDE Integration¶
For the best development experience with IDE autocomplete:
- Generate schemas for your configuration types
- Configure your IDE to use the JSON schemas
- Most modern IDEs (VS Code, PyCharm, etc.) will provide autocomplete and validation
VS Code Example:
Troubleshooting¶
Issue: Section names not preserved¶
Wrong:
# Missing section name - will fail
network:
interfaces:
- device: br-lan # Wrong: list instead of dict
proto: static
Correct:
Issue: Private fields appearing in YAML¶
Use exclude_private=True (default) to hide fields starting with _:
# Good - no private fields
interface.to_yaml() # exclude_private=True by default
# Bad - includes _package, _section, etc.
interface.to_yaml(exclude_private=False)
Issue: None values in output¶
Use exclude_none=True (default) to omit None values: