diff --git a/apps/federation/appinfo/info.xml b/apps/federation/appinfo/info.xml
index fa4ba4f4ee3e2..d8df4c3d1594d 100644
--- a/apps/federation/appinfo/info.xml
+++ b/apps/federation/appinfo/info.xml
@@ -30,6 +30,7 @@
+ OCA\Federation\Command\ManageTrustedServers
OCA\Federation\Command\SyncFederationAddressBooks
OCA\Federation\Command\SyncFederationCalendars
diff --git a/apps/federation/composer/composer/autoload_classmap.php b/apps/federation/composer/composer/autoload_classmap.php
index deca7ce3f04c8..b0f31ce46a260 100644
--- a/apps/federation/composer/composer/autoload_classmap.php
+++ b/apps/federation/composer/composer/autoload_classmap.php
@@ -10,6 +10,7 @@
'OCA\\Federation\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
'OCA\\Federation\\BackgroundJob\\GetSharedSecret' => $baseDir . '/../lib/BackgroundJob/GetSharedSecret.php',
'OCA\\Federation\\BackgroundJob\\RequestSharedSecret' => $baseDir . '/../lib/BackgroundJob/RequestSharedSecret.php',
+ 'OCA\\Federation\\Command\\ManageTrustedServers' => $baseDir . '/../lib/Command/ManageTrustedServers.php',
'OCA\\Federation\\Command\\SyncFederationAddressBooks' => $baseDir . '/../lib/Command/SyncFederationAddressBooks.php',
'OCA\\Federation\\Command\\SyncFederationCalendars' => $baseDir . '/../lib/Command/SyncFederationCalendars.php',
'OCA\\Federation\\Controller\\OCSAuthAPIController' => $baseDir . '/../lib/Controller/OCSAuthAPIController.php',
diff --git a/apps/federation/composer/composer/autoload_static.php b/apps/federation/composer/composer/autoload_static.php
index edae4c88ab97d..8b5ffbd056401 100644
--- a/apps/federation/composer/composer/autoload_static.php
+++ b/apps/federation/composer/composer/autoload_static.php
@@ -25,6 +25,7 @@ class ComposerStaticInitFederation
'OCA\\Federation\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
'OCA\\Federation\\BackgroundJob\\GetSharedSecret' => __DIR__ . '/..' . '/../lib/BackgroundJob/GetSharedSecret.php',
'OCA\\Federation\\BackgroundJob\\RequestSharedSecret' => __DIR__ . '/..' . '/../lib/BackgroundJob/RequestSharedSecret.php',
+ 'OCA\\Federation\\Command\\ManageTrustedServers' => __DIR__ . '/..' . '/../lib/Command/ManageTrustedServers.php',
'OCA\\Federation\\Command\\SyncFederationAddressBooks' => __DIR__ . '/..' . '/../lib/Command/SyncFederationAddressBooks.php',
'OCA\\Federation\\Command\\SyncFederationCalendars' => __DIR__ . '/..' . '/../lib/Command/SyncFederationCalendars.php',
'OCA\\Federation\\Controller\\OCSAuthAPIController' => __DIR__ . '/..' . '/../lib/Controller/OCSAuthAPIController.php',
diff --git a/apps/federation/lib/Command/ManageTrustedServers.php b/apps/federation/lib/Command/ManageTrustedServers.php
new file mode 100644
index 0000000000000..b84960bd1d349
--- /dev/null
+++ b/apps/federation/lib/Command/ManageTrustedServers.php
@@ -0,0 +1,147 @@
+ 'ok',
+ TrustedServers::STATUS_PENDING => 'pending',
+ TrustedServers::STATUS_FAILURE => 'failure',
+ TrustedServers::STATUS_ACCESS_REVOKED => 'access revoked',
+ ];
+
+ public function __construct(
+ private TrustedServers $trustedServersManager,
+ ) {
+ }
+
+ #[AsCommand(
+ name: 'federation:trusted-servers:add',
+ description: 'Add trusted servers',
+ )]
+ public function add(
+ IOutput $output,
+ #[Argument(description: 'space-separated list of server URLs to add to the trusted servers list')]
+ array $servers = [],
+ ): ExitCode {
+ if (empty($servers)) {
+ $output->writeln('You must provide at least one server URL.');
+ return ExitCode::Invalid;
+ }
+
+ $output->writeln('Adding trusted servers:');
+ $hasFailure = false;
+ foreach ($servers as $server) {
+ if ($this->trustedServersManager->isTrustedServer($server)) {
+ $output->writeln(" - $server already trusted, skipping");
+ continue;
+ }
+
+ try {
+ $this->trustedServersManager->addServer($server);
+ $output->writeln(" - $server added");
+ } catch (\Exception $e) {
+ $output->writeln(" - $server failed: " . $e->getMessage());
+ $hasFailure = true;
+ }
+ }
+
+ return $hasFailure ? ExitCode::Failure : ExitCode::Success;
+ }
+
+ #[AsCommand(
+ name: 'federation:trusted-servers:remove',
+ description: 'Remove trusted servers',
+ )]
+ public function remove(
+ IOutput $output,
+ #[Argument(description: 'space-separated list of server URLs to remove from the trusted servers list')]
+ array $servers = [],
+ ): ExitCode {
+ if (empty($servers)) {
+ $output->writeln('You must provide at least one server URL.');
+ return ExitCode::Invalid;
+ }
+
+ $output->writeln('Removing trusted servers:');
+ $hasFailure = false;
+ foreach ($servers as $server) {
+ $id = $this->findServerId($server);
+ if ($id === null) {
+ $output->writeln(" - $server not found, skipping");
+ continue;
+ }
+
+ try {
+ $this->trustedServersManager->removeServer($id);
+ $output->writeln(" - $server removed");
+ } catch (\Exception $e) {
+ $output->writeln(" - $server failed: " . $e->getMessage());
+ $hasFailure = true;
+ }
+ }
+
+ return $hasFailure ? ExitCode::Failure : ExitCode::Success;
+ }
+
+ #[AsCommand(
+ name: 'federation:trusted-servers:list',
+ description: 'List trusted servers',
+ supportsOutputFormat: true,
+ )]
+ public function list(
+ IOutput $output,
+ ): ExitCode {
+ $servers = $this->trustedServersManager->getServers();
+
+ if (empty($servers)) {
+ $output->writeln('No trusted servers configured.');
+ return ExitCode::Success;
+ }
+
+ $output->writeTableInOutputFormat(
+ array_map(fn (array $server) => [
+ 'id' => $server['id'],
+ 'url' => $server['url'],
+ 'status' => self::STATUS_LABELS[$server['status']] ?? (string)$server['status'],
+ ], $servers),
+ );
+
+ return ExitCode::Success;
+ }
+
+ /**
+ * Trusted servers are stored with a normalized (https:// prefixed,
+ * trailing-slash trimmed) URL, so removal has to look up the id by
+ * matching that same normalization rather than the raw user input.
+ */
+ private function findServerId(string $url): ?int {
+ $normalized = rtrim(
+ (str_starts_with($url, 'http://') || str_starts_with($url, 'https://'))
+ ? $url
+ : 'https://' . $url,
+ '/'
+ );
+
+ foreach ($this->trustedServersManager->getServers() ?? [] as $server) {
+ if (rtrim($server['url'], '/') === $normalized) {
+ return $server['id'];
+ }
+ }
+
+ return null;
+ }
+}