From f5023ed88dd5e18c4de7e64d3c2c646c5b34ac18 Mon Sep 17 00:00:00 2001 From: Alan Savage <3028205+asavageiv@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:12:36 -0700 Subject: [PATCH 1/7] Factor out getPlaceholderValue from CreateContainer Signed-off-by: Alan Savage <3028205+asavageiv@users.noreply.github.com> --- php/src/Docker/DockerActionManager.php | 317 +++++++++++++------------ 1 file changed, 160 insertions(+), 157 deletions(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 206bc904..e3c7456f 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -253,163 +253,7 @@ readonly class DockerActionManager { if (preg_match($patterns[0], $env, $out) === 1) { $replacements = array(); - - if ($out[1] === 'NC_DOMAIN') { - $replacements[1] = $this->configurationManager->GetDomain(); - } elseif ($out[1] === 'NC_BASE_DN') { - $replacements[1] = $this->configurationManager->GetBaseDN(); - } elseif ($out[1] === 'AIO_TOKEN') { - $replacements[1] = $this->configurationManager->GetToken(); - } elseif ($out[1] === 'BORGBACKUP_REMOTE_REPO') { - $replacements[1] = $this->configurationManager->GetBorgRemoteRepo(); - } elseif ($out[1] === 'BORGBACKUP_MODE') { - $replacements[1] = $this->configurationManager->GetBackupMode(); - } elseif ($out[1] === 'AIO_URL') { - $replacements[1] = $this->configurationManager->GetAIOURL(); - } elseif ($out[1] === 'SELECTED_RESTORE_TIME') { - $replacements[1] = $this->configurationManager->GetSelectedRestoreTime(); - } elseif ($out[1] === 'RESTORE_EXCLUDE_PREVIEWS') { - $replacements[1] = $this->configurationManager->GetRestoreExcludePreviews(); - } elseif ($out[1] === 'APACHE_PORT') { - $replacements[1] = $this->configurationManager->GetApachePort(); - } elseif ($out[1] === 'TALK_PORT') { - $replacements[1] = $this->configurationManager->GetTalkPort(); - } elseif ($out[1] === 'NEXTCLOUD_MOUNT') { - $replacements[1] = $this->configurationManager->GetNextcloudMount(); - } elseif ($out[1] === 'BACKUP_RESTORE_PASSWORD') { - $replacements[1] = $this->configurationManager->GetBorgRestorePassword(); - } elseif ($out[1] === 'CLAMAV_ENABLED') { - if ($this->configurationManager->isClamavEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'TALK_RECORDING_ENABLED') { - if ($this->configurationManager->isTalkRecordingEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'ONLYOFFICE_ENABLED') { - if ($this->configurationManager->isOnlyofficeEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'COLLABORA_ENABLED') { - if ($this->configurationManager->isCollaboraEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'TALK_ENABLED') { - if ($this->configurationManager->isTalkEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'UPDATE_NEXTCLOUD_APPS') { - if ($this->configurationManager->isDailyBackupRunning() && $this->configurationManager->areAutomaticUpdatesEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'TIMEZONE') { - if ($this->configurationManager->GetTimezone() === '') { - $replacements[1] = 'Etc/UTC'; - } else { - $replacements[1] = $this->configurationManager->GetTimezone(); - } - } elseif ($out[1] === 'COLLABORA_DICTIONARIES') { - if ($this->configurationManager->GetCollaboraDictionaries() === '') { - $replacements[1] = 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru'; - } else { - $replacements[1] = $this->configurationManager->GetCollaboraDictionaries(); - } - } elseif ($out[1] === 'IMAGINARY_ENABLED') { - if ($this->configurationManager->isImaginaryEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'FULLTEXTSEARCH_ENABLED') { - if ($this->configurationManager->isFulltextsearchEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'DOCKER_SOCKET_PROXY_ENABLED') { - if ($this->configurationManager->isDockerSocketProxyEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'NEXTCLOUD_UPLOAD_LIMIT') { - $replacements[1] = $this->configurationManager->GetNextcloudUploadLimit(); - } elseif ($out[1] === 'NEXTCLOUD_MEMORY_LIMIT') { - $replacements[1] = $this->configurationManager->GetNextcloudMemoryLimit(); - } elseif ($out[1] === 'NEXTCLOUD_MAX_TIME') { - $replacements[1] = $this->configurationManager->GetNextcloudMaxTime(); - } elseif ($out[1] === 'BORG_RETENTION_POLICY') { - $replacements[1] = $this->configurationManager->GetBorgRetentionPolicy(); - } elseif ($out[1] === 'FULLTEXTSEARCH_JAVA_OPTIONS') { - $replacements[1] = $this->configurationManager->GetFulltextsearchJavaOptions(); - } elseif ($out[1] === 'NEXTCLOUD_TRUSTED_CACERTS_DIR') { - $replacements[1] = $this->configurationManager->GetTrustedCacertsDir(); - } elseif ($out[1] === 'ADDITIONAL_DIRECTORIES_BACKUP') { - if ($this->configurationManager->GetAdditionalBackupDirectoriesString() !== '') { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'BORGBACKUP_HOST_LOCATION') { - $replacements[1] = $this->configurationManager->GetBorgBackupHostLocation(); - } elseif ($out[1] === 'APACHE_MAX_SIZE') { - $replacements[1] = $this->configurationManager->GetApacheMaxSize(); - } elseif ($out[1] === 'COLLABORA_SECCOMP_POLICY') { - $replacements[1] = $this->configurationManager->GetCollaboraSeccompPolicy(); - } elseif ($out[1] === 'NEXTCLOUD_STARTUP_APPS') { - $replacements[1] = $this->configurationManager->GetNextcloudStartupApps(); - } elseif ($out[1] === 'NEXTCLOUD_ADDITIONAL_APKS') { - $replacements[1] = $this->configurationManager->GetNextcloudAdditionalApks(); - } elseif ($out[1] === 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS') { - $replacements[1] = $this->configurationManager->GetNextcloudAdditionalPhpExtensions(); - } elseif ($out[1] === 'INSTALL_LATEST_MAJOR') { - if ($this->configurationManager->shouldLatestMajorGetInstalled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'REMOVE_DISABLED_APPS') { - if ($this->configurationManager->shouldDisabledAppsGetRemoved()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - // 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) - } elseif ($out[1] === 'AIO_DATABASE_HOST') { - $replacements[1] = gethostbyname('nextcloud-aio-database'); - // Allow to get local ip-address of caddy container and add it to trusted proxies automatically - } elseif ($out[1] === 'CADDY_IP_ADDRESS') { - $replacements[1] = ''; - $communityContainers = $this->configurationManager->GetEnabledCommunityContainers(); - if (in_array('caddy', $communityContainers, true)) { - $replacements[1] = gethostbyname('nextcloud-aio-caddy'); - } - } elseif ($out[1] === 'WHITEBOARD_ENABLED') { - if ($this->configurationManager->isWhiteboardEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } else { - $secret = $this->configurationManager->GetSecret($out[1]); - if ($secret === "") { - throw new \Exception("The secret " . $out[1] . " is empty. Cannot substitute its value. Please check if it is defined in secrets of containers.json."); - } - $replacements[1] = $secret; - } - + $replacements[1] = $this->getPlaceholderValue($out[1]); $envs[$key] = preg_replace($patterns, $replacements, $env); } } @@ -644,6 +488,165 @@ readonly class DockerActionManager { } } + private function getPlaceholderValue($placeholder) { + if ($placeholder === 'NC_DOMAIN') { + return $this->configurationManager->GetDomain(); + } elseif ($placeholder === 'NC_BASE_DN') { + return $this->configurationManager->GetBaseDN(); + } elseif ($placeholder === 'AIO_TOKEN') { + return $this->configurationManager->GetToken(); + } elseif ($placeholder === 'BORGBACKUP_REMOTE_REPO') { + return $this->configurationManager->GetBorgRemoteRepo(); + } elseif ($placeholder === 'BORGBACKUP_MODE') { + return $this->configurationManager->GetBackupMode(); + } elseif ($placeholder === 'AIO_URL') { + return $this->configurationManager->GetAIOURL(); + } elseif ($placeholder === 'SELECTED_RESTORE_TIME') { + return $this->configurationManager->GetSelectedRestoreTime(); + } elseif ($placeholder === 'RESTORE_EXCLUDE_PREVIEWS') { + return $this->configurationManager->GetRestoreExcludePreviews(); + } elseif ($placeholder === 'APACHE_PORT') { + return $this->configurationManager->GetApachePort(); + } elseif ($placeholder === 'TALK_PORT') { + return $this->configurationManager->GetTalkPort(); + } elseif ($placeholder === 'NEXTCLOUD_MOUNT') { + return $this->configurationManager->GetNextcloudMount(); + } elseif ($placeholder === 'BACKUP_RESTORE_PASSWORD') { + return $this->configurationManager->GetBorgRestorePassword(); + } elseif ($placeholder === 'CLAMAV_ENABLED') { + if ($this->configurationManager->isClamavEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'TALK_RECORDING_ENABLED') { + if ($this->configurationManager->isTalkRecordingEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'ONLYOFFICE_ENABLED') { + if ($this->configurationManager->isOnlyofficeEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'COLLABORA_ENABLED') { + if ($this->configurationManager->isCollaboraEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'TALK_ENABLED') { + if ($this->configurationManager->isTalkEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'UPDATE_NEXTCLOUD_APPS') { + if ($this->configurationManager->isDailyBackupRunning() && $this->configurationManager->areAutomaticUpdatesEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'TIMEZONE') { + if ($this->configurationManager->GetTimezone() === '') { + return 'Etc/UTC'; + } else { + return $this->configurationManager->GetTimezone(); + } + } elseif ($placeholder === 'COLLABORA_DICTIONARIES') { + if ($this->configurationManager->GetCollaboraDictionaries() === '') { + return 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru'; + } else { + return $this->configurationManager->GetCollaboraDictionaries(); + } + } elseif ($placeholder === 'IMAGINARY_ENABLED') { + if ($this->configurationManager->isImaginaryEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'FULLTEXTSEARCH_ENABLED') { + if ($this->configurationManager->isFulltextsearchEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'DOCKER_SOCKET_PROXY_ENABLED') { + if ($this->configurationManager->isDockerSocketProxyEnabled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'NEXTCLOUD_UPLOAD_LIMIT') { + return $this->configurationManager->GetNextcloudUploadLimit(); + } elseif ($placeholder === 'NEXTCLOUD_MEMORY_LIMIT') { + return $this->configurationManager->GetNextcloudMemoryLimit(); + } elseif ($placeholder === 'NEXTCLOUD_MAX_TIME') { + return $this->configurationManager->GetNextcloudMaxTime(); + } elseif ($placeholder === 'BORG_RETENTION_POLICY') { + return $this->configurationManager->GetBorgRetentionPolicy(); + } elseif ($placeholder === 'FULLTEXTSEARCH_JAVA_OPTIONS') { + return $this->configurationManager->GetFulltextsearchJavaOptions(); + } elseif ($placeholder === 'NEXTCLOUD_TRUSTED_CACERTS_DIR') { + return $this->configurationManager->GetTrustedCacertsDir(); + } elseif ($placeholder === 'ADDITIONAL_DIRECTORIES_BACKUP') { + if ($this->configurationManager->GetAdditionalBackupDirectoriesString() !== '') { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'BORGBACKUP_HOST_LOCATION') { + return $this->configurationManager->GetBorgBackupHostLocation(); + } elseif ($placeholder === 'APACHE_MAX_SIZE') { + return $this->configurationManager->GetApacheMaxSize(); + } elseif ($placeholder === 'COLLABORA_SECCOMP_POLICY') { + return $this->configurationManager->GetCollaboraSeccompPolicy(); + } elseif ($placeholder === 'NEXTCLOUD_STARTUP_APPS') { + return $this->configurationManager->GetNextcloudStartupApps(); + } elseif ($placeholder === 'NEXTCLOUD_ADDITIONAL_APKS') { + return $this->configurationManager->GetNextcloudAdditionalApks(); + } elseif ($placeholder === 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS') { + return $this->configurationManager->GetNextcloudAdditionalPhpExtensions(); + } elseif ($placeholder === 'INSTALL_LATEST_MAJOR') { + if ($this->configurationManager->shouldLatestMajorGetInstalled()) { + return 'yes'; + } else { + return ''; + } + } elseif ($placeholder === 'REMOVE_DISABLED_APPS') { + if ($this->configurationManager->shouldDisabledAppsGetRemoved()) { + return 'yes'; + } else { + return ''; + } + // 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) + } elseif ($placeholder === 'AIO_DATABASE_HOST') { + return gethostbyname('nextcloud-aio-database'); + // Allow to get local ip-address of caddy container and add it to trusted proxies automatically + } elseif ($placeholder === 'CADDY_IP_ADDRESS') { + $communityContainers = $this->configurationManager->GetEnabledCommunityContainers(); + if (in_array('caddy', $communityContainers, true)) { + return gethostbyname('nextcloud-aio-caddy'); + } else { + return ''; + } + } elseif ($placeholder === 'WHITEBOARD_ENABLED') { + if ($this->configurationManager->isWhiteboardEnabled()) { + return 'yes'; + } else { + return ''; + } + } else { + $secret = $this->configurationManager->GetSecret($placeholder); + if ($secret === "") { + throw new \Exception("The secret " . $placeholder . " is empty. Cannot substitute its value. Please check if it is defined in secrets of containers.json."); + } + return $secret; + } + } + private function isContainerUpdateAvailable(string $id): string { $container = $this->containerDefinitionFetcher->GetContainerById($id); From 026707240f37568a614091f1d5d17f3822b48904 Mon Sep 17 00:00:00 2001 From: Alan Savage <3028205+asavageiv@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:14:31 -0700 Subject: [PATCH 2/7] Support multiple placeholders in ENV values in containers.json Signed-off-by: Alan Savage <3028205+asavageiv@users.noreply.github.com> --- php/src/Docker/DockerActionManager.php | 41 ++++++++++++++++---------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index e3c7456f..b43e7a8c 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -240,22 +240,7 @@ readonly class DockerActionManager { $envs[] = $this->GetAllNextcloudExecCommands(); } foreach ($envs as $key => $env) { - // TODO: This whole block below is a hack and needs to get reworked in order to support multiple substitutions per line by default for all envs - if (str_starts_with($env, 'extra_params=')) { - $env = str_replace('%COLLABORA_SECCOMP_POLICY%', $this->configurationManager->GetCollaboraSeccompPolicy(), $env); - $env = str_replace('%NC_DOMAIN%', $this->configurationManager->GetDomain(), $env); - $envs[$key] = $env; - continue; - } - - // Original implementation - $patterns = ['/%(.*)%/']; - - if (preg_match($patterns[0], $env, $out) === 1) { - $replacements = array(); - $replacements[1] = $this->getPlaceholderValue($out[1]); - $envs[$key] = preg_replace($patterns, $replacements, $env); - } + $envs[$key] = $this->replaceEnvPlaceholders($env); } if (count($envs) > 0) { @@ -488,6 +473,30 @@ readonly class DockerActionManager { } } + // Replaces placeholders in $envValue with their values. + // E.g. "%NC_DOMAIN%:%APACHE_PORT" becomes "my.nextcloud.com:11000" + private function replaceEnvPlaceholders($envValue) { + // $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) { + $placeholders = $matches[0]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...] + $placeholderNames = $matches[1]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...] + $placeholderToPattern = fn($placeholder) => '/' . $placeholder . '/'; + $placeholderPatterns = array_map($placeholderToPattern, $placeholders); // ["/%PLACEHOLDER1%/", ...] + $placeholderValues = array_map([$this, 'getPlaceholderValue'], $placeholderNames); // ["val1", "val2"] + $result = preg_replace($placeholderPatterns, $placeholderValues, $envValue); + return $result; + } + return $envValue; + } + private function getPlaceholderValue($placeholder) { if ($placeholder === 'NC_DOMAIN') { return $this->configurationManager->GetDomain(); From d374fd2c1c9788205e2d6b1635b9daa683e1f7b1 Mon Sep 17 00:00:00 2001 From: Alan Savage <3028205+asavageiv@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:49:42 -0700 Subject: [PATCH 3/7] Refactor getPlaceholderValue to use `match` Signed-off-by: Alan Savage <3028205+asavageiv@users.noreply.github.com> --- php/src/Docker/DockerActionManager.php | 204 +++++++------------------ 1 file changed, 51 insertions(+), 153 deletions(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index b43e7a8c..35da8663 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -498,162 +498,60 @@ readonly class DockerActionManager { } private function getPlaceholderValue($placeholder) { - if ($placeholder === 'NC_DOMAIN') { - return $this->configurationManager->GetDomain(); - } elseif ($placeholder === 'NC_BASE_DN') { - return $this->configurationManager->GetBaseDN(); - } elseif ($placeholder === 'AIO_TOKEN') { - return $this->configurationManager->GetToken(); - } elseif ($placeholder === 'BORGBACKUP_REMOTE_REPO') { - return $this->configurationManager->GetBorgRemoteRepo(); - } elseif ($placeholder === 'BORGBACKUP_MODE') { - return $this->configurationManager->GetBackupMode(); - } elseif ($placeholder === 'AIO_URL') { - return $this->configurationManager->GetAIOURL(); - } elseif ($placeholder === 'SELECTED_RESTORE_TIME') { - return $this->configurationManager->GetSelectedRestoreTime(); - } elseif ($placeholder === 'RESTORE_EXCLUDE_PREVIEWS') { - return $this->configurationManager->GetRestoreExcludePreviews(); - } elseif ($placeholder === 'APACHE_PORT') { - return $this->configurationManager->GetApachePort(); - } elseif ($placeholder === 'TALK_PORT') { - return $this->configurationManager->GetTalkPort(); - } elseif ($placeholder === 'NEXTCLOUD_MOUNT') { - return $this->configurationManager->GetNextcloudMount(); - } elseif ($placeholder === 'BACKUP_RESTORE_PASSWORD') { - return $this->configurationManager->GetBorgRestorePassword(); - } elseif ($placeholder === 'CLAMAV_ENABLED') { - if ($this->configurationManager->isClamavEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'TALK_RECORDING_ENABLED') { - if ($this->configurationManager->isTalkRecordingEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'ONLYOFFICE_ENABLED') { - if ($this->configurationManager->isOnlyofficeEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'COLLABORA_ENABLED') { - if ($this->configurationManager->isCollaboraEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'TALK_ENABLED') { - if ($this->configurationManager->isTalkEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'UPDATE_NEXTCLOUD_APPS') { - if ($this->configurationManager->isDailyBackupRunning() && $this->configurationManager->areAutomaticUpdatesEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'TIMEZONE') { - if ($this->configurationManager->GetTimezone() === '') { - return 'Etc/UTC'; - } else { - return $this->configurationManager->GetTimezone(); - } - } elseif ($placeholder === 'COLLABORA_DICTIONARIES') { - if ($this->configurationManager->GetCollaboraDictionaries() === '') { - return 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru'; - } else { - return $this->configurationManager->GetCollaboraDictionaries(); - } - } elseif ($placeholder === 'IMAGINARY_ENABLED') { - if ($this->configurationManager->isImaginaryEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'FULLTEXTSEARCH_ENABLED') { - if ($this->configurationManager->isFulltextsearchEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'DOCKER_SOCKET_PROXY_ENABLED') { - if ($this->configurationManager->isDockerSocketProxyEnabled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'NEXTCLOUD_UPLOAD_LIMIT') { - return $this->configurationManager->GetNextcloudUploadLimit(); - } elseif ($placeholder === 'NEXTCLOUD_MEMORY_LIMIT') { - return $this->configurationManager->GetNextcloudMemoryLimit(); - } elseif ($placeholder === 'NEXTCLOUD_MAX_TIME') { - return $this->configurationManager->GetNextcloudMaxTime(); - } elseif ($placeholder === 'BORG_RETENTION_POLICY') { - return $this->configurationManager->GetBorgRetentionPolicy(); - } elseif ($placeholder === 'FULLTEXTSEARCH_JAVA_OPTIONS') { - return $this->configurationManager->GetFulltextsearchJavaOptions(); - } elseif ($placeholder === 'NEXTCLOUD_TRUSTED_CACERTS_DIR') { - return $this->configurationManager->GetTrustedCacertsDir(); - } elseif ($placeholder === 'ADDITIONAL_DIRECTORIES_BACKUP') { - if ($this->configurationManager->GetAdditionalBackupDirectoriesString() !== '') { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'BORGBACKUP_HOST_LOCATION') { - return $this->configurationManager->GetBorgBackupHostLocation(); - } elseif ($placeholder === 'APACHE_MAX_SIZE') { - return $this->configurationManager->GetApacheMaxSize(); - } elseif ($placeholder === 'COLLABORA_SECCOMP_POLICY') { - return $this->configurationManager->GetCollaboraSeccompPolicy(); - } elseif ($placeholder === 'NEXTCLOUD_STARTUP_APPS') { - return $this->configurationManager->GetNextcloudStartupApps(); - } elseif ($placeholder === 'NEXTCLOUD_ADDITIONAL_APKS') { - return $this->configurationManager->GetNextcloudAdditionalApks(); - } elseif ($placeholder === 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS') { - return $this->configurationManager->GetNextcloudAdditionalPhpExtensions(); - } elseif ($placeholder === 'INSTALL_LATEST_MAJOR') { - if ($this->configurationManager->shouldLatestMajorGetInstalled()) { - return 'yes'; - } else { - return ''; - } - } elseif ($placeholder === 'REMOVE_DISABLED_APPS') { - if ($this->configurationManager->shouldDisabledAppsGetRemoved()) { - return 'yes'; - } else { - return ''; - } + 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(), + 'TALK_PORT' => $this->configurationManager->GetTalkPort(), + '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' => $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) - } elseif ($placeholder === 'AIO_DATABASE_HOST') { - return gethostbyname('nextcloud-aio-database'); + 'AIO_DATABASE_HOST' => gethostbyname('nextcloud-aio-database'), // Allow to get local ip-address of caddy container and add it to trusted proxies automatically - } elseif ($placeholder === 'CADDY_IP_ADDRESS') { - $communityContainers = $this->configurationManager->GetEnabledCommunityContainers(); - if (in_array('caddy', $communityContainers, true)) { - return gethostbyname('nextcloud-aio-caddy'); - } else { - return ''; - } - } elseif ($placeholder === 'WHITEBOARD_ENABLED') { - if ($this->configurationManager->isWhiteboardEnabled()) { - return 'yes'; - } else { - return ''; - } - } else { - $secret = $this->configurationManager->GetSecret($placeholder); - if ($secret === "") { - throw new \Exception("The secret " . $placeholder . " is empty. Cannot substitute its value. Please check if it is defined in secrets of containers.json."); - } - return $secret; + 'CADDY_IP_ADDRESS' => in_array('caddy', $this->configurationManager->GetEnabledCommunityContainers(), true) ? gethostbyname('nextcloud-aio-caddy') : '', + 'WHITEBOARD_ENABLED' => $this->configurationManager->isWhiteboardEnabled() ? 'yes' : '', + default => $this->getSecretOrThrow($placeholder), + }; + } + + private function getSecretOrThrow($secretName) { + $secret = $this->configurationManager->GetSecret($secretName); + if ($secret === "") { + throw new \Exception("The secret " . $secretName . " is empty. Cannot substitute its value. Please check if it is defined in secrets of containers.json."); } + return $secret; } private function isContainerUpdateAvailable(string $id): string { From 0f858dc3fe3738d5d5c0c93053197debb0cfc5c8 Mon Sep 17 00:00:00 2001 From: Alan Savage <3028205+asavageiv@users.noreply.github.com> Date: Fri, 20 Jun 2025 20:32:26 +0000 Subject: [PATCH 4/7] Fix psalm errors in DockerActionManager env handling code Signed-off-by: Alan Savage <3028205+asavageiv@users.noreply.github.com> --- php/src/Docker/DockerActionManager.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 35da8663..5e113072 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -475,7 +475,7 @@ readonly class DockerActionManager { // Replaces placeholders in $envValue with their values. // E.g. "%NC_DOMAIN%:%APACHE_PORT" becomes "my.nextcloud.com:11000" - private function replaceEnvPlaceholders($envValue) { + 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 @@ -488,16 +488,17 @@ readonly class DockerActionManager { if ($matchCount > 0) { $placeholders = $matches[0]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...] $placeholderNames = $matches[1]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...] - $placeholderToPattern = fn($placeholder) => '/' . $placeholder . '/'; + $placeholderToPattern = fn(string $p): string => '/' . $p . '/'; $placeholderPatterns = array_map($placeholderToPattern, $placeholders); // ["/%PLACEHOLDER1%/", ...] $placeholderValues = array_map([$this, 'getPlaceholderValue'], $placeholderNames); // ["val1", "val2"] - $result = preg_replace($placeholderPatterns, $placeholderValues, $envValue); + // Guaranteed to be non-null because we found the placeholders in the preg_match_all. + $result = (string) preg_replace($placeholderPatterns, $placeholderValues, $envValue); return $result; } return $envValue; } - private function getPlaceholderValue($placeholder) { + private function getPlaceholderValue(string $placeholder) : string { return match ($placeholder) { 'NC_DOMAIN' => $this->configurationManager->GetDomain(), 'NC_BASE_DN' => $this->configurationManager->GetBaseDN(), @@ -530,7 +531,7 @@ readonly class DockerActionManager { 'NEXTCLOUD_TRUSTED_CACERTS_DIR' => $this->configurationManager->GetTrustedCacertsDir(), 'ADDITIONAL_DIRECTORIES_BACKUP' => $this->configurationManager->GetAdditionalBackupDirectoriesString() !== '' ? 'yes' : '', 'BORGBACKUP_HOST_LOCATION' => $this->configurationManager->GetBorgBackupHostLocation(), - 'APACHE_MAX_SIZE' => $this->configurationManager->GetApacheMaxSize(), + '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(), @@ -546,7 +547,7 @@ readonly class DockerActionManager { }; } - private function getSecretOrThrow($secretName) { + private function getSecretOrThrow(string $secretName): string { $secret = $this->configurationManager->GetSecret($secretName); if ($secret === "") { throw new \Exception("The secret " . $secretName . " is empty. Cannot substitute its value. Please check if it is defined in secrets of containers.json."); From 0b929d74de9b42ddc0c3e9ad1cd06ea8473aa3a0 Mon Sep 17 00:00:00 2001 From: Alan Savage <3028205+asavageiv@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:57:34 +0000 Subject: [PATCH 5/7] Use guard clause in replaceEnvPlaceholders to reduce indentation Signed-off-by: Alan Savage <3028205+asavageiv@users.noreply.github.com> --- php/src/Docker/DockerActionManager.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 5e113072..3667294c 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -485,17 +485,18 @@ readonly class DockerActionManager { // escaping. $pattern = '/%([^%]+)%/'; $matchCount = preg_match_all($pattern, $envValue, $matches); - if ($matchCount > 0) { - $placeholders = $matches[0]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...] - $placeholderNames = $matches[1]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...] - $placeholderToPattern = fn(string $p): string => '/' . $p . '/'; - $placeholderPatterns = array_map($placeholderToPattern, $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. - $result = (string) preg_replace($placeholderPatterns, $placeholderValues, $envValue); - return $result; + + if ($matchCount === 0) { + return $envValue; } - return $envValue; + + $placeholders = $matches[0]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...] + $placeholderNames = $matches[1]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...] + $placeholderToPattern = fn(string $p): string => '/' . $p . '/'; + $placeholderPatterns = array_map($placeholderToPattern, $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 { From f81d22cf930b5cc6bdf25b8dea9f608179912cc3 Mon Sep 17 00:00:00 2001 From: Alan Savage <3028205+asavageiv@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:05:19 +0000 Subject: [PATCH 6/7] Inline placeholderToPattern and use preg_quote Signed-off-by: Alan Savage <3028205+asavageiv@users.noreply.github.com> --- php/src/Docker/DockerActionManager.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 3667294c..8072b2fc 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -492,8 +492,7 @@ readonly class DockerActionManager { $placeholders = $matches[0]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...] $placeholderNames = $matches[1]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...] - $placeholderToPattern = fn(string $p): string => '/' . $p . '/'; - $placeholderPatterns = array_map($placeholderToPattern, $placeholders); // ["/%PLACEHOLDER1%/", ...] + $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); From f7d158c6322571116bd4d62d36b6706da1af560e Mon Sep 17 00:00:00 2001 From: Alan Savage <3028205+asavageiv@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:05:43 +0000 Subject: [PATCH 7/7] Use modern callable syntax for $placeholderValues array_map Signed-off-by: Alan Savage <3028205+asavageiv@users.noreply.github.com> --- php/src/Docker/DockerActionManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 8072b2fc..0ccdcf58 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -493,7 +493,7 @@ readonly class DockerActionManager { $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"] + $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); }