diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..0350cecc
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,8 @@
+
+
+* Resolves: #
+* [Sign-off message](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md) is added to all commits
diff --git a/.github/workflows/collabora.yml b/.github/workflows/collabora.yml
index 81ea8ff1..a61067f3 100644
--- a/.github/workflows/collabora.yml
+++ b/.github/workflows/collabora.yml
@@ -18,7 +18,7 @@ jobs:
mv cool-seccomp-profile.json php/
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: collabora-seccomp-update automated change
signoff: true
diff --git a/.github/workflows/dependency-updates.yml b/.github/workflows/dependency-updates.yml
index 7bdc5d1a..3805a0d0 100644
--- a/.github/workflows/dependency-updates.yml
+++ b/.github/workflows/dependency-updates.yml
@@ -44,7 +44,7 @@ jobs:
)"
sed -i "s|pecl install APCu.*\;|pecl install APCu-$apcu_version\;|" ./Containers/mastercontainer/Dockerfile
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: php dependency updates
signoff: true
diff --git a/.github/workflows/imaginary-update.yml b/.github/workflows/imaginary-update.yml
index 171fb132..05050a20 100644
--- a/.github/workflows/imaginary-update.yml
+++ b/.github/workflows/imaginary-update.yml
@@ -22,7 +22,7 @@ jobs:
sed -i "s|^ENV IMAGINARY_HASH.*$|ENV IMAGINARY_HASH=$imaginary_version|" ./Containers/imaginary/Dockerfile
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: imaginary-update automated change
signoff: true
diff --git a/.github/workflows/nextcloud-update.yml b/.github/workflows/nextcloud-update.yml
index 5b420c20..b2475290 100644
--- a/.github/workflows/nextcloud-update.yml
+++ b/.github/workflows/nextcloud-update.yml
@@ -79,7 +79,7 @@ jobs:
fi
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: nextcloud-update automated change
signoff: true
diff --git a/.github/workflows/psalm-update-baseline.yml b/.github/workflows/psalm-update-baseline.yml
index 0c2f8aee..bcbb12c3 100644
--- a/.github/workflows/psalm-update-baseline.yml
+++ b/.github/workflows/psalm-update-baseline.yml
@@ -30,7 +30,7 @@ jobs:
continue-on-error: true
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
commit-message: Update psalm baseline
diff --git a/.github/workflows/talk.yml b/.github/workflows/talk.yml
index 28f9fef7..b19e1cb5 100644
--- a/.github/workflows/talk.yml
+++ b/.github/workflows/talk.yml
@@ -45,7 +45,7 @@ jobs:
sed -i "s|^ARG JANUS_VERSION=.*$|ARG JANUS_VERSION=$janus_version|" ./Containers/talk/Dockerfile
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: talk-update automated change
signoff: true
diff --git a/.github/workflows/update-helm.yml b/.github/workflows/update-helm.yml
index 2dcd2e73..92cbb978 100644
--- a/.github/workflows/update-helm.yml
+++ b/.github/workflows/update-helm.yml
@@ -23,7 +23,7 @@ jobs:
sudo bash nextcloud-aio-helm-chart/update-helm.sh "$DOCKER_TAG"
fi
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: Helm Chart updates
signoff: true
diff --git a/.github/workflows/update-yaml.yml b/.github/workflows/update-yaml.yml
index a60ea1c6..6e150261 100644
--- a/.github/workflows/update-yaml.yml
+++ b/.github/workflows/update-yaml.yml
@@ -16,7 +16,7 @@ jobs:
run: |
sudo bash manual-install/update-yaml.sh
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: Yaml updates
signoff: true
diff --git a/.github/workflows/watchtower-update.yml b/.github/workflows/watchtower-update.yml
index c04657be..ecd82a69 100644
--- a/.github/workflows/watchtower-update.yml
+++ b/.github/workflows/watchtower-update.yml
@@ -26,7 +26,7 @@ jobs:
sed -i "s|\$WATCHTOWER_COMMIT_HASH.*$|\$WATCHTOWER_COMMIT_HASH # $watchtower_version|" ./Containers/watchtower/Dockerfile
- name: Create Pull Request
- uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
with:
commit-message: watchtower-update automated change
signoff: true
diff --git a/Containers/alpine/Dockerfile b/Containers/alpine/Dockerfile
index 718c5510..1098b4c4 100644
--- a/Containers/alpine/Dockerfile
+++ b/Containers/alpine/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:latest
-FROM alpine:3.23.2
+FROM alpine:3.23.3
RUN set -ex; \
apk upgrade --no-cache -a
diff --git a/Containers/borgbackup/Dockerfile b/Containers/borgbackup/Dockerfile
index 97d6198b..6e3180cb 100644
--- a/Containers/borgbackup/Dockerfile
+++ b/Containers/borgbackup/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:latest
-FROM alpine:3.23.2
+FROM alpine:3.23.3
RUN set -ex; \
\
diff --git a/Containers/clamav/Dockerfile b/Containers/clamav/Dockerfile
index e81fb06e..6910ae1c 100644
--- a/Containers/clamav/Dockerfile
+++ b/Containers/clamav/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:latest
-FROM alpine:3.23.2
+FROM alpine:3.23.3
RUN set -ex; \
apk upgrade --no-cache -a; \
diff --git a/Containers/collabora/Dockerfile b/Containers/collabora/Dockerfile
index 976360cb..d1693da0 100644
--- a/Containers/collabora/Dockerfile
+++ b/Containers/collabora/Dockerfile
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:latest
# From a file located probably somewhere here: https://github.com/CollaboraOnline/online/blob/master/docker/from-packages/Dockerfile
-FROM collabora/code:25.04.8.1.1
+FROM collabora/code:25.04.8.2.1
USER root
ARG DEBIAN_FRONTEND=noninteractive
diff --git a/Containers/domaincheck/Dockerfile b/Containers/domaincheck/Dockerfile
index 8122f315..374aba4a 100644
--- a/Containers/domaincheck/Dockerfile
+++ b/Containers/domaincheck/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:latest
-FROM alpine:3.23.2
+FROM alpine:3.23.3
RUN set -ex; \
apk upgrade --no-cache -a; \
apk add --no-cache bash lighttpd netcat-openbsd; \
diff --git a/Containers/imaginary/Dockerfile b/Containers/imaginary/Dockerfile
index 650c4c67..b108ac18 100644
--- a/Containers/imaginary/Dockerfile
+++ b/Containers/imaginary/Dockerfile
@@ -14,7 +14,7 @@ RUN set -ex; \
build-base; \
go install github.com/h2non/imaginary@"$IMAGINARY_HASH";
-FROM alpine:3.23.2
+FROM alpine:3.23.3
RUN set -ex; \
apk upgrade --no-cache -a; \
apk add --no-cache \
diff --git a/Containers/nextcloud/entrypoint.sh b/Containers/nextcloud/entrypoint.sh
index 5f47a0f4..d4b4f253 100644
--- a/Containers/nextcloud/entrypoint.sh
+++ b/Containers/nextcloud/entrypoint.sh
@@ -182,8 +182,11 @@ if ! [ -f "$NEXTCLOUD_DATA_DIR/skip.update" ]; then
curl -fsSL -o nextcloud.tar.bz2.asc "https://download.nextcloud.com/server/releases/latest-${NEXT_MAJOR}.tar.bz2.asc"
GNUPGHOME="$(mktemp -d)"
export GNUPGHOME
- # gpg key from https://nextcloud.com/nextcloud.asc
- gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 28806A878AE423A28372792ED75899B9A724937A
+ if ! gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 28806A878AE423A28372792ED75899B9A724937A; then
+ if ! gpg --batch --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 28806A878AE423A28372792ED75899B9A724937A; then
+ curl -sSL https://nextcloud.com/nextcloud.asc | gpg --import
+ fi
+ fi
gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2
mkdir -p /usr/src/tmp
tar -xjf nextcloud.tar.bz2 -C /usr/src/tmp/
diff --git a/Containers/notify-push/Dockerfile b/Containers/notify-push/Dockerfile
index 425115c4..838c847c 100644
--- a/Containers/notify-push/Dockerfile
+++ b/Containers/notify-push/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:latest
-FROM alpine:3.23.2
+FROM alpine:3.23.3
COPY --chmod=775 start.sh /start.sh
COPY --chmod=775 healthcheck.sh /healthcheck.sh
diff --git a/Containers/talk/Dockerfile b/Containers/talk/Dockerfile
index fb78f943..e8d3d72f 100644
--- a/Containers/talk/Dockerfile
+++ b/Containers/talk/Dockerfile
@@ -1,8 +1,8 @@
# syntax=docker/dockerfile:latest
-FROM nats:2.12.3-scratch AS nats
+FROM nats:2.12.4-scratch AS nats
FROM eturnal/eturnal:1.12.2-alpine AS eturnal
FROM strukturag/nextcloud-spreed-signaling:2.0.4 AS signaling
-FROM alpine:3.23.2 AS janus
+FROM alpine:3.23.3 AS janus
ARG JANUS_VERSION=v1.3.3
WORKDIR /src
@@ -35,7 +35,7 @@ RUN set -ex; \
make configs; \
rename -v ".jcfg.sample" ".jcfg" /usr/local/etc/janus/*.jcfg.sample
-FROM alpine:3.23.2
+FROM alpine:3.23.3
ENV ETURNAL_ETC_DIR="/conf"
ENV SKIP_CERT_VERIFY=false
COPY --from=janus --chmod=777 --chown=1000:1000 /usr/local /usr/local
diff --git a/Containers/watchtower/Dockerfile b/Containers/watchtower/Dockerfile
index 83bccc07..fc9ea093 100644
--- a/Containers/watchtower/Dockerfile
+++ b/Containers/watchtower/Dockerfile
@@ -9,7 +9,7 @@ RUN set -ex; \
build-base; \
go install github.com/nicholas-fedor/watchtower@$WATCHTOWER_COMMIT_HASH # v1.14.0
-FROM alpine:3.23.2
+FROM alpine:3.23.3
RUN set -ex; \
apk upgrade --no-cache -a; \
diff --git a/manual-install/update-yaml.sh b/manual-install/update-yaml.sh
index af746aee..928275da 100644
--- a/manual-install/update-yaml.sh
+++ b/manual-install/update-yaml.sh
@@ -47,6 +47,7 @@ sed -i '/AIO_URL/d' containers.yml
sed -i '/DOCKER_SOCKET_PROXY_ENABLED/d' containers.yml
sed -i '/ADDITIONAL_TRUSTED_PROXY/d' containers.yml
sed -i '/TURN_DOMAIN/d' containers.yml
+sed -i '/NC_AIO_VERSION/d' containers.yml
TCP="$(grep -oP '[%A-Z0-9_]+/tcp' containers.yml | sort -u)"
mapfile -t TCP <<< "$TCP"
diff --git a/php/composer.lock b/php/composer.lock
index ee344d52..77403624 100644
--- a/php/composer.lock
+++ b/php/composer.lock
@@ -4058,16 +4058,16 @@
},
{
"name": "symfony/finder",
- "version": "v6.4.32",
+ "version": "v6.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "3ec24885c1d9ababbb9c8f63bb42fea3c8c9b6de"
+ "reference": "24965ca011dac87431729640feef8bcf7b5523e0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/3ec24885c1d9ababbb9c8f63bb42fea3c8c9b6de",
- "reference": "3ec24885c1d9ababbb9c8f63bb42fea3c8c9b6de",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/24965ca011dac87431729640feef8bcf7b5523e0",
+ "reference": "24965ca011dac87431729640feef8bcf7b5523e0",
"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.32"
+ "source": "https://github.com/symfony/finder/tree/v6.4.33"
},
"funding": [
{
@@ -4122,7 +4122,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-10T14:09:00+00:00"
+ "time": "2026-01-26T13:03:48+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
diff --git a/php/containers.json b/php/containers.json
index 8c507f91..8e9218ac 100644
--- a/php/containers.json
+++ b/php/containers.json
@@ -219,6 +219,7 @@
"SIGNALING_SECRET=%SIGNALING_SECRET%",
"ONLYOFFICE_SECRET=%ONLYOFFICE_SECRET%",
"AIO_URL=%AIO_URL%",
+ "NC_AIO_VERSION=v%AIO_VERSION%",
"NEXTCLOUD_MOUNT=%NEXTCLOUD_MOUNT%",
"CLAMAV_ENABLED=%CLAMAV_ENABLED%",
"CLAMAV_HOST=nextcloud-aio-clamav",
diff --git a/php/get-configurable-aio-variables.sh b/php/get-configurable-aio-variables.sh
new file mode 100755
index 00000000..3093e1e0
--- /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 }' 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/containers-form-submit.js b/php/public/containers-form-submit.js
index b7ffd2d8..1382bced 100644
--- a/php/public/containers-form-submit.js
+++ b/php/public/containers-form-submit.js
@@ -1,7 +1,9 @@
document.addEventListener("DOMContentLoaded", function () {
// Hide submit button initially
- const optionsFormSubmit = document.getElementById("options-form-submit");
- optionsFormSubmit.style.display = 'none';
+ const optionsFormSubmit = document.querySelectorAll(".options-form-submit");
+ optionsFormSubmit.forEach(element => {
+ element.style.display = 'none';
+ });
const communityFormSubmit = document.getElementById("community-form-submit");
communityFormSubmit.style.display = 'none';
@@ -12,6 +14,14 @@ document.addEventListener("DOMContentLoaded", function () {
const optionsContainersCheckboxes = document.querySelectorAll("#options-form input[type='checkbox']");
const communityContainersCheckboxes = document.querySelectorAll("#community-form input[type='checkbox']");
+ // Office suite radio buttons
+ const collaboraRadio = document.getElementById('office-collabora');
+ const onlyofficeRadio = document.getElementById('office-onlyoffice');
+ const noneRadio = document.getElementById('office-none');
+ const collaboraHidden = document.getElementById('collabora');
+ const onlyofficeHidden = document.getElementById('onlyoffice');
+ let initialOfficeSelection = null;
+
optionsContainersCheckboxes.forEach(checkbox => {
initialStateOptionsContainers[checkbox.id] = checkbox.checked; // Use checked property to capture actual initial state
});
@@ -20,6 +30,17 @@ document.addEventListener("DOMContentLoaded", function () {
initialStateCommunityContainers[checkbox.id] = checkbox.checked; // Use checked property to capture actual initial state
});
+ // Store initial office suite selection
+ if (collaboraRadio && onlyofficeRadio && noneRadio) {
+ if (collaboraRadio.checked) {
+ initialOfficeSelection = 'collabora';
+ } else if (onlyofficeRadio.checked) {
+ initialOfficeSelection = 'onlyoffice';
+ } else {
+ initialOfficeSelection = 'none';
+ }
+ }
+
// Function to compare current states to initial states
function checkForOptionContainerChanges() {
let hasChanges = false;
@@ -30,8 +51,32 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
+ // Check office suite changes and sync to hidden inputs
+ if (collaboraRadio && onlyofficeRadio && noneRadio && collaboraHidden && onlyofficeHidden) {
+ let currentOfficeSelection = null;
+ if (collaboraRadio.checked) {
+ currentOfficeSelection = 'collabora';
+ collaboraHidden.value = 'on';
+ onlyofficeHidden.value = '';
+ } else if (onlyofficeRadio.checked) {
+ currentOfficeSelection = 'onlyoffice';
+ collaboraHidden.value = '';
+ onlyofficeHidden.value = 'on';
+ } else {
+ currentOfficeSelection = 'none';
+ collaboraHidden.value = '';
+ onlyofficeHidden.value = '';
+ }
+
+ if (currentOfficeSelection !== initialOfficeSelection) {
+ hasChanges = true;
+ }
+ }
+
// Show or hide submit button based on changes
- optionsFormSubmit.style.display = hasChanges ? 'block' : 'none';
+ optionsFormSubmit.forEach(element => {
+ element.style.display = hasChanges ? 'block' : 'none';
+ });
}
// Function to compare current states to initial states
@@ -82,6 +127,13 @@ document.addEventListener("DOMContentLoaded", function () {
// Initialize talk-recording visibility on page load
handleTalkVisibility(); // Ensure talk-recording is correctly initialized
+ // Add event listeners for office suite radio buttons
+ if (collaboraRadio && onlyofficeRadio && noneRadio) {
+ collaboraRadio.addEventListener('change', checkForOptionContainerChanges);
+ onlyofficeRadio.addEventListener('change', checkForOptionContainerChanges);
+ noneRadio.addEventListener('change', checkForOptionContainerChanges);
+ }
+
// Initial call to check for changes
checkForOptionContainerChanges();
checkForCommunityContainerChanges();
diff --git a/php/public/disable-collabora.js b/php/public/disable-collabora.js
index 3064ef51..762252ce 100644
--- a/php/public/disable-collabora.js
+++ b/php/public/disable-collabora.js
@@ -1,5 +1,5 @@
document.addEventListener("DOMContentLoaded", function(event) {
// Collabora
- let collabora = document.getElementById("collabora");
+ const collabora = document.getElementById("office-collabora");
collabora.disabled = true;
});
\ No newline at end of file
diff --git a/php/public/disable-onlyoffice.js b/php/public/disable-onlyoffice.js
index 83482339..c660bd9d 100644
--- a/php/public/disable-onlyoffice.js
+++ b/php/public/disable-onlyoffice.js
@@ -1,7 +1,5 @@
document.addEventListener("DOMContentLoaded", function(event) {
// OnlyOffice
- let onlyoffice = document.getElementById("onlyoffice");
- if (onlyoffice) {
- onlyoffice.disabled = true;
- }
+ const onlyoffice = document.getElementById("office-onlyoffice");
+ onlyoffice.disabled = true;
});
\ No newline at end of file
diff --git a/php/public/index.php b/php/public/index.php
index b57f65a5..cc06bb90 100644
--- a/php/public/index.php
+++ b/php/public/index.php
@@ -91,54 +91,54 @@ $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(),
- 'borg_public_key' => $configurationManager->GetBorgPublicKey(),
- 'nextcloud_password' => $configurationManager->GetAndGenerateSecret('NEXTCLOUD_PASSWORD'),
+ '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(),
- 'borgbackup_password' => $configurationManager->GetAndGenerateSecret('BORGBACKUP_PASSWORD'),
+ 'borgbackup_password' => $configurationManager->getAndGenerateSecret('BORGBACKUP_PASSWORD'),
'is_mastercontainer_update_available' => ( $bypass_mastercontainer_update ? false : $dockerActionManager->IsMastercontainerUpdateAvailable() ),
'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(),
+ '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(),
- 'daily_backup_time' => $configurationManager->GetDailyBackupTime(),
+ '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(),
- '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(),
+ 'is_backup_section_enabled' => !$configurationManager->disableBackupSection,
+ 'is_imaginary_enabled' => $configurationManager->isImaginaryEnabled,
+ 'is_fulltextsearch_enabled' => $configurationManager->isFulltextsearchEnabled,
+ 'additional_backup_directories' => $configurationManager->getAdditionalBackupDirectoriesString(),
+ '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/public/style.css b/php/public/style.css
index b4d5f8a5..b35883d0 100644
--- a/php/public/style.css
+++ b/php/public/style.css
@@ -28,7 +28,7 @@
--border-radius-large: 12px;
--default-font-size: 13px;
--checkbox-size: 16px;
- --max-width: 500px;
+ --max-width: 580px;
--container-top-margin: 20px;
--container-bottom-margin: 20px;
--container-padding: 2px;
@@ -37,9 +37,9 @@
--main-padding: 50px;
}
-/* Breakpoint calculation: 500px (max-width) + 100px (main-padding * 2) + 200px (additional space) = 800px
+/* Breakpoint calculation: 580px (max-width) + 100px (main-padding * 2) + 200px (additional space) = 880px
Note: Unfortunately, it's not possible to calculate this dynamically using CSS variables in media queries */
-@media only screen and (max-width: 800px) {
+@media only screen and (max-width: 880px) {
:root {
--container-top-margin: 50px;
--container-bottom-margin: 0px;
@@ -549,3 +549,160 @@ input[type="checkbox"]:disabled:not(:checked) + label {
#theme-toggle:not(:hover) #theme-icon {
opacity: 0.6; /* Slightly transparent */
}
+/* Office Suite Feature Cards */
+.office-suite-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 16px;
+ margin: 20px 0;
+ align-items: stretch;
+}
+
+.office-radio {
+ display: none;
+}
+
+.office-card {
+ position: relative;
+ border: 2px solid var(--color-border-maxcontrast);
+ border-radius: var(--border-radius-large);
+ padding: 20px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ background-color: var(--color-main-background);
+ display: flex;
+ flex-direction: column;
+}
+
+.office-card-disabled {
+ opacity: 50%;
+ pointer-events: none;
+}
+
+.office-card:hover {
+ border-color: var(--color-primary-element);
+ box-shadow: 0 4px 12px rgba(0, 130, 201, 0.15);
+ transform: translateY(-2px);
+}
+
+#office-collabora:checked + .office-card,
+#office-onlyoffice:checked + .office-card {
+ border-color: var(--color-nextcloud-blue);
+ background: linear-gradient(135deg, rgba(0, 130, 201, 0.08) 0%, rgba(0, 130, 201, 0.02) 100%);
+}
+
+[data-theme="dark"] #office-collabora:checked + .office-card,
+[data-theme="dark"] #office-onlyoffice:checked + .office-card {
+ background: linear-gradient(135deg, rgba(0, 145, 242, 0.15) 0%, rgba(0, 145, 242, 0.03) 100%);
+}
+
+.office-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.office-card h4 {
+ margin: 0;
+ height: 24px;
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--color-main-text);
+}
+
+.office-checkmark {
+ flex-shrink: 0;
+ display: none;
+}
+
+#office-collabora:checked + .office-card .office-checkmark,
+#office-onlyoffice:checked + .office-card .office-checkmark {
+ display: block;
+}
+
+.office-features {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.office-features li {
+ position: relative;
+ padding-left: 20px;
+ margin-bottom: 4px;
+ font-size: var(--default-font-size);
+ line-height: 1.5;
+ color: var(--color-main-text);
+}
+
+.office-features li::before {
+ content: '•';
+ position: absolute;
+ left: 6px;
+ color: var(--color-nextcloud-blue);
+ font-weight: bold;
+}
+
+.office-checkbox {
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.office-learn-more {
+ display: inline-flex;
+ align-items: center;
+ margin-top: 12px;
+ color: var(--color-primary-element);
+ text-decoration: none;
+ font-size: var(--default-font-size);
+ font-weight: 500;
+ transition: color 0.2s ease;
+}
+
+.office-learn-more:hover {
+ color: var(--color-primary-element-hover);
+}
+
+.office-learn-more svg {
+ transition: transform 0.2s ease;
+}
+
+.office-learn-more:hover svg {
+ transform: translateX(3px);
+}
+
+.office-none-card {
+ text-align: center;
+ margin: 12px 0 20px 0;
+}
+
+.office-none-label {
+ display: inline-flex;
+ align-items: center;
+ font-size: 13px;
+ color: var(--color-primary-element);
+ cursor: pointer;
+ opacity: 0.7;
+ transition: opacity 0.2s ease;
+ padding: 8px 12px;
+ border-radius: var(--border-radius);
+}
+
+.office-none-label:hover {
+ opacity: 1;
+ background-color: var(--color-primary-element-light);
+}
+
+#office-none:checked + .office-none-label {
+ opacity: 1;
+ font-weight: 600;
+}
+
+/* Responsive adjustments for mobile */
+@media only screen and (max-width: 800px) {
+ .office-suite-cards {
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
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..d2519ed7 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;
}
}
@@ -246,7 +246,7 @@ readonly class ContainerDefinitionFetcher {
// All secrets are registered with the configuration when they
// are discovered so they can be later generated at time-of-use.
foreach ($entry['secrets'] as $secret) {
- $this->configurationManager->RegisterSecret($secret);
+ $this->configurationManager->registerSecret($secret);
}
}
diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php
index 45586f9c..bb55e10f 100644
--- a/php/src/Controller/ConfigurationController.php
+++ b/php/src/Controller/ConfigurationController.php
@@ -20,26 +20,26 @@ readonly class ConfigurationController {
if (isset($request->getParsedBody()['domain'])) {
$domain = $request->getParsedBody()['domain'] ?? '';
$skipDomainValidation = isset($request->getParsedBody()['skip_domain_validation']);
- $this->configurationManager->SetDomain($domain, $skipDomainValidation);
+ $this->configurationManager->setDomain($domain, $skipDomainValidation);
}
if (isset($request->getParsedBody()['current-master-password']) || isset($request->getParsedBody()['new-master-password'])) {
$currentMasterPassword = $request->getParsedBody()['current-master-password'] ?? '';
$newMasterPassword = $request->getParsedBody()['new-master-password'] ?? '';
- $this->configurationManager->ChangeMasterPassword($currentMasterPassword, $newMasterPassword);
+ $this->configurationManager->changeMasterPassword($currentMasterPassword, $newMasterPassword);
}
if (isset($request->getParsedBody()['borg_backup_host_location']) || isset($request->getParsedBody()['borg_remote_repo'])) {
$location = $request->getParsedBody()['borg_backup_host_location'] ?? '';
$borgRemoteRepo = $request->getParsedBody()['borg_remote_repo'] ?? '';
- $this->configurationManager->SetBorgLocationVars($location, $borgRemoteRepo);
+ $this->configurationManager->setBorgLocationVars($location, $borgRemoteRepo);
}
if (isset($request->getParsedBody()['borg_restore_host_location']) || isset($request->getParsedBody()['borg_restore_remote_repo']) || isset($request->getParsedBody()['borg_restore_password'])) {
$restoreLocation = $request->getParsedBody()['borg_restore_host_location'] ?? '';
$borgRemoteRepo = $request->getParsedBody()['borg_restore_remote_repo'] ?? '';
$borgPassword = $request->getParsedBody()['borg_restore_password'] ?? '';
- $this->configurationManager->SetBorgRestoreLocationVarsAndPassword($restoreLocation, $borgRemoteRepo, $borgPassword);
+ $this->configurationManager->setBorgRestoreLocationVarsAndPassword($restoreLocation, $borgRemoteRepo, $borgPassword);
}
if (isset($request->getParsedBody()['daily_backup_time'])) {
@@ -54,76 +54,47 @@ readonly class ConfigurationController {
$successNotification = false;
}
$dailyBackupTime = $request->getParsedBody()['daily_backup_time'] ?? '';
- $this->configurationManager->SetDailyBackupTime($dailyBackupTime, $enableAutomaticUpdates, $successNotification);
+ $this->configurationManager->setDailyBackupTime($dailyBackupTime, $enableAutomaticUpdates, $successNotification);
}
if (isset($request->getParsedBody()['delete_daily_backup_time'])) {
- $this->configurationManager->DeleteDailyBackupTime();
+ $this->configurationManager->deleteDailyBackupTime();
}
if (isset($request->getParsedBody()['additional_backup_directories'])) {
$additionalBackupDirectories = $request->getParsedBody()['additional_backup_directories'] ?? '';
- $this->configurationManager->SetAdditionalBackupDirectories($additionalBackupDirectories);
+ $this->configurationManager->setAdditionalBackupDirectories($additionalBackupDirectories);
}
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);
+ $officeSuiteChoice = $request->getParsedBody()['office_suite_choice'] ?? '';
+
+ if ($officeSuiteChoice === 'collabora') {
+ $this->configurationManager->isCollaboraEnabled = true;
+ $this->configurationManager->isOnlyofficeEnabled = false;
+ } elseif ($officeSuiteChoice === 'onlyoffice') {
+ $this->configurationManager->isCollaboraEnabled = false;
+ $this->configurationManager->isOnlyofficeEnabled = true;
} 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->isCollaboraEnabled = false;
+ $this->configurationManager->isOnlyofficeEnabled = false;
}
+ $this->configurationManager->isClamavEnabled = isset($request->getParsedBody()['clamav']);
+ $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,29 +108,29 @@ readonly class ConfigurationController {
$enabledCC[] = $item;
}
}
- $this->configurationManager->SetEnabledCommunityContainers($enabledCC);
+ $this->configurationManager->aioCommunityContainers = $enabledCC;
}
if (isset($request->getParsedBody()['delete_collabora_dictionaries'])) {
- $this->configurationManager->DeleteCollaboraDictionaries();
+ $this->configurationManager->deleteCollaboraDictionaries();
}
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'])) {
- $this->configurationManager->DeleteBorgBackupLocationItems();
+ $this->configurationManager->deleteBorgBackupLocationItems();
}
return $response->withStatus(201)->withHeader('Location', '.');
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..e65d5504 100644
--- a/php/src/Data/ConfigurationManager.php
+++ b/php/src/Data/ConfigurationManager.php
@@ -9,61 +9,359 @@ 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 => $this->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 => $this->booleanize($this->getEnvironmentalVariableOrConfig('AIO_DISABLE_BACKUP_SECTION', 'disable_backup_section', ''));
+ set { $this->set('disable_backup_section', $value); }
+ }
+
+ public bool $nextcloudEnableDriDevice{
+ get => $this->booleanize($this->getEnvironmentalVariableOrConfig('NEXTCLOUD_ENABLE_DRI_DEVICE', 'nextcloud_enable_dri_device', ''));
+ set { $this->set('nextcloud_enable_dri_device', $value); }
+ }
+
+ public bool $enableNvidiaGpu {
+ get => $this->booleanize($this->getEnvironmentalVariableOrConfig('NEXTCLOUD_ENABLE_NVIDIA_GPU', 'enable_nvidia_gpu', ''));
+ set { $this->set('enable_nvidia_gpu', $value); }
+ }
+
+ public bool $nextcloudKeepDisabledApps {
+ get => $this->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;
}
- public function GetAndGenerateSecret(string $secretId) : string {
+ /**
+ * 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 {
if ($secretId === '') {
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 {
+ public function getRegisteredSecret(string $secretId) : string {
if ($this->secrets[$secretId]) {
- return $this->GetAndGenerateSecret($secretId);
+ return $this->getAndGenerateSecret($secretId);
}
throw new \Exception("The secret " . $secretId . " was not registered. Please check if it is defined in secrets of containers.json.");
}
- public function RegisterSecret(string $secretId) : void {
+ public function registerSecret(string $secretId) : void {
$this->secrets[$secretId] = true;
}
- private function DoubleSafeBackupSecret(string $borgBackupPassword) : void {
+ private function doubleSafeBackupSecret(string $borgBackupPassword) : void {
file_put_contents(DataConst::GetBackupSecretFile(), $borgBackupPassword);
}
@@ -75,7 +373,7 @@ class ConfigurationManager
}
}
- public function GetLastBackupTime() : string {
+ public function getLastBackupTime() : string {
if (!file_exists(DataConst::GetBackupArchivesList())) {
return '';
}
@@ -100,7 +398,7 @@ class ConfigurationManager
return $lastBackupTime;
}
- public function GetBackupTimes() : array {
+ public function getBackupTimes() : array {
if (!file_exists(DataConst::GetBackupArchivesList())) {
return [];
}
@@ -122,12 +420,12 @@ class ConfigurationManager
return $backupTimes;
}
- public function wasStartButtonClicked() : bool {
- if (isset($this->GetConfig()['wasStartButtonClicked'])) {
- return true;
- } else {
- return false;
+ public function getAioVersion() : string {
+ $path = DataConst::GetAioVersionFile();
+ if ($path !== '' && file_exists($path)) {
+ return trim((string)file_get_contents($path));
}
+ return '';
}
private function isx64Platform() : bool {
@@ -138,157 +436,12 @@ 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 {
+ public function setDomain(string $domain, bool $skipDomainValidation) : void {
// Validate that at least one dot is contained
if (!str_contains($domain, '.')) {
throw new InvalidSettingConfigurationException("Domain must contain at least one dot!");
@@ -336,7 +489,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') {
@@ -355,7 +508,7 @@ class ConfigurationManager
}
// Get Instance ID
- $instanceID = $this->GetAndGenerateSecret('INSTANCE_ID');
+ $instanceID = $this->getAndGenerateSecret('INSTANCE_ID');
// set protocol
if ($port !== '443') {
@@ -392,86 +545,36 @@ 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);
+ $this->borgRestorePassword = '';
+ $this->startTransaction();
+ $this->commitTransaction();
}
- public function GetDomain() : string {
- $config = $this->GetConfig();
- if(!isset($config['domain'])) {
- $config['domain'] = '';
- }
-
- return $config['domain'];
- }
-
- public function GetBaseDN() : string {
- $domain = $this->GetDomain();
+ public function getBaseDN() : string {
+ $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);
+ public function setBorgLocationVars(string $location, string $repo) : void {
+ $this->validateBorgLocationVars($location, $repo);
+ $this->startTransaction();
+ $this->borgBackupHostLocation = $location;
+ $this->borgRemoteRepo = $repo;
+ $this->commitTransaction();
}
- private function ValidateBorgLocationVars(string $location, string $repo) : void {
+ private function validateBorgLocationVars(string $location, string $repo) : void {
if ($location === '' && $repo === '') {
throw new InvalidSettingConfigurationException("Please enter a path or a remote repo url!");
} elseif ($location !== '' && $repo !== '') {
@@ -492,16 +595,16 @@ 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 {
- $this->ValidateBorgRemoteRepo($repo);
+ $this->validateBorgRemoteRepo($repo);
}
}
- private function ValidateBorgRemoteRepo(string $repo) : void {
+ private function validateBorgRemoteRepo(string $repo) : void {
$commonMsg = "For valid urls, see the remote examples at https://borgbackup.readthedocs.io/en/stable/usage/general.html#repository-urls";
if ($repo === "") {
// Ok, remote repo is optional
@@ -512,12 +615,12 @@ class ConfigurationManager
}
}
- public function DeleteBorgBackupLocationItems() : void {
+ 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())) {
@@ -530,30 +633,30 @@ class ConfigurationManager
/**
* @throws InvalidSettingConfigurationException
*/
- public function SetBorgRestoreLocationVarsAndPassword(string $location, string $repo, string $password) : void {
- $this->ValidateBorgLocationVars($location, $repo);
+ public function setBorgRestoreLocationVarsAndPassword(string $location, string $repo, string $password) : void {
+ $this->validateBorgLocationVars($location, $repo);
if ($password === '') {
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();
}
/**
* @throws InvalidSettingConfigurationException
*/
- public function ChangeMasterPassword(string $currentPassword, string $newPassword) : void {
+ public function changeMasterPassword(string $currentPassword, string $newPassword) : void {
if ($currentPassword === '') {
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,89 +673,50 @@ 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 {
+ 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 {
+ public function getBorgPublicKey() : string {
if (!file_exists(DataConst::GetBackupPublicKey())) {
return "";
}
@@ -660,135 +724,18 @@ 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 {
+ 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
*/
- public function SetDailyBackupTime(string $time, bool $enableAutomaticUpdates, bool $successNotification) : void {
+ public function setDailyBackupTime(string $time, bool $enableAutomaticUpdates, bool $successNotification) : void {
if ($time === "") {
throw new InvalidSettingConfigurationException("The daily backup time must not be empty!");
}
@@ -810,7 +757,7 @@ class ConfigurationManager
file_put_contents(DataConst::GetDailyBackupTimeFile(), $time);
}
- public function GetDailyBackupTime() : string {
+ public function getDailyBackupTime() : string {
if (!file_exists(DataConst::GetDailyBackupTimeFile())) {
return '';
}
@@ -832,7 +779,7 @@ class ConfigurationManager
}
}
- public function DeleteDailyBackupTime() : void {
+ public function deleteDailyBackupTime() : void {
if (file_exists(DataConst::GetDailyBackupTimeFile())) {
unlink(DataConst::GetDailyBackupTimeFile());
}
@@ -841,7 +788,7 @@ class ConfigurationManager
/**
* @throws InvalidSettingConfigurationException
*/
- public function SetAdditionalBackupDirectories(string $additionalBackupDirectories) : void {
+ public function setAdditionalBackupDirectories(string $additionalBackupDirectories) : void {
$additionalBackupDirectoriesArray = explode("\n", $additionalBackupDirectories);
$validDirectories = '';
foreach($additionalBackupDirectoriesArray as $entry) {
@@ -862,48 +809,28 @@ 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 {
+ public function getAdditionalBackupDirectoriesString() : string {
if (!file_exists(DataConst::GetAdditionalBackupDirectoriesFile())) {
return '';
}
return (string)file_get_contents(DataConst::GetAdditionalBackupDirectoriesFile());
}
- public function GetAdditionalBackupDirectoriesArray() : array {
- $additionalBackupDirectories = $this->GetAdditionalBackupDirectoriesString();
+ public function getAdditionalBackupDirectoriesArray() : array {
+ $additionalBackupDirectories = $this->getAdditionalBackupDirectoriesString();
$additionalBackupDirectoriesArray = explode("\n", $additionalBackupDirectories);
$additionalBackupDirectoriesArray = array_unique($additionalBackupDirectoriesArray, SORT_REGULAR);
return $additionalBackupDirectoriesArray;
}
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 +838,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 {
@@ -930,7 +854,7 @@ class ConfigurationManager
return false;
}
- public function GetNextcloudStartupApps() : string {
+ public function getNextcloudStartupApps() : string {
$apps = getenv('NEXTCLOUD_STARTUP_APPS');
if (is_string($apps)) {
return trim($apps);
@@ -938,19 +862,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 +873,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);
}
- public function DeleteCollaboraDictionaries() : void {
- $config = $this->GetConfig();
- $config['collabora_dictionaries'] = '';
- $this->WriteConfig($config);
+ /**
+ * Provide an extra method since the corresponding attribute setter prevents setting an empty value.
+ */
+ public function deleteCollaboraDictionaries() : void {
+ $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 +893,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 +941,122 @@ class ConfigurationManager
return $cc;
}
- /** @return list */
- public function GetEnabledCommunityContainers(): array {
- return explode(' ', $this->GetCommunityContainers());
- }
-
- 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;
+ private function camelize(string $input, string $delimiter = '_') : string {
+ if ($input === '') {
+ throw new InvalidSettingConfigurationException('input cannot be empty!');
}
- }
-
- 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;
+ if ($delimiter === '') {
+ $delimiter = '_';
}
+ return lcfirst(implode("", array_map('ucfirst', explode($delimiter, strtolower($input)))));
+
+ }
+
+ public function setAioVariables(array $input) : void {
+ if ($input === []) {
+ return;
+ }
+ $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 = $this->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($this, $key)) {
+ error_log("Error: '$key' is not a valid configuration key (in '$keyWithValue')");
+ } else {
+ $this->$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 ? 'yes' : '',
+ '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' : '',
+ 'AIO_VERSION' => $this->getAioVersion(),
+ default => $this->getRegisteredSecret($placeholder),
+ };
+ }
+
+ private function booleanize(mixed $value) : bool {
+ return in_array($value, [true, 'true'], true);
}
}
diff --git a/php/src/Data/DataConst.php b/php/src/Data/DataConst.php
index 9111a98a..9272e3d4 100644
--- a/php/src/Data/DataConst.php
+++ b/php/src/Data/DataConst.php
@@ -66,4 +66,8 @@ class DataConst {
public static function GetContainersDefinitionPath() : string {
return (string)realpath(__DIR__ . '/../../containers.json');
}
+
+ public static function GetAioVersionFile() : string {
+ return (string)realpath(__DIR__ . '/../../templates/includes/aio-version.twig');
+ }
}
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..a8891c3c 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'] = [
[
@@ -391,7 +383,7 @@ readonly class DockerActionManager {
// Make volumes read only in case of borgbackup container. The viewer makes them writeable
$isReadOnly = $container->identifier === 'nextcloud-aio-borgbackup';
- foreach ($this->configurationManager->GetAdditionalBackupDirectoriesArray() as $additionalBackupDirectories) {
+ foreach ($this->configurationManager->getAdditionalBackupDirectoriesArray() as $additionalBackupDirectories) {
if ($additionalBackupDirectories !== '') {
if (!str_starts_with($additionalBackupDirectories, '/')) {
$mounts[] = ["Type" => "volume", "Source" => $additionalBackupDirectories, "Target" => "/docker_volumes/" . $additionalBackupDirectories, "ReadOnly" => $isReadOnly];
@@ -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->nextcloudMount) {
// continue;
// }
// $mounts[] = ["Type" => "bind", "Source" => $volume->name, "Target" => $volume->mountPoint, "ReadOnly" => !$volume->isWritable, "BindOptions" => [ "Propagation" => "rshared"]];
@@ -420,15 +412,21 @@ 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 !== '') {
+ // Split the list of Collabora options, which are stored as a string but must be assigned as an array.
+ // To avoid problems with whitespace or dashes in option arguments we use a regular expression
+ // that splits the string at every position where a whitespace is followed by '--o:'.
+ // The leading whitespace is removed in the split but the following characters are not.
+ // Example: "--o:example_config1='some thing' --o:example_config2=something-else" -> ["--o:example_config1='some thing'", "--o:example_config2=something-else"]
+ $regEx = '/\s+(?=--o:)/';
+ $requestBody['Cmd'] = preg_split($regEx, rtrim($this->configurationManager->collaboraAdditionalOptions));
}
}
@@ -530,82 +528,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 +543,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 +843,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);
}
@@ -1024,7 +946,7 @@ readonly class DockerActionManager {
}
public function GetAndGenerateSecretWrapper(string $secretId): string {
- return $this->configurationManager->GetAndGenerateSecret($secretId);
+ return $this->configurationManager->getAndGenerateSecret($secretId);
}
public function isNextcloudImageOutdated(): bool {
diff --git a/php/templates/containers.twig b/php/templates/containers.twig
index 2f722768..8e437bc2 100644
--- a/php/templates/containers.twig
+++ b/php/templates/containers.twig
@@ -27,7 +27,7 @@
{# js for optional containers and additional containers forms #}
-
+
{% set hasBackupLocation = borg_backup_host_location or borg_remote_repo %}
{% set isAnyRunning = false %}
diff --git a/php/templates/includes/optional-containers.twig b/php/templates/includes/optional-containers.twig
index b4764592..dcf59bfb 100644
--- a/php/templates/includes/optional-containers.twig
+++ b/php/templates/includes/optional-containers.twig
@@ -9,6 +9,105 @@
+
Office Suite
+ {% if isAnyRunning == false %}
+
Choose your preferred office suite. Only one can be enabled at a time.
+ {% endif %}
+
+
+
+
+
+
+
+
+ {% if isAnyRunning == false %}
+
+
+
+
+ {% endif %}
+
+
Additional Optional Containers
-
-
-
-
-
-
-
-
+
-
+
Minimal system requirements: When any optional container is enabled, at least 2GB RAM, a dual-core CPU and 40GB system storage are required. When enabling ClamAV, Nextcloud Talk Recording-server or Fulltextsearch, at least 3GB RAM are required. For Talk Recording-server additional 2 vCPUs are required. When enabling everything, at least 5GB RAM and a quad-core CPU are required. Recommended are at least 1GB more RAM than the minimal requirement. For further advice and recommendations see this documentation