-
Notifications
You must be signed in to change notification settings - Fork 65
feat(services): add lifecycle and connectivity services #984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCA\ServerInfo; | ||
|
|
||
| use OCP\Http\Client\IClientService; | ||
| use OCP\IAppConfig; | ||
|
|
||
| class AppStoreReachability { | ||
| private const CACHE_TTL = 3600; | ||
| private const TIMEOUT = 4; | ||
|
|
||
| public function __construct( | ||
| private IClientService $clientService, | ||
| private IAppConfig $appConfig, | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * @return array{ | ||
| * reachable: bool, | ||
| * statusCode: int, | ||
| * latencyMs: int, | ||
| * checkedAt: int, | ||
| * cached: bool | ||
| * } | ||
| */ | ||
| public function check(): array { | ||
| $cachedAt = $this->appConfig->getValueInt('serverinfo', 'appstore_check_at', 0); | ||
| if ($cachedAt > 0 && (time() - $cachedAt) < self::CACHE_TTL) { | ||
| return [ | ||
| 'reachable' => $this->appConfig->getValueBool('serverinfo', 'appstore_check_reachable'), | ||
| 'statusCode' => $this->appConfig->getValueInt('serverinfo', 'appstore_check_status'), | ||
| 'latencyMs' => $this->appConfig->getValueInt('serverinfo', 'appstore_check_latency'), | ||
| 'checkedAt' => $cachedAt, | ||
| 'cached' => true, | ||
| ]; | ||
| } | ||
|
|
||
| $client = $this->clientService->newClient(); | ||
| $start = microtime(true); | ||
| $reachable = false; | ||
| $status = 0; | ||
| try { | ||
| $response = $client->head('https://apps.nextcloud.com/api/v1/platform/29.0.0/apps.json', [ | ||
| 'timeout' => self::TIMEOUT, | ||
| 'connect_timeout' => self::TIMEOUT, | ||
| 'verify' => true, | ||
| ]); | ||
| $status = $response->getStatusCode(); | ||
| $reachable = $status >= 200 && $status < 400; | ||
| } catch (\Throwable) { | ||
| $reachable = false; | ||
| } | ||
| $latency = (int)round((microtime(true) - $start) * 1000); | ||
|
Check failure on line 61 in lib/AppStoreReachability.php
|
||
| $now = time(); | ||
| $this->appConfig->setValueInt('serverinfo', 'appstore_check_at', $now); | ||
| $this->appConfig->setValueBool('serverinfo', 'appstore_check_reachable', $reachable); | ||
| $this->appConfig->setValueInt('serverinfo', 'appstore_check_status', $status); | ||
| $this->appConfig->setValueInt('serverinfo', 'appstore_check_latency', $latency); | ||
| return [ | ||
| 'reachable' => $reachable, | ||
| 'statusCode' => $status, | ||
| 'latencyMs' => $latency, | ||
| 'checkedAt' => $now, | ||
| 'cached' => false, | ||
| ]; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,105 @@ | ||||||
| <?php | ||||||
|
|
||||||
| declare(strict_types=1); | ||||||
|
|
||||||
| /** | ||||||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||||||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
| */ | ||||||
|
|
||||||
| namespace OCA\ServerInfo; | ||||||
|
|
||||||
| use OCP\IConfig; | ||||||
| use OCP\ServerVersion; | ||||||
|
|
||||||
| class EolInfo { | ||||||
| /** | ||||||
| * PHP version end-of-life dates (security support end). | ||||||
| * Source: https://www.php.net/supported-versions.php | ||||||
| * | ||||||
| * @var array<string, string> version "8.1" => "2025-12-31" | ||||||
| */ | ||||||
| private const PHP_EOL = [ | ||||||
| '7.4' => '2022-11-28', | ||||||
| '8.0' => '2023-11-26', | ||||||
| '8.1' => '2025-12-31', | ||||||
| '8.2' => '2026-12-31', | ||||||
| '8.3' => '2027-12-31', | ||||||
| '8.4' => '2028-12-31', | ||||||
| '8.5' => '2029-12-31', | ||||||
| ]; | ||||||
|
|
||||||
| /** | ||||||
| * Nextcloud major version EOL (community/security end). | ||||||
| * Approximate based on the Nextcloud release schedule. | ||||||
| * | ||||||
| * @var array<string, string> "27" => "2024-06-30" | ||||||
| */ | ||||||
| private const NC_EOL = [ | ||||||
|
Check failure on line 38 in lib/EolInfo.php
|
||||||
| '27' => '2024-06-30', | ||||||
| '28' => '2024-12-31', | ||||||
| '29' => '2025-06-30', | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| '30' => '2025-12-31', | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| '31' => '2026-06-30', | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| '32' => '2026-12-31', | ||||||
| '33' => '2027-06-30', | ||||||
| '34' => '2028-06-30', | ||||||
| '35' => '2028-12-31', | ||||||
| ]; | ||||||
|
Comment on lines
+38
to
+48
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. … and this list/data is external information that has no update mechanism. I'm clarifying how we want to cope with this. |
||||||
|
|
||||||
| public function __construct( | ||||||
| private IConfig $config, | ||||||
| private ServerVersion $serverVersion, | ||||||
| ) { | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @return array{ | ||||||
| * php: array{version: string, eol: ?string, daysUntilEol: ?int, status: string}, | ||||||
| * nextcloud: array{version: string, major: string, eol: ?string, daysUntilEol: ?int, status: string} | ||||||
| * } | ||||||
| */ | ||||||
| public function getEolInfo(): array { | ||||||
| $phpMajor = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; | ||||||
| $phpEol = self::PHP_EOL[$phpMajor] ?? null; | ||||||
| $ncVersionParts = $this->serverVersion->getVersion(); | ||||||
| $ncVersion = implode('.', $ncVersionParts); | ||||||
| $ncMajor = (string)(int)($ncVersionParts[0] ?? 0); | ||||||
| $ncEol = self::NC_EOL[$ncMajor] ?? null; | ||||||
|
Check failure on line 68 in lib/EolInfo.php
|
||||||
|
|
||||||
| return [ | ||||||
| 'php' => $this->makeEntry($phpMajor, $phpEol), | ||||||
| 'nextcloud' => array_merge( | ||||||
| $this->makeEntry($ncMajor, $ncEol), | ||||||
| ['version' => $ncVersion, 'major' => $ncMajor], | ||||||
| ), | ||||||
| ]; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @return array{version: string, eol: ?string, daysUntilEol: ?int, status: string} | ||||||
| */ | ||||||
| private function makeEntry(string $version, ?string $eolDate): array { | ||||||
| if ($eolDate === null) { | ||||||
| return ['version' => $version, 'eol' => null, 'daysUntilEol' => null, 'status' => 'unknown']; | ||||||
| } | ||||||
| try { | ||||||
| $eolTs = (new \DateTimeImmutable($eolDate))->getTimestamp(); | ||||||
| } catch (\Throwable) { | ||||||
| return ['version' => $version, 'eol' => $eolDate, 'daysUntilEol' => null, 'status' => 'unknown']; | ||||||
| } | ||||||
| $days = (int)floor(($eolTs - time()) / 86400); | ||||||
| $status = 'ok'; | ||||||
| if ($days < 0) { | ||||||
| $status = 'critical'; | ||||||
| } elseif ($days < 90) { | ||||||
| $status = 'warning'; | ||||||
| } | ||||||
| return [ | ||||||
| 'version' => $version, | ||||||
| 'eol' => $eolDate, | ||||||
| 'daysUntilEol' => $days, | ||||||
| 'status' => $status, | ||||||
| ]; | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCA\ServerInfo; | ||
|
|
||
| use OCP\App\IAppManager; | ||
| use OCP\IDBConnection; | ||
| use OCP\Share\IShare; | ||
|
|
||
| class FederationStats { | ||
| public function __construct( | ||
| private IAppManager $appManager, | ||
| private IDBConnection $db, | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * @return array{ | ||
| * enabled: bool, | ||
| * sharesSent: int, | ||
| * sharesReceived: int, | ||
| * sharesSentToGroups: int, | ||
| * trustedServers: int, | ||
| * topPeers: list<array{server: string, count: int}> | ||
| * } | ||
| */ | ||
| public function getFederationStats(): array { | ||
| $enabled = $this->appManager->isInstalled('federation') || $this->appManager->isInstalled('federatedfilesharing'); | ||
|
|
||
| try { | ||
| $sent = $this->countShareType(IShare::TYPE_REMOTE); | ||
| $sentGroup = $this->countShareType(IShare::TYPE_REMOTE_GROUP); | ||
| $received = $this->countReceived(); | ||
| $trusted = $this->countTrustedServers(); | ||
| $top = $this->topPeers(); | ||
| } catch (\Throwable) { | ||
| $sent = 0; | ||
| $sentGroup = 0; | ||
| $received = 0; | ||
| $trusted = 0; | ||
| $top = []; | ||
| } | ||
|
|
||
| return [ | ||
| 'enabled' => $enabled, | ||
| 'sharesSent' => $sent, | ||
| 'sharesSentToGroups' => $sentGroup, | ||
| 'sharesReceived' => $received, | ||
| 'trustedServers' => $trusted, | ||
| 'topPeers' => $top, | ||
| ]; | ||
| } | ||
|
|
||
| private function countShareType(int $type): int { | ||
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->select($qb->func()->count('id')) | ||
| ->from('share') | ||
| ->where($qb->expr()->eq('share_type', $qb->createNamedParameter($type))); | ||
| $result = $qb->executeQuery(); | ||
| $count = (int)$result->fetchOne(); | ||
| $result->closeCursor(); | ||
| return $count; | ||
| } | ||
|
|
||
| private function countReceived(): int { | ||
| try { | ||
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->select($qb->func()->count('id'))->from('share_external'); | ||
| $result = $qb->executeQuery(); | ||
| $count = (int)$result->fetchOne(); | ||
| $result->closeCursor(); | ||
| return $count; | ||
| } catch (\Throwable) { | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| private function countTrustedServers(): int { | ||
| try { | ||
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->select($qb->func()->count('id'))->from('trusted_servers'); | ||
| $result = $qb->executeQuery(); | ||
| $count = (int)$result->fetchOne(); | ||
| $result->closeCursor(); | ||
| return $count; | ||
| } catch (\Throwable) { | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @return list<array{server: string, count: int}> | ||
| */ | ||
| private function topPeers(int $limit = 5): array { | ||
| try { | ||
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->select('share_with') | ||
| ->selectAlias($qb->func()->count('id'), 'count') | ||
| ->from('share') | ||
| ->where($qb->expr()->in('share_type', $qb->createNamedParameter( | ||
| [IShare::TYPE_REMOTE, IShare::TYPE_REMOTE_GROUP], | ||
| \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT_ARRAY, | ||
| ))) | ||
| ->groupBy('share_with') | ||
| ->orderBy('count', 'DESC') | ||
| ->setMaxResults($limit); | ||
| $result = $qb->executeQuery(); | ||
| $out = []; | ||
| while (($row = $result->fetch()) !== false) { | ||
| $with = (string)($row['share_with'] ?? ''); | ||
| $at = strrpos($with, '@'); | ||
| $server = $at !== false ? substr($with, $at + 1) : $with; | ||
| $out[] = [ | ||
| 'server' => $server, | ||
| 'count' => (int)($row['count'] ?? 0), | ||
| ]; | ||
| } | ||
| $result->closeCursor(); | ||
| return $out; | ||
| } catch (\Throwable) { | ||
| return []; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This list/data …