get('/modules/backup/backups', function () { ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/backup/backups'); global $response; $this->requireClassicSuperuserPermission('modules_backup_list'); try { $response->success( (new backup_store())->listBackups( (int)($this->fromQuery('limit') ?? 50), (int)($this->fromQuery('offset') ?? 0) ) ); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'modules_backup_list' => 'List backup records, components, and legacy metadata', ]); $this->post('/modules/backup/backups', function () { ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups'); global $response; $this->requireClassicSuperuserPermission('modules_backup_create'); $user_id = $this->actorUserId(); try { $parameters = $this->getParametersAsArray(); $result = (new backup_store())->enqueueCreateBackup( $parameters['name'] ?? null, $parameters['description'] ?? null, $user_id, 'manual' ); (new logs_o())->add('modules_backup', 'global', 1, $user_id ?? 0, 'MODULES_BACKUP', 'Queued backup: ' . $result['backup_uuid']); $response->success($result); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'modules_backup_create' => 'Queue a backup job', ]); $this->get('/modules/backup/jobs/{id}', function () { ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/backup/jobs/{id}'); global $response; $this->requireClassicSuperuserPermission('modules_backup_list'); $job_id = (int)$this->fromRoute('id'); $job = (new backup_store())->getJob($job_id); if ($job === null) { $response->error('Backup job not found.', 404); } $response->success($job); }, [ 'modules_backup_list' => 'View backup job status', ]); $this->post('/modules/backup/backups/{backup_uuid}/verify', function () { ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups/{backup_uuid}/verify'); global $response; $this->requireClassicSuperuserPermission('modules_backup_verify'); try { $backup_uuid = (string)$this->fromRoute('backup_uuid'); $result = (new backup_store())->enqueueVerifyBackup($backup_uuid, $this->actorUserId()); (new logs_o())->add('modules_backup', 'global', 1, $this->actorUserId() ?? 0, 'MODULES_BACKUP_VERIFY', 'Queued backup verification: ' . $backup_uuid); $response->success($result); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'modules_backup_verify' => 'Verify a backup', ]); $this->post('/modules/backup/backups/{backup_uuid}/restore/preview', function () { ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups/{backup_uuid}/restore/preview'); global $response; $this->requireClassicSuperuserPermission('modules_backup_restore'); try { $backup_uuid = (string)$this->fromRoute('backup_uuid'); $result = (new backup_store())->previewRestore($backup_uuid, $this->actorUserId()); (new logs_o())->add('modules_backup', 'global', 1, $this->actorUserId() ?? 0, 'MODULES_BACKUP_RESTORE_PREVIEW', 'Previewed backup restore: ' . $backup_uuid); $response->success($result); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'modules_backup_restore' => 'Preview production restore from a backup', ]); $this->post('/modules/backup/backups/{backup_uuid}/restore', function () { ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups/{backup_uuid}/restore'); global $response; $this->requireClassicSuperuserPermission('modules_backup_restore'); try { $parameters = $this->getParametersAsArray(); $backup_uuid = (string)$this->fromRoute('backup_uuid'); $result = (new backup_store())->enqueueRestore( $backup_uuid, (int)($parameters['preview_id'] ?? 0), (string)($parameters['confirmation_phrase'] ?? ''), (string)($parameters['reason'] ?? ''), $this->actorUserId(), [ 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', ] ); (new logs_o())->add('modules_backup', 'global', 1, $this->actorUserId() ?? 0, 'MODULES_BACKUP_RESTORE', 'Queued backup restore: ' . $backup_uuid); $response->success($result); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'modules_backup_restore' => 'Execute production restore from a backup', ]); $this->get('/modules/backup/restore-audit', function () { ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/backup/restore-audit'); global $response; $this->requireClassicSuperuserPermission('modules_backup_restore'); try { $response->success((new backup_store())->restoreAudit((int)($this->fromQuery('limit') ?? 50))); } catch (Throwable $throwable) { $response->error(['message' => $throwable->getMessage()], 409); } }, [ 'modules_backup_restore' => 'View backup restore audit log', ]); } private function requireClassicSuperuserPermission(string $permission): bool { global $response; if ((new authentication())->get_subuser() !== false) { $response->error('Subuser sessions cannot manage backup disaster recovery.', 403); } return $this->requirePermission($permission); } private function actorUserId(): ?int { try { $user = (new authentication())->get_user(); return $user !== false && isset($user->id) ? (int)$user->id : null; } catch (Throwable) { return null; } } }