Issue: #599 Author: mrveiss Copyright: © 2025 mrveiss Date: 2025-12-25 Status: Architecture Design
This document defines the Single Source of Truth (SSOT) configuration architecture for AutoBot. After comprehensive analysis of the current 19+ configuration files across Python backend, Vue/TypeScript frontend, shell scripts, and infrastructure components, this design provides a unified approach that eliminates redundancy while maintaining type safety, environment awareness, and cross-language compatibility.
| Category | File/Module | Lines | Purpose | Problems |
|---|---|---|---|---|
| Backend - Config Package | ||||
src/config/manager.py |
~96 | Core UnifiedConfigManager | Good architecture | |
src/config/settings.py |
~35 | Pydantic settings | Separate from main | |
src/config/defaults.py |
~183 | Default config generation | Hardcoded fallbacks | |
src/config/loader.py |
~192 | Config loading/merging | Complex precedence | |
src/config/sync_ops.py |
~140 | Synchronous operations | Duplicates async | |
src/config/async_ops.py |
~220 | Async operations | Complex | |
src/config/model_config.py |
~120 | LLM model management | Overlaps constants | |
src/config/service_config.py |
~240 | Service host/port config | Overlaps network_constants | |
src/config/timeout_config.py |
~140 | Timeout management | Scattered across files | |
| Backend - Constants | ||||
src/constants/network_constants.py |
~380 | IPs, ports, URLs | CRITICAL: Hardcoded fallbacks | |
src/constants/model_constants.py |
~189 | LLM model names | Overlaps config | |
src/constants/path_constants.py |
~99 | Directory paths | Good isolation | |
src/constants/redis_constants.py |
~? | Redis-specific | Overlaps network | |
src/constants/security_constants.py |
~? | Security settings | ||
src/constants/threshold_constants.py |
~? | Thresholds | ||
| Backend - Models | ||||
backend/models/settings.py |
~389 | Pydantic AutoBotSettings | Separate validation | |
backend/services/config_service.py |
~502 | API layer for config CRUD | Wrapper complexity | |
| Frontend | ||||
autobot-frontend/src/config/defaults.js |
~200 | DEFAULT_CONFIG with fallbacks | Hardcoded IPs | |
autobot-frontend/src/config/ServiceDiscovery.js |
~488 | Dynamic URL resolution | Complex fallback logic | |
autobot-frontend/src/constants/network.ts |
~204 | TypeScript network constants | Mirrors Python | |
vite.config.ts |
~280 | Vite configuration | Hardcoded NETWORK_DEFAULTS | |
| Environment Files | ||||
.env / .env.example |
~260 | Environment variables | Multiple sources | |
autobot-frontend/.env.example |
~20 | Frontend env vars | Separate from backend | |
config/config.yaml |
~652 | Runtime configuration | Deeply nested, duplicated |
- No Single Authority: 19+ files contain configuration logic with unclear precedence
- Hardcoded Fallbacks: IP addresses (172.16.168.x) hardcoded in 6+ locations
- Cross-Language Duplication: Python
NetworkConstantsduplicated in TypeScript - Complex Precedence:
config.yaml->settings.json-> environment variables - Nested Duplication:
config.yamlhas 5+ levels of nestedunifiedblocks (bug) - Environment File Fragmentation:
.envandautobot-frontend/.envnot synchronized - Type Safety Gap: Frontend defaults.js lacks TypeScript types
- Hot Reload Complexity: Multiple file watchers, unclear which triggers updates
| Category | Example Values | Current Source(s) |
|---|---|---|
| Network Infrastructure | VM IPs, ports | network_constants.py, defaults.js, .env |
| Service URLs | API endpoints, WebSocket | Built dynamically from IPs/ports |
| LLM Configuration | Model names, endpoints, providers | model_constants.py, config.yaml |
| Redis Configuration | Host, port, databases | network_constants.py, redis_constants.py |
| Timeouts | HTTP, LLM, Redis operation timeouts | config.yaml, timeout_config.py |
| Paths | Directories, log files | path_constants.py, .env |
| UI Settings | Theme, language, animations | config.yaml, frontend store |
| Feature Flags | WebSockets, VNC, debug mode | config.yaml, .env |
| Security | Auth, encryption, session timeout | config.yaml, security_constants.py |
Concept: Single JSON Schema file defines ALL configuration. Code generators produce:
- Python Pydantic models
- TypeScript interfaces
- Default YAML templates
- Documentation
master-config-schema.json
|
+-> generate-python.py -> src/config/generated/models.py
+-> generate-typescript.ts -> autobot-frontend/src/config/generated/types.ts
+-> generate-yaml.py -> config/config.yaml.template
+-> generate-docs.py -> docs/configuration-reference.md
Pros:
- True single source of truth
- Automatic cross-language consistency
- Self-documenting schema
- Validation baked in
Cons:
- Build step complexity
- Learning curve for JSON Schema
- Generated code can be hard to debug
- Runtime schema loading adds complexity
Complexity: High Risk: Medium (well-established pattern)
Concept: Python remains the authority. Backend exposes /api/config/schema endpoint that frontend consumes at build/runtime.
Python (Authority) TypeScript (Consumer)
---------------- -------------------
src/config/schema.py -> GET /api/config/schema
- Pydantic models -> generate types.ts (build time)
- Validation rules -> fetch config (runtime)
- Defaults -> type-safe consumption
Implementation:
# Backend: src/config/schema.py
class AutoBotConfigSchema(BaseModel):
"""Master configuration schema - exports to TypeScript"""
network: NetworkSchema
llm: LLMSchema
redis: RedisSchema
timeouts: TimeoutSchema
ui: UISchema
class Config:
json_schema_extra = {
"title": "AutoBot Configuration",
"description": "Single source of truth for all AutoBot configuration"
}
def export_typescript_types(self) -> str:
"""Generate TypeScript interface from Pydantic model"""
# Uses pydantic-to-typescript or custom generatorPros:
- Python ecosystem leveraged (Pydantic excellent)
- No build step for Python code
- Runtime schema available for UI
- Incremental migration possible
Cons:
- TypeScript depends on Python for types
- Build-time code generation still needed
- API must be available for frontend build
Complexity: Medium Risk: Low (builds on existing Pydantic)
Concept: Single canonical .env file as the true source. All other config files are either:
- Default providers (read-only, provide fallbacks)
- Schema validators (validate .env values)
- Derived consumers (build values from .env)
.env (SSOT)
|
+-------------+-------------+
| | |
Python Loader TypeScript Shell
| Loader Scripts
v v v
Pydantic Models Vite Env Bash vars
+ NetworkConstants + network.ts
Key Principles:
- All configuration values defined in
.envwithAUTOBOT_prefix - Frozen defaults in code are only used if
.envmissing (emergency fallback) - No hardcoded values in business logic - always reference config
- Runtime config.yaml for mutable state (user preferences, not infrastructure)
Implementation Layers:
Layer 1: .env (Required Configuration)
-----------------------------------------
AUTOBOT_MAIN_MACHINE_IP=<backend-ip>
AUTOBOT_FRONTEND_VM_IP=<frontend-ip>
AUTOBOT_REDIS_VM_IP=<database-ip>
AUTOBOT_BACKEND_PORT=8001
AUTOBOT_DEFAULT_LLM_MODEL=qwen3.5:9b
Layer 2: Frozen Code Defaults (Emergency Fallback)
--------------------------------------------------
# Only used if .env completely missing
# NEVER for production
Layer 3: Runtime YAML (User Preferences)
----------------------------------------
# Mutable settings like UI theme, not infrastructure
config/user-preferences.yaml
Layer 4: Language-Specific Loaders
----------------------------------
# Python: src/config/env_loader.py
# TypeScript: src/config/env-loader.ts
# Both read same .env, validate, provide typed access
Pros:
- Simple mental model (env vars = config)
- Standard 12-factor app pattern
- No code generation required
- Docker/K8s friendly
- Easy to override per environment
Cons:
- Flat namespace (mitigated by prefix convention)
- Secrets need special handling
- Complex nested config awkward in env vars
Complexity: Low Risk: Very Low (proven pattern)
After analyzing the current state and considering AutoBot's distributed 6-VM architecture:
-
Simplicity Wins: The current 19-file fragmentation is a maintenance nightmare. A single
.envfile with clear prefixes is immediately understandable. -
Docker/VM Friendly: Each VM can have its own
.envwith overrides. This matches the distributed deployment model. -
No Build Step: Approach A requires code generation. Approach C works immediately.
-
Incremental Migration: Can migrate file-by-file without breaking changes.
-
Existing Foundation: The
.env.examplealready has most configuration defined.
The canonical .env structure with all configuration values:
# =============================================================================
# AUTOBOT MASTER CONFIGURATION - SINGLE SOURCE OF TRUTH
# =============================================================================
# Version: 1.0.0
#
# This file defines ALL configuration for AutoBot.
# All other configuration files derive from these values.
#
# Naming Convention: AUTOBOT_{CATEGORY}_{NAME}
# Categories: NETWORK, VM, LLM, REDIS, TIMEOUT, PATH, UI, FEATURE, SECURITY
# =============================================================================
# -----------------------------------------------------------------------------
# NETWORK INFRASTRUCTURE (6-VM Architecture)
# -----------------------------------------------------------------------------
# Main Machine (WSL) - Backend API + VNC Desktop
AUTOBOT_VM_MAIN_IP=<backend-ip>
# VM1 Frontend - Web interface (SINGLE FRONTEND SERVER)
AUTOBOT_VM_FRONTEND_IP=<frontend-ip>
# VM2 NPU Worker - Hardware AI acceleration
AUTOBOT_VM_NPU_IP=<npu-ip>
# VM3 Redis - Data layer
AUTOBOT_VM_REDIS_IP=<database-ip>
# VM4 AI Stack - AI processing
AUTOBOT_VM_AISTACK_IP=<aiml-ip>
# VM5 Browser - Web automation (Playwright)
AUTOBOT_VM_BROWSER_IP=<browser-ip>
# -----------------------------------------------------------------------------
# SERVICE PORTS
# -----------------------------------------------------------------------------
AUTOBOT_PORT_BACKEND=8001
AUTOBOT_PORT_FRONTEND=5173
AUTOBOT_PORT_REDIS=6379
AUTOBOT_PORT_OLLAMA=11434
AUTOBOT_PORT_VNC=6080
AUTOBOT_PORT_BROWSER=3000
AUTOBOT_PORT_AISTACK=8080
AUTOBOT_PORT_NPU=8081
AUTOBOT_PORT_NPU_WINDOWS=8082
AUTOBOT_PORT_PROMETHEUS=9090
AUTOBOT_PORT_GRAFANA=3000
# -----------------------------------------------------------------------------
# LLM CONFIGURATION
# -----------------------------------------------------------------------------
AUTOBOT_LLM_DEFAULT_MODEL=qwen3.5:9b
AUTOBOT_LLM_EMBEDDING_MODEL=nomic-embed-text:latest
AUTOBOT_LLM_CLASSIFICATION_MODEL=gemma2:2b
AUTOBOT_LLM_PROVIDER=ollama
AUTOBOT_LLM_TIMEOUT=120
# Optional cloud providers
AUTOBOT_LLM_OPENAI_KEY=
AUTOBOT_LLM_OPENAI_MODEL=gpt-4
AUTOBOT_LLM_ANTHROPIC_KEY=
AUTOBOT_LLM_ANTHROPIC_MODEL=claude-3-5-sonnet-20241022
# -----------------------------------------------------------------------------
# REDIS CONFIGURATION
# -----------------------------------------------------------------------------
AUTOBOT_REDIS_PASSWORD=
AUTOBOT_REDIS_DB_MAIN=0
AUTOBOT_REDIS_DB_KNOWLEDGE=1
AUTOBOT_REDIS_DB_CACHE=2
AUTOBOT_REDIS_DB_SESSIONS=3
AUTOBOT_REDIS_DB_METRICS=4
AUTOBOT_REDIS_DB_VECTORS=8
# -----------------------------------------------------------------------------
# TIMEOUTS (in seconds)
# -----------------------------------------------------------------------------
AUTOBOT_TIMEOUT_HTTP=30
AUTOBOT_TIMEOUT_HTTP_LONG=120
AUTOBOT_TIMEOUT_WEBSOCKET=300
AUTOBOT_TIMEOUT_REDIS_CONNECT=2
AUTOBOT_TIMEOUT_REDIS_OP=1
AUTOBOT_TIMEOUT_LLM_DEFAULT=120
AUTOBOT_TIMEOUT_LLM_REASONING=300
AUTOBOT_TIMEOUT_HEALTH_CHECK=3
# -----------------------------------------------------------------------------
# DEPLOYMENT MODE
# -----------------------------------------------------------------------------
AUTOBOT_DEPLOYMENT_MODE=distributed
AUTOBOT_ENVIRONMENT=development
AUTOBOT_DEBUG=false
# -----------------------------------------------------------------------------
# FEATURE FLAGS
# -----------------------------------------------------------------------------
AUTOBOT_FEATURE_WEBSOCKETS=true
AUTOBOT_FEATURE_VNC=true
AUTOBOT_FEATURE_NPU=true
AUTOBOT_FEATURE_DEBUG=false
# -----------------------------------------------------------------------------
# SECURITY
# -----------------------------------------------------------------------------
AUTOBOT_SECURITY_AUTH_ENABLED=false
AUTOBOT_SECURITY_SESSION_TIMEOUT=30
AUTOBOT_SECURITY_ENCRYPTION=false# src/config/ssot_config.py
"""
SSOT Configuration Loader for Python
Single source of truth - reads from .env
"""
from functools import lru_cache
from pathlib import Path
from typing import Optional
from pydantic import Field
from pydantic_settings import BaseSettings
class VMConfig(BaseSettings):
"""VM IP addresses"""
main: str = Field(alias="AUTOBOT_VM_MAIN_IP", default="<backend-ip>")
frontend: str = Field(alias="AUTOBOT_VM_FRONTEND_IP", default="<frontend-ip>")
npu: str = Field(alias="AUTOBOT_VM_NPU_IP", default="<npu-ip>")
redis: str = Field(alias="AUTOBOT_VM_REDIS_IP", default="<database-ip>")
aistack: str = Field(alias="AUTOBOT_VM_AISTACK_IP", default="<aiml-ip>")
browser: str = Field(alias="AUTOBOT_VM_BROWSER_IP", default="<browser-ip>")
class PortConfig(BaseSettings):
"""Service ports"""
backend: int = Field(alias="AUTOBOT_PORT_BACKEND", default=8001)
frontend: int = Field(alias="AUTOBOT_PORT_FRONTEND", default=5173)
redis: int = Field(alias="AUTOBOT_PORT_REDIS", default=6379)
ollama: int = Field(alias="AUTOBOT_PORT_OLLAMA", default=11434)
vnc: int = Field(alias="AUTOBOT_PORT_VNC", default=6080)
browser: int = Field(alias="AUTOBOT_PORT_BROWSER", default=3000)
aistack: int = Field(alias="AUTOBOT_PORT_AISTACK", default=8080)
npu: int = Field(alias="AUTOBOT_PORT_NPU", default=8081)
class LLMConfig(BaseSettings):
"""LLM configuration"""
default_model: str = Field(alias="AUTOBOT_LLM_DEFAULT_MODEL", default="qwen3.5:9b")
embedding_model: str = Field(alias="AUTOBOT_LLM_EMBEDDING_MODEL", default="nomic-embed-text:latest")
provider: str = Field(alias="AUTOBOT_LLM_PROVIDER", default="ollama")
timeout: int = Field(alias="AUTOBOT_LLM_TIMEOUT", default=120)
class TimeoutConfig(BaseSettings):
"""Timeout configuration"""
http: int = Field(alias="AUTOBOT_TIMEOUT_HTTP", default=30)
http_long: int = Field(alias="AUTOBOT_TIMEOUT_HTTP_LONG", default=120)
websocket: int = Field(alias="AUTOBOT_TIMEOUT_WEBSOCKET", default=300)
redis_connect: float = Field(alias="AUTOBOT_TIMEOUT_REDIS_CONNECT", default=2.0)
redis_op: float = Field(alias="AUTOBOT_TIMEOUT_REDIS_OP", default=1.0)
llm_default: int = Field(alias="AUTOBOT_TIMEOUT_LLM_DEFAULT", default=120)
class FeatureConfig(BaseSettings):
"""Feature flags"""
websockets: bool = Field(alias="AUTOBOT_FEATURE_WEBSOCKETS", default=True)
vnc: bool = Field(alias="AUTOBOT_FEATURE_VNC", default=True)
npu: bool = Field(alias="AUTOBOT_FEATURE_NPU", default=True)
debug: bool = Field(alias="AUTOBOT_FEATURE_DEBUG", default=False)
class AutoBotConfig(BaseSettings):
"""Master configuration - SINGLE SOURCE OF TRUTH"""
vm: VMConfig = Field(default_factory=VMConfig)
port: PortConfig = Field(default_factory=PortConfig)
llm: LLMConfig = Field(default_factory=LLMConfig)
timeout: TimeoutConfig = Field(default_factory=TimeoutConfig)
feature: FeatureConfig = Field(default_factory=FeatureConfig)
deployment_mode: str = Field(alias="AUTOBOT_DEPLOYMENT_MODE", default="distributed")
environment: str = Field(alias="AUTOBOT_ENVIRONMENT", default="development")
debug: bool = Field(alias="AUTOBOT_DEBUG", default=False)
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
extra = "ignore"
# Computed properties for backward compatibility
@property
def backend_url(self) -> str:
return f"http://{self.vm.main}:{self.port.backend}"
@property
def redis_url(self) -> str:
return f"redis://{self.vm.redis}:{self.port.redis}"
@property
def ollama_url(self) -> str:
return f"http://{self.vm.aistack}:{self.port.ollama}"
@lru_cache()
def get_config() -> AutoBotConfig:
"""Get singleton configuration instance"""
return AutoBotConfig()
# Convenience exports for backward compatibility
config = get_config()// autobot-frontend/src/config/ssot-config.ts
/**
* SSOT Configuration Loader for TypeScript
* Single source of truth - reads from Vite environment
*/
interface VMConfig {
main: string;
frontend: string;
npu: string;
redis: string;
aistack: string;
browser: string;
}
interface PortConfig {
backend: number;
frontend: number;
redis: number;
ollama: number;
vnc: number;
browser: number;
aistack: number;
npu: number;
}
interface LLMConfig {
defaultModel: string;
embeddingModel: string;
provider: string;
timeout: number;
}
interface TimeoutConfig {
http: number;
httpLong: number;
websocket: number;
redisConnect: number;
redisOp: number;
llmDefault: number;
}
interface FeatureConfig {
websockets: boolean;
vnc: boolean;
npu: boolean;
debug: boolean;
}
export interface AutoBotConfig {
vm: VMConfig;
port: PortConfig;
llm: LLMConfig;
timeout: TimeoutConfig;
feature: FeatureConfig;
deploymentMode: string;
environment: string;
debug: boolean;
// Computed URLs
backendUrl: string;
redisUrl: string;
ollamaUrl: string;
websocketUrl: string;
}
// Environment variable helper
function getEnv(key: string, defaultValue: string): string {
return import.meta.env[key] ?? defaultValue;
}
function getEnvNumber(key: string, defaultValue: number): number {
const value = import.meta.env[key];
return value ? parseInt(value, 10) : defaultValue;
}
function getEnvBoolean(key: string, defaultValue: boolean): boolean {
const value = import.meta.env[key];
if (value === undefined) return defaultValue;
return value === 'true' || value === '1';
}
// Singleton configuration
let _config: AutoBotConfig | null = null;
export function getConfig(): AutoBotConfig {
if (_config) return _config;
const vm: VMConfig = {
main: getEnv('VITE_VM_MAIN_IP', '<backend-ip>'),
frontend: getEnv('VITE_VM_FRONTEND_IP', '<frontend-ip>'),
npu: getEnv('VITE_VM_NPU_IP', '<npu-ip>'),
redis: getEnv('VITE_VM_REDIS_IP', '<database-ip>'),
aistack: getEnv('VITE_VM_AISTACK_IP', '<aiml-ip>'),
browser: getEnv('VITE_VM_BROWSER_IP', '<browser-ip>'),
};
const port: PortConfig = {
backend: getEnvNumber('VITE_PORT_BACKEND', 8001),
frontend: getEnvNumber('VITE_PORT_FRONTEND', 5173),
redis: getEnvNumber('VITE_PORT_REDIS', 6379),
ollama: getEnvNumber('VITE_PORT_OLLAMA', 11434),
vnc: getEnvNumber('VITE_PORT_VNC', 6080),
browser: getEnvNumber('VITE_PORT_BROWSER', 3000),
aistack: getEnvNumber('VITE_PORT_AISTACK', 8080),
npu: getEnvNumber('VITE_PORT_NPU', 8081),
};
const llm: LLMConfig = {
defaultModel: getEnv('VITE_LLM_DEFAULT_MODEL', 'qwen3.5:9b'),
embeddingModel: getEnv('VITE_LLM_EMBEDDING_MODEL', 'nomic-embed-text:latest'),
provider: getEnv('VITE_LLM_PROVIDER', 'ollama'),
timeout: getEnvNumber('VITE_LLM_TIMEOUT', 120),
};
const timeout: TimeoutConfig = {
http: getEnvNumber('VITE_TIMEOUT_HTTP', 30000),
httpLong: getEnvNumber('VITE_TIMEOUT_HTTP_LONG', 120000),
websocket: getEnvNumber('VITE_TIMEOUT_WEBSOCKET', 300000),
redisConnect: getEnvNumber('VITE_TIMEOUT_REDIS_CONNECT', 2000),
redisOp: getEnvNumber('VITE_TIMEOUT_REDIS_OP', 1000),
llmDefault: getEnvNumber('VITE_TIMEOUT_LLM_DEFAULT', 120000),
};
const feature: FeatureConfig = {
websockets: getEnvBoolean('VITE_FEATURE_WEBSOCKETS', true),
vnc: getEnvBoolean('VITE_FEATURE_VNC', true),
npu: getEnvBoolean('VITE_FEATURE_NPU', true),
debug: getEnvBoolean('VITE_FEATURE_DEBUG', false),
};
_config = {
vm,
port,
llm,
timeout,
feature,
deploymentMode: getEnv('VITE_DEPLOYMENT_MODE', 'distributed'),
environment: getEnv('VITE_ENVIRONMENT', 'development'),
debug: getEnvBoolean('VITE_DEBUG', false),
// Computed URLs
get backendUrl() { return `http://${vm.main}:${port.backend}`; },
get redisUrl() { return `redis://${vm.redis}:${port.redis}`; },
get ollamaUrl() { return `http://${vm.aistack}:${port.ollama}`; },
get websocketUrl() { return `ws://${vm.main}:${port.backend}/ws`; },
};
return _config;
}
// Default export for convenience
export default getConfig();Create a shared .env generation script:
#!/bin/bash
# scripts/sync-env.sh
# Generates both backend and frontend .env files from master
MASTER_ENV=".env"
FRONTEND_ENV="autobot-frontend/.env"
if [[ ! -f "$MASTER_ENV" ]]; then
echo "Error: Master .env not found. Copy from .env.example"
exit 1
fi
# Generate frontend .env with VITE_ prefixes
echo "# AUTO-GENERATED from $MASTER_ENV - DO NOT EDIT DIRECTLY" > "$FRONTEND_ENV"
echo "# Run: ./scripts/sync-env.sh to regenerate" >> "$FRONTEND_ENV"
echo "" >> "$FRONTEND_ENV"
# Map AUTOBOT_ variables to VITE_ variables
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ "$key" =~ ^#.*$ ]] && continue
[[ -z "$key" ]] && continue
# Convert AUTOBOT_VM_ to VITE_VM_
if [[ "$key" == AUTOBOT_VM_* ]]; then
new_key="${key/AUTOBOT_/VITE_}"
echo "$new_key=$value" >> "$FRONTEND_ENV"
# Convert AUTOBOT_PORT_ to VITE_PORT_
elif [[ "$key" == AUTOBOT_PORT_* ]]; then
new_key="${key/AUTOBOT_/VITE_}"
echo "$new_key=$value" >> "$FRONTEND_ENV"
# Convert AUTOBOT_LLM_ to VITE_LLM_
elif [[ "$key" == AUTOBOT_LLM_* ]]; then
new_key="${key/AUTOBOT_/VITE_}"
echo "$new_key=$value" >> "$FRONTEND_ENV"
# Convert AUTOBOT_TIMEOUT_ to VITE_TIMEOUT_
elif [[ "$key" == AUTOBOT_TIMEOUT_* ]]; then
new_key="${key/AUTOBOT_/VITE_}"
echo "$new_key=$value" >> "$FRONTEND_ENV"
# Convert AUTOBOT_FEATURE_ to VITE_FEATURE_
elif [[ "$key" == AUTOBOT_FEATURE_* ]]; then
new_key="${key/AUTOBOT_/VITE_}"
echo "$new_key=$value" >> "$FRONTEND_ENV"
# Convert deployment mode
elif [[ "$key" == "AUTOBOT_DEPLOYMENT_MODE" ]]; then
echo "VITE_DEPLOYMENT_MODE=$value" >> "$FRONTEND_ENV"
elif [[ "$key" == "AUTOBOT_ENVIRONMENT" ]]; then
echo "VITE_ENVIRONMENT=$value" >> "$FRONTEND_ENV"
elif [[ "$key" == "AUTOBOT_DEBUG" ]]; then
echo "VITE_DEBUG=$value" >> "$FRONTEND_ENV"
fi
done < "$MASTER_ENV"
echo "Frontend .env synchronized from master .env"Shell scripts in scripts/ must also consume configuration from the SSOT .env file:
#!/bin/bash
# scripts/example-script.sh
# Example of SSOT-compliant shell script
# Load configuration from master .env
set -a # Automatically export all variables
source "$(dirname "$0")/../.env"
set +a
# Now use AUTOBOT_ prefixed variables
echo "Backend URL: http://${AUTOBOT_VM_MAIN_IP}:${AUTOBOT_PORT_BACKEND}"
echo "Redis URL: redis://${AUTOBOT_VM_REDIS_IP}:${AUTOBOT_PORT_REDIS}"
echo "Default LLM: ${AUTOBOT_LLM_DEFAULT_MODEL}"
# Example: Health check using SSOT config
curl -s "http://${AUTOBOT_VM_MAIN_IP}:${AUTOBOT_PORT_BACKEND}/api/health"Shell Script Standards:
- Always source
.envat script start - Never hardcode IPs, ports, or model names
- Use AUTOBOT_ prefix for all configuration variables
- Provide fallbacks only for non-critical scripts (testing/development)
Scripts Requiring Migration:
| Script | Current Status | Required Changes |
|---|---|---|
run_autobot.sh |
Deprecated (#863) | Replaced by systemd + scripts/start-services.sh |
setup.sh |
Hardcoded | Source .env for all network config |
scripts/utilities/sync-to-vm.sh |
Hardcoded IPs | Use AUTOBOT_VM_* variables |
scripts/utilities/sync-frontend.sh |
Hardcoded | Use AUTOBOT_VM_FRONTEND_IP |
All configuration-related documentation must be updated during SSOT migration:
Documents to Update:
| Document | Update Required |
|---|---|
CLAUDE.md |
Update Redis/Network sections to reference SSOT |
docs/developer/HARDCODING_PREVENTION.md |
Add SSOT config pattern as canonical |
docs/developer/REDIS_CLIENT_USAGE.md |
Reference SSOT for connection strings |
docs/developer/INFRASTRUCTURE_DEPLOYMENT.md |
Update VM IP references |
.env.example |
Complete rewrite with SSOT structure |
autobot-frontend/.env.example |
Auto-generated from master |
docs/api/COMPREHENSIVE_API_DOCUMENTATION.md |
Add /api/config endpoints |
New Documentation Required:
docs/developer/SSOT_CONFIG_GUIDE.md- Developer guide for using SSOT configdocs/developer/CONFIG_MIGRATION_CHECKLIST.md- Checklist for migrating existing code
Objective: Create new SSOT infrastructure without breaking existing code.
Tasks:
- Create
src/config/ssot_config.pywith Pydantic models - Create
autobot-frontend/src/config/ssot-config.tswith TypeScript types - Create
scripts/sync-env.shfor environment synchronization - Update
.env.examplewith new canonical structure - Add validation tests for both loaders
Files to Create:
src/config/ssot_config.pyautobot-frontend/src/config/ssot-config.tsscripts/sync-env.sh
Backward Compatibility: Old config modules remain functional.
Objective: Migrate Python code to use new SSOT config.
Tasks:
- Update
network_constants.pyto read from SSOT config - Update
model_constants.pyto read from SSOT config - Update
UnifiedConfigManagerto use SSOT as base - Migrate
config_service.pyto wrap SSOT - Add deprecation warnings to old accessors
Migration Pattern:
# Before
from src.constants.network_constants import NetworkConstants
host = NetworkConstants.REDIS_VM_IP
# After
from src.config.ssot_config import config
host = config.vm.redisObjective: Migrate TypeScript/Vue code to use new SSOT config.
Tasks:
- Update
defaults.jsto import fromssot-config.ts - Update
network.tsto use SSOT config - Update
ServiceDiscovery.jsto use SSOT config - Update
vite.config.tsto use SSOT config - Remove hardcoded fallbacks
Migration Pattern:
// Before
import { DEFAULT_CONFIG } from './defaults.js';
const host = DEFAULT_CONFIG.network.backend.host;
// After
import { config } from './ssot-config';
const host = config.vm.main;Objective: Remove deprecated code and finalize architecture.
Tasks:
- Remove deprecated config modules
- Consolidate
config.yamlto user preferences only - Update all documentation
- Add pre-commit validation for hardcoded values
- Create migration guide
Files to Deprecate:
autobot-frontend/src/config/defaults.js(merge into ssot-config.ts)vite.config.tsNETWORK_DEFAULTS block
| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| Breaking existing code during migration | High | Medium | Phased migration with backward compatibility shims |
| Frontend build fails if .env missing | High | Low | Fallback defaults in TypeScript loader |
| Environment variable sprawl | Medium | Medium | Clear naming convention, documentation |
| Secret exposure in .env | High | Low | Separate .env.secrets for sensitive values |
| VM IP changes break config | Medium | Medium | Service discovery for production |
- File Count: Reduce from 19+ config files to 5 canonical files
- Hardcoded Values: Zero hardcoded IPs/ports in business logic
- Cross-Language Consistency: 100% parity between Python and TypeScript config
- Hot Reload: Single file change triggers config update
- Onboarding Time: New developer can understand config in < 30 minutes
- Test Coverage: 90%+ coverage on config loaders
├── .env # MASTER CONFIG - SINGLE SOURCE OF TRUTH
├── .env.example # Template with all values documented
├── .env.secrets.example # Template for secrets (gitignored)
│
├── config/
│ ├── user-preferences.yaml # Mutable user settings (theme, etc.)
│ └── config.yaml # DEPRECATED - redirect to .env
│
├── src/config/
│ ├── ssot_config.py # NEW: Python SSOT loader
│ ├── manager.py # UPDATED: Uses SSOT as base
│ ├── compat.py # UPDATED: Backward compatibility shims
│ └── ... (other modules - thin wrappers)
│
├── src/constants/
│ ├── network_constants.py # UPDATED: Reads from SSOT config
│ ├── model_constants.py # UPDATED: Reads from SSOT config
│ └── ... (other constants - derive from SSOT)
│
├── autobot-frontend/
│ ├── .env # AUTO-GENERATED from master .env
│ └── src/config/
│ ├── ssot-config.ts # NEW: TypeScript SSOT loader
│ ├── defaults.js # DEPRECATED - redirect to ssot-config
│ └── ServiceDiscovery.js # UPDATED: Uses SSOT config
│
└── scripts/
└── sync-env.sh # NEW: Syncs .env to frontend
The Environment-Centric approach (Approach C) provides the optimal balance of simplicity, maintainability, and compatibility for AutoBot's configuration needs. By establishing .env as the single source of truth with typed loaders in both Python and TypeScript, we eliminate the current fragmentation while maintaining type safety and environment awareness.
The phased implementation strategy ensures zero disruption to the running system while progressively consolidating configuration management. The 8-week timeline allows for thorough testing and validation at each phase.
Document Status: Ready for Review Next Steps: Create GitHub issue for Phase 1 implementation