feat(config): split monolithic config + extract secrets

- Update .gitignore for v0.3 multi-file layout
- Add config/.env.example template for secrets
- Add config/local.yaml.example for operator values
- Wire main.py to use new config_loader
- Support both legacy and new layouts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
K7ZVX 2026-05-14 15:14:12 +00:00
commit 965a844b0d
4 changed files with 113 additions and 7 deletions

20
.gitignore vendored
View file

@ -1,3 +1,13 @@
# Operator-identifying config and secrets (v0.3 split)
/data/config/local.yaml
/data/config/secrets/
/data/secrets/
.env
.env.local
.env.*
!.env.example
local.yaml
!local.yaml.example
# Python
__pycache__/
*.py[cod]
@ -49,3 +59,13 @@ data/
# OS
.DS_Store
Thumbs.db
# Operator-identifying config and secrets (v0.3 split)
/data/config/local.yaml
/data/config/secrets/
/data/secrets/
.env
.env.local
.env.*
!.env.example
local.yaml
!local.yaml.example

19
config/.env.example Normal file
View file

@ -0,0 +1,19 @@
# MeshAI Secrets Template
# Copy to /data/secrets/.env and fill in your values
# This file is gitignored - never commit real secrets
# LLM API Keys (only one needed based on your backend choice)
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GOOGLE_API_KEY=
# Mesh Source Credentials
MESHMONITOR_API_TOKEN=
MQTT_PASSWORD=
# Environmental Feed Keys
TOMTOM_API_KEY=
FIRMS_MAP_KEY=
# Notification Credentials
SMTP_PASSWORD=

57
config/local.yaml.example Normal file
View file

@ -0,0 +1,57 @@
# MeshAI Local Configuration Template
# Copy to /data/config/local.yaml and customize for your deployment
# This file is gitignored - contains operator-identifying values
# Operator Identity
identity:
name: "" # Bot display name
owner: "" # Owner callsign/name
primary_node_id: "" # Your main mesh node ID
contact_email: "" # For NWS user_agent, SMTP from
# Region Coordinates
# Map your region names to their lat/lon center points
regions:
"Example Region":
lat: 0.0
lon: 0.0
# Add more regions as needed:
# "Another Region":
# lat: 42.5
# lon: -114.5
# Mesh Data Source URLs
mesh_sources:
meshmonitor_url: "" # Your MeshMonitor instance
sources:
# Per-source URL overrides (matches names in mesh_sources.yaml)
"My-Meshview":
url: ""
# "My-MeshMonitor":
# url: ""
# Infrastructure Hosts
infrastructure:
tcp_host: "" # Meshtastic TCP host (meshtasticd)
qdrant_host: "" # Qdrant vector DB (optional)
tei_host: "" # TEI embedding service (optional)
sparse_host: "" # Sparse embedding service (optional)
# Environmental Feed Center Point
env_center:
latitude: 0.0 # Center of your coverage area
longitude: 0.0
# Notification Targets
notification_targets:
smtp_from: "" # Email from address
smtp_recipients: [] # Default email recipients
webhook_urls: [] # Webhook endpoints
alert_node_ids: [] # Node IDs for mesh DM alerts
# Critical Infrastructure Nodes (short names)
critical_nodes: []
# Example:
# critical_nodes:
# - "MHR"
# - "HPR"

View file

@ -16,7 +16,8 @@ from .cli import run_configurator
from .commands import CommandDispatcher
from .commands.dispatcher import create_dispatcher
from .commands.status import set_start_time
from .config import Config, load_config
from .config import Config
from .config_loader import load_config, get_config_dir_from_path
from .connector import MeshConnector, MeshMessage
from .context import MeshContext
from .history import ConversationHistory
@ -712,12 +713,21 @@ def main() -> None:
run_configurator(args.config_file)
return
# Load config
config = load_config(args.config_file)
# Load config - support both old (/data/config.yaml) and new (/data/config/) layouts
config_path = args.config_file
config_dir = get_config_dir_from_path(config_path)
# Check if config exists
if not args.config_file.exists():
logger.warning(f"Config file not found: {args.config_file}")
# Check for new multi-file layout first
if (config_dir / "config.yaml").exists():
logger.info(f"Loading config from multi-file layout: {config_dir}")
config = load_config(config_dir)
elif config_path.exists():
# Fall back to legacy single-file loading
logger.info(f"Loading legacy config: {config_path}")
from .config import load_config as legacy_load
config = legacy_load(config_path)
else:
logger.warning(f"Config not found at {config_path} or {config_dir}")
logger.info("Run 'meshai --config' to create one, or copy config.example.yaml")
sys.exit(1)