Vapora/provisioning/scripts/validate-config.nu
Jesús Pérez a395bd972f
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
mdBook Build & Deploy / Build mdBook (push) Has been cancelled
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
mdBook Build & Deploy / Documentation Quality Check (push) Has been cancelled
mdBook Build & Deploy / Deploy to GitHub Pages (push) Has been cancelled
mdBook Build & Deploy / Notification (push) Has been cancelled
chore: add cd/ci ops
2026-01-12 03:36:55 +00:00

339 lines
9.8 KiB
Plaintext
Executable File

#!/usr/bin/env nu
# VAPORA Configuration Validation Utility
# Comprehensive validation of Nickel and rendered configurations
# Version: 1.0.0
def main [
--config: string
--mode: string
] {
if ($config == null) and ($mode == null) {
print "VAPORA Configuration Validator"
print ""
print "Usage:"
print " Validate single config: nu validate-config.nu --config <path>"
print " Validate mode config: nu validate-config.nu --mode <solo|multiuser|enterprise>"
print " Validate all modes: nu validate-config.nu --mode all"
return
}
if ($config != null) {
validate-config-file $config
} else if ($mode == "all") {
["solo", "multiuser", "enterprise"] | each { |m| validate-mode $m }
} else {
validate-mode $mode
}
}
def validate-mode [mode: string] {
print $"🔍 Validating ($mode) mode configuration"
print ""
# Step 1: Export from Nickel
print "Step 1: Generating from Nickel..."
let config_file = ([$env.PWD, "..", "schemas", "platform", "configs", $"vapora-($mode).ncl"] | path join)
if not ($config_file | path exists) {
error make {msg: $"Config not found: ($config_file)"}
}
let result = do {
nickel export $config_file
} | complete
if $result.exit_code != 0 {
error make {msg: $"Nickel export failed: ($result.stderr)"}
}
let json_output = ($result.stdout | from json)
print " ✓ Nickel export successful"
# Step 2: Validate structure
print "Step 2: Validating structure..."
validate-structure $json_output
print " ✓ Structure valid"
# Step 3: Validate field ranges
print "Step 3: Validating field ranges..."
validate-ranges $json_output $mode
print " ✓ Field ranges valid"
# Step 4: Validate provider configuration
print "Step 4: Validating provider configuration..."
validate-providers $json_output $mode
print " ✓ Provider configuration valid"
# Step 5: Validate security settings
print "Step 5: Validating security settings..."
validate-security $json_output $mode
print " ✓ Security settings valid"
# Step 6: Consistency checks
print "Step 6: Checking consistency..."
validate-consistency $json_output $mode
print " ✓ Consistency checks passed"
print ""
print $"✅ ($mode) configuration valid"
print ""
}
def validate-config-file [config_path: string] {
print $"🔍 Validating: ($config_path)"
print ""
if not ($config_path | path exists) {
error make {msg: $"Config file not found: ($config_path)"}
}
# Determine file type
if ($config_path | str ends-with ".json") {
validate-json-file $config_path
} else if ($config_path | str ends-with ".ncl") {
validate-nickel-file $config_path
} else if ($config_path | str ends-with ".toml") {
validate-toml-file $config_path
} else if ($config_path | str ends-with ".yaml") or ($config_path | str ends-with ".yml") {
validate-yaml-file $config_path
} else {
error make {msg: "Unknown file type"}
}
}
def validate-structure [config: record] {
let required_fields = [
"deployment_mode"
"workspace_name"
"backend"
"agents"
"llm_router"
"frontend"
"database"
"nats"
"providers"
"monitoring"
"security"
"storage"
]
let missing = $required_fields | where { |field|
($config | get $field -o) == null
}
if ($missing | length) > 0 {
error make {msg: $"Missing required fields: ($missing | str join ', ')"}
}
# Validate nested structures
let backend_required = ["host", "port", "workers", "auth", "database"]
let backend_missing = $backend_required | where { |field|
($config.backend | get $field -i) == null
}
if ($backend_missing | length) > 0 {
error make {msg: $"Backend missing: ($backend_missing | str join ', ')"}
}
}
def validate-ranges [config: record, mode: string] {
let port_min = 1024
let port_max = 65535
# Validate ports
if ($config.backend.port < $port_min) or ($config.backend.port > $port_max) {
error make {msg: $"Invalid backend port: ($config.backend.port)"}
}
if ($config.agents.port < $port_min) or ($config.agents.port > $port_max) {
error make {msg: $"Invalid agents port: ($config.agents.port)"}
}
if ($config.llm_router.port < $port_min) or ($config.llm_router.port > $port_max) {
error make {msg: $"Invalid llm_router port: ($config.llm_router.port)"}
}
# Validate workers based on mode
let max_workers = match $mode {
"solo" => 4
"multiuser" => 16
"enterprise" => 32
_ => 4
}
if ($config.backend.workers < 1) or ($config.backend.workers > $max_workers) {
error make {msg: $"Invalid worker count: ($config.backend.workers)"}
}
# Validate pool sizes
if ($config.backend.database.pool_size < 1) or ($config.backend.database.pool_size > 500) {
error make {msg: $"Invalid pool size: ($config.backend.database.pool_size)"}
}
# Validate timeouts
if ($config.backend.request_timeout < 1000) or ($config.backend.request_timeout > 600000) {
error make {msg: $"Invalid request timeout: ($config.backend.request_timeout)"}
}
}
def validate-providers [config: record, mode: string] {
let provider_count = [
$config.providers.claude_enabled
$config.providers.openai_enabled
$config.providers.gemini_enabled
$config.providers.ollama_enabled
] | where { |p| $p } | length
if $provider_count == 0 {
error make {msg: "At least one LLM provider must be enabled"}
}
# Validate Ollama URL if enabled
if $config.providers.ollama_enabled {
if ($config.providers.ollama_url | is-empty) {
error make {msg: "Ollama enabled but URL not set"}
}
if not ($config.providers.ollama_url | str starts-with "http") {
error make {msg: "Invalid Ollama URL format"}
}
}
}
def validate-security [config: record, mode: string] {
# JWT secret warning (but allow empty for local dev)
if ($config.backend.auth.jwt_secret | is-empty) and ($mode != "solo") {
print " ⚠️ Warning: JWT secret is empty (non-solo mode)"
}
# TLS validation
if $config.security.tls_enabled {
if ($config.security.tls_cert_path | is-empty) {
error make {msg: "TLS enabled but cert path not set"}
}
if ($config.security.tls_key_path | is-empty) {
error make {msg: "TLS enabled but key path not set"}
}
}
# MFA validation
if $config.backend.auth.mfa_enabled and ($config.backend.auth.method == "jwt") {
print " ⚠️ Warning: MFA with JWT only (consider OAuth2)"
}
}
def validate-consistency [config: record, mode: string] {
# Deployment mode consistency
if $config.deployment_mode != $mode {
error make {msg: $"Deployment mode mismatch: expected ($mode), got ($config.deployment_mode)"}
}
# Database URL should match mode expectations
if $mode == "solo" {
if not ($config.database.url | str contains "localhost") and not ($config.database.url | str contains "127.0.0.1") {
print " ⚠️ Warning: Solo mode with remote database"
}
} else if $mode == "multiuser" {
if not ($config.agents.nats.enabled) {
print " ⚠️ Warning: Multiuser mode without NATS"
}
}
# Enterprise mode should have high availability enabled
if $mode == "enterprise" {
if not ($config.agents.nats.enabled) {
error make {msg: "Enterprise mode requires NATS enabled"}
}
if not ($config.monitoring.prometheus_enabled) {
print " ⚠️ Warning: Enterprise mode without Prometheus"
}
}
# API URL should be set for non-localhost deployments
if ($config.backend.host != "127.0.0.1") {
if ($config.frontend.api_url == null) or ($config.frontend.api_url | is-empty) {
print " ⚠️ Warning: No API URL set for non-localhost backend"
}
}
}
def validate-json-file [path: string] {
print "Validating JSON file..."
let result = do {
open $path
} | complete
if $result.exit_code != 0 {
error make {msg: "Failed to parse JSON"}
}
let config = ($result.stdout | from json)
print " ✓ Valid JSON"
validate-structure $config
print " ✓ Structure valid"
print ""
print "✅ JSON file valid"
}
def validate-nickel-file [path: string] {
print "Validating Nickel file..."
# Typecheck
let typecheck_result = do {
nickel typecheck $path
} | complete
if $typecheck_result.exit_code != 0 {
error make {msg: $"Typecheck failed: ($typecheck_result.stderr)"}
}
print " ✓ Typecheck passed"
# Export
let export_result = do {
nickel export $path
} | complete
if $export_result.exit_code != 0 {
error make {msg: $"Export failed: ($export_result.stderr)"}
}
print " ✓ Export successful"
print ""
print "✅ Nickel file valid"
}
def validate-yaml-file [path: string] {
print "Validating YAML file..."
let result = do {
yq eval '.' $path
} | complete
if $result.exit_code != 0 {
error make {msg: "Invalid YAML syntax"}
}
print " ✓ Valid YAML"
print ""
print "✅ YAML file valid"
}
def validate-toml-file [path: string] {
print "Validating TOML file..."
# Basic check: should parse and have [sections]
let content = (open $path)
if not ($content | str contains "[") {
error make {msg: "Invalid TOML: no sections found"}
}
print " ✓ Valid TOML structure"
print ""
print "✅ TOML file valid"
}
# Run main function
main