appwrite/src/Appwrite/Platform/Tasks/Maintenance.php

187 lines
7.5 KiB
PHP
Raw Normal View History

2020-12-14 16:39:44 +00:00
<?php
2022-11-14 10:01:41 +00:00
namespace Appwrite\Platform\Tasks;
2020-12-14 16:39:44 +00:00
use Appwrite\Event\Certificate;
2022-04-19 13:13:55 +00:00
use Appwrite\Event\Delete;
2025-06-10 12:40:02 +00:00
use DateInterval;
use DateTime;
use Utopia\Console;
2022-07-13 07:02:55 +00:00
use Utopia\Database\Database;
use Utopia\Database\DateTime as DatabaseDateTime;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Document;
2022-04-10 09:38:22 +00:00
use Utopia\Database\Query;
2022-07-13 07:02:55 +00:00
use Utopia\Platform\Action;
2024-04-01 11:02:47 +00:00
use Utopia\System\System;
use Utopia\Validator\WhiteList;
2022-07-13 07:02:55 +00:00
2024-08-20 07:08:07 +00:00
class Maintenance extends Action
2022-07-14 02:04:31 +00:00
{
2022-08-02 01:58:36 +00:00
public static function getName(): string
{
return 'maintenance';
}
2022-07-13 07:02:55 +00:00
public function __construct()
{
$this
->desc('Schedules maintenance tasks and publishes them to our queues')
->param('type', 'loop', new WhiteList(['loop', 'trigger']), 'How to run task. "loop" is meant for container entrypoint, and "trigger" for manual execution.')
->inject('dbForPlatform')
->inject('console')
2022-12-20 16:11:30 +00:00
->inject('queueForCertificates')
->inject('queueForDeletes')
2025-06-04 08:37:43 +00:00
->callback($this->action(...));
2022-07-13 07:02:55 +00:00
}
public function action(string $type, Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes): void
2022-07-13 07:02:55 +00:00
{
2021-01-26 20:49:13 +00:00
Console::title('Maintenance V1');
Console::success(APP_NAME . ' maintenance process v1 has started');
2021-01-26 20:49:13 +00:00
$interval = (int) System::getEnv('_APP_MAINTENANCE_INTERVAL', '86400'); // 1 day
2024-04-01 11:02:47 +00:00
$usageStatsRetentionHourly = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_HOURLY', '8640000'); //100 days
$cacheRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
$schedulesDeletionRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_SCHEDULES', '86400'); // 1 Day
$jobInitTime = System::getEnv('_APP_MAINTENANCE_START_TIME', '00:00'); // (hour:minutes)
$now = new \DateTime();
$now->setTimezone(new \DateTimeZone(date_default_timezone_get()));
$next = new \DateTime($now->format("Y-m-d $jobInitTime"));
$next->setTimezone(new \DateTimeZone(date_default_timezone_get()));
$delay = $next->getTimestamp() - $now->getTimestamp();
/**
* If time passed for the target day.
*/
if ($delay <= 0) {
$next->add(\DateInterval::createFromDateString('1 days'));
$delay = $next->getTimestamp() - $now->getTimestamp();
}
$action = function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
$time = DatabaseDateTime::now();
2021-01-26 20:49:13 +00:00
2023-06-05 16:13:00 +00:00
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
2023-12-12 11:26:28 +00:00
2025-06-11 14:36:51 +00:00
// Iterate through project only if it was accessed in last 30 days
$dateInterval = DateInterval::createFromDateString('30 days');
$before30days = (new DateTime())->sub($dateInterval);
2025-04-11 14:52:19 +00:00
$dbForPlatform->foreach(
'projects',
function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($project)
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
2025-04-11 14:52:19 +00:00
->trigger();
},
[
2025-04-14 06:23:35 +00:00
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
2025-04-11 14:52:19 +00:00
Query::limit(100),
2025-06-11 14:36:51 +00:00
Query::greaterThanEqual('accessedAt', DatabaseDateTime::format($before30days)),
Query::orderAsc('teamInternalId'),
2025-04-11 14:52:19 +00:00
]
);
2023-12-12 11:26:28 +00:00
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($console)
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
2023-06-05 16:13:00 +00:00
$this->notifyDeleteConnections($queueForDeletes);
$this->renewCertificates($dbForPlatform, $queueForCertificates);
2023-06-05 16:13:00 +00:00
$this->notifyDeleteCache($cacheRetention, $queueForDeletes);
$this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes);
2025-11-14 02:55:41 +00:00
$this->notifyDeleteCSVExports($queueForDeletes);
};
if ($type === 'loop') {
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
Console::loop(function () use ($action) {
$action();
}, $interval, $delay);
} elseif ($type === 'trigger') {
$action();
}
2023-06-05 16:13:00 +00:00
}
2021-01-26 20:49:13 +00:00
2023-06-05 16:13:00 +00:00
private function notifyDeleteConnections(Delete $queueForDeletes): void
{
2024-01-11 03:15:11 +00:00
$queueForDeletes
2023-06-05 16:13:00 +00:00
->setType(DELETE_TYPE_REALTIME)
->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -60))
2023-06-05 16:13:00 +00:00
->trigger();
}
2022-04-10 09:38:22 +00:00
2025-11-14 02:55:41 +00:00
private function notifyDeleteCSVExports(Delete $queueForDeletes): void
{
$queueForDeletes
->setType(DELETE_TYPE_CSV_EXPORTS)
->trigger();
}
private function renewCertificates(Database $dbForPlatform, Certificate $queueForCertificate): void
2023-06-05 16:13:00 +00:00
{
$time = DatabaseDateTime::now();
2023-06-05 16:13:00 +00:00
$certificates = $dbForPlatform->find('certificates', [
2023-06-05 16:13:00 +00:00
Query::lessThan('attempts', 5), // Maximum 5 attempts
Query::isNotNull('renewDate'),
2023-06-05 16:13:00 +00:00
Query::lessThanEqual('renewDate', $time), // includes 60 days cooldown (we have 30 days to renew)
Query::limit(200), // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
]);
2022-07-03 09:36:59 +00:00
if (\count($certificates) === 0) {
Console::info("[{$time}] No certificates for renewal.");
return;
}
2022-11-16 12:51:43 +00:00
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
2023-06-05 16:13:00 +00:00
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$appRegion = System::getEnv('_APP_REGION', 'default');
foreach ($certificates as $certificate) {
$domain = $certificate->getAttribute('domain');
$rule = $isMd5 ?
$dbForPlatform->getDocument('rules', md5($domain)) :
$dbForPlatform->findOne('rules', [
2025-07-29 11:23:55 +00:00
Query::equal('domain', [$domain]),
Query::limit(1)
2025-07-29 11:23:55 +00:00
]);
if ($rule->isEmpty() || $rule->getAttribute('region') !== $appRegion) {
continue;
2023-06-05 16:13:00 +00:00
}
$queueForCertificate
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
]))
->setAction(Certificate::ACTION_GENERATION)
->trigger();
2022-11-16 12:51:43 +00:00
}
2023-06-05 16:13:00 +00:00
}
2022-11-16 12:51:43 +00:00
2023-06-05 16:13:00 +00:00
private function notifyDeleteCache($interval, Delete $queueForDeletes): void
{
$queueForDeletes
2023-06-05 16:13:00 +00:00
->setType(DELETE_TYPE_CACHE_BY_TIMESTAMP)
->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $interval))
2023-06-05 16:13:00 +00:00
->trigger();
}
2020-12-14 21:26:37 +00:00
2023-06-05 16:13:00 +00:00
private function notifyDeleteSchedules($interval, Delete $queueForDeletes): void
{
$queueForDeletes
2023-06-05 16:13:00 +00:00
->setType(DELETE_TYPE_SCHEDULES)
->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $interval))
2023-06-05 16:13:00 +00:00
->trigger();
2022-07-13 07:02:55 +00:00
}
2022-07-14 02:04:31 +00:00
}