/** * Module debugging commands * Show detailed module information including auto-derived values */ import { eq } from 'drizzle-orm'; import { getDb } from '../../db/client'; import { type NetworkZone, modules } from '../../db/schema'; import { type ModuleManifest, getSingularSystemSpec } from '../../manifest/schema'; import { buildResolutionContext } from '../../variables/context'; import { getArg, validateRequiredArgs } from '../parser'; import type { CommandResult } from '../types'; /** * Handle module show-config command * Shows ALL configuration including auto-derived values * * Usage: celilo module show-config * * @param args - Command arguments * @returns Command result */ export async function handleModuleShowConfig(args: string[]): Promise { const error = validateRequiredArgs(args, 1); if (error) { return { success: false, error: `${error}\n\nUsage: celilo module show-config `, }; } const moduleId = getArg(args, 0); if (!moduleId) { return { success: false, error: 'Module ID is required', }; } const db = getDb(); // Check if module exists const module = db.select().from(modules).where(eq(modules.id, moduleId)).get(); if (!module) { return { success: false, error: `Module not found: ${moduleId}`, }; } try { // Build resolution context to get ALL config (including auto-derived) const context = await buildResolutionContext(moduleId, db); const lines = [`Complete configuration for ${moduleId}:`, '']; // Group config by source const userConfig: string[] = []; const autoConfig: string[] = []; for (const [key, value] of Object.entries(context.selfConfig)) { // Skip internal/derived keys that aren't useful for display if (key.startsWith('requires.system.')) continue; const line = ` ${key} = ${value}`; // Categorize: inventory.* and some others are auto-derived if ( key.startsWith('inventory.') || key === 'vmid' || key === 'target_ip' || key === 'gateway' || key === 'vlan' || key === 'subnet' || key === 'bridge' ) { autoConfig.push(line); } else { userConfig.push(line); } } if (userConfig.length > 0) { lines.push('User Configuration:'); lines.push(...userConfig); lines.push(''); } if (autoConfig.length > 0) { lines.push('Auto-Derived Configuration:'); lines.push(...autoConfig); lines.push(''); } // Show zone if set if (context.selfConfig.zone) { lines.push(`Network Zone: ${context.selfConfig.zone}`); } return { success: true, message: lines.join('\n'), data: context.selfConfig, }; } catch (error) { return { success: false, error: `Failed to get module configuration: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * Handle module show-zone command * Shows which zone the module is in * * Usage: celilo module show-zone * * @param args - Command arguments * @returns Command result */ export async function handleModuleShowZone(args: string[]): Promise { const error = validateRequiredArgs(args, 1); if (error) { return { success: false, error: `${error}\n\nUsage: celilo module show-zone `, }; } const moduleId = getArg(args, 0); if (!moduleId) { return { success: false, error: 'Module ID is required', }; } const db = getDb(); // Check if module exists const module = db.select().from(modules).where(eq(modules.id, moduleId)).get(); if (!module) { return { success: false, error: `Module not found: ${moduleId}`, }; } try { // Build resolution context to get zone const context = await buildResolutionContext(moduleId, db); const manifest = module.manifestData as ModuleManifest; const zone = context.selfConfig.zone || getSingularSystemSpec(manifest)?.zone; if (!zone) { return { success: true, message: `Module ${moduleId} has no zone configured`, }; } // Keyed by NetworkZone, not string: a new zone added to NETWORK_ZONES becomes a // compile error here rather than silently rendering as "Unknown zone". Both // `internal` and `secure-mgmt` were missing from the previous string-keyed map. const zoneDescriptions: Record = { 'isp-transit': 'ISP transit (Private segment to the router upstream of the firewall)', internal: 'Internal (Semi-trusted network behind the firewall)', dmz: 'DMZ (Public-facing services)', app: 'Application (Internal services)', secure: 'Secure (Authentication/Database)', 'secure-mgmt': "Secure-Mgmt (celilo's own control plane)", external: 'External (VPS/Cloud)', 'control-plane-vpn': 'Control-plane VPN (administrative remote access)', }; // Cast at the lookup, not the declaration: `zone` comes from config and may be // any string, so the runtime fallback stays — but the map above still has to // cover every NetworkZone. const description = zoneDescriptions[zone as NetworkZone] ?? 'Unknown zone'; const lines = [`Module: ${moduleId}`, `Zone: ${zone} - ${description}`, '']; // Show network config if available if (zone !== 'external') { lines.push('Network Configuration:'); if (context.selfConfig.gateway) lines.push(` Gateway: ${context.selfConfig.gateway}`); if (context.selfConfig.vlan) lines.push(` VLAN: ${context.selfConfig.vlan}`); if (context.selfConfig.subnet) lines.push(` Subnet: ${context.selfConfig.subnet}`); if (context.selfConfig.bridge) lines.push(` Bridge: ${context.selfConfig.bridge}`); } else { lines.push('External zone (VPS/Cloud) - no local network config'); } return { success: true, message: lines.join('\n'), data: { zone, network: zone !== 'external' ? { gateway: context.selfConfig.gateway, vlan: context.selfConfig.vlan, subnet: context.selfConfig.subnet, bridge: context.selfConfig.bridge, } : null, }, }; } catch (error) { return { success: false, error: `Failed to get module zone: ${error instanceof Error ? error.message : String(error)}`, }; } }