diff --git a/bin/release-watcher.php b/bin/release-watcher.php index 8326b24..0df08d7 100755 --- a/bin/release-watcher.php +++ b/bin/release-watcher.php @@ -18,6 +18,7 @@ use Gitcd\Helpers\GitHub; use Gitcd\Helpers\AuditLog; use Gitcd\Helpers\BlueGreen; +use Gitcd\Helpers\GitHubApp; use Gitcd\Utils\Json; use Gitcd\Utils\JsonLock; @@ -41,6 +42,15 @@ if ($activeRelease && $activeRelease !== $currentRelease) { echo "[" . date('Y-m-d H:i:s') . "] New release detected: {$activeRelease} (was: " . ($currentRelease ?: 'none') . ")\n"; + // Refresh GitHub App credentials if configured (tokens expire after 1 hour) + if (GitHubApp::isConfigured()) { + $creds = GitHubApp::loadCredentials(); + $appOwner = $creds['owner'] ?? null; + if ($appOwner) { + GitHubApp::refreshGitCredentials($appOwner); + } + } + // Fetch latest tags $remote = Git::remoteName($repo_dir) ?: 'origin'; Shell::run("git -C " . escapeshellarg($repo_dir) . " fetch {$remote} --tags 2>/dev/null"); diff --git a/src/Commands/ProtocolInit.php b/src/Commands/ProtocolInit.php index 65f637c..0425518 100644 --- a/src/Commands/ProtocolInit.php +++ b/src/Commands/ProtocolInit.php @@ -479,7 +479,7 @@ protected function flowSlaveNode( } } - $canAccess = $this->testRepoAccess($gitRemote); + $canAccess = $this->testRepoAccess(GitHubApp::resolveUrl($gitRemote)); if (!$canAccess) { $output->writeln(" ✗ Cannot access repository"); @@ -493,11 +493,14 @@ protected function flowSlaveNode( $output->writeln(" ✓ Repository accessible"); } + // Resolve URL for git operations (SSH → HTTPS when GitHub App is configured) + $gitCloneUrl = GitHubApp::resolveUrl($gitRemote); + // Fetch protocol.json from the remote repo $output->writeln(''); $output->writeln(" › Fetching project configuration..."); - $protocolData = $this->fetchRemoteProtocolJson($gitRemote); + $protocolData = $this->fetchRemoteProtocolJson($gitCloneUrl); $projectName = $protocolData['name'] ?? basename(parse_url($gitRemote, PHP_URL_PATH) ?: $gitRemote, '.git'); if (!empty($protocolData)) { @@ -606,7 +609,7 @@ protected function flowSlaveNode( if ($awaitingRelease) { $output->writeln(''); $output->writeln(" › Checking for new release tags..."); - $tagsCheck = Shell::run("GIT_TERMINAL_PROMPT=0 git ls-remote --tags " . escapeshellarg($gitRemote) . " 2>/dev/null"); + $tagsCheck = Shell::run("GIT_TERMINAL_PROMPT=0 git ls-remote --tags " . escapeshellarg($gitCloneUrl) . " 2>/dev/null"); $hasTags = false; if ($tagsCheck) { foreach (explode("\n", trim($tagsCheck)) as $line) { @@ -643,7 +646,7 @@ protected function flowSlaveNode( // List available release tags from the remote $output->writeln(" › Checking for available releases..."); - $tagsOutput = Shell::run("GIT_TERMINAL_PROMPT=0 git ls-remote --tags " . escapeshellarg($gitRemote) . " 2>/dev/null"); + $tagsOutput = Shell::run("GIT_TERMINAL_PROMPT=0 git ls-remote --tags " . escapeshellarg($gitCloneUrl) . " 2>/dev/null"); $tags = []; if ($tagsOutput) { @@ -680,7 +683,7 @@ protected function flowSlaveNode( if ($helper->ask($input, $output, $question)) { // Determine branch $branch = 'main'; - $branchOutput = Shell::run("GIT_TERMINAL_PROMPT=0 git ls-remote --symref " . escapeshellarg($gitRemote) . " HEAD 2>/dev/null"); + $branchOutput = Shell::run("GIT_TERMINAL_PROMPT=0 git ls-remote --symref " . escapeshellarg($gitCloneUrl) . " HEAD 2>/dev/null"); if ($branchOutput && preg_match('#ref: refs/heads/(\S+)#', $branchOutput, $m)) { $branch = $m[1]; } @@ -690,7 +693,7 @@ protected function flowSlaveNode( $nightlyDir = rtrim($releasesDir, '/') . '/' . $branch; if (!is_dir($nightlyDir)) { - Shell::run("GIT_TERMINAL_PROMPT=0 git clone " . escapeshellarg($gitRemote) . " " . escapeshellarg($nightlyDir) . " --branch " . escapeshellarg($branch) . " 2>&1"); + Shell::run("GIT_TERMINAL_PROMPT=0 git clone " . escapeshellarg($gitCloneUrl) . " " . escapeshellarg($nightlyDir) . " --branch " . escapeshellarg($branch) . " 2>&1"); } else { Shell::run("GIT_TERMINAL_PROMPT=0 git -C " . escapeshellarg($nightlyDir) . " pull 2>&1"); } @@ -730,7 +733,7 @@ protected function flowSlaveNode( $output->writeln(''); $output->writeln(" › Cloning release {$selectedTag}..."); - $cloneSuccess = ReleaseBuilder::initReleaseDir($repo_dir, $selectedTag, $gitRemote); + $cloneSuccess = ReleaseBuilder::initReleaseDir($repo_dir, $selectedTag, $gitCloneUrl); if ($cloneSuccess) { $releaseDir = BlueGreen::getReleaseDir($repo_dir, $selectedTag); ReleaseBuilder::checkoutVersion($releaseDir, $selectedTag); @@ -779,9 +782,9 @@ protected function flowSlaveNode( $output->writeln(" › Cloning primary repository..."); if ($currentStrategy === 'branch' && $currentBranch) { - Shell::run("GIT_TERMINAL_PROMPT=0 git clone " . escapeshellarg($gitRemote) . " " . escapeshellarg($activeCloneDir) . " --branch " . escapeshellarg($currentBranch) . " 2>&1"); + Shell::run("GIT_TERMINAL_PROMPT=0 git clone " . escapeshellarg($gitCloneUrl) . " " . escapeshellarg($activeCloneDir) . " --branch " . escapeshellarg($currentBranch) . " 2>&1"); } else { - ReleaseBuilder::initReleaseDir($repo_dir, $currentRelease, $gitRemote); + ReleaseBuilder::initReleaseDir($repo_dir, $currentRelease, $gitCloneUrl); ReleaseBuilder::checkoutVersion($activeCloneDir, $currentRelease); } if (is_dir($activeCloneDir)) { @@ -804,7 +807,8 @@ protected function flowSlaveNode( } $output->writeln(" › Cloning configuration repository..."); - $cloneResult = Shell::run("GIT_TERMINAL_PROMPT=0 git clone " . escapeshellarg($configRemote) . " " . escapeshellarg($configDir)); + $configCloneUrl = GitHubApp::resolveUrl($configRemote); + $cloneResult = Shell::run("GIT_TERMINAL_PROMPT=0 git clone " . escapeshellarg($configCloneUrl) . " " . escapeshellarg($configDir)); if (is_dir($configDir)) { // Switch to environment branch if it exists diff --git a/src/Helpers/BlueGreen/ReleaseBuilder.php b/src/Helpers/BlueGreen/ReleaseBuilder.php index 0cc4ece..bb0a4fe 100644 --- a/src/Helpers/BlueGreen/ReleaseBuilder.php +++ b/src/Helpers/BlueGreen/ReleaseBuilder.php @@ -52,8 +52,9 @@ public static function initReleaseDir(string $repo_dir, string $version, string Shell::run("rm -rf " . escapeshellarg(rtrim($releaseDir, '/'))); } + $cloneUrl = \Gitcd\Helpers\GitHubApp::resolveUrl($gitRemote); $result = Shell::run( - "git clone " . escapeshellarg($gitRemote) . " " . escapeshellarg(rtrim($releaseDir, '/')) . " 2>&1", + "GIT_TERMINAL_PROMPT=0 git clone " . escapeshellarg($cloneUrl) . " " . escapeshellarg(rtrim($releaseDir, '/')) . " 2>&1", $returnVar ); diff --git a/src/Helpers/GitHubApp.php b/src/Helpers/GitHubApp.php index 6c36073..10ff16e 100644 --- a/src/Helpers/GitHubApp.php +++ b/src/Helpers/GitHubApp.php @@ -227,6 +227,32 @@ public static function refreshGitCredentials(?string $owner = null): bool return true; } + /** + * Resolve a git remote URL for cloning/fetching. + * + * If a GitHub App is configured, converts SSH URLs to HTTPS so the + * credential helper can provide the installation token. Non-GitHub + * URLs and URLs when no App is configured are returned unchanged. + */ + public static function resolveUrl(string $gitRemote): string + { + if (!self::isConfigured()) { + return $gitRemote; + } + + // Convert git@github.com:owner/repo.git → https://github.com/owner/repo.git + if (preg_match('#^git@github\.com:(.+)$#', $gitRemote, $m)) { + return 'https://github.com/' . $m[1]; + } + + // Convert ssh://git@github.com/owner/repo.git + if (preg_match('#^ssh://git@github\.com/(.+)$#', $gitRemote, $m)) { + return 'https://github.com/' . $m[1]; + } + + return $gitRemote; + } + /** * Base64url encode (JWT-safe). */