diff --git a/Containers/mastercontainer/start.sh b/Containers/mastercontainer/start.sh index ad1734f1..a65e29ae 100644 --- a/Containers/mastercontainer/start.sh +++ b/Containers/mastercontainer/start.sh @@ -166,7 +166,7 @@ elif ! sudo -E -u www-data docker volume ls --format "{{.Name}}" | grep -q "^nex print_red "It seems like you did not give the mastercontainer volume the correct name? (The 'nextcloud_aio_mastercontainer' volume was not found.) Using a different name is not supported since the built-in backup solution will not work in that case!" exit 1 -elif ! sudo -E -u www-data docker inspect nextcloud-aio-mastercontainer | grep -q "nextcloud_aio_mastercontainer"; then +elif ! sudo -E -u www-data docker inspect nextcloud-aio-mastercontainer --format '{{.Mounts}}' | grep -q " nextcloud_aio_mastercontainer "; then print_red "It seems like you did not attach the 'nextcloud_aio_mastercontainer' volume to the mastercontainer? This is not supported since the built-in backup solution will not work in that case!" exit 1 diff --git a/php/composer.lock b/php/composer.lock index 75e53dfe..42bc1415 100644 --- a/php/composer.lock +++ b/php/composer.lock @@ -1644,16 +1644,16 @@ }, { "name": "twig/twig", - "version": "v3.22.2", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2" + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2", - "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", "shasum": "" }, "require": { @@ -1707,7 +1707,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.22.2" + "source": "https://github.com/twigphp/Twig/tree/v3.23.0" }, "funding": [ { @@ -1719,7 +1719,7 @@ "type": "tidelift" } ], - "time": "2025-12-14T11:28:47+00:00" + "time": "2026-01-23T21:00:41+00:00" } ], "packages-dev": [ @@ -3890,16 +3890,16 @@ }, { "name": "symfony/console", - "version": "v6.4.31", + "version": "v6.4.32", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f9f8a889f54c264f9abac3fc0f7a371ffca51997" + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f9f8a889f54c264f9abac3fc0f7a371ffca51997", - "reference": "f9f8a889f54c264f9abac3fc0f7a371ffca51997", + "url": "https://api.github.com/repos/symfony/console/zipball/0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", "shasum": "" }, "require": { @@ -3964,7 +3964,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.31" + "source": "https://github.com/symfony/console/tree/v6.4.32" }, "funding": [ { @@ -3984,7 +3984,7 @@ "type": "tidelift" } ], - "time": "2025-12-22T08:30:34+00:00" + "time": "2026-01-13T08:45:59+00:00" }, { "name": "symfony/filesystem", @@ -4058,16 +4058,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.31", + "version": "v6.4.32", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "5547f2e1f0ca8e2e7abe490156b62da778cfbe2b" + "reference": "3ec24885c1d9ababbb9c8f63bb42fea3c8c9b6de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5547f2e1f0ca8e2e7abe490156b62da778cfbe2b", - "reference": "5547f2e1f0ca8e2e7abe490156b62da778cfbe2b", + "url": "https://api.github.com/repos/symfony/finder/zipball/3ec24885c1d9ababbb9c8f63bb42fea3c8c9b6de", + "reference": "3ec24885c1d9ababbb9c8f63bb42fea3c8c9b6de", "shasum": "" }, "require": { @@ -4102,7 +4102,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.31" + "source": "https://github.com/symfony/finder/tree/v6.4.32" }, "funding": [ { @@ -4122,7 +4122,7 @@ "type": "tidelift" } ], - "time": "2025-12-11T14:52:17+00:00" + "time": "2026-01-10T14:09:00+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -4460,16 +4460,16 @@ }, { "name": "symfony/string", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { @@ -4527,7 +4527,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.0" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { @@ -4547,7 +4547,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-12T10:54:30+00:00" }, { "name": "vimeo/psalm", diff --git a/php/get-configurable-aio-variables.sh b/php/get-configurable-aio-variables.sh new file mode 100755 index 00000000..44536bd3 --- /dev/null +++ b/php/get-configurable-aio-variables.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +awk '/^ public [^f][^u][^n]/ { sub(/\$/, "", $3); print $3 }' php/src/Data/ConfigurationManager.php | sort diff --git a/php/psalm.xml b/php/psalm.xml index d7ce38c9..576d82d2 100644 --- a/php/psalm.xml +++ b/php/psalm.xml @@ -20,5 +20,10 @@ + + + + + diff --git a/php/public/index.php b/php/public/index.php index b57f65a5..1ec42949 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -91,10 +91,10 @@ $app->get('/containers', function (Request $request, Response $response, array $ $skip_domain_validation = isset($params['skip_domain_validation']); return $view->render($response, 'containers.twig', [ - 'domain' => $configurationManager->GetDomain(), - 'apache_port' => $configurationManager->GetApachePort(), - 'borg_backup_host_location' => $configurationManager->GetBorgBackupHostLocation(), - 'borg_remote_repo' => $configurationManager->GetBorgRemoteRepo(), + 'domain' => $configurationManager->domain, + 'apache_port' => $configurationManager->apachePort, + 'borg_backup_host_location' => $configurationManager->borgBackupHostLocation, + 'borg_remote_repo' => $configurationManager->borgRemoteRepo, 'borg_public_key' => $configurationManager->GetBorgPublicKey(), 'nextcloud_password' => $configurationManager->GetAndGenerateSecret('NEXTCLOUD_PASSWORD'), 'containers' => (new \AIO\ContainerDefinitionFetcher($container->get(\AIO\Data\ConfigurationManager::class), $container))->FetchDefinition(), @@ -103,42 +103,42 @@ $app->get('/containers', function (Request $request, Response $response, array $ 'has_backup_run_once' => $configurationManager->hasBackupRunOnce(), 'is_backup_container_running' => $dockerActionManager->isBackupContainerRunning(), 'backup_exit_code' => $dockerActionManager->GetBackupcontainerExitCode(), - 'is_instance_restore_attempt' => $configurationManager->isInstanceRestoreAttempt(), - 'borg_backup_mode' => $configurationManager->GetBackupMode(), - 'was_start_button_clicked' => $configurationManager->wasStartButtonClicked(), + 'is_instance_restore_attempt' => $configurationManager->instanceRestoreAttempt, + 'borg_backup_mode' => $configurationManager->backupMode, + 'was_start_button_clicked' => $configurationManager->wasStartButtonClicked, 'has_update_available' => $dockerActionManager->isAnyUpdateAvailable(), 'last_backup_time' => $configurationManager->GetLastBackupTime(), 'backup_times' => $configurationManager->GetBackupTimes(), 'current_channel' => $dockerActionManager->GetCurrentChannel(), - 'is_clamav_enabled' => $configurationManager->isClamavEnabled(), - 'is_onlyoffice_enabled' => $configurationManager->isOnlyofficeEnabled(), - 'is_collabora_enabled' => $configurationManager->isCollaboraEnabled(), - 'is_talk_enabled' => $configurationManager->isTalkEnabled(), - 'borg_restore_password' => $configurationManager->GetBorgRestorePassword(), + 'is_clamav_enabled' => $configurationManager->isClamavEnabled, + 'is_onlyoffice_enabled' => $configurationManager->isOnlyofficeEnabled, + 'is_collabora_enabled' => $configurationManager->isCollaboraEnabled, + 'is_talk_enabled' => $configurationManager->isTalkEnabled, + 'borg_restore_password' => $configurationManager->borgRestorePassword, 'daily_backup_time' => $configurationManager->GetDailyBackupTime(), 'is_daily_backup_running' => $configurationManager->isDailyBackupRunning(), - 'timezone' => $configurationManager->GetTimezone(), + 'timezone' => $configurationManager->timezone, 'skip_domain_validation' => $configurationManager->shouldDomainValidationBeSkipped($skip_domain_validation), - 'talk_port' => $configurationManager->GetTalkPort(), - 'collabora_dictionaries' => $configurationManager->GetCollaboraDictionaries(), - 'collabora_additional_options' => $configurationManager->GetAdditionalCollaboraOptions(), + 'talk_port' => $configurationManager->talkPort, + 'collabora_dictionaries' => $configurationManager->collaboraDictionaries, + 'collabora_additional_options' => $configurationManager->collaboraAdditionalOptions, 'automatic_updates' => $configurationManager->areAutomaticUpdatesEnabled(), - 'is_backup_section_enabled' => $configurationManager->isBackupSectionEnabled(), - 'is_imaginary_enabled' => $configurationManager->isImaginaryEnabled(), - 'is_fulltextsearch_enabled' => $configurationManager->isFulltextsearchEnabled(), + 'is_backup_section_enabled' => !$configurationManager->disableBackupSection, + 'is_imaginary_enabled' => $configurationManager->isImaginaryEnabled, + 'is_fulltextsearch_enabled' => $configurationManager->isFulltextsearchEnabled, 'additional_backup_directories' => $configurationManager->GetAdditionalBackupDirectoriesString(), - 'nextcloud_datadir' => $configurationManager->GetNextcloudDatadirMount(), - 'nextcloud_mount' => $configurationManager->GetNextcloudMount(), - 'nextcloud_upload_limit' => $configurationManager->GetNextcloudUploadLimit(), - 'nextcloud_max_time' => $configurationManager->GetNextcloudMaxTime(), - 'nextcloud_memory_limit' => $configurationManager->GetNextcloudMemoryLimit(), - 'is_dri_device_enabled' => $configurationManager->isDriDeviceEnabled(), - 'is_nvidia_gpu_enabled' => $configurationManager->isNvidiaGpuEnabled(), - 'is_talk_recording_enabled' => $configurationManager->isTalkRecordingEnabled(), - 'is_docker_socket_proxy_enabled' => $configurationManager->isDockerSocketProxyEnabled(), - 'is_whiteboard_enabled' => $configurationManager->isWhiteboardEnabled(), + 'nextcloud_datadir' => $configurationManager->nextcloudDatadirMount, + 'nextcloud_mount' => $configurationManager->nextcloudMount, + 'nextcloud_upload_limit' => $configurationManager->nextcloudUploadLimit, + 'nextcloud_max_time' => $configurationManager->nextcloudMaxTime, + 'nextcloud_memory_limit' => $configurationManager->nextcloudMemoryLimit, + 'is_dri_device_enabled' => $configurationManager->nextcloudEnableDriDevice, + 'is_nvidia_gpu_enabled' => $configurationManager->enableNvidiaGpu, + 'is_talk_recording_enabled' => $configurationManager->isTalkRecordingEnabled, + 'is_docker_socket_proxy_enabled' => $configurationManager->isDockerSocketProxyEnabled, + 'is_whiteboard_enabled' => $configurationManager->isWhiteboardEnabled, 'community_containers' => $configurationManager->listAvailableCommunityContainers(), - 'community_containers_enabled' => $configurationManager->GetEnabledCommunityContainers(), + 'community_containers_enabled' => $configurationManager->aioCommunityContainers, 'bypass_container_update' => $bypass_container_update, ]); })->setName('profile'); diff --git a/php/src/Auth/AuthManager.php b/php/src/Auth/AuthManager.php index 925ff89f..f6ab0d10 100644 --- a/php/src/Auth/AuthManager.php +++ b/php/src/Auth/AuthManager.php @@ -15,11 +15,11 @@ readonly class AuthManager { } public function CheckCredentials(string $password) : bool { - return hash_equals($this->configurationManager->GetPassword(), $password); + return hash_equals($this->configurationManager->password, $password); } public function CheckToken(string $token) : bool { - return hash_equals($this->configurationManager->GetToken(), $token); + return hash_equals($this->configurationManager->aioToken, $token); } public function SetAuthState(bool $isLoggedIn) : void { diff --git a/php/src/ContainerDefinitionFetcher.php b/php/src/ContainerDefinitionFetcher.php index d7498047..831a7bc5 100644 --- a/php/src/ContainerDefinitionFetcher.php +++ b/php/src/ContainerDefinitionFetcher.php @@ -41,7 +41,7 @@ readonly class ContainerDefinitionFetcher { $data = json_decode((string)file_get_contents(DataConst::GetContainersDefinitionPath()), true, 512, JSON_THROW_ON_ERROR); $additionalContainerNames = []; - foreach ($this->configurationManager->GetEnabledCommunityContainers() as $communityContainer) { + foreach ($this->configurationManager->aioCommunityContainers as $communityContainer) { if ($communityContainer !== '') { $path = DataConst::GetCommunityContainersDirectory() . '/' . $communityContainer . '/' . $communityContainer . '.json'; $additionalData = json_decode((string)file_get_contents($path), true, 512, JSON_THROW_ON_ERROR); @@ -56,42 +56,42 @@ readonly class ContainerDefinitionFetcher { $containers = []; foreach ($data['aio_services_v1'] as $entry) { if ($entry['container_name'] === 'nextcloud-aio-clamav') { - if (!$this->configurationManager->isClamavEnabled()) { + if (!$this->configurationManager->isClamavEnabled) { continue; } } elseif ($entry['container_name'] === 'nextcloud-aio-onlyoffice') { - if (!$this->configurationManager->isOnlyofficeEnabled()) { + if (!$this->configurationManager->isOnlyofficeEnabled) { continue; } } elseif ($entry['container_name'] === 'nextcloud-aio-collabora') { - if (!$this->configurationManager->isCollaboraEnabled()) { + if (!$this->configurationManager->isCollaboraEnabled) { continue; } if ($this->configurationManager->isCollaboraSubscriptionEnabled()) { $entry['image'] = 'ghcr.io/nextcloud-releases/aio-collabora-online'; } } elseif ($entry['container_name'] === 'nextcloud-aio-talk') { - if (!$this->configurationManager->isTalkEnabled()) { + if (!$this->configurationManager->isTalkEnabled) { continue; } } elseif ($entry['container_name'] === 'nextcloud-aio-talk-recording') { - if (!$this->configurationManager->isTalkRecordingEnabled()) { + if (!$this->configurationManager->isTalkRecordingEnabled) { continue; } } elseif ($entry['container_name'] === 'nextcloud-aio-imaginary') { - if (!$this->configurationManager->isImaginaryEnabled()) { + if (!$this->configurationManager->isImaginaryEnabled) { continue; } } elseif ($entry['container_name'] === 'nextcloud-aio-fulltextsearch') { - if (!$this->configurationManager->isFulltextsearchEnabled()) { + if (!$this->configurationManager->isFulltextsearchEnabled) { continue; } } elseif ($entry['container_name'] === 'nextcloud-aio-docker-socket-proxy') { - if (!$this->configurationManager->isDockerSocketProxyEnabled()) { + if (!$this->configurationManager->isDockerSocketProxyEnabled) { continue; } } elseif ($entry['container_name'] === 'nextcloud-aio-whiteboard') { - if (!$this->configurationManager->isWhiteboardEnabled()) { + if (!$this->configurationManager->isWhiteboardEnabled) { continue; } } @@ -113,34 +113,34 @@ readonly class ContainerDefinitionFetcher { if (isset($entry['volumes'])) { foreach ($entry['volumes'] as $value) { if($value['source'] === '%BORGBACKUP_HOST_LOCATION%') { - $value['source'] = $this->configurationManager->GetBorgBackupHostLocation(); + $value['source'] = $this->configurationManager->borgBackupHostLocation; if($value['source'] === '') { continue; } } if($value['source'] === '%NEXTCLOUD_MOUNT%') { - $value['source'] = $this->configurationManager->GetNextcloudMount(); + $value['source'] = $this->configurationManager->nextcloudMount; if($value['source'] === '') { continue; } } elseif ($value['source'] === '%NEXTCLOUD_DATADIR%') { - $value['source'] = $this->configurationManager->GetNextcloudDatadirMount(); + $value['source'] = $this->configurationManager->nextcloudDatadirMount; if ($value['source'] === '') { continue; } } elseif ($value['source'] === '%WATCHTOWER_DOCKER_SOCKET_PATH%') { - $value['source'] = $this->configurationManager->GetDockerSocketPath(); + $value['source'] = $this->configurationManager->dockerSocketPath; if($value['source'] === '') { continue; } } elseif ($value['source'] === '%NEXTCLOUD_TRUSTED_CACERTS_DIR%') { - $value['source'] = $this->configurationManager->GetTrustedCacertsDir(); + $value['source'] = $this->configurationManager->trustedCacertsDir; if($value['source'] === '') { continue; } } if ($value['destination'] === '%NEXTCLOUD_MOUNT%') { - $value['destination'] = $this->configurationManager->GetNextcloudMount(); + $value['destination'] = $this->configurationManager->nextcloudMount; if($value['destination'] === '') { continue; } @@ -168,39 +168,39 @@ readonly class ContainerDefinitionFetcher { } foreach ($valueDependsOn as $value) { if ($value === 'nextcloud-aio-clamav') { - if (!$this->configurationManager->isClamavEnabled()) { + if (!$this->configurationManager->isClamavEnabled) { continue; } } elseif ($value === 'nextcloud-aio-onlyoffice') { - if (!$this->configurationManager->isOnlyofficeEnabled()) { + if (!$this->configurationManager->isOnlyofficeEnabled) { continue; } } elseif ($value === 'nextcloud-aio-collabora') { - if (!$this->configurationManager->isCollaboraEnabled()) { + if (!$this->configurationManager->isCollaboraEnabled) { continue; } } elseif ($value === 'nextcloud-aio-talk') { - if (!$this->configurationManager->isTalkEnabled()) { + if (!$this->configurationManager->isTalkEnabled) { continue; } } elseif ($value === 'nextcloud-aio-talk-recording') { - if (!$this->configurationManager->isTalkRecordingEnabled()) { + if (!$this->configurationManager->isTalkRecordingEnabled) { continue; } } elseif ($value === 'nextcloud-aio-imaginary') { - if (!$this->configurationManager->isImaginaryEnabled()) { + if (!$this->configurationManager->isImaginaryEnabled) { continue; } } elseif ($value === 'nextcloud-aio-fulltextsearch') { - if (!$this->configurationManager->isFulltextsearchEnabled()) { + if (!$this->configurationManager->isFulltextsearchEnabled) { continue; } } elseif ($value === 'nextcloud-aio-docker-socket-proxy') { - if (!$this->configurationManager->isDockerSocketProxyEnabled()) { + if (!$this->configurationManager->isDockerSocketProxyEnabled) { continue; } } elseif ($value === 'nextcloud-aio-whiteboard') { - if (!$this->configurationManager->isWhiteboardEnabled()) { + if (!$this->configurationManager->isWhiteboardEnabled) { continue; } } diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php index 45586f9c..d73fd656 100644 --- a/php/src/Controller/ConfigurationController.php +++ b/php/src/Controller/ConfigurationController.php @@ -67,63 +67,27 @@ readonly class ConfigurationController { } if (isset($request->getParsedBody()['delete_timezone'])) { - $this->configurationManager->DeleteTimezone(); + $this->configurationManager->deleteTimezone(); } if (isset($request->getParsedBody()['timezone'])) { $timezone = $request->getParsedBody()['timezone'] ?? ''; - $this->configurationManager->SetTimezone($timezone); + $this->configurationManager->timezone = $timezone; } if (isset($request->getParsedBody()['options-form'])) { if (isset($request->getParsedBody()['collabora']) && isset($request->getParsedBody()['onlyoffice'])) { throw new InvalidSettingConfigurationException("Collabora and Onlyoffice are not allowed to be enabled at the same time!"); } - if (isset($request->getParsedBody()['clamav'])) { - $this->configurationManager->SetClamavEnabledState(1); - } else { - $this->configurationManager->SetClamavEnabledState(0); - } - if (isset($request->getParsedBody()['onlyoffice'])) { - $this->configurationManager->SetOnlyofficeEnabledState(1); - } else { - $this->configurationManager->SetOnlyofficeEnabledState(0); - } - if (isset($request->getParsedBody()['collabora'])) { - $this->configurationManager->SetCollaboraEnabledState(1); - } else { - $this->configurationManager->SetCollaboraEnabledState(0); - } - if (isset($request->getParsedBody()['talk'])) { - $this->configurationManager->SetTalkEnabledState(1); - } else { - $this->configurationManager->SetTalkEnabledState(0); - } - if (isset($request->getParsedBody()['talk-recording'])) { - $this->configurationManager->SetTalkRecordingEnabledState(1); - } else { - $this->configurationManager->SetTalkRecordingEnabledState(0); - } - if (isset($request->getParsedBody()['imaginary'])) { - $this->configurationManager->SetImaginaryEnabledState(1); - } else { - $this->configurationManager->SetImaginaryEnabledState(0); - } - if (isset($request->getParsedBody()['fulltextsearch'])) { - $this->configurationManager->SetFulltextsearchEnabledState(1); - } else { - $this->configurationManager->SetFulltextsearchEnabledState(0); - } - if (isset($request->getParsedBody()['docker-socket-proxy'])) { - $this->configurationManager->SetDockerSocketProxyEnabledState(1); - } else { - $this->configurationManager->SetDockerSocketProxyEnabledState(0); - } - if (isset($request->getParsedBody()['whiteboard'])) { - $this->configurationManager->SetWhiteboardEnabledState(1); - } else { - $this->configurationManager->SetWhiteboardEnabledState(0); - } + $this->configurationManager->isClamavEnabled = isset($request->getParsedBody()['clamav']); + $this->configurationManager->isOnlyofficeEnabled = isset($request->getParsedBody()['onlyoffice']); + $this->configurationManager->isCollaboraEnabled = isset($request->getParsedBody()['collabora']); + $this->configurationManager->isTalkEnabled = isset($request->getParsedBody()['talk']); + $this->configurationManager->isTalkRecordingEnabled = isset($request->getParsedBody()['talk-recording']); + $this->configurationManager->isImaginaryEnabled = isset($request->getParsedBody()['imaginary']); + $this->configurationManager->isFulltextsearchEnabled = isset($request->getParsedBody()['fulltextsearch']); + $this->configurationManager->isDockerSocketProxyEnabled = isset($request->getParsedBody()['docker-socket-proxy']); + $this->configurationManager->isWhiteboardEnabled = isset($request->getParsedBody()['whiteboard']); } if (isset($request->getParsedBody()['community-form'])) { @@ -137,7 +101,7 @@ readonly class ConfigurationController { $enabledCC[] = $item; } } - $this->configurationManager->SetEnabledCommunityContainers($enabledCC); + $this->configurationManager->aioCommunityContainers = $enabledCC; } if (isset($request->getParsedBody()['delete_collabora_dictionaries'])) { @@ -146,16 +110,16 @@ readonly class ConfigurationController { if (isset($request->getParsedBody()['collabora_dictionaries'])) { $collaboraDictionaries = $request->getParsedBody()['collabora_dictionaries'] ?? ''; - $this->configurationManager->SetCollaboraDictionaries($collaboraDictionaries); + $this->configurationManager->collaboraDictionaries = $collaboraDictionaries; } if (isset($request->getParsedBody()['delete_collabora_additional_options'])) { - $this->configurationManager->DeleteAdditionalCollaboraOptions(); + $this->configurationManager->deleteAdditionalCollaboraOptions(); } if (isset($request->getParsedBody()['collabora_additional_options'])) { $additionalCollaboraOptions = $request->getParsedBody()['collabora_additional_options'] ?? ''; - $this->configurationManager->SetAdditionalCollaboraOptions($additionalCollaboraOptions); + $this->configurationManager->collaboraAdditionalOptions = $additionalCollaboraOptions; } if (isset($request->getParsedBody()['delete_borg_backup_location_vars'])) { diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index a924e61f..81b920d0 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -89,7 +89,7 @@ readonly class DockerController { } public function startBackup(bool $forceStopNextcloud = false) : void { - $this->configurationManager->SetBackupMode('backup'); + $this->configurationManager->backupMode = 'backup'; $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); @@ -109,29 +109,25 @@ readonly class DockerController { } public function checkBackup() : void { - $this->configurationManager->SetBackupMode('check'); + $this->configurationManager->backupMode = 'check'; $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); } private function listBackup() : void { - $this->configurationManager->SetBackupMode('list'); + $this->configurationManager->backupMode = 'list'; $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); } public function StartBackupContainerRestore(Request $request, Response $response, array $args) : Response { - $this->configurationManager->SetBackupMode('restore'); - $config = $this->configurationManager->GetConfig(); - $config['selected-restore-time'] = $request->getParsedBody()['selected_restore_time'] ?? ''; - if (isset($request->getParsedBody()['restore-exclude-previews'])) { - $config['restore-exclude-previews'] = 1; - } else { - $config['restore-exclude-previews'] = ''; - } - $this->configurationManager->WriteConfig($config); + $this->configurationManager->startTransaction(); + $this->configurationManager->backupMode = 'restore'; + $this->configurationManager->selectedRestoreTime = $request->getParsedBody()['selected_restore_time'] ?? ''; + $this->configurationManager->restoreExcludePreviews = isset($request->getParsedBody()['restore-exclude-previews']); + $this->configurationManager->commitTransaction(); $id = self::TOP_CONTAINER; $forceStopNextcloud = true; @@ -144,22 +140,22 @@ readonly class DockerController { } public function StartBackupContainerCheckRepair(Request $request, Response $response, array $args) : Response { - $this->configurationManager->SetBackupMode('check-repair'); + $this->configurationManager->backupMode = 'check-repair'; $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); // Restore to backup check which is needed to make the UI logic work correctly - $this->configurationManager->SetBackupMode('check'); + $this->configurationManager->backupMode = 'check'; return $response->withStatus(201)->withHeader('Location', '.'); } public function StartBackupContainerTest(Request $request, Response $response, array $args) : Response { - $this->configurationManager->SetBackupMode('test'); - $config = $this->configurationManager->GetConfig(); - $config['instance_restore_attempt'] = 0; - $this->configurationManager->WriteConfig($config); + $this->configurationManager->startTransaction(); + $this->configurationManager->backupMode = 'test'; + $this->configurationManager->instanceRestoreAttempt = false; + $this->configurationManager->commitTransaction(); $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id); @@ -182,20 +178,19 @@ readonly class DockerController { } if (isset($request->getParsedBody()['install_latest_major'])) { - $installLatestMajor = 32; + $installLatestMajor = '32'; } else { - $installLatestMajor = ""; + $installLatestMajor = ''; } - - $config = $this->configurationManager->GetConfig(); + + $this->configurationManager->startTransaction(); + $this->configurationManager->installLatestMajor = $installLatestMajor; // set AIO_URL - $config['AIO_URL'] = $host . ':' . (string)$port . $path; + $this->configurationManager->aioUrl = $host . ':' . (string)$port . $path; // set wasStartButtonClicked - $config['wasStartButtonClicked'] = 1; - // set install_latest_major - $config['install_latest_major'] = $installLatestMajor; - $this->configurationManager->WriteConfig($config); - + $this->configurationManager->wasStartButtonClicked = true; + $this->configurationManager->commitTransaction(); + // Do not pull container images in case 'bypass_container_update' is set via url params // Needed for local testing $pullImage = !isset($request->getParsedBody()['bypass_container_update']); @@ -213,10 +208,7 @@ readonly class DockerController { } public function startTopContainer(bool $pullImage) : void { - $config = $this->configurationManager->GetConfig(); - // set AIO_TOKEN - $config['AIO_TOKEN'] = bin2hex(random_bytes(24)); - $this->configurationManager->WriteConfig($config); + $this->configurationManager->aioToken = bin2hex(random_bytes(24)); // Stop domaincheck since apache would not be able to start otherwise $this->StopDomaincheckContainer(); @@ -244,7 +236,7 @@ readonly class DockerController { // This is a hack but no better solution was found for the meantime // Stop Collabora first to make sure it force-saves // See https://github.com/nextcloud/richdocuments/issues/3799 - if ($id === self::TOP_CONTAINER && $this->configurationManager->isCollaboraEnabled()) { + if ($id === self::TOP_CONTAINER && $this->configurationManager->isCollaboraEnabled) { $this->PerformRecursiveContainerStop('nextcloud-aio-collabora'); } @@ -277,7 +269,7 @@ readonly class DockerController { public function StartDomaincheckContainer() : void { # Don't start if domain is already set - if ($this->configurationManager->GetDomain() !== '' || $this->configurationManager->wasStartButtonClicked()) { + if ($this->configurationManager->domain !== '' || $this->configurationManager->wasStartButtonClicked) { return; } diff --git a/php/src/Data/ConfigurationManager.php b/php/src/Data/ConfigurationManager.php index 320bc477..19b0a7bb 100644 --- a/php/src/Data/ConfigurationManager.php +++ b/php/src/Data/ConfigurationManager.php @@ -9,29 +9,327 @@ class ConfigurationManager { private array $secrets = []; - public function GetConfig() : array + private array $config = []; + + private bool $noWrite = false; + + public string $aioToken { + get => $this->get('AIO_TOKEN', ''); + set { $this->set('AIO_TOKEN', $value); } + } + + public string $password { + get => $this->get('password', ''); + set { $this->set('password', $value); } + } + + public bool $isDockerSocketProxyEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isDockerSocketProxyEnabled', false); + set { $this->set('isDockerSocketProxyEnabled', $value); } + } + + public bool $isWhiteboardEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isWhiteboardEnabled', true); + set { $this->set('isWhiteboardEnabled', $value); } + } + + public bool $restoreExcludePreviews { + // Type-cast because old configs could have '1'/'' for this key. + get => (bool) $this->get('restore-exclude-previews', false); + set { $this->set('restore-exclude-previews', $value); } + } + + public string $selectedRestoreTime { + get => $this->get('selected-restore-time', ''); + set { $this->set('selected-restore-time', $value); } + } + + public string $backupMode { + get => $this->get('backup-mode', ''); + set { $this->set('backup-mode', $value); } + } + + public bool $instanceRestoreAttempt { + // Type-cast because old configs could have 1/'' for this key. + get => (bool) $this->get('instance_restore_attempt', false); + set { $this->set('instance_restore_attempt', $value); } + } + + public string $aioUrl { + get => $this->get('AIO_URL', ''); + set { $this->set('AIO_URL', $value); } + } + + public bool $wasStartButtonClicked { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('wasStartButtonClicked', false); + set { $this->set('wasStartButtonClicked', $value); } + } + + public string $installLatestMajor { + // Type-cast because old configs could have integers for this key. + get => (string) $this->get('install_latest_major', ''); + set { $this->set('install_latest_major', $value); } + } + + public bool $isClamavEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isClamavEnabled', false); + set { $this->set('isClamavEnabled', $value); } + } + + public bool $isOnlyofficeEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isOnlyofficeEnabled', false); + set { $this->set('isOnlyofficeEnabled', $value); } + } + + public bool $isCollaboraEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isCollaboraEnabled', true); + set { $this->set('isCollaboraEnabled', $value); } + } + + public bool $isTalkEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isTalkEnabled', true); + set { $this->set('isTalkEnabled', $value); } + } + + public bool $isTalkRecordingEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->isTalkEnabled && $this->get('isTalkRecordingEnabled', false); + set { $this->set('isTalkRecordingEnabled', $this->isTalkEnabled && $value); } + } + + public bool $isImaginaryEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isImaginaryEnabled', true); + set { $this->set('isImaginaryEnabled', $value); } + } + + public bool $isFulltextsearchEnabled { + // Type-cast because old configs could have 1/0 for this key. + get => (bool) $this->get('isFulltextsearchEnabled', false); + // Elasticsearch does not work on kernels without seccomp anymore. See https://github.com/nextcloud/all-in-one/discussions/5768 + set { $this->set('isFulltextsearchEnabled', ($this->collaboraSeccompDisabled && $value)); } + } + + public string $domain { + get => $this->get('domain', ''); + set { $this->SetDomain($value); } + } + + public string $borgBackupHostLocation { + get => $this->get('borg_backup_host_location', ''); + set { $this->set('borg_backup_host_location', $value); } + } + + public string $borgRemoteRepo { + get => $this->get('borg_remote_repo', ''); + set { $this->set('borg_remote_repo', $value); } + } + + public string $borgRestorePassword { + get => $this->get('borg_restore_password', ''); + set { $this->set('borg_restore_password', $value); } + } + + public string $apacheIpBinding { + get => $this->GetEnvironmentalVariableOrConfig('APACHE_IP_BINDING', 'apache_ip_binding', ''); + set { $this->set('apache_ip_binding', $value); } + } + + /** + * @throws InvalidSettingConfigurationException + */ + public string $timezone { + get => $this->get('timezone', ''); + set { + // This throws an exception if the validation fails. + $this->validateTimezone($value); + $this->set('timezone', $value); + } + } + + /** + * @throws InvalidSettingConfigurationException + */ + public string $collaboraDictionaries { + get => $this->get('collabora_dictionaries', ''); + set { + // This throws an exception if the validation fails. + $this->validateCollaboraDictionaries($value); + $this->set('collabora_dictionaries', $value); + } + } + + /** + * @throws InvalidSettingConfigurationException + */ + public string $collaboraAdditionalOptions { + get => $this->get('collabora_additional_options', ''); + set { + // This throws an exception if the validation fails. + $this->validateCollaboraAdditionalOptions($value); + $this->set('collabora_additional_options', $value); + } + } + + public array $aioCommunityContainers { + get => explode(' ', $this->get('aio_community_containers', '')); + set { $this->set('aio_community_containers', implode(' ', $value)); } + } + + public string $turnDomain { + get => $this->get('turn_domain', ''); + set { $this->set('turn_domain', $value); } + } + + public string $apachePort { + get => $this->GetEnvironmentalVariableOrConfig('APACHE_PORT', 'apache_port', '443'); + set { $this->set('apache_port', $value); } + } + + public string $talkPort { + get => $this->GetEnvironmentalVariableOrConfig('TALK_PORT', 'talk_port', '3478'); + set { $this->set('talk_port', $value); } + } + + public string $nextcloudMount { + get => $this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_MOUNT', 'nextcloud_mount', ''); + set { $this->set('nextcloud_mount', $value); } + } + + public string $nextcloudDatadirMount { + get => $this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_DATADIR', 'nextcloud_datadir', 'nextcloud_aio_nextcloud_data'); + set { $this->set('nextcloud_datadir_mount', $value); } + } + + public string $nextcloudUploadLimit { + get => $this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_UPLOAD_LIMIT', 'nextcloud_upload_limit', '16G'); + set { $this->set('nextcloud_upload_limit', $value); } + } + + public string $nextcloudMemoryLimit { + get => $this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_MEMORY_LIMIT', 'nextcloud_memory_limit', '512M'); + set { $this->set('nextcloud_memory_limit', $value); } + } + + public function GetApacheMaxSize() : int { + $uploadLimit = (int)rtrim($this->nextcloudUploadLimit, 'G'); + return $uploadLimit * 1024 * 1024 * 1024; + } + + public string $nextcloudMaxTime { + get => $this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_MAX_TIME', 'nextcloud_max_time', '3600'); + set { $this->set('nextcloud_max_time', $value); } + } + + public string $borgRetentionPolicy { + get => $this->GetEnvironmentalVariableOrConfig('BORG_RETENTION_POLICY', 'borg_retention_policy', '--keep-within=7d --keep-weekly=4 --keep-monthly=6'); + set { $this->set('borg_retention_policy', $value); } + } + + public string $fulltextsearchJavaOptions { + get => $this->GetEnvironmentalVariableOrConfig('FULLTEXTSEARCH_JAVA_OPTIONS', 'fulltextsearch_java_options', '-Xms512M -Xmx512M'); + set { $this->set('fulltextsearch_java_options', $value); } + } + + public string $dockerSocketPath { + get => $this->GetEnvironmentalVariableOrConfig('WATCHTOWER_DOCKER_SOCKET_PATH', 'docker_socket_path', '/var/run/docker.sock'); + set { $this->set('docker_socket_path', $value); } + } + + public string $trustedCacertsDir { + get => $this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_TRUSTED_CACERTS_DIR', 'trusted_cacerts_dir', ''); + set { $this->set('trusted_cacerts_dir', $value); } + } + + public string $nextcloudAdditionalApks { + get => trim($this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_ADDITIONAL_APKS', 'nextcloud_additional_apks', 'imagemagick')); + set { $this->set('nextcloud_addtional_apks', $value); } + } + + public string $nextcloudAdditionalPhpExtensions { + get => trim($this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS', 'nextcloud_additional_php_extensions', 'imagick')); + set { $this->set('nextcloud_additional_php_extensions', $value); } + } + + public bool $collaboraSeccompDisabled { + get => booleanize($this->GetEnvironmentalVariableOrConfig('COLLABORA_SECCOMP_DISABLED', 'collabora_seccomp_disabled', '')); + set { $this->set('collabora_seccomp_disabled', $value); } + } + + public string $apacheAdditionalNetwork { + get => $this->GetEnvironmentalVariableOrConfig('APACHE_ADDITIONAL_NETWORK', 'apache_additional_network', ''); + set { $this->set('apache_additional_network', $value); } + } + + public bool $disableBackupSection { + get => booleanize($this->GetEnvironmentalVariableOrConfig('AIO_DISABLE_BACKUP_SECTION', 'disable_backup_section', '')); + set { $this->set('disable_backup_section', $value); } + } + + public bool $nextcloudEnableDriDevice{ + get => booleanize($this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_ENABLE_DRI_DEVICE', 'nextcloud_enable_dri_device', '')); + set { $this->set('nextcloud_enable_dri_device', $value); } + } + + public bool $enableNvidiaGpu { + get => booleanize($this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_ENABLE_NVIDIA_GPU', 'enable_nvidia_gpu', '')); + set { $this->set('enable_nvidia_gpu', $value); } + } + + public bool $nextcloudKeepDisabledApps { + get => booleanize($this->GetEnvironmentalVariableOrConfig('NEXTCLOUD_KEEP_DISABLED_APPS', 'nextcloud_keep_disabled_apps', '')); + set { $this->set('nextcloud_keep_disabled_apps', $value); } + } + + private function GetConfig() : array { - if(file_exists(DataConst::GetConfigFile())) + if ($this->config === [] && file_exists(DataConst::GetConfigFile())) { $configContent = (string)file_get_contents(DataConst::GetConfigFile()); - return json_decode($configContent, true, 512, JSON_THROW_ON_ERROR); + $this->config = json_decode($configContent, true, 512, JSON_THROW_ON_ERROR); } - return []; + return $this->config; } - public function GetPassword() : string { - return $this->GetConfig()['password']; + private function get(string $key, mixed $fallbackValue = null) : mixed { + return $this->GetConfig()[$key] ?? $fallbackValue; } - public function GetToken() : string { - return $this->GetConfig()['AIO_TOKEN']; + private function set(string $key, mixed $value) : void { + $this->GetConfig(); + $this->config[$key] = $value; + // Only write if this isn't called in between startTransaction() and commitTransaction(). + if ($this->noWrite !== true) { + $this->WriteConfig(); + } } - public function SetPassword(string $password) : void { - $config = $this->GetConfig(); - $config['password'] = $password; - $this->WriteConfig($config); + /** + * This allows to assign multiple attributes without saving the config to disk in between. It must be + * followed by a call to commitTransaction(), which then writes all changes to disk. + */ + public function startTransaction() : void { + $this->GetConfig(); + $this->noWrite = true; + } + + /** + * This allows to assign multiple attributes without saving the config to disk in between. + */ + public function commitTransaction() : void { + try { + $this->WriteConfig(); + } finally { + $this->noWrite = false; + } } public function GetAndGenerateSecret(string $secretId) : string { @@ -39,17 +337,17 @@ class ConfigurationManager return ''; } - $config = $this->GetConfig(); - if(!isset($config['secrets'][$secretId])) { - $config['secrets'][$secretId] = bin2hex(random_bytes(24)); - $this->WriteConfig($config); + $secrets = $this->get('secrets', []); + if (!isset($secrets[$secretId])) { + $secrets[$secretId] = bin2hex(random_bytes(24)); + $this->set('secrets', $secrets); } if ($secretId === 'BORGBACKUP_PASSWORD' && !file_exists(DataConst::GetBackupSecretFile())) { - $this->DoubleSafeBackupSecret($config['secrets'][$secretId]); + $this->DoubleSafeBackupSecret($secrets[$secretId]); } - return $config['secrets'][$secretId]; + return $secrets[$secretId]; } public function GetRegisteredSecret(string $secretId) : string { @@ -122,14 +420,6 @@ class ConfigurationManager return $backupTimes; } - public function wasStartButtonClicked() : bool { - if (isset($this->GetConfig()['wasStartButtonClicked'])) { - return true; - } else { - return false; - } - } - private function isx64Platform() : bool { if (php_uname('m') === 'x86_64') { return true; @@ -138,155 +428,10 @@ class ConfigurationManager } } - public function isClamavEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isClamavEnabled']) && $config['isClamavEnabled'] === 1) { - return true; - } else { - return false; - } - } - - public function isDockerSocketProxyEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isDockerSocketProxyEnabled']) && $config['isDockerSocketProxyEnabled'] === 1) { - return true; - } else { - return false; - } - } - - public function SetDockerSocketProxyEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isDockerSocketProxyEnabled'] = $value; - $this->WriteConfig($config); - } - - public function isWhiteboardEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isWhiteboardEnabled']) && $config['isWhiteboardEnabled'] === 0) { - return false; - } else { - return true; - } - } - - public function SetWhiteboardEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isWhiteboardEnabled'] = $value; - $this->WriteConfig($config); - } - - public function SetClamavEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isClamavEnabled'] = $value; - $this->WriteConfig($config); - } - - public function isImaginaryEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isImaginaryEnabled']) && $config['isImaginaryEnabled'] === 0) { - return false; - } else { - return true; - } - } - - public function SetImaginaryEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isImaginaryEnabled'] = $value; - $this->WriteConfig($config); - } - - public function isFulltextsearchEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isFulltextsearchEnabled']) && $config['isFulltextsearchEnabled'] === 1) { - return true; - } else { - return false; - } - } - - public function SetFulltextsearchEnabledState(int $value) : void { - // Elasticsearch does not work on kernels without seccomp anymore. See https://github.com/nextcloud/all-in-one/discussions/5768 - if ($this->isSeccompDisabled()) { - $value = 0; - } - - $config = $this->GetConfig(); - $config['isFulltextsearchEnabled'] = $value; - $this->WriteConfig($config); - } - - public function isOnlyofficeEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isOnlyofficeEnabled']) && $config['isOnlyofficeEnabled'] === 1) { - return true; - } else { - return false; - } - } - - public function SetOnlyofficeEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isOnlyofficeEnabled'] = $value; - $this->WriteConfig($config); - } - - public function isCollaboraEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isCollaboraEnabled']) && $config['isCollaboraEnabled'] === 0) { - return false; - } else { - return true; - } - } - - public function SetCollaboraEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isCollaboraEnabled'] = $value; - $this->WriteConfig($config); - } - - public function isTalkEnabled() : bool { - $config = $this->GetConfig(); - if (isset($config['isTalkEnabled']) && $config['isTalkEnabled'] === 0) { - return false; - } else { - return true; - } - } - - public function SetTalkEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isTalkEnabled'] = $value; - $this->WriteConfig($config); - } - - public function isTalkRecordingEnabled() : bool { - if (!$this->isTalkEnabled()) { - return false; - } - $config = $this->GetConfig(); - if (isset($config['isTalkRecordingEnabled']) && $config['isTalkRecordingEnabled'] === 1) { - return true; - } else { - return false; - } - } - - public function SetTalkRecordingEnabledState(int $value) : void { - if (!$this->isTalkEnabled()) { - $value = 0; - } - - $config = $this->GetConfig(); - $config['isTalkRecordingEnabled'] = $value; - $this->WriteConfig($config); - } - /** * @throws InvalidSettingConfigurationException + * + * We can't turn this into a private validation method because of the second argument. */ public function SetDomain(string $domain, bool $skipDomainValidation) : void { // Validate that at least one dot is contained @@ -336,7 +481,7 @@ class ConfigurationManager } // Get the apache port - $port = $this->GetApachePort(); + $port = $this->apachePort; if (!filter_var($dnsRecordIP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { if ($port === '443') { @@ -392,83 +537,33 @@ class ConfigurationManager } } + $this->startTransaction(); // Write domain - $config = $this->GetConfig(); - $config['domain'] = $domain; + // Don't set the domain via the attribute, or we create a loop. + $this->set('domain', $domain); // Reset the borg restore password when setting the domain - $config['borg_restore_password'] = ''; - $this->WriteConfig($config); - } - - public function GetDomain() : string { - $config = $this->GetConfig(); - if(!isset($config['domain'])) { - $config['domain'] = ''; - } - - return $config['domain']; + $this->borgRestorePassword = ''; + $this->startTransaction(); + $this->commitTransaction(); } public function GetBaseDN() : string { - $domain = $this->GetDomain(); + $domain = $this->domain; if ($domain === "") { return ""; } return 'dc=' . implode(',dc=', explode('.', $domain)); } - public function GetBackupMode() : string { - $config = $this->GetConfig(); - if(!isset($config['backup-mode'])) { - $config['backup-mode'] = ''; - } - - return $config['backup-mode']; - } - - public function SetBackupMode(string $mode) : void { - $config = $this->GetConfig(); - $config['backup-mode'] = $mode; - $this->WriteConfig($config); - } - - public function GetSelectedRestoreTime() : string { - $config = $this->GetConfig(); - if(!isset($config['selected-restore-time'])) { - $config['selected-restore-time'] = ''; - } - - return $config['selected-restore-time']; - } - - public function GetRestoreExcludePreviews() : string { - $config = $this->GetConfig(); - if(!isset($config['restore-exclude-previews'])) { - $config['restore-exclude-previews'] = ''; - } - - return $config['restore-exclude-previews']; - } - - public function GetAIOURL() : string { - $config = $this->GetConfig(); - if(!isset($config['AIO_URL'])) { - $config['AIO_URL'] = ''; - } - - return $config['AIO_URL']; - } - /** * @throws InvalidSettingConfigurationException */ public function SetBorgLocationVars(string $location, string $repo) : void { $this->ValidateBorgLocationVars($location, $repo); - - $config = $this->GetConfig(); - $config['borg_backup_host_location'] = $location; - $config['borg_remote_repo'] = $repo; - $this->WriteConfig($config); + $this->startTransaction(); + $this->borgBackupHostLocation = $location; + $this->borgRemoteRepo = $repo; + $this->commitTransaction(); } private function ValidateBorgLocationVars(string $location, string $repo) : void { @@ -492,8 +587,8 @@ class ConfigurationManager // Prevent backup to be contained in Nextcloud Datadir as this will delete the backup archive upon restore // See https://github.com/nextcloud/all-in-one/issues/6607 - if (str_starts_with($location . '/', rtrim($this->GetNextcloudDatadirMount(), '/') . '/')) { - throw new InvalidSettingConfigurationException("The path must not be a children of or equal to NEXTCLOUD_DATADIR, which is currently set to " . $this->GetNextcloudDatadirMount()); + if (str_starts_with($location . '/', rtrim($this->nextcloudDatadirMount, '/') . '/')) { + throw new InvalidSettingConfigurationException("The path must not be a children of or equal to NEXTCLOUD_DATADIR, which is currently set to " . $this->nextcloudDatadirMount); } } else { @@ -514,10 +609,10 @@ class ConfigurationManager public function DeleteBorgBackupLocationItems() : void { // Delete the variables - $config = $this->GetConfig(); - $config['borg_backup_host_location'] = ''; - $config['borg_remote_repo'] = ''; - $this->WriteConfig($config); + $this->startTransaction(); + $this->borgBackupHostLocation = ''; + $this->borgRemoteRepo = ''; + $this->commitTransaction(); // Also delete the borg config file to be able to start over if (file_exists(DataConst::GetBackupKeyFile())) { @@ -537,12 +632,12 @@ class ConfigurationManager throw new InvalidSettingConfigurationException("Please enter the password!"); } - $config = $this->GetConfig(); - $config['borg_backup_host_location'] = $location; - $config['borg_remote_repo'] = $repo; - $config['borg_restore_password'] = $password; - $config['instance_restore_attempt'] = 1; - $this->WriteConfig($config); + $this->startTransaction(); + $this->borgBackupHostLocation = $location; + $this->borgRemoteRepo = $repo; + $this->borgRestorePassword = $password; + $this->instanceRestoreAttempt = true; + $this->commitTransaction(); } /** @@ -553,7 +648,7 @@ class ConfigurationManager throw new InvalidSettingConfigurationException("Please enter your current password."); } - if ($currentPassword !== $this->GetPassword()) { + if ($currentPassword !== $this->password) { throw new InvalidSettingConfigurationException("The entered current password is not correct."); } @@ -570,88 +665,49 @@ class ConfigurationManager } // All checks pass so set the password - $this->SetPassword($newPassword); - } - - public function GetApachePort() : string { - $envVariableName = 'APACHE_PORT'; - $configName = 'apache_port'; - $defaultValue = '443'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetTalkPort() : string { - $envVariableName = 'TALK_PORT'; - $configName = 'talk_port'; - $defaultValue = '3478'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetTurnDomain() : string { - $config = $this->GetConfig(); - if(!isset($config['turn_domain'])) { - $config['turn_domain'] = ''; - } - - return $config['turn_domain']; + $this->set('password', $newPassword); } /** * @throws InvalidSettingConfigurationException */ - public function WriteConfig(array $config) : void { + private function WriteConfig() : void { if(!is_dir(DataConst::GetDataDirectory())) { throw new InvalidSettingConfigurationException(DataConst::GetDataDirectory() . " does not exist! Something was set up falsely!"); } + // Shouldn't happen, but as a precaution we won't write an empty config to disk. + if ($this->config === []) { + return; + } $df = disk_free_space(DataConst::GetDataDirectory()); - $content = json_encode($config, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR); + $content = json_encode($this->config, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR); $size = strlen($content) + 10240; if ($df !== false && (int)$df < $size) { throw new InvalidSettingConfigurationException(DataConst::GetDataDirectory() . " does not have enough space for writing the config file! Not writing it back!"); } file_put_contents(DataConst::GetConfigFile(), $content); + $this->config = []; } private function GetEnvironmentalVariableOrConfig(string $envVariableName, string $configName, string $defaultValue) : string { $envVariableOutput = getenv($envVariableName); + $configValue = $this->get($configName, ''); if ($envVariableOutput === false) { - $config = $this->GetConfig(); - if (!isset($config[$configName]) || $config[$configName] === '') { - $config[$configName] = $defaultValue; + if ($configValue === '') { + return $defaultValue; } - return $config[$configName]; + return $configValue; } - if(file_exists(DataConst::GetConfigFile())) { - $config = $this->GetConfig(); - if (!isset($config[$configName])) { - $config[$configName] = ''; - } - if ($envVariableOutput !== $config[$configName]) { - $config[$configName] = $envVariableOutput; - $this->WriteConfig($config); + + if (file_exists(DataConst::GetConfigFile())) { + if ($envVariableOutput !== $configValue) { + $this->set($configName, $envVariableOutput); } } + return $envVariableOutput; } - public function GetBorgBackupHostLocation() : string { - $config = $this->GetConfig(); - if(!isset($config['borg_backup_host_location'])) { - $config['borg_backup_host_location'] = ''; - } - - return $config['borg_backup_host_location']; - } - - public function GetBorgRemoteRepo() : string { - $config = $this->GetConfig(); - if(!isset($config['borg_remote_repo'])) { - $config['borg_remote_repo'] = ''; - } - - return $config['borg_remote_repo']; - } - public function GetBorgPublicKey() : string { if (!file_exists(DataConst::GetBackupPublicKey())) { return ""; @@ -660,131 +716,14 @@ class ConfigurationManager return trim((string)file_get_contents(DataConst::GetBackupPublicKey())); } - public function GetBorgRestorePassword() : string { - $config = $this->GetConfig(); - if(!isset($config['borg_restore_password'])) { - $config['borg_restore_password'] = ''; - } - - return $config['borg_restore_password']; - } - - public function isInstanceRestoreAttempt() : bool { - $config = $this->GetConfig(); - if(!isset($config['instance_restore_attempt'])) { - $config['instance_restore_attempt'] = ''; - } - - if ($config['instance_restore_attempt'] === 1) { - return true; - } - return false; - } - - public function GetNextcloudMount() : string { - $envVariableName = 'NEXTCLOUD_MOUNT'; - $configName = 'nextcloud_mount'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetNextcloudDatadirMount() : string { - $envVariableName = 'NEXTCLOUD_DATADIR'; - $configName = 'nextcloud_datadir'; - $defaultValue = 'nextcloud_aio_nextcloud_data'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetNextcloudUploadLimit() : string { - $envVariableName = 'NEXTCLOUD_UPLOAD_LIMIT'; - $configName = 'nextcloud_upload_limit'; - $defaultValue = '16G'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetNextcloudMemoryLimit() : string { - $envVariableName = 'NEXTCLOUD_MEMORY_LIMIT'; - $configName = 'nextcloud_memory_limit'; - $defaultValue = '512M'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetApacheMaxSize() : int { - $uploadLimit = (int)rtrim($this->GetNextcloudUploadLimit(), 'G'); - return $uploadLimit * 1024 * 1024 * 1024; - } - - public function GetNextcloudMaxTime() : string { - $envVariableName = 'NEXTCLOUD_MAX_TIME'; - $configName = 'nextcloud_max_time'; - $defaultValue = '3600'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetBorgRetentionPolicy() : string { - $envVariableName = 'BORG_RETENTION_POLICY'; - $configName = 'borg_retention_policy'; - $defaultValue = '--keep-within=7d --keep-weekly=4 --keep-monthly=6'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetFulltextsearchJavaOptions() : string { - $envVariableName = 'FULLTEXTSEARCH_JAVA_OPTIONS'; - $configName = 'fulltextsearch_java_options'; - $defaultValue = '-Xms512M -Xmx512M'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetDockerSocketPath() : string { - $envVariableName = 'WATCHTOWER_DOCKER_SOCKET_PATH'; - $configName = 'docker_socket_path'; - $defaultValue = '/var/run/docker.sock'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetTrustedCacertsDir() : string { - $envVariableName = 'NEXTCLOUD_TRUSTED_CACERTS_DIR'; - $configName = 'trusted_cacerts_dir'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetNextcloudAdditionalApks() : string { - $envVariableName = 'NEXTCLOUD_ADDITIONAL_APKS'; - $configName = 'nextcloud_additional_apks'; - $defaultValue = 'imagemagick'; - return trim($this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue)); - } - - public function GetNextcloudAdditionalPhpExtensions() : string { - $envVariableName = 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS'; - $configName = 'nextcloud_additional_php_extensions'; - $defaultValue = 'imagick'; - return trim($this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue)); - } - public function GetCollaboraSeccompPolicy() : string { $defaultString = '--o:security.seccomp='; - if (!$this->isSeccompDisabled()) { + if (!$this->collaboraSeccompDisabled) { return $defaultString . 'true'; } return $defaultString . 'false'; } - private function GetCollaboraSeccompDisabledState() : string { - $envVariableName = 'COLLABORA_SECCOMP_DISABLED'; - $configName = 'collabora_seccomp_disabled'; - $defaultValue = 'false'; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function isSeccompDisabled() : bool { - if ($this->GetCollaboraSeccompDisabledState() === 'true') { - return true; - } - return false; - } - /** * @throws InvalidSettingConfigurationException */ @@ -862,14 +801,6 @@ class ConfigurationManager } } - public function shouldLatestMajorGetInstalled() : bool { - $config = $this->GetConfig(); - if(!isset($config['install_latest_major'])) { - $config['install_latest_major'] = ''; - } - return $config['install_latest_major'] !== ''; - } - public function GetAdditionalBackupDirectoriesString() : string { if (!file_exists(DataConst::GetAdditionalBackupDirectoriesFile())) { return ''; @@ -885,25 +816,13 @@ class ConfigurationManager } public function isDailyBackupRunning() : bool { - if (file_exists(DataConst::GetDailyBackupBlockFile())) { - return true; - } - return false; - } - - public function GetTimezone() : string { - $config = $this->GetConfig(); - if(!isset($config['timezone'])) { - $config['timezone'] = ''; - } - - return $config['timezone']; + return file_exists(DataConst::GetDailyBackupBlockFile()); } /** * @throws InvalidSettingConfigurationException */ - public function SetTimezone(string $timezone) : void { + private function validateTimezone(string $timezone) : void { if ($timezone === "") { throw new InvalidSettingConfigurationException("The timezone must not be empty!"); } @@ -911,16 +830,13 @@ class ConfigurationManager if (!preg_match("#^[a-zA-Z0-9_\-\/\+]+$#", $timezone)) { throw new InvalidSettingConfigurationException("The entered timezone does not seem to be a valid timezone!"); } - - $config = $this->GetConfig(); - $config['timezone'] = $timezone; - $this->WriteConfig($config); } - public function DeleteTimezone() : void { - $config = $this->GetConfig(); - $config['timezone'] = ''; - $this->WriteConfig($config); + /** + * Provide an extra method since our `timezone` attribute setter prevents setting an empty timezone. + */ + public function deleteTimezone() : void { + $this->set('timezone', ''); } public function shouldDomainValidationBeSkipped(bool $skipDomainValidation) : bool { @@ -938,19 +854,10 @@ class ConfigurationManager return 'deck twofactor_totp tasks calendar contacts notes'; } - public function GetCollaboraDictionaries() : string { - $config = $this->GetConfig(); - if(!isset($config['collabora_dictionaries'])) { - $config['collabora_dictionaries'] = ''; - } - - return $config['collabora_dictionaries']; - } - /** * @throws InvalidSettingConfigurationException */ - public function SetCollaboraDictionaries(string $CollaboraDictionaries) : void { + private function validateCollaboraDictionaries(string $CollaboraDictionaries) : void { if ($CollaboraDictionaries === "") { throw new InvalidSettingConfigurationException("The dictionaries must not be empty!"); } @@ -958,22 +865,19 @@ class ConfigurationManager if (!preg_match("#^[a-zA-Z_ ]+$#", $CollaboraDictionaries)) { throw new InvalidSettingConfigurationException("The entered dictionaries do not seem to be a valid!"); } - - $config = $this->GetConfig(); - $config['collabora_dictionaries'] = $CollaboraDictionaries; - $this->WriteConfig($config); } + /** + * Provide an extra method since the corresponding attribute setter prevents setting an empty value. + */ public function DeleteCollaboraDictionaries() : void { - $config = $this->GetConfig(); - $config['collabora_dictionaries'] = ''; - $this->WriteConfig($config); + $this->set('collabora_dictionaries', ''); } /** * @throws InvalidSettingConfigurationException */ - public function SetAdditionalCollaboraOptions(string $additionalCollaboraOptions) : void { + private function validateCollaboraAdditionalOptions(string $additionalCollaboraOptions) : void { if ($additionalCollaboraOptions === "") { throw new InvalidSettingConfigurationException("The additional options must not be empty!"); } @@ -981,73 +885,19 @@ class ConfigurationManager if (!preg_match("#^--o:#", $additionalCollaboraOptions)) { throw new InvalidSettingConfigurationException("The entered options must start with '--o:'. So the config does not seem to be a valid!"); } - - $config = $this->GetConfig(); - $config['collabora_additional_options'] = $additionalCollaboraOptions; - $this->WriteConfig($config); - } - - public function GetAdditionalCollaboraOptions() : string { - $config = $this->GetConfig(); - if(!isset($config['collabora_additional_options'])) { - $config['collabora_additional_options'] = ''; - } - - return $config['collabora_additional_options']; } public function isCollaboraSubscriptionEnabled() : bool { - if (str_contains($this->GetAdditionalCollaboraOptions(), '--o:support_key=')) { - return true; - } - return false; + return str_contains($this->collaboraAdditionalOptions, '--o:support_key='); } - public function DeleteAdditionalCollaboraOptions() : void { - $config = $this->GetConfig(); - $config['collabora_additional_options'] = ''; - $this->WriteConfig($config); + /** + * Provide an extra method since the corresponding attribute setter prevents setting an empty value. + */ + public function deleteAdditionalCollaboraOptions() : void { + $this->set('collabora_additional_options', ''); } - public function GetApacheAdditionalNetwork() : string { - $envVariableName = 'APACHE_ADDITIONAL_NETWORK'; - $configName = 'apache_additional_network'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function GetApacheIPBinding() : string { - $envVariableName = 'APACHE_IP_BINDING'; - $configName = 'apache_ip_binding'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - private function GetDisableBackupSection() : string { - $envVariableName = 'AIO_DISABLE_BACKUP_SECTION'; - $configName = 'disable_backup_section'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function isBackupSectionEnabled() : bool { - if ($this->GetDisableBackupSection() === 'true') { - return false; - } else { - return true; - } - } - - private function GetCommunityContainers() : string { - $config = $this->GetConfig(); - if(!isset($config['aio_community_containers'])) { - $config['aio_community_containers'] = ''; - } - - return $config['aio_community_containers']; - } - - public function listAvailableCommunityContainers() : array { $cc = []; $dir = scandir(DataConst::GetCommunityContainersDirectory()); @@ -1083,55 +933,115 @@ class ConfigurationManager return $cc; } - /** @return list */ - public function GetEnabledCommunityContainers(): array { - return explode(' ', $this->GetCommunityContainers()); + private function camelize(string $input, string $delimiter = '_') : string { + return lcfirst(implode("", array_map('ucfirst', explode($delimiter, strtolower($input))))); + } - public function SetEnabledCommunityContainers(array $enabledCommunityContainers) : void { - $config = $this->GetConfig(); - $config['aio_community_containers'] = implode(' ', $enabledCommunityContainers); - $this->WriteConfig($config); - } - - private function GetEnabledDriDevice() : string { - $envVariableName = 'NEXTCLOUD_ENABLE_DRI_DEVICE'; - $configName = 'nextcloud_enable_dri_device'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function isDriDeviceEnabled() : bool { - if ($this->GetEnabledDriDevice() === 'true') { - return true; - } else { - return false; + public function setAioVariables(array $input) : void { + if ($input === []) { + return; } - } - - private function GetEnabledNvidiaGpu() : string { - $envVariableName = 'NEXTCLOUD_ENABLE_NVIDIA_GPU'; - $configName = 'enable_nvidia_gpu'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function isNvidiaGpuEnabled() : bool { - return $this->GetEnabledNvidiaGpu() === 'true'; - } - - private function GetKeepDisabledApps() : string { - $envVariableName = 'NEXTCLOUD_KEEP_DISABLED_APPS'; - $configName = 'nextcloud_keep_disabled_apps'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function shouldDisabledAppsGetRemoved() : bool { - if ($this->GetKeepDisabledApps() === 'true') { - return false; - } else { - return true; + $this->startTransaction(); + foreach ($input as $variable) { + if (!is_string($variable) || !str_contains($variable, '=')) { + error_log("Invalid input: '$variable' is not a string or does not contain an equal sign ('=')"); + continue; + } + $keyWithValue = $confManager->replaceEnvPlaceholders($variable); + // Pad the result with nulls so psalm is happy (and we don't risk to run into warnings in case + // the check for an equal sign from above gets changed). + [$key, $value] = explode('=', $keyWithValue, 2) + [null, null]; + $key = $this->camelize($key); + if ($value === null) { + error_log("Invalid input: '$keyWithValue' has no value after the equal sign"); + } else if (!property_exists($confManager, $key)) { + error_log("Error: '$key' is not a valid configuration key (in '$keyWithValue')"); + } else { + $confManager->$key = $value; + } } + $this->commitTransaction(); + } + + // + // Replaces placeholders in $envValue with their values. + // E.g. "%NC_DOMAIN%:%APACHE_PORT" becomes "my.nextcloud.com:11000" + public function replaceEnvPlaceholders(string $envValue): string { + // $pattern breaks down as: + // % - matches a literal percent sign + // ([^%]+) - capture group that matches one or more characters that are NOT percent signs + // % - matches the closing percent sign + // + // Assumes literal percent signs are always matched and there is no + // escaping. + $pattern = '/%([^%]+)%/'; + $matchCount = preg_match_all($pattern, $envValue, $matches); + + if ($matchCount === 0) { + return $envValue; + } + + $placeholders = $matches[0]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...] + $placeholderNames = $matches[1]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...] + $placeholderPatterns = array_map(static fn(string $p) => '/' . preg_quote($p) . '/', $placeholders); // ["/%PLACEHOLDER1%/", ...] + $placeholderValues = array_map($this->getPlaceholderValue(...), $placeholderNames); // ["val1", "val2"] + // Guaranteed to be non-null because we found the placeholders in the preg_match_all. + return (string) preg_replace($placeholderPatterns, $placeholderValues, $envValue); + } + + private function getPlaceholderValue(string $placeholder) : string { + return match ($placeholder) { + 'NC_DOMAIN' => $this->domain, + 'NC_BASE_DN' => $this->GetBaseDN(), + 'AIO_TOKEN' => $this->aioToken, + 'BORGBACKUP_REMOTE_REPO' => $this->borgRemoteRepo, + 'BORGBACKUP_MODE' => $this->backupMode, + 'AIO_URL' => $this->aioUrl, + 'SELECTED_RESTORE_TIME' => $this->selectedRestoreTime, + 'RESTORE_EXCLUDE_PREVIEWS' => $this->restoreExcludePreviews ? '1' : '', + 'APACHE_PORT' => $this->apachePort, + 'APACHE_IP_BINDING' => $this->apacheIpBinding, + 'TALK_PORT' => $this->talkPort, + 'TURN_DOMAIN' => $this->turnDomain, + 'NEXTCLOUD_MOUNT' => $this->nextcloudMount, + 'BACKUP_RESTORE_PASSWORD' => $this->borgRestorePassword, + 'CLAMAV_ENABLED' => $this->isClamavEnabled ? 'yes' : '', + 'TALK_RECORDING_ENABLED' => $this->isTalkRecordingEnabled ? 'yes' : '', + 'ONLYOFFICE_ENABLED' => $this->isOnlyofficeEnabled ? 'yes' : '', + 'COLLABORA_ENABLED' => $this->isCollaboraEnabled ? 'yes' : '', + 'TALK_ENABLED' => $this->isTalkEnabled ? 'yes' : '', + 'UPDATE_NEXTCLOUD_APPS' => ($this->isDailyBackupRunning() && $this->areAutomaticUpdatesEnabled()) ? 'yes' : '', + 'TIMEZONE' => $this->timezone === '' ? 'Etc/UTC' : $this->timezone, + 'COLLABORA_DICTIONARIES' => $this->collaboraDictionaries === '' ? 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru' : $this->collaboraDictionaries, + 'IMAGINARY_ENABLED' => $this->isImaginaryEnabled ? 'yes' : '', + 'FULLTEXTSEARCH_ENABLED' => $this->isFulltextsearchEnabled ? 'yes' : '', + 'DOCKER_SOCKET_PROXY_ENABLED' => $this->isDockerSocketProxyEnabled ? 'yes' : '', + 'NEXTCLOUD_UPLOAD_LIMIT' => $this->nextcloudUploadLimit, + 'NEXTCLOUD_MEMORY_LIMIT' => $this->nextcloudMemoryLimit, + 'NEXTCLOUD_MAX_TIME' => $this->nextcloudMaxTime, + 'BORG_RETENTION_POLICY' => $this->borgRetentionPolicy, + 'FULLTEXTSEARCH_JAVA_OPTIONS' => $this->fulltextsearchJavaOptions, + 'NEXTCLOUD_TRUSTED_CACERTS_DIR' => $this->trustedCacertsDir, + 'ADDITIONAL_DIRECTORIES_BACKUP' => $this->GetAdditionalBackupDirectoriesString() !== '' ? 'yes' : '', + 'BORGBACKUP_HOST_LOCATION' => $this->borgBackupHostLocation, + 'APACHE_MAX_SIZE' => (string)($this->GetApacheMaxSize()), + 'COLLABORA_SECCOMP_POLICY' => $this->GetCollaboraSeccompPolicy(), + 'NEXTCLOUD_STARTUP_APPS' => $this->GetNextcloudStartupApps(), + 'NEXTCLOUD_ADDITIONAL_APKS' => $this->nextcloudAdditionalApks, + 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS' => $this->nextcloudAdditionalPhpExtensions, + 'INSTALL_LATEST_MAJOR' => $this->installLatestMajor, + 'REMOVE_DISABLED_APPS' => $this->nextcloudKeepDisabledApps ? '' : 'yes', + // Allow to get local ip-address of database container which allows to talk to it even in host mode (the container that requires this needs to be started first then) + 'AIO_DATABASE_HOST' => gethostbyname('nextcloud-aio-database'), + // Allow to get local ip-address of caddy container and add it to trusted proxies automatically + 'CADDY_IP_ADDRESS' => in_array('caddy', $this->aioCommunityContainers, true) ? gethostbyname('nextcloud-aio-caddy') : '', + 'WHITEBOARD_ENABLED' => $this->isWhiteboardEnabled ? 'yes' : '', + default => $this->GetRegisteredSecret($placeholder), + }; + } + + private function booleanize(mixed $value) : bool { + return in_array($value, [true, 'true'], true); } } diff --git a/php/src/Data/Setup.php b/php/src/Data/Setup.php index f8f43e4b..e409eef8 100644 --- a/php/src/Data/Setup.php +++ b/php/src/Data/Setup.php @@ -17,7 +17,7 @@ readonly class Setup { } $password = $this->passwordGenerator->GeneratePassword(8); - $this->configurationManager->SetPassword($password); + $this->configurationManager->password = $password; return $password; } diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index fb3701a4..509f4c28 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -115,9 +115,9 @@ readonly class DockerActionManager { $containerName = $container->identifier; $internalPort = $container->internalPorts; if ($internalPort === '%APACHE_PORT%') { - $internalPort = $this->configurationManager->GetApachePort(); + $internalPort = $this->configurationManager->apachePort; } elseif ($internalPort === '%TALK_PORT%') { - $internalPort = $this->configurationManager->GetTalkPort(); + $internalPort = $this->configurationManager->talkPort; } if ($internalPort !== "" && $internalPort !== 'host') { @@ -205,7 +205,7 @@ readonly class DockerActionManager { foreach ($container->volumes->GetVolumes() as $volume) { // // NEXTCLOUD_MOUNT gets added via bind-mount later on // if ($container->identifier === 'nextcloud-aio-nextcloud') { - // if ($volume->name === $this->configurationManager->GetNextcloudMount()) { + // if ($volume->name === $this->configurationManager->nextcloudMount) { // continue; // } // } @@ -228,15 +228,7 @@ readonly class DockerActionManager { $requestBody['HostConfig']['Binds'] = $volumes; } - $aioVariables = $container->aioVariables->GetVariables(); - foreach ($aioVariables as $variable) { - $config = $this->configurationManager->GetConfig(); - $variable = $this->replaceEnvPlaceholders($variable); - $variableArray = explode('=', $variable); - $config[$variableArray[0]] = $variableArray[1]; - $this->configurationManager->WriteConfig($config); - sleep(1); - } + $this->configurationManager->setAioVariables($container->aioVariables->GetVariables()); $envs = $container->containerEnvironmentVariables->GetVariables(); // Special thing for the nextcloud container @@ -244,7 +236,7 @@ readonly class DockerActionManager { $envs[] = $this->GetAllNextcloudExecCommands(); } foreach ($envs as $key => $env) { - $envs[$key] = $this->replaceEnvPlaceholders($env); + $envs[$key] = $this->configurationManager->replaceEnvPlaceholders($env); } if (count($envs) > 0) { @@ -261,13 +253,13 @@ readonly class DockerActionManager { $port = $value->port; $protocol = $value->protocol; if ($port === '%APACHE_PORT%') { - $port = $this->configurationManager->GetApachePort(); + $port = $this->configurationManager->apachePort; // Do not expose udp if AIO is in reverse proxy mode if ($port !== '443' && $protocol === 'udp') { continue; } } else if ($port === '%TALK_PORT%') { - $port = $this->configurationManager->GetTalkPort(); + $port = $this->configurationManager->talkPort; } $portWithProtocol = $port . '/' . $protocol; $exposedPorts[$portWithProtocol] = null; @@ -283,13 +275,13 @@ readonly class DockerActionManager { $port = $value->port; $protocol = $value->protocol; if ($port === '%APACHE_PORT%') { - $port = $this->configurationManager->GetApachePort(); + $port = $this->configurationManager->apachePort; // Do not expose udp if AIO is in reverse proxy mode if ($port !== '443' && $protocol === 'udp') { continue; } } else if ($port === '%TALK_PORT%') { - $port = $this->configurationManager->GetTalkPort(); + $port = $this->configurationManager->talkPort; // Skip publishing talk tcp port if it is set to 443 if ($port === '443' && $protocol === 'tcp') { continue; @@ -297,7 +289,7 @@ readonly class DockerActionManager { } $ipBinding = $value->ipBinding; if ($ipBinding === '%APACHE_IP_BINDING%') { - $ipBinding = $this->configurationManager->GetApacheIPBinding(); + $ipBinding = $this->configurationManager->apacheIpBinding; // Do not expose if AIO is in internal network mode if ($ipBinding === '@INTERNAL') { continue; @@ -315,7 +307,7 @@ readonly class DockerActionManager { $devices = []; foreach ($container->devices as $device) { - if ($device === '/dev/dri' && !$this->configurationManager->isDriDeviceEnabled()) { + if ($device === '/dev/dri' && !$this->configurationManager->nextcloudEnableDriDevice) { continue; } $devices[] = ["PathOnHost" => $device, "PathInContainer" => $device, "CgroupPermissions" => "rwm"]; @@ -325,7 +317,7 @@ readonly class DockerActionManager { $requestBody['HostConfig']['Devices'] = $devices; } - if ($container->enableNvidiaGpu && $this->configurationManager->isNvidiaGpuEnabled()) { + if ($container->enableNvidiaGpu && $this->configurationManager->enableNvidiaGpu) { $requestBody['HostConfig']['Runtime'] = 'nvidia'; $requestBody['HostConfig']['DeviceRequests'] = [ [ @@ -408,7 +400,7 @@ readonly class DockerActionManager { // // Special things for the nextcloud container which should not be exposed in the containers.json // } elseif ($container->identifier === 'nextcloud-aio-nextcloud') { // foreach ($container->volumes->GetVolumes() as $volume) { - // if ($volume->name !== $this->configurationManager->GetNextcloudMount()) { + // if ($volume->name !== $this->configurationManager->nextcloud_mount) { // continue; // } // $mounts[] = ["Type" => "bind", "Source" => $volume->name, "Target" => $volume->mountPoint, "ReadOnly" => !$volume->isWritable, "BindOptions" => [ "Propagation" => "rshared"]]; @@ -420,15 +412,15 @@ readonly class DockerActionManager { // Special things for the collabora container which should not be exposed in the containers.json } elseif ($container->identifier === 'nextcloud-aio-collabora') { - if (!$this->configurationManager->isSeccompDisabled()) { + if (!$this->configurationManager->collaboraSeccompDisabled) { // Load reference seccomp profile for collabora $seccompProfile = (string)file_get_contents(DataConst::GetCollaboraSeccompProfilePath()); $requestBody['HostConfig']['SecurityOpt'] = ["label:disable", "seccomp=$seccompProfile"]; } // Additional Collabora options - if ($this->configurationManager->GetAdditionalCollaboraOptions() !== '') { - $requestBody['Cmd'] = [$this->configurationManager->GetAdditionalCollaboraOptions()]; + if ($this->configurationManager->collaboraAdditionalOptions !== '') { + $requestBody['Cmd'] = [$this->configurationManager->collaboraAdditionalOptions]; } } @@ -530,82 +522,6 @@ readonly class DockerActionManager { } } - // Replaces placeholders in $envValue with their values. - // E.g. "%NC_DOMAIN%:%APACHE_PORT" becomes "my.nextcloud.com:11000" - private function replaceEnvPlaceholders(string $envValue): string { - // $pattern breaks down as: - // % - matches a literal percent sign - // ([^%]+) - capture group that matches one or more characters that are NOT percent signs - // % - matches the closing percent sign - // - // Assumes literal percent signs are always matched and there is no - // escaping. - $pattern = '/%([^%]+)%/'; - $matchCount = preg_match_all($pattern, $envValue, $matches); - - if ($matchCount === 0) { - return $envValue; - } - - $placeholders = $matches[0]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...] - $placeholderNames = $matches[1]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...] - $placeholderPatterns = array_map(static fn(string $p) => '/' . preg_quote($p) . '/', $placeholders); // ["/%PLACEHOLDER1%/", ...] - $placeholderValues = array_map($this->getPlaceholderValue(...), $placeholderNames); // ["val1", "val2"] - // Guaranteed to be non-null because we found the placeholders in the preg_match_all. - return (string) preg_replace($placeholderPatterns, $placeholderValues, $envValue); - } - - private function getPlaceholderValue(string $placeholder) : string { - return match ($placeholder) { - 'NC_DOMAIN' => $this->configurationManager->GetDomain(), - 'NC_BASE_DN' => $this->configurationManager->GetBaseDN(), - 'AIO_TOKEN' => $this->configurationManager->GetToken(), - 'BORGBACKUP_REMOTE_REPO' => $this->configurationManager->GetBorgRemoteRepo(), - 'BORGBACKUP_MODE' => $this->configurationManager->GetBackupMode(), - 'AIO_URL' => $this->configurationManager->GetAIOURL(), - 'SELECTED_RESTORE_TIME' => $this->configurationManager->GetSelectedRestoreTime(), - 'RESTORE_EXCLUDE_PREVIEWS' => $this->configurationManager->GetRestoreExcludePreviews(), - 'APACHE_PORT' => $this->configurationManager->GetApachePort(), - 'APACHE_IP_BINDING' => $this->configurationManager->GetApacheIPBinding(), - 'TALK_PORT' => $this->configurationManager->GetTalkPort(), - 'TURN_DOMAIN' => $this->configurationManager->GetTurnDomain(), - 'NEXTCLOUD_MOUNT' => $this->configurationManager->GetNextcloudMount(), - 'BACKUP_RESTORE_PASSWORD' => $this->configurationManager->GetBorgRestorePassword(), - 'CLAMAV_ENABLED' => $this->configurationManager->isClamavEnabled() ? 'yes' : '', - 'TALK_RECORDING_ENABLED' => $this->configurationManager->isTalkRecordingEnabled() ? 'yes' : '', - 'ONLYOFFICE_ENABLED' => $this->configurationManager->isOnlyofficeEnabled() ? 'yes' : '', - 'COLLABORA_ENABLED' => $this->configurationManager->isCollaboraEnabled() ? 'yes' : '', - 'TALK_ENABLED' => $this->configurationManager->isTalkEnabled() ? 'yes' : '', - 'UPDATE_NEXTCLOUD_APPS' => ($this->configurationManager->isDailyBackupRunning() && $this->configurationManager->areAutomaticUpdatesEnabled()) ? 'yes' : '', - 'TIMEZONE' => $this->configurationManager->GetTimezone() === '' ? 'Etc/UTC' : $this->configurationManager->GetTimezone(), - 'COLLABORA_DICTIONARIES' => $this->configurationManager->GetCollaboraDictionaries() === '' ? 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru' : $this->configurationManager->GetCollaboraDictionaries(), - 'IMAGINARY_ENABLED' => $this->configurationManager->isImaginaryEnabled() ? 'yes' : '', - 'FULLTEXTSEARCH_ENABLED' => $this->configurationManager->isFulltextsearchEnabled() ? 'yes' : '', - 'DOCKER_SOCKET_PROXY_ENABLED' => $this->configurationManager->isDockerSocketProxyEnabled() ? 'yes' : '', - 'NEXTCLOUD_UPLOAD_LIMIT' => $this->configurationManager->GetNextcloudUploadLimit(), - 'NEXTCLOUD_MEMORY_LIMIT' => $this->configurationManager->GetNextcloudMemoryLimit(), - 'NEXTCLOUD_MAX_TIME' => $this->configurationManager->GetNextcloudMaxTime(), - 'BORG_RETENTION_POLICY' => $this->configurationManager->GetBorgRetentionPolicy(), - 'FULLTEXTSEARCH_JAVA_OPTIONS' => $this->configurationManager->GetFulltextsearchJavaOptions(), - 'NEXTCLOUD_TRUSTED_CACERTS_DIR' => $this->configurationManager->GetTrustedCacertsDir(), - 'ADDITIONAL_DIRECTORIES_BACKUP' => $this->configurationManager->GetAdditionalBackupDirectoriesString() !== '' ? 'yes' : '', - 'BORGBACKUP_HOST_LOCATION' => $this->configurationManager->GetBorgBackupHostLocation(), - 'APACHE_MAX_SIZE' => (string)($this->configurationManager->GetApacheMaxSize()), - 'COLLABORA_SECCOMP_POLICY' => $this->configurationManager->GetCollaboraSeccompPolicy(), - 'NEXTCLOUD_STARTUP_APPS' => $this->configurationManager->GetNextcloudStartupApps(), - 'NEXTCLOUD_ADDITIONAL_APKS' => $this->configurationManager->GetNextcloudAdditionalApks(), - 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS' => $this->configurationManager->GetNextcloudAdditionalPhpExtensions(), - 'INSTALL_LATEST_MAJOR' => $this->configurationManager->shouldLatestMajorGetInstalled() ? 'yes' : '', - 'REMOVE_DISABLED_APPS' => $this->configurationManager->shouldDisabledAppsGetRemoved() ? 'yes' : '', - // Allow to get local ip-address of database container which allows to talk to it even in host mode (the container that requires this needs to be started first then) - 'AIO_DATABASE_HOST' => gethostbyname('nextcloud-aio-database'), - // Allow to get local ip-address of caddy container and add it to trusted proxies automatically - 'CADDY_IP_ADDRESS' => in_array('caddy', $this->configurationManager->GetEnabledCommunityContainers(), true) ? gethostbyname('nextcloud-aio-caddy') : '', - 'WHITEBOARD_ENABLED' => $this->configurationManager->isWhiteboardEnabled() ? 'yes' : '', - default => $this->configurationManager->GetRegisteredSecret($placeholder), - }; - } - private function isContainerUpdateAvailable(string $id): string { $container = $this->containerDefinitionFetcher->GetContainerById($id); @@ -621,7 +537,7 @@ readonly class DockerActionManager { public function isAnyUpdateAvailable(): bool { // return early if instance is not installed - if (!$this->configurationManager->wasStartButtonClicked()) { + if (!$this->configurationManager->wasStartButtonClicked) { return false; } $id = 'nextcloud-aio-apache'; @@ -921,7 +837,7 @@ readonly class DockerActionManager { $this->ConnectContainerIdToNetwork($container->identifier, $container->internalPorts, alias: $alias); if ($container->identifier === 'nextcloud-aio-apache' || $container->identifier === 'nextcloud-aio-domaincheck') { - $apacheAdditionalNetwork = $this->configurationManager->GetApacheAdditionalNetwork(); + $apacheAdditionalNetwork = $this->configurationManager->apacheAdditionalNetwork; if ($apacheAdditionalNetwork !== '') { $this->ConnectContainerIdToNetwork($container->identifier, $container->internalPorts, $apacheAdditionalNetwork, false, $alias); }