error('.env file not found at: ' . $envPath); return self::FAILURE; } // Skip if credentials are already properly configured (unless --force) if (!$this->option('force')) { $existingUser = $this->readEnvValue($envPath, 'AMI_USERNAME'); $existingSecret = $this->readEnvValue($envPath, 'AMI_SECRET'); if ($existingUser && $existingSecret && $existingUser !== 'theAMIManager') { $this->line('AMI credentials are already configured (AMI_USERNAME=' . $existingUser . ').'); $this->line('Use --force to regenerate.'); return self::SUCCESS; } } // Remove old manager user if regenerating if ($this->option('force')) { $oldUser = $this->readEnvValue($envPath, 'AMI_USERNAME'); if ($oldUser) { try { DB::table('asterisk.manager')->where('name', $oldUser)->delete(); $this->line('Removed old manager user: ' . $oldUser); } catch (\Exception $e) { // Not fatal — table might not have the old user } } } // Generate credentials // Username: short, readable, no spaces (fits varchar(50)) $username = 'ccs_ami_' . bin2hex(random_bytes(3)); // e.g. ccs_ami_a1b2c3 $secret = Str::random(24); // 24-char alphanumeric // Step 1 — Register in asterisk.manager (direct DB, no external dependency) try { $existing = DB::table('asterisk.manager')->where('name', $username)->first(); if ($existing) { DB::table('asterisk.manager') ->where('name', $username) ->update(['secret' => $secret]); } else { DB::table('asterisk.manager')->insert([ 'name' => $username, 'secret' => $secret, 'deny' => '0.0.0.0/0.0.0.0', 'permit' => '127.0.0.1/255.255.255.0', 'read' => self::AMI_READ, 'write' => self::AMI_WRITE, 'writetimeout' => 600, ]); } } catch (\Exception $e) { $this->error('Failed to register AMI user in asterisk.manager: ' . $e->getMessage()); \Log::error('ami:generate-credentials failed', ['error' => $e->getMessage()]); return self::FAILURE; } // Step 2 — Write to .env try { $this->writeEnv($envPath, 'AMI_USERNAME', $username); $this->writeEnv($envPath, 'AMI_SECRET', $secret); $this->writeEnv($envPath, 'AMI_PORT', '5038'); // Ensure port is set (default AMI port) } catch (\Exception $e) { // Roll back the asterisk.manager entry to keep state consistent DB::table('asterisk.manager')->where('name', $username)->delete(); $this->error('Failed to update .env: ' . $e->getMessage()); return self::FAILURE; } // Step 3 — Reload the Asterisk manager module so the new user is live immediately $this->reloadManager(); $this->line('AMI credentials generated successfully.'); $this->line(' Username : ' . $username); $this->line(' Manager : registered in asterisk.manager'); $this->line(' .env : AMI_USERNAME, AMI_SECRET, and AMI_PORT updated'); \Log::info('ami:generate-credentials completed', ['username' => $username]); return self::SUCCESS; } /** * Read a single key's value directly from the .env file (bypasses config cache). */ protected function readEnvValue(string $envPath, string $key): ?string { $content = file_get_contents($envPath); if ($content === false) { return null; } if (preg_match('/^' . preg_quote($key, '/') . '=(.*)$/m', $content, $matches)) { return trim($matches[1], " \t\"'"); } return null; } /** * Update or append a key=value pair in the .env file. */ protected function writeEnv(string $envPath, string $key, string $value): void { $content = file_get_contents($envPath); if ($content === false) { throw new \RuntimeException('Cannot read .env file'); } // Escape any special characters in the value (spaces, quotes, etc.) $escaped = $this->escapeEnvValue($value); $line = $key . '=' . $escaped; // Replace existing key (with or without quotes, any value) if (preg_match('/^' . preg_quote($key, '/') . '=.*/m', $content)) { $content = preg_replace('/^' . preg_quote($key, '/') . '=.*/m', $line, $content); } else { // Append at the end if the key doesn't exist $content = rtrim($content) . "\n" . $line . "\n"; } if (file_put_contents($envPath, $content) === false) { throw new \RuntimeException('Cannot write .env file'); } } /** * Wrap value in double-quotes if it contains spaces or special shell characters. */ protected function escapeEnvValue(string $value): string { if (preg_match('/[\s"\'\\\\#$]/', $value)) { return '"' . addcslashes($value, '"\\') . '"'; } return $value; } /** * Ask Asterisk to reload its manager configuration so the new user goes live. */ protected function reloadManager(): void { // Try the Asterisk CLI first (covers both native installs and containers) $candidates = [ ['/usr/sbin/asterisk', '-rx', 'manager reload'], ['/usr/bin/asterisk', '-rx', 'manager reload'], ]; foreach ($candidates as $cmd) { if (file_exists($cmd[0])) { $proc = new \Symfony\Component\Process\Process($cmd); $proc->setTimeout(10); $proc->run(); if ($proc->isSuccessful()) { $this->line(' Asterisk manager reloaded.'); } else { $this->warn(' Asterisk manager reload returned: ' . trim($proc->getOutput() ?: $proc->getErrorOutput())); } return; } } $this->warn(' Asterisk CLI not found — manager reload skipped. Restart Asterisk manually to activate the new user.'); } }