generateWithSshKeygen($application); $application->forceFill([ 'deploy_key_public' => $keyPair['public'], 'deploy_key_private' => $keyPair['private'], 'deploy_key_fingerprint' => $keyPair['fingerprint'] ?? $this->fingerprint($keyPair['public']), 'deploy_key_installed_at' => null, ])->save(); return $application->refresh(); } /** * @return array{public: string, private: string, fingerprint: string} */ private function generateWithSshKeygen(Application $application): array { $directory = storage_path('app/private/deploy-keys/'.str()->uuid()->toString()); $privateKeyPath = $directory.'/id_ed25519'; File::ensureDirectoryExists($directory, 0700); try { $result = Process::run([ 'ssh-keygen', '-t', 'ed25519', '-C', "keystone-application-{$application->id}", '-N', '', '-f', $privateKeyPath, ]); if ($result->failed()) { throw new RuntimeException('Unable to generate deploy key: '.$result->errorOutput()); } return [ 'public' => trim(File::get($privateKeyPath.'.pub')), 'private' => trim(File::get($privateKeyPath)), 'fingerprint' => $this->fingerprint(trim(File::get($privateKeyPath.'.pub'))), ]; } finally { rescue(fn () => File::deleteDirectory($directory), report: false); } } private function fingerprint(string $publicKey): string { try { $parts = explode(' ', trim($publicKey)); $keyMaterial = $parts[1] ?? $publicKey; return 'SHA256:'.rtrim(strtr(base64_encode(hash('sha256', base64_decode($keyMaterial, true) ?: $publicKey, true)), '+/', '-_'), '='); } catch (Throwable) { return 'SHA256:'.hash('sha256', $publicKey); } } }