New command vitec:scheduler-setup creates the scheduler group VITEC with the tasks vitec:ssr-health every 5 minutes and vitec:client-error-cleanup daily at 04:23, safe to run again; --disabled creates them switched off, --enable switches them on after the old crontab lines are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
6.0 KiB
PHP
137 lines
6.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Evomedien\Vitec\Command;
|
||
|
||
use Symfony\Component\Console\Attribute\AsCommand;
|
||
use Symfony\Component\Console\Command\Command;
|
||
use Symfony\Component\Console\Input\InputInterface;
|
||
use Symfony\Component\Console\Input\InputOption;
|
||
use Symfony\Component\Console\Output\OutputInterface;
|
||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||
use TYPO3\CMS\Core\Database\Connection;
|
||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||
use TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository;
|
||
use TYPO3\CMS\Scheduler\Execution;
|
||
use TYPO3\CMS\Scheduler\Service\TaskService;
|
||
|
||
/**
|
||
* Puts the project's recurring jobs into the TYPO3 Scheduler (group
|
||
* "VITEC"), so they are visible, startable and switchable in
|
||
* System › Scheduler instead of hiding in the crontab. The only cron
|
||
* line left on dedi5092 is the core "scheduler:run" every minute.
|
||
*
|
||
* Idempotent: a task whose command already exists in the scheduler is
|
||
* left alone. --disabled creates new tasks switched off (use while the
|
||
* old crontab lines still run, so nothing runs twice), --enable switches
|
||
* the VITEC tasks on afterwards.
|
||
*/
|
||
#[AsCommand(
|
||
name: 'vitec:scheduler-setup',
|
||
description: 'Create the VITEC scheduler tasks (SSR health every 5 min, client error cleanup daily) in the group "VITEC"; safe to run again'
|
||
)]
|
||
final class SchedulerSetupCommand extends Command
|
||
{
|
||
private const GROUP = 'VITEC';
|
||
|
||
/** command => [cron expression, description] */
|
||
private const TASKS = [
|
||
'vitec:ssr-health' => [
|
||
'*/5 * * * *',
|
||
'SSR health check: samples /__health and /__stats of the SSR node into the health log (cockpit charts), '
|
||
. 'mails the alert recipients from Site settings › VITEC on down/up changes and falls back to the SPA '
|
||
. 'after repeated failures. Formerly crontab.',
|
||
],
|
||
'vitec:client-error-cleanup' => [
|
||
'23 4 * * *',
|
||
'Deletes browser error reports (client errors from the React frontend) older than 30 days. Formerly crontab.',
|
||
],
|
||
];
|
||
|
||
public function __construct(
|
||
private readonly TaskService $taskService,
|
||
private readonly SchedulerTaskRepository $taskRepository,
|
||
private readonly ConnectionPool $connectionPool,
|
||
) {
|
||
parent::__construct();
|
||
}
|
||
|
||
protected function configure(): void
|
||
{
|
||
$this->addOption('disabled', null, InputOption::VALUE_NONE, 'Create new tasks switched off');
|
||
$this->addOption('enable', null, InputOption::VALUE_NONE, 'Switch on all VITEC tasks created by this command');
|
||
}
|
||
|
||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||
{
|
||
Bootstrap::initializeBackendAuthentication();
|
||
$groupUid = $this->groupUid();
|
||
$output->writeln('Scheduler group "' . self::GROUP . '": uid ' . $groupUid);
|
||
|
||
foreach (self::TASKS as $command => [$cron, $description]) {
|
||
$existing = $this->findTask($command);
|
||
if ($existing !== null) {
|
||
if ($input->getOption('enable') && (int)$existing['disable'] === 1) {
|
||
$task = $this->taskRepository->findByUid((int)$existing['uid']);
|
||
$task->setDisabled(false);
|
||
$this->taskRepository->update($task);
|
||
$output->writeln(sprintf('<info>%s: enabled (uid %d)</info>', $command, $existing['uid']));
|
||
} else {
|
||
$output->writeln(sprintf('%s: exists (uid %d, %s)', $command, $existing['uid'],
|
||
(int)$existing['disable'] === 1 ? 'disabled' : 'enabled'));
|
||
}
|
||
continue;
|
||
}
|
||
if (!$this->taskService->isTaskTypeRegistered($command)) {
|
||
$output->writeln('<error>' . $command . ': not available as scheduler task type</error>');
|
||
continue;
|
||
}
|
||
$task = $this->taskService->createNewTask($command);
|
||
$task->setDescription($description);
|
||
$task->setTaskGroup($groupUid);
|
||
$task->setExecution(Execution::createRecurringExecution(time() - 60, 0, 0, false, $cron));
|
||
$task->setDisabled((bool)$input->getOption('disabled'));
|
||
if ($this->taskRepository->add($task)) {
|
||
$output->writeln(sprintf('<info>%s: created (uid %d, %s, %s)</info>', $command, $task->getTaskUid(), $cron,
|
||
$task->isDisabled() ? 'disabled' : 'enabled'));
|
||
} else {
|
||
$output->writeln('<error>' . $command . ': could not be created</error>');
|
||
}
|
||
}
|
||
return Command::SUCCESS;
|
||
}
|
||
|
||
private function groupUid(): int
|
||
{
|
||
$connection = $this->connectionPool->getConnectionForTable('tx_scheduler_task_group');
|
||
$uid = $connection->executeQuery(
|
||
'SELECT uid FROM tx_scheduler_task_group WHERE groupName = ? AND deleted = 0 ORDER BY uid LIMIT 1',
|
||
[self::GROUP]
|
||
)->fetchOne();
|
||
if ($uid !== false) {
|
||
return (int)$uid;
|
||
}
|
||
$sorting = (int)$connection->executeQuery('SELECT MAX(sorting) FROM tx_scheduler_task_group')->fetchOne();
|
||
$connection->insert('tx_scheduler_task_group', [
|
||
'pid' => 0,
|
||
'tstamp' => time(),
|
||
'crdate' => time(),
|
||
'groupName' => self::GROUP,
|
||
'color' => '#f47937',
|
||
'description' => 'Recurring jobs of the VITEC website (SSR monitoring, cleanups)',
|
||
'sorting' => $sorting + 256,
|
||
], ['pid' => Connection::PARAM_INT, 'tstamp' => Connection::PARAM_INT, 'crdate' => Connection::PARAM_INT, 'sorting' => Connection::PARAM_INT]);
|
||
return (int)$connection->lastInsertId();
|
||
}
|
||
|
||
private function findTask(string $command): ?array
|
||
{
|
||
$row = $this->connectionPool->getConnectionForTable('tx_scheduler_task')->executeQuery(
|
||
'SELECT uid, disable FROM tx_scheduler_task WHERE tasktype = ? AND deleted = 0 ORDER BY uid LIMIT 1',
|
||
[$command]
|
||
)->fetchAssociative();
|
||
return $row === false ? null : $row;
|
||
}
|
||
}
|