Skip to main content
Glama
config-loader.jsโ€ข8.37 kB
import * as dotenv from 'dotenv'; import TOML from '@iarna/toml'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { logger } from './logger.js'; export class ConfigLoader { constructor() { this.servers = new Map(); this.configSource = null; } /** * Load configuration from multiple sources with priority: * 1. Environment variables (highest priority) * 2. .env file * 3. TOML config file (lowest priority) */ async load(options = {}) { const { envPath = path.join(process.cwd(), '.env'), tomlPath = process.env.SSH_CONFIG_PATH || path.join(os.homedir(), '.codex', 'ssh-config.toml'), preferToml = false } = options; // Clear existing servers this.servers.clear(); // Load in reverse priority order (lowest to highest) let loadedFromToml = false; let loadedFromEnv = false; // Try loading TOML config first (lowest priority) if (fs.existsSync(tomlPath)) { try { await this.loadTomlConfig(tomlPath); loadedFromToml = true; logger.info(`Loaded SSH configuration from TOML: ${tomlPath}`); } catch (error) { logger.warn(`Failed to load TOML config: ${error.message}`); } } // Load .env file (higher priority, overwrites TOML) if (!preferToml && fs.existsSync(envPath)) { try { this.loadEnvConfig(envPath); loadedFromEnv = true; logger.info(`Loaded SSH configuration from .env: ${envPath}`); } catch (error) { logger.warn(`Failed to load .env config: ${error.message}`); } } // Load from environment variables (highest priority, overwrites everything) this.loadEnvironmentVariables(); // Determine primary config source if (loadedFromEnv) { this.configSource = 'env'; } else if (loadedFromToml) { this.configSource = 'toml'; } else if (this.servers.size > 0) { this.configSource = 'environment'; } else { this.configSource = null; logger.warn('No SSH server configurations found'); } return this.servers; } /** * Load configuration from TOML file */ async loadTomlConfig(tomlPath) { const content = fs.readFileSync(tomlPath, 'utf8'); const config = TOML.parse(content); if (config.ssh_servers) { for (const [name, serverConfig] of Object.entries(config.ssh_servers)) { const normalizedName = name.toLowerCase(); this.servers.set(normalizedName, { name: normalizedName, host: serverConfig.host, user: serverConfig.user || serverConfig.username, password: serverConfig.password, keyPath: serverConfig.key_path || serverConfig.keypath || serverConfig.ssh_key, port: serverConfig.port || 22, defaultDir: serverConfig.default_dir || serverConfig.default_directory || serverConfig.cwd, sudoPassword: serverConfig.sudo_password, description: serverConfig.description, source: 'toml' }); } } } /** * Load configuration from .env file */ loadEnvConfig(envPath) { dotenv.config({ path: envPath }); this.parseEnvVariables(process.env); } /** * Load configuration from environment variables */ loadEnvironmentVariables() { this.parseEnvVariables(process.env); } /** * Parse environment variables for SSH server configurations */ parseEnvVariables(env) { const serverPattern = /^SSH_SERVER_([A-Z0-9_]+)_HOST$/; const processedServers = new Set(); for (const [key, value] of Object.entries(env)) { const match = key.match(serverPattern); if (match) { const serverName = match[1].toLowerCase(); // Skip if already processed from a higher priority source if (processedServers.has(serverName)) continue; const server = { name: serverName, host: value, user: env[`SSH_SERVER_${match[1]}_USER`], password: env[`SSH_SERVER_${match[1]}_PASSWORD`], keyPath: env[`SSH_SERVER_${match[1]}_KEYPATH`], port: parseInt(env[`SSH_SERVER_${match[1]}_PORT`] || '22'), defaultDir: env[`SSH_SERVER_${match[1]}_DEFAULT_DIR`], sudoPassword: env[`SSH_SERVER_${match[1]}_SUDO_PASSWORD`], description: env[`SSH_SERVER_${match[1]}_DESCRIPTION`], source: 'env' }; this.servers.set(serverName, server); processedServers.add(serverName); } } } /** * Get server configuration by name */ getServer(name) { return this.servers.get(name.toLowerCase()); } /** * Get all server configurations */ getAllServers() { return Array.from(this.servers.values()); } /** * Check if server exists */ hasServer(name) { return this.servers.has(name.toLowerCase()); } /** * Export current configuration to TOML format */ exportToToml() { const config = { ssh_servers: {} }; for (const [name, server] of this.servers) { const serverConfig = { host: server.host, user: server.user, port: server.port }; if (server.password) serverConfig.password = server.password; if (server.keyPath) serverConfig.key_path = server.keyPath; if (server.defaultDir) serverConfig.default_dir = server.defaultDir; if (server.sudoPassword) serverConfig.sudo_password = server.sudoPassword; if (server.description) serverConfig.description = server.description; config.ssh_servers[name] = serverConfig; } return TOML.stringify(config); } /** * Export current configuration to .env format */ exportToEnv() { const lines = ['# SSH Server Configuration']; lines.push('# Generated by MCP SSH Manager'); lines.push(''); for (const [name, server] of this.servers) { const upperName = name.toUpperCase(); lines.push(`# Server: ${name}`); lines.push(`SSH_SERVER_${upperName}_HOST=${server.host}`); lines.push(`SSH_SERVER_${upperName}_USER=${server.user}`); if (server.password) lines.push(`SSH_SERVER_${upperName}_PASSWORD="${server.password}"`); if (server.keyPath) lines.push(`SSH_SERVER_${upperName}_KEYPATH=${server.keyPath}`); lines.push(`SSH_SERVER_${upperName}_PORT=${server.port || 22}`); if (server.defaultDir) lines.push(`SSH_SERVER_${upperName}_DEFAULT_DIR=${server.defaultDir}`); if (server.sudoPassword) lines.push(`SSH_SERVER_${upperName}_SUDO_PASSWORD="${server.sudoPassword}"`); if (server.description) lines.push(`SSH_SERVER_${upperName}_DESCRIPTION="${server.description}"`); lines.push(''); } return lines.join('\n'); } /** * Save configuration to Codex TOML format */ async saveToCodexConfig(codexConfigPath = path.join(os.homedir(), '.codex', 'config.toml')) { let config = {}; // Load existing config if it exists if (fs.existsSync(codexConfigPath)) { const content = fs.readFileSync(codexConfigPath, 'utf8'); config = TOML.parse(content); } // Add MCP server configuration if (!config.mcp_servers) { config.mcp_servers = {}; } config.mcp_servers['ssh-manager'] = { command: 'node', args: [path.join(process.cwd(), 'src', 'index.js')], env: { SSH_CONFIG_PATH: path.join(os.homedir(), '.codex', 'ssh-config.toml') }, startup_timeout_ms: 20000 }; // Write back to config file const tomlContent = TOML.stringify(config); fs.writeFileSync(codexConfigPath, tomlContent, 'utf8'); logger.info(`Updated Codex configuration at ${codexConfigPath}`); } /** * Migrate .env configuration to TOML */ async migrateEnvToToml(envPath, tomlPath) { // Load from .env this.servers.clear(); this.loadEnvConfig(envPath); // Export to TOML const tomlContent = this.exportToToml(); // Ensure directory exists const tomlDir = path.dirname(tomlPath); if (!fs.existsSync(tomlDir)) { fs.mkdirSync(tomlDir, { recursive: true }); } // Write TOML file fs.writeFileSync(tomlPath, tomlContent, 'utf8'); logger.info(`Migrated ${this.servers.size} servers from ${envPath} to ${tomlPath}`); return this.servers.size; } } // Export singleton instance export const configLoader = new ConfigLoader();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bvisible/mcp-ssh-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server