From 414793898306653c4eb081f6e46855244e03e0ca Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Wed, 5 Mar 2025 21:47:54 +0000 Subject: [PATCH] almost finish update system --- hesabixCore/composer.json | 1 + hesabixCore/composer.lock | 230 +++++++++++++++++- .../src/Command/ReleaseUpdateLockCommand.php | 2 +- .../src/Command/UpdateSoftwareCommand.php | 148 ++++++++--- 4 files changed, 348 insertions(+), 33 deletions(-) diff --git a/hesabixCore/composer.json b/hesabixCore/composer.json index b1f4b83..f389f77 100644 --- a/hesabixCore/composer.json +++ b/hesabixCore/composer.json @@ -23,6 +23,7 @@ "phpdocumentor/reflection-docblock": "^5.3", "phpoffice/phpspreadsheet": "^1.29", "phpstan/phpdoc-parser": "^1.16", + "ramsey/uuid": "^4.7", "symfony/apache-pack": "^1.0", "symfony/asset": "7.1.*", "symfony/console": "7.1.*", diff --git a/hesabixCore/composer.lock b/hesabixCore/composer.lock index 5e6fb69..d2865eb 100644 --- a/hesabixCore/composer.lock +++ b/hesabixCore/composer.lock @@ -4,8 +4,68 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b5f9aa88af7c562b82e1bcf67a995845", + "content-hash": "8fade6c168119e9380774d2ae76c4b26", "packages": [ + { + "name": "brick/math", + "version": "0.12.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-02-28T13:11:00+00:00" + }, { "name": "composer/pcre", "version": "3.3.2", @@ -3744,6 +3804,174 @@ }, "time": "2021-10-29T13:26:27+00:00" }, + { + "name": "ramsey/collection", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.0" + }, + "time": "2025-03-02T04:48:29+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, { "name": "sabberworm/php-css-parser", "version": "v8.7.0", diff --git a/hesabixCore/src/Command/ReleaseUpdateLockCommand.php b/hesabixCore/src/Command/ReleaseUpdateLockCommand.php index 1c95b66..a626730 100644 --- a/hesabixCore/src/Command/ReleaseUpdateLockCommand.php +++ b/hesabixCore/src/Command/ReleaseUpdateLockCommand.php @@ -9,7 +9,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Lock\LockFactory; #[AsCommand( - name: 'app:release-update-lock', + name: 'hesabix:release-update-lock', description: 'Releases the software update lock manually.' )] class ReleaseUpdateLockCommand extends Command diff --git a/hesabixCore/src/Command/UpdateSoftwareCommand.php b/hesabixCore/src/Command/UpdateSoftwareCommand.php index 66bf01a..53ee5f6 100644 --- a/hesabixCore/src/Command/UpdateSoftwareCommand.php +++ b/hesabixCore/src/Command/UpdateSoftwareCommand.php @@ -13,8 +13,8 @@ use Ramsey\Uuid\Uuid; use Symfony\Component\Lock\LockFactory; #[AsCommand( - name: 'app:update-software', - description: 'Updates the software by pulling from GitHub, clearing cache, and updating the database.' + name: 'hesabix:update-software', + description: 'Updates the Hesabix Core by pulling from GitHub, clearing cache, and updating the database.' )] class UpdateSoftwareCommand extends Command { @@ -51,7 +51,6 @@ class UpdateSoftwareCommand extends Command $this->logger->info("Starting software update with UUID: $uuid"); $this->writeOutput($output, "Starting software update (UUID: $uuid)..."); - // چک کردن اینکه آیا آپدیت لازم است if ($this->isUpToDate()) { $this->writeOutput($output, 'The software is already up to date with the remote repository.'); $this->logger->info('No update needed, software is up to date.'); @@ -59,7 +58,6 @@ class UpdateSoftwareCommand extends Command return Command::SUCCESS; } - // ادامه فرآیند فقط در صورتی که آپدیت لازم باشه if (!is_dir($this->backupDir)) { mkdir($this->backupDir, 0755, true); } @@ -74,9 +72,101 @@ class UpdateSoftwareCommand extends Command $archiveBackup = null; 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->postUpdateChecks($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, 'Software update completed successfully!'); + $this->saveState($uuid, $state); + return Command::SUCCESS; } catch (\Exception $e) { - // بقیه کد بدون تغییر ... + $this->logger->error('Update failed: ' . $e->getMessage()); + $this->writeOutput($output, 'An error occurred: ' . $e->getMessage() . ''); + $this->rollback($gitHeadBefore, $cacheBackup, $dbBackup, $archiveBackup, $output); + $this->writeOutput($output, 'Update process aborted and rolled back.'); + $state['error'] = $e->getMessage(); + $this->saveState($uuid, $state); + return Command::FAILURE; } finally { $this->cleanupBackups($cacheBackup, $dbBackup, $archiveBackup); $lock->release(); @@ -86,30 +176,6 @@ 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 { $output->writeln($message); @@ -144,6 +210,26 @@ class UpdateSoftwareCommand extends Command } } + private function isUpToDate(): bool + { + $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()); + + $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] ?? ''; + + return $localHead === $remoteHead; + } + private function preUpdateChecks(OutputInterface $output): void { $this->writeOutput($output, 'Running pre-update checks...'); @@ -153,7 +239,7 @@ class UpdateSoftwareCommand extends Command $this->writeOutput($output, 'Database connection OK.'); } - private function postUpdateTest(OutputInterface $output): void + private function postUpdateChecks(OutputInterface $output): void { $this->writeOutput($output, 'Running post-update tests...'); $this->runProcess(['php', 'bin/console', 'cache:warmup', '--env=prod'], $this->appDir, $output);