388 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			388 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * Mailer CLI | ||
|  |  * Main command-line interface implementation | ||
|  |  */ | ||
|  | 
 | ||
|  | import { DaemonManager } from '../daemon/daemon-manager.ts'; | ||
|  | import { ConfigManager } from '../config/config-manager.ts'; | ||
|  | import { DnsManager } from '../dns/dns-manager.ts'; | ||
|  | import { CloudflareClient } from '../dns/cloudflare-client.ts'; | ||
|  | import { Email } from '../mail/core/index.ts'; | ||
|  | import { commitinfo } from '../00_commitinfo_data.ts'; | ||
|  | 
 | ||
|  | export class MailerCli { | ||
|  |   private configManager: ConfigManager; | ||
|  |   private daemonManager: DaemonManager; | ||
|  |   private dnsManager: DnsManager; | ||
|  | 
 | ||
|  |   constructor() { | ||
|  |     this.configManager = new ConfigManager(); | ||
|  |     this.daemonManager = new DaemonManager(); | ||
|  |     this.dnsManager = new DnsManager(); | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Parse and execute CLI commands | ||
|  |    */ | ||
|  |   async parseAndExecute(args: string[]): Promise<void> { | ||
|  |     // Get command
 | ||
|  |     const command = args[2] || 'help'; | ||
|  |     const subcommand = args[3]; | ||
|  |     const commandArgs = args.slice(4); | ||
|  | 
 | ||
|  |     try { | ||
|  |       switch (command) { | ||
|  |         case 'service': | ||
|  |           await this.handleServiceCommand(subcommand, commandArgs); | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 'domain': | ||
|  |           await this.handleDomainCommand(subcommand, commandArgs); | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 'dns': | ||
|  |           await this.handleDnsCommand(subcommand, commandArgs); | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 'send': | ||
|  |           await this.handleSendCommand(commandArgs); | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 'config': | ||
|  |           await this.handleConfigCommand(subcommand, commandArgs); | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 'version': | ||
|  |         case '--version': | ||
|  |         case '-v': | ||
|  |           this.showVersion(); | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 'help': | ||
|  |         case '--help': | ||
|  |         case '-h': | ||
|  |         default: | ||
|  |           this.showHelp(); | ||
|  |           break; | ||
|  |       } | ||
|  |     } catch (error) { | ||
|  |       console.error(`Error: ${error.message}`); | ||
|  |       Deno.exit(1); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Handle service commands (daemon control) | ||
|  |    */ | ||
|  |   private async handleServiceCommand(subcommand: string, args: string[]): Promise<void> { | ||
|  |     switch (subcommand) { | ||
|  |       case 'start': | ||
|  |         console.log('Starting mailer daemon...'); | ||
|  |         await this.daemonManager.start(); | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'stop': | ||
|  |         console.log('Stopping mailer daemon...'); | ||
|  |         await this.daemonManager.stop(); | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'restart': | ||
|  |         console.log('Restarting mailer daemon...'); | ||
|  |         await this.daemonManager.stop(); | ||
|  |         await new Promise(resolve => setTimeout(resolve, 2000)); | ||
|  |         await this.daemonManager.start(); | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'status': | ||
|  |         console.log('Checking mailer daemon status...'); | ||
|  |         // TODO: Implement status check
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'enable': | ||
|  |         console.log('Enabling mailer service (systemd)...'); | ||
|  |         // TODO: Implement systemd enable
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'disable': | ||
|  |         console.log('Disabling mailer service (systemd)...'); | ||
|  |         // TODO: Implement systemd disable
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       default: | ||
|  |         console.log('Usage: mailer service {start|stop|restart|status|enable|disable}'); | ||
|  |         break; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Handle domain management commands | ||
|  |    */ | ||
|  |   private async handleDomainCommand(subcommand: string, args: string[]): Promise<void> { | ||
|  |     const config = await this.configManager.load(); | ||
|  | 
 | ||
|  |     switch (subcommand) { | ||
|  |       case 'add': { | ||
|  |         const domain = args[0]; | ||
|  |         if (!domain) { | ||
|  |           console.error('Error: Domain name required'); | ||
|  |           console.log('Usage: mailer domain add <domain>'); | ||
|  |           Deno.exit(1); | ||
|  |         } | ||
|  | 
 | ||
|  |         config.domains.push({ | ||
|  |           domain, | ||
|  |           dnsMode: 'external-dns', | ||
|  |         }); | ||
|  | 
 | ||
|  |         await this.configManager.save(config); | ||
|  |         console.log(`✓ Domain ${domain} added`); | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       case 'remove': { | ||
|  |         const domain = args[0]; | ||
|  |         if (!domain) { | ||
|  |           console.error('Error: Domain name required'); | ||
|  |           console.log('Usage: mailer domain remove <domain>'); | ||
|  |           Deno.exit(1); | ||
|  |         } | ||
|  | 
 | ||
|  |         config.domains = config.domains.filter(d => d.domain !== domain); | ||
|  |         await this.configManager.save(config); | ||
|  |         console.log(`✓ Domain ${domain} removed`); | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       case 'list': | ||
|  |         console.log('Configured domains:'); | ||
|  |         if (config.domains.length === 0) { | ||
|  |           console.log('  (none)'); | ||
|  |         } else { | ||
|  |           for (const domain of config.domains) { | ||
|  |             console.log(`  - ${domain.domain} (${domain.dnsMode})`); | ||
|  |           } | ||
|  |         } | ||
|  |         break; | ||
|  | 
 | ||
|  |       default: | ||
|  |         console.log('Usage: mailer domain {add|remove|list} [domain]'); | ||
|  |         break; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Handle DNS commands | ||
|  |    */ | ||
|  |   private async handleDnsCommand(subcommand: string, args: string[]): Promise<void> { | ||
|  |     const domain = args[0]; | ||
|  | 
 | ||
|  |     if (!domain && subcommand !== 'help') { | ||
|  |       console.error('Error: Domain name required'); | ||
|  |       console.log('Usage: mailer dns {setup|validate|show} <domain>'); | ||
|  |       Deno.exit(1); | ||
|  |     } | ||
|  | 
 | ||
|  |     switch (subcommand) { | ||
|  |       case 'setup': { | ||
|  |         console.log(`Setting up DNS for ${domain}...`); | ||
|  | 
 | ||
|  |         const config = await this.configManager.load(); | ||
|  |         const domainConfig = config.domains.find(d => d.domain === domain); | ||
|  | 
 | ||
|  |         if (!domainConfig) { | ||
|  |           console.error(`Error: Domain ${domain} not configured. Add it first with: mailer domain add ${domain}`); | ||
|  |           Deno.exit(1); | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!domainConfig.cloudflare?.apiToken) { | ||
|  |           console.error('Error: Cloudflare API token not configured'); | ||
|  |           console.log('Set it with: mailer config set cloudflare.apiToken <token>'); | ||
|  |           Deno.exit(1); | ||
|  |         } | ||
|  | 
 | ||
|  |         const cloudflare = new CloudflareClient({ apiToken: domainConfig.cloudflare.apiToken }); | ||
|  |         const records = this.dnsManager.getRequiredRecords(domain, config.hostname); | ||
|  |         await cloudflare.createRecords(domain, records); | ||
|  | 
 | ||
|  |         console.log(`✓ DNS records created for ${domain}`); | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       case 'validate': { | ||
|  |         console.log(`Validating DNS for ${domain}...`); | ||
|  |         const result = await this.dnsManager.validateDomain(domain); | ||
|  | 
 | ||
|  |         if (result.valid) { | ||
|  |           console.log(`✓ DNS configuration is valid`); | ||
|  |         } else { | ||
|  |           console.log(`✗ DNS configuration has errors:`); | ||
|  |           for (const error of result.errors) { | ||
|  |             console.log(`  - ${error}`); | ||
|  |           } | ||
|  |         } | ||
|  | 
 | ||
|  |         if (result.warnings.length > 0) { | ||
|  |           console.log('Warnings:'); | ||
|  |           for (const warning of result.warnings) { | ||
|  |             console.log(`  - ${warning}`); | ||
|  |           } | ||
|  |         } | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       case 'show': { | ||
|  |         console.log(`Required DNS records for ${domain}:`); | ||
|  |         const config = await this.configManager.load(); | ||
|  |         const records = this.dnsManager.getRequiredRecords(domain, config.hostname); | ||
|  | 
 | ||
|  |         for (const record of records) { | ||
|  |           console.log(`\n${record.type} Record:`); | ||
|  |           console.log(`  Name: ${record.name}`); | ||
|  |           console.log(`  Value: ${record.value}`); | ||
|  |           if (record.priority) console.log(`  Priority: ${record.priority}`); | ||
|  |           if (record.ttl) console.log(`  TTL: ${record.ttl}`); | ||
|  |         } | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       default: | ||
|  |         console.log('Usage: mailer dns {setup|validate|show} <domain>'); | ||
|  |         break; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Handle send command | ||
|  |    */ | ||
|  |   private async handleSendCommand(args: string[]): Promise<void> { | ||
|  |     console.log('Sending email...'); | ||
|  | 
 | ||
|  |     // Parse basic arguments
 | ||
|  |     const from = args[args.indexOf('--from') + 1]; | ||
|  |     const to = args[args.indexOf('--to') + 1]; | ||
|  |     const subject = args[args.indexOf('--subject') + 1]; | ||
|  |     const text = args[args.indexOf('--text') + 1]; | ||
|  | 
 | ||
|  |     if (!from || !to || !subject || !text) { | ||
|  |       console.error('Error: Missing required arguments'); | ||
|  |       console.log('Usage: mailer send --from <email> --to <email> --subject <subject> --text <text>'); | ||
|  |       Deno.exit(1); | ||
|  |     } | ||
|  | 
 | ||
|  |     const email = new Email({ | ||
|  |       from, | ||
|  |       to, | ||
|  |       subject, | ||
|  |       text, | ||
|  |     }); | ||
|  | 
 | ||
|  |     console.log(`✓ Email created: ${email.toString()}`); | ||
|  |     // TODO: Actually send the email via SMTP client
 | ||
|  |     console.log('TODO: Implement actual sending'); | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Handle config commands | ||
|  |    */ | ||
|  |   private async handleConfigCommand(subcommand: string, args: string[]): Promise<void> { | ||
|  |     const config = await this.configManager.load(); | ||
|  | 
 | ||
|  |     switch (subcommand) { | ||
|  |       case 'show': | ||
|  |         console.log('Current configuration:'); | ||
|  |         console.log(JSON.stringify(config, null, 2)); | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'set': { | ||
|  |         const key = args[0]; | ||
|  |         const value = args[1]; | ||
|  | 
 | ||
|  |         if (!key || !value) { | ||
|  |           console.error('Error: Key and value required'); | ||
|  |           console.log('Usage: mailer config set <key> <value>'); | ||
|  |           Deno.exit(1); | ||
|  |         } | ||
|  | 
 | ||
|  |         // Simple key-value setting (can be enhanced)
 | ||
|  |         if (key === 'smtpPort') config.smtpPort = parseInt(value); | ||
|  |         else if (key === 'apiPort') config.apiPort = parseInt(value); | ||
|  |         else if (key === 'hostname') config.hostname = value; | ||
|  |         else { | ||
|  |           console.error(`Error: Unknown config key: ${key}`); | ||
|  |           Deno.exit(1); | ||
|  |         } | ||
|  | 
 | ||
|  |         await this.configManager.save(config); | ||
|  |         console.log(`✓ Configuration updated: ${key} = ${value}`); | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       default: | ||
|  |         console.log('Usage: mailer config {show|set} [key] [value]'); | ||
|  |         break; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Show version information | ||
|  |    */ | ||
|  |   private showVersion(): void { | ||
|  |     console.log(`${commitinfo.name} v${commitinfo.version}`); | ||
|  |     console.log(commitinfo.description); | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Show help information | ||
|  |    */ | ||
|  |   private showHelp(): void { | ||
|  |     console.log(`
 | ||
|  | ${commitinfo.name} v${commitinfo.version} | ||
|  | ${commitinfo.description} | ||
|  | 
 | ||
|  | Usage: mailer <command> [options] | ||
|  | 
 | ||
|  | Commands: | ||
|  |   service <action>              Daemon service control | ||
|  |     start                       Start the mailer daemon | ||
|  |     stop                        Stop the mailer daemon | ||
|  |     restart                     Restart the mailer daemon | ||
|  |     status                      Show daemon status | ||
|  |     enable                      Enable systemd service | ||
|  |     disable                     Disable systemd service | ||
|  | 
 | ||
|  |   domain <action> [domain]      Domain management | ||
|  |     add <domain>                Add a domain | ||
|  |     remove <domain>             Remove a domain | ||
|  |     list                        List all domains | ||
|  | 
 | ||
|  |   dns <action> <domain>         DNS management | ||
|  |     setup <domain>              Auto-configure DNS via Cloudflare | ||
|  |     validate <domain>           Validate DNS configuration | ||
|  |     show <domain>               Show required DNS records | ||
|  | 
 | ||
|  |   send [options]                Send an email | ||
|  |     --from <email>              Sender email address | ||
|  |     --to <email>                Recipient email address | ||
|  |     --subject <subject>         Email subject | ||
|  |     --text <text>               Email body text | ||
|  | 
 | ||
|  |   config <action>               Configuration management | ||
|  |     show                        Show current configuration | ||
|  |     set <key> <value>           Set configuration value | ||
|  | 
 | ||
|  |   version, -v, --version        Show version information | ||
|  |   help, -h, --help              Show this help message | ||
|  | 
 | ||
|  | Examples: | ||
|  |   mailer service start          Start the mailer daemon | ||
|  |   mailer domain add example.com Add example.com domain | ||
|  |   mailer dns setup example.com  Setup DNS for example.com | ||
|  |   mailer send --from sender@example.com --to recipient@example.com \\ | ||
|  |               --subject "Hello" --text "World" | ||
|  | 
 | ||
|  | For more information, visit: | ||
|  |   https://code.foss.global/serve.zone/mailer
 | ||
|  | `);
 | ||
|  |   } | ||
|  | } |