From 48ffd217b861fea1e12bef7a65514f27d3e89779 Mon Sep 17 00:00:00 2001 From: F4ilji Date: Thu, 2 Jul 2026 17:26:38 +0500 Subject: [PATCH] fix: secure deploy API, remove Docker-in-Docker, add DB backup - Add web+superadmin middleware to /api/deploy routes - Return JSON 403 in EnsureUserIsSuperadmin for API requests - Remove Docker CLI and docker.sock from app container - Replace docker compose restart with supervisorctl - Add mysqldump backup step before migrations --- _docker/app/Dockerfile | 11 +---- app/Console/Commands/DeployRunCommand.php | 43 +++++++++++++------ .../Dashboard/UI/API/Routes/api.php | 6 ++- .../Middleware/EnsureUserIsSuperadmin.php | 4 ++ docker-compose.yml | 1 - 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/_docker/app/Dockerfile b/_docker/app/Dockerfile index f1bc103..f129457 100755 --- a/_docker/app/Dockerfile +++ b/_docker/app/Dockerfile @@ -33,13 +33,6 @@ RUN apt-get update && apt-get install -y \ && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ && apt-get install -y nodejs \ && npm install -g npm \ - # Docker CLI for deploy script - && apt-get install -y ca-certificates curl gnupg \ - && install -m 0755 -d /etc/apt/keyrings \ - && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ - && chmod a+r /etc/apt/keyrings/docker.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list \ - && apt-get update && apt-get install -y docker-ce-cli docker-compose-plugin \ # Очистка кэша для уменьшения размера образа && apt-get clean && rm -rf /var/lib/apt/lists/* @@ -63,10 +56,8 @@ RUN curl -sS https://getcomposer.org/installer | php -- \ RUN mkdir -p storage/framework/views storage/framework/sessions storage/framework/cache storage/logs bootstrap/cache # Добавляем www-data в группу с GID=1000 (совпадает с GID хост-пользователя) -# и в группу docker для доступа к Docker socket при деплое RUN groupadd -g 1000 hostgroup 2>/dev/null || true \ - && groupadd docker 2>/dev/null || true \ - && usermod -aG hostgroup -G docker www-data + && usermod -aG hostgroup www-data # Установка правильных прав доступа RUN chown -R www-data:www-data storage bootstrap/cache \ diff --git a/app/Console/Commands/DeployRunCommand.php b/app/Console/Commands/DeployRunCommand.php index d430d8c..942cf76 100644 --- a/app/Console/Commands/DeployRunCommand.php +++ b/app/Console/Commands/DeployRunCommand.php @@ -13,7 +13,7 @@ class DeployRunCommand extends Command private string $statusFile = '/tmp/deploy-status.json'; private string $lockFile = '/tmp/deploy.lock'; - private int $totalSteps = 12; + private int $totalSteps = 13; public function handle(): int { @@ -102,19 +102,36 @@ class DeployRunCommand extends Command $this->execCommand('composer install --no-dev --no-interaction --prefer-dist --no-cache'); }); - $this->executeStep(4, 'Migrate', function () { + $this->executeStep(4, 'Database backup', function () { + $backupDir = storage_path('app/backups'); + if (!is_dir($backupDir)) { + mkdir($backupDir, 0755, true); + } + $filename = 'backup_' . now()->format('Y-m-d_H-i-s') . '.sql.gz'; + $filepath = $backupDir . '/' . $filename; + $dbHost = env('DB_HOST', 'db'); + $dbName = env('DB_DATABASE', 'ntspi_db'); + $dbUser = env('DB_USERNAME', 'admin'); + $dbPass = env('DB_PASSWORD', 'secret'); + $this->execCommand( + "mysqldump -h {$dbHost} -u {$dbUser} -p{$dbPass} {$dbName} | gzip > {$filepath}" + ); + $this->addLog("Backup saved: {$filepath}"); + }); + + $this->executeStep(5, 'Migrate', function () { $this->execCommand('php artisan migrate --force'); }); - $this->executeStep(5, 'NPM install', function () { + $this->executeStep(6, 'NPM install', function () { $this->execCommand('npm install --legacy-peer-deps'); }); - $this->executeStep(6, 'NPM build', function () { + $this->executeStep(7, 'NPM build', function () { $this->execCommand('npm run build'); }); - $this->executeStep(7, 'Cache clear', function () { + $this->executeStep(8, 'Cache clear', function () { $this->execCommand('php artisan cache:clear'); $this->execCommand('php artisan config:clear'); $this->execCommand('php artisan route:clear'); @@ -123,7 +140,7 @@ class DeployRunCommand extends Command $this->execCommand('php artisan filament:optimize-clear'); }); - $this->executeStep(8, 'Cache warm', function () { + $this->executeStep(9, 'Cache warm', function () { $this->execCommand('php artisan routes:register'); $this->execCommand('php artisan route:cache'); $this->execCommand('php artisan view:cache'); @@ -132,21 +149,21 @@ class DeployRunCommand extends Command $this->execCommand('php artisan filament:optimize'); }); - $this->executeStep(9, 'Fix permissions', function () { + $this->executeStep(10, 'Fix permissions', function () { $this->execCommand('chown -R www-data:www-data storage bootstrap/cache'); $this->execCommand('chmod -R 775 storage bootstrap/cache'); }); - $this->executeStep(10, 'Restart containers', function () { - $this->execCommand('docker compose down'); - $this->execCommand('docker compose up -d --remove-orphans'); + $this->executeStep(11, 'Restart processes', function () { + $this->execCommand('supervisorctl restart inertia-ssr'); + $this->execCommand('supervisorctl restart cron'); }); - $this->executeStep(11, 'Wait 10 seconds', function () { - sleep(10); + $this->executeStep(12, 'Restart queue workers', function () { + $this->execCommand('php artisan queue:restart'); }); - $this->executeStep(12, 'Maintenance off', function () { + $this->executeStep(13, 'Maintenance off', function () { $this->execCommand('php artisan up'); }); } diff --git a/app/Containers/Dashboard/UI/API/Routes/api.php b/app/Containers/Dashboard/UI/API/Routes/api.php index fb4c042..94ec754 100644 --- a/app/Containers/Dashboard/UI/API/Routes/api.php +++ b/app/Containers/Dashboard/UI/API/Routes/api.php @@ -3,5 +3,7 @@ use App\Containers\Dashboard\UI\API\Controllers\DeployController; use Illuminate\Support\Facades\Route; -Route::post('/deploy', [DeployController::class, 'store']); -Route::get('/deploy/status', [DeployController::class, 'status']); +Route::middleware(['web', 'superadmin'])->group(function () { + Route::post('/deploy', [DeployController::class, 'store']); + Route::get('/deploy/status', [DeployController::class, 'status']); +}); diff --git a/app/Ship/Middleware/EnsureUserIsSuperadmin.php b/app/Ship/Middleware/EnsureUserIsSuperadmin.php index c7f90e6..9b6d7f4 100755 --- a/app/Ship/Middleware/EnsureUserIsSuperadmin.php +++ b/app/Ship/Middleware/EnsureUserIsSuperadmin.php @@ -18,6 +18,10 @@ class EnsureUserIsSuperadmin public function handle(Request $request, Closure $next) { if (!Auth::check() || !Auth::user()->hasRole('super_admin')) { + if ($request->expectsJson() || $request->is('api/*')) { + return response()->json(['error' => 'Forbidden'], 403); + } + return Inertia::render('Error', ['status' => 403]) ->toResponse($request) ->setStatusCode(403); diff --git a/docker-compose.yml b/docker-compose.yml index b016405..659f63a 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,6 @@ services: dockerfile: _docker/app/Dockerfile volumes: - ./:/var/www - - /var/run/docker.sock:/var/run/docker.sock container_name: ntspi-php restart: always ports: