From 8aee3a64ea5213c4279c9caf762fb94a5ad2ed91 Mon Sep 17 00:00:00 2001 From: Alexander Andreev Date: Sun, 22 Feb 2026 05:04:55 +0300 Subject: [PATCH] Admin: allow configurable deploy remote for update checks --- README.md | 17 +++++++--- admin.php | 13 ++++++-- config.php.example | 2 ++ lib/admin_deploy.php | 68 ++++++++++++++++++++++++++++++++++---- lib/admin_post_actions.php | 12 ++++--- scripts/deploy.sh | 16 +++++++-- 6 files changed, 108 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 72d6f79..bcc6dd5 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,12 @@ cp config.php.example config.php 2. Заполни доступы к БД в `config.php`. + Для деплоя из админки в `config.php` можно задать: + + - `deploy.remote_name` (обычно `origin`), + - `deploy.remote_url` (по умолчанию `git@github.com:wrkandreev/reframe.git`), + - `deploy.branch` (`main` или `dev`). + 3. Создай `secrets.php`: ```bash @@ -155,15 +161,16 @@ php scripts/generate_thumbs.php Деплой запускается из админки (вкладка `Настройки`): -- кнопка `Проверить обновления` делает `git fetch` и сравнивает `HEAD` с `origin/`, +- кнопка `Проверить обновления` делает `git fetch` и сравнивает `HEAD` с `/`, - если локальная ветка отстает и не расходится (`behind > 0`, `ahead = 0`) — показывается кнопка `Обновить проект`. Скрипт `scripts/deploy.sh`: -1. делает `git fetch --all --prune`, -2. переключает код на `origin/` через `git reset --hard`, -3. запускает миграции `php scripts/migrate.php`, -4. сохраняет runtime-папки (`photos`, `thumbs`, `data`). +1. настраивает remote из `REMOTE_NAME`/`REMOTE_URL` (если передан `REMOTE_URL`), +2. делает `git fetch --prune`, +3. переключает код на `/` через `git reset --hard`, +4. запускает миграции `php scripts/migrate.php`, +5. сохраняет runtime-папки (`photos`, `thumbs`, `data`). Важно: деплой-скрипт перетирает рабочие изменения в репозитории на сервере. diff --git a/admin.php b/admin.php index 6411bdf..cfe6f91 100644 --- a/admin.php +++ b/admin.php @@ -48,6 +48,11 @@ if ($tokenExpected === '' || !hash_equals($tokenExpected, $tokenIncoming)) { } $deployConfig = (array)($config['deploy'] ?? []); +$deployRemoteName = trim((string)($deployConfig['remote_name'] ?? 'origin')); +if ($deployRemoteName === '') { + $deployRemoteName = 'origin'; +} +$deployRemoteUrl = trim((string)($deployConfig['remote_url'] ?? '')); $allowedDeployBranches = ['main', 'dev']; $defaultDeployBranch = trim((string)($deployConfig['branch'] ?? 'main')); if (!in_array($defaultDeployBranch, $allowedDeployBranches, true)) { @@ -83,6 +88,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $result = adminHandlePostAction($action, $isAjax, __DIR__, [ + 'remote_name' => $deployRemoteName, + 'remote_url' => $deployRemoteUrl, 'branch' => $deployBranch, 'script' => $deployScript, 'php_bin' => $deployPhpBin, @@ -285,10 +292,12 @@ function assetUrl(string $path): string { $f=__DIR__ . '/' . ltrim($path,'/'); $

Обновление проекта

-

Выбери ветку для проверки и обновления:

+

Remote:

+

Выбери ветку для проверки и обновления:

+

- Локально: · origin/: · behind: · ahead: + Локально: · /: · behind: · ahead:
diff --git a/config.php.example b/config.php.example index 1917285..b28cef8 100644 --- a/config.php.example +++ b/config.php.example @@ -9,6 +9,8 @@ return [ 'charset' => 'utf8mb4', ], 'deploy' => [ + 'remote_name' => 'origin', + 'remote_url' => 'git@github.com:wrkandreev/reframe.git', 'branch' => 'main', 'script' => __DIR__ . '/scripts/deploy.sh', 'php_bin' => 'php', diff --git a/lib/admin_deploy.php b/lib/admin_deploy.php index 9d08235..b58b9ce 100644 --- a/lib/admin_deploy.php +++ b/lib/admin_deploy.php @@ -2,21 +2,26 @@ declare(strict_types=1); -function adminCheckForUpdates(string $projectRoot, string $branch): array +function adminCheckForUpdates(string $projectRoot, string $branch, string $remoteName = 'origin', string $remoteUrl = ''): array { if (!is_dir($projectRoot . '/.git')) { throw new RuntimeException('Репозиторий не найден: .git отсутствует'); } - $fetch = adminRunShellCommand('git fetch origin ' . escapeshellarg($branch) . ' --prune', $projectRoot); + $remoteName = adminNormalizeRemoteName($remoteName); + adminEnsureRemote($projectRoot, $remoteName, $remoteUrl); + + $remoteRef = $remoteName . '/' . $branch; + + $fetch = adminRunShellCommand('git fetch ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($branch) . ' --prune', $projectRoot); if ($fetch['code'] !== 0) { - throw new RuntimeException('Не удалось обновить данные из origin: ' . adminTailOutput($fetch['output'])); + throw new RuntimeException('Не удалось обновить данные из ' . $remoteName . ': ' . adminTailOutput($fetch['output'])); } $local = adminRunShellCommand('git rev-parse --short=12 HEAD', $projectRoot); - $remote = adminRunShellCommand('git rev-parse --short=12 origin/' . escapeshellarg($branch), $projectRoot); - $behindRaw = adminRunShellCommand('git rev-list --count HEAD..origin/' . escapeshellarg($branch), $projectRoot); - $aheadRaw = adminRunShellCommand('git rev-list --count origin/' . escapeshellarg($branch) . '..HEAD', $projectRoot); + $remote = adminRunShellCommand('git rev-parse --short=12 ' . escapeshellarg($remoteRef), $projectRoot); + $behindRaw = adminRunShellCommand('git rev-list --count HEAD..' . escapeshellarg($remoteRef), $projectRoot); + $aheadRaw = adminRunShellCommand('git rev-list --count ' . escapeshellarg($remoteRef) . '..HEAD', $projectRoot); if ($local['code'] !== 0 || $remote['code'] !== 0 || $behindRaw['code'] !== 0 || $aheadRaw['code'] !== 0) { throw new RuntimeException('Не удалось определить состояние ветки'); @@ -36,6 +41,7 @@ function adminCheckForUpdates(string $projectRoot, string $branch): array return [ 'state' => $state, + 'remote_name' => $remoteName, 'branch' => $branch, 'local_ref' => trim($local['output']), 'remote_ref' => trim($remote['output']), @@ -45,15 +51,19 @@ function adminCheckForUpdates(string $projectRoot, string $branch): array ]; } -function adminRunDeployScript(string $projectRoot, string $branch, string $scriptPath, string $phpBin): array +function adminRunDeployScript(string $projectRoot, string $branch, string $scriptPath, string $phpBin, string $remoteName = 'origin', string $remoteUrl = ''): array { if (!is_file($scriptPath)) { throw new RuntimeException('Скрипт деплоя не найден: ' . $scriptPath); } + $remoteName = adminNormalizeRemoteName($remoteName); + $run = adminRunShellCommand('bash ' . escapeshellarg($scriptPath), $projectRoot, [ 'BRANCH' => $branch, 'PHP_BIN' => $phpBin, + 'REMOTE_NAME' => $remoteName, + 'REMOTE_URL' => $remoteUrl, ]); return [ @@ -63,6 +73,50 @@ function adminRunDeployScript(string $projectRoot, string $branch, string $scrip ]; } +function adminEnsureRemote(string $projectRoot, string $remoteName, string $remoteUrl): void +{ + $getRemote = adminRunShellCommand('git remote get-url ' . escapeshellarg($remoteName), $projectRoot); + if ($getRemote['code'] !== 0) { + if ($remoteUrl === '') { + throw new RuntimeException('Remote ' . $remoteName . ' не найден'); + } + + $add = adminRunShellCommand('git remote add ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($remoteUrl), $projectRoot); + if ($add['code'] !== 0) { + throw new RuntimeException('Не удалось добавить remote ' . $remoteName . ': ' . adminTailOutput($add['output'])); + } + return; + } + + if ($remoteUrl === '') { + return; + } + + $currentUrl = trim($getRemote['output']); + if ($currentUrl === $remoteUrl) { + return; + } + + $set = adminRunShellCommand('git remote set-url ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($remoteUrl), $projectRoot); + if ($set['code'] !== 0) { + throw new RuntimeException('Не удалось обновить remote ' . $remoteName . ': ' . adminTailOutput($set['output'])); + } +} + +function adminNormalizeRemoteName(string $remoteName): string +{ + $remoteName = trim($remoteName); + if ($remoteName === '') { + return 'origin'; + } + + if (!preg_match('/^[A-Za-z0-9._-]+$/', $remoteName)) { + throw new RuntimeException('Некорректное имя remote'); + } + + return $remoteName; +} + function adminRunShellCommand(string $command, string $cwd, array $env = []): array { $envPrefix = ''; diff --git a/lib/admin_post_actions.php b/lib/admin_post_actions.php index 725737e..795be6f 100644 --- a/lib/admin_post_actions.php +++ b/lib/admin_post_actions.php @@ -144,8 +144,10 @@ function adminHandlePostAction(string $action, bool $isAjax, string $projectRoot } case 'check_updates': { + $remoteName = (string)($deployOptions['remote_name'] ?? 'origin'); + $remoteUrl = (string)($deployOptions['remote_url'] ?? ''); $branch = (string)($deployOptions['branch'] ?? 'main'); - $deployStatus = adminCheckForUpdates($projectRoot, $branch); + $deployStatus = adminCheckForUpdates($projectRoot, $branch, $remoteName, $remoteUrl); $state = (string)($deployStatus['state'] ?? ''); if ($state === 'update_available') { @@ -162,11 +164,13 @@ function adminHandlePostAction(string $action, bool $isAjax, string $projectRoot } case 'deploy_updates': { + $remoteName = (string)($deployOptions['remote_name'] ?? 'origin'); + $remoteUrl = (string)($deployOptions['remote_url'] ?? ''); $branch = (string)($deployOptions['branch'] ?? 'main'); $scriptPath = (string)($deployOptions['script'] ?? ($projectRoot . '/scripts/deploy.sh')); $phpBin = (string)($deployOptions['php_bin'] ?? 'php'); - $deployStatus = adminCheckForUpdates($projectRoot, $branch); + $deployStatus = adminCheckForUpdates($projectRoot, $branch, $remoteName, $remoteUrl); if (!(bool)($deployStatus['can_deploy'] ?? false)) { $state = (string)($deployStatus['state'] ?? ''); if ($state === 'up_to_date') { @@ -182,13 +186,13 @@ function adminHandlePostAction(string $action, bool $isAjax, string $projectRoot throw new RuntimeException('Нельзя применить обновление в текущем состоянии ветки.'); } - $deployResult = adminRunDeployScript($projectRoot, $branch, $scriptPath, $phpBin); + $deployResult = adminRunDeployScript($projectRoot, $branch, $scriptPath, $phpBin, $remoteName, $remoteUrl); $deployOutput = (string)($deployResult['output'] ?? ''); if (!(bool)($deployResult['ok'] ?? false)) { throw new RuntimeException('Деплой завершился с ошибкой: ' . ($deployOutput !== '' ? $deployOutput : ('код ' . (int)($deployResult['code'] ?? 1)))); } - $deployStatus = adminCheckForUpdates($projectRoot, $branch); + $deployStatus = adminCheckForUpdates($projectRoot, $branch, $remoteName, $remoteUrl); $message = 'Обновление выполнено.'; break; } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index e0ea8cf..5e6169a 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -6,10 +6,14 @@ set -euo pipefail # bash scripts/deploy.sh # Optional env: # APP_DIR=/home/USER/www/photo-gallery +# REMOTE_NAME=origin +# REMOTE_URL=git@github.com:wrkandreev/reframe.git # BRANCH=main # PHP_BIN=php APP_DIR="${APP_DIR:-$(cd "$(dirname "$0")/.." && pwd)}" +REMOTE_NAME="${REMOTE_NAME:-origin}" +REMOTE_URL="${REMOTE_URL:-}" BRANCH="${BRANCH:-main}" PHP_BIN="${PHP_BIN:-php}" @@ -41,8 +45,16 @@ if [ "$current_branch" != "$BRANCH" ]; then git checkout "$BRANCH" fi -git fetch --all --prune -git reset --hard "origin/$BRANCH" +if [ -n "$REMOTE_URL" ]; then + if git remote get-url "$REMOTE_NAME" >/dev/null 2>&1; then + git remote set-url "$REMOTE_NAME" "$REMOTE_URL" + else + git remote add "$REMOTE_NAME" "$REMOTE_URL" + fi +fi + +git fetch "$REMOTE_NAME" "$BRANCH" --prune +git reset --hard "$REMOTE_NAME/$BRANCH" # Run DB migrations required by current code "$PHP_BIN" scripts/migrate.php