/** * Module where command — host discovery: which system(s) serve a module. * * Answers "which host is providing services for ?" without an ssh * scavenger hunt (ce-jje). Data source: the deployed-systems layer * (module_systems) for the addressable hosts, reconciled live against Proxmox * for the real node. Reachability is a role-based hint derived from the zone — * never a hardcoded address (see CLAUDE.md network-model rules). */ import { eq } from 'drizzle-orm'; import { getDb } from '../../db/client'; import type { NetworkZone } from '../../db/schema'; import { modules } from '../../db/schema'; import { getModuleSystems } from '../../services/deployed-systems'; import { formatPlacementLine, reconcilePlacement } from '../../services/placement-reconcile'; import { getArg, hasFlag, validateRequiredArgs } from '../parser'; import type { CommandResult } from '../types'; /** * Role-based reachability hint for a zone. Says HOW to reach a host by the * zone's firewall posture, not a literal address (subnets are operator config). */ export function reachabilityHint(zone: NetworkZone | string): string { switch (zone) { case 'internal': return 'reachable directly from the internal/home LAN'; case 'external': return 'public zone — reachable at its own address (cloud/VPS)'; case 'dmz': case 'app': case 'secure': return `firewall-segmented (${zone}) — reach via the firewall's natIp DNAT, not routable directly from the LAN`; case 'secure-mgmt': return "celilo's control plane — firewall-segmented inbound like the other zones, but trusted OUTBOUND to every segmented tier"; default: return `zone ${zone}`; } } /** * Handle module where command. * * Usage: celilo module where [--json] * * @param args - Command arguments (module id) * @param flags - Command flags (--json) * @returns Command result */ export async function handleModuleWhere( args: string[], flags: Record = {}, ): Promise { const error = validateRequiredArgs(args, 1); if (error) { return { success: false, error: `${error}\n\nUsage: celilo module where [--json]`, }; } const moduleId = getArg(args, 0); if (!moduleId) { return { success: false, error: 'Module ID is required' }; } const db = getDb(); const module = db.select().from(modules).where(eq(modules.id, moduleId)).get(); if (!module) { // CI/build infra (the celilo-builder VM, Forgejo runners) is NOT a module // and is not in module_systems — point the operator at where that topology // actually lives rather than a bare "not found". return { success: false, error: `Module not found: ${moduleId}\n\nHost discovery covers deployed modules only. CI/build infrastructure (the celilo-builder VM, Forgejo CI runners) is not tracked here — see openspec/specs/forgejo-runner/spec.md and openspec/changes/build-bus-poll-cd/proposal.md for that topology.`, }; } const systems = getModuleSystems(moduleId, db); if (systems.length === 0) { const message = `Module '${moduleId}' has no deployed systems (API-only module, or not yet deployed).`; if (hasFlag(flags, 'json')) { return { success: true, message: JSON.stringify({ module: moduleId, systems: [] }, null, 2), rawOutput: true, data: { module: moduleId, systems: [] }, }; } return { success: true, message }; } // Reconcile the real node live from Proxmox (never throws; degrades to // "node unknown" on outage) — same live-placement source as module status. const placements = await reconcilePlacement(systems); const data = placements.map(({ system, resolution }) => ({ name: system.name, hostname: system.hostname, ipv4_address: system.ipv4_address, zone: system.zone, vmid: system.infrastructure.vmid ?? null, infra_type: system.infrastructure.type, placement: formatPlacementLine(system, resolution), reachability: reachabilityHint(system.zone), })); if (hasFlag(flags, 'json')) { return { success: true, message: JSON.stringify({ module: moduleId, systems: data }, null, 2), rawOutput: true, data: { module: moduleId, systems: data }, }; } const lines = [`Module '${moduleId}' is served by:`, '']; for (const sys of data) { lines.push(` ${sys.hostname} (${sys.ipv4_address})`); lines.push(` placement: ${sys.placement}`); lines.push(` reachability: ${sys.reachability}`); lines.push(''); } return { success: true, message: lines.join('\n').trimEnd(), data: { module: moduleId, systems: data }, }; }