From 7ae57e3d987c08e3d5978e09f10fb4161ff36bee Mon Sep 17 00:00:00 2001 From: Phan An Date: Mon, 22 Jul 2024 22:42:58 +0200 Subject: [PATCH] feat: add Scheduler installation command (#1802) --- app/Console/Commands/InitCommand.php | 48 +++++++++++------- .../Commands/InstallSchedulerCommand.php | 49 +++++++++++++++++++ composer.json | 3 +- composer.lock | 49 ++++++++++++++++++- docs/cli-commands.md | 22 ++++++--- 5 files changed, 145 insertions(+), 26 deletions(-) create mode 100644 app/Console/Commands/InstallSchedulerCommand.php diff --git a/app/Console/Commands/InitCommand.php b/app/Console/Commands/InitCommand.php index 327f7497..64182214 100644 --- a/app/Console/Commands/InitCommand.php +++ b/app/Console/Commands/InitCommand.php @@ -7,15 +7,15 @@ use App\Exceptions\InstallationFailedException; use App\Models\Setting; use App\Models\User; use Illuminate\Console\Command; -use Illuminate\Contracts\Hashing\Hasher as Hash; -use Illuminate\Database\DatabaseManager as DB; use Illuminate\Encryption\Encrypter; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Process; use Illuminate\Support\Str; use Jackiedo\DotenvEditor\DotenvEditor; -use Psr\Log\LoggerInterface; use Throwable; class InitCommand extends Command @@ -32,12 +32,8 @@ class InitCommand extends Command private bool $adminSeeded = false; - public function __construct( - private readonly Hash $hash, - private readonly DotenvEditor $dotenvEditor, - private readonly DB $db, - private readonly LoggerInterface $logger - ) { + public function __construct(private readonly DotenvEditor $dotenvEditor) + { parent::__construct(); } @@ -64,8 +60,9 @@ class InitCommand extends Command $this->maybeSetMediaPath(); $this->maybeCompileFrontEndAssets(); $this->dotenvEditor->save(); + $this->tryInstallingScheduler(); } catch (Throwable $e) { - $this->logger->error($e); + Log::error($e); $this->components->error("Oops! Koel installation or upgrade didn't finish successfully."); $this->components->error('Please check the error log at storage/logs/laravel.log and try again.'); @@ -92,7 +89,7 @@ class InitCommand extends Command $this->info('Again, visit 📙 ' . config('koel.misc.docs_url') . ' for more tips and tweaks.'); $this->info( - "Feeling generous and want to support Koel's development? Check out " + "Feeling generous and want to support Koel’s development? Check out " . config('koel.misc.sponsor_github_url') . ' 🤗' ); @@ -202,7 +199,7 @@ class InitCommand extends Command User::query()->create([ 'name' => self::DEFAULT_ADMIN_NAME, 'email' => self::DEFAULT_ADMIN_EMAIL, - 'password' => $this->hash->make(self::DEFAULT_ADMIN_PASSWORD), + 'password' => Hash::make(self::DEFAULT_ADMIN_PASSWORD), 'is_admin' => true, ]); @@ -241,12 +238,12 @@ class InitCommand extends Command try { // Make sure the config cache is cleared before another attempt. Artisan::call('config:clear', ['--quiet' => true]); - $this->db->reconnect(); - $this->db->getDoctrineSchemaManager()->listTables(); + DB::reconnect(); + DB::getDoctrineSchemaManager()->listTables(); break; } catch (Throwable $e) { - $this->logger->error($e); + Log::error($e); // We only try to update credentials if running in interactive mode. // Otherwise, we require admin intervention to fix them. @@ -347,11 +344,26 @@ class InitCommand extends Command return File::isDirectory($path) && File::isReadable($path); } - /** - * Generate a random key for the application. - */ private function generateRandomKey(): string { return 'base64:' . base64_encode(Encrypter::generateKey($this->laravel['config']['app.cipher'])); } + + private function tryInstallingScheduler(): void + { + if (PHP_OS_FAMILY === 'Windows' || PHP_OS_FAMILY === 'Unknown') { + return; + } + + $this->components->info('Trying to install Koel scheduler…'); + + if (Artisan::call('koel:scheduler:install') !== self::SUCCESS) { + $this->components->warn( + 'Failed to install scheduler. ' . + 'Please install manually: https://docs.koel.dev/cli-commands#command-scheduling' + ); + } else { + $this->components->info('Koel scheduler installed successfully.'); + } + } } diff --git a/app/Console/Commands/InstallSchedulerCommand.php b/app/Console/Commands/InstallSchedulerCommand.php new file mode 100644 index 00000000..d2bd2ff7 --- /dev/null +++ b/app/Console/Commands/InstallSchedulerCommand.php @@ -0,0 +1,49 @@ +components->error('This command is only available on Linux systems.'); + + return self::FAILURE; + } + + $crontab = new CrontabRepository(new CrontabAdapter()); + + $this->components->info('Trying to install Koel scheduler…'); + + if (self::schedulerInstalled($crontab)) { + $this->components->info('Koel scheduler is already installed. Skipping…'); + + return self::SUCCESS; + } + + $job = CrontabJob::createFromCrontabLine( + '* * * * * cd ' . base_path() . ' && php artisan schedule:run >> /dev/null 2>&1' + ); + + $crontab->addJob($job); + $crontab->persist(); + + $this->components->info('Koel scheduler installed successfully.'); + + return self::SUCCESS; + } + + private static function schedulerInstalled(CrontabRepository $crontab): bool + { + return (bool) $crontab->findJobByRegex('/artisan schedule:run/'); + } +} diff --git a/composer.json b/composer.json index 1d9a2b38..32e9db97 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,8 @@ "league/flysystem-sftp-v3": "^3.0", "saloonphp/xml-wrangler": "^1.2", "phanan/poddle": "^1.0", - "spatie/laravel-ray": "^1.36" + "spatie/laravel-ray": "^1.36", + "tiben/crontab-manager": "*" }, "require-dev": { "mockery/mockery": "~1.0", diff --git a/composer.lock b/composer.lock index 2ea66a31..2cfa220d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "306146627425d3042b82f1cd347237c8", + "content-hash": "9da46e14c758c109ce78f30cbaa63a09", "packages": [ { "name": "algolia/algoliasearch-client-php", @@ -9590,6 +9590,53 @@ ], "time": "2023-07-17T10:34:14+00:00" }, + { + "name": "tiben/crontab-manager", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/TiBeN/CrontabManager.git", + "reference": "79ade7bfc895c4594905a2554d7a82f6567aabf3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TiBeN/CrontabManager/zipball/79ade7bfc895c4594905a2554d7a82f6567aabf3", + "reference": "79ade7bfc895c4594905a2554d7a82f6567aabf3", + "shasum": "" + }, + "require": { + "php": ">= 5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36|^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "TiBeN\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Benjamin Legendre", + "email": "legendre.benjamin@gmail.com" + } + ], + "description": "A library for managing linux cron jobs.", + "keywords": [ + "crontab", + "scheduling" + ], + "support": { + "issues": "https://github.com/TiBeN/CrontabManager/issues", + "source": "https://github.com/TiBeN/CrontabManager/tree/v1.4.0" + }, + "time": "2023-05-02T16:54:54+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.2.7", diff --git a/docs/cli-commands.md b/docs/cli-commands.md index 3bb64fbf..094d7916 100644 --- a/docs/cli-commands.md +++ b/docs/cli-commands.md @@ -143,9 +143,13 @@ php artisan koel:sync [options] # Alias, deprecated | `I`, `--ignore=` | The comma-separated tags to ignore (exclude) from scanning. Valid tags are `title`, `album`,`artist`, `albumartist`, `track`, `disc`, `year`, `genre`, `lyrics`, and `cover`. | | `F`, `--force` | Force re-scanning even unchanged files. | +### `koel:scheduler:install` + +Install the command scheduler. Refer to [Command Scheduling](#command-scheduling) for more information. + ### `koel:search:import` -Import all searchable entities with Scout. See [Instant Search](./usage/search) for more information. +Import all searchable entities with Scout. Refer to [Instant Search](./usage/search) for more information. #### Usage @@ -210,16 +214,22 @@ php artisan koel:tags:collect ## Command Scheduling Some of the commands, such as `koel:scan` and `koel:prune`, can be scheduled to run at regular intervals. -Koel uses Laravel’s built-in scheduler to manage this. +Instead of setting up individual cron jobs, you can use Koel’s built-in scheduler to automatically handle the commands for you. -In order to set up the scheduler, you need to add the following cron entry into the crontab of the webserver user (for example, -if it's `www-data`, run `sudo crontab -u www-data -e`): +To set up the scheduler, run the `koel:scheduler:install` command as the web server user (e.g. `www-data` or `nginx`): + +```bash +php artisan koel:scheduler:install +``` + +Alternatively, you can manually add the following cron entry into the crontab of the webserver user (for example, if it's `www-data`, run `sudo crontab -u www-data -e`): ```bash * * * * * cd /path-to-koel-installation && php artisan schedule:run >> /dev/null 2>&1 ``` -This will run the scheduler every minute, which will then run any scheduled commands as needed. +Either way, the scheduler will run every minute once installed, executing any scheduled commands as needed. By default, `koel:scan`, `koel:prune`, and `koel:podcasts:sync` are set to run every day at midnight. -Though you can still manually set up cron jobs for individual commands, the scheduler is the recommended approach to command scheduling in Koel, as it will automatically cover any commands that may be added in the future. +Though you can still manually set up cron jobs for individual commands, the scheduler is the recommended approach to do command scheduling in Koel, +as it will automatically cover any commands that may be added in the future.