Skip to main content
Glama
bootstrap-config.ts11.9 kB
#!/usr/bin/env tsx /** * Bootstrap Configuration Script * * First-time setup for AI MCP Gateway without .env file * Prompts for essential configuration and stores in database */ import { createInterface } from 'readline/promises'; import { stdin as input, stdout as output } from 'process'; import { Client } from 'pg'; import crypto from 'crypto'; import { readFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); interface BootstrapConfig { // Database dbHost: string; dbPort: number; dbName: string; dbUser: string; dbPassword: string; dbSsl: boolean; // Encryption encryptionKey: string; // Essential providers openrouterKey?: string; openaiKey?: string; anthropicKey?: string; // Server apiPort: number; apiHost: string; } const rl = createInterface({ input, output }); function generateEncryptionKey(): string { return crypto.randomBytes(32).toString('base64').substring(0, 32); } async function prompt(question: string, defaultValue?: string): Promise<string> { const def = defaultValue ? ` (default: ${defaultValue})` : ''; const answer = await rl.question(`${question}${def}: `); return answer.trim() || defaultValue || ''; } async function promptPassword(question: string): Promise<string> { // Note: In production, use a proper password input library return await prompt(question); } async function promptBoolean(question: string, defaultValue: boolean = false): Promise<boolean> { const answer = await prompt(`${question} (y/n)`, defaultValue ? 'y' : 'n'); return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'; } async function promptNumber(question: string, defaultValue: number): Promise<number> { const answer = await prompt(question, defaultValue.toString()); return parseInt(answer, 10) || defaultValue; } async function collectConfig(): Promise<BootstrapConfig> { console.log('\n╔═══════════════════════════════════════════════════════════╗'); console.log('║ AI MCP Gateway - Initial Configuration Setup ║'); console.log('╚═══════════════════════════════════════════════════════════╝\n'); console.log('📊 Database Configuration\n'); const dbHost = await prompt('PostgreSQL host', 'localhost'); const dbPort = await promptNumber('PostgreSQL port', 5432); const dbName = await prompt('Database name', 'ai_mcp_gateway'); const dbUser = await prompt('Database user', 'postgres'); const dbPassword = await promptPassword('Database password'); const dbSsl = await promptBoolean('Use SSL for database connection', false); console.log('\n🔐 Security Configuration\n'); const useGeneratedKey = await promptBoolean('Generate encryption key automatically', true); const encryptionKey = useGeneratedKey ? generateEncryptionKey() : await prompt('Encryption key (32 characters)'); if (useGeneratedKey) { console.log(`\n⚠️ SAVE THIS ENCRYPTION KEY SECURELY:\n ${encryptionKey}\n`); } console.log('\n🤖 LLM Provider API Keys (at least one required)\n'); const openrouterKey = await prompt('OpenRouter API key (recommended)', ''); const openaiKey = await prompt('OpenAI API key (optional)', ''); const anthropicKey = await prompt('Anthropic API key (optional)', ''); if (!openrouterKey && !openaiKey && !anthropicKey) { console.log('\n⚠️ WARNING: No provider keys configured. You can add them later via web UI.'); } console.log('\n🌐 Server Configuration\n'); const apiPort = await promptNumber('API server port', 3000); const apiHost = await prompt('API server host', '0.0.0.0'); return { dbHost, dbPort, dbName, dbUser, dbPassword, dbSsl, encryptionKey, openrouterKey, openaiKey, anthropicKey, apiPort, apiHost }; } async function testDbConnection(config: BootstrapConfig): Promise<Client> { console.log('\n🔌 Testing database connection...'); const client = new Client({ host: config.dbHost, port: config.dbPort, database: config.dbName, user: config.dbUser, password: config.dbPassword, ssl: config.dbSsl ? { rejectUnauthorized: false } : undefined }); try { await client.connect(); console.log('✅ Database connection successful\n'); return client; } catch (error) { console.error('❌ Database connection failed:', error); throw error; } } async function runMigrations(client: Client): Promise<void> { console.log('📦 Running database migrations...\n'); const migrationsDir = join(__dirname, '../migrations'); const migrationFiles = [ '001_phase1_tracing_multitenant.sql', '002_phase1_analytics_quotas.sql', '003_phase1_security_roles.sql', '005_provider_management.sql', '006_add_model_priority.sql', '007_chat_context_optimization.sql', '008_chat_context_minimal.sql', '009_system_configuration.sql' ]; for (const file of migrationFiles) { const filePath = join(migrationsDir, file); if (!existsSync(filePath)) { console.log(`⚠️ Migration file not found: ${file}`); continue; } console.log(` Running ${file}...`); const sql = readFileSync(filePath, 'utf8'); try { await client.query(sql); console.log(` ✅ ${file} completed`); } catch (error: any) { // Ignore "already exists" errors if (error.code === '42P07' || error.code === '42710') { console.log(` ℹ️ ${file} already applied`); } else { console.error(` ❌ ${file} failed:`, error.message); throw error; } } } console.log('\n✅ All migrations completed\n'); } function encryptValue(text: string, key: string): string { const keyBuffer = Buffer.from(crypto.createHash('sha256').update(key).digest('base64').substring(0, 32)); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', keyBuffer, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted; } async function saveConfiguration(client: Client, config: BootstrapConfig): Promise<void> { console.log('💾 Saving configuration to database...\n'); // Store encryption key in a secure config table (encrypted with itself initially) await client.query( `INSERT INTO system_config (key, value, value_type, category, description, is_required) VALUES ($1, $2, 'string', 'security', 'Master encryption key for sensitive data', true) ON CONFLICT (key) DO UPDATE SET value = $2`, ['ENCRYPTION_KEY', encryptValue(config.encryptionKey, config.encryptionKey)] ); // Save database connection info (for future reference, not used at runtime) await client.query( `INSERT INTO system_config (key, value, value_type, category) VALUES ('DB_HOST', $1, 'string', 'database'), ('DB_PORT', $2, 'number', 'database'), ('DB_NAME', $3, 'string', 'database'), ('DB_USER', $4, 'string', 'database') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`, [config.dbHost, config.dbPort.toString(), config.dbName, config.dbUser] ); // Save server config await client.query( `UPDATE system_config SET value = $1 WHERE key = 'API_PORT'`, [config.apiPort.toString()] ); await client.query( `UPDATE system_config SET value = $1 WHERE key = 'API_HOST'`, [config.apiHost] ); // Save provider credentials (encrypted) if (config.openrouterKey) { console.log(' Saving OpenRouter credentials...'); await client.query( `UPDATE provider_credentials SET api_key_encrypted = $1, enabled = true WHERE provider = 'openrouter'`, [encryptValue(config.openrouterKey, config.encryptionKey)] ); } if (config.openaiKey) { console.log(' Saving OpenAI credentials...'); await client.query( `UPDATE provider_credentials SET api_key_encrypted = $1, enabled = true WHERE provider = 'openai'`, [encryptValue(config.openaiKey, config.encryptionKey)] ); } if (config.anthropicKey) { console.log(' Saving Anthropic credentials...'); await client.query( `UPDATE provider_credentials SET api_key_encrypted = $1, enabled = true WHERE provider = 'anthropic'`, [encryptValue(config.anthropicKey, config.encryptionKey)] ); } console.log('\n✅ Configuration saved successfully\n'); } async function createBootstrapFile(config: BootstrapConfig): Promise<void> { const fs = await import('fs/promises'); // Create minimal bootstrap file with DB connection and encryption key only const bootstrapContent = `# AI MCP Gateway Bootstrap Configuration # This file is ONLY used for initial database connection # All other configuration is stored in the database # Database Connection (required for startup) DB_HOST=${config.dbHost} DB_PORT=${config.dbPort} DB_NAME=${config.dbName} DB_USER=${config.dbUser} DB_PASSWORD=${config.dbPassword} DB_SSL=${config.dbSsl} # Encryption Key (required for decrypting config from DB) CONFIG_ENCRYPTION_KEY=${config.encryptionKey} # Note: DO NOT add other configuration here # Use the web UI at http://localhost:${config.apiPort}/admin to manage all other settings `; await fs.writeFile(join(__dirname, '../.env.bootstrap'), bootstrapContent, 'utf8'); console.log('📝 Created .env.bootstrap file (minimal bootstrap config)\n'); console.log('⚠️ IMPORTANT: Keep .env.bootstrap file secure and backed up!\n'); } async function main() { try { const config = await collectConfig(); const proceed = await promptBoolean('\nProceed with these settings', true); if (!proceed) { console.log('Setup cancelled.'); process.exit(0); } const client = await testDbConnection(config); await runMigrations(client); await saveConfiguration(client, config); await createBootstrapFile(config); await client.end(); console.log('╔═══════════════════════════════════════════════════════════╗'); console.log('║ Setup Complete! 🎉 ║'); console.log('╚═══════════════════════════════════════════════════════════╝\n'); console.log('Next steps:\n'); console.log('1. Start the server:'); console.log(' npm run build && npm start\n'); console.log(`2. Access admin dashboard:`); console.log(` http://localhost:${config.apiPort}/admin\n`); console.log('3. Configure additional settings via web UI\n'); rl.close(); process.exit(0); } catch (error) { console.error('\n❌ Setup failed:', error); rl.close(); process.exit(1); } } main();

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/babasida246/ai-mcp-gateway'

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