diff --git a/apps/ainas-controlplane/appinfo/info.xml b/apps/ainas-controlplane/appinfo/info.xml
new file mode 100644
index 0000000..d36430e
--- /dev/null
+++ b/apps/ainas-controlplane/appinfo/info.xml
@@ -0,0 +1,30 @@
+
+
+ ainascontrolplane
+ aiNAS Control Plane
+ Thin aiNAS shell app for Nextcloud integration
+ Provides aiNAS-branded entry points inside Nextcloud while delegating business logic to the aiNAS control plane.
+ 0.1.0
+ AGPL-3.0-or-later
+ aiNAS
+ AinasControlplane
+ integration
+
+
+
+
+
+ ainascontrolplane
+ aiNAS
+ ainascontrolplane.page.index
+ app.svg
+ link
+
+
+
+ OCA\AinasControlplane\Settings\Admin
+ OCA\AinasControlplane\Settings\AdminSection
+
+
+
diff --git a/apps/ainas-controlplane/composer.json b/apps/ainas-controlplane/composer.json
new file mode 100644
index 0000000..6f316d5
--- /dev/null
+++ b/apps/ainas-controlplane/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "ainas/ainascontrolplane",
+ "description": "aiNAS Nextcloud shell app",
+ "license": "AGPL-3.0-or-later",
+ "autoload": {
+ "psr-4": {
+ "OCA\\AinasControlplane\\": "lib/"
+ }
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "nextcloud/ocp": "dev-stable31"
+ }
+}
+
diff --git a/apps/ainas-controlplane/lib/AppInfo/Application.php b/apps/ainas-controlplane/lib/AppInfo/Application.php
new file mode 100644
index 0000000..6a6dbd2
--- /dev/null
+++ b/apps/ainas-controlplane/lib/AppInfo/Application.php
@@ -0,0 +1,25 @@
+controlPlaneClient->fetchSnapshot());
+ }
+}
+
diff --git a/apps/ainas-controlplane/lib/Controller/PageController.php b/apps/ainas-controlplane/lib/Controller/PageController.php
new file mode 100644
index 0000000..f8c3d92
--- /dev/null
+++ b/apps/ainas-controlplane/lib/Controller/PageController.php
@@ -0,0 +1,43 @@
+ 'aiNAS Control Plane',
+ 'controlPlaneUrl' => $this->controlPlaneConfig->getBaseUrl(),
+ 'snapshot' => $this->controlPlaneClient->fetchSnapshot(),
+ ],
+ );
+ }
+}
+
diff --git a/apps/ainas-controlplane/lib/Service/ControlPlaneClient.php b/apps/ainas-controlplane/lib/Service/ControlPlaneClient.php
new file mode 100644
index 0000000..da6e916
--- /dev/null
+++ b/apps/ainas-controlplane/lib/Service/ControlPlaneClient.php
@@ -0,0 +1,88 @@
+
+ */
+ public function fetchSnapshot(): array {
+ $baseUrl = $this->controlPlaneConfig->getBaseUrl();
+
+ try {
+ $healthResponse = $this->request($baseUrl . '/health');
+ $versionResponse = $this->request($baseUrl . '/version');
+
+ return [
+ 'available' => $healthResponse['statusCode'] === 200,
+ 'url' => $baseUrl,
+ 'health' => $healthResponse['body'],
+ 'version' => $versionResponse['body'],
+ ];
+ } catch (\Throwable $exception) {
+ $this->logger->warning('Failed to reach aiNAS control plane', [
+ 'exception' => $exception,
+ 'url' => $baseUrl,
+ ]);
+
+ return [
+ 'available' => false,
+ 'url' => $baseUrl,
+ 'error' => $exception->getMessage(),
+ ];
+ }
+ }
+
+ /**
+ * @return array{statusCode: int, body: array}
+ */
+ private function request(string $url): array {
+ $client = $this->clientService->newClient();
+ $response = $client->get($url, [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'timeout' => 2,
+ 'nextcloud' => [
+ 'allow_local_address' => true,
+ ],
+ ]);
+
+ return [
+ 'statusCode' => $response->getStatusCode(),
+ 'body' => $this->decodeBody($response),
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ private function decodeBody(IResponse $response): array {
+ $body = $response->getBody();
+ if ($body === '') {
+ return [];
+ }
+
+ $decoded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
+ if (!is_array($decoded)) {
+ return [];
+ }
+
+ return $decoded;
+ }
+}
+
diff --git a/apps/ainas-controlplane/lib/Service/ControlPlaneConfig.php b/apps/ainas-controlplane/lib/Service/ControlPlaneConfig.php
new file mode 100644
index 0000000..0b443d0
--- /dev/null
+++ b/apps/ainas-controlplane/lib/Service/ControlPlaneConfig.php
@@ -0,0 +1,31 @@
+appConfig->getValueString(
+ Application::APP_ID,
+ 'control_plane_url',
+ 'http://control-plane:3000',
+ );
+
+ return rtrim($configuredUrl, '/');
+ }
+}
+
diff --git a/apps/ainas-controlplane/lib/Settings/Admin.php b/apps/ainas-controlplane/lib/Settings/Admin.php
new file mode 100644
index 0000000..927bfb4
--- /dev/null
+++ b/apps/ainas-controlplane/lib/Settings/Admin.php
@@ -0,0 +1,39 @@
+ $this->controlPlaneConfig->getBaseUrl(),
+ 'snapshot' => $this->controlPlaneClient->fetchSnapshot(),
+ ],
+ );
+ }
+
+ public function getSection(): string {
+ return Application::APP_ID;
+ }
+
+ public function getPriority(): int {
+ return 50;
+ }
+}
+
diff --git a/apps/ainas-controlplane/lib/Settings/AdminSection.php b/apps/ainas-controlplane/lib/Settings/AdminSection.php
new file mode 100644
index 0000000..caab8b9
--- /dev/null
+++ b/apps/ainas-controlplane/lib/Settings/AdminSection.php
@@ -0,0 +1,35 @@
+l->t('aiNAS');
+ }
+
+ public function getPriority(): int {
+ return 50;
+ }
+
+ public function getIcon(): ?string {
+ return $this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg');
+ }
+}
+