get('/superuser/replication', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_view'); $refresh = $this->toBool($this->getParameter('refresh'), false); $response->success((new replication_manager())->summary($refresh)); }, [ 'superuser_replication_view' => 'View database, Redis, and MinIO replication topology and status', ]); $this->post('/superuser/replication/databases', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); $host = (new replication_manager())->addHost('database', $this->getParametersAsArray(), $this->actorUserId()); $response->success($host, 201); }, [ 'superuser_replication_manage' => 'Add and manage database replication host credentials', ]); $this->post('/superuser/replication/redis', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); $host = (new replication_manager())->addHost('redis', $this->getParametersAsArray(), $this->actorUserId()); $response->success($host, 201); }, [ 'superuser_replication_manage' => 'Add and manage Redis replication host credentials', ]); $this->post('/superuser/replication/minio', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); $host = (new replication_manager())->addHost('minio', $this->getParametersAsArray(), $this->actorUserId()); $response->success($host, 201); }, [ 'superuser_replication_manage' => 'Add and manage MinIO replication host credentials', ]); $this->post('/superuser/replication/compose-template', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); $response->success(replication_manager::composeTemplate($this->getParametersAsArray())); }, [ 'superuser_replication_manage' => 'Generate Docker Compose templates for replication-ready database, Redis, and MinIO hosts', ]); $this->post('/superuser/replication/test-credentials', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); $parameters = $this->getParametersAsArray(); $response->success((new replication_manager())->testCredentials( (string)($parameters['kind'] ?? ''), $parameters )); }, [ 'superuser_replication_manage' => 'Test database, Redis, and MinIO replication host credentials before saving them', ]); $this->post('/superuser/replication/{kind}/{id}/test', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); $response->success((new replication_manager())->testHost( (string)$this->fromRoute('kind'), $this->routeId(), $this->actorUserId() )); }, [ 'superuser_replication_manage' => 'Validate database, Redis, and MinIO replication host connectivity and privileges', ]); $this->post('/superuser/replication/{kind}/{id}/provision', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); try { $result = (new replication_manager())->provisionHost( (string)$this->fromRoute('kind'), $this->routeId(), $this->actorUserId(), true ); if (($result['ok'] ?? false) !== true) { $response->error($result, 409); } $response->success($result); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'superuser_replication_manage' => 'Provision a database, Redis, or MinIO host as a replica of the current primary', ]); $this->post('/superuser/replication/{kind}/{id}/promote', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_promote'); try { $response->success((new replication_manager())->promoteHost( (string)$this->fromRoute('kind'), $this->routeId(), $this->actorUserId() )); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'superuser_replication_promote' => 'Promote a healthy caught-up database, Redis, or MinIO replica to primary', ]); $this->patch('/superuser/replication/{kind}/{id}', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_manage'); try { $response->success((new replication_manager())->renameHost( (string)$this->fromRoute('kind'), $this->routeId(), $this->getParametersAsArray(), $this->actorUserId() )); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 400); } }, [ 'superuser_replication_manage' => 'Rename database, Redis, and MinIO replication hosts', ]); $this->delete('/superuser/replication/{kind}/{id}', function () { global $response; $this->requireClassicSuperuserPermission('superuser_replication_remove'); try { $response->success((new replication_manager())->removeHost( (string)$this->fromRoute('kind'), $this->routeId(), $this->actorUserId() )); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'superuser_replication_remove' => 'Remove inactive prior hosts and unhealthy database, Redis, or MinIO replicas', ]); } /** * Replication controls alter infrastructure state and must only be used by * a classic superuser session. Subuser bearer tokens can carry a delegated * customer context via X-Customer-Number, so do not allow them to fall back * to plain string user permission checks for these routes. */ private function requireClassicSuperuserPermission(string $permission): bool { global $response; if ((new authentication())->get_subuser() !== false) { $response->error('Subuser sessions cannot manage replication.', 403); } return $this->requirePermission($permission); } private function routeId(): int { $id = (int)$this->fromRoute('id'); $this->requireParameterIntPositive($id, 'id'); return $id; } private function actorUserId(): ?int { try { $user = (new authentication())->get_user(); return $user !== false && isset($user->id) ? (int)$user->id : null; } catch (Throwable) { return null; } } private function toBool(mixed $value, bool $default): bool { if (is_bool($value)) { return $value; } if ($value === null) { return $default; } $normalized = strtolower(trim((string)$value)); if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) { return true; } if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) { return false; } return $default; } }