From fa50f5c141c869050ee7537d051a170c93bfe2bd Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Tue, 6 Jan 2026 19:11:51 +0100 Subject: [PATCH] Trigger config saving manually and add more setter methods This requires some additional code lines but allows the calling code to group calls to setter methods and only save them at once. Signed-off-by: Pablo Zmdl --- .../Controller/ConfigurationController.php | 2 + php/src/Controller/DockerController.php | 37 ++-- php/src/Data/ConfigurationManager.php | 192 ++++++++++-------- php/src/Data/Setup.php | 1 + 4 files changed, 127 insertions(+), 105 deletions(-) diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php index 45586f9c..2afd7c1d 100644 --- a/php/src/Controller/ConfigurationController.php +++ b/php/src/Controller/ConfigurationController.php @@ -162,6 +162,8 @@ readonly class ConfigurationController { $this->configurationManager->DeleteBorgBackupLocationItems(); } + $this->configurationManager->save(); + return $response->withStatus(201)->withHeader('Location', '.'); } catch (InvalidSettingConfigurationException $ex) { $response->getBody()->write($ex->getMessage()); diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index 27a06bc8..04b8eb08 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -90,6 +90,7 @@ readonly class DockerController { public function startBackup(bool $forceStopNextcloud = false) : void { $this->configurationManager->SetBackupMode('backup'); + $this->configurationManager->save(); $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); @@ -110,6 +111,7 @@ readonly class DockerController { public function checkBackup() : void { $this->configurationManager->SetBackupMode('check'); + $this->configurationManager->save(); $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); @@ -117,6 +119,7 @@ readonly class DockerController { private function listBackup() : void { $this->configurationManager->SetBackupMode('list'); + $this->configurationManager->save(); $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); @@ -124,14 +127,13 @@ readonly class DockerController { 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'] ?? ''; + $this->configurationManager->setSelectedRestoreTime($request->getParsedBody()['selected_restore_time'] ?? ''); if (isset($request->getParsedBody()['restore-exclude-previews'])) { - $config['restore-exclude-previews'] = 1; + $this->configurationManager->setRestoreExcludePreviews(1); } else { - $config['restore-exclude-previews'] = ''; + $this->configurationManager->setRestoreExcludePreviews(''); } - $this->configurationManager->WriteConfig($config); + $this->configurationManager->save(); $id = self::TOP_CONTAINER; $forceStopNextcloud = true; @@ -145,21 +147,22 @@ readonly class DockerController { public function StartBackupContainerCheckRepair(Request $request, Response $response, array $args) : Response { $this->configurationManager->SetBackupMode('check-repair'); + $this->configurationManager->save(); $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->save(); 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->setNoInstanceRestoreAttempt(); + $this->configurationManager->save(); $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id); @@ -187,14 +190,10 @@ readonly class DockerController { $installLatestMajor = ""; } - $config = $this->configurationManager->GetConfig(); - // set AIO_URL - $config['AIO_URL'] = $host . ':' . (string)$port . $path; - // set wasStartButtonClicked - $config['wasStartButtonClicked'] = 1; - // set install_latest_major - $config['install_latest_major'] = $installLatestMajor; - $this->configurationManager->WriteConfig($config); + $this->configurationManager->setAIOURL($host . ':' . (string)$port . $path); + $this->configurationManager->startButtonWasClicked(); + $this->configurationManager->setInstallLatestMajor($installLatestMajor); + $this->configurationManager->save(); // Do not pull container images in case 'bypass_container_update' is set via url params // Needed for local testing @@ -213,10 +212,8 @@ 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->setToken(bin2hex(random_bytes(24))); + $this->configurationManager->save(); // Stop domaincheck since apache would not be able to start otherwise $this->StopDomaincheckContainer(); diff --git a/php/src/Data/ConfigurationManager.php b/php/src/Data/ConfigurationManager.php index 320bc477..e2bea55d 100644 --- a/php/src/Data/ConfigurationManager.php +++ b/php/src/Data/ConfigurationManager.php @@ -9,15 +9,20 @@ class ConfigurationManager { private array $secrets = []; - public function GetConfig() : array + // Store the config on the instance, so multiple setter methods can change the config without writing the + // config file in between those changes. + private array $config = []; + + private function GetConfig() : array { - if(file_exists(DataConst::GetConfigFile())) + // Only load the config from disk if it's not present, yet. + 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 { @@ -28,10 +33,14 @@ class ConfigurationManager return $this->GetConfig()['AIO_TOKEN']; } + public function setToken(string $token) : void { + $this->GetConfig(); + $this->config['AIO_TOKEN'] = $token; + } + public function SetPassword(string $password) : void { - $config = $this->GetConfig(); - $config['password'] = $password; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['password'] = $password; } public function GetAndGenerateSecret(string $secretId) : string { @@ -39,17 +48,17 @@ class ConfigurationManager return ''; } - $config = $this->GetConfig(); - if(!isset($config['secrets'][$secretId])) { - $config['secrets'][$secretId] = bin2hex(random_bytes(24)); - $this->WriteConfig($config); + $this->GetConfig(); + if(!isset($this->config['secrets'][$secretId])) { + $this->config['secrets'][$secretId] = bin2hex(random_bytes(24)); + $this->save(); } if ($secretId === 'BORGBACKUP_PASSWORD' && !file_exists(DataConst::GetBackupSecretFile())) { - $this->DoubleSafeBackupSecret($config['secrets'][$secretId]); + $this->DoubleSafeBackupSecret($this->config['secrets'][$secretId]); } - return $config['secrets'][$secretId]; + return $this->config['secrets'][$secretId]; } public function GetRegisteredSecret(string $secretId) : string { @@ -130,6 +139,11 @@ class ConfigurationManager } } + public function startButtonWasClicked() : void { + $this->GetConfig(); + $this->config['wasStartButtonClicked'] = '1'; + } + private function isx64Platform() : bool { if (php_uname('m') === 'x86_64') { return true; @@ -157,9 +171,8 @@ class ConfigurationManager } public function SetDockerSocketProxyEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isDockerSocketProxyEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isDockerSocketProxyEnabled'] = $value; } public function isWhiteboardEnabled() : bool { @@ -172,15 +185,13 @@ class ConfigurationManager } public function SetWhiteboardEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isWhiteboardEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isWhiteboardEnabled'] = $value; } public function SetClamavEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isClamavEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isClamavEnabled'] = $value; } public function isImaginaryEnabled() : bool { @@ -193,9 +204,8 @@ class ConfigurationManager } public function SetImaginaryEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isImaginaryEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isImaginaryEnabled'] = $value; } public function isFulltextsearchEnabled() : bool { @@ -213,9 +223,8 @@ class ConfigurationManager $value = 0; } - $config = $this->GetConfig(); - $config['isFulltextsearchEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isFulltextsearchEnabled'] = $value; } public function isOnlyofficeEnabled() : bool { @@ -228,9 +237,8 @@ class ConfigurationManager } public function SetOnlyofficeEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isOnlyofficeEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isOnlyofficeEnabled'] = $value; } public function isCollaboraEnabled() : bool { @@ -243,9 +251,8 @@ class ConfigurationManager } public function SetCollaboraEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isCollaboraEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isCollaboraEnabled'] = $value; } public function isTalkEnabled() : bool { @@ -258,9 +265,8 @@ class ConfigurationManager } public function SetTalkEnabledState(int $value) : void { - $config = $this->GetConfig(); - $config['isTalkEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isTalkEnabled'] = $value; } public function isTalkRecordingEnabled() : bool { @@ -280,9 +286,8 @@ class ConfigurationManager $value = 0; } - $config = $this->GetConfig(); - $config['isTalkRecordingEnabled'] = $value; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['isTalkRecordingEnabled'] = $value; } /** @@ -392,12 +397,11 @@ class ConfigurationManager } } + $this->GetConfig(); // Write domain - $config = $this->GetConfig(); - $config['domain'] = $domain; + $this->config['domain'] = $domain; // Reset the borg restore password when setting the domain - $config['borg_restore_password'] = ''; - $this->WriteConfig($config); + $this->config['borg_restore_password'] = ''; } public function GetDomain() : string { @@ -427,9 +431,8 @@ class ConfigurationManager } public function SetBackupMode(string $mode) : void { - $config = $this->GetConfig(); - $config['backup-mode'] = $mode; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['backup-mode'] = $mode; } public function GetSelectedRestoreTime() : string { @@ -441,6 +444,11 @@ class ConfigurationManager return $config['selected-restore-time']; } + public function setSelectedRestoreTime(string $time) : void { + $this->GetConfig(); + $this->config['selected-restore-time'] = $time; + } + public function GetRestoreExcludePreviews() : string { $config = $this->GetConfig(); if(!isset($config['restore-exclude-previews'])) { @@ -450,6 +458,11 @@ class ConfigurationManager return $config['restore-exclude-previews']; } + public function setRestoreExcludePreviews(mixed $value) : void { + $this->GetConfig(); + $this->config['restore-exclude-previews'] = $value; + } + public function GetAIOURL() : string { $config = $this->GetConfig(); if(!isset($config['AIO_URL'])) { @@ -459,16 +472,20 @@ class ConfigurationManager return $config['AIO_URL']; } + public function setAIOURL(string $url) : void { + $this->GetConfig(); + $this->config['AIO_URL'] = $host . ':' . (string)$port . $path; + } + /** * @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->GetConfig(); + $this->config['borg_backup_host_location'] = $location; + $this->config['borg_remote_repo'] = $repo; } private function ValidateBorgLocationVars(string $location, string $repo) : void { @@ -514,10 +531,9 @@ 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->GetConfig(); + $this->config['borg_backup_host_location'] = ''; + $this->config['borg_remote_repo'] = ''; // Also delete the borg config file to be able to start over if (file_exists(DataConst::GetBackupKeyFile())) { @@ -537,12 +553,11 @@ 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->GetConfig(); + $this->config['borg_backup_host_location'] = $location; + $this->config['borg_remote_repo'] = $repo; + $this->config['borg_restore_password'] = $password; + $this->config['instance_restore_attempt'] = 1; } /** @@ -599,7 +614,7 @@ class ConfigurationManager /** * @throws InvalidSettingConfigurationException */ - public function WriteConfig(array $config) : void { + public function save() : void { if(!is_dir(DataConst::GetDataDirectory())) { throw new InvalidSettingConfigurationException(DataConst::GetDataDirectory() . " does not exist! Something was set up falsely!"); } @@ -610,6 +625,9 @@ class ConfigurationManager 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); + // Force reloading the config after it was written. It's not clear to me if keeping the config loaded + // might cause race conditions, e.g. in case multiple processes write to the file, so better safe than sorry. + $this->config = []; } private function GetEnvironmentalVariableOrConfig(string $envVariableName, string $configName, string $defaultValue) : string { @@ -627,8 +645,8 @@ class ConfigurationManager $config[$configName] = ''; } if ($envVariableOutput !== $config[$configName]) { - $config[$configName] = $envVariableOutput; - $this->WriteConfig($config); + $this->config[$configName] = $envVariableOutput; + $this->save(); } } return $envVariableOutput; @@ -681,6 +699,12 @@ class ConfigurationManager return false; } + public function setNoInstanceRestoreAttempt() : void + { + $this->GetConfig(); + $this->config['instance_restore_attempt'] = 0; + } + public function GetNextcloudMount() : string { $envVariableName = 'NEXTCLOUD_MOUNT'; $configName = 'nextcloud_mount'; @@ -870,6 +894,11 @@ class ConfigurationManager return $config['install_latest_major'] !== ''; } + public function setInstallLatestMajor(mixed $value) : void { + $this->GetConfig(); + $this->config['install_latest_major'] = $value; + } + public function GetAdditionalBackupDirectoriesString() : string { if (!file_exists(DataConst::GetAdditionalBackupDirectoriesFile())) { return ''; @@ -912,15 +941,13 @@ class ConfigurationManager throw new InvalidSettingConfigurationException("The entered timezone does not seem to be a valid timezone!"); } - $config = $this->GetConfig(); - $config['timezone'] = $timezone; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['timezone'] = $timezone; } public function DeleteTimezone() : void { - $config = $this->GetConfig(); - $config['timezone'] = ''; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['timezone'] = ''; } public function shouldDomainValidationBeSkipped(bool $skipDomainValidation) : bool { @@ -959,15 +986,13 @@ class ConfigurationManager throw new InvalidSettingConfigurationException("The entered dictionaries do not seem to be a valid!"); } - $config = $this->GetConfig(); - $config['collabora_dictionaries'] = $CollaboraDictionaries; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['collabora_dictionaries'] = $CollaboraDictionaries; } public function DeleteCollaboraDictionaries() : void { - $config = $this->GetConfig(); - $config['collabora_dictionaries'] = ''; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['collabora_dictionaries'] = ''; } /** @@ -982,9 +1007,8 @@ class ConfigurationManager 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); + $this->GetConfig(); + $this->config['collabora_additional_options'] = $additionalCollaboraOptions; } public function GetAdditionalCollaboraOptions() : string { @@ -1004,9 +1028,8 @@ class ConfigurationManager } public function DeleteAdditionalCollaboraOptions() : void { - $config = $this->GetConfig(); - $config['collabora_additional_options'] = ''; - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['collabora_additional_options'] = ''; } public function GetApacheAdditionalNetwork() : string { @@ -1089,9 +1112,8 @@ class ConfigurationManager } public function SetEnabledCommunityContainers(array $enabledCommunityContainers) : void { - $config = $this->GetConfig(); - $config['aio_community_containers'] = implode(' ', $enabledCommunityContainers); - $this->WriteConfig($config); + $this->GetConfig(); + $this->config['aio_community_containers'] = implode(' ', $enabledCommunityContainers); } private function GetEnabledDriDevice() : string { diff --git a/php/src/Data/Setup.php b/php/src/Data/Setup.php index f8f43e4b..ee493076 100644 --- a/php/src/Data/Setup.php +++ b/php/src/Data/Setup.php @@ -18,6 +18,7 @@ readonly class Setup { $password = $this->passwordGenerator->GeneratePassword(8); $this->configurationManager->SetPassword($password); + $this->configurationManager->save(); return $password; }