Add secure deploy webhook with local config example
This commit is contained in:
parent
b5c49caeb2
commit
6c57f8d5a7
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -13,5 +13,8 @@ Thumbs.db
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# Local secrets
|
||||||
|
deploy-config.php
|
||||||
|
|
||||||
# Logs/temp
|
# Logs/temp
|
||||||
*.log
|
*.log
|
||||||
|
|
|
||||||
33
README.md
33
README.md
|
|
@ -6,7 +6,7 @@
|
||||||
- при каждом открытии страницы проверяет, появились ли новые/обновлённые фото,
|
- при каждом открытии страницы проверяет, появились ли новые/обновлённые фото,
|
||||||
- создаёт и обновляет превью в `thumbs/`,
|
- создаёт и обновляет превью в `thumbs/`,
|
||||||
- показывает категории и фото в веб-интерфейсе,
|
- показывает категории и фото в веб-интерфейсе,
|
||||||
- открывает большую фотографию в лайтбоксе или в новой вкладке.
|
- открывает большую фотографию в лайтбоксе.
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
|
|
@ -14,11 +14,13 @@
|
||||||
photo-gallery/
|
photo-gallery/
|
||||||
├─ index.php # основной скрипт: индексация + HTML
|
├─ index.php # основной скрипт: индексация + HTML
|
||||||
├─ style.css # стили (material-like, строгий)
|
├─ style.css # стили (material-like, строгий)
|
||||||
├─ app.js # лайтбокс
|
├─ app.js # лайтбокс
|
||||||
├─ photos/ # исходные фото по категориям (папкам)
|
├─ deploy.php # webhook-триггер деплоя
|
||||||
├─ thumbs/ # автогенерируемые превью
|
├─ deploy-config.php.example # пример конфига webhook
|
||||||
|
├─ photos/ # исходные фото по категориям (папкам)
|
||||||
|
├─ thumbs/ # автогенерируемые превью
|
||||||
└─ data/
|
└─ data/
|
||||||
└─ last_indexed.txt # timestamp последней индексации
|
└─ last_indexed.txt # timestamp последней индексации
|
||||||
```
|
```
|
||||||
|
|
||||||
## Как работает индексация
|
## Как работает индексация
|
||||||
|
|
@ -89,6 +91,27 @@ bash scripts/deploy.sh
|
||||||
BRANCH=master bash scripts/deploy.sh
|
BRANCH=master bash scripts/deploy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Удалённый запуск деплоя по ссылке (webhook)
|
||||||
|
|
||||||
|
1. На хостинге создай конфиг из примера:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp deploy-config.php.example deploy-config.php
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Заполни в `deploy-config.php` минимум:
|
||||||
|
- `token` (длинный секрет)
|
||||||
|
- при желании `allowed_ips`
|
||||||
|
- при желании `basic_auth_user/basic_auth_pass`
|
||||||
|
|
||||||
|
3. Запуск деплоя:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://<домен>/deploy.php?token=<твой_секрет>
|
||||||
|
```
|
||||||
|
|
||||||
|
Рекомендация: включить IP whitelist и Basic Auth.
|
||||||
|
|
||||||
## Примечания
|
## Примечания
|
||||||
|
|
||||||
- Превью генерируются в формате JPEG с качеством ~82.
|
- Превью генерируются в формате JPEG с качеством ~82.
|
||||||
|
|
|
||||||
25
deploy-config.php.example
Normal file
25
deploy-config.php.example
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copy this file to deploy-config.php and fill secrets.
|
||||||
|
* deploy-config.php is ignored by git.
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
// REQUIRED: long random token (40+ chars)
|
||||||
|
'token' => 'CHANGE_ME_TO_LONG_RANDOM_TOKEN',
|
||||||
|
|
||||||
|
// Optional: HTTP Basic auth layer
|
||||||
|
'basic_auth_user' => '',
|
||||||
|
'basic_auth_pass' => '',
|
||||||
|
|
||||||
|
// Optional: allow only these client IPs (empty = allow all)
|
||||||
|
'allowed_ips' => [
|
||||||
|
// '1.2.3.4',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Deploy options
|
||||||
|
'branch' => 'main',
|
||||||
|
'deploy_script' => __DIR__ . '/scripts/deploy.sh',
|
||||||
|
|
||||||
|
// Where to write webhook logs
|
||||||
|
'log_file' => __DIR__ . '/data/deploy-webhook.log',
|
||||||
|
];
|
||||||
86
deploy.php
Normal file
86
deploy.php
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$configPath = __DIR__ . '/deploy-config.php';
|
||||||
|
if (!is_file($configPath)) {
|
||||||
|
http_response_code(500);
|
||||||
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
|
echo "deploy-config.php not found. Create it from deploy-config.php.example\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array<string,mixed> $config */
|
||||||
|
$config = require $configPath;
|
||||||
|
$tokenExpected = (string)($config['token'] ?? '');
|
||||||
|
$allowedIps = (array)($config['allowed_ips'] ?? []);
|
||||||
|
$basicUser = (string)($config['basic_auth_user'] ?? '');
|
||||||
|
$basicPass = (string)($config['basic_auth_pass'] ?? '');
|
||||||
|
$branch = (string)($config['branch'] ?? 'main');
|
||||||
|
$deployScript = (string)($config['deploy_script'] ?? (__DIR__ . '/scripts/deploy.sh'));
|
||||||
|
$logFile = (string)($config['log_file'] ?? (__DIR__ . '/data/deploy-webhook.log'));
|
||||||
|
|
||||||
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
|
|
||||||
|
if ($basicUser !== '' || $basicPass !== '') {
|
||||||
|
$authUser = $_SERVER['PHP_AUTH_USER'] ?? '';
|
||||||
|
$authPass = $_SERVER['PHP_AUTH_PW'] ?? '';
|
||||||
|
if (!hash_equals($basicUser, (string)$authUser) || !hash_equals($basicPass, (string)$authPass)) {
|
||||||
|
header('WWW-Authenticate: Basic realm="Deploy"');
|
||||||
|
http_response_code(401);
|
||||||
|
echo "Unauthorized\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$clientIp = (string)($_SERVER['REMOTE_ADDR'] ?? 'unknown');
|
||||||
|
if ($allowedIps !== [] && !in_array($clientIp, $allowedIps, true)) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo "Forbidden: IP not allowed\n";
|
||||||
|
logLine($logFile, "DENY ip={$clientIp} reason=ip_not_allowed");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokenIncoming = (string)($_REQUEST['token'] ?? '');
|
||||||
|
if ($tokenExpected === '' || !hash_equals($tokenExpected, $tokenIncoming)) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo "Forbidden: invalid token\n";
|
||||||
|
logLine($logFile, "DENY ip={$clientIp} reason=bad_token");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_file($deployScript)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Deploy script not found\n";
|
||||||
|
logLine($logFile, "ERROR ip={$clientIp} reason=script_missing path={$deployScript}");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = 'BRANCH=' . escapeshellarg($branch) . ' bash ' . escapeshellarg($deployScript) . ' 2>&1';
|
||||||
|
exec($cmd, $output, $code);
|
||||||
|
|
||||||
|
$preview = implode("\n", array_slice($output, -30));
|
||||||
|
logLine(
|
||||||
|
$logFile,
|
||||||
|
"RUN ip={$clientIp} code={$code} branch={$branch} output=" . str_replace(["\n", "\r"], ['\\n', ''], $preview)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($code !== 0) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Deploy failed\n\n";
|
||||||
|
echo $preview . "\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "OK: deploy completed\n\n";
|
||||||
|
echo $preview . "\n";
|
||||||
|
|
||||||
|
function logLine(string $path, string $line): void
|
||||||
|
{
|
||||||
|
$dir = dirname($path);
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
@mkdir($dir, 0775, true);
|
||||||
|
}
|
||||||
|
$ts = date('Y-m-d H:i:s');
|
||||||
|
@file_put_contents($path, "[{$ts}] {$line}\n", FILE_APPEND);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user