26 KiB
Netmode Developer Guide
Table of Contents
- Development Environment Setup
- Package Build System
- Code Organization
- API Reference
- Mode Script Development
- UCI Integration
- Data Model Development
- Debugging Techniques
- Testing Framework
- Release Process
Development Environment Setup
Prerequisites
# OpenWrt build environment
sudo apt-get install build-essential libncurses5-dev gawk git \
subversion libssl-dev gettext unzip zlib1g-dev file python3
# Clone iopsys repository
git clone https://dev.iopsys.eu/iopsys/iopsyswrt.git
cd iopsyswrt
# Update feeds
./scripts/feeds update -a
./scripts/feeds install -a
Building netmode Package
# Configure build
make menuconfig
# Navigate to: Utilities -> netmode
# Build package only
make package/feeds/iopsys/netmode/compile V=s
# Build with debug symbols
make package/feeds/iopsys/netmode/compile V=s CONFIG_DEBUG=y
Installing to Device
# Copy to device
scp bin/packages/*/iopsys/netmode_*.ipk root@192.168.1.1:/tmp/
# Install on device
ssh root@192.168.1.1
opkg install /tmp/netmode_*.ipk
Development Workflow
# Make changes to files in feeds/iopsys/netmode/files/
# Clean and rebuild
make package/feeds/iopsys/netmode/clean
make package/feeds/iopsys/netmode/compile V=s
# Test on device
scp bin/packages/*/iopsys/netmode_*.ipk root@192.168.1.1:/tmp/
ssh root@192.168.1.1 "opkg remove netmode; opkg install /tmp/netmode_*.ipk"
Package Build System
Makefile Analysis
PKG_NAME:=netmode
PKG_VERSION:=1.1.11
PKG_RELEASE:=1
Version Scheme: MAJOR.MINOR.PATCH
- MAJOR: Breaking changes
- MINOR: New features, backward compatible
- PATCH: Bug fixes
Build Configuration
define Package/$(PKG_NAME)/config
config NETMODE_VENDOR_PREFIX
depends on PACKAGE_netmode
string "Vendor Extension used for netmode datamodel"
default ""
endef
Usage in code:
"object": "{BBF_VENDOR_PREFIX}NetMode"
Replaced by BBFDM_REGISTER_SERVICES during build.
Installation Targets
define Package/netmode/install
$(INSTALL_DIR) $(1)/etc
$(INSTALL_DIR) $(1)/lib
$(CP) ./files/etc/* $(1)/etc/
$(CP) ./files/lib/* $(1)/lib/
$(BBFDM_REGISTER_SERVICES) -v ${VENDOR_PREFIX} ./bbfdm_service.json $(1) $(PKG_NAME)
$(BBFDM_INSTALL_MS_DM) -v ${VENDOR_PREFIX} ./files/datamodel.json $(1) $(PKG_NAME)
endef
File Permissions:
- Scripts in
/etc/init.d/:0755(executable) - UCI configs:
0644(readable) - Mode scripts: Must be made executable in postinst
Code Organization
Module Responsibilities
| Module | File | Responsibility |
|---|---|---|
| Init System | /etc/init.d/netmode |
Service lifecycle, mode switching orchestration |
| Mode Scripts | /etc/netmodes/<mode>/scripts/* |
Mode-specific UCI configuration |
| Pre-hooks | /lib/netmode/pre/* |
Pre-switch preparation |
| Post-hooks | /lib/netmode/post/* |
Post-switch cleanup, reboot |
| UCI Defaults | /etc/uci-defaults/* |
First-boot configuration |
| Data Model | /files/datamodel.json |
BBF TR-181 mappings |
| Service Def | bbfdm_service.json |
BBF service registration |
Init Script Architecture
/etc/init.d/netmode
├── start_service() # Main entry point
│ ├── configure_env_vars() # Setup NETMODE_* vars
│ ├── libnetmode_exec("pre")
│ ├── Copy UCI files
│ ├── libnetmode_exec() # Generic scripts
│ ├── Execute mode scripts
│ ├── libnetmode_exec("post")
│ └── cleanup_env_vars()
├── service_triggers() # Procd reload trigger
└── Helper functions:
├── _log()
├── _get_modes_sec_name()
├── _set_env_args()
└── cleanup_arg_values()
API Reference
Init Script Functions
start_service()
Main service entry point called by procd.
Execution Conditions:
/etc/config/netmodeexistsnetmode.global.enabled = 1netmode.global.modeis set- Current mode differs from
.last_mode
Side Effects:
- Modifies UCI configuration
- Exports environment variables
- Writes
.last_modefile - May trigger system reboot (via post-hook)
configure_env_vars(mode)
Exports mode arguments as environment variables.
Parameters:
mode: Mode name (e.g., "routed-pppoe")
Behavior:
- Find UCI section matching mode name
- Iterate
supported_argswith matchingdm_parent - Export
NETMODE_<name>=<value>if value is non-empty - Exit if required arguments are missing
Example:
configure_env_vars "routed-pppoe"
# Exports:
# NETMODE_username="user@isp.com"
# NETMODE_password="secret123"
cleanup_env_vars(mode)
Unsets NETMODE_* environment variables and clears UCI values.
Parameters:
mode: Current mode name
Behavior:
- Unset all
NETMODE_*environment variables - Clear
valuefield in UCI for all arguments of current mode - Commit UCI changes
Security Note: Prevents leaking credentials to subsequent processes.
libnetmode_exec(when)
Executes hook scripts from /lib/netmode/<when>/.
Parameters:
when: Hook type ("pre", "post", or empty for default)
Execution:
- Scripts executed in lexicographical order
- Runs in subshell via
sh <script> - Logs execution to syslog
Example:
libnetmode_exec "pre"
# Executes: /lib/netmode/pre/10-backup
# /lib/netmode/pre/20-prepare
_log(message)
Logs message to syslog with netmode tag.
Parameters:
message: Log message
Implementation:
logger -s -p user.info -t "netmode" "$*"
Mode Script Development
Script Template
#!/bin/sh
#
# Mode: <mode-name>
# Description: <brief description>
# Required Arguments: <list>
# Optional Arguments: <list>
#
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
# Source device-specific info if available
[ -f /etc/device_info ] && source "/etc/device_info"
# Logging function
_log() {
logger -s -p user.info -t "netmode-<mode>" "$*"
}
# Main configuration function
configure_network() {
_log "Starting network configuration"
# Get WAN device
wandev="$(uci -q get network.WAN.ifname)"
[ -z "$wandev" ] && wandev="eth0" # Fallback
# Validate required arguments
if [ -z "$NETMODE_required_arg" ]; then
_log "ERROR: Missing required argument: required_arg"
return 1
fi
# Configure network interface
uci -q set network.lan=interface
uci -q set network.lan.device='br-lan'
uci -q set network.lan.proto='static'
uci -q set network.lan.ipaddr='192.168.1.1'
uci -q set network.lan.netmask='255.255.255.0'
# Mode-specific WAN configuration
uci -q set network.wan=interface
uci -q set network.wan.device="$wandev"
# Add mode-specific settings here
uci -q commit network
_log "Network configuration complete"
}
# Configure DHCP server
configure_dhcp() {
_log "Configuring DHCP"
uci -q set dhcp.lan.ignore=0
uci -q set dhcp.wan.ignore=1
uci -q commit dhcp
}
# Configure firewall
configure_firewall() {
_log "Configuring firewall"
uci -q set firewall.globals.enabled="1"
uci -q commit firewall
}
# Configure multicast
configure_multicast() {
_log "Configuring multicast"
# L3 or L2 multicast config
rm -f /etc/config/mcast
sh /rom/etc/uci-defaults/61-mcast_config_generate
uci -q commit mcast
}
# Update service dependencies
configure_services() {
_log "Updating service configurations"
# CWMP WAN interface
uci -q set cwmp.cpe.default_wan_interface="wan"
uci -q commit cwmp
# Gateway WAN interface
uci -q set gateway.global.wan_interface="wan"
uci -q commit gateway
# SSDPD
uci -q set ssdpd.ssdp.enabled="1"
uci -q commit ssdpd
}
# Main execution
configure_network || exit 1
configure_dhcp
configure_firewall
configure_multicast
configure_services
_log "Mode configuration complete"
exit 0
Common Patterns
VLAN Configuration
configure_vlan() {
local wandev="$1"
# Delete existing VLANs
for vlandev_sec in $(uci show network | grep "type=.*8021q" | cut -d'.' -f1,2); do
uci -q delete $vlandev_sec
done
# Create new VLAN if vlanid provided
if [ -n "$wandev" ] && echo "$NETMODE_vlanid" | grep -Eq '^[0-9]+$' && [ "$NETMODE_vlanid" -ge 1 ]; then
local vlandev="$wandev.$NETMODE_vlanid"
local vlandev_sec=$(echo $vlandev | tr '.' '_')
uci -q set network.${vlandev_sec}=device
uci -q set network.${vlandev_sec}.type="8021q"
uci -q set network.${vlandev_sec}.name="$vlandev"
uci -q set network.${vlandev_sec}.ifname="$wandev"
uci -q set network.${vlandev_sec}.vid=$NETMODE_vlanid
echo "$vlandev"
else
echo "$wandev"
fi
}
# Usage
wandev="$(uci -q get network.WAN.ifname)"
wandev=$(configure_vlan "$wandev")
uci -q set network.wan.device="$wandev"
DNS Server Configuration
configure_dns() {
uci -q delete network.wan.dns
if [ -n "$NETMODE_dns_servers" ]; then
local dns_servers="$(echo $NETMODE_dns_servers | tr ',' ' ')"
for server in $dns_servers; do
# Validate IP address format
if echo "$server" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
uci add_list network.wan.dns="$server"
else
_log "WARNING: Invalid DNS server: $server"
fi
done
fi
}
Bridge Port Configuration
configure_bridge_ports() {
uci -q delete network.br_lan.ports
uci -q set network.br_lan.bridge_empty='1'
add_port_to_br_lan() {
local port="$1"
[ -n "$port" -a -d /sys/class/net/$port ] || return
uci add_list network.br_lan.ports="$port"
_log "Added $port to br-lan"
}
if [ -f /etc/board.json ]; then
json_load_file /etc/board.json
json_select network
# LAN ports
json_select lan
if json_is_a ports array; then
json_for_each_item add_port_to_br_lan ports
else
json_get_var device device
[ -n "$device" ] && add_port_to_br_lan "$device"
fi
json_select ..
# WAN ports (for bridged mode)
if [ "$1" = "bridged" ]; then
json_select wan 2>/dev/null
json_get_var device device
[ -n "$device" ] && add_port_to_br_lan "$device"
fi
json_cleanup
fi
}
Error Handling
# Validate critical operations
configure_network() {
if ! uci -q get network.WAN >/dev/null; then
_log "ERROR: WAN interface not found in board config"
return 1
fi
if [ -z "$NETMODE_required_param" ]; then
_log "ERROR: Required parameter missing"
return 1
fi
# Perform configuration
uci -q set network.wan.setting="$NETMODE_required_param" || {
_log "ERROR: Failed to set UCI option"
return 1
}
uci -q commit network || {
_log "ERROR: Failed to commit network config"
return 1
}
return 0
}
# Check return value
if ! configure_network; then
_log "Configuration failed, aborting mode switch"
exit 1
fi
UCI Integration
Reading UCI Values
# Get single value
enabled=$(uci -q get netmode.global.enabled)
# Get with default
mode=$(uci -q get netmode.global.mode || echo "routed-dhcp")
# Check if option exists
if uci -q get network.wan >/dev/null; then
echo "WAN interface exists"
fi
# Iterate sections
config_load netmode
config_foreach handle_mode supported_modes
handle_mode() {
local section="$1"
local name
config_get name "$section" name
echo "Found mode: $name"
}
Writing UCI Values
# Set option
uci set network.wan.proto='dhcp'
# Delete option
uci delete network.wan.username
# Delete section
uci delete network.wan6
# Add list item
uci add_list network.wan.dns='8.8.8.8'
# Delete all list items
uci delete network.wan.dns
# Always commit after changes
uci commit network
UCI Section References
# Reference by index
uci set netmode.@supported_modes[0].name='routed-dhcp'
# Reference by name (if section is named)
uci set netmode.global.mode='routed-pppoe'
# Get section index
idx=$(uci show netmode | grep "name='routed-pppoe'" | cut -d. -f2 | cut -d= -f1)
uci set "netmode.$idx.description='PPPoE Mode'"
Advanced UCI Operations
# Create anonymous section
uci add netmode supported_modes
# Rename section
uci rename netmode.@supported_modes[0]=pppoe_mode
# Reorder sections
uci reorder netmode.pppoe_mode=1
# Batch operations
uci batch <<EOF
set network.wan=interface
set network.wan.proto='dhcp'
set network.wan.device='eth0'
delete network.wan.ipaddr
commit network
EOF
Data Model Development
Adding New Parameters
1. Update datamodel.json
{
"Device.{BBF_VENDOR_PREFIX}NetMode.SupportedModes.{i}.": {
"NewParameter": {
"type": "string",
"read": true,
"write": true,
"description": "New parameter description",
"protocols": ["cwmp", "usp"],
"mapping": [
{
"type": "uci_sec",
"data": "@Parent",
"key": "new_param"
}
]
}
}
}
2. Update UCI Schema
Add to /etc/config/netmode:
config supported_modes
option name 'mode-name'
option new_param 'default-value'
3. Handle in Mode Script
#!/bin/sh
new_value="$NETMODE_new_param"
if [ -n "$new_value" ]; then
uci set some_config.section.option="$new_value"
fi
Data Type Mappings
| BBF Type | UCI Type | Shell Validation |
|---|---|---|
string |
text | [ -n "$var" ] |
unsignedInt |
integer | echo "$var" | grep -Eq '^[0-9]+$' |
boolean |
0/1 | [ "$var" = "1" ] |
dateTime |
timestamp | date -d "$var" |
base64 |
base64 | echo "$var" | base64 -d |
Mapping Types
uci Mapping (Static Section)
{
"mapping": [
{
"type": "uci",
"uci": {
"file": "netmode",
"section": {
"name": "global"
},
"option": {
"name": "enabled"
}
}
}
]
}
uci_sec Mapping (Dynamic Section)
{
"mapping": [
{
"type": "uci_sec",
"data": "@Parent",
"key": "name"
}
]
}
Special Flags
| Flag | Purpose | Example |
|---|---|---|
Linker |
Reference target | Mode name for linking |
Reference |
Reference source | Mode selection |
Unique |
Value must be unique | Mode names |
Secure |
Hidden from GET | Passwords |
Debugging Techniques
Enable Verbose Logging
# In mode script
set -x # Enable shell tracing
trap 'set +x' EXIT
# Or use logger extensively
_log() {
logger -s -p user.debug -t "netmode-debug" "$*"
}
Live Log Monitoring
# Terminal 1: Monitor logs
logread -f | grep -E "netmode|network|firewall"
# Terminal 2: Trigger mode switch
service netmode restart
Capture Environment State
#!/bin/sh
# Add to mode script for debugging
DEBUG_DIR="/tmp/netmode-debug"
mkdir -p "$DEBUG_DIR"
# Save environment
env > "$DEBUG_DIR/env.txt"
# Save UCI state
uci export network > "$DEBUG_DIR/network.uci"
uci export netmode > "$DEBUG_DIR/netmode.uci"
# Save script output
exec 2>&1 | tee "$DEBUG_DIR/script-output.log"
Interactive Testing
# Manually execute mode script
sh -x /etc/netmodes/routed-pppoe/scripts/10-routed-pppoe
# Set environment manually
export NETMODE_username="test"
export NETMODE_password="test123"
sh /etc/netmodes/routed-pppoe/scripts/10-routed-pppoe
# Check UCI changes without commit
uci changes
Common Debug Commands
# Check last mode
cat /etc/netmodes/.last_mode
# Check netmode UCI
uci show netmode
# Check network UCI
uci show network | grep -E "lan|wan"
# Check process environment
cat /proc/$(pgrep netmode)/environ | tr '\0' '\n' | grep NETMODE
# Check procd service status
ubus call service list '{"name":"netmode"}'
Procd Debugging
# Check procd service state
procd_status() {
ubus call service list '{"name":"netmode"}' | jsonfilter -e '@.netmode'
}
# Manual procd trigger
ubus call service event '{"type":"config.change","data":{"package":"netmode"}}'
# Check init script syntax
sh -n /etc/init.d/netmode
Testing Framework
Unit Test Template
#!/bin/sh
# test-mode-routed-dhcp.sh
. /lib/functions.sh
PASSED=0
FAILED=0
assert_equal() {
local expected="$1"
local actual="$2"
local test_name="$3"
if [ "$expected" = "$actual" ]; then
echo "✓ PASS: $test_name"
PASSED=$((PASSED + 1))
else
echo "✗ FAIL: $test_name"
echo " Expected: $expected"
echo " Actual: $actual"
FAILED=$((FAILED + 1))
fi
}
assert_exists() {
local path="$1"
local test_name="$2"
if [ -e "$path" ]; then
echo "✓ PASS: $test_name"
PASSED=$((PASSED + 1))
else
echo "✗ FAIL: $test_name (path not found: $path)"
FAILED=$((FAILED + 1))
fi
}
test_mode_applied() {
local expected_mode="$1"
local actual_mode=$(cat /etc/netmodes/.last_mode 2>/dev/null)
assert_equal "$expected_mode" "$actual_mode" "Mode applied"
}
test_wan_proto() {
local expected_proto="$1"
local actual_proto=$(uci -q get network.wan.proto)
assert_equal "$expected_proto" "$actual_proto" "WAN protocol"
}
test_dhcp_enabled() {
local expected="$1"
local actual=$(uci -q get dhcp.lan.ignore)
[ "$expected" = "enabled" ] && expected="0" || expected="1"
assert_equal "$expected" "$actual" "DHCP server state"
}
# Run tests
echo "Testing routed-dhcp mode..."
test_mode_applied "routed-dhcp"
test_wan_proto "dhcp"
test_dhcp_enabled "enabled"
assert_exists "/etc/config/network" "Network config exists"
echo ""
echo "Results: $PASSED passed, $FAILED failed"
[ $FAILED -eq 0 ] && exit 0 || exit 1
Integration Test Script
#!/bin/sh
# test-mode-switch.sh
MODE="$1"
[ -z "$MODE" ] && { echo "Usage: $0 <mode>"; exit 1; }
echo "=== Mode Switch Test: $MODE ==="
# Backup config
cp /etc/config/network /tmp/network.backup
cp /etc/config/netmode /tmp/netmode.backup
# Set mode
echo "Setting mode to $MODE..."
uci set netmode.global.mode="$MODE"
uci commit netmode
# Trigger switch
echo "Applying mode..."
service netmode restart
sleep 2
# Verify
echo "Verifying..."
LAST_MODE=$(cat /etc/netmodes/.last_mode)
if [ "$LAST_MODE" = "$MODE" ]; then
echo "✓ Mode switch successful"
# Run mode-specific tests
case "$MODE" in
routed-dhcp)
[ "$(uci -q get network.wan.proto)" = "dhcp" ] && echo "✓ WAN proto correct"
;;
routed-pppoe)
[ "$(uci -q get network.wan.proto)" = "pppoe" ] && echo "✓ WAN proto correct"
;;
bridged)
[ -z "$(uci -q get network.wan)" ] && echo "✓ WAN interface removed"
;;
esac
else
echo "✗ Mode switch failed"
echo " Expected: $MODE"
echo " Actual: $LAST_MODE"
fi
# Restore config
echo "Restoring config..."
cp /tmp/network.backup /etc/config/network
cp /tmp/netmode.backup /etc/config/netmode
uci commit
Automated Test Suite
#!/bin/sh
# run-all-tests.sh
TESTS_DIR="$(dirname $0)/tests"
RESULTS_DIR="/tmp/netmode-test-results"
mkdir -p "$RESULTS_DIR"
TOTAL=0
PASSED=0
FAILED=0
for test_script in "$TESTS_DIR"/test-*.sh; do
[ -f "$test_script" ] || continue
TOTAL=$((TOTAL + 1))
test_name=$(basename "$test_script" .sh)
echo "Running $test_name..."
if sh "$test_script" > "$RESULTS_DIR/$test_name.log" 2>&1; then
echo "✓ $test_name PASSED"
PASSED=$((PASSED + 1))
else
echo "✗ $test_name FAILED"
FAILED=$((FAILED + 1))
cat "$RESULTS_DIR/$test_name.log"
fi
done
echo ""
echo "=== Test Summary ==="
echo "Total: $TOTAL"
echo "Passed: $PASSED"
echo "Failed: $FAILED"
[ $FAILED -eq 0 ] && exit 0 || exit 1
Release Process
Version Bump Procedure
-
Update version in Makefile:
PKG_VERSION:=1.2.0 PKG_RELEASE:=1 -
Update changelog:
# Create CHANGELOG.md if not exists echo "## [1.2.0] - $(date +%Y-%m-%d)" >> CHANGELOG.md echo "### Added" >> CHANGELOG.md echo "- New feature description" >> CHANGELOG.md -
Test on target hardware:
make package/feeds/iopsys/netmode/clean make package/feeds/iopsys/netmode/compile V=s # Deploy and test -
Commit changes:
git add Makefile CHANGELOG.md git commit -m "netmode: update to version 1.2.0" -
Tag release:
git tag -a netmode-1.2.0 -m "Release version 1.2.0" git push origin netmode-1.2.0
Pre-release Checklist
- All tests passing
- Documentation updated
- CHANGELOG.md updated
- Version numbers bumped
- Tested on at least 2 different hardware platforms
- Backward compatibility verified
- Data model compatibility checked
- No hardcoded paths or device-specific code
- Logging is appropriate (not too verbose)
- UCI defaults handle upgrade scenarios
Package Validation
# Check package structure
tar -tzf netmode_*.ipk
# Verify file permissions
tar -xzf netmode_*.ipk -O ./data.tar.gz | tar -tzv
# Check dependencies
opkg info netmode_*.ipk | grep Depends
# Validate UCI config syntax
tar -xzf netmode_*.ipk -O ./data.tar.gz | tar -xz ./etc/config/netmode
uci import netmode < netmode
Best Practices Summary
Code Quality
-
Always use shellcheck:
shellcheck /etc/init.d/netmode shellcheck /etc/netmodes/*/scripts/* -
Use set -e for critical scripts:
#!/bin/sh set -e # Exit on error -
Quote all variables:
# Good uci set network.wan.device="$wandev" # Bad uci set network.wan.device=$wandev -
Check return values:
if ! configure_network; then logger -t netmode "Configuration failed" exit 1 fi
Performance Optimization
-
Minimize UCI commits:
# Bad: Multiple commits uci set network.lan.ipaddr='192.168.1.1' uci commit network uci set network.lan.netmask='255.255.255.0' uci commit network # Good: Single commit uci set network.lan.ipaddr='192.168.1.1' uci set network.lan.netmask='255.255.255.0' uci commit network -
Avoid unnecessary mode switches:
# Check .last_mode before proceeding [ "$mode" = "$(cat /etc/netmodes/.last_mode)" ] && return -
Use batch UCI operations:
uci batch <<EOF set network.wan=interface set network.wan.proto='dhcp' commit network EOF
Security Hardening
-
Validate all input:
# VLAN ID validation if ! echo "$NETMODE_vlanid" | grep -Eq '^[0-9]+$'; then logger -t netmode "Invalid VLAN ID" return 1 fi -
Clear sensitive data:
# Automatic via cleanup_env_vars() # But also clear from UCI uci set netmode.@supported_args[X].value='' -
Use secure file permissions:
# Scripts should be 0755 # Configs should be 0644 # Never 0777
Troubleshooting
Build Issues
Issue: Package fails to build
Solution:
# Clean build
make package/feeds/iopsys/netmode/clean
rm -rf build_dir/target-*/netmode-*
# Rebuild with verbose output
make package/feeds/iopsys/netmode/compile V=s
Issue: Data model registration fails
Solution:
# Check vendor prefix
grep BBF_VENDOR_PREFIX .config
# Verify bbfdm is installed
opkg list-installed | grep bbfdm
Runtime Issues
Issue: Mode not switching
Debug:
# Check service status
service netmode status
# Check logs
logread | grep netmode
# Verify UCI
uci show netmode
Issue: Environment variables not set
Debug:
# Check dm_parent references
uci show netmode | grep dm_parent
# Verify argument values
uci show netmode | grep value
Contributing Guidelines
Code Style
- Use tabs for indentation (OpenWrt standard)
- Max line length: 120 characters
- Function names:
lowercase_with_underscores - Variables:
lowercaseorUPPERCASEfor constants
Commit Messages
netmode: brief description (max 50 chars)
Detailed explanation of changes (wrap at 72 chars).
- Bullet points for multiple changes
- Reference issue numbers if applicable
Signed-off-by: Your Name <email@example.com>
Pull Request Process
- Fork the repository
- Create feature branch:
git checkout -b feature/my-feature - Make changes and test thoroughly
- Update documentation
- Submit PR with clear description
- Address review comments
Resources
- OpenWrt Documentation: https://openwrt.org/docs/
- UCI Documentation: https://openwrt.org/docs/guide-user/base-system/uci
- Procd: https://openwrt.org/docs/guide-developer/procd-init-scripts
- Shell Style Guide: https://google.github.io/styleguide/shellguide.html
- iopsys Development: https://dev.iopsys.eu/
Maintainer: iopsys Development Team License: GPL-2.0-only Version: 1.1.11