Files
api/services/nginx/app/classes/application_write_freeze.php
T

111 lines
3.0 KiB
PHP

<?php
namespace classes;
use RuntimeException;
class application_write_freeze
{
public static function freeze(string $reason, string $owner, int $ttlSeconds = 300): void
{
$payload = [
'reason' => $reason,
'owner' => $owner,
'created_at' => date('c'),
'expires_at' => date('c', time() + max(30, $ttlSeconds)),
];
self::writeState($payload);
}
public static function unfreeze(?string $owner = null): void
{
$state = self::state();
if ($owner !== null && isset($state['owner']) && $state['owner'] !== $owner) {
return;
}
$path = self::statePath();
if (is_file($path)) {
@unlink($path);
}
}
public static function state(): array
{
$path = self::statePath();
if (!is_file($path)) {
return [];
}
$state = json_decode((string)file_get_contents($path), true);
if (!is_array($state)) {
@unlink($path);
return [];
}
$expiresAt = strtotime((string)($state['expires_at'] ?? ''));
if ($expiresAt !== false && $expiresAt < time()) {
@unlink($path);
return [];
}
return $state;
}
public static function isFrozen(): bool
{
return self::state() !== [];
}
public static function shouldBlock(string $method, string $uri, bool $isCronOrCli): bool
{
if (!self::isFrozen()) {
return false;
}
if ($isCronOrCli) {
return true;
}
$method = strtoupper($method);
if (in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
return false;
}
$path = parse_url($uri, PHP_URL_PATH) ?: '';
return !str_starts_with($path, '/superuser/replication');
}
private static function writeState(array $state): void
{
$path = self::statePath();
$dir = dirname($path);
if (!is_dir($dir) && !mkdir($dir, 0770, true) && !is_dir($dir)) {
throw new RuntimeException('Could not create write-freeze directory.');
}
$tempPath = tempnam($dir, 'write-freeze-');
if ($tempPath === false) {
throw new RuntimeException('Could not create write-freeze temp file.');
}
try {
file_put_contents($tempPath, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL, LOCK_EX);
if (!rename($tempPath, $path)) {
throw new RuntimeException('Could not atomically replace write-freeze state.');
}
} finally {
if (is_file($tempPath)) {
@unlink($tempPath);
}
}
}
private static function statePath(): string
{
$root = defined('WD') ? WD : dirname(__DIR__);
return $root . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'application-write-freeze.json';
}
}