/** * Service Add Digital Ocean Command * Configure a Digital Ocean VPS service */ import type { NetworkZone } from '../../db/schema'; import { askMultiselect, askText, withInterviewSession } from '../../services/bus-interview'; import { addContainerService, testConnection as testServiceConnection, updateVerificationStatus, } from '../../services/container-service'; import { celiloIntro, celiloOutro } from '../prompts'; import { resolveServiceCredential } from '../service-credential'; import type { CommandResult } from '../types'; /** * Handle service add digitalocean command * * @param args - Command arguments (unused for interactive mode) * @param flags - Command flags */ export async function handleServiceAddDigitalOcean( _args: string[], flags: Record = {}, ): Promise { // Non-secret prompts route through the bus interview (ISS-0127); the DO API // token is a service credential that travels by flag/env only (D7). const scope = 'service-add:digitalocean'; return withInterviewSession(async () => { try { celiloIntro('Add Digital Ocean VPS Service'); const name = await askText({ scope, key: 'name', message: 'Human-readable name', defaultValue: 'Digital Ocean VPS', placeholder: 'Digital Ocean VPS', required: true, }); const zones = await askMultiselect({ scope, key: 'zones', message: 'Zones this service can provision to', options: [ { value: 'internal', label: 'internal' }, { value: 'dmz', label: 'dmz' }, { value: 'app', label: 'app' }, { value: 'secure', label: 'secure' }, { value: 'secure-mgmt', label: 'secure-mgmt' }, { value: 'external', label: 'external' }, ], required: true, }); console.log('\nDigital Ocean Configuration'); console.log('──────────────────────────'); // API token is a service credential — flag/env only (D7). const apiToken = await resolveServiceCredential({ field: 'API token (Personal Access Token)', flag: 'api-token', envVar: 'DIGITALOCEAN_API_TOKEN', flagValue: flags['api-token'], }); const defaultRegion = await askText({ scope, key: 'default_region', message: 'Default region', defaultValue: 'nyc3', placeholder: 'nyc3', required: true, }); const defaultSize = await askText({ scope, key: 'default_size', message: 'Default droplet size', defaultValue: 's-1vcpu-1gb', placeholder: 's-1vcpu-1gb', required: true, }); const defaultImage = await askText({ scope, key: 'default_image', message: 'Default image', defaultValue: 'ubuntu-22-04-x64', placeholder: 'ubuntu-22-04-x64', required: true, }); // Store the service first so we can test it const service = await addContainerService({ name, providerName: 'digitalocean', zones: zones as unknown as NetworkZone[], apiCredentials: { api_token: apiToken, }, providerConfig: { default_region: defaultRegion, default_size: defaultSize, default_image: defaultImage, }, }); // Test connection console.log('\nTesting connection...'); const testResult = await testServiceConnection(service); await updateVerificationStatus(service.id, testResult); if (!testResult.success) { console.log(`✗ Connection test failed: ${testResult.message}`); console.log( '\nService saved but not verified. The service will not be used until verification succeeds.', ); celiloOutro( `Service '${service.serviceId}' (${name}) added but not verified.\n\nNext steps:\n - Fix the connection issue\n - Re-verify: celilo service verify ${service.serviceId}\n - Check status: celilo service list`, ); } else { console.log(`✓ ${testResult.message}`); celiloOutro( `Service '${service.serviceId}' (${name}) added and verified successfully!\n\nService ID: ${service.serviceId}\n\nNext steps:\n - Generate module: celilo module generate \n - List services: celilo service list`, ); } return { success: true, message: `Added Digital Ocean service: ${service.id}`, }; } catch (error) { return { success: false, error: `Failed to add Digital Ocean service: ${error instanceof Error ? error.message : String(error)}`, }; } }); }