almost finish updater
This commit is contained in:
parent
165dbc3797
commit
5d4613cf39
|
@ -4,6 +4,7 @@ namespace App\Command;
|
||||||
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
|
@ -38,43 +39,58 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->rootDir = dirname($this->appDir);
|
$this->rootDir = dirname($this->appDir);
|
||||||
$this->archiveDir = $this->rootDir . '/hesabixArchive';
|
$this->archiveDir = $this->rootDir . '/hesabixArchive';
|
||||||
$this->backupDir = $this->rootDir . '/hesabixBackup';
|
$this->backupDir = $this->rootDir . '/hesabixBackup';
|
||||||
$this->stateFile = $this->backupDir . '/' . Uuid::uuid4() . '/update_state.json';
|
|
||||||
$envConfig = file_exists($this->appDir . '/.env.local.php') ? require $this->appDir . '/.env.local.php' : [];
|
$envConfig = file_exists($this->appDir . '/.env.local.php') ? require $this->appDir . '/.env.local.php' : [];
|
||||||
$this->env = $envConfig['APP_ENV'] ?? getenv('APP_ENV') ?: 'prod';
|
$this->env = $envConfig['APP_ENV'] ?? getenv('APP_ENV') ?: 'prod';
|
||||||
$this->logger->info("Environment detected: " . $this->env);
|
$this->logger->info("Environment detected: " . $this->env);
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addArgument('state-file', InputArgument::OPTIONAL, 'Path to the state file');
|
||||||
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
$lock = $this->lockFactory->createLock('software-update', 3600);
|
$lock = $this->lockFactory->createLock('hesabix-update', 3600);
|
||||||
if (!$lock->acquire()) {
|
if (!$lock->acquire()) {
|
||||||
$this->writeOutput($output, '<error>Another update process is currently running. Please try again later.</error>');
|
$this->writeOutput($output, '<error>Another update process is currently running. Please try again later.</error>');
|
||||||
$this->logger->warning('Update attempt blocked due to existing lock.');
|
$this->logger->warning('Update attempt blocked due to existing lock.');
|
||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$uuid = Uuid::uuid4()->toString();
|
$this->stateFile = $input->getArgument('state-file') ?? $this->backupDir . '/update_state_' . Uuid::uuid4() . '.json';
|
||||||
|
if (!file_exists(dirname($this->stateFile))) {
|
||||||
|
mkdir(dirname($this->stateFile), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر فایل وضعیت وجود ندارد، آن را ایجاد کن
|
||||||
|
if (!file_exists($this->stateFile)) {
|
||||||
|
file_put_contents($this->stateFile, json_encode([
|
||||||
|
'uuid' => Uuid::uuid4()->toString(),
|
||||||
|
'log' => '',
|
||||||
|
'completedSteps' => [],
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$uuid = json_decode(file_get_contents($this->stateFile), true)['uuid'];
|
||||||
|
|
||||||
$this->logger->info("Starting software update with UUID: $uuid in {$this->env} mode");
|
$this->logger->info("Starting software update with UUID: $uuid in {$this->env} mode");
|
||||||
$this->writeOutput($output, "Starting software update (UUID: $uuid) in {$this->env} mode");
|
$this->writeOutput($output, "Starting software update (UUID: $uuid) in {$this->env} mode");
|
||||||
|
|
||||||
|
$state = $this->loadState($uuid);
|
||||||
|
$state['log'] = $state['log'] ?? ''; // اطمینان از وجود کلید log
|
||||||
|
|
||||||
if ($this->isUpToDate()) {
|
if ($this->isUpToDate()) {
|
||||||
$this->writeOutput($output, '<info>The software is already up to date with the remote repository.</info>');
|
$this->writeOutput($output, '<info>The software is already up to date with the remote repository.</info>');
|
||||||
$this->logger->info('No update needed, software is up to date.');
|
$this->logger->info('No update needed, software is up to date.');
|
||||||
|
$state['log'] .= "No update needed, software is up to date.\n"; // اضافه کردن مستقیم به لاگ
|
||||||
|
$state['completedSteps'] = ['post_update_test'];
|
||||||
|
$this->saveState($uuid, $state, $output, 'No update needed');
|
||||||
$lock->release();
|
$lock->release();
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stateDir = dirname($this->stateFile);
|
|
||||||
if (!is_dir($stateDir)) {
|
|
||||||
$this->logger->debug("Creating state directory: $stateDir");
|
|
||||||
if (!mkdir($stateDir, 0755, true) && !is_dir($stateDir)) {
|
|
||||||
throw new \RuntimeException("Failed to create state directory: $stateDir");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$state = $this->loadState($uuid);
|
|
||||||
$state['log'] = $state['log'] ?? '';
|
|
||||||
$state['completedSteps'] = $state['completedSteps'] ?? [];
|
$state['completedSteps'] = $state['completedSteps'] ?? [];
|
||||||
|
|
||||||
$gitHeadBefore = null;
|
$gitHeadBefore = null;
|
||||||
|
@ -93,10 +109,7 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->writeOutput($output, 'Backing up hesabixArchive...');
|
$this->writeOutput($output, 'Backing up hesabixArchive...');
|
||||||
$archiveBackupDir = $this->backupDir . '/' . Uuid::uuid4();
|
$archiveBackupDir = $this->backupDir . '/' . Uuid::uuid4();
|
||||||
if (!is_dir($archiveBackupDir)) {
|
if (!is_dir($archiveBackupDir)) {
|
||||||
$this->logger->debug("Creating archive backup directory: $archiveBackupDir");
|
mkdir($archiveBackupDir, 0755, true);
|
||||||
if (!mkdir($archiveBackupDir, 0755, true) && !is_dir($archiveBackupDir)) {
|
|
||||||
throw new \RuntimeException("Failed to create archive backup directory: $archiveBackupDir");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$archiveBackup = $archiveBackupDir . '/hesabixArchive_backup_' . time() . '.tar';
|
$archiveBackup = $archiveBackupDir . '/hesabixArchive_backup_' . time() . '.tar';
|
||||||
$this->runProcess(['tar', '-cf', $archiveBackup, '-C', $this->rootDir, 'hesabixArchive'], $this->rootDir, $output, 3);
|
$this->runProcess(['tar', '-cf', $archiveBackup, '-C', $this->rootDir, 'hesabixArchive'], $this->rootDir, $output, 3);
|
||||||
|
@ -137,10 +150,7 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->writeOutput($output, 'Clearing cache...');
|
$this->writeOutput($output, 'Clearing cache...');
|
||||||
$cacheBackupDir = $this->backupDir . '/' . Uuid::uuid4();
|
$cacheBackupDir = $this->backupDir . '/' . Uuid::uuid4();
|
||||||
if (!is_dir($cacheBackupDir)) {
|
if (!is_dir($cacheBackupDir)) {
|
||||||
$this->logger->debug("Creating cache backup directory: $cacheBackupDir");
|
mkdir($cacheBackupDir, 0755, true);
|
||||||
if (!mkdir($cacheBackupDir, 0755, true) && !is_dir($cacheBackupDir)) {
|
|
||||||
throw new \RuntimeException("Failed to create cache backup directory: $cacheBackupDir");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$cacheBackup = $cacheBackupDir . '/cache_backup_' . time();
|
$cacheBackup = $cacheBackupDir . '/cache_backup_' . time();
|
||||||
$this->runProcess(['cp', '-r', $this->appDir . '/var/cache', $cacheBackup], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput());
|
$this->runProcess(['cp', '-r', $this->appDir . '/var/cache', $cacheBackup], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput());
|
||||||
|
@ -156,10 +166,7 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->writeOutput($output, 'Updating database schema...');
|
$this->writeOutput($output, 'Updating database schema...');
|
||||||
$dbBackupDir = $this->backupDir . '/' . Uuid::uuid4();
|
$dbBackupDir = $this->backupDir . '/' . Uuid::uuid4();
|
||||||
if (!is_dir($dbBackupDir)) {
|
if (!is_dir($dbBackupDir)) {
|
||||||
$this->logger->debug("Creating database backup directory: $dbBackupDir");
|
mkdir($dbBackupDir, 0755, true);
|
||||||
if (!mkdir($dbBackupDir, 0755, true) && !is_dir($dbBackupDir)) {
|
|
||||||
throw new \RuntimeException("Failed to create database backup directory: $dbBackupDir");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$dbBackup = $dbBackupDir . '/db_backup_' . time() . '.sql';
|
$dbBackup = $dbBackupDir . '/db_backup_' . time() . '.sql';
|
||||||
$this->backupDatabaseToFile($dbBackup, $output);
|
$this->backupDatabaseToFile($dbBackup, $output);
|
||||||
|
@ -198,6 +205,11 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->logger->info('Software update completed successfully!');
|
$this->logger->info('Software update completed successfully!');
|
||||||
$this->writeOutput($output, '<info>Software update completed successfully!</info>');
|
$this->writeOutput($output, '<info>Software update completed successfully!</info>');
|
||||||
$this->saveState($uuid, $state, $output, 'Update completed successfully');
|
$this->saveState($uuid, $state, $output, 'Update completed successfully');
|
||||||
|
|
||||||
|
$this->writeOutput($output, 'Cleaning up all temporary directories...');
|
||||||
|
$this->cleanupAllTempDirectories();
|
||||||
|
|
||||||
|
$lock->release();
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logger->error('Update failed: ' . $e->getMessage());
|
$this->logger->error('Update failed: ' . $e->getMessage());
|
||||||
|
@ -206,15 +218,10 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->writeOutput($output, '<comment>Update process aborted and rolled back.</comment>');
|
$this->writeOutput($output, '<comment>Update process aborted and rolled back.</comment>');
|
||||||
$state['error'] = $e->getMessage();
|
$state['error'] = $e->getMessage();
|
||||||
$this->saveState($uuid, $state, $output, 'Update failed and rolled back');
|
$this->saveState($uuid, $state, $output, 'Update failed and rolled back');
|
||||||
|
$lock->release();
|
||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
} finally {
|
} finally {
|
||||||
$this->cleanupBackups($cacheBackup, $dbBackup, $archiveBackup);
|
|
||||||
$lock->release();
|
$lock->release();
|
||||||
$stateDir = dirname($this->stateFile);
|
|
||||||
if (is_dir($stateDir)) {
|
|
||||||
$this->logger->info('Cleaning up state directory: ' . $stateDir);
|
|
||||||
$this->runProcess(['rm', '-rf', $stateDir], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +247,7 @@ class UpdateSoftwareCommand extends Command
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$process->mustRun();
|
$process->mustRun();
|
||||||
|
$this->writeOutput($output, $process->getOutput());
|
||||||
}
|
}
|
||||||
$this->logger->info('Command executed successfully: ' . implode(' ', $command));
|
$this->logger->info('Command executed successfully: ' . implode(' ', $command));
|
||||||
return;
|
return;
|
||||||
|
@ -319,13 +327,9 @@ class UpdateSoftwareCommand extends Command
|
||||||
{
|
{
|
||||||
$backupDir = dirname($backupFile);
|
$backupDir = dirname($backupFile);
|
||||||
if (!is_dir($backupDir)) {
|
if (!is_dir($backupDir)) {
|
||||||
$this->logger->debug("Creating database backup directory: $backupDir");
|
mkdir($backupDir, 0755, true);
|
||||||
if (!mkdir($backupDir, 0755, true) && !is_dir($backupDir)) {
|
|
||||||
throw new \RuntimeException("Failed to create database backup directory: $backupDir");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// خواندن مستقیم از .env.local.php
|
|
||||||
$envConfig = file_exists($this->appDir . '/.env.local.php') ? require $this->appDir . '/.env.local.php' : [];
|
$envConfig = file_exists($this->appDir . '/.env.local.php') ? require $this->appDir . '/.env.local.php' : [];
|
||||||
$dbUrl = $envConfig['DATABASE_URL'] ?? getenv('DATABASE_URL');
|
$dbUrl = $envConfig['DATABASE_URL'] ?? getenv('DATABASE_URL');
|
||||||
if (!$dbUrl) {
|
if (!$dbUrl) {
|
||||||
|
@ -342,10 +346,8 @@ class UpdateSoftwareCommand extends Command
|
||||||
if (in_array($dbScheme, ['mysql', 'mariadb'])) {
|
if (in_array($dbScheme, ['mysql', 'mariadb'])) {
|
||||||
$command = [
|
$command = [
|
||||||
'mysqldump',
|
'mysqldump',
|
||||||
'-h',
|
'-h', $dbHost,
|
||||||
$dbHost,
|
'-u', $dbUser,
|
||||||
'-u',
|
|
||||||
$dbUser,
|
|
||||||
'-p' . $dbPass,
|
'-p' . $dbPass,
|
||||||
$dbName,
|
$dbName,
|
||||||
'--result-file=' . $backupFile
|
'--result-file=' . $backupFile
|
||||||
|
@ -353,16 +355,12 @@ class UpdateSoftwareCommand extends Command
|
||||||
} elseif ($dbScheme === 'pgsql') {
|
} elseif ($dbScheme === 'pgsql') {
|
||||||
$command = [
|
$command = [
|
||||||
'pg_dump',
|
'pg_dump',
|
||||||
'-h',
|
'-h', $dbHost,
|
||||||
$dbHost,
|
'-U', $dbUser,
|
||||||
'-U',
|
'-d', $dbName,
|
||||||
$dbUser,
|
|
||||||
'-d',
|
|
||||||
$dbName,
|
|
||||||
'--no-owner',
|
'--no-owner',
|
||||||
'--no-privileges',
|
'--no-privileges',
|
||||||
'-f',
|
'-f', $backupFile
|
||||||
$backupFile
|
|
||||||
];
|
];
|
||||||
if ($dbPass) {
|
if ($dbPass) {
|
||||||
putenv("PGPASSWORD=$dbPass");
|
putenv("PGPASSWORD=$dbPass");
|
||||||
|
@ -381,7 +379,6 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->logger->info("Database backup created at: $backupFile (scheme: $dbScheme)");
|
$this->logger->info("Database backup created at: $backupFile (scheme: $dbScheme)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function restoreArchive(string $backupFile): void
|
private function restoreArchive(string $backupFile): void
|
||||||
{
|
{
|
||||||
$this->runProcess(['rm', '-rf', $this->archiveDir], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput());
|
$this->runProcess(['rm', '-rf', $this->archiveDir], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput());
|
||||||
|
@ -437,11 +434,10 @@ class UpdateSoftwareCommand extends Command
|
||||||
|
|
||||||
if ($dbBackup) {
|
if ($dbBackup) {
|
||||||
try {
|
try {
|
||||||
// خواندن مستقیم از .env.local.php
|
|
||||||
$envConfig = file_exists($this->appDir . '/.env.local.php') ? require $this->appDir . '/.env.local.php' : [];
|
$envConfig = file_exists($this->appDir . '/.env.local.php') ? require $this->appDir . '/.env.local.php' : [];
|
||||||
$dbUrl = $envConfig['DATABASE_URL'] ?? getenv('DATABASE_URL');
|
$dbUrl = $envConfig['DATABASE_URL'] ?? getenv('DATABASE_URL');
|
||||||
if (!$dbUrl) {
|
if (!$dbUrl) {
|
||||||
throw new \RuntimeException('Could not determine DATABASE_URL from .env.local.php or environment for rollback.');
|
throw new \RuntimeException('Could not determine DATABASE_URL for rollback.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$urlParts = parse_url($dbUrl);
|
$urlParts = parse_url($dbUrl);
|
||||||
|
@ -454,10 +450,8 @@ class UpdateSoftwareCommand extends Command
|
||||||
if (in_array($dbScheme, ['mysql', 'mariadb'])) {
|
if (in_array($dbScheme, ['mysql', 'mariadb'])) {
|
||||||
$command = [
|
$command = [
|
||||||
'mysql',
|
'mysql',
|
||||||
'-h',
|
'-h', $dbHost,
|
||||||
$dbHost,
|
'-u', $dbUser,
|
||||||
'-u',
|
|
||||||
$dbUser,
|
|
||||||
'-p' . $dbPass,
|
'-p' . $dbPass,
|
||||||
$dbName
|
$dbName
|
||||||
];
|
];
|
||||||
|
@ -466,14 +460,10 @@ class UpdateSoftwareCommand extends Command
|
||||||
} elseif ($dbScheme === 'pgsql') {
|
} elseif ($dbScheme === 'pgsql') {
|
||||||
$command = [
|
$command = [
|
||||||
'psql',
|
'psql',
|
||||||
'-h',
|
'-h', $dbHost,
|
||||||
$dbHost,
|
'-U', $dbUser,
|
||||||
'-U',
|
'-d', $dbName,
|
||||||
$dbUser,
|
'-f', $dbBackup
|
||||||
'-d',
|
|
||||||
$dbName,
|
|
||||||
'-f',
|
|
||||||
$dbBackup
|
|
||||||
];
|
];
|
||||||
if ($dbPass) {
|
if ($dbPass) {
|
||||||
putenv("PGPASSWORD=$dbPass");
|
putenv("PGPASSWORD=$dbPass");
|
||||||
|
@ -501,25 +491,25 @@ class UpdateSoftwareCommand extends Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanupBackups(?string $cacheBackup, ?string $dbBackup, ?string $archiveBackup): void
|
private function cleanupAllTempDirectories(): void
|
||||||
{
|
{
|
||||||
if ($cacheBackup && is_dir($cacheBackup)) {
|
$directories = glob($this->backupDir . '/*', GLOB_ONLYDIR);
|
||||||
$this->runProcess(['rm', '-rf', $cacheBackup], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput());
|
$protectedDirs = ['databasefiles', 'versions'];
|
||||||
|
|
||||||
|
foreach ($directories as $dir) {
|
||||||
|
$dirName = basename($dir);
|
||||||
|
if (!in_array($dirName, $protectedDirs)) {
|
||||||
|
$this->logger->info("Removing temporary directory: $dir");
|
||||||
|
$this->runProcess(['rm', '-rf', $dir], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput());
|
||||||
}
|
}
|
||||||
if ($dbBackup && file_exists($dbBackup)) {
|
|
||||||
unlink($dbBackup);
|
|
||||||
}
|
|
||||||
if ($archiveBackup && file_exists($archiveBackup)) {
|
|
||||||
unlink($archiveBackup);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadState(string $uuid): array
|
private function loadState(string $uuid): array
|
||||||
{
|
{
|
||||||
$file = $this->stateFile;
|
if (file_exists($this->stateFile)) {
|
||||||
if (file_exists($file)) {
|
$state = json_decode(file_get_contents($this->stateFile), true);
|
||||||
$state = json_decode(file_get_contents($file), true);
|
if ($state && $state['uuid'] === $uuid) {
|
||||||
if ($state['uuid'] === $uuid) {
|
|
||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -529,14 +519,7 @@ class UpdateSoftwareCommand extends Command
|
||||||
private function saveState(string $uuid, array $state, OutputInterface $output, string $message): void
|
private function saveState(string $uuid, array $state, OutputInterface $output, string $message): void
|
||||||
{
|
{
|
||||||
$state['uuid'] = $uuid;
|
$state['uuid'] = $uuid;
|
||||||
$state['log'] .= $output->getVerbosity() >= OutputInterface::VERBOSITY_NORMAL ? $message . "\n" : '';
|
$state['log'] = ($state['log'] ?? '') . ($output->getVerbosity() >= OutputInterface::VERBOSITY_NORMAL ? $message . "\n" : '');
|
||||||
$stateDir = dirname($this->stateFile);
|
|
||||||
if (!is_dir($stateDir)) {
|
|
||||||
$this->logger->debug("Creating state directory: $stateDir");
|
|
||||||
if (!mkdir($stateDir, 0755, true) && !is_dir($stateDir)) {
|
|
||||||
throw new \RuntimeException("Failed to create state directory: $stateDir");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_put_contents($this->stateFile, json_encode($state, JSON_PRETTY_PRINT));
|
file_put_contents($this->stateFile, json_encode($state, JSON_PRETTY_PRINT));
|
||||||
$this->logger->debug('State saved to ' . $this->stateFile);
|
$this->logger->debug('State saved to ' . $this->stateFile);
|
||||||
}
|
}
|
||||||
|
|
210
hesabixCore/src/Controller/System/UpdateCoreController.php
Normal file
210
hesabixCore/src/Controller/System/UpdateCoreController.php
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\System;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
|
||||||
|
final class UpdateCoreController extends AbstractController
|
||||||
|
{
|
||||||
|
private Connection $connection;
|
||||||
|
|
||||||
|
public function __construct(Connection $connection)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/updatecore/run', name: 'api_admin_updatecore_run', methods: ['POST'])]
|
||||||
|
public function api_admin_updatecore_run(): JsonResponse
|
||||||
|
{
|
||||||
|
$projectDir = $this->getParameter('kernel.project_dir');
|
||||||
|
$uuid = uniqid();
|
||||||
|
$stateFile = $projectDir . '/../backup/update_state_' . $uuid . '.json';
|
||||||
|
|
||||||
|
if (!file_exists(dirname($stateFile))) {
|
||||||
|
mkdir(dirname($stateFile), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($stateFile, json_encode([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'log' => 'Update process started at ' . date('Y-m-d H:i:s') . "\n",
|
||||||
|
'completedSteps' => [],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$process = new Process(['php', 'bin/console', 'hesabix:update', $stateFile], $projectDir);
|
||||||
|
$process->setTimeout(3600);
|
||||||
|
$process->run(function ($type, $buffer) use ($stateFile) {
|
||||||
|
$state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => uniqid(), 'log' => ''];
|
||||||
|
$state['log'] .= $buffer;
|
||||||
|
file_put_contents($stateFile, json_encode($state));
|
||||||
|
});
|
||||||
|
|
||||||
|
$state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => $uuid, 'log' => ''];
|
||||||
|
|
||||||
|
if (!$process->isSuccessful()) {
|
||||||
|
$state['error'] = $process->getErrorOutput() ?: 'Unknown error';
|
||||||
|
file_put_contents($stateFile, json_encode($state));
|
||||||
|
return new JsonResponse([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Update process failed: ' . $state['error'],
|
||||||
|
'uuid' => $uuid,
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$state['log'] .= $process->getOutput() . $process->getErrorOutput();
|
||||||
|
file_put_contents($stateFile, json_encode($state));
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'status' => 'started',
|
||||||
|
'message' => 'Update process started',
|
||||||
|
'uuid' => $uuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/updatecore/status', name: 'api_admin_updatecore_status', methods: ['GET'])]
|
||||||
|
public function api_admin_updatecore_status(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$uuid = $request->query->get('uuid');
|
||||||
|
if (!$uuid) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'UUID is required',
|
||||||
|
'output' => '',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stateFile = $this->getParameter('kernel.project_dir') . '/../backup/update_state_' . $uuid . '.json';
|
||||||
|
|
||||||
|
if (!file_exists($stateFile)) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'status' => 'idle',
|
||||||
|
'message' => 'No update process is currently running',
|
||||||
|
'output' => '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = json_decode(file_get_contents($stateFile), true) ?? ['log' => ''];
|
||||||
|
$output = $state['log'] ?? '';
|
||||||
|
|
||||||
|
$isRunning = !isset($state['error']) &&
|
||||||
|
!in_array('post_update_test', $state['completedSteps'] ?? []) &&
|
||||||
|
!str_contains($output, 'No update needed') &&
|
||||||
|
!str_contains($output, 'Software update completed successfully');
|
||||||
|
|
||||||
|
if ($state['error'] ?? false) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Update failed: ' . $state['error'],
|
||||||
|
'output' => $output,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isRunning) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Update completed successfully',
|
||||||
|
'output' => $output,
|
||||||
|
'commit_hash' => $state['commit_hash'] ?? 'unknown',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'status' => 'running',
|
||||||
|
'message' => 'Update is in progress',
|
||||||
|
'output' => $output,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/updatecore/commits', name: 'api_admin_updatecore_commits', methods: ['GET'])]
|
||||||
|
public function api_admin_updatecore_commits(): JsonResponse
|
||||||
|
{
|
||||||
|
$projectDir = $this->getParameter('kernel.project_dir');
|
||||||
|
|
||||||
|
$currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $projectDir);
|
||||||
|
$currentProcess->run();
|
||||||
|
$currentCommit = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : 'unknown';
|
||||||
|
|
||||||
|
$targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $projectDir);
|
||||||
|
$targetProcess->run();
|
||||||
|
$targetOutput = $targetProcess->isSuccessful() ? explode("\t", trim($targetProcess->getOutput()))[0] : 'unknown';
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'currentCommit' => $currentCommit,
|
||||||
|
'targetCommit' => $targetOutput,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/updatecore/system-info', name: 'api_admin_updatecore_system_info', methods: ['GET'])]
|
||||||
|
public function api_admin_updatecore_system_info(): JsonResponse
|
||||||
|
{
|
||||||
|
// اطلاعات سیستمعامل
|
||||||
|
$osName = php_uname('s');
|
||||||
|
$osRelease = php_uname('r');
|
||||||
|
$osVersion = php_uname('v');
|
||||||
|
$osMachine = php_uname('m');
|
||||||
|
|
||||||
|
// اطلاعات پردازنده
|
||||||
|
$cpuInfo = 'Unknown';
|
||||||
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||||
|
$cpuInfo = shell_exec('wmic cpu get caption') ?? 'Unknown';
|
||||||
|
} else {
|
||||||
|
$cpuInfo = shell_exec('cat /proc/cpuinfo | grep "model name" | head -n 1') ?? 'Unknown';
|
||||||
|
$cpuInfo = str_replace('model name : ', '', trim($cpuInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// اطلاعات توزیع لینوکس
|
||||||
|
$distroName = 'Unknown';
|
||||||
|
$distroVersion = 'Unknown';
|
||||||
|
if (strtoupper(PHP_OS) === 'LINUX') {
|
||||||
|
$distroInfo = shell_exec('cat /etc/os-release | grep -E "^(NAME|VERSION)="') ?? '';
|
||||||
|
if ($distroInfo) {
|
||||||
|
preg_match('/NAME="([^"]+)"/', $distroInfo, $nameMatch);
|
||||||
|
preg_match('/VERSION="([^"]+)"/', $distroInfo, $versionMatch);
|
||||||
|
$distroName = $nameMatch[1] ?? 'Unknown';
|
||||||
|
$distroVersion = $versionMatch[1] ?? 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// اطلاعات وبسرور
|
||||||
|
$webServer = $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown';
|
||||||
|
|
||||||
|
// اطلاعات بانک اطلاعاتی
|
||||||
|
$dbVersion = 'Unknown';
|
||||||
|
$dbName = 'Unknown';
|
||||||
|
try {
|
||||||
|
$dbParams = $this->connection->getParams();
|
||||||
|
$dbName = $dbParams['driver'] ?? 'Unknown';
|
||||||
|
|
||||||
|
// گرفتن نسخه بانک اطلاعاتی با کوئری SQL
|
||||||
|
if (str_contains($dbName, 'mysql')) {
|
||||||
|
$dbVersion = $this->connection->fetchOne('SELECT VERSION()');
|
||||||
|
} elseif (str_contains($dbName, 'pgsql')) {
|
||||||
|
$dbVersion = $this->connection->fetchOne('SHOW server_version');
|
||||||
|
} elseif (str_contains($dbName, 'sqlite')) {
|
||||||
|
$dbVersion = $this->connection->fetchOne('SELECT sqlite_version()');
|
||||||
|
} else {
|
||||||
|
$dbVersion = 'Unsupported database type';
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$dbVersion = 'Error fetching DB version: ' . $e->getMessage();
|
||||||
|
$dbName = 'Error fetching DB name';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'osName' => $osName,
|
||||||
|
'osRelease' => $osRelease,
|
||||||
|
'osVersion' => $osVersion,
|
||||||
|
'osMachine' => $osMachine,
|
||||||
|
'cpuInfo' => $cpuInfo,
|
||||||
|
'distroName' => $distroName,
|
||||||
|
'distroVersion' => $distroVersion,
|
||||||
|
'webServer' => $webServer,
|
||||||
|
'dbName' => $dbName,
|
||||||
|
'dbVersion' => $dbVersion,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue