almost finish automatic update proccess
This commit is contained in:
parent
26e6bb401b
commit
df0dda31b0
|
@ -1,28 +1,28 @@
|
||||||
# This file is the entry point to configure your own services.
|
|
||||||
# Files in the packages/ subdirectory configure your dependencies.
|
|
||||||
|
|
||||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
|
||||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
|
||||||
parameters:
|
parameters:
|
||||||
archiveMediaDir: '%kernel.project_dir%/../hesabixArchive'
|
archiveMediaDir: '%kernel.project_dir%/../hesabixArchive'
|
||||||
archiveTempMediaDir: '%kernel.project_dir%/../hesabixArchive/temp'
|
archiveTempMediaDir: '%kernel.project_dir%/../hesabixArchive/temp'
|
||||||
avatarDir: '%kernel.project_dir%/../hesabixArchive/avatars'
|
avatarDir: '%kernel.project_dir%/../hesabixArchive/avatars'
|
||||||
sealDir: '%kernel.project_dir%/../hesabixArchive/seal'
|
sealDir: '%kernel.project_dir%/../hesabixArchive/seal'
|
||||||
SupportFilesDir: '%kernel.project_dir%/../hesabixArchive/support'
|
SupportFilesDir: '%kernel.project_dir%/../hesabixArchive/support'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
|
||||||
_defaults:
|
_defaults:
|
||||||
autowire: true # Automatically injects dependencies in your services.
|
autowire: true
|
||||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
autoconfigure: true
|
||||||
public: false
|
public: false
|
||||||
|
|
||||||
App\Command\UpdateSoftwareCommand:
|
App\Command\UpdateSoftwareCommand:
|
||||||
arguments:
|
arguments:
|
||||||
$logger: '@Psr\Log\LoggerInterface'
|
$logger: '@Psr\Log\LoggerInterface'
|
||||||
$projectDir: '%kernel.project_dir%'
|
|
||||||
$lockFactory: '@Symfony\Component\Lock\LockFactory'
|
$lockFactory: '@Symfony\Component\Lock\LockFactory'
|
||||||
tags:
|
tags:
|
||||||
- { name: 'console.command' }
|
- { name: 'console.command' }
|
||||||
|
App\Command\ReleaseUpdateLockCommand:
|
||||||
|
arguments:
|
||||||
|
$lockFactory: '@Symfony\Component\Lock\LockFactory'
|
||||||
|
tags:
|
||||||
|
- { name: 'console.command' }
|
||||||
|
|
||||||
# تنظیمات Lock
|
# تنظیمات Lock
|
||||||
Symfony\Component\Lock\LockFactory:
|
Symfony\Component\Lock\LockFactory:
|
||||||
arguments:
|
arguments:
|
||||||
|
@ -33,19 +33,7 @@ services:
|
||||||
arguments:
|
arguments:
|
||||||
- '%kernel.project_dir%/var/lock'
|
- '%kernel.project_dir%/var/lock'
|
||||||
|
|
||||||
doctrine.orm.default_attribute_driver:
|
# سایر سرویسها
|
||||||
class: Doctrine\ORM\Mapping\Driver\AttributeDriver
|
|
||||||
arguments:
|
|
||||||
- [ '%kernel.project_dir%/src/Entity' ]
|
|
||||||
- true # reportFieldsWhereDeclared
|
|
||||||
tags:
|
|
||||||
- { name: doctrine.orm.mapping_driver }
|
|
||||||
App\Security\AuthenticationFailureHandler:
|
|
||||||
arguments:
|
|
||||||
$captchaService: '@App\Service\CaptchaService'
|
|
||||||
$requestStack: '@request_stack'
|
|
||||||
# makes classes in src/ available to be used as services
|
|
||||||
# this creates a service per class whose id is the fully-qualified class name
|
|
||||||
App\:
|
App\:
|
||||||
resource: '../src/'
|
resource: '../src/'
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -53,28 +41,46 @@ services:
|
||||||
- '../src/Entity/'
|
- '../src/Entity/'
|
||||||
- '../src/Kernel.php'
|
- '../src/Kernel.php'
|
||||||
|
|
||||||
# add more service definitions when explicit configuration is needed
|
doctrine.orm.default_attribute_driver:
|
||||||
# please note that last definitions always *replace* previous ones
|
class: Doctrine\ORM\Mapping\Driver\AttributeDriver
|
||||||
|
arguments:
|
||||||
|
- [ '%kernel.project_dir%/src/Entity' ]
|
||||||
|
- true
|
||||||
|
tags:
|
||||||
|
- { name: doctrine.orm.mapping_driver }
|
||||||
|
|
||||||
|
App\Security\AuthenticationFailureHandler:
|
||||||
|
arguments:
|
||||||
|
$captchaService: '@App\Service\CaptchaService'
|
||||||
|
$requestStack: '@request_stack'
|
||||||
|
|
||||||
Jdate:
|
Jdate:
|
||||||
class: App\Service\Jdate
|
class: App\Service\Jdate
|
||||||
|
|
||||||
Exctractor:
|
Exctractor:
|
||||||
class: App\Service\Exctractor
|
class: App\Service\Exctractor
|
||||||
|
|
||||||
Log:
|
Log:
|
||||||
class: App\Service\Log
|
class: App\Service\Log
|
||||||
arguments: [ "@doctrine.orm.entity_manager" ]
|
arguments: [ '@doctrine.orm.entity_manager' ]
|
||||||
|
|
||||||
SMS:
|
SMS:
|
||||||
class: App\Service\SMS
|
class: App\Service\SMS
|
||||||
arguments:
|
arguments:
|
||||||
$entityManager: "@doctrine.orm.entity_manager"
|
$entityManager: '@doctrine.orm.entity_manager'
|
||||||
|
|
||||||
Provider:
|
Provider:
|
||||||
class: App\Service\Provider
|
class: App\Service\Provider
|
||||||
arguments: [ "@doctrine.orm.entity_manager" ]
|
arguments: [ '@doctrine.orm.entity_manager' ]
|
||||||
|
|
||||||
twigFunctions:
|
twigFunctions:
|
||||||
class: App\Service\twigFunctions
|
class: App\Service\twigFunctions
|
||||||
arguments: [ "@doctrine.orm.entity_manager" ]
|
arguments: [ '@doctrine.orm.entity_manager' ]
|
||||||
|
|
||||||
registryMGR:
|
registryMGR:
|
||||||
class: App\Service\registryMGR
|
class: App\Service\registryMGR
|
||||||
arguments: [ "@doctrine.orm.entity_manager" ]
|
arguments: [ '@doctrine.orm.entity_manager' ]
|
||||||
|
|
||||||
Printers:
|
Printers:
|
||||||
class: App\Service\Printers
|
class: App\Service\Printers
|
||||||
arguments: [ "@doctrine.orm.entity_manager" ]
|
arguments: [ '@doctrine.orm.entity_manager' ]
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Command;
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
|
|
||||||
#[AsCommand(
|
|
||||||
name: 'hesabix',
|
|
||||||
description: 'Add a short description for your command',
|
|
||||||
)]
|
|
||||||
class HesabixCommand extends Command
|
|
||||||
{
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description')
|
|
||||||
->addOption('option1', null, InputOption::VALUE_NONE, 'Option description')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$io = new SymfonyStyle($input, $output);
|
|
||||||
$arg1 = $input->getArgument('arg1');
|
|
||||||
|
|
||||||
if ($arg1) {
|
|
||||||
$io->note(sprintf('You passed an argument: %s', $arg1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input->getOption('option1')) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
$io->success('You have a new command! Now make it your own! Pass --help to see your options.');
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,19 @@
|
||||||
<?php
|
<?php
|
||||||
// src/Command/ReleaseUpdateLockCommand.php
|
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
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\Lock\LockFactory;
|
use Symfony\Component\Lock\LockFactory;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:release-update-lock',
|
||||||
|
description: 'Releases the software update lock manually.'
|
||||||
|
)]
|
||||||
class ReleaseUpdateLockCommand extends Command
|
class ReleaseUpdateLockCommand extends Command
|
||||||
{
|
{
|
||||||
protected static $defaultName = 'app:release-update-lock';
|
|
||||||
private LockFactory $lockFactory;
|
private LockFactory $lockFactory;
|
||||||
|
|
||||||
public function __construct(LockFactory $lockFactory)
|
public function __construct(LockFactory $lockFactory)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
@ -10,29 +11,30 @@ use Symfony\Component\Process\Process;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use Symfony\Component\Lock\LockFactory;
|
use Symfony\Component\Lock\LockFactory;
|
||||||
use Symfony\Component\Lock\Store\FlockStore;
|
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:update-software',
|
||||||
|
description: 'Updates the software by pulling from GitHub, clearing cache, and updating the database.'
|
||||||
|
)]
|
||||||
class UpdateSoftwareCommand extends Command
|
class UpdateSoftwareCommand extends Command
|
||||||
{
|
{
|
||||||
protected static $defaultName = 'app:update-software'; // نام کاماند
|
|
||||||
|
|
||||||
private LoggerInterface $logger;
|
private LoggerInterface $logger;
|
||||||
|
private LockFactory $lockFactory;
|
||||||
private string $rootDir;
|
private string $rootDir;
|
||||||
private string $appDir;
|
private string $appDir;
|
||||||
private string $archiveDir;
|
private string $archiveDir;
|
||||||
private string $backupDir;
|
private string $backupDir;
|
||||||
private string $stateFile;
|
private string $stateFile;
|
||||||
private LockFactory $lockFactory;
|
|
||||||
|
|
||||||
public function __construct(LoggerInterface $logger, string $projectDir, LockFactory $lockFactory)
|
public function __construct(LoggerInterface $logger, LockFactory $lockFactory)
|
||||||
{
|
{
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->rootDir = dirname($projectDir);
|
$this->lockFactory = $lockFactory;
|
||||||
$this->appDir = $projectDir;
|
$this->appDir = dirname(__DIR__, 2); // src/Command -> hesabixCore
|
||||||
|
$this->rootDir = dirname($this->appDir); // hesabixCore -> parent dir
|
||||||
$this->archiveDir = $this->rootDir . '/hesabixArchive';
|
$this->archiveDir = $this->rootDir . '/hesabixArchive';
|
||||||
$this->backupDir = $this->rootDir . '/../backup';
|
$this->backupDir = $this->rootDir . '/../backup';
|
||||||
$this->stateFile = $this->backupDir . '/update_state.json';
|
$this->stateFile = $this->backupDir . '/update_state.json';
|
||||||
$this->lockFactory = $lockFactory;
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +51,15 @@ class UpdateSoftwareCommand extends Command
|
||||||
$this->logger->info("Starting software update with UUID: $uuid");
|
$this->logger->info("Starting software update with UUID: $uuid");
|
||||||
$this->writeOutput($output, "Starting software update (UUID: $uuid)...");
|
$this->writeOutput($output, "Starting software update (UUID: $uuid)...");
|
||||||
|
|
||||||
|
// چک کردن اینکه آیا آپدیت لازم است
|
||||||
|
if ($this->isUpToDate()) {
|
||||||
|
$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.');
|
||||||
|
$lock->release();
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ادامه فرآیند فقط در صورتی که آپدیت لازم باشه
|
||||||
if (!is_dir($this->backupDir)) {
|
if (!is_dir($this->backupDir)) {
|
||||||
mkdir($this->backupDir, 0755, true);
|
mkdir($this->backupDir, 0755, true);
|
||||||
}
|
}
|
||||||
|
@ -63,101 +74,9 @@ class UpdateSoftwareCommand extends Command
|
||||||
$archiveBackup = null;
|
$archiveBackup = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!in_array('pre_checks', $state['completedSteps'])) {
|
// بقیه کد بدون تغییر ...
|
||||||
$this->preUpdateChecks($output);
|
|
||||||
$state['completedSteps'][] = 'pre_checks';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array('archive_backup', $state['completedSteps'])) {
|
|
||||||
$this->writeOutput($output, 'Backing up hesabixArchive...');
|
|
||||||
$archiveBackup = $this->backupArchive();
|
|
||||||
$state['archiveBackup'] = $archiveBackup;
|
|
||||||
$archiveHashBefore = $this->getDirectoryHash($this->archiveDir);
|
|
||||||
$state['archiveHashBefore'] = $archiveHashBefore;
|
|
||||||
$state['completedSteps'][] = 'archive_backup';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
} else {
|
|
||||||
$archiveBackup = $state['archiveBackup'];
|
|
||||||
$archiveHashBefore = $state['archiveHashBefore'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array('git_pull', $state['completedSteps'])) {
|
|
||||||
$this->writeOutput($output, 'Pulling latest changes from GitHub...');
|
|
||||||
$gitHeadBefore = $this->getCurrentGitHead();
|
|
||||||
$this->runProcess(['git', 'pull'], $this->rootDir, $output, 3);
|
|
||||||
$state['gitHeadBefore'] = $gitHeadBefore;
|
|
||||||
$state['completedSteps'][] = 'git_pull';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
} else {
|
|
||||||
$gitHeadBefore = $state['gitHeadBefore'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array('composer_install', $state['completedSteps'])) {
|
|
||||||
$this->writeOutput($output, 'Installing dependencies...');
|
|
||||||
$this->runProcess(['composer', 'install', '--no-dev', '--optimize-autoloader'], $this->appDir, $output, 3);
|
|
||||||
$state['completedSteps'][] = 'composer_install';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array('cache_clear', $state['completedSteps'])) {
|
|
||||||
$this->writeOutput($output, 'Clearing cache...');
|
|
||||||
$cacheDir = $this->appDir . '/var/cache';
|
|
||||||
$cacheBackup = $this->backupCache($cacheDir);
|
|
||||||
$state['cacheBackup'] = $cacheBackup;
|
|
||||||
$this->runProcess(['php', 'bin/console', 'cache:clear', '--env=prod'], $this->appDir, $output, 3);
|
|
||||||
$state['completedSteps'][] = 'cache_clear';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
} else {
|
|
||||||
$cacheBackup = $state['cacheBackup'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array('db_update', $state['completedSteps'])) {
|
|
||||||
$this->writeOutput($output, 'Updating database schema...');
|
|
||||||
$dbBackup = $this->backupDatabase();
|
|
||||||
$state['dbBackup'] = $dbBackup;
|
|
||||||
$this->runProcess(['php', 'bin/console', 'doctrine:schema:update', '--force', '--no-interaction'], $this->appDir, $output, 3);
|
|
||||||
$state['completedSteps'][] = 'db_update';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
} else {
|
|
||||||
$dbBackup = $state['dbBackup'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array('archive_check', $state['completedSteps'])) {
|
|
||||||
$archiveHashAfter = $this->getDirectoryHash($this->archiveDir);
|
|
||||||
if ($archiveHashBefore !== $archiveHashAfter) {
|
|
||||||
$this->writeOutput($output, 'hesabixArchive has changed, restoring from backup...');
|
|
||||||
$this->restoreArchive($archiveBackup);
|
|
||||||
$this->writeOutput($output, 'hesabixArchive restored successfully.');
|
|
||||||
} else {
|
|
||||||
$this->writeOutput($output, 'hesabixArchive unchanged, no restore needed.');
|
|
||||||
}
|
|
||||||
$state['completedSteps'][] = 'archive_check';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array('post_update_test', $state['completedSteps'])) {
|
|
||||||
$this->postUpdateTest($output);
|
|
||||||
$state['completedSteps'][] = 'post_update_test';
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
}
|
|
||||||
|
|
||||||
$version = $this->getPackageVersion();
|
|
||||||
$this->writeOutput($output, "Software updated to version: $version");
|
|
||||||
$state['version'] = $version;
|
|
||||||
|
|
||||||
$this->logger->info('Software update completed successfully!');
|
|
||||||
$this->writeOutput($output, '<info>Software update completed successfully!</info>');
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
return Command::SUCCESS;
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logger->error('Update failed: ' . $e->getMessage());
|
// بقیه کد بدون تغییر ...
|
||||||
$this->writeOutput($output, '<error>An error occurred: ' . $e->getMessage() . '</error>');
|
|
||||||
$this->rollback($gitHeadBefore, $cacheBackup, $dbBackup, $archiveBackup, $output);
|
|
||||||
$this->writeOutput($output, '<comment>Update process aborted and rolled back.</comment>');
|
|
||||||
$state['error'] = $e->getMessage();
|
|
||||||
$this->saveState($uuid, $state);
|
|
||||||
return Command::FAILURE;
|
|
||||||
} finally {
|
} finally {
|
||||||
$this->cleanupBackups($cacheBackup, $dbBackup, $archiveBackup);
|
$this->cleanupBackups($cacheBackup, $dbBackup, $archiveBackup);
|
||||||
$lock->release();
|
$lock->release();
|
||||||
|
@ -167,6 +86,30 @@ class UpdateSoftwareCommand extends Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// متد جدید برای چک کردن بهروز بودن
|
||||||
|
private function isUpToDate(): bool
|
||||||
|
{
|
||||||
|
// گرفتن HEAD فعلی مخزن محلی
|
||||||
|
$localHeadProcess = new Process(['git', 'rev-parse', 'HEAD'], $this->rootDir);
|
||||||
|
$localHeadProcess->run();
|
||||||
|
if (!$localHeadProcess->isSuccessful()) {
|
||||||
|
throw new \RuntimeException('Failed to get local Git HEAD: ' . $localHeadProcess->getErrorOutput());
|
||||||
|
}
|
||||||
|
$localHead = trim($localHeadProcess->getOutput());
|
||||||
|
|
||||||
|
// گرفتن HEAD مخزن ریموت
|
||||||
|
$remoteHeadProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $this->rootDir);
|
||||||
|
$remoteHeadProcess->run();
|
||||||
|
if (!$remoteHeadProcess->isSuccessful()) {
|
||||||
|
throw new \RuntimeException('Failed to get remote Git HEAD: ' . $remoteHeadProcess->getErrorOutput());
|
||||||
|
}
|
||||||
|
$remoteOutput = explode("\t", trim($remoteHeadProcess->getOutput()));
|
||||||
|
$remoteHead = $remoteOutput[0] ?? '';
|
||||||
|
|
||||||
|
// مقایسه HEAD محلی و ریموت
|
||||||
|
return $localHead === $remoteHead;
|
||||||
|
}
|
||||||
|
|
||||||
private function writeOutput(OutputInterface $output, string $message): void
|
private function writeOutput(OutputInterface $output, string $message): void
|
||||||
{
|
{
|
||||||
$output->writeln($message);
|
$output->writeln($message);
|
||||||
|
|
Loading…
Reference in a new issue