You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dvmhost/tools/dvmcfggen/templates.py

285 lines
9.5 KiB

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
#/**
#* Digital Voice Modem - Host Configuration Generator
#* GPLv2 Open Source. Use is subject to license terms.
#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#*
#*/
#/*
#* Copyright (C) 2026 by Bryan Biedenkapp N2PLL
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU General Public License as published by
#* the Free Software Foundation; either version 2 of the License, or
#* (at your option) any later version.
#*
#* This program is distributed in the hope that it will be useful,
#* but WITHOUT ANY WARRANTY; without even the implied warranty of
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#* GNU General Public License for more details.
#*
#* You should have received a copy of the GNU General Public License
#* along with this program; if not, write to the Free Software
#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#*/
"""
dvmcfggen - DVMHost Configuration Generator
Pre-configured templates loaded from dvmhost config.example.yml
Ensures all configuration parameters are present
"""
from typing import Dict, Any, Optional
import secrets
import yaml
from pathlib import Path
# Possible locations for config.example.yml
CONFIG_EXAMPLE_PATHS = [
Path(__file__).parent.parent / 'configs' / 'config.example.yml',
Path('/opt/dvm/config.example.yml'),
Path('./config.example.yml'),
]
def find_config_example() -> Optional[Path]:
"""Find the config.example.yml file from dvmhost"""
for path in CONFIG_EXAMPLE_PATHS:
if path.exists():
return path
return None
def load_base_config() -> Dict[str, Any]:
"""
Load the base configuration from config.example.yml
Falls back to minimal config if not found
"""
example_path = find_config_example()
if example_path:
try:
with open(example_path, 'r') as f:
config = yaml.safe_load(f)
if config:
return config
except Exception as e:
print(f"Warning: Could not load {example_path}: {e}")
# Fallback to minimal config if example not found
print("Warning: config.example.yml not found, using minimal fallback config")
return get_minimal_fallback_config()
def get_minimal_fallback_config() -> Dict[str, Any]:
"""Minimal fallback configuration if config.example.yml is not available"""
return {
'daemon': True,
'log': {
'displayLevel': 1,
'fileLevel': 1,
'filePath': '.',
'activityFilePath': '.',
'fileRoot': 'DVM'
},
'network': {
'enable': True,
'id': 100000,
'address': '127.0.0.1',
'port': 62031,
'password': 'PASSWORD',
'rpcAddress': '127.0.0.1',
'rpcPort': 9890,
'rpcPassword': 'ULTRA-VERY-SECURE-DEFAULT',
},
'system': {
'identity': 'DVM',
'timeout': 180,
'duplex': True,
'modeHang': 10,
'rfTalkgroupHang': 10,
'activeTickDelay': 5,
'idleTickDelay': 5,
'info': {
'latitude': 0.0,
'longitude': 0.0,
'height': 1,
'power': 0,
'location': 'Anywhere'
},
'config': {
'authoritative': True,
'supervisor': False,
'channelId': 1,
'channelNo': 1,
'serviceClass': 1,
'siteId': 1,
'dmrNetId': 1,
'dmrColorCode': 1,
'p25NAC': 0x293,
'p25NetId': 0xBB800,
'nxdnRAN': 1
},
'cwId': {
'enable': True,
'time': 10,
'callsign': 'MYCALL'
},
'modem': {
'protocol': {
'type': 'uart',
'uart': {
'port': '/dev/ttyUSB0',
'speed': 115200
}
},
'rxLevel': 50,
'txLevel': 50,
'rxDCOffset': 0,
'txDCOffset': 0
}
},
'protocols': {
'dmr': {
'enable': True,
'control': {
'enable': True,
'dedicated': False
}
},
'p25': {
'enable': True,
'control': {
'enable': True,
'dedicated': False
}
},
'nxdn': {
'enable': False,
'control': {
'enable': False,
'dedicated': False
}
}
}
}
def generate_secure_key(length: int = 64) -> str:
"""Generate a secure random hex key"""
return secrets.token_hex(length // 2).upper()
def apply_template_customizations(config: Dict[str, Any], template_type: str) -> Dict[str, Any]:
"""
Apply template-specific customizations to the base config
Args:
config: Base configuration dict
template_type: Template name (repeater, etc.)
Returns:
Customized configuration dict
"""
# Make a deep copy to avoid modifying the original
import copy
config = copy.deepcopy(config)
if template_type == 'control-channel-p25':
# P25 dedicated control channel
config['system']['duplex'] = True
config['system']['identity'] = 'CC-P25'
config['system']['config']['authoritative'] = True
config['system']['config']['supervisor'] = True
config['protocols']['dmr']['enable'] = False
config['protocols']['p25']['enable'] = True
config['protocols']['nxdn']['enable'] = False
config['protocols']['p25']['control']['enable'] = True
config['protocols']['p25']['control']['dedicated'] = True
elif template_type == 'control-channel-dmr':
# DMR dedicated control channel
config['system']['duplex'] = True
config['system']['identity'] = 'CC-DMR'
config['system']['config']['authoritative'] = True
config['system']['config']['supervisor'] = True
config['protocols']['dmr']['enable'] = True
config['protocols']['p25']['enable'] = False
config['protocols']['nxdn']['enable'] = False
config['protocols']['dmr']['control']['enable'] = True
config['protocols']['dmr']['control']['dedicated'] = True
elif template_type == 'voice-channel':
# Voice channel for trunking
config['system']['duplex'] = True
config['system']['identity'] = 'VC'
config['system']['config']['authoritative'] = False
config['system']['config']['supervisor'] = False
config['protocols']['dmr']['enable'] = True
config['protocols']['p25']['enable'] = True
config['protocols']['nxdn']['enable'] = False
config['protocols']['dmr']['control']['enable'] = False
config['protocols']['dmr']['control']['dedicated'] = False
config['protocols']['p25']['control']['enable'] = False
config['protocols']['p25']['control']['dedicated'] = False
elif template_type == 'enhanced':
# Enhanced conventional repeater with grants
config['system']['duplex'] = True
config['system']['identity'] = 'CONV'
config['protocols']['dmr']['enable'] = True
config['protocols']['p25']['enable'] = True
config['protocols']['nxdn']['enable'] = False
config['protocols']['dmr']['control']['enable'] = True
config['protocols']['dmr']['control']['dedicated'] = False
config['protocols']['p25']['control']['enable'] = True
config['protocols']['p25']['control']['dedicated'] = False
elif template_type == 'conventional':
# Conventional repeater
config['system']['duplex'] = True
config['system']['identity'] = 'RPT'
config['protocols']['dmr']['enable'] = False
config['protocols']['p25']['enable'] = True
config['protocols']['nxdn']['enable'] = False
config['protocols']['dmr']['control']['enable'] = False
config['protocols']['dmr']['control']['dedicated'] = False
config['protocols']['p25']['control']['enable'] = True
config['protocols']['p25']['control']['dedicated'] = False
return config
def get_template(template_name: str) -> Dict[str, Any]:
"""
Get a configuration template by name
Args:
template_name: Name of the template
Returns:
Complete configuration dictionary with all parameters
"""
if template_name not in TEMPLATES:
raise ValueError(f"Unknown template: {template_name}")
# Load base config from config.example.yml
base_config = load_base_config()
# Apply template-specific customizations
customized_config = apply_template_customizations(base_config, template_name)
return customized_config
# Available templates
TEMPLATES = {
'conventional': 'Conventional repeater/hotspot',
'enhanced': 'Enhanced conventional repeater/hotspot with grants',
'control-channel-p25': 'P25 dedicated control channel for trunking',
'control-channel-dmr': 'DMR dedicated control channel for trunking',
'voice-channel': 'Voice channel for trunking system',
}

Powered by TurnKey Linux.