1, 'generated_at' => date('c'), ], $snapshot); $json = json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); if ($json === false) { throw new RuntimeException('Could not encode replication bootstrap snapshot.'); } $tempPath = tempnam($dir, 'replication-bootstrap-'); if ($tempPath === false) { throw new RuntimeException('Could not create replication bootstrap snapshot temp file.'); } try { if (file_put_contents($tempPath, $json . PHP_EOL, LOCK_EX) === false) { throw new RuntimeException('Could not write replication bootstrap snapshot.'); } if (!rename($tempPath, $path)) { throw new RuntimeException('Could not atomically replace replication bootstrap snapshot.'); } } finally { if (is_file($tempPath)) { @unlink($tempPath); } } } public static function applyToGlobals(array $snapshot): void { try { $active = is_array($snapshot['active'] ?? null) ? $snapshot['active'] : []; $database = self::activeDatabaseConfigFromSnapshot($active['database'] ?? null); $redis = self::activeRedisConfigFromSnapshot($active['redis'] ?? null); $minio = self::activeMinioConfigFromSnapshot($active['minio'] ?? null); if ($database !== null) { $GLOBALS['CONFIG_DB'] = array_merge($GLOBALS['CONFIG_DB'] ?? [], $database); } if ($redis !== null) { $GLOBALS['REDIS_CONFIG'] = array_merge($GLOBALS['REDIS_CONFIG'] ?? [], $redis); } if ($minio !== null) { $GLOBALS['MINIO'] = array_merge($GLOBALS['MINIO'] ?? [], $minio); } } catch (Throwable $throwable) { error_log('[replication-bootstrap] Falling back to environment configuration: ' . $throwable->getMessage()); } } public static function activeDatabaseConfigFromSnapshot(mixed $config): ?array { if (!is_array($config)) { return null; } $host = trim((string)($config['host'] ?? '')); $database = trim((string)($config['database'] ?? '')); $user = trim((string)($config['user'] ?? '')); if ($host === '' || $database === '' || $user === '') { return null; } return [ 'host' => $host, 'user' => $user, 'password' => replication_secret_box::decrypt($config['password_secret'] ?? ''), 'database' => $database, 'port' => (int)($config['port'] ?? 3306) ?: 3306, 'ssl_mode' => (string)($config['ssl_mode'] ?? 'DISABLED'), ]; } public static function activeRedisConfigFromSnapshot(mixed $config): ?array { if (!is_array($config)) { return null; } $host = trim((string)($config['host'] ?? '')); if ($host === '') { return null; } return [ 'host' => $host, 'user' => (string)($config['user'] ?? ''), 'password' => replication_secret_box::decrypt($config['password_secret'] ?? ''), 'database' => (int)($config['database'] ?? 0), 'port' => (int)($config['port'] ?? 6379) ?: 6379, ]; } public static function activeMinioConfigFromSnapshot(mixed $config): ?array { if (!is_array($config)) { return null; } $endpoint = trim((string)($config['endpoint'] ?? '')); $accessKey = trim((string)($config['access_key'] ?? $config['user'] ?? '')); if ($endpoint === '' || $accessKey === '') { return null; } return [ 'endpoint' => $endpoint, 'access_key' => $accessKey, 'secret_key' => replication_secret_box::decrypt($config['secret_key_secret'] ?? $config['password_secret'] ?? ''), 'buckets' => is_array($config['buckets'] ?? null) ? array_values($config['buckets']) : ($config['buckets'] ?? null), ]; } private static function storageDir(): string { $root = defined('WD') ? WD : dirname(__DIR__); return $root . DIRECTORY_SEPARATOR . 'storage'; } }