diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 691221d7..aca2e718 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -30,4 +30,10 @@ labels: 0. Needs triage #### Docker run command or docker-compose file that you used -#### Other valuable info +#### Output of `sudo docker logs nextcloud-aio-mastercontainer` + +#### Output of `sudo docker inspect nextcloud-aio-mastercontainer` + +#### Output of `sudo docker ps -a` + +#### Other valuable info diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0caaabfb..7fe1067e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,8 @@ updates: labels: - 3. to review - dependencies + cooldown: + default-days: 7 - package-ecosystem: composer directory: "/php/" schedule: @@ -55,6 +57,6 @@ updates: - dependency-name: "postgres" update-types: ["version-update:semver-major"] - dependency-name: "redis" - update-types: ["version-update:semver-major", "version-update:semver-minor"] + update-types: ["version-update:semver-major"] - dependency-name: "elasticsearch" update-types: ["version-update:semver-major"] 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/codespell.yml b/.github/workflows/codespell.yml index 9104a88d..475940a9 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check spelling - uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2 + uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2 with: check_filenames: true check_hidden: true diff --git a/.github/workflows/collabora.yml b/.github/workflows/collabora.yml new file mode 100644 index 00000000..a61067f3 --- /dev/null +++ b/.github/workflows/collabora.yml @@ -0,0 +1,29 @@ +name: collabora-update + +on: + workflow_dispatch: + schedule: + - cron: '00 12 * * *' + +jobs: + collabora-update: + name: update collabora + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Run collabora-profile-update + run: | + rm -f php/cool-seccomp-profile.json + wget https://raw.githubusercontent.com/CollaboraOnline/online/refs/heads/main/docker/cool-seccomp-profile.json + mv cool-seccomp-profile.json php/ + + - name: Create Pull Request + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7 + with: + commit-message: collabora-seccomp-update automated change + signoff: true + title: collabora seccomp update + body: Automated collabora seccomp profile update + labels: dependencies, 3. to review + milestone: next + branch: collabora-seccomp-update diff --git a/.github/workflows/community-containers.yml b/.github/workflows/community-containers.yml index eddc2be1..5271bfa8 100644 --- a/.github/workflows/community-containers.yml +++ b/.github/workflows/community-containers.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Validate structure run: | CONTAINERS="$(find ./community-containers -mindepth 1 -maxdepth 1 -type d)" diff --git a/.github/workflows/dependency-updates.yml b/.github/workflows/dependency-updates.yml index e6205d4e..3805a0d0 100644 --- a/.github/workflows/dependency-updates.yml +++ b/.github/workflows/dependency-updates.yml @@ -10,8 +10,8 @@ jobs: name: Run dependency update script runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: php-version: 8.4 extensions: apcu @@ -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@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7 with: commit-message: php dependency updates signoff: true diff --git a/.github/workflows/docker-lint.yml b/.github/workflows/docker-lint.yml index 1551c48a..3f09bb98 100644 --- a/.github/workflows/docker-lint.yml +++ b/.github/workflows/docker-lint.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install hadolint run: | diff --git a/.github/workflows/fail-on-prerelease.yml b/.github/workflows/fail-on-prerelease.yml new file mode 100644 index 00000000..a5b876c3 --- /dev/null +++ b/.github/workflows/fail-on-prerelease.yml @@ -0,0 +1,50 @@ +name: Block if prerelease is present + +on: + pull_request: + +permissions: + contents: read + +jobs: + check-latest-release: + runs-on: ubuntu-latest + steps: + - name: "Check latest published release isn't a prerelease" + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v6 + with: + script: | + const tags = await github.rest.repos.listTags({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 1 + }); + + if (!tags.data || tags.data.length === 0) { + core.info('No tags found for this repository; skipping prerelease check.'); + return; + } + + const latestTag = tags.data[0].name; + core.info(`Latest tag found: ${latestTag}`); + + try { + const { data } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: latestTag + }); + + if (data.prerelease) { + core.setFailed(`Release for tag ${latestTag} (${data.tag_name}) is a prerelease. Blocking merges to main as we need to wait for the prerelease to become stable.`); + } else { + core.info(`Release for tag ${latestTag} (${data.tag_name}) is not a prerelease.`); + } + + } catch (err) { + if (err.status === 404) { + core.info(`No release found for tag ${latestTag}; skipping prerelease check.`); + } else { + throw err; + } + } diff --git a/.github/workflows/helm-release.yml b/.github/workflows/helm-release.yml index e6527250..ba3b865d 100644 --- a/.github/workflows/helm-release.yml +++ b/.github/workflows/helm-release.yml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Turnstyle - uses: softprops/turnstyle@9d692f15fa9f84928799bccac2dba6565e024bdf # v2 + uses: softprops/turnstyle@e565d2d86403c5d23533937e95980570545e5586 # v2 with: continue-after-seconds: 180 env: diff --git a/.github/workflows/imaginary-update.yml b/.github/workflows/imaginary-update.yml index 4b7fe096..05050a20 100644 --- a/.github/workflows/imaginary-update.yml +++ b/.github/workflows/imaginary-update.yml @@ -10,7 +10,7 @@ jobs: name: update to latest imaginary commit on master branch runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run imaginary-update run: | # Imaginary @@ -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@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7 with: commit-message: imaginary-update automated change signoff: true diff --git a/.github/workflows/json-validator.yml b/.github/workflows/json-validator.yml index e22ca689..8c0a7f45 100644 --- a/.github/workflows/json-validator.yml +++ b/.github/workflows/json-validator.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Validate Json run: | sudo apt-get update diff --git a/.github/workflows/lint-helm.yml b/.github/workflows/lint-helm.yml index 650ae709..61e51450 100644 --- a/.github/workflows/lint-helm.yml +++ b/.github/workflows/lint-helm.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 diff --git a/.github/workflows/lint-php.yml b/.github/workflows/lint-php.yml index 81ada6da..c0d2d577 100644 --- a/.github/workflows/lint-php.yml +++ b/.github/workflows/lint-php.yml @@ -2,6 +2,9 @@ # # https://github.com/nextcloud/.github # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization +# +# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT name: Lint php @@ -33,10 +36,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2.36.0 with: php-version: ${{ matrix.php-versions }} coverage: none diff --git a/.github/workflows/lint-yaml.yml b/.github/workflows/lint-yaml.yml new file mode 100644 index 00000000..e36b8f4c --- /dev/null +++ b/.github/workflows/lint-yaml.yml @@ -0,0 +1,42 @@ +# This workflow is provided via the organization template repository +# +# https://github.com/nextcloud/.github +# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization +# +# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: Lint YAML + +on: + pull_request: + paths: + - '**.yml' + +permissions: + contents: read + +jobs: + yaml-lint: + runs-on: ubuntu-latest + + name: yaml + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.1 + with: + persist-credentials: false + + - name: GitHub action templates lint + uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c # v3.1.1 + with: + file_or_dir: .github/workflows + config_data: | + line-length: warning + + - name: Install the latest version of uv + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 + + - name: Check GitHub actions + run: uvx zizmor --min-severity medium .github/workflows/*.yml diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml index be8273d5..bda40ee2 100644 --- a/.github/workflows/lock-threads.yml +++ b/.github/workflows/lock-threads.yml @@ -14,7 +14,7 @@ jobs: action: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5 + - uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v5 with: issue-inactive-days: '14' process-only: 'issues' diff --git a/.github/workflows/nextcloud-update.yml b/.github/workflows/nextcloud-update.yml index 7d153d7b..b2475290 100644 --- a/.github/workflows/nextcloud-update.yml +++ b/.github/workflows/nextcloud-update.yml @@ -11,7 +11,7 @@ jobs: name: Run nextcloud-update script runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run nextcloud-update script run: | # Inspired by https://github.com/nextcloud/docker/blob/master/update.sh @@ -79,7 +79,7 @@ jobs: fi - name: Create Pull Request - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7 with: commit-message: nextcloud-update automated change signoff: true diff --git a/.github/workflows/php-deprecation-detector.yml b/.github/workflows/php-deprecation-detector.yml index d2bccbd5..38b0fa8d 100644 --- a/.github/workflows/php-deprecation-detector.yml +++ b/.github/workflows/php-deprecation-detector.yml @@ -16,9 +16,9 @@ jobs: name: PHP Deprecation Detector runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up php - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: php-version: 8.4 extensions: apcu diff --git a/.github/workflows/playwright-on-push.yml b/.github/workflows/playwright-on-push.yml new file mode 100644 index 00000000..40277e57 --- /dev/null +++ b/.github/workflows/playwright-on-push.yml @@ -0,0 +1,123 @@ +name: Playwright Tests on push + +on: + pull_request: + paths: + - 'php/**' + push: + branches: + - main + paths: + - 'php/**' + +concurrency: + group: playwright-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + BASE_URL: https://localhost:8080 + +jobs: + test: + timeout-minutes: 60 + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 + with: + node-version: lts/* + + - name: Install dependencies + run: cd php/tests && npm ci + + - name: Install Playwright Browsers + run: cd php/tests && npx playwright install --with-deps chromium + + - name: Set up php 8.4 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2.36.0 + with: + extensions: apcu + php-version: 8.4 + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Adjust some things and fix permissions + run: | + cd php + rm -r ./data + rm -r ./session + composer install --no-dev + composer clear-cache + sudo chmod 777 -R ./ + + - name: Start fresh development server + run: | + docker rm --force nextcloud-aio-{mastercontainer,apache,notify-push,nextcloud,redis,database,domaincheck,whiteboard,imaginary,talk,collabora,borgbackup} || true + docker volume rm nextcloud_aio_{mastercontainer,apache,database,database_dump,nextcloud,nextcloud_data,redis,backup_cache,elasticsearch} || true + docker pull ghcr.io/nextcloud-releases/all-in-one:develop + docker run \ + -d \ + --init \ + --name nextcloud-aio-mastercontainer \ + --restart always \ + --publish 8080:8080 \ + --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ + --volume ./php:/var/www/docker-aio/php \ + --volume /var/run/docker.sock:/var/run/docker.sock:ro \ + --env SKIP_DOMAIN_VALIDATION=true \ + --env APACHE_PORT=11000 \ + ghcr.io/nextcloud-releases/all-in-one:develop + echo Waiting for 10 seconds for the development container to start ... + sleep 10 + + - name: Run Playwright tests for initial setup + run: | + cd php/tests + export DEBUG=pw:api + if ! npx playwright test tests/initial-setup.spec.js; then + docker logs nextcloud-aio-mastercontainer + docker logs nextcloud-aio-borgbackup + exit 1 + fi + + - name: Start fresh development server + run: | + docker rm --force nextcloud-aio-{mastercontainer,apache,notify-push,nextcloud,redis,database,domaincheck,whiteboard,imaginary,talk,collabora,borgbackup} || true + docker volume rm nextcloud_aio_{mastercontainer,apache,database,database_dump,nextcloud,nextcloud_data,redis,backup_cache,elasticsearch} || true + docker run \ + -d \ + --init \ + --name nextcloud-aio-mastercontainer \ + --restart always \ + --publish 8080:8080 \ + --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ + --volume ./php:/var/www/docker-aio/php \ + --volume /var/run/docker.sock:/var/run/docker.sock:ro \ + --env SKIP_DOMAIN_VALIDATION=false \ + --env APACHE_PORT=11000 \ + ghcr.io/nextcloud-releases/all-in-one:develop + echo Waiting for 10 seconds for the development container to start ... + sleep 10 + + - name: Run Playwright tests for backup restore + run: | + cd php/tests + export DEBUG=pw:api + if ! npx playwright test tests/restore-instance.spec.js; then + docker logs nextcloud-aio-mastercontainer + docker logs nextcloud-aio-borgbackup + exit 1 + fi + + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: php/tests/playwright-report/ + retention-days: 14 + overwrite: true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright-on-workflow-dispatch.yml similarity index 92% rename from .github/workflows/playwright.yml rename to .github/workflows/playwright-on-workflow-dispatch.yml index 9de89243..6d2f6d32 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright-on-workflow-dispatch.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 with: node-version: lts/* @@ -82,7 +82,7 @@ jobs: exit 1 fi - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 if: ${{ !cancelled() }} with: name: playwright-report diff --git a/.github/workflows/psalm-update-baseline.yml b/.github/workflows/psalm-update-baseline.yml index 67618422..bcbb12c3 100644 --- a/.github/workflows/psalm-update-baseline.yml +++ b/.github/workflows/psalm-update-baseline.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up php - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: php-version: 8.4 extensions: apcu @@ -30,7 +30,7 @@ jobs: continue-on-error: true - name: Create Pull Request - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # 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/psalm.yml b/.github/workflows/psalm.yml index 3fcdae12..2bab876e 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -2,6 +2,9 @@ # # https://github.com/nextcloud/.github # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization +# +# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT name: Static analysis @@ -19,6 +22,9 @@ concurrency: group: psalm-${{ github.head_ref || github.run_id }} cancel-in-progress: true +permissions: + contents: read + jobs: static-analysis: runs-on: ubuntu-latest @@ -26,15 +32,19 @@ jobs: name: static-psalm-analysis steps: - name: Checkout - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up php - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2.36.0 with: php-version: 8.4 extensions: apcu coverage: none ini-file: development + # Temporary workaround for missing pcntl_* in PHP 8.3 + ini-values: disable_functions= env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 4924d9bc..b051c355 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -15,7 +15,7 @@ jobs: name: Check Shell runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 with: diff --git a/.github/workflows/talk.yml b/.github/workflows/talk.yml index ed04851a..b19e1cb5 100644 --- a/.github/workflows/talk.yml +++ b/.github/workflows/talk.yml @@ -10,7 +10,7 @@ jobs: name: update talk runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run talk-container-update run: | # Recording @@ -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@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7 with: commit-message: talk-update automated change signoff: true diff --git a/.github/workflows/twig-lint.yml b/.github/workflows/twig-lint.yml index 107edc8b..27b8776d 100644 --- a/.github/workflows/twig-lint.yml +++ b/.github/workflows/twig-lint.yml @@ -24,10 +24,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: php-version: 8.4 extensions: apcu diff --git a/.github/workflows/update-copyright.yml b/.github/workflows/update-copyright.yml index 364b3aae..103851c9 100644 --- a/.github/workflows/update-copyright.yml +++ b/.github/workflows/update-copyright.yml @@ -8,4 +8,4 @@ jobs: name: update copyright runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/update-helm.yml b/.github/workflows/update-helm.yml index b10fb430..92cbb978 100644 --- a/.github/workflows/update-helm.yml +++ b/.github/workflows/update-helm.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: update helm chart run: | set -x @@ -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@271a8d0340265f705b14b6d32b9829c1cb33d45e # 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 d8c6c6e0..6e150261 100644 --- a/.github/workflows/update-yaml.yml +++ b/.github/workflows/update-yaml.yml @@ -11,12 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: update yaml files run: | sudo bash manual-install/update-yaml.sh - name: Create Pull Request - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # 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 new file mode 100644 index 00000000..ecd82a69 --- /dev/null +++ b/.github/workflows/watchtower-update.yml @@ -0,0 +1,37 @@ +name: watchtower-update + +on: + workflow_dispatch: + schedule: + - cron: '00 12 * * *' + +jobs: + watchtower-update: + name: update watchtower + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Run watchtower-container-update + run: | + # Watchtower + watchtower_version="$( + git ls-remote https://github.com/nicholas-fedor/watchtower v* \ + | cut -d/ -f3 \ + | sort -V \ + | grep -E "^v[0-9\.]+$" \ + | tail -1 + )" + watchtower_commit_hash="$(git ls-remote https://github.com/nicholas-fedor/watchtower $watchtower_version | sed 's/refs.*//')" + sed -i "s|^ENV WATCHTOWER_COMMIT_HASH.*$|ENV WATCHTOWER_COMMIT_HASH=$watchtower_commit_hash|" ./Containers/watchtower/Dockerfile + sed -i "s|\$WATCHTOWER_COMMIT_HASH.*$|\$WATCHTOWER_COMMIT_HASH # $watchtower_version|" ./Containers/watchtower/Dockerfile + + - name: Create Pull Request + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7 + with: + commit-message: watchtower-update automated change + signoff: true + title: watchtower container update + body: Automated watchtower container update + labels: dependencies, 3. to review + milestone: next + branch: watchtower-container-update diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..fec85a59 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,13 @@ + +In the Nextcloud community, participants from all over the world come together to create Free Software for a free internet. This is made possible by the support, hard work and enthusiasm of thousands of people, including those who create and use Nextcloud software. + +Our code of conduct offers some guidance to ensure Nextcloud participants can cooperate effectively in a positive and inspiring atmosphere, and to explain how together we can strengthen and support each other. + +The Code of Conduct is shared by all contributors and users who engage with the Nextcloud team and its community services. It presents a summary of the shared values and “common sense” thinking in our community. + +You can find our full code of conduct on our website: https://nextcloud.com/code-of-conduct/ + +Please, keep our CoC in mind when you contribute! That way, everyone can be a part of our community in a productive, positive, creative and fun way. diff --git a/Containers/alpine/Dockerfile b/Containers/alpine/Dockerfile index 25ac9671..1098b4c4 100644 --- a/Containers/alpine/Dockerfile +++ b/Containers/alpine/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.22.2 +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a diff --git a/Containers/apache/Dockerfile b/Containers/apache/Dockerfile index d9602864..9ccadfb8 100644 --- a/Containers/apache/Dockerfile +++ b/Containers/apache/Dockerfile @@ -2,7 +2,7 @@ FROM caddy:2.10.2-alpine AS caddy # From https://github.com/docker-library/httpd/blob/master/2.4/alpine/Dockerfile -FROM httpd:2.4.65-alpine3.22 +FROM httpd:2.4.66-alpine3.23 COPY --from=caddy /usr/bin/caddy /usr/bin/caddy @@ -88,4 +88,5 @@ CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/borgbackup/Dockerfile b/Containers/borgbackup/Dockerfile index 3d4ce1ce..6e3180cb 100644 --- a/Containers/borgbackup/Dockerfile +++ b/Containers/borgbackup/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.22.2 +FROM alpine:3.23.3 RUN set -ex; \ \ @@ -24,5 +24,6 @@ ENTRYPOINT ["/start.sh"] USER root LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" ENV BORG_RETENTION_POLICY="--keep-within=7d --keep-weekly=4 --keep-monthly=6" diff --git a/Containers/borgbackup/backupscript.sh b/Containers/borgbackup/backupscript.sh index 602ec586..b7b96147 100644 --- a/Containers/borgbackup/backupscript.sh +++ b/Containers/borgbackup/backupscript.sh @@ -138,11 +138,6 @@ if [ "$BORG_MODE" = backup ]; then NEW_REPOSITORY=1 if ! borg init --debug --encryption=repokey-blake2; then echo "Could not initialize borg repository." - if [ -z "$BORG_REMOTE_REPO" ]; then - # Originally we checked for presence of the config file instead of calling `borg info`. Likely `borg info` - # will error on a partially initialized repo, so this line is probably no longer necessary - rm -f "$BORG_BACKUP_DIRECTORY/config" - fi exit 1 fi @@ -199,7 +194,7 @@ if [ "$BORG_MODE" = backup ]; then if [ -f "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/.noaiobackup" ]; then BORG_EXCLUDE+=(--exclude "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/") BORG_INCLUDE+=(--pattern="+/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/.noaiobackup") - echo "⚠️⚠️⚠️ '.noaiobackup' file was found in Nextclouds data directory. Excluding the data directory from backup!" + echo "⚠️⚠️⚠️ '.noaiobackup' file was found in Nextcloud's data directory. Excluding the data directory from backup!" # Exclude preview folder if .noaiobackup file was found elif [ -f /nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/appdata_*/preview/.noaiobackup ]; then BORG_EXCLUDE+=(--exclude "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/appdata_*/preview/") @@ -344,7 +339,7 @@ if [ "$BORG_MODE" = restore ]; then ADDITIONAL_RSYNC_EXCLUDES=(--exclude "nextcloud_aio_nextcloud_data/**") ADDITIONAL_BORG_EXCLUDES=(--exclude "sh:nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/**") ADDITIONAL_FIND_EXCLUDES=(-o -regex 'nextcloud_aio_volumes/nextcloud_aio_nextcloud_data\(/.*\)?') - echo "⚠️⚠️⚠️ '.noaiobackup' file was found in Nextclouds data directory. Excluding the data directory from restore!" + echo "⚠️⚠️⚠️ '.noaiobackup' file was found in Nextcloud's data directory. Excluding the data directory from restore!" echo "You might run into problems due to this afterwards as potentially this makes the directory go out of sync with the database." echo "You might be able to fix this by running 'occ files:scan --all' and 'occ maintenance:repair' and 'occ files:scan-app-data' after the restore." echo "See https://github.com/nextcloud/all-in-one#how-to-run-occ-commands" @@ -617,3 +612,12 @@ if [ "$BORG_MODE" = test ]; then fi fi fi + +if [ "$BORG_MODE" = list ]; then + echo "Updating backup list..." + if ! borg info > /dev/null; then + echo "Could not update the backup list." + exit 1 + fi + # The update gets done automatically in the wrapper start.sh script. +fi diff --git a/Containers/borgbackup/start.sh b/Containers/borgbackup/start.sh index 9da0d840..bb7a8a6a 100644 --- a/Containers/borgbackup/start.sh +++ b/Containers/borgbackup/start.sh @@ -32,8 +32,8 @@ else fi # Validate BORG_MODE -if [ "$BORG_MODE" != backup ] && [ "$BORG_MODE" != restore ] && [ "$BORG_MODE" != check ] && [ "$BORG_MODE" != "check-repair" ] && [ "$BORG_MODE" != test ]; then - echo "No correct BORG_MODE mode applied. Valid are 'backup', 'check', 'restore' and 'test'." +if [ "$BORG_MODE" != backup ] && [ "$BORG_MODE" != restore ] && [ "$BORG_MODE" != check ] && [ "$BORG_MODE" != "check-repair" ] && [ "$BORG_MODE" != "test" ] && [ "$BORG_MODE" != "list" ]; then + echo "No correct BORG_MODE mode applied. Valid are 'backup', 'check', 'restore', 'test' and 'list'." exit 1 fi diff --git a/Containers/clamav/Dockerfile b/Containers/clamav/Dockerfile index db59f64a..6910ae1c 100644 --- a/Containers/clamav/Dockerfile +++ b/Containers/clamav/Dockerfile @@ -1,18 +1,26 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.22.2 +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a; \ - apk add --no-cache tzdata clamav supervisor bash; \ - mkdir -p /var/lib/clamav /run/clamav /var/log/supervisord /var/run/supervisord; \ - chmod 777 -R /run/clamav /var/log/clamav /var/log/supervisord /var/run/supervisord; \ + apk add --no-cache tzdata clamav clamav-milter supervisor bash; \ + mkdir -p /tmp /var/lib/clamav /run/clamav /var/log/supervisord /var/run/supervisord; \ + chmod 777 -R /tmp /run/clamav /var/log/clamav /var/log/supervisord /var/run/supervisord; \ chown -R 100:100 /var/lib/clamav; \ sed -i "s|#\?MaxDirectoryRecursion.*|MaxDirectoryRecursion 30|g" /etc/clamav/clamd.conf; \ - sed -i "s|#\?MaxFileSize.*|MaxFileSize 2G|g" /etc/clamav/clamd.conf; \ - sed -i "s|#\?PCREMaxFileSize.*|PCREMaxFileSize aio-placeholder|g" /etc/clamav/clamd.conf; \ - sed -i "s|#\?StreamMaxLength.*|StreamMaxLength aio-placeholder|g" /etc/clamav/clamd.conf; \ + sed -i "s|#\?MaxScanSize.*|MaxScanSize 2000M|g" /etc/clamav/clamd.conf; \ + sed -i "s|#\?MaxFileSize.*|MaxFileSize 2000M|g" /etc/clamav/clamd.conf; \ + sed -i "s|#\?PCREMaxFileSize.*|PCREMaxFileSize 2000M|g" /etc/clamav/clamd.conf; \ +# StreamMaxLength must be synced with av_stream_max_length inside the Nextcloud files_antivirus plugin + sed -i "s|#\?StreamMaxLength.*|StreamMaxLength 2000M|g" /etc/clamav/clamd.conf; \ sed -i "s|#\?TCPSocket|TCPSocket|g" /etc/clamav/clamd.conf; \ - sed -i "s|^LocalSocket .*|LocalSocket /tmp/clamd.sock|g" /etc/clamav/clamd.conf + sed -i "s|^LocalSocket .*|LocalSocket /tmp/clamd.sock|g" /etc/clamav/clamd.conf; \ + sed -i "s|Example| |g" /etc/clamav/clamav-milter.conf; \ + sed -i "s|#\?MilterSocket inet:7357|MilterSocket inet:7357|g" /etc/clamav/clamav-milter.conf; \ + sed -i "s|#\?ClamdSocket unix:/run/clamav/clamd.sock|ClamdSocket unix:/tmp/clamd.sock|g" /etc/clamav/clamav-milter.conf; \ + sed -i "s|#\?OnInfected Quarantine|OnInfected Reject|g" /etc/clamav/clamav-milter.conf; \ + sed -i "s|#\?AddHeader Replace|AddHeader Add|g" /etc/clamav/clamav-milter.conf; \ + sed -i "s|#\?Foreground yes|Foreground yes|g" /etc/clamav/clamav-milter.conf COPY --chmod=775 start.sh /start.sh COPY --chmod=775 healthcheck.sh /healthcheck.sh @@ -25,5 +33,6 @@ VOLUME /var/lib/clamav ENTRYPOINT ["/start.sh"] CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"] LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" HEALTHCHECK --start-period=60s --retries=9 CMD /healthcheck.sh diff --git a/Containers/clamav/start.sh b/Containers/clamav/start.sh index bda4add5..609120c1 100644 --- a/Containers/clamav/start.sh +++ b/Containers/clamav/start.sh @@ -1,7 +1,5 @@ #!/bin/bash -sed "s|aio-placeholder|$MAX_SIZE|" /etc/clamav/clamd.conf > /tmp/clamd.conf - # Print out clamav version for compliance reasons clamscan --version diff --git a/Containers/clamav/supervisord.conf b/Containers/clamav/supervisord.conf index 8f53856a..1895ceb6 100644 --- a/Containers/clamav/supervisord.conf +++ b/Containers/clamav/supervisord.conf @@ -13,11 +13,18 @@ stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 -command=freshclam --foreground --stdout --daemon --daemon-notify=/tmp/clamd.conf +command=freshclam --foreground --stdout --daemon --daemon-notify=/etc/clamav/clamd.conf [program:clamd] stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 -command=clamd --foreground --config-file=/tmp/clamd.conf +command=clamd --foreground --config-file=/etc/clamav/clamd.conf + +[program:milter] +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +command=clamav-milter --config-file=/etc/clamav/clamav-milter.conf \ No newline at end of file diff --git a/Containers/collabora-online/Dockerfile b/Containers/collabora-online/Dockerfile new file mode 100644 index 00000000..ec8b63f0 --- /dev/null +++ b/Containers/collabora-online/Dockerfile @@ -0,0 +1,16 @@ +# syntax=docker/dockerfile:latest +# From https://gitlab.collabora.com/collabora-online/docker +# hadolint ignore=DL3007 +FROM registry.gitlab.collabora.com/collabora-online/docker:latest + +USER root +ARG DEBIAN_FRONTEND=noninteractive + +COPY --chmod=775 healthcheck.sh /healthcheck.sh + +USER 1001 + +HEALTHCHECK --start-period=60s --retries=9 CMD /healthcheck.sh +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/collabora-online/healthcheck.sh b/Containers/collabora-online/healthcheck.sh new file mode 100644 index 00000000..45e9278b --- /dev/null +++ b/Containers/collabora-online/healthcheck.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Unfortunately, no curl and no nc is installed in the container +# and packages can also not be added as the package list is broken. +# So always exiting 0 for now. +# nc http://127.0.0.1:9980 || exit 1 +exit 0 diff --git a/Containers/collabora/Dockerfile b/Containers/collabora/Dockerfile index 347ae83f..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.6.2.1 +FROM collabora/code:25.04.8.2.1 USER root ARG DEBIAN_FRONTEND=noninteractive @@ -11,4 +11,5 @@ USER 1001 HEALTHCHECK --start-period=60s --retries=9 CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/docker-socket-proxy/Dockerfile b/Containers/docker-socket-proxy/Dockerfile index 72034cec..ffc867a8 100644 --- a/Containers/docker-socket-proxy/Dockerfile +++ b/Containers/docker-socket-proxy/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM haproxy:3.2.6-alpine +FROM haproxy:3.3.2-alpine # hadolint ignore=DL3002 USER root @@ -19,4 +19,5 @@ COPY --chmod=664 haproxy.cfg /haproxy.cfg ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/domaincheck/Dockerfile b/Containers/domaincheck/Dockerfile index c4b340e2..374aba4a 100644 --- a/Containers/domaincheck/Dockerfile +++ b/Containers/domaincheck/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.22.2 +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a; \ apk add --no-cache bash lighttpd netcat-openbsd; \ @@ -18,4 +18,5 @@ ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD nc -z 127.0.0.1 $APACHE_PORT || exit 1 LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/fulltextsearch/Dockerfile b/Containers/fulltextsearch/Dockerfile index 980fe275..ff1e923f 100644 --- a/Containers/fulltextsearch/Dockerfile +++ b/Containers/fulltextsearch/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:latest # Probably from here https://github.com/elastic/elasticsearch/blob/main/distribution/docker/src/docker/Dockerfile -FROM elasticsearch:8.19.5 +FROM elasticsearch:8.19.10 USER root @@ -22,5 +22,6 @@ USER 1000:0 HEALTHCHECK --interval=10s --timeout=5s --start-period=1m --retries=5 CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" ENV ES_JAVA_OPTS="-Xms512M -Xmx512M" diff --git a/Containers/imaginary/Dockerfile b/Containers/imaginary/Dockerfile index 47eccfad..b108ac18 100644 --- a/Containers/imaginary/Dockerfile +++ b/Containers/imaginary/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:latest -FROM golang:1.25.3-alpine3.22 AS go +FROM golang:1.25.6-alpine3.23 AS go -ENV IMAGINARY_HASH=1d4e251cfcd58ea66f8361f8721d7b8cc85002a3 +ENV IMAGINARY_HASH=6a274b488759a896aff02f52afee6e50b5e3a3ee RUN set -ex; \ apk upgrade --no-cache -a; \ @@ -14,7 +14,7 @@ RUN set -ex; \ build-base; \ go install github.com/h2non/imaginary@"$IMAGINARY_HASH"; -FROM alpine:3.22.2 +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a; \ apk add --no-cache \ @@ -43,4 +43,5 @@ ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/mastercontainer/Caddyfile b/Containers/mastercontainer/Caddyfile deleted file mode 100644 index da0e222d..00000000 --- a/Containers/mastercontainer/Caddyfile +++ /dev/null @@ -1,37 +0,0 @@ -{ - # auto_https will create redirects for https://{host}:8443 instead of https://{host} - # https redirects are added manually in the http://:80 block - auto_https disable_redirects - - storage file_system { - root /mnt/docker-aio-config/caddy/ - } - - log { - level ERROR - } - - servers { - protocols h1 h2 h2c - } - - on_demand_tls { - ask http://127.0.0.1:9876/ - } -} - -http://:80 { - redir https://{host}{uri} permanent -} - -https://:8443 { - - reverse_proxy 127.0.0.1:8000 - - tls { - on_demand - issuer acme { - disable_tlsalpn_challenge - } - } -} diff --git a/Containers/mastercontainer/Dockerfile b/Containers/mastercontainer/Dockerfile index c1cbaa59..ef93600a 100644 --- a/Containers/mastercontainer/Dockerfile +++ b/Containers/mastercontainer/Dockerfile @@ -1,26 +1,28 @@ # syntax=docker/dockerfile:latest # Docker CLI is a requirement -FROM docker:28.5.1-cli AS docker +FROM docker:29.2.0-cli AS docker # Caddy is a requirement FROM caddy:2.10.2-alpine AS caddy -# From https://github.com/docker-library/php/blob/master/8.4/alpine3.22/fpm/Dockerfile -FROM php:8.4.13-fpm-alpine3.22 +# From https://github.com/docker-library/php/blob/master/8.4/alpine3.23/fpm/Dockerfile +FROM php:8.4.17-fpm-alpine3.23 EXPOSE 80 EXPOSE 8080 EXPOSE 8443 +# Overwrite home variable for subservices +ENV HOME=/var/www + COPY --from=caddy /usr/bin/caddy /usr/bin/caddy COPY --from=docker /usr/local/bin/docker /usr/local/bin/docker COPY community-containers /var/www/docker-aio/community-containers COPY php /var/www/docker-aio/php COPY --chmod=775 Containers/mastercontainer/*.sh / -COPY --chmod=664 Containers/mastercontainer/Caddyfile /Caddyfile +COPY --chmod=664 Containers/mastercontainer/*.Caddyfile / COPY --chmod=664 Containers/mastercontainer/supervisord.conf /supervisord.conf -COPY Containers/mastercontainer/mastercontainer.conf /etc/apache2/sites-available/mastercontainer.conf WORKDIR /var/www/docker-aio @@ -34,13 +36,8 @@ RUN set -ex; \ apk add --no-cache \ util-linux-misc \ ca-certificates \ - wget \ bash \ - apache2 \ - apache2-proxy \ - apache2-ssl \ supervisor \ - openssl \ sudo \ netcat-openbsd \ curl \ @@ -49,7 +46,7 @@ RUN set -ex; \ apk add --no-cache --virtual .build-deps \ autoconf \ build-base; \ - pecl install APCu-5.1.27; \ + pecl install APCu-5.1.28; \ docker-php-ext-enable apcu; \ rm -r /tmp/pear; \ runDeps="$( \ @@ -64,18 +61,20 @@ RUN set -ex; \ sed -i 's/^pm = dynamic/pm = ondemand/' /usr/local/etc/php-fpm.d/www.conf; \ sed -i 's/^pm.max_children =.*/pm.max_children = 80/' /usr/local/etc/php-fpm.d/www.conf; \ sed -i 's|access.log = /proc/self/fd/2|access.log = /proc/self/fd/1|' /usr/local/etc/php-fpm.d/docker.conf; \ - grep -q ';listen.allowed_clients' /usr/local/etc/php-fpm.d/www.conf; \ - sed -i 's|;listen.allowed_clients.*|listen.allowed_clients = 127.0.0.1,::1|' /usr/local/etc/php-fpm.d/www.conf; \ + grep -q 'listen =' /usr/local/etc/php-fpm.d/www.conf; \ + sed -i 's|listen =.*|;listen = /run/php.sock # handled in zz-docker.conf|' /usr/local/etc/php-fpm.d/www.conf; \ + grep -q 'listen =' /usr/local/etc/php-fpm.d/zz-docker.conf; \ + sed -i 's|listen =.*|listen = /run/php.sock|' /usr/local/etc/php-fpm.d/zz-docker.conf; \ \ apk add --no-cache git; \ - wget https://getcomposer.org/installer -O - | php -- --install-dir=/usr/local/bin --filename=composer; \ + curl https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \ chmod +x /usr/local/bin/composer; \ cd /var/www/docker-aio; \ rm -r ./php/tests; \ chown www-data:www-data -R /var/www/docker-aio; \ cd php; \ - sudo -u www-data composer install --no-dev; \ - sudo -u www-data composer clear-cache; \ + sudo -E -u www-data composer install --no-dev; \ + sudo -E -u www-data composer clear-cache; \ cd ..; \ rm -f /usr/local/bin/composer; \ chmod -R 770 /var/www/docker-aio; \ @@ -83,46 +82,13 @@ RUN set -ex; \ rm -r php/data; \ rm -r php/session; \ \ - mkdir -p /etc/apache2/certs; \ - cd /etc/apache2/certs; \ - openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 -subj "/C=DE/ST=BE/L=Local/O=Dev/CN=nextcloud.local" -keyout /etc/apache2/certs/ssl.key -out /etc/apache2/certs/ssl.crt; \ - \ - sed -i \ - -e '/^Listen /d' \ - -e 's/^LogLevel .*/LogLevel error/' \ - -e 's|^ErrorLog .*|ErrorLog /proc/self/fd/2|' \ - -e 's/User apache/User www-data/g' \ - -e 's/Group apache/Group www-data/g' \ - -e 's/^#\(LoadModule .*mod_rewrite.so\)/\1/' \ - -e 's/^#\(LoadModule .*mod_headers.so\)/\1/' \ - -e 's/^#\(LoadModule .*mod_env.so\)/\1/' \ - -e 's/^#\(LoadModule .*mod_mime.so\)/\1/' \ - -e 's/^#\(LoadModule .*mod_dir.so\)/\1/' \ - -e 's/^#\(LoadModule .*mod_authz_core.so\)/\1/' \ - -e 's/^#\(LoadModule .*mod_mpm_event.so\)/\1/' \ - -e 's/\(LoadModule .*mod_mpm_worker.so\)/#\1/' \ - -e 's/\(LoadModule .*mod_mpm_prefork.so\)/#\1/' \ - -e 's/\(ScriptAlias \)/#\1/' \ - /etc/apache2/httpd.conf; \ - mkdir -p /etc/apache2/logs; \ - rm /etc/apache2/conf.d/ssl.conf; \ - echo "ServerName localhost" | tee -a /etc/apache2/httpd.conf; \ - grep -q '^LoadModule lbmethod_heartbeat_module' /etc/apache2/conf.d/proxy.conf; \ - sed -i 's|^LoadModule lbmethod_heartbeat_module.*|#LoadModule lbmethod_heartbeat_module|' /etc/apache2/conf.d/proxy.conf; \ - echo "SSLSessionCache nonenotnull" | tee -a /etc/apache2/httpd.conf; \ - echo "LoadModule ssl_module modules/mod_ssl.so" | tee -a /etc/apache2/httpd.conf; \ - echo "LoadModule socache_shmcb_module modules/mod_socache_shmcb.so" | tee -a /etc/apache2/httpd.conf; \ - echo "Include /etc/apache2/sites-available/mastercontainer.conf" | tee -a /etc/apache2/httpd.conf; \ - \ - rm -f /etc/apache2/conf.d/default.conf \ - /etc/apache2/conf.d/userdir.conf \ - /etc/apache2/conf.d/info.conf; \ - \ - rm -rf /var/www/localhost/cgi-bin/; \ mkdir /var/log/supervisord; \ mkdir /var/run/supervisord; -LABEL org.label-schema.vendor="Nextcloud" +# hadolint ignore=DL3048 +LABEL org.label-schema.vendor="Nextcloud" \ + wud.watch="false" \ + com.docker.compose.project="nextcloud-aio" # hadolint ignore=DL3002 USER root diff --git a/Containers/mastercontainer/README.md b/Containers/mastercontainer/README.md index de6b535d..7206a5f3 100644 --- a/Containers/mastercontainer/README.md +++ b/Containers/mastercontainer/README.md @@ -12,8 +12,8 @@ The mastercontainer acts as the central orchestration service for the deployment of all other containers in the Nextcloud All-in-One stack. It hosts: - A dedicated PHP SAPI/backend (php-fpm) for AIO itself (not Nextcloud Server) -- An Apache service for accessing the AIO interface via a self-signed HTTPS VirtualHost on 8080/tcp -- A Caddy reverse proxy service enabling HTTPS access to the AIO frontend on port 8443/tcp. +- A Caddy server enabling self-signed HTTPS access to the AIO frontend on port 8080/tcp. +- A Caddy server enabling trusted HTTPS access to the AIO frontend on port 8443/tcp. - Caddy will automatically issue a Let's Encrypt issued certificate if port 80 and 8443 is open/forwarded and a domain pointer is in place; then, simply open the Nextcloud AIO interface using the domain (`https://your-domain-that-points-to-this-server.tld:8443`). The Let's Encrypt certificate request will diff --git a/Containers/mastercontainer/acme.Caddyfile b/Containers/mastercontainer/acme.Caddyfile new file mode 100644 index 00000000..25fa6ab9 --- /dev/null +++ b/Containers/mastercontainer/acme.Caddyfile @@ -0,0 +1,41 @@ +{ + # auto_https will create redirects for https://{host}:8443 instead of https://{host} + # https redirects are added manually in the http://:80 block + auto_https disable_redirects + + storage file_system { + root /mnt/docker-aio-config/caddy/ + } + + log { + level ERROR + } + + servers { + protocols h1 h2 h2c + } + + on_demand_tls { + ask http://127.0.0.1:9876/ + } + + skip_install_trust +} + +http://:80 { + redir https://{host}{uri} permanent +} + +https://:8443 { + root * /var/www/docker-aio/php/public + encode + php_fastcgi unix//run/php.sock + file_server + + tls { + on_demand + issuer acme { + disable_tlsalpn_challenge + } + } +} diff --git a/Containers/mastercontainer/cron.sh b/Containers/mastercontainer/cron.sh index fc8c4081..67af80e7 100644 --- a/Containers/mastercontainer/cron.sh +++ b/Containers/mastercontainer/cron.sh @@ -45,29 +45,29 @@ while true; do # Check for updates and send notification if yes on saturdays if [ "$(date +%u)" = 6 ]; then - sudo -u www-data php /var/www/docker-aio/php/src/Cron/UpdateNotification.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/UpdateNotification.php fi # Check if AIO is outdated - sudo -u www-data php /var/www/docker-aio/php/src/Cron/OutdatedNotification.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/OutdatedNotification.php # Remove sessions older than 24h find "/mnt/docker-aio-config/session/" -mindepth 1 -mmin +1440 -delete # Remove nextcloud-aio-domaincheck container - if sudo -u www-data docker ps --format "{{.Names}}" --filter "status=exited" | grep -q "^nextcloud-aio-domaincheck$"; then - sudo -u www-data docker container remove nextcloud-aio-domaincheck + if sudo -E -u www-data docker ps --format "{{.Names}}" --filter "status=exited" | grep -q "^nextcloud-aio-domaincheck$"; then + sudo -E -u www-data docker container remove nextcloud-aio-domaincheck fi # Remove dangling images - sudo -u www-data docker image prune --force + sudo -E -u www-data docker image prune --filter "label=org.label-schema.vendor=Nextcloud" --force # Check for available free space - sudo -u www-data php /var/www/docker-aio/php/src/Cron/CheckFreeDiskSpace.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/CheckFreeDiskSpace.php # Remove mastercontainer from default bridge network - if sudo -u www-data docker inspect nextcloud-aio-mastercontainer --format "{{.NetworkSettings.Networks}}" | grep -q "bridge"; then - sudo -u www-data docker network disconnect bridge nextcloud-aio-mastercontainer + if sudo -E -u www-data docker inspect nextcloud-aio-mastercontainer --format "{{.NetworkSettings.Networks}}" | grep -q "bridge"; then + sudo -E -u www-data docker network disconnect bridge nextcloud-aio-mastercontainer fi # Wait 60s so that the whole loop will not be executed again diff --git a/Containers/mastercontainer/daily-backup.sh b/Containers/mastercontainer/daily-backup.sh index 5c97c0ca..89ef3cd5 100644 --- a/Containers/mastercontainer/daily-backup.sh +++ b/Containers/mastercontainer/daily-backup.sh @@ -4,7 +4,7 @@ echo "Daily backup script has started" # Check if initial configuration has been done, otherwise this script should do nothing. CONFIG_FILE=/mnt/docker-aio-config/data/configuration.json -if ! [ -f "$CONFIG_FILE" ] || ! grep -q "wasStartButtonClicked.*1" "$CONFIG_FILE"; then +if ! [ -f "$CONFIG_FILE" ] || (! grep -q "wasStartButtonClicked.*1" "$CONFIG_FILE" && ! grep -q "wasStartButtonClicked.*true" "$CONFIG_FILE"); then echo "Initial configuration via AIO interface not done yet. Exiting..." exit 0 fi @@ -20,11 +20,11 @@ fi if [ "$LOCK_FILE_PRESENT" = 0 ] || ! [ -f "/mnt/docker-aio-config/data/daily_backup_running" ]; then find "/mnt/docker-aio-config/session/" -mindepth 1 -delete fi -sudo -u www-data touch "/mnt/docker-aio-config/data/daily_backup_running" +sudo -E -u www-data touch "/mnt/docker-aio-config/data/daily_backup_running" # Check if apache is running/stopped, watchtower is stopped and backupcontainer is stopped -APACHE_PORT="$(docker inspect nextcloud-aio-apache --format "{{.Config.Env}}" | grep -o 'APACHE_PORT=[0-9]\+' | grep -o '[0-9]\+' | head -1)" -if [ -z "$APACHE_PORT" ]; then +LOCAL_APACHE_PORT="$(docker inspect nextcloud-aio-apache --format "{{.Config.Env}}" | grep -o 'APACHE_PORT=[0-9]\+' | grep -o '[0-9]\+' | head -1)" +if [ -z "$LOCAL_APACHE_PORT" ]; then echo "APACHE_PORT is not set which is not expected..." else # Connect mastercontainer to nextcloud-aio network to make sure that nextcloud-aio-apache is reachable @@ -32,7 +32,7 @@ else docker network connect nextcloud-aio nextcloud-aio-mastercontainer &>/dev/null # Wait for apache to start - while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-apache$" && ! nc -z nextcloud-aio-apache "$APACHE_PORT"; do + while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-apache$" && ! nc -z nextcloud-aio-apache "$LOCAL_APACHE_PORT"; do echo "Waiting for apache to become available" sleep 30 done @@ -50,7 +50,7 @@ done if [ "$AUTOMATIC_UPDATES" = 1 ]; then echo "Starting mastercontainer update..." echo "(The script might get exited due to that. In order to update all the other containers correctly, you need to run this script with the same settings a second time.)" - sudo -u www-data php /var/www/docker-aio/php/src/Cron/UpdateMastercontainer.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/UpdateMastercontainer.php fi # Wait for watchtower to stop @@ -67,20 +67,20 @@ fi # Update container images to reduce downtime later on if [ "$AUTOMATIC_UPDATES" = 1 ]; then echo "Updating container images..." - sudo -u www-data php /var/www/docker-aio/php/src/Cron/PullContainerImages.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/PullContainerImages.php fi # Stop containers if required # shellcheck disable=SC2235 if [ "$CHECK_BACKUP" != 1 ] && ([ "$DAILY_BACKUP" != 1 ] || [ "$STOP_CONTAINERS" = 1 ]); then echo "Stopping containers..." - sudo -u www-data php /var/www/docker-aio/php/src/Cron/StopContainers.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/StopContainers.php fi # Execute the backup itself and some related tasks (also stops the containers) if [ "$DAILY_BACKUP" = 1 ]; then echo "Creating daily backup..." - sudo -u www-data php /var/www/docker-aio/php/src/Cron/CreateBackup.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/CreateBackup.php if ! docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-borgbackup$"; then echo "Something seems to be wrong: the borg container should be started at this step." fi @@ -93,17 +93,17 @@ fi # Execute backup check if [ "$CHECK_BACKUP" = 1 ]; then echo "Starting backup check..." - sudo -u www-data php /var/www/docker-aio/php/src/Cron/CheckBackup.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/CheckBackup.php fi # Start and/or update containers if [ "$AUTOMATIC_UPDATES" = 1 ]; then echo "Starting and updating containers..." - sudo -u www-data php /var/www/docker-aio/php/src/Cron/StartAndUpdateContainers.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/StartAndUpdateContainers.php else if [ "$START_CONTAINERS" = 1 ]; then echo "Starting containers without updating them..." - sudo -u www-data php /var/www/docker-aio/php/src/Cron/StartContainers.php + sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/StartContainers.php fi fi diff --git a/Containers/mastercontainer/healthcheck.sh b/Containers/mastercontainer/healthcheck.sh index 72187591..29ad40a6 100644 --- a/Containers/mastercontainer/healthcheck.sh +++ b/Containers/mastercontainer/healthcheck.sh @@ -2,9 +2,8 @@ if [ -f "/mnt/docker-aio-config/data/configuration.json" ]; then nc -z 127.0.0.1 80 || exit 1 - nc -z 127.0.0.1 8000 || exit 1 nc -z 127.0.0.1 8080 || exit 1 nc -z 127.0.0.1 8443 || exit 1 - nc -z 127.0.0.1 9000 || exit 1 + [ -f /run/php.sock ] || exit 1 nc -z 127.0.0.1 9876 || exit 1 fi diff --git a/Containers/mastercontainer/internal.Caddyfile b/Containers/mastercontainer/internal.Caddyfile new file mode 100644 index 00000000..8e8ea311 --- /dev/null +++ b/Containers/mastercontainer/internal.Caddyfile @@ -0,0 +1,29 @@ +{ + auto_https off + + storage file_system { + root /mnt/docker-aio-config/caddy/ + } + + log { + level ERROR + } + + servers { + protocols h1 h2 + } + + skip_install_trust +} + +https://:8080 { + root * /var/www/docker-aio/php/public + encode + php_fastcgi unix//run/php.sock + file_server + + tls { + on_demand + issuer internal + } +} diff --git a/Containers/mastercontainer/mastercontainer.conf b/Containers/mastercontainer/mastercontainer.conf deleted file mode 100644 index 7d294694..00000000 --- a/Containers/mastercontainer/mastercontainer.conf +++ /dev/null @@ -1,62 +0,0 @@ -Listen 127.0.0.1:8000 -Listen 8080 https - -# Deny access to .ht files - - Require all denied - - -# Http host - - ServerName 127.0.0.1 - - # Add error log - CustomLog /proc/self/fd/1 proxy - LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy - ErrorLog /proc/self/fd/2 - ErrorLogFormat "[%t] [%l] [%E] [client: %{X-Forwarded-For}i] [%M] [%{User-Agent}i]" - LogLevel warn - - # PHP match - - SetHandler "proxy:fcgi://127.0.0.1:9000" - - # Master dir - DocumentRoot /var/www/docker-aio/php/public/ - - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [QSA,L] - Options Indexes FollowSymLinks - Require all granted - AllowOverride All - Options FollowSymLinks MultiViews - Satisfy Any - - Dav off - - - - -# Https host - - # Proxy to https - ProxyPass / http://127.0.0.1:8000/ - ProxyPassReverse / http://127.0.0.1:8000/ - ProxyPreserveHost On - # SSL - SSLCertificateKeyFile /etc/apache2/certs/ssl.key - SSLCertificateFile /etc/apache2/certs/ssl.crt - SSLEngine on - SSLProtocol -all +TLSv1.2 +TLSv1.3 - SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 - SSLHonorCipherOrder off - SSLSessionTickets off - - -# Increase timeout in case e.g. the initial download takes a long time -Timeout 7200 -ProxyTimeout 7200 - -# See https://httpd.apache.org/docs/trunk/mod/core.html#traceenable -TraceEnable Off diff --git a/Containers/mastercontainer/start.sh b/Containers/mastercontainer/start.sh index 616068f3..a1eec5e2 100644 --- a/Containers/mastercontainer/start.sh +++ b/Containers/mastercontainer/start.sh @@ -51,7 +51,7 @@ elif mountpoint -q /var/www/docker-aio/php/containers.json; then echo "If you need to customize things, feel free to use https://github.com/nextcloud/all-in-one/tree/main/manual-install" echo "See https://github.com/nextcloud/all-in-one/blob/main/manual-install/latest.yml" exit 1 -elif ! sudo -u www-data test -r /var/run/docker.sock; then +elif ! sudo -E -u www-data test -r /var/run/docker.sock; then echo "Trying to fix docker.sock permissions internally..." DOCKER_GROUP=$(stat -c '%G' /var/run/docker.sock) DOCKER_GROUP_ID=$(stat -c '%g' /var/run/docker.sock) @@ -69,28 +69,68 @@ elif ! sudo -u www-data test -r /var/run/docker.sock; then groupadd -g "$DOCKER_GROUP_ID" docker usermod -aG docker www-data fi - if ! sudo -u www-data test -r /var/run/docker.sock; then + if ! sudo -E -u www-data test -r /var/run/docker.sock; then print_red "Docker socket is not readable by the www-data user. Cannot continue." exit 1 fi fi -# Check if api version is supported -if ! sudo -u www-data docker info &>/dev/null; then - print_red "Cannot connect to the docker socket. Cannot proceed." - echo "Did you maybe remove group read permissions for the docker socket? AIO needs them in order to access the docker socket." - echo "If SELinux is enabled on your host, see https://github.com/nextcloud/all-in-one#are-there-known-problems-when-selinux-is-enabled" - echo "If you are on TrueNas SCALE, see https://github.com/nextcloud/all-in-one#can-i-run-aio-on-truenas-scale" - exit 1 -fi +# Get default docker api version API_VERSION_FILE="$(find ./ -name DockerActionManager.php | head -1)" API_VERSION="$(grep -oP 'const string API_VERSION.*\;' "$API_VERSION_FILE" | grep -oP '[0-9]+.[0-9]+' | head -1)" +if [ -z "$API_VERSION" ]; then + print_red "Could not get API_VERSION. Something is wrong!" + exit 1 +fi + +# Check if DOCKER_API_VERSION is set globally +if [ -n "$DOCKER_API_VERSION" ]; then + if ! echo "$DOCKER_API_VERSION" | grep -q '^[0-9].[0-9]\+$'; then + print_red "You've set DOCKER_API_VERSION but not to an allowed value. +The string must be a version number like e.g. '1.44'. +It is set to '$DOCKER_API_VERSION'." + exit 1 + fi + print_red "DOCKER_API_VERSION was found to be set to '$DOCKER_API_VERSION'." + print_red "Please note that only v$API_VERSION is officially supported and tested by the maintainers of Nextcloud AIO." + print_red "So you run on your own risk and things might break without warning." +else + # Export docker api version to use it everywhere + export DOCKER_API_VERSION="$API_VERSION" +fi + +# Set a fallback docker api version. Needed for api version check. +# The check will not work otherwise on old docker versions +FALLBACK_DOCKER_API_VERSION="1.41" + +# Check if docker info can be used +if ! sudo -E -u www-data docker info &>/dev/null; then + if ! sudo -E -u www-data DOCKER_API_VERSION="$FALLBACK_DOCKER_API_VERSION" docker info &>/dev/null; then + print_red "Cannot connect to the docker socket. Cannot proceed." + echo "Did you maybe remove group read permissions for the docker socket? AIO needs them in order to access the docker socket." + echo "If SELinux is enabled on your host, see https://github.com/nextcloud/all-in-one#are-there-known-problems-when-selinux-is-enabled" + echo "If you are on TrueNas SCALE, see https://github.com/nextcloud/all-in-one#can-i-run-aio-on-truenas-scale" + echo "On macOS, see https://github.com/nextcloud/all-in-one#how-to-run-aio-on-macos" + echo "Another possibility might be that Docker api v$API_VERSION is not supported by your docker daemon." + echo "In that case, you should report this to https://github.com/nextcloud/all-in-one/issues" + echo "" + exit 1 + fi +fi + +# Docker api version check # shellcheck disable=SC2001 -API_VERSION_NUMB="$(echo "$API_VERSION" | sed 's/\.//')" -LOCAL_API_VERSION_NUMB="$(sudo -u www-data docker version | grep -i "api version" | grep -oP '[0-9]+.[0-9]+' | head -1 | sed 's/\.//')" +API_VERSION_NUMB="$(echo "$DOCKER_API_VERSION" | sed 's/\.//')" +LOCAL_API_VERSION_NUMB="$(sudo -E -u www-data docker version | grep -i "api version" | grep -oP '[0-9]+.[0-9]+' | head -1 | sed 's/\.//')" +if [ -z "$LOCAL_API_VERSION_NUMB" ]; then + LOCAL_API_VERSION_NUMB="$(sudo -E -u www-data DOCKER_API_VERSION="$FALLBACK_DOCKER_API_VERSION" docker version | grep -i "api version" | grep -oP '[0-9]+.[0-9]+' | head -1 | sed 's/\.//')" +fi if [ -n "$LOCAL_API_VERSION_NUMB" ] && [ -n "$API_VERSION_NUMB" ]; then if ! [ "$LOCAL_API_VERSION_NUMB" -ge "$API_VERSION_NUMB" ]; then - print_red "Docker API v$API_VERSION is not supported by your docker engine. Cannot proceed. Please upgrade your docker engine if you want to run Nextcloud AIO!" + print_red "Docker API v$DOCKER_API_VERSION is not supported by your docker engine. Cannot proceed. Please upgrade your docker engine if you want to run Nextcloud AIO!" + echo "Alternatively, set the DOCKER_API_VERSION environmental variable to a compatible version." + echo "However please note that only v$API_VERSION is officially supported and tested by the maintainers of Nextcloud AIO." + echo "See https://github.com/nextcloud/all-in-one#how-to-adjust-the-internally-used-docker-api-version" exit 1 fi else @@ -99,7 +139,7 @@ else fi # Check Storage drivers -STORAGE_DRIVER="$(sudo -u www-data docker info | grep "Storage Driver")" +STORAGE_DRIVER="$(sudo -E -u www-data docker info | grep "Storage Driver")" # Check if vfs is used: https://github.com/nextcloud/all-in-one/discussions/1467 if echo "$STORAGE_DRIVER" | grep -q vfs; then echo "$STORAGE_DRIVER" @@ -110,23 +150,23 @@ elif echo "$STORAGE_DRIVER" | grep -q fuse-overlayfs; then fi # Check if snap install -if sudo -u www-data docker info | grep "Docker Root Dir" | grep "/var/snap/docker/"; then +if sudo -E -u www-data docker info | grep "Docker Root Dir" | grep "/var/snap/docker/"; then print_red "Warning: It looks like your installation uses docker installed via snap." print_red "This comes with some limitations and is disrecommended by the docker maintainers." print_red "See for example https://github.com/nextcloud/all-in-one/discussions/4890#discussioncomment-10386752" fi # Check if startup command was executed correctly -if ! sudo -u www-data docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-mastercontainer$"; then +if ! sudo -E -u www-data docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-mastercontainer$"; then print_red "It seems like you did not give the mastercontainer the correct name? (The 'nextcloud-aio-mastercontainer' container was not found.) Using a different name is not supported since mastercontainer updates will not work in that case! If you are on docker swarm and try to run AIO, see https://github.com/nextcloud/all-in-one#can-i-run-this-with-docker-swarm" exit 1 -elif ! sudo -u www-data docker volume ls --format "{{.Name}}" | grep -q "^nextcloud_aio_mastercontainer$"; then +elif ! sudo -E -u www-data docker volume ls --format "{{.Name}}" | grep -q "^nextcloud_aio_mastercontainer$"; then print_red "It seems like you did not give the mastercontainer volume the correct name? (The 'nextcloud_aio_mastercontainer' volume was not found.) Using a different name is not supported since the built-in backup solution will not work in that case!" exit 1 -elif ! sudo -u www-data docker inspect nextcloud-aio-mastercontainer | grep -q "nextcloud_aio_mastercontainer"; then +elif ! sudo -E -u www-data docker inspect nextcloud-aio-mastercontainer --format '{{.Mounts}}' | grep -q " nextcloud_aio_mastercontainer "; then print_red "It seems like you did not attach the 'nextcloud_aio_mastercontainer' volume to the mastercontainer? This is not supported since the built-in backup solution will not work in that case!" exit 1 @@ -331,36 +371,6 @@ chown www-data:www-data -R /mnt/docker-aio-config/session/ chown www-data:www-data -R /mnt/docker-aio-config/caddy/ chown root:root -R /mnt/docker-aio-config/certs/ -# Don't allow access to the AIO interface from the Nextcloud container -# Probably more cosmetic than anything but at least an attempt -if ! grep -q '# nextcloud-aio-block' /etc/apache2/httpd.conf; then - cat << APACHE_CONF >> /etc/apache2/httpd.conf -# nextcloud-aio-block-start - -order allow,deny -deny from nextcloud-aio-nextcloud.nextcloud-aio -allow from all - -# nextcloud-aio-block-end -APACHE_CONF -fi - -# Adjust certs -GENERATED_CERTS="/mnt/docker-aio-config/certs" -TMP_CERTS="/etc/apache2/certs" -mkdir -p "$GENERATED_CERTS" -cd "$GENERATED_CERTS" || exit 1 -if ! [ -f ./ssl.crt ] && ! [ -f ./ssl.key ]; then - openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 -subj "/C=DE/ST=BE/L=Local/O=Dev/CN=nextcloud.local" -keyout ./ssl.key -out ./ssl.crt -fi -if [ -f ./ssl.crt ] && [ -f ./ssl.key ]; then - cd "$TMP_CERTS" || exit 1 - rm ./ssl.crt - rm ./ssl.key - cp "$GENERATED_CERTS/ssl.crt" ./ - cp "$GENERATED_CERTS/ssl.key" ./ -fi - print_green "Initial startup of Nextcloud All-in-One complete! You should be able to open the Nextcloud AIO Interface now on port 8080 of this server! E.g. https://internal.ip.of.this.server:8080 @@ -372,8 +382,8 @@ https://your-domain-that-points-to-this-server.tld:8443" # Set the timezone to Etc/UTC export TZ=Etc/UTC -# Fix apache startup -rm -f /var/run/apache2/httpd.pid +# Remove unused certs +rm -vrf /mnt/docker-aio-config/certs # Fix caddy startup if [ -d "/mnt/docker-aio-config/caddy/locks" ]; then @@ -381,7 +391,8 @@ if [ -d "/mnt/docker-aio-config/caddy/locks" ]; then fi # Fix the Caddyfile format -caddy fmt --overwrite /Caddyfile +caddy fmt --overwrite /acme.Caddyfile +caddy fmt --overwrite /internal.Caddyfile # Fix caddy log chmod 777 /root diff --git a/Containers/mastercontainer/supervisord.conf b/Containers/mastercontainer/supervisord.conf index fa5d0845..9fbb9516 100644 --- a/Containers/mastercontainer/supervisord.conf +++ b/Containers/mastercontainer/supervisord.conf @@ -16,20 +16,20 @@ stderr_logfile_maxbytes=0 command=php-fpm user=root -[program:apache] -# Stdout logging is disabled as otherwise the logs are spammed -stdout_logfile=NONE -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -command=httpd -DFOREGROUND -user=root - -[program:caddy] +[program:caddy-internal] stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 -command=/usr/bin/caddy run --config /Caddyfile +command=/usr/bin/caddy run --config /internal.Caddyfile +user=www-data + +[program:caddy-acme] +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +command=/usr/bin/caddy run --config /acme.Caddyfile user=www-data [program:cron] diff --git a/Containers/nextcloud/Dockerfile b/Containers/nextcloud/Dockerfile index 4eae8029..c6d9bf7e 100644 --- a/Containers/nextcloud/Dockerfile +++ b/Containers/nextcloud/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM php:8.3.26-fpm-alpine3.22 +FROM php:8.3.30-fpm-alpine3.23 ENV PHP_MEMORY_LIMIT=512M ENV PHP_UPLOAD_LIMIT=16G @@ -8,7 +8,7 @@ ENV SOURCE_LOCATION=/usr/src/nextcloud ENV REDIS_DB_INDEX=0 # AIO settings start # Do not remove or change this line! -ENV NEXTCLOUD_VERSION=31.0.9 +ENV NEXTCLOUD_VERSION=32.0.5 ENV AIO_TOKEN=123456 ENV AIO_URL=localhost # AIO settings end # Do not remove or change this line! @@ -83,16 +83,17 @@ RUN set -ex; \ \ # pecl will claim success even if one install fails, so we need to perform each install separately pecl install -o igbinary-3.2.16; \ - pecl install APCu-5.1.27; \ + pecl install APCu-5.1.28; \ pecl install -D 'enable-memcached-igbinary="yes"' memcached-3.4.0; \ - pecl install -oD 'enable-redis-igbinary="yes" enable-redis-zstd="yes" enable-redis-lz4="yes"' redis-6.2.0; \ - pecl install -o imagick-3.8.0; \ + pecl install -oD 'enable-redis-igbinary="yes" enable-redis-zstd="yes" enable-redis-lz4="yes"' redis-6.3.0; \ + pecl install -o imagick-3.8.1; \ \ docker-php-ext-enable \ igbinary \ apcu \ memcached \ redis \ + imagick \ ; \ rm -r /tmp/pear; \ \ @@ -138,7 +139,7 @@ RUN set -ex; \ \ { \ echo 'session.save_handler = redis'; \ - echo 'session.save_path = "tcp://${REDIS_HOST}:6379?database=${REDIS_DB_INDEX}${REDIS_USER_AUTH}&auth[]=${REDIS_HOST_PASSWORD}"'; \ + echo 'session.save_path = "tcp://${REDIS_HOST}:${REDIS_PORT}?database=${REDIS_DB_INDEX}${REDIS_USER_AUTH}&auth[]=${REDIS_HOST_PASSWORD}"'; \ echo 'redis.session.locking_enabled = 1'; \ echo 'redis.session.lock_retries = -1'; \ echo 'redis.session.lock_wait_time = 10000'; \ @@ -156,7 +157,7 @@ RUN set -ex; \ ; \ \ curl -fsSL -o nextcloud.tar.bz2 \ - "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2"; \ + "https://github.com/nextcloud-releases/server/releases/download/v${NEXTCLOUD_VERSION}/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2"; \ curl -fsSL -o nextcloud.tar.bz2.asc \ "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2.asc"; \ export GNUPGHOME="$(mktemp -d)"; \ @@ -230,7 +231,6 @@ RUN set -ex; \ sudo \ grep \ nodejs \ - libreoffice \ bind-tools \ imagemagick \ imagemagick-svg \ @@ -252,6 +252,7 @@ RUN set -ex; \ chmod 777 -R /usr/local/etc/php/conf.d && \ chmod 777 -R /usr/local/etc/php-fpm.d && \ chmod -R 777 /tmp; \ + chmod -R 777 /etc/openldap; \ \ mkdir -p /nc-updater; \ chmod -R 777 /nc-updater @@ -263,4 +264,5 @@ CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/nextcloud/config/certificates-bundle.config.php b/Containers/nextcloud/config/certificates-bundle.config.php new file mode 100644 index 00000000..cc05b06a --- /dev/null +++ b/Containers/nextcloud/config/certificates-bundle.config.php @@ -0,0 +1,5 @@ + array( 'mode' => 'verify-ca', - 'rootcert' => '/var/www/html/data/certificates/POSTGRES', + 'rootcert' => '/var/www/html/data/certificates/ca-bundle.crt', ), ); } +if (getenv('NEXTCLOUD_TRUSTED_CERTIFICATES_MYSQL')) { + $CONFIG = array( + 'dbdriveroptions' => array( + PDO::MYSQL_ATTR_SSL_CA => '/var/www/html/data/certificates/ca-bundle.crt', + ), + ); +} + diff --git a/Containers/nextcloud/config/redis.config.php b/Containers/nextcloud/config/redis.config.php index 80848974..b59fe4ea 100644 --- a/Containers/nextcloud/config/redis.config.php +++ b/Containers/nextcloud/config/redis.config.php @@ -9,10 +9,8 @@ if (getenv('REDIS_HOST')) { ), ); - if (getenv('REDIS_HOST_PORT')) { - $CONFIG['redis']['port'] = (int) getenv('REDIS_HOST_PORT'); - } elseif (getenv('REDIS_HOST')[0] != '/') { - $CONFIG['redis']['port'] = 6379; + if (getenv('REDIS_PORT')) { + $CONFIG['redis']['port'] = (int) getenv('REDIS_PORT'); } if (getenv('REDIS_DB_INDEX')) { diff --git a/Containers/nextcloud/config/s3.config.php b/Containers/nextcloud/config/s3.config.php index 04e606be..6ea06697 100644 --- a/Containers/nextcloud/config/s3.config.php +++ b/Containers/nextcloud/config/s3.config.php @@ -6,9 +6,11 @@ if (getenv('OBJECTSTORE_S3_BUCKET')) { $autocreate = getenv('OBJECTSTORE_S3_AUTOCREATE'); $multibucket = getenv('OBJECTSTORE_S3_MULTIBUCKET'); $CONFIG = array( - $multibucket === 'true' ? 'objectstore_multibucket' : 'objectstore' => array( + 'objectstore' => array( 'class' => '\OC\Files\ObjectStore\S3', 'arguments' => array( + 'multibucket' => $multibucket === 'true', + 'num_buckets' => (int)getenv('OBJECTSTORE_S3_NUM_BUCKETS') ?: 64, 'bucket' => getenv('OBJECTSTORE_S3_BUCKET'), 'key' => getenv('OBJECTSTORE_S3_KEY') ?: '', 'secret' => getenv('OBJECTSTORE_S3_SECRET') ?: '', @@ -22,7 +24,8 @@ if (getenv('OBJECTSTORE_S3_BUCKET')) { // required for some non Amazon S3 implementations 'use_path_style' => strtolower($use_path) === 'true', // required for older protocol versions - 'legacy_auth' => strtolower($use_legacyauth) === 'true' + 'legacy_auth' => strtolower($use_legacyauth) === 'true', + 'use_nextcloud_bundle' => 1, ) ) ); diff --git a/Containers/nextcloud/config/smtp.config.php b/Containers/nextcloud/config/smtp.config.php index 40cfdf94..b57f9b68 100644 --- a/Containers/nextcloud/config/smtp.config.php +++ b/Containers/nextcloud/config/smtp.config.php @@ -18,3 +18,14 @@ if (getenv('SMTP_HOST') && getenv('MAIL_FROM_ADDRESS') && getenv('MAIL_DOMAIN')) $CONFIG['mail_smtppassword'] = ''; } } + +if (getenv('NEXTCLOUD_TRUSTED_CERTIFICATES_MAILER')) { + $CONFIG = array( + 'mail_smtpstreamoptions' => array( + 'ssl' => array( + 'verify_peer_name' => false, + 'cafile' => '/var/www/html/data/certificates/ca-bundle.crt', + ) + ) + ); +} diff --git a/Containers/nextcloud/entrypoint.sh b/Containers/nextcloud/entrypoint.sh index fd8e6136..d4b4f253 100644 --- a/Containers/nextcloud/entrypoint.sh +++ b/Containers/nextcloud/entrypoint.sh @@ -20,6 +20,64 @@ run_upgrade_if_needed_due_to_app_update() { fi } +# Create cert bundle +if env | grep -q NEXTCLOUD_TRUSTED_CERTIFICATES_; then + + # Enable debug mode + set -x + + # Default vars + CERTIFICATES_ROOT_DIR="/var/www/html/data/certificates" + CERTIFICATE_BUNDLE="/var/www/html/data/certificates/ca-bundle.crt" + + # Remove old root certs and recreate them with current ones + rm -rf "$CERTIFICATES_ROOT_DIR" + mkdir -p "$CERTIFICATES_ROOT_DIR" + + # Retrieve default root cert bundle + if ! [ -f "$SOURCE_LOCATION/resources/config/ca-bundle.crt" ]; then + echo "Root ca-bundle not found. Only concattening configured NEXTCLOUD_TRUSTED_CERTIFICATES files!" + # Recreate cert file + touch "$CERTIFICATE_BUNDLE" + else + # Write default bundle to the target ca file + cat "$SOURCE_LOCATION/resources/config/ca-bundle.crt" > "$CERTIFICATE_BUNDLE" + fi + + # Iterate through certs + TRUSTED_CERTIFICATES="$(env | grep NEXTCLOUD_TRUSTED_CERTIFICATES_ | grep -oP '^[A-Z_a-z0-9]+')" + mapfile -t TRUSTED_CERTIFICATES <<< "$TRUSTED_CERTIFICATES" + for certificate in "${TRUSTED_CERTIFICATES[@]}"; do + + # Create new line + echo "" >> "$CERTIFICATE_BUNDLE" + + # Check if variable is an actual cert + if echo "${!certificate}" | grep -q "BEGIN CERTIFICATE" && echo "${!certificate}" | grep -q "END CERTIFICATE"; then + # Write out cert to bundle + echo "${!certificate}" >> "$CERTIFICATE_BUNDLE" + fi + + # Create file in cert dir for extra logic in other places + if ! [ -f "$CERTIFICATES_ROOT_DIR/$CERTIFICATE_NAME" ]; then + touch "$CERTIFICATES_ROOT_DIR/$CERTIFICATE_NAME" + fi + + done + + # Backwards compatibility with older instances + if [ -f "/var/www/html/config/postgres.config.php" ]; then + sed -i "s|/var/www/html/data/certificates/POSTGRES|/var/www/html/data/certificates/ca-bundle.crt|" /var/www/html/config/postgres.config.php + sed -i "s|/var/www/html/data/certificates/MYSQL|/var/www/html/data/certificates/ca-bundle.crt|" /var/www/html/config/postgres.config.php + fi + + # Print out bundle one last time + cat "$CERTIFICATE_BUNDLE" + + # Disable debug mode + set +x +fi + # Adjust DATABASE_TYPE to by Nextcloud supported value if [ "$DATABASE_TYPE" = postgres ]; then export DATABASE_TYPE=pgsql @@ -27,7 +85,7 @@ fi # Only start container if Redis is accessible # shellcheck disable=SC2153 -while ! nc -z "$REDIS_HOST" "6379"; do +while ! nc -z "$REDIS_HOST" "$REDIS_PORT"; do echo "Waiting for Redis to start..." sleep 5 done @@ -124,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/ @@ -279,12 +340,6 @@ if ! [ -f "$NEXTCLOUD_DATA_DIR/skip.update" ]; then ); EOF - # Write out postgres root cert - if [ -n "$NEXTCLOUD_TRUSTED_CERTIFICATES_POSTGRES" ]; then - mkdir /var/www/html/data/certificates - echo "$NEXTCLOUD_TRUSTED_CERTIFICATES_POSTGRES" > "/var/www/html/data/certificates/POSTGRES" - fi - echo "Installing with $DATABASE_TYPE database" # Set a default value for POSTGRES_PORT if [ -z "$POSTGRES_PORT" ]; then @@ -645,24 +700,6 @@ else fi # AIO app end # Do not remove or change this line! -# Allow to add custom certs to Nextcloud's trusted cert store -if env | grep -q NEXTCLOUD_TRUSTED_CERTIFICATES_; then - set -x - TRUSTED_CERTIFICATES="$(env | grep NEXTCLOUD_TRUSTED_CERTIFICATES_ | grep -oP '^[A-Z_a-z0-9]+')" - mapfile -t TRUSTED_CERTIFICATES <<< "$TRUSTED_CERTIFICATES" - CERTIFICATES_ROOT_DIR="/var/www/html/data/certificates" - mkdir -p "$CERTIFICATES_ROOT_DIR" - for certificate in "${TRUSTED_CERTIFICATES[@]}"; do - # shellcheck disable=SC2001 - CERTIFICATE_NAME="$(echo "$certificate" | sed 's|^NEXTCLOUD_TRUSTED_CERTIFICATES_||')" - if ! [ -f "$CERTIFICATES_ROOT_DIR/$CERTIFICATE_NAME" ]; then - echo "${!certificate}" > "$CERTIFICATES_ROOT_DIR/$CERTIFICATE_NAME" - php /var/www/html/occ security:certificates:import "$CERTIFICATES_ROOT_DIR/$CERTIFICATE_NAME" - fi - done - set +x -fi - # Notify push if ! [ -d "/var/www/html/custom_apps/notify_push" ]; then php /var/www/html/occ app:install notify_push @@ -741,7 +778,7 @@ if [ "$COLLABORA_ENABLED" = 'yes' ]; then echo "No IPv6 address found for $COLLABORA_HOST." fi if [ -n "$COLLABORA_ALLOW_LIST" ]; then - PRIVATE_IP_RANGES='127.0.0.1/8,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8,fd00::/8,::1' + PRIVATE_IP_RANGES='127.0.0.0/8,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8,100.64.0.0/10,fd00::/8,::1/128' if ! echo "$COLLABORA_ALLOW_LIST" | grep -q "$PRIVATE_IP_RANGES"; then COLLABORA_ALLOW_LIST+=",$PRIVATE_IP_RANGES" fi @@ -769,33 +806,39 @@ if [ "$ONLYOFFICE_ENABLED" = 'yes' ]; then ONLYOFFICE_PORT=443 fi - # Wait for OnlyOffice to become available - while ! nc -z "$ONLYOFFICE_HOST" "$ONLYOFFICE_PORT"; do + count=0 + while ! nc -z "$ONLYOFFICE_HOST" "$ONLYOFFICE_PORT" && [ "$count" -lt 90 ]; do echo "Waiting for OnlyOffice to become available..." + count=$((count+5)) sleep 5 done + if [ "$count" -ge 90 ]; then + bash /notify.sh "Onlyoffice did not start in time!" "Skipping initialization and disabling onlyoffice app." + php /var/www/html/occ app:disable onlyoffice + else + # Install or enable OnlyOffice app as needed + if ! [ -d "/var/www/html/custom_apps/onlyoffice" ]; then + php /var/www/html/occ app:install onlyoffice + elif [ "$(php /var/www/html/occ config:app:get onlyoffice enabled)" != "yes" ]; then + php /var/www/html/occ app:enable onlyoffice + elif [ "$SKIP_UPDATE" != 1 ]; then + php /var/www/html/occ app:update onlyoffice + fi - # Install or enable OnlyOffice app as needed - if ! [ -d "/var/www/html/custom_apps/onlyoffice" ]; then - php /var/www/html/occ app:install onlyoffice - elif [ "$(php /var/www/html/occ config:app:get onlyoffice enabled)" != "yes" ]; then - php /var/www/html/occ app:enable onlyoffice - elif [ "$SKIP_UPDATE" != 1 ]; then - php /var/www/html/occ app:update onlyoffice + # Set OnlyOffice configuration + php /var/www/html/occ config:system:set onlyoffice editors_check_interval --value="0" --type=integer + php /var/www/html/occ config:system:set onlyoffice jwt_secret --value="$ONLYOFFICE_SECRET" + php /var/www/html/occ config:app:set onlyoffice jwt_secret --value="$ONLYOFFICE_SECRET" + php /var/www/html/occ config:system:set onlyoffice jwt_header --value="AuthorizationJwt" + + # Adjust the OnlyOffice host if using internal pattern + if echo "$ONLYOFFICE_HOST" | grep -q "nextcloud-.*-onlyoffice"; then + ONLYOFFICE_HOST="$NC_DOMAIN/onlyoffice" + export ONLYOFFICE_HOST + fi + + php /var/www/html/occ config:app:set onlyoffice DocumentServerUrl --value="https://$ONLYOFFICE_HOST" fi - - # Set OnlyOffice configuration - php /var/www/html/occ config:system:set onlyoffice jwt_secret --value="$ONLYOFFICE_SECRET" - php /var/www/html/occ config:app:set onlyoffice jwt_secret --value="$ONLYOFFICE_SECRET" - php /var/www/html/occ config:system:set onlyoffice jwt_header --value="AuthorizationJwt" - - # Adjust the OnlyOffice host if using internal pattern - if echo "$ONLYOFFICE_HOST" | grep -q "nextcloud-.*-onlyoffice"; then - ONLYOFFICE_HOST="$NC_DOMAIN/onlyoffice" - export ONLYOFFICE_HOST - fi - - php /var/www/html/occ config:app:set onlyoffice DocumentServerUrl --value="https://$ONLYOFFICE_HOST" else # Remove OnlyOffice app if disabled and removal is requested if [ "$REMOVE_DISABLED_APPS" = yes ] && \ @@ -854,7 +897,9 @@ if [ -d "/var/www/html/custom_apps/spreed" ]; then RECORDING_SERVERS_STRING="{\"servers\":[{\"server\":\"http://$TALK_RECORDING_HOST:1234/\",\"verify\":true}],\"secret\":\"$RECORDING_SECRET\"}" php /var/www/html/occ config:app:set spreed recording_servers --value="$RECORDING_SERVERS_STRING" else - php /var/www/html/occ config:app:delete spreed recording_servers + if [ "$REMOVE_DISABLED_APPS" = yes ]; then + php /var/www/html/occ config:app:delete spreed recording_servers + fi fi fi @@ -867,7 +912,7 @@ if [ "$CLAMAV_ENABLED" = 'yes' ]; then sleep 5 done if [ "$count" -ge 90 ]; then - echo "ClamAV did not start in time. Skipping initialization and disabling files_antivirus app." + bash /notify.sh "ClamAV did not start in time!" "Skipping initialization and disabling files_antivirus app." php /var/www/html/occ app:disable files_antivirus else if ! [ -d "/var/www/html/custom_apps/files_antivirus" ]; then @@ -880,8 +925,9 @@ if [ "$CLAMAV_ENABLED" = 'yes' ]; then php /var/www/html/occ config:app:set files_antivirus av_mode --value="daemon" php /var/www/html/occ config:app:set files_antivirus av_port --value="3310" php /var/www/html/occ config:app:set files_antivirus av_host --value="$CLAMAV_HOST" - php /var/www/html/occ config:app:set files_antivirus av_stream_max_length --value="$CLAMAV_MAX_SIZE" - php /var/www/html/occ config:app:set files_antivirus av_max_file_size --value="$CLAMAV_MAX_SIZE" + # av_stream_max_length must be synced with StreamMaxLength inside clamav + php /var/www/html/occ config:app:set files_antivirus av_stream_max_length --value="2147483648" + php /var/www/html/occ config:app:set files_antivirus av_max_file_size --value="-1" php /var/www/html/occ config:app:set files_antivirus av_infected_action --value="only_log" if [ -n "$CLAMAV_BLOCKLISTED_DIRECTORIES" ]; then php /var/www/html/occ config:app:set files_antivirus av_blocklisted_directories --value="$CLAMAV_BLOCKLISTED_DIRECTORIES" @@ -924,6 +970,9 @@ if [ "$FULLTEXTSEARCH_ENABLED" = 'yes' ]; then php /var/www/html/occ app:disable fulltextsearch_elasticsearch php /var/www/html/occ app:disable files_fulltextsearch else + if [ -z "$FULLTEXTSEARCH_PROTOCOL" ]; then + FULLTEXTSEARCH_PROTOCOL="http" + fi if ! [ -d "/var/www/html/custom_apps/fulltextsearch" ]; then php /var/www/html/occ app:install fulltextsearch elif [ "$(php /var/www/html/occ config:app:get fulltextsearch enabled)" != "yes" ]; then @@ -946,8 +995,8 @@ if [ "$FULLTEXTSEARCH_ENABLED" = 'yes' ]; then php /var/www/html/occ app:update files_fulltextsearch fi php /var/www/html/occ fulltextsearch:configure '{"search_platform":"OCA\\FullTextSearch_Elasticsearch\\Platform\\ElasticSearchPlatform"}' - php /var/www/html/occ fulltextsearch_elasticsearch:configure "{\"elastic_host\":\"http://$FULLTEXTSEARCH_USER:$FULLTEXTSEARCH_PASSWORD@$FULLTEXTSEARCH_HOST:$FULLTEXTSEARCH_PORT\",\"elastic_index\":\"$FULLTEXTSEARCH_INDEX\"}" - php /var/www/html/occ files_fulltextsearch:configure "{\"files_pdf\":\"1\",\"files_office\":\"1\"}" + php /var/www/html/occ fulltextsearch_elasticsearch:configure "{\"elastic_host\":\"$FULLTEXTSEARCH_PROTOCOL://$FULLTEXTSEARCH_USER:$FULLTEXTSEARCH_PASSWORD@$FULLTEXTSEARCH_HOST:$FULLTEXTSEARCH_PORT\",\"elastic_index\":\"$FULLTEXTSEARCH_INDEX\"}" + php /var/www/html/occ files_fulltextsearch:configure "{\"files_pdf\":true,\"files_office\":true}" # Do the index if ! [ -f "$NEXTCLOUD_DATA_DIR/fts-index.done" ]; then diff --git a/Containers/nextcloud/run-exec-commands.sh b/Containers/nextcloud/run-exec-commands.sh index 9ef6ba69..e8066881 100644 --- a/Containers/nextcloud/run-exec-commands.sh +++ b/Containers/nextcloud/run-exec-commands.sh @@ -19,11 +19,6 @@ else echo "Activating Collabora config..." php /var/www/html/occ richdocuments:activate-config fi - # OnlyOffice must work also if using manual-install - if [ "$ONLYOFFICE_ENABLED" = yes ]; then - echo "Activating OnlyOffice config..." - php /var/www/html/occ onlyoffice:documentserver --check - fi fi signal_handler() { diff --git a/Containers/nextcloud/start.sh b/Containers/nextcloud/start.sh index 37aa4d98..a5f38534 100644 --- a/Containers/nextcloud/start.sh +++ b/Containers/nextcloud/start.sh @@ -8,7 +8,7 @@ fi # Only start container if database is accessible # POSTGRES_HOST must be set in the containers env vars and POSTGRES_PORT has a default above # shellcheck disable=SC2153 -while ! sudo -u www-data nc -z "$POSTGRES_HOST" "$POSTGRES_PORT"; do +while ! sudo -E -u www-data nc -z "$POSTGRES_HOST" "$POSTGRES_PORT"; do echo "Waiting for database to start..." sleep 5 done @@ -25,7 +25,7 @@ fi # Fix false database connection on old instances if [ -f "/var/www/html/config/config.php" ]; then sleep 2 - while ! sudo -u www-data psql -d "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" -c "select now()"; do + while ! sudo -E -u www-data psql -d "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" -c "select now()"; do echo "Waiting for the database to start..." sleep 5 done @@ -56,12 +56,12 @@ fi set +x # Check datadir permissions -sudo -u www-data touch "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" &>/dev/null +sudo -E -u www-data touch "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" &>/dev/null if ! [ -f "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" ]; then chown -R www-data:root "$NEXTCLOUD_DATA_DIR" chmod 750 -R "$NEXTCLOUD_DATA_DIR" fi -sudo -u www-data rm -f "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" +sudo -E -u www-data rm -f "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" # Install additional dependencies if [ -n "$ADDITIONAL_APKS" ]; then @@ -86,13 +86,15 @@ fi # Install additional php extensions if [ -n "$ADDITIONAL_PHP_EXTENSIONS" ]; then if ! [ -f "/additional-php-extensions-are-installed" ]; then + # Allow to disable imagick without having to enable it each time + if ! echo "$ADDITIONAL_PHP_EXTENSIONS" | grep -q imagick; then + # Remove the ini file as there is no docker-php-ext-disable script available + rm /usr/local/etc/php/conf.d/docker-php-ext-imagick.ini + fi read -ra ADDITIONAL_PHP_EXTENSIONS_ARRAY <<< "$ADDITIONAL_PHP_EXTENSIONS" for app in "${ADDITIONAL_PHP_EXTENSIONS_ARRAY[@]}"; do if [ "$app" = imagick ]; then - echo "Enabling Imagick..." - if ! docker-php-ext-enable imagick >/dev/null; then - echo "Could not install PHP extension imagick!" - fi + # imagick is already enabled by default, so does not need to be enabled anymore. continue fi # shellcheck disable=SC2086 diff --git a/Containers/notify-push/Dockerfile b/Containers/notify-push/Dockerfile index 812ec840..838c847c 100644 --- a/Containers/notify-push/Dockerfile +++ b/Containers/notify-push/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.22.2 +FROM alpine:3.23.3 COPY --chmod=775 start.sh /start.sh COPY --chmod=775 healthcheck.sh /healthcheck.sh @@ -11,6 +11,7 @@ RUN set -ex; \ netcat-openbsd \ tzdata \ bash \ + jq \ openssl; \ # Give root a random password echo "root:$(openssl rand -base64 12)" | chpasswd; \ @@ -22,4 +23,5 @@ ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/notify-push/start.sh b/Containers/notify-push/start.sh index 859c6309..26d74333 100644 --- a/Containers/notify-push/start.sh +++ b/Containers/notify-push/start.sh @@ -52,6 +52,10 @@ fi if [ -z "$REDIS_DB_INDEX" ]; then REDIS_DB_INDEX=0 fi +# Set a default value for REDIS_PORT +if [ -z "$REDIS_PORT" ]; then + REDIS_PORT=6379 +fi # Set a default for db type if [ -z "$DATABASE_TYPE" ]; then DATABASE_TYPE=postgres @@ -66,14 +70,21 @@ if [ "$POSTGRES_USER" = nextcloud ]; then export POSTGRES_USER fi +# URL-encode passwords +POSTGRES_PASSWORD="$(jq -rn --arg v "$POSTGRES_PASSWORD" '$v|@uri')" +REDIS_HOST_PASSWORD="$(jq -rn --arg v "$REDIS_HOST_PASSWORD" '$v|@uri')" + # Postgres root cert if [ -f "/nextcloud/data/certificates/POSTGRES" ]; then - POSTGRES_CERT="?sslmode=verify-ca&sslrootcert=/nextcloud/data/certificates/POSTGRES" + CERT_OPTIONS="?sslmode=verify-ca&sslrootcert=/nextcloud/data/certificates/ca-bundle.crt" +# Mysql root cert +elif [ -f "/nextcloud/data/certificates/MYSQL" ]; then + CERT_OPTIONS="?sslmode=verify-ca&ssl-ca=/nextcloud/data/certificates/ca-bundle.crt" fi # Set sensitive values as env -export DATABASE_URL="$DATABASE_TYPE://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB$POSTGRES_CERT" -export REDIS_URL="redis://$REDIS_USER:$REDIS_HOST_PASSWORD@$REDIS_HOST/$REDIS_DB_INDEX" +export DATABASE_URL="$DATABASE_TYPE://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB$CERT_OPTIONS" +export REDIS_URL="redis://$REDIS_USER:$REDIS_HOST_PASSWORD@$REDIS_HOST:$REDIS_PORT/$REDIS_DB_INDEX" # Run it /nextcloud/custom_apps/notify_push/bin/"$CPU_ARCH"/notify_push \ diff --git a/Containers/onlyoffice/Dockerfile b/Containers/onlyoffice/Dockerfile index be1d580f..13b4d456 100644 --- a/Containers/onlyoffice/Dockerfile +++ b/Containers/onlyoffice/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:latest # From https://github.com/ONLYOFFICE/Docker-DocumentServer/blob/master/Dockerfile -FROM onlyoffice/documentserver:9.1.0.1 +FROM onlyoffice/documentserver:9.2.1.1 # USER root is probably used @@ -8,4 +8,5 @@ COPY --chmod=775 healthcheck.sh /healthcheck.sh HEALTHCHECK --start-period=60s --retries=9 CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/postgresql/Dockerfile b/Containers/postgresql/Dockerfile index 2533a5d1..56090f26 100644 --- a/Containers/postgresql/Dockerfile +++ b/Containers/postgresql/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:latest -# From https://github.com/docker-library/postgres/blob/master/17/alpine3.22/Dockerfile -FROM postgres:17.6-alpine +# From https://github.com/docker-library/postgres/blob/master/17/alpine3.23/Dockerfile +FROM postgres:17.7-alpine COPY --chmod=775 start.sh /start.sh COPY --chmod=775 healthcheck.sh /healthcheck.sh @@ -44,4 +44,5 @@ ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/redis/Dockerfile b/Containers/redis/Dockerfile index 8cb0f973..cc9181ad 100644 --- a/Containers/redis/Dockerfile +++ b/Containers/redis/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:latest -# From https://github.com/docker-library/redis/blob/master/7.2/alpine/Dockerfile -FROM redis:7.2.11-alpine +# From https://github.com/redis/docker-library-redis/blob/release/8.2/alpine/Dockerfile +FROM redis:8.2.3-alpine COPY --chmod=775 start.sh /start.sh @@ -21,4 +21,5 @@ ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/talk-recording/Dockerfile b/Containers/talk-recording/Dockerfile index 83b7859b..8df5b89e 100644 --- a/Containers/talk-recording/Dockerfile +++ b/Containers/talk-recording/Dockerfile @@ -1,10 +1,10 @@ # syntax=docker/dockerfile:latest -FROM python:3.14.0-alpine3.22 +FROM python:3.14.2-alpine3.23 COPY --chmod=775 start.sh /start.sh COPY --chmod=775 healthcheck.sh /healthcheck.sh -ENV RECORDING_VERSION=v0.2.0 +ENV RECORDING_VERSION=v0.2.1 ENV ALLOW_ALL=false ENV HPB_PROTOCOL=https ENV NC_PROTOCOL=https @@ -58,4 +58,5 @@ CMD ["python", "-m", "nextcloud.talk.recording", "--config", "/conf/recording.co HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/talk/Dockerfile b/Containers/talk/Dockerfile index 37ece0f4..e8d3d72f 100644 --- a/Containers/talk/Dockerfile +++ b/Containers/talk/Dockerfile @@ -1,10 +1,10 @@ # syntax=docker/dockerfile:latest -FROM nats:2.12.1-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.22.2 AS janus +FROM alpine:3.23.3 AS janus -ARG JANUS_VERSION=v1.3.2 +ARG JANUS_VERSION=v1.3.3 WORKDIR /src RUN set -ex; \ apk upgrade --no-cache -a; \ @@ -35,7 +35,7 @@ RUN set -ex; \ make configs; \ rename -v ".jcfg.sample" ".jcfg" /usr/local/etc/janus/*.jcfg.sample -FROM alpine:3.22.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 @@ -107,4 +107,5 @@ CMD ["supervisord", "-c", "/supervisord.conf"] HEALTHCHECK CMD /healthcheck.sh LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/watchtower/Dockerfile b/Containers/watchtower/Dockerfile index c2b38b18..fc9ea093 100644 --- a/Containers/watchtower/Dockerfile +++ b/Containers/watchtower/Dockerfile @@ -1,13 +1,21 @@ # syntax=docker/dockerfile:latest -FROM ghcr.io/nicholas-fedor/watchtower:1.12.1 AS watchtower +FROM golang:1.25.6-alpine3.23 AS go -FROM alpine:3.22.2 +ENV WATCHTOWER_COMMIT_HASH=f522ce27e1fbe4618da54833025a95be62aa838a + +RUN set -ex; \ + apk upgrade --no-cache -a; \ + apk add --no-cache \ + build-base; \ + go install github.com/nicholas-fedor/watchtower@$WATCHTOWER_COMMIT_HASH # v1.14.0 + +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a; \ apk add --no-cache bash ca-certificates tzdata -COPY --from=watchtower /watchtower /watchtower +COPY --from=go /go/bin/watchtower /watchtower COPY --chmod=775 start.sh /start.sh @@ -16,4 +24,5 @@ USER root ENTRYPOINT ["/start.sh"] LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/whiteboard/Dockerfile b/Containers/whiteboard/Dockerfile index e60bb815..c83dd46b 100644 --- a/Containers/whiteboard/Dockerfile +++ b/Containers/whiteboard/Dockerfile @@ -1,12 +1,16 @@ # syntax=docker/dockerfile:latest # Probably from this file: https://github.com/nextcloud/whiteboard/blob/main/Dockerfile -FROM ghcr.io/nextcloud-releases/whiteboard:v1.3.0 +FROM ghcr.io/nextcloud-releases/whiteboard:v1.5.4 USER root RUN set -ex; \ - apk upgrade --no-cache -a; \ - apk add --no-cache bash; \ - chmod 777 -R /tmp + apk add --no-cache bash jq; \ + chmod 777 -R /tmp; \ + if [ -f /usr/lib/chromium/chrome_crashpad_handler ] && [ ! -f /usr/lib/chromium/chrome_crashpad_handler.real ]; then \ + mv /usr/lib/chromium/chrome_crashpad_handler /usr/lib/chromium/chrome_crashpad_handler.real; \ + printf '%s\n' '#!/bin/sh' "exec /usr/lib/chromium/chrome_crashpad_handler.real --no-periodic-tasks --database=\"\${CRASHPAD_DATABASE:-/tmp/chrome-crashpad}\" \"\$@\"" >/usr/lib/chromium/chrome_crashpad_handler; \ + chmod +x /usr/lib/chromium/chrome_crashpad_handler; \ + fi USER 65534 COPY --chmod=775 start.sh /start.sh @@ -19,4 +23,5 @@ WORKDIR /tmp ENTRYPOINT ["/start.sh"] LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ org.label-schema.vendor="Nextcloud" diff --git a/Containers/whiteboard/healthcheck.sh b/Containers/whiteboard/healthcheck.sh index 4f53988a..5909db82 100644 --- a/Containers/whiteboard/healthcheck.sh +++ b/Containers/whiteboard/healthcheck.sh @@ -1,4 +1,4 @@ #!/bin/bash -nc -z "$REDIS_HOST" 6379 || exit 0 +nc -z "$REDIS_HOST" "$REDIS_PORT" || exit 0 nc -z 127.0.0.1 3002 || exit 1 diff --git a/Containers/whiteboard/start.sh b/Containers/whiteboard/start.sh index 962df9b9..e0babd7f 100644 --- a/Containers/whiteboard/start.sh +++ b/Containers/whiteboard/start.sh @@ -1,7 +1,7 @@ #!/bin/bash # Only start container if nextcloud is accessible -while ! nc -z "$REDIS_HOST" 6379; do +while ! nc -z "$REDIS_HOST" "$REDIS_PORT"; do echo "Waiting for redis to start..." sleep 5 done @@ -11,7 +11,10 @@ if [ -z "$REDIS_DB_INDEX" ]; then REDIS_DB_INDEX=0 fi -export REDIS_URL="redis://$REDIS_USER:$REDIS_HOST_PASSWORD@$REDIS_HOST/$REDIS_DB_INDEX" +# URL-encode password +REDIS_HOST_PASSWORD="$(jq -rn --arg v "$REDIS_HOST_PASSWORD" '$v|@uri')" + +export REDIS_URL="redis://$REDIS_USER:$REDIS_HOST_PASSWORD@$REDIS_HOST:$REDIS_PORT/$REDIS_DB_INDEX" # Run it exec npm --prefix /app run server:start diff --git a/app/appinfo/info.xml b/app/appinfo/info.xml index 8b911c10..832d3ccd 100644 --- a/app/appinfo/info.xml +++ b/app/appinfo/info.xml @@ -13,7 +13,7 @@ monitoring https://github.com/nextcloud/all-in-one/issues - + diff --git a/community-containers/borgbackup-viewer/readme.md b/community-containers/borgbackup-viewer/readme.md index dc3d5806..ddd11be7 100644 --- a/community-containers/borgbackup-viewer/readme.md +++ b/community-containers/borgbackup-viewer/readme.md @@ -5,7 +5,7 @@ This container allows to view the local borg repository in a web session. It als - After adding and starting the container, you need to visit `https://ip.address.of.this.server:5801` in order to log in with the user `nextcloud` and the password that you can see next to the container in the AIO interface. (The web page uses a self-signed certificate, so you need to accept the warning). - Then, you should see a terminal. There type in `borg mount /mnt/borgbackup/borg /tmp/borg` to mount the backup archive at `/tmp/borg` inside the container. Afterwards type in `nautilus /tmp/borg` which will show a file explorer and allows you to see all the files. You can then copy files and folders back to their initial mountpoints inside `/nextcloud_aio_volumes/`, `/host_mounts/` and `/docker_volumes/`. ⚠️ Be very carefully while doing that as can break your instance! - After you are done with the operation, click on the terminal in the background and press `[CTRL]+[c]` multiple times to close any open application. Then run `umount /tmp/borg` to unmount the mountpoint correctly. -- You can also delete specific archives by running `borg list`, delete a specific archive e.g. via `borg delete --stats --progress "::20220223_174237-nextcloud-aio"` and compact the archives via `borg compact`. After doing so, make sure to update the backup archives list in the AIO interface! You can do so by clicking on the `Check backup integrity` button or `Create backup` button. +- You can also delete specific archives by running `borg list`, delete a specific archive e.g. via `borg delete --stats --progress "::20220223_174237-nextcloud-aio"` and compact the archives via `borg compact`. After doing so, make sure to update the backup archives list in the AIO interface! You can do so by clicking on the `Update backup list` button in the `Update backup list` section inside the `Backup and restore` section. - ⚠️ After you are done doing your operations, remove the container for better security again from the stack: https://github.com/nextcloud/all-in-one/tree/main/community-containers#how-to-remove-containers-from-aios-stack - See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack diff --git a/community-containers/caddy/caddy.json b/community-containers/caddy/caddy.json index 0e78175d..e27df683 100644 --- a/community-containers/caddy/caddy.json +++ b/community-containers/caddy/caddy.json @@ -5,7 +5,7 @@ "display_name": "Caddy with geoblocking", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy", "image": "ghcr.io/szaimen/aio-caddy", - "image_tag": "v2", + "image_tag": "v4", "internal_port": "443", "restart": "unless-stopped", "ports": [ @@ -13,17 +13,13 @@ "ip_binding": "", "port_number": "443", "protocol": "tcp" - }, - { - "ip_binding": "", - "port_number": "443", - "protocol": "udp" } ], "environment": [ "TZ=%TIMEZONE%", "NC_DOMAIN=%NC_DOMAIN%", "APACHE_PORT=%APACHE_PORT%", + "APACHE_IP_BINDING=%APACHE_IP_BINDING%", "NEXTCLOUD_EXPORTER_CADDY_PASSWORD=%NEXTCLOUD_EXPORTER_CADDY_PASSWORD%" ], "volumes": [ @@ -43,7 +39,9 @@ ], "aio_variables": [ "apache_ip_binding=@INTERNAL", - "apache_port=11000" + "apache_port=11000", + "turn_domain=%NC_DOMAIN%", + "talk_port=443" ], "nextcloud_exec_commands": [ "mkdir '/mnt/ncdata/admin/files/nextcloud-aio-caddy'", diff --git a/community-containers/caddy/readme.md b/community-containers/caddy/readme.md index 56984d59..fd2f30ef 100644 --- a/community-containers/caddy/readme.md +++ b/community-containers/caddy/readme.md @@ -1,9 +1,12 @@ ## Caddy with geoblocking -This container bundles caddy and auto-configures it for you. It also covers [vaultwarden](https://github.com/nextcloud/all-in-one/tree/main/community-containers/vaultwarden) by listening on `bw.$NC_DOMAIN`, if installed. It also covers [stalwart](https://github.com/nextcloud/all-in-one/tree/main/community-containers/stalwart) by listening on `mail.$NC_DOMAIN`, if installed. It also covers [jellyfin](https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyfin) by listening on `media.$NC_DOMAIN`, if installed. It also covers [lldap](https://github.com/nextcloud/all-in-one/tree/main/community-containers/lldap) by listening on `ldap.$NC_DOMAIN`, if installed. It also covers [nocodb](https://github.com/nextcloud/all-in-one/tree/main/community-containers/nocodb) by listening on `tables.$NC_DOMAIN`, if installed. It also covers [jellyseerr](https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyseerr) by listening on `requests.$NC_DOMAIN`, if installed. It also covers [nextcloud-exporter](https://github.com/nextcloud/all-in-one/tree/main/community-containers/nextcloud-exporter) by listening on `metrics.$NC_DOMAIN`, if installed. +This container bundles caddy and auto-configures it for you. It also covers [vaultwarden](https://github.com/nextcloud/all-in-one/tree/main/community-containers/vaultwarden) by listening on `bw.$NC_DOMAIN`, if installed. It also covers [stalwart](https://github.com/nextcloud/all-in-one/tree/main/community-containers/stalwart) by listening on `mail.$NC_DOMAIN`, if installed. It also covers [jellyfin](https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyfin) by listening on `media.$NC_DOMAIN`, if installed. It also covers [lldap](https://github.com/nextcloud/all-in-one/tree/main/community-containers/lldap) by listening on `ldap.$NC_DOMAIN`, if installed. It also covers [nocodb](https://github.com/nextcloud/all-in-one/tree/main/community-containers/nocodb) by listening on `tables.$NC_DOMAIN`, if installed. It also covers [jellyseerr](https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyseerr) by listening on `requests.$NC_DOMAIN`, if installed. It also covers [nextcloud-exporter](https://github.com/nextcloud/all-in-one/tree/main/community-containers/nextcloud-exporter) by listening on `metrics.$NC_DOMAIN`, if installed. It also covers [LocalAI](https://github.com/nextcloud/all-in-one/tree/main/community-containers/local-ai) by listening on `ai.$NC_DOMAIN`, if installed. ### Notes - This container is incompatible with the [npmplus](https://github.com/nextcloud/all-in-one/tree/main/community-containers/npmplus) community container. So make sure that you do not enable both at the same time! -- Make sure that no other service is using port 443 on your host as otherwise the containers will fail to start. You can check this with `sudo netstat -tulpn | grep 443` before installing AIO. +- Make sure that no other service is using port 443/tcp on your host as otherwise the containers will fail to start. You can check this with `sudo netstat -tulpn | grep 443` before installing AIO. +- Starting with AIO v12, the Talk port that was usually exposed on port 3478 is now set to port 443 udp and tcp and reachable via `your-nc-domain.com`. For the changes to become activated, you need to go to `https://your-nc-domain.com/settings/admin/talk` and delete all turn and stun servers. Then restart the containers and the new config should become active. +- Starting with AIO v12, you can also limit vaultwarden, stalwart and lldap to certain ip-addresses. You can do so by creating a `allowed-IPs-vaultwarden.txt`, `allowed-IPs-stalwart.txt`, or `allowed-IPs-lldap.txt` file in the `nextcloud-aio-caddy` directory of your admin user and adding the ip-addresses in these files. +- The container also supports the proxy protocol inside caddy. That means that you can run a supported web server in front of port 443/tcp and use the proxy protocol. You can enable this by configuring the `APACHE_IP_BINDING` environmental variable for the mastercontainer and set it to an ip-address from which the protocol shall be accepted. ⚠️ Note that the initial domain validation will not work correctly if you want to use the proxy protocol. So make sure to skip the domain validation in that case. See the [documentation](https://github.com/nextcloud/all-in-one#how-to-skip-the-domain-validation). - If you want to use this with [vaultwarden](https://github.com/nextcloud/all-in-one/tree/main/community-containers/vaultwarden), make sure that you point `bw.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for vaultwarden. - If you want to use this with [stalwart](https://github.com/nextcloud/all-in-one/tree/main/community-containers/stalwart), make sure that you point `mail.your-nc-domain.com` to your server using an A, AAAA or CNAME record so that caddy can get a certificate automatically for stalwart. - If you want to use this with [jellyfin](https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyfin), make sure that you point `media.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for jellyfin. @@ -11,9 +14,11 @@ This container bundles caddy and auto-configures it for you. It also covers [vau - If you want to use this with [nocodb](https://github.com/nextcloud/all-in-one/tree/main/community-containers/nocodb), make sure that you point `tables.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for nocodb. - If you want to use this with [jellyseerr](https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyseerr), make sure that you point `requests.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for jellyseerr. - If you want to use this with [nextcloud-exporter](https://github.com/nextcloud/all-in-one/tree/main/community-containers/nextcloud-exporter), make sure that you point `metrics.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for nextcloud-exporter. +- If you want to use this with [local AI](https://github.com/nextcloud/all-in-one/tree/main/community-containers/local-ai), make sure that you point `ai.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for local AI. - After the container was started the first time, you should see a new `nextcloud-aio-caddy` folder and inside there an `allowed-countries.txt` file when you open the files app with the default `admin` user. In there you can adjust the allowed country codes for caddy by adding them to the first line, e.g. `IT FR` would allow access from italy and france. Private ip-ranges are always allowed. Additionally, in order to activate this config, you need to get an account at https://dev.maxmind.com/geoip/geolite2-free-geolocation-data and download the `GeoLite2-Country.mmdb` and upload it with this exact name into the `nextcloud-aio-caddy` folder. Afterwards restart all containers from the AIO interface and your new config should be active! - You can add your own Caddy configurations in `/data/caddy-imports/` inside the Caddy container (`sudo docker exec -it nextcloud-aio-caddy bash`). These will be imported on container startup. **Please note:** If you do not have CLI access to the server, you can now run docker commands via a web session by using this community container: https://github.com/nextcloud/all-in-one/tree/main/community-containers/container-management - See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack +- If you want to remove the container again and revert back to the default, you need to disable the container via the AIO-interface and follow https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md#8-removing-the-reverse-proxy ### Repository https://github.com/szaimen/aio-caddy diff --git a/community-containers/calcardbackup/readme.md b/community-containers/calcardbackup/readme.md index 0bb04a3b..42a218b4 100644 --- a/community-containers/calcardbackup/readme.md +++ b/community-containers/calcardbackup/readme.md @@ -2,7 +2,7 @@ This container packages calcardbackup which is a tool that exports calendars and addressbooks from Nextcloud to .ics and .vcf files and saves them to a compressed file. ### Notes -- Backups will be created at 00:00 CEST every day. Make sure that this does not conflict with the configured daily backups inside AIO. +- Backups will be created at 00:00 UTC every day. Make sure that this does not conflict with the configured daily backups inside AIO. - All the exports will be included in AIOs backup solution - You can find the exports in the nextcloud_aio_calcardbackup volume - See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack diff --git a/community-containers/local-ai/local-ai.json b/community-containers/local-ai/local-ai.json index 8e2aedb3..e906b5a7 100644 --- a/community-containers/local-ai/local-ai.json +++ b/community-containers/local-ai/local-ai.json @@ -4,42 +4,59 @@ "container_name": "nextcloud-aio-local-ai", "display_name": "Local AI", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/local-ai", - "image": "ghcr.io/szaimen/aio-local-ai", - "image_tag": "v2", - "internal_port": "8080", + "image": "ghcr.io/docjyj/aio-local-ai-vulkan", + "image_tag": "v1", + "internal_port": "10078", "restart": "unless-stopped", "environment": [ "TZ=%TIMEZONE%", - "MODELS_PATH=/models" + "LOCALAI_API_KEY=%LOCALAI_API_KEY%", + "LOCALAI_ADDRESS=:10078", + "LOCALAI_CONFIG_DIR=/configuration", + "LOCALAI_MODEL_PATH=/models", + "LOCALAI_BACKEND_PATH=/backends" + ], + "ports": [ + { + "ip_binding": "%APACHE_IP_BINDING%", + "port_number": "10078", + "protocol": "tcp" + } ], "volumes": [ + { + "source": "nextcloud_aio_localai_configuration", + "destination": "/configuration", + "writeable": true + }, { "source": "nextcloud_aio_localai_models", "destination": "/models", "writeable": true }, { - "source": "nextcloud_aio_localai_images", - "destination": "/tmp/generated/images/", + "source": "nextcloud_aio_localai_backends", + "destination": "/backends", "writeable": true - }, - { - "source": "%NEXTCLOUD_DATADIR%", - "destination": "/nextcloud", - "writeable": false } ], - "enable_nvidia_gpu": false, + "secrets": [ + "LOCALAI_API_KEY" + ], + "ui_secret": "LOCALAI_API_KEY", + "devices": [ + "/dev/dri" + ], "nextcloud_exec_commands": [ - "mkdir '/mnt/ncdata/admin/files/nextcloud-aio-local-ai'", - "touch '/mnt/ncdata/admin/files/nextcloud-aio-local-ai/models.yaml'", - "echo 'Scanning nextcloud-aio-local-ai folder for admin user...'", - "php /var/www/html/occ files:scan --path='/admin/files/nextcloud-aio-local-ai'", "php /var/www/html/occ app:install integration_openai", "php /var/www/html/occ app:enable integration_openai", - "php /var/www/html/occ config:app:set integration_openai url --value http://nextcloud-aio-local-ai:8080", + "php /var/www/html/occ config:app:set integration_openai url --value http://nextcloud-aio-local-ai:10078", + "php /var/www/html/occ config:app:set integration_openai api_key --value %LOCALAI_API_KEY%", "php /var/www/html/occ app:install assistant", "php /var/www/html/occ app:enable assistant" + ], + "backup_volumes": [ + "nextcloud_aio_localai_configuration" ] } ] diff --git a/community-containers/local-ai/readme.md b/community-containers/local-ai/readme.md index 2ab05996..02722bd0 100644 --- a/community-containers/local-ai/readme.md +++ b/community-containers/local-ai/readme.md @@ -1,21 +1,16 @@ ## Local AI -This container bundles Local AI and auto-configures it for you. +This container bundles Local AI and auto-configures it for you. It support hardware acceleration with Vulkan. ### Notes -- Make sure to have enough storage space available. This container alone needs ~7GB storage. Every model that you add to `models.yaml` will of course use additional space which adds up quite fast. -- After the container was started the first time, you should see a new `nextcloud-aio-local-ai` folder when you open the files app with the default `admin` user. In there you should see a `models.yaml` config file. You can now add models in there. Please refer [here](https://github.com/mudler/LocalAI/blob/master/gallery/index.yaml) where you can get further urls that you can put in there. Afterwards restart all containers from the AIO interface and the models should automatically get downloaded by the local-ai container and activated. -- Example for content of `models.yaml` (if you add all of them, it takes around 10GB additional space): -```yaml -# Stable Diffusion in NCNN with c++, supported txt2img and img2img -- url: github:mudler/LocalAI/blob/master/gallery/stablediffusion.yaml - name: Stable_diffusion -``` -- To make it work, you first need to browse `https://your-nc-domain.com/settings/admin/ai` and enable or disable specific features for your models in the openAI settings. Afterwards using the Nextcloud Assistant should work. +Documentation is available on the container repository. This documentation is regularly updated and is intended to be as simple and detailed as possible. Thanks for all your feedback! + +- See https://github.com/docjyJ/aio-local-ai-vulkan#getting-started for getting start with this container. - See [this guide](https://github.com/nextcloud/all-in-one/discussions/5430) for how to improve AI task pickup speed +- Note that Nextcloud supports only one server for AI queries, so this container cannot be used at the same time as other AI containers. - See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack ### Repository -https://github.com/szaimen/aio-local-ai +https://github.com/docjyJ/aio-local-ai-vulkan ### Maintainer -https://github.com/szaimen +https://github.com/docjyJ diff --git a/community-containers/makemkv/readme.md b/community-containers/makemkv/readme.md index ed9ce040..e78510ee 100644 --- a/community-containers/makemkv/readme.md +++ b/community-containers/makemkv/readme.md @@ -5,7 +5,7 @@ This container bundles MakeMKV and auto-configures it for you. - This container should only be run in home networks - ⚠️ This container mounts all devices from the host inside the container in order to be able to access the external DVD/Blu-ray drives which is a security issue. However no better solution was found for the time being. - This container only works on Linux and not on Docker-Desktop. -- This container requires the [`NEXTCLOUD_MOUNT` variable in AIO to be set](https://github.com/nextcloud/all-in-one?tab=readme-ov-file#how-to-allow-the-nextcloud-container-to-access-directories-on-the-host). Otherwise the output will not be saved correctly.. +- This container requires the [`NEXTCLOUD_MOUNT` variable in AIO to be set](https://github.com/nextcloud/all-in-one#how-to-allow-the-nextcloud-container-to-access-directories-on-the-host). Otherwise the output will not be saved correctly.. - After adding and starting the container, you need to visit `https://internal.ip.of.server:5802` in order to log in with the `makemkv` user and the password that you can see next to the container in the AIO interface. (The web page uses a self-signed certificate, so you need to accept the warning). - After the first login, you can adjust the `/output` directory in the MakeMKV settings to a subdirectory of the root of your chosen `NEXTCLOUD_MOUNT`. (by default `NEXTCLOUD_MOUNT` is mounted to `/output` inside the container. Thus all data is written to the root of it) - The configured `NEXTCLOUD_DATADIR` is getting mounted to `/storage` inside the container. diff --git a/community-containers/minio/minio.json b/community-containers/minio/minio.json index 2403f213..50613202 100644 --- a/community-containers/minio/minio.json +++ b/community-containers/minio/minio.json @@ -2,7 +2,7 @@ "aio_services_v1": [ { "container_name": "nextcloud-aio-minio", - "image_tag": "v1", + "image_tag": "v2", "display_name": "Minio S3 Storage", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/minio", "image": "ghcr.io/szaimen/aio-minio", diff --git a/community-containers/nocodb/nocodb.json b/community-containers/nocodb/nocodb.json index 7ef4cc5c..e93d173c 100644 --- a/community-containers/nocodb/nocodb.json +++ b/community-containers/nocodb/nocodb.json @@ -2,7 +2,7 @@ "aio_services_v1": [ { "container_name": "nextcloud-aio-nocodb", - "display_name": "NocoDB", + "display_name": "NocoDB (deprecated)", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/nocodb", "image": "nocodb/nocodb", "image_tag": "latest", diff --git a/community-containers/nocodb/readme.md b/community-containers/nocodb/readme.md index 4c1281b5..fa23f8f6 100644 --- a/community-containers/nocodb/readme.md +++ b/community-containers/nocodb/readme.md @@ -1,3 +1,8 @@ +> [!CAUTION] +> NocoDB is licensed under a non-free license. +> +> And is no longer maintained. + > [!NOTE] > This container is there to compensate for the lack of functionality in Nextcloud Tables. > diff --git a/community-containers/notifications/notifications.json b/community-containers/notifications/notifications.json new file mode 100644 index 00000000..5d886ec9 --- /dev/null +++ b/community-containers/notifications/notifications.json @@ -0,0 +1,23 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-notifications", + "display_name": "Notifications", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/notifications", + "image": "ghcr.io/szaimen/aio-notifications", + "image_tag": "v1", + "internal_port": "10000", + "restart": "unless-stopped", + "volumes": [ + { + "source": "%WATCHTOWER_DOCKER_SOCKET_PATH%", + "destination": "/var/run/docker.sock", + "writeable": false + } + ], + "environment": [ + "TZ=%TIMEZONE%" + ] + } + ] +} diff --git a/community-containers/notifications/readme.md b/community-containers/notifications/readme.md new file mode 100644 index 00000000..78ec49b6 --- /dev/null +++ b/community-containers/notifications/readme.md @@ -0,0 +1,12 @@ +## Notifications +This container allows other AIO community containers to send admin notifications to Nextcloud users. + +### Notes +- It needs to be enabled for the [scrutiny container](https://github.com/nextcloud/all-in-one/tree/main/community-containers/scrutiny) for example to make use of admin notifications that are sent if a smartctl failure was found. +- See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack + +### Repository +https://github.com/szaimen/aio-notifications + +### Maintainer +https://github.com/szaimen diff --git a/community-containers/scrutiny/readme.md b/community-containers/scrutiny/readme.md index dc972d44..3bb728f7 100644 --- a/community-containers/scrutiny/readme.md +++ b/community-containers/scrutiny/readme.md @@ -6,7 +6,7 @@ This container bundles Scrutiny which is a frontend for SMART stats and auto-con - ⚠️ This container mounts all devices from the host inside the container in order to be able to access the drives and smartctl stats which is a security issue. However no better solution was found for the time being. - This container only works on Linux and not on Docker-Desktop. - After adding and starting the container, you need to visit `http://internal.ip.of.server:8000` which will show the dashboard for your drives. -- It currently does not support sending notifications as no good solution was found yet that makes this possible. See https://github.com/szaimen/aio-scrutiny/issues/3 +- It supports sending notifications in case of a smartctl failure if you enable the notifications community container: https://github.com/nextcloud/all-in-one/tree/main/community-containers/notifications - See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack ### Repository diff --git a/community-containers/scrutiny/scrutiny.json b/community-containers/scrutiny/scrutiny.json index 4b368291..b367e497 100644 --- a/community-containers/scrutiny/scrutiny.json +++ b/community-containers/scrutiny/scrutiny.json @@ -5,7 +5,7 @@ "display_name": "Scrutiny", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/scrutiny", "image": "ghcr.io/szaimen/aio-scrutiny", - "image_tag": "v1", + "image_tag": "v2", "internal_port": "8000", "init": false, "restart": "unless-stopped", diff --git a/community-containers/smbserver/readme.md b/community-containers/smbserver/readme.md index 9886f4b2..20d90c9f 100644 --- a/community-containers/smbserver/readme.md +++ b/community-containers/smbserver/readme.md @@ -3,7 +3,6 @@ This container bundles an SMB-server and allows to configure it via a graphical ### Notes - This container should only be run in home networks -- This container currently only works on amd64. See https://github.com/szaimen/aio-smbserver/issues/3 - After adding and starting the container, you need to visit `https://internal.ip.of.server:5803` in order to log in with the `smbserver` user and the password that you can see next to the container in the AIO interface. (The web page uses a self-signed certificate, so you need to accept the warning). Then type in `bash /smbserver.sh` and you will see a graphical UI for configuring the smb-server interactively. - The config data of SMB-server will be automatically included in AIOs backup solution! - See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack diff --git a/compose.yaml b/compose.yaml index f2026388..c18d92d3 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,3 +1,4 @@ +name: nextcloud-aio # Add the container to the same compose project like all the sibling containers are added to automatically. services: nextcloud-aio-mastercontainer: image: ghcr.io/nextcloud-releases/all-in-one:latest # This is the container image used. You can switch to ghcr.io/nextcloud-releases/all-in-one:beta if you want to help testing new releases. See https://github.com/nextcloud/all-in-one#how-to-switch-the-channel @@ -21,6 +22,7 @@ services: # APACHE_ADDITIONAL_NETWORK: frontend_net # (Optional) Connect the apache container to an additional docker network. Needed when behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else) running in a different docker network on same server. See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md # BORG_RETENTION_POLICY: --keep-within=7d --keep-weekly=4 --keep-monthly=6 # Allows to adjust borgs retention policy. See https://github.com/nextcloud/all-in-one#how-to-adjust-borgs-retention-policy # COLLABORA_SECCOMP_DISABLED: false # Setting this to true allows to disable Collabora's Seccomp feature. See https://github.com/nextcloud/all-in-one#how-to-disable-collaboras-seccomp-feature + # DOCKER_API_VERSION: 1.44 # You can adjust the internally used docker api version with this variable. ⚠️⚠️⚠️ Warning: please note that only the default api version (unset this variable) is supported and tested by the maintainers of Nextcloud AIO. So use this on your own risk and things might break without warning. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-internally-used-docker-api-version # FULLTEXTSEARCH_JAVA_OPTIONS: "-Xms1024M -Xmx1024M" # Allows to adjust the fulltextsearch java options. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-fulltextsearch-java-options # NEXTCLOUD_DATADIR: /mnt/ncdata # Allows to set the host directory for Nextcloud's datadir. ⚠️⚠️⚠️ Warning: do not set or adjust this value after the initial Nextcloud installation is done! See https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir # NEXTCLOUD_MOUNT: /mnt/ # Allows the Nextcloud container to access the chosen directory on the host. See https://github.com/nextcloud/all-in-one#how-to-allow-the-nextcloud-container-to-access-directories-on-the-host @@ -34,7 +36,7 @@ services: # NEXTCLOUD_ENABLE_DRI_DEVICE: true # This allows to enable the /dev/dri device for containers that profit from it. ⚠️⚠️⚠️ Warning: this only works if the '/dev/dri' device is present on the host! If it should not exist on your host, don't set this to true as otherwise the Nextcloud container will fail to start! See https://github.com/nextcloud/all-in-one#how-to-enable-hardware-acceleration-for-nextcloud # NEXTCLOUD_ENABLE_NVIDIA_GPU: true # This allows to enable the NVIDIA runtime and GPU access for containers that profit from it. ⚠️⚠️⚠️ Warning: this only works if an NVIDIA gpu is installed on the server. See https://github.com/nextcloud/all-in-one#how-to-enable-hardware-acceleration-for-nextcloud. # NEXTCLOUD_KEEP_DISABLED_APPS: false # Setting this to true will keep Nextcloud apps that are disabled in the AIO interface and not uninstall them if they should be installed. See https://github.com/nextcloud/all-in-one#how-to-keep-disabled-apps - # SKIP_DOMAIN_VALIDATION: false # This should only be set to true if things are correctly configured. See https://github.com/nextcloud/all-in-one?tab=readme-ov-file#how-to-skip-the-domain-validation + # SKIP_DOMAIN_VALIDATION: false # This should only be set to true if things are correctly configured. See https://github.com/nextcloud/all-in-one#how-to-skip-the-domain-validation # TALK_PORT: 3478 # This allows to adjust the port that the talk container is using which is exposed on the host. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-talk-port # WATCHTOWER_DOCKER_SOCKET_PATH: /var/run/docker.sock # Needs to be specified if the docker socket on the host is not located in the default '/var/run/docker.sock'. Otherwise mastercontainer updates will fail. For macos it needs to be '/var/run/docker.sock' diff --git a/develop.md b/develop.md index 6c5faf2d..457b3a76 100644 --- a/develop.md +++ b/develop.md @@ -67,6 +67,6 @@ docker buildx build --file Containers/nextcloud/Dockerfile --tag ghcr.io/nextclo # For all other containers docker buildx build --file Containers/{container}/Dockerfile --tag ghcr.io/nextcloud-releases/aio-{container}:develop --load Containers/{container} ``` -1. Stop the containers using the AIO admin interface. -1. Reload the AIO admin interface with the param `bypass_container_update` to avoid overwriting your local changes, e.g. `https://localhost:8080/containers?bypass_container_update`. +1. Stop the containers using the AIO interface. +1. Reload the AIO interface with the param `bypass_container_update` to avoid overwriting your local changes, e.g. `https://localhost:8080/containers?bypass_container_update`. 1. Click "Start and update containers" and test your changes. Containers will not be updated, despite the button text. diff --git a/local-instance.md b/local-instance.md index 1da26280..8abbddb6 100644 --- a/local-instance.md +++ b/local-instance.md @@ -22,10 +22,11 @@ The normal way is the following: **Hint:** You may have a look at [this video](https://youtu.be/zk-y2wVkY4c) for a more complete but possibly outdated example. ## 3. Use the ACME DNS-challenge -You can alternatively use the ACME DNS-challenge to get a valid certificate for Nextcloud. Here is described how to set it up: https://github.com/nextcloud/all-in-one#how-to-get-nextcloud-running-using-the-acme-dns-challenge +You can alternatively use the ACME DNS-challenge to get a valid certificate for Nextcloud. Here is described how to set it up using an external caddy reverse proxy: https://github.com/nextcloud/all-in-one#how-to-get-nextcloud-running-using-the-acme-dns-challenge ## 4. Use Cloudflare If you do not have any control over the network, you may think about using Cloudflare Tunnel to get a valid certificate for your Nextcloud. However it will be opened to the public internet then. See https://github.com/nextcloud/all-in-one#how-to-run-nextcloud-behind-a-cloudflare-tunnel how to set this up. ## 5. Buy a certificate and use that If none of the above ways work for you, you may simply buy a certificate from an issuer for your domain. You then download the certificate onto your server, configure AIO in [reverse proxy mode](./reverse-proxy.md) and use the certificate for your domain in your reverse proxy config. + diff --git a/manual-install/latest.yml b/manual-install/latest.yml index 83bc1ef1..e9362ccc 100644 --- a/manual-install/latest.yml +++ b/manual-install/latest.yml @@ -135,6 +135,7 @@ services: - POSTGRES_DB=nextcloud_database - POSTGRES_USER=nextcloud - REDIS_HOST=nextcloud-aio-redis + - REDIS_PORT=6379 - REDIS_HOST_PASSWORD=${REDIS_PASSWORD} - APACHE_HOST=nextcloud-aio-apache - APACHE_PORT @@ -160,11 +161,11 @@ services: - TALK_PORT - IMAGINARY_ENABLED - IMAGINARY_HOST=nextcloud-aio-imaginary - - CLAMAV_MAX_SIZE=${APACHE_MAX_SIZE} - PHP_UPLOAD_LIMIT=${NEXTCLOUD_UPLOAD_LIMIT} - PHP_MEMORY_LIMIT=${NEXTCLOUD_MEMORY_LIMIT} - FULLTEXTSEARCH_ENABLED - FULLTEXTSEARCH_HOST=nextcloud-aio-fulltextsearch + - FULLTEXTSEARCH_PROTOCOL=http - FULLTEXTSEARCH_PORT=9200 - FULLTEXTSEARCH_USER=elastic - FULLTEXTSEARCH_INDEX=nextcloud-aio @@ -207,6 +208,7 @@ services: - NEXTCLOUD_HOST=nextcloud-aio-nextcloud - TZ=${TIMEZONE} - REDIS_HOST=nextcloud-aio-redis + - REDIS_PORT=6379 - REDIS_HOST_PASSWORD=${REDIS_PASSWORD} - POSTGRES_HOST=nextcloud-aio-database - POSTGRES_PORT=5432 @@ -256,7 +258,7 @@ services: - "9980" environment: - aliasgroup1=https://${NC_DOMAIN}:443,http://nextcloud-aio-apache:23973 - - extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:mount_jail_tree=false --o:logging.level=warning --o:logging.level_startup=warning --o:home_mode.enable=true --o:remote_font_config.url=https://${NC_DOMAIN}/apps/richdocuments/settings/fonts.json --o:net.post_allow.host[0]=.+ + - extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:logging.disable_server_audit=true --o:logging.level=warning --o:logging.level_startup=warning --o:welcome.enable=false --o:remote_font_config.url=https://${NC_DOMAIN}/apps/richdocuments/settings/fonts.json --o:net.post_allow.host[0]=.+ - dictionaries=${COLLABORA_DICTIONARIES} - TZ=${TIMEZONE} - server_name=${NC_DOMAIN} @@ -439,7 +441,7 @@ services: environment: - TZ=${TIMEZONE} - ES_JAVA_OPTS=${FULLTEXTSEARCH_JAVA_OPTIONS} - - bootstrap.memory_lock=true + - bootstrap.memory_lock=false - cluster.name=nextcloud-aio - discovery.type=single-node - logger.level=WARN @@ -476,6 +478,7 @@ services: - JWT_SECRET_KEY=${WHITEBOARD_SECRET} - STORAGE_STRATEGY=redis - REDIS_HOST=nextcloud-aio-redis + - REDIS_PORT=6379 - REDIS_HOST_PASSWORD=${REDIS_PASSWORD} - BACKUP_DIR=/tmp restart: unless-stopped diff --git a/manual-install/readme.md b/manual-install/readme.md index 874a5b4b..ea2c2978 100644 --- a/manual-install/readme.md +++ b/manual-install/readme.md @@ -6,7 +6,7 @@ You can run the containers that are build for AIO with docker-compose. This come - You can run it without a container having access to the docker socket - You can modify all values on your own - You can run the containers with docker swarm -- You can run this in environments where access to docker.io is not possible. See [this issue](https://github.com/nextcloud/all-in-one/discussions/5268). +- You can run this in environments where access to ghcr.io is not possible. See [this issue](https://github.com/nextcloud/all-in-one/discussions/5268). ### Disadvantages - You lose the AIO interface diff --git a/manual-install/update-yaml.sh b/manual-install/update-yaml.sh index 70d14b4e..928275da 100644 --- a/manual-install/update-yaml.sh +++ b/manual-install/update-yaml.sh @@ -46,6 +46,8 @@ sed -i '/AIO_TOKEN/d' containers.yml 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/manual-upgrade.md b/manual-upgrade.md index 84c742e5..fbc07d3e 100644 --- a/manual-upgrade.md +++ b/manual-upgrade.md @@ -108,7 +108,7 @@ However, if you are unsure check the ghcr.io (https://github.com/nextcloud-relea Once you see no more activities in the logs or a message like ```NOTICE: ready to handle connections```, we've done it! -#### Now you can handle everything through the AIO admin interface and stop and restart the containers normally. +#### Now you can handle everything through the AIO interface and stop and restart the containers normally. --- diff --git a/nextcloud-aio-helm-chart/Chart.yaml b/nextcloud-aio-helm-chart/Chart.yaml index 6eb13d92..6288a381 100755 --- a/nextcloud-aio-helm-chart/Chart.yaml +++ b/nextcloud-aio-helm-chart/Chart.yaml @@ -1,6 +1,6 @@ name: nextcloud-aio-helm-chart description: A generated Helm Chart for Nextcloud AIO from Skippbox Kompose -version: 11.10.0 +version: 12.5.0 apiVersion: v2 keywords: - latest diff --git a/nextcloud-aio-helm-chart/readme.md b/nextcloud-aio-helm-chart/readme.md index edf6c779..cb31e601 100755 --- a/nextcloud-aio-helm-chart/readme.md +++ b/nextcloud-aio-helm-chart/readme.md @@ -1,7 +1,7 @@ # Nextcloud AIO Helm-chart > [!NOTE] -> For an enterprise-ready and scalable deployment method based on Helm Charts (also available for Podman), please [contact Nextcloud GmbH](https://nextcloud.com/enterprise/). +> For an enterprise-ready and scalable deployment method based on Helm Charts (also available for Podman and OpenShift), please [contact Nextcloud GmbH](https://nextcloud.com/enterprise/). > [!IMPORTANT] > This Helm-Chart is not intended to be used with Ingress as it handles TLS itself via the built-in apache container and exposes a Loadbalancer port itself on the Cluster. See the [apache service](https://github.com/nextcloud/all-in-one/blob/main/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-service.yaml). However if the Cluster is used behind NAT, you can adjust `APACHE_PORT` to a different one than 443 and do the TLS offloading on an external Reverse Proxy that forwards the traffic to the configured port via http. If you really need the Ingress feature, please [contact Nextcloud GmbH](https://nextcloud.com/enterprise/) as we offer an enterprise-ready and scalable deployment method based on Helm Charts that also allows Ingress to be used. diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-deployment.yaml index 4196cb99..e540791c 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-apache name: nextcloud-aio-apache @@ -17,7 +17,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-apache spec: @@ -61,7 +61,7 @@ spec: value: "{{ .Values.TIMEZONE }}" - name: WHITEBOARD_HOST value: nextcloud-aio-whiteboard - image: ghcr.io/nextcloud-releases/aio-apache:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-apache:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-service.yaml index 404ee626..98e33a4d 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-apache-service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-apache name: nextcloud-aio-apache diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-deployment.yaml index 9f8dda8d..57ec7739 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-clamav name: nextcloud-aio-clamav @@ -18,7 +18,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-clamav spec: @@ -36,7 +36,7 @@ spec: {{- end }} initContainers: - name: init-subpath - image: ghcr.io/nextcloud-releases/aio-alpine:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - mkdir - "-p" @@ -59,7 +59,7 @@ spec: value: "{{ .Values.NEXTCLOUD_UPLOAD_LIMIT }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-clamav:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-clamav:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-service.yaml index 8dc8597d..8b236093 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-clamav-service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-clamav name: nextcloud-aio-clamav diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-deployment.yaml index 5db29234..cd4e1368 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-collabora name: nextcloud-aio-collabora @@ -16,7 +16,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-collabora spec: @@ -32,10 +32,14 @@ spec: - name: dictionaries value: "{{ .Values.COLLABORA_DICTIONARIES }}" - name: extra_params - value: --o:ssl.enable=false --o:ssl.termination=true --o:mount_jail_tree=false --o:logging.level=warning --o:logging.level_startup=warning --o:home_mode.enable=true --o:remote_font_config.url=https://{{ .Values.NC_DOMAIN }}/apps/richdocuments/settings/fonts.json --o:net.post_allow.host[0]=.+ + value: --o:ssl.enable=false --o:ssl.termination=true --o:logging.disable_server_audit=true --o:logging.level=warning --o:logging.level_startup=warning --o:welcome.enable=false --o:remote_font_config.url=https://{{ .Values.NC_DOMAIN }}/apps/richdocuments/settings/fonts.json --o:net.post_allow.host[0]=.+ - name: server_name value: "{{ .Values.NC_DOMAIN }}" - image: ghcr.io/nextcloud-releases/aio-collabora:20251015_082711 + {{- if contains "--o:support_key=" (join " " (.Values.ADDITIONAL_COLLABORA_OPTIONS | default list)) }} + image: ghcr.io/nextcloud-releases/aio-collabora-online:20260122_105751 + {{- else }} + image: ghcr.io/nextcloud-releases/aio-collabora:20260122_105751 + {{- end }} readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-service.yaml index ebe7bf3f..5c81ef3e 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-collabora-service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-collabora name: nextcloud-aio-collabora diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-deployment.yaml index 8466fe03..be6a9c90 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-database name: nextcloud-aio-database @@ -17,7 +17,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-database spec: @@ -35,7 +35,7 @@ spec: {{- end }} initContainers: - name: init-subpath - image: ghcr.io/nextcloud-releases/aio-alpine:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - mkdir - "-p" @@ -64,7 +64,7 @@ spec: value: nextcloud - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-postgresql:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-postgresql:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-service.yaml index 9451d908..45fdce3a 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-database-service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-database name: nextcloud-aio-database diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-deployment.yaml index 5b9ef69f..bed60a0c 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-fulltextsearch name: nextcloud-aio-fulltextsearch @@ -18,13 +18,13 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-fulltextsearch spec: initContainers: - name: init-volumes - image: ghcr.io/nextcloud-releases/aio-alpine:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - chmod - "777" @@ -41,7 +41,7 @@ spec: - name: TZ value: "{{ .Values.TIMEZONE }}" - name: bootstrap.memory_lock - value: "true" + value: "false" - name: cluster.name value: nextcloud-aio - name: discovery.type @@ -54,7 +54,7 @@ spec: value: basic - name: xpack.security.enabled value: "false" - image: ghcr.io/nextcloud-releases/aio-fulltextsearch:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-fulltextsearch:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-service.yaml index ae759475..efe474b3 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-fulltextsearch-service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-fulltextsearch name: nextcloud-aio-fulltextsearch diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-deployment.yaml index 84ea42e6..af15d4b3 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-imaginary name: nextcloud-aio-imaginary @@ -16,7 +16,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-imaginary spec: @@ -38,7 +38,7 @@ spec: value: "{{ .Values.IMAGINARY_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-imaginary:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-imaginary:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-service.yaml index a5fb3266..44a57006 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-imaginary-service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-imaginary name: nextcloud-aio-imaginary diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-deployment.yaml index c6559fbd..8b6e8211 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-nextcloud name: nextcloud-aio-nextcloud @@ -17,7 +17,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-nextcloud spec: @@ -38,7 +38,7 @@ spec: # AIO settings start # Do not remove or change this line! initContainers: - name: init-volumes - image: ghcr.io/nextcloud-releases/aio-alpine:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - chmod - "777" @@ -100,8 +100,6 @@ spec: value: "{{ .Values.CLAMAV_ENABLED }}" - name: CLAMAV_HOST value: nextcloud-aio-clamav - - name: CLAMAV_MAX_SIZE - value: "{{ .Values.APACHE_MAX_SIZE }}" - name: COLLABORA_ENABLED value: "{{ .Values.COLLABORA_ENABLED }}" - name: COLLABORA_HOST @@ -116,6 +114,8 @@ spec: value: "{{ .Values.FULLTEXTSEARCH_PASSWORD }}" - name: FULLTEXTSEARCH_PORT value: "9200" + - name: FULLTEXTSEARCH_PROTOCOL + value: http - name: FULLTEXTSEARCH_USER value: elastic - name: IMAGINARY_ENABLED @@ -162,6 +162,8 @@ spec: value: nextcloud-aio-redis - name: REDIS_HOST_PASSWORD value: "{{ .Values.REDIS_PASSWORD }}" + - name: REDIS_PORT + value: "6379" - name: REMOVE_DISABLED_APPS value: "{{ .Values.REMOVE_DISABLED_APPS }}" - name: SIGNALING_SECRET @@ -188,7 +190,7 @@ spec: value: "{{ .Values.WHITEBOARD_ENABLED }}" - name: WHITEBOARD_SECRET value: "{{ .Values.WHITEBOARD_SECRET }}" - image: ghcr.io/nextcloud-releases/aio-nextcloud:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-nextcloud:20260122_105751 {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} # AIO-config - do not change this comment! securityContext: # The items below only work in container context diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-service.yaml index 18cf84d8..08ab70f2 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-nextcloud name: nextcloud-aio-nextcloud diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-deployment.yaml index dac83c98..c8e30d05 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-notify-push name: nextcloud-aio-notify-push @@ -17,7 +17,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-notify-push spec: @@ -53,9 +53,11 @@ spec: value: nextcloud-aio-redis - name: REDIS_HOST_PASSWORD value: "{{ .Values.REDIS_PASSWORD }}" + - name: REDIS_PORT + value: "6379" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-notify-push:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-notify-push:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-service.yaml index 2b7bfccd..986d98d4 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-notify-push-service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-notify-push name: nextcloud-aio-notify-push diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-deployment.yaml index 98f75058..2bb79f19 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-onlyoffice name: nextcloud-aio-onlyoffice @@ -18,13 +18,13 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-onlyoffice spec: initContainers: - name: init-volumes - image: ghcr.io/nextcloud-releases/aio-alpine:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - chmod - "777" @@ -42,7 +42,7 @@ spec: value: "{{ .Values.ONLYOFFICE_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-onlyoffice:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-onlyoffice:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-service.yaml index 6ff9afa1..5fc10b85 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-onlyoffice-service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-onlyoffice name: nextcloud-aio-onlyoffice diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-deployment.yaml index 61938748..28335e64 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-redis name: nextcloud-aio-redis @@ -17,7 +17,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-redis spec: @@ -39,7 +39,7 @@ spec: value: "{{ .Values.REDIS_PASSWORD }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-redis:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-redis:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-service.yaml index af82a0bb..a6a9a0a5 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-redis-service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-redis name: nextcloud-aio-redis diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-deployment.yaml index aa078677..679dd66e 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk name: nextcloud-aio-talk @@ -16,7 +16,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk spec: @@ -52,7 +52,7 @@ spec: value: "{{ .Values.TURN_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-talk:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-talk:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-deployment.yaml index 27a50beb..8e631656 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk-recording name: nextcloud-aio-talk-recording @@ -18,7 +18,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk-recording spec: @@ -44,7 +44,7 @@ spec: value: "{{ .Values.RECORDING_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-talk-recording:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-talk-recording:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-service.yaml index 4410ed72..87fe0355 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk-recording name: nextcloud-aio-talk-recording diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-service.yaml index 675a2729..65388792 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-service.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk name: nextcloud-aio-talk-public @@ -12,7 +12,6 @@ metadata: spec: ipFamilyPolicy: PreferDualStack type: LoadBalancer - externalTrafficPolicy: Local ports: - name: "{{ .Values.TALK_PORT }}" port: {{ .Values.TALK_PORT }} @@ -28,7 +27,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk name: nextcloud-aio-talk diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-deployment.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-deployment.yaml index 08d3fcca..5788cfa0 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-deployment.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-whiteboard name: nextcloud-aio-whiteboard @@ -16,7 +16,7 @@ spec: template: metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-whiteboard spec: @@ -44,11 +44,13 @@ spec: value: nextcloud-aio-redis - name: REDIS_HOST_PASSWORD value: "{{ .Values.REDIS_PASSWORD }}" + - name: REDIS_PORT + value: "6379" - name: STORAGE_STRATEGY value: redis - name: TZ value: "{{ .Values.TIMEZONE }}" - image: ghcr.io/nextcloud-releases/aio-whiteboard:20251015_082711 + image: ghcr.io/nextcloud-releases/aio-whiteboard:20260122_105751 readinessProbe: exec: command: diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-service.yaml index 8c8cb5aa..299f1ec3 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-service.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.version: 1.37.0 (fb0539e64) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-whiteboard name: nextcloud-aio-whiteboard diff --git a/nextcloud-aio-helm-chart/update-helm.sh b/nextcloud-aio-helm-chart/update-helm.sh index 80882854..9e5aba86 100755 --- a/nextcloud-aio-helm-chart/update-helm.sh +++ b/nextcloud-aio-helm-chart/update-helm.sh @@ -27,7 +27,7 @@ cp latest.yml latest.yml.backup # Additional config # shellcheck disable=SC1083 -sed -i -E '/^( *- )(NET_RAW|SYS_NICE|MKNOD|SYS_ADMIN|CHOWN|SYS_CHROOT|FOWNER)$/!s/( *- )([A-Z_]+)$/\1\2=${\2}/' latest.yml +sed -i -E '/^( *- )(NET_RAW|SYS_NICE|MKNOD|SYS_ADMIN|CHOWN|SYS_CHROOT|FOWNER|MAC_OVERRIDE|BLOCK_SUSPEND|AUDIT_READ)$/!s/( *- )([A-Z_]+)$/\1\2=${\2}/' latest.yml cp sample.conf /tmp/ sed -i 's|^|export |' /tmp/sample.conf # shellcheck disable=SC1091 @@ -252,7 +252,7 @@ find ./ -name '*talk-service.yaml' -exec grep -v '{{ .Values.TALK.*}}\|protocol: # shellcheck disable=SC1083 find ./ -name '*talk-service.yaml' -exec mv /tmp/talk-service.copy \{} \; # shellcheck disable=SC1083 -find ./ -name '*service.yaml' -exec sed -i "/type: LoadBalancer/a\ \ externalTrafficPolicy: Local" \{} \; +find ./ -name '*apache-service.yaml' -exec sed -i "/type: LoadBalancer/a\ \ externalTrafficPolicy: Local" \{} \; # shellcheck disable=SC1083 find ./ -name '*service.yaml' -exec sed -i "/^spec:/a\ \ ipFamilyPolicy: PreferDualStack" \{} \; # shellcheck disable=SC1083 @@ -343,6 +343,21 @@ EOL # shellcheck disable=SC1083 find ./ -name '*talk-deployment.yaml' -exec sed -i "/^.*\- env:/r /tmp/additional-talk.config" \{} \; +# Additional collabora config +# shellcheck disable=SC1083 +find ./ -name '*collabora-deployment.yaml' -exec sed -i "s/image: ghcr.io.*/IMAGE_PLACEHOLDER/" \{} \; +cat << EOL > /tmp/additional-collabora.config + {{- if contains "--o:support_key=" (join " " (.Values.ADDITIONAL_COLLABORA_OPTIONS | default list)) }} + image: ghcr.io/nextcloud-releases/aio-collabora-online:$DOCKER_TAG + {{- else }} + image: ghcr.io/nextcloud-releases/aio-collabora:$DOCKER_TAG + {{- end }} +EOL +# shellcheck disable=SC1083 +find ./ -name '*collabora-deployment.yaml' -exec sed -i "/IMAGE_PLACEHOLDER/r /tmp/additional-collabora.config" \{} \; +# shellcheck disable=SC1083 +find ./ -name '*collabora-deployment.yaml' -exec sed -i "/IMAGE_PLACEHOLDER/d" \{} \; + cat << EOL > templates/nextcloud-aio-networkpolicy.yaml {{- if eq .Values.NETWORK_POLICY_ENABLED "yes" }} # https://github.com/ahmetb/kubernetes-network-policy-recipes/blob/master/04-deny-traffic-from-other-namespaces.md @@ -392,7 +407,7 @@ rm latest.yml mv latest.yml.backup latest.yml # Get version of AIO -AIO_VERSION="$(grep 'Nextcloud AIO ' ../php/templates/containers.twig | grep -oP '[0-9]+.[0-9]+.[0-9]+')" +AIO_VERSION="$(grep 'Nextcloud AIO ' ../php/templates/includes/aio-version.twig | grep -oP '[0-9]+.[0-9]+.[0-9]+')" sed -i "s|^version:.*|version: $AIO_VERSION|" ../helm-chart/Chart.yaml # Conversion of sample.conf @@ -410,8 +425,8 @@ sed -i 's|17179869184|"17179869184"|' /tmp/sample.conf # shellcheck disable=SC2129 echo "" >> /tmp/sample.conf # shellcheck disable=SC2129 -echo 'STORAGE_CLASS: # By setting this, you can adjust the storage class for your volumes. This should be a fast storage like SSD backed storage!' >> /tmp/sample.conf -echo 'STORAGE_CLASS_DATA: # Allows to set a dedicated storage class for the Nextcloud data volume. This can be a bit slower storage than the one above. ⚠️ Warning: only set this for new installations, not existing ones!' >> /tmp/sample.conf +echo 'STORAGE_CLASS: # By setting this, you can adjust the storage class for your volumes. This should be a fast storage like SSD backed storage! This storage class must provide RWX and RWO volumes (ReadWriteMany and ReadWriteOnce).' >> /tmp/sample.conf +echo 'STORAGE_CLASS_DATA: # Allows to set a dedicated storage class for the Nextcloud data volume. This can be a bit slower storage than the one above. This storage class must provide RWX volumes (ReadWriteMany). ⚠️ Warning: only set this for new installations, not existing ones!' >> /tmp/sample.conf for variable in "${VOLUME_VARIABLE[@]}"; do echo "$variable: 1Gi # You can change the size of the $(echo "$variable" | sed 's|_STORAGE_SIZE||;s|_|-|g' | tr '[:upper:]' '[:lower:]') volume that default to 1Gi with this value" >> /tmp/sample.conf done diff --git a/nextcloud-aio-helm-chart/values.yaml b/nextcloud-aio-helm-chart/values.yaml index 10603a7c..25fb2c92 100755 --- a/nextcloud-aio-helm-chart/values.yaml +++ b/nextcloud-aio-helm-chart/values.yaml @@ -38,8 +38,8 @@ REMOVE_DISABLED_APPS: yes # Setting this to no keep Nextcloud apps that a TALK_PORT: 3478 # This allows to adjust the port that the talk container is using. It should be set to something higher than 1024! Otherwise it might not work! UPDATE_NEXTCLOUD_APPS: no # When setting to yes (with quotes), it will automatically update all installed Nextcloud apps upon container startup on saturdays. -STORAGE_CLASS: # By setting this, you can adjust the storage class for your volumes. This should be a fast storage like SSD backed storage! -STORAGE_CLASS_DATA: # Allows to set a dedicated storage class for the Nextcloud data volume. This can be a bit slower storage than the one above. ⚠️ Warning: only set this for new installations, not existing ones! +STORAGE_CLASS: # By setting this, you can adjust the storage class for your volumes. This should be a fast storage like SSD backed storage! This storage class must provide RWX and RWO volumes (ReadWriteMany and ReadWriteOnce). +STORAGE_CLASS_DATA: # Allows to set a dedicated storage class for the Nextcloud data volume. This can be a bit slower storage than the one above. This storage class must provide RWX volumes (ReadWriteMany). ⚠️ Warning: only set this for new installations, not existing ones! APACHE_STORAGE_SIZE: 1Gi # You can change the size of the apache volume that default to 1Gi with this value CLAMAV_STORAGE_SIZE: 1Gi # You can change the size of the clamav volume that default to 1Gi with this value DATABASE_STORAGE_SIZE: 1Gi # You can change the size of the database volume that default to 1Gi with this value diff --git a/php/composer.lock b/php/composer.lock index 4809bae8..77403624 100644 --- a/php/composer.lock +++ b/php/composer.lock @@ -333,16 +333,16 @@ }, { "name": "http-interop/http-factory-guzzle", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/http-interop/http-factory-guzzle.git", - "reference": "8f06e92b95405216b237521cc64c804dd44c4a81" + "reference": "c2c859ceb05c3f42e710b60555f4c35b6a4a3995" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/8f06e92b95405216b237521cc64c804dd44c4a81", - "reference": "8f06e92b95405216b237521cc64c804dd44c4a81", + "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/c2c859ceb05c3f42e710b60555f4c35b6a4a3995", + "reference": "c2c859ceb05c3f42e710b60555f4c35b6a4a3995", "shasum": "" }, "require": { @@ -385,22 +385,22 @@ ], "support": { "issues": "https://github.com/http-interop/http-factory-guzzle/issues", - "source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.0" + "source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.1" }, - "time": "2021-07-21T13:50:14+00:00" + "time": "2025-12-15T11:28:16+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.6", + "version": "v2.0.8", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "038ce42edee619599a1debb7e81d7b3759492819" + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", - "reference": "038ce42edee619599a1debb7e81d7b3759492819", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7581a4407012f5f53365e11bafc520fd7f36bc9b", + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b", "shasum": "" }, "require": { @@ -409,7 +409,7 @@ "require-dev": { "illuminate/support": "^10.0|^11.0|^12.0", "nesbot/carbon": "^2.67|^3.0", - "pestphp/pest": "^2.36|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/phpstan": "^2.0", "symfony/var-dumper": "^6.2.0|^7.0.0" }, @@ -448,7 +448,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-10-09T13:42:30+00:00" + "time": "2026-01-08T16:22:46+00:00" }, { "name": "nikic/fast-route", @@ -1092,16 +1092,16 @@ }, { "name": "slim/csrf", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/slimphp/Slim-Csrf.git", - "reference": "179cbcf40ee1d246d4906aefed42d3e62066974b" + "reference": "a476a61e38451e138c400f6b4ca96037f3c2dd39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim-Csrf/zipball/179cbcf40ee1d246d4906aefed42d3e62066974b", - "reference": "179cbcf40ee1d246d4906aefed42d3e62066974b", + "url": "https://api.github.com/repos/slimphp/Slim-Csrf/zipball/a476a61e38451e138c400f6b4ca96037f3c2dd39", + "reference": "a476a61e38451e138c400f6b4ca96037f3c2dd39", "shasum": "" }, "require": { @@ -1112,8 +1112,6 @@ "psr/http-server-middleware": "^1.0" }, "require-dev": { - "phpspec/prophecy": "^1.19", - "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "^3.10" }, @@ -1144,28 +1142,28 @@ ], "support": { "issues": "https://github.com/slimphp/Slim-Csrf/issues", - "source": "https://github.com/slimphp/Slim-Csrf/tree/1.5.0" + "source": "https://github.com/slimphp/Slim-Csrf/tree/1.5.1" }, - "time": "2024-06-08T16:37:18+00:00" + "time": "2025-11-02T14:58:28+00:00" }, { "name": "slim/slim", - "version": "4.15.0", + "version": "4.15.1", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", - "reference": "17eba5182975878a0ab9b27982cd2e2cfcb67ea2" + "reference": "887893516557506f254d950425ce7f5387a26970" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/17eba5182975878a0ab9b27982cd2e2cfcb67ea2", - "reference": "17eba5182975878a0ab9b27982cd2e2cfcb67ea2", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/887893516557506f254d950425ce7f5387a26970", + "reference": "887893516557506f254d950425ce7f5387a26970", "shasum": "" }, "require": { "ext-json": "*", "nikic/fast-route": "^1.3", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "psr/container": "^1.0 || ^2.0", "psr/http-factory": "^1.1", "psr/http-message": "^1.1 || ^2.0", @@ -1185,7 +1183,7 @@ "phpspec/prophecy": "^1.19", "phpspec/prophecy-phpunit": "^2.1", "phpstan/phpstan": "^1 || ^2", - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^9.6 || ^10 || ^11 || ^12", "slim/http": "^1.3", "slim/psr7": "^1.6", "squizlabs/php_codesniffer": "^3.10", @@ -1262,7 +1260,7 @@ "type": "tidelift" } ], - "time": "2025-08-20T18:16:16+00:00" + "time": "2025-11-21T12:23:44+00:00" }, { "name": "slim/twig-view", @@ -1646,16 +1644,16 @@ }, { "name": "twig/twig", - "version": "v3.21.1", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", "shasum": "" }, "require": { @@ -1709,7 +1707,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.21.1" + "source": "https://github.com/twigphp/Twig/tree/v3.23.0" }, "funding": [ { @@ -1721,7 +1719,7 @@ "type": "tidelift" } ], - "time": "2025-05-03T07:21:55+00:00" + "time": "2026-01-23T21:00:41+00:00" } ], "packages-dev": [ @@ -2037,16 +2035,16 @@ }, { "name": "amphp/parallel", - "version": "v2.3.2", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/amphp/parallel.git", - "reference": "321b45ae771d9c33a068186b24117e3cd1c48dce" + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parallel/zipball/321b45ae771d9c33a068186b24117e3cd1c48dce", - "reference": "321b45ae771d9c33a068186b24117e3cd1c48dce", + "url": "https://api.github.com/repos/amphp/parallel/zipball/296b521137a54d3a02425b464e5aee4c93db2c60", + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60", "shasum": "" }, "require": { @@ -2109,7 +2107,7 @@ ], "support": { "issues": "https://github.com/amphp/parallel/issues", - "source": "https://github.com/amphp/parallel/tree/v2.3.2" + "source": "https://github.com/amphp/parallel/tree/v2.3.3" }, "funding": [ { @@ -2117,7 +2115,7 @@ "type": "github" } ], - "time": "2025-08-27T21:55:40+00:00" + "time": "2025-11-15T06:23:42+00:00" }, { "name": "amphp/parser", @@ -2757,22 +2755,22 @@ }, { "name": "danog/advanced-json-rpc", - "version": "v3.2.2", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/danog/php-advanced-json-rpc.git", - "reference": "aadb1c4068a88c3d0530cfe324b067920661efcb" + "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danog/php-advanced-json-rpc/zipball/aadb1c4068a88c3d0530cfe324b067920661efcb", - "reference": "aadb1c4068a88c3d0530cfe324b067920661efcb", + "url": "https://api.github.com/repos/danog/php-advanced-json-rpc/zipball/ae703ea7b4811797a10590b6078de05b3b33dd91", + "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91", "shasum": "" }, "require": { "netresearch/jsonmapper": "^5", "php": ">=8.1", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0 || ^6" }, "replace": { "felixfbecker/php-advanced-json-rpc": "^3" @@ -2803,9 +2801,9 @@ "description": "A more advanced JSONRPC implementation", "support": { "issues": "https://github.com/danog/php-advanced-json-rpc/issues", - "source": "https://github.com/danog/php-advanced-json-rpc/tree/v3.2.2" + "source": "https://github.com/danog/php-advanced-json-rpc/tree/v3.2.3" }, - "time": "2025-02-14T10:55:15+00:00" + "time": "2026-01-12T21:07:10+00:00" }, { "name": "daverandom/libdns", @@ -3113,33 +3111,38 @@ }, { "name": "league/uri", - "version": "7.5.1", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "4436c6ec8d458e4244448b069cc572d088230b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.8", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-components": "Needed to easily manipulate URI objects components", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3167,6 +3170,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -3179,9 +3183,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -3191,7 +3197,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.8.0" }, "funding": [ { @@ -3199,26 +3205,25 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2026-01-14T17:24:56+00:00" }, { "name": "league/uri-interfaces", - "version": "7.5.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -3226,6 +3231,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3250,7 +3256,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -3275,7 +3281,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" }, "funding": [ { @@ -3283,7 +3289,7 @@ "type": "github" } ], - "time": "2024-12-08T08:18:47+00:00" + "time": "2026-01-15T06:54:53+00:00" }, { "name": "netresearch/jsonmapper", @@ -3338,16 +3344,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -3390,9 +3396,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3449,16 +3455,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.3", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" + "reference": "2f5cbed597cb261d1ea458f3da3a9ad32e670b1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2f5cbed597cb261d1ea458f3da3a9ad32e670b1e", + "reference": "2f5cbed597cb261d1ea458f3da3a9ad32e670b1e", "shasum": "" }, "require": { @@ -3466,9 +3472,9 @@ "ext-filter": "*", "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1" + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { "mockery/mockery": "~1.3.5 || ~1.6.0", @@ -3477,7 +3483,8 @@ "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "psalm/phar": "^5.26" + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" }, "type": "library", "extra": { @@ -3507,44 +3514,44 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.1" }, - "time": "2025-08-01T19:43:32+00:00" + "time": "2026-01-20T15:30:42+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.10.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.3 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.18|^2.0" + "phpstan/phpdoc-parser": "^2.0" }, "require-dev": { "ext-tokenizer": "*", "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" + "psalm/phar": "^4" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -3565,22 +3572,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" }, - "time": "2024-11-09T15:12:26+00:00" + "time": "2026-01-06T21:53:42+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.0", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { @@ -3612,22 +3619,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2025-08-30T15:50:23+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { "name": "revolt/event-loop", - "version": "v1.0.7", + "version": "v1.0.8", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", "shasum": "" }, "require": { @@ -3684,9 +3691,9 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" }, - "time": "2025-01-25T19:27:39+00:00" + "time": "2025-08-27T21:33:23+00:00" }, { "name": "sebastian/diff", @@ -3757,16 +3764,16 @@ }, { "name": "spatie/array-to-xml", - "version": "3.4.0", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67" + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", "shasum": "" }, "require": { @@ -3809,7 +3816,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.4.0" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" }, "funding": [ { @@ -3821,7 +3828,7 @@ "type": "github" } ], - "time": "2024-12-16T12:45:15+00:00" + "time": "2025-12-15T09:00:41+00:00" }, { "name": "sserbin/twig-linter", @@ -3883,16 +3890,16 @@ }, { "name": "symfony/console", - "version": "v6.4.26", + "version": "v6.4.32", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "492de6dfd93910d7d7a729c5a04ddcd2b9e99c4f" + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/492de6dfd93910d7d7a729c5a04ddcd2b9e99c4f", - "reference": "492de6dfd93910d7d7a729c5a04ddcd2b9e99c4f", + "url": "https://api.github.com/repos/symfony/console/zipball/0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", "shasum": "" }, "require": { @@ -3957,7 +3964,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.26" + "source": "https://github.com/symfony/console/tree/v6.4.32" }, "funding": [ { @@ -3977,29 +3984,29 @@ "type": "tidelift" } ], - "time": "2025-09-26T12:13:46+00:00" + "time": "2026-01-13T08:45:59+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.2", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4027,7 +4034,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.2" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -4047,20 +4054,20 @@ "type": "tidelift" } ], - "time": "2025-07-07T08:17:47+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/finder", - "version": "v6.4.24", + "version": "v6.4.33", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "73089124388c8510efb8d2d1689285d285937b08" + "reference": "24965ca011dac87431729640feef8bcf7b5523e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/73089124388c8510efb8d2d1689285d285937b08", - "reference": "73089124388c8510efb8d2d1689285d285937b08", + "url": "https://api.github.com/repos/symfony/finder/zipball/24965ca011dac87431729640feef8bcf7b5523e0", + "reference": "24965ca011dac87431729640feef8bcf7b5523e0", "shasum": "" }, "require": { @@ -4095,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.24" + "source": "https://github.com/symfony/finder/tree/v6.4.33" }, "funding": [ { @@ -4115,7 +4122,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T12:02:45+00:00" + "time": "2026-01-26T13:03:48+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -4366,16 +4373,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -4429,7 +4436,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -4440,31 +4447,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v7.3.4", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f96476035142921000338bad71e5247fbc138872" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", - "reference": "f96476035142921000338bad71e5247fbc138872", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -4472,11 +4484,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4515,7 +4527,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.4" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { @@ -4535,20 +4547,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:36:48+00:00" + "time": "2026-01-12T10:54:30+00:00" }, { "name": "vimeo/psalm", - "version": "6.13.1", + "version": "6.14.3", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51" + "reference": "d0b040a91f280f071c1abcb1b77ce3822058725a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51", - "reference": "1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d0b040a91f280f071c1abcb1b77ce3822058725a", + "reference": "d0b040a91f280f071c1abcb1b77ce3822058725a", "shasum": "" }, "require": { @@ -4571,11 +4583,11 @@ "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^5.0", "nikic/php-parser": "^5.0.0", - "php": "~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3", + "php": "~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3 || ~8.5.0", "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", - "symfony/console": "^6.0 || ^7.0", - "symfony/filesystem": "~6.3.12 || ~6.4.3 || ^7.0.3", + "symfony/console": "^6.0 || ^7.0 || ^8.0", + "symfony/filesystem": "~6.3.12 || ~6.4.3 || ^7.0.3 || ^8.0", "symfony/polyfill-php84": "^1.31.0" }, "provide": { @@ -4597,7 +4609,7 @@ "psalm/plugin-phpunit": "^0.19", "slevomat/coding-standard": "^8.4", "squizlabs/php_codesniffer": "^3.6", - "symfony/process": "^6.0 || ^7.0" + "symfony/process": "^6.0 || ^7.0 || ^8.0" }, "suggest": { "ext-curl": "In order to send data to shepherd", @@ -4653,7 +4665,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2025-08-06T10:10:28+00:00" + "time": "2025-12-23T15:36:48+00:00" }, { "name": "wapmorgan/php-deprecation-detector", @@ -4724,33 +4736,33 @@ }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", "shasum": "" }, "require": { "ext-ctype": "*", - "php": "^7.2 || ^8.0" + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-feature/2-0": "2.0-dev" } }, "autoload": { @@ -4766,6 +4778,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", @@ -4776,9 +4792,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/2.1.2" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2026-01-13T14:02:24+00:00" } ], "aliases": [], @@ -4797,5 +4813,5 @@ "ext-apcu": "*" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/php/containers.json b/php/containers.json index 4506c09e..8e9218ac 100644 --- a/php/containers.json +++ b/php/containers.json @@ -204,6 +204,7 @@ "POSTGRES_DB=nextcloud_database", "POSTGRES_USER=nextcloud", "REDIS_HOST=nextcloud-aio-redis", + "REDIS_PORT=6379", "REDIS_HOST_PASSWORD=%REDIS_PASSWORD%", "APACHE_HOST=nextcloud-aio-apache", "APACHE_PORT=%APACHE_PORT%", @@ -218,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", @@ -229,13 +231,14 @@ "UPDATE_NEXTCLOUD_APPS=%UPDATE_NEXTCLOUD_APPS%", "TZ=%TIMEZONE%", "TALK_PORT=%TALK_PORT%", + "TURN_DOMAIN=%TURN_DOMAIN%", "IMAGINARY_ENABLED=%IMAGINARY_ENABLED%", "IMAGINARY_HOST=nextcloud-aio-imaginary", - "CLAMAV_MAX_SIZE=%APACHE_MAX_SIZE%", "PHP_UPLOAD_LIMIT=%NEXTCLOUD_UPLOAD_LIMIT%", "PHP_MEMORY_LIMIT=%NEXTCLOUD_MEMORY_LIMIT%", "FULLTEXTSEARCH_ENABLED=%FULLTEXTSEARCH_ENABLED%", "FULLTEXTSEARCH_HOST=nextcloud-aio-fulltextsearch", + "FULLTEXTSEARCH_PROTOCOL=http", "FULLTEXTSEARCH_PORT=9200", "FULLTEXTSEARCH_USER=elastic", "FULLTEXTSEARCH_INDEX=nextcloud-aio", @@ -305,6 +308,7 @@ "NEXTCLOUD_HOST=nextcloud-aio-nextcloud", "TZ=%TIMEZONE%", "REDIS_HOST=nextcloud-aio-redis", + "REDIS_PORT=6379", "REDIS_HOST_PASSWORD=%REDIS_PASSWORD%", "POSTGRES_HOST=nextcloud-aio-database", "POSTGRES_PORT=5432", @@ -380,7 +384,7 @@ "internal_port": "9980", "environment": [ "aliasgroup1=https://%NC_DOMAIN%:443,http://nextcloud-aio-apache:23973", - "extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:mount_jail_tree=false --o:logging.level=warning --o:logging.level_startup=warning --o:home_mode.enable=true %COLLABORA_SECCOMP_POLICY% --o:remote_font_config.url=https://%NC_DOMAIN%/apps/richdocuments/settings/fonts.json --o:net.post_allow.host[0]=.+", + "extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:logging.disable_server_audit=true --o:logging.level=warning --o:logging.level_startup=warning --o:welcome.enable=false %COLLABORA_SECCOMP_POLICY% --o:remote_font_config.url=https://%NC_DOMAIN%/apps/richdocuments/settings/fonts.json --o:net.post_allow.host[0]=.+", "dictionaries=%COLLABORA_DICTIONARIES%", "TZ=%TIMEZONE%", "server_name=%NC_DOMAIN%", @@ -720,10 +724,6 @@ "ONLYOFFICE_SECRET" ], "restart": "unless-stopped", - "nextcloud_exec_commands": [ - "echo 'Activating OnlyOffice config...'", - "php /var/www/html/occ onlyoffice:documentserver --check" - ], "profiles": [ "onlyoffice" ], @@ -794,7 +794,7 @@ "environment": [ "TZ=%TIMEZONE%", "ES_JAVA_OPTS=%FULLTEXTSEARCH_JAVA_OPTIONS%", - "bootstrap.memory_lock=true", + "bootstrap.memory_lock=false", "cluster.name=nextcloud-aio", "discovery.type=single-node", "logger.level=WARN", @@ -875,6 +875,7 @@ "JWT_SECRET_KEY=%WHITEBOARD_SECRET%", "STORAGE_STRATEGY=redis", "REDIS_HOST=nextcloud-aio-redis", + "REDIS_PORT=6379", "REDIS_HOST_PASSWORD=%REDIS_PASSWORD%", "BACKUP_DIR=/tmp" ], diff --git a/php/cool-seccomp-profile.json b/php/cool-seccomp-profile.json new file mode 100644 index 00000000..ed2320ca --- /dev/null +++ b/php/cool-seccomp-profile.json @@ -0,0 +1,844 @@ +{ + "defaultAction": "SCMP_ACT_ERRNO", + "defaultErrnoRet": 1, + "archMap": [ + { + "architecture": "SCMP_ARCH_X86_64", + "subArchitectures": [ + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ] + }, + { + "architecture": "SCMP_ARCH_AARCH64", + "subArchitectures": [ + "SCMP_ARCH_ARM" + ] + }, + { + "architecture": "SCMP_ARCH_MIPS64", + "subArchitectures": [ + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64N32" + ] + }, + { + "architecture": "SCMP_ARCH_MIPS64N32", + "subArchitectures": [ + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64" + ] + }, + { + "architecture": "SCMP_ARCH_MIPSEL64", + "subArchitectures": [ + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64N32" + ] + }, + { + "architecture": "SCMP_ARCH_MIPSEL64N32", + "subArchitectures": [ + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64" + ] + }, + { + "architecture": "SCMP_ARCH_S390X", + "subArchitectures": [ + "SCMP_ARCH_S390" + ] + }, + { + "architecture": "SCMP_ARCH_RISCV64", + "subArchitectures": null + } + ], + "syscalls": [ + { + "names": [ + "unshare", + "mount", + "setns", + "clone", + "chroot", + "umount2" + ], + "action": "SCMP_ACT_ALLOW" + }, + { + "names": [ + "accept", + "accept4", + "access", + "adjtimex", + "alarm", + "bind", + "brk", + "cachestat", + "capget", + "capset", + "chdir", + "chmod", + "chown", + "chown32", + "clock_adjtime", + "clock_adjtime64", + "clock_getres", + "clock_getres_time64", + "clock_gettime", + "clock_gettime64", + "clock_nanosleep", + "clock_nanosleep_time64", + "close", + "close_range", + "connect", + "copy_file_range", + "creat", + "dup", + "dup2", + "dup3", + "epoll_create", + "epoll_create1", + "epoll_ctl", + "epoll_ctl_old", + "epoll_pwait", + "epoll_pwait2", + "epoll_wait", + "epoll_wait_old", + "eventfd", + "eventfd2", + "execve", + "execveat", + "exit", + "exit_group", + "faccessat", + "faccessat2", + "fadvise64", + "fadvise64_64", + "fallocate", + "fanotify_mark", + "fchdir", + "fchmod", + "fchmodat", + "fchmodat2", + "fchown", + "fchown32", + "fchownat", + "fcntl", + "fcntl64", + "fdatasync", + "fgetxattr", + "flistxattr", + "flock", + "fork", + "fremovexattr", + "fsetxattr", + "fstat", + "fstat64", + "fstatat64", + "fstatfs", + "fstatfs64", + "fsync", + "ftruncate", + "ftruncate64", + "futex", + "futex_requeue", + "futex_time64", + "futex_wait", + "futex_waitv", + "futex_wake", + "futimesat", + "getcpu", + "getcwd", + "getdents", + "getdents64", + "getegid", + "getegid32", + "geteuid", + "geteuid32", + "getgid", + "getgid32", + "getgroups", + "getgroups32", + "getitimer", + "getpeername", + "getpgid", + "getpgrp", + "getpid", + "getppid", + "getpriority", + "getrandom", + "getresgid", + "getresgid32", + "getresuid", + "getresuid32", + "getrlimit", + "get_robust_list", + "getrusage", + "getsid", + "getsockname", + "getsockopt", + "get_thread_area", + "gettid", + "gettimeofday", + "getuid", + "getuid32", + "getxattr", + "inotify_add_watch", + "inotify_init", + "inotify_init1", + "inotify_rm_watch", + "io_cancel", + "ioctl", + "io_destroy", + "io_getevents", + "io_pgetevents", + "io_pgetevents_time64", + "ioprio_get", + "ioprio_set", + "io_setup", + "io_submit", + "ipc", + "kill", + "landlock_add_rule", + "landlock_create_ruleset", + "landlock_restrict_self", + "lchown", + "lchown32", + "lgetxattr", + "link", + "linkat", + "listen", + "listxattr", + "llistxattr", + "_llseek", + "lremovexattr", + "lseek", + "lsetxattr", + "lstat", + "lstat64", + "madvise", + "map_shadow_stack", + "membarrier", + "memfd_create", + "memfd_secret", + "mincore", + "mkdir", + "mkdirat", + "mknod", + "mknodat", + "mlock", + "mlock2", + "mlockall", + "mmap", + "mmap2", + "mprotect", + "mq_getsetattr", + "mq_notify", + "mq_open", + "mq_timedreceive", + "mq_timedreceive_time64", + "mq_timedsend", + "mq_timedsend_time64", + "mq_unlink", + "mremap", + "msgctl", + "msgget", + "msgrcv", + "msgsnd", + "msync", + "munlock", + "munlockall", + "munmap", + "name_to_handle_at", + "nanosleep", + "newfstatat", + "_newselect", + "open", + "openat", + "openat2", + "pause", + "pidfd_open", + "pidfd_send_signal", + "pipe", + "pipe2", + "pkey_alloc", + "pkey_free", + "pkey_mprotect", + "poll", + "ppoll", + "ppoll_time64", + "prctl", + "pread64", + "preadv", + "preadv2", + "prlimit64", + "process_mrelease", + "pselect6", + "pselect6_time64", + "pwrite64", + "pwritev", + "pwritev2", + "read", + "readahead", + "readlink", + "readlinkat", + "readv", + "recv", + "recvfrom", + "recvmmsg", + "recvmmsg_time64", + "recvmsg", + "remap_file_pages", + "removexattr", + "rename", + "renameat", + "renameat2", + "restart_syscall", + "rmdir", + "rseq", + "rt_sigaction", + "rt_sigpending", + "rt_sigprocmask", + "rt_sigqueueinfo", + "rt_sigreturn", + "rt_sigsuspend", + "rt_sigtimedwait", + "rt_sigtimedwait_time64", + "rt_tgsigqueueinfo", + "sched_getaffinity", + "sched_getattr", + "sched_getparam", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_getscheduler", + "sched_rr_get_interval", + "sched_rr_get_interval_time64", + "sched_setaffinity", + "sched_setattr", + "sched_setparam", + "sched_setscheduler", + "sched_yield", + "seccomp", + "select", + "semctl", + "semget", + "semop", + "semtimedop", + "semtimedop_time64", + "send", + "sendfile", + "sendfile64", + "sendmmsg", + "sendmsg", + "sendto", + "setfsgid", + "setfsgid32", + "setfsuid", + "setfsuid32", + "setgid", + "setgid32", + "setgroups", + "setgroups32", + "setitimer", + "setpgid", + "setpriority", + "setregid", + "setregid32", + "setresgid", + "setresgid32", + "setresuid", + "setresuid32", + "setreuid", + "setreuid32", + "setrlimit", + "set_robust_list", + "setsid", + "setsockopt", + "set_thread_area", + "set_tid_address", + "setuid", + "setuid32", + "setxattr", + "shmat", + "shmctl", + "shmdt", + "shmget", + "shutdown", + "sigaltstack", + "signalfd", + "signalfd4", + "sigprocmask", + "sigreturn", + "socketcall", + "socketpair", + "splice", + "stat", + "stat64", + "statfs", + "statfs64", + "statx", + "symlink", + "symlinkat", + "sync", + "sync_file_range", + "syncfs", + "sysinfo", + "tee", + "tgkill", + "time", + "timer_create", + "timer_delete", + "timer_getoverrun", + "timer_gettime", + "timer_gettime64", + "timer_settime", + "timer_settime64", + "timerfd_create", + "timerfd_gettime", + "timerfd_gettime64", + "timerfd_settime", + "timerfd_settime64", + "times", + "tkill", + "truncate", + "truncate64", + "ugetrlimit", + "umask", + "uname", + "unlink", + "unlinkat", + "utime", + "utimensat", + "utimensat_time64", + "utimes", + "vfork", + "vmsplice", + "wait4", + "waitid", + "waitpid", + "write", + "writev" + ], + "action": "SCMP_ACT_ALLOW" + }, + { + "names": [ + "process_vm_readv", + "process_vm_writev", + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "minKernel": "4.8" + } + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 40, + "op": "SCMP_CMP_NE" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 0, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 8, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131072, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131080, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 4294967295, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "sync_file_range2", + "swapcontext" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "ppc64le" + ] + } + }, + { + "names": [ + "arm_fadvise64_64", + "arm_sync_file_range", + "sync_file_range2", + "breakpoint", + "cacheflush", + "set_tls" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "arm", + "arm64" + ] + } + }, + { + "names": [ + "arch_prctl" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "amd64", + "x32" + ] + } + }, + { + "names": [ + "modify_ldt" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "amd64", + "x32", + "x86" + ] + } + }, + { + "names": [ + "s390_pci_mmio_read", + "s390_pci_mmio_write", + "s390_runtime_instr" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "s390", + "s390x" + ] + } + }, + { + "names": [ + "riscv_flush_icache" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "arches": [ + "riscv64" + ] + } + }, + { + "names": [ + "open_by_handle_at" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_DAC_READ_SEARCH" + ] + } + }, + { + "names": [ + "bpf", + "clone", + "clone3", + "fanotify_init", + "fsconfig", + "fsmount", + "fsopen", + "fspick", + "lookup_dcookie", + "mount", + "mount_setattr", + "move_mount", + "open_tree", + "perf_event_open", + "quotactl", + "quotactl_fd", + "setdomainname", + "sethostname", + "setns", + "syslog", + "umount", + "umount2", + "unshare" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 2114060288, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ], + "arches": [ + "s390", + "s390x" + ] + } + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 1, + "value": 2114060288, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "comment": "s390 parameter ordering for clone is different", + "includes": { + "arches": [ + "s390", + "s390x" + ] + }, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "clone3" + ], + "action": "SCMP_ACT_ERRNO", + "errnoRet": 38, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "reboot" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_BOOT" + ] + } + }, + { + "names": [ + "chroot" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_CHROOT" + ] + } + }, + { + "names": [ + "delete_module", + "init_module", + "finit_module" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_MODULE" + ] + } + }, + { + "names": [ + "acct" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_PACCT" + ] + } + }, + { + "names": [ + "kcmp", + "pidfd_getfd", + "process_madvise", + "process_vm_readv", + "process_vm_writev", + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_PTRACE" + ] + } + }, + { + "names": [ + "iopl", + "ioperm" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_RAWIO" + ] + } + }, + { + "names": [ + "settimeofday", + "stime", + "clock_settime", + "clock_settime64" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_TIME" + ] + } + }, + { + "names": [ + "vhangup" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_TTY_CONFIG" + ] + } + }, + { + "names": [ + "get_mempolicy", + "mbind", + "set_mempolicy", + "set_mempolicy_home_node" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYS_NICE" + ] + } + }, + { + "names": [ + "syslog" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_SYSLOG" + ] + } + }, + { + "names": [ + "bpf" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_BPF" + ] + } + }, + { + "names": [ + "perf_event_open" + ], + "action": "SCMP_ACT_ALLOW", + "includes": { + "caps": [ + "CAP_PERFMON" + ] + } + } + ] +} diff --git a/php/domain-validator.php b/php/domain-validator.php index 57506b8a..4ac92690 100644 --- a/php/domain-validator.php +++ b/php/domain-validator.php @@ -3,15 +3,15 @@ $domain = $_GET['domain'] ?? ''; if (!str_contains($domain, '.')) { - http_response_code(400); + http_response_code(400); } elseif (str_contains($domain, '/')) { - http_response_code(400); + http_response_code(400); } elseif (str_contains($domain, ':')) { - http_response_code(400); + http_response_code(400); } elseif (filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false) { - http_response_code(400); + http_response_code(400); } elseif (filter_var($domain, FILTER_VALIDATE_IP)) { - http_response_code(400); + http_response_code(400); } else { // Commented because logging is disabled as otherwise all attempts will be logged which spams the logs // error_log($domain . ' was accepted as valid domain.'); 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-baseline.xml b/php/psalm-baseline.xml index 93ddb312..a9b7140d 100644 --- a/php/psalm-baseline.xml +++ b/php/psalm-baseline.xml @@ -1,64 +1,2 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 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/base_path.js b/php/public/base_path.js new file mode 100644 index 00000000..a55ed943 --- /dev/null +++ b/php/public/base_path.js @@ -0,0 +1,7 @@ +document.addEventListener("DOMContentLoaded", function() { + basePath = document.getElementById("base_path") + if (basePath) { + // Remove '/containers' from the end of the path, to get the base path only + basePath.value = window.location.pathname.slice(0, -11); + } +}); \ No newline at end of file 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 aac83826..cc06bb90 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -60,6 +60,7 @@ $app->get('/api/docker/getwatchtower', AIO\Controller\DockerController::class . $app->post('/api/docker/start', AIO\Controller\DockerController::class . ':StartContainer'); $app->post('/api/docker/backup', AIO\Controller\DockerController::class . ':StartBackupContainerBackup'); $app->post('/api/docker/backup-check', AIO\Controller\DockerController::class . ':StartBackupContainerCheck'); +$app->post('/api/docker/backup-list', AIO\Controller\DockerController::class . ':StartBackupContainerList'); $app->post('/api/docker/backup-check-repair', AIO\Controller\DockerController::class . ':StartBackupContainerCheckRepair'); $app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest'); $app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore'); @@ -76,76 +77,77 @@ $app->get('/containers', function (Request $request, Response $response, array $ $view->addExtension(new \AIO\Twig\ClassExtension()); /** @var \AIO\Data\ConfigurationManager $configurationManager */ $configurationManager = $container->get(\AIO\Data\ConfigurationManager::class); - /** @var \AIO\Docker\DockerActionManager $dockerActionManger */ - $dockerActionManger = $container->get(\AIO\Docker\DockerActionManager::class); + /** @var \AIO\Docker\DockerActionManager $dockerActionManager */ + $dockerActionManager = $container->get(\AIO\Docker\DockerActionManager::class); /** @var \AIO\Controller\DockerController $dockerController */ $dockerController = $container->get(\AIO\Controller\DockerController::class); - $dockerActionManger->ConnectMasterContainerToNetwork(); + $dockerActionManager->ConnectMasterContainerToNetwork(); $dockerController->StartDomaincheckContainer(); // Check if bypass_mastercontainer_update is provided on the URL, a special developer mode to bypass a mastercontainer update and use local image. $params = $request->getQueryParams(); $bypass_mastercontainer_update = isset($params['bypass_mastercontainer_update']); $bypass_container_update = isset($params['bypass_container_update']); + $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'), - 'is_mastercontainer_update_available' => ( $bypass_mastercontainer_update ? false : $dockerActionManger->IsMastercontainerUpdateAvailable() ), + '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' => $dockerActionManger->isBackupContainerRunning(), - 'backup_exit_code' => $dockerActionManger->GetBackupcontainerExitCode(), - 'is_instance_restore_attempt' => $configurationManager->isInstanceRestoreAttempt(), - 'borg_backup_mode' => $configurationManager->GetBorgBackupMode(), - 'was_start_button_clicked' => $configurationManager->wasStartButtonClicked(), - 'has_update_available' => $dockerActionManger->isAnyUpdateAvailable(), - 'last_backup_time' => $configurationManager->GetLastBackupTime(), - 'backup_times' => $configurationManager->GetBackupTimes(), - 'current_channel' => $dockerActionManger->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_backup_container_running' => $dockerActionManager->isBackupContainerRunning(), + 'backup_exit_code' => $dockerActionManager->GetBackupcontainerExitCode(), + 'is_instance_restore_attempt' => $configurationManager->instanceRestoreAttempt, + 'borg_backup_mode' => $configurationManager->backupMode, + 'was_start_button_clicked' => $configurationManager->wasStartButtonClicked, + 'has_update_available' => $dockerActionManager->isAnyUpdateAvailable(), + 'last_backup_time' => $configurationManager->getLastBackupTime(), + 'backup_times' => $configurationManager->getBackupTimes(), + 'current_channel' => $dockerActionManager->GetCurrentChannel(), + 'is_clamav_enabled' => $configurationManager->isClamavEnabled, + 'is_onlyoffice_enabled' => $configurationManager->isOnlyofficeEnabled, + 'is_collabora_enabled' => $configurationManager->isCollaboraEnabled, + 'is_talk_enabled' => $configurationManager->isTalkEnabled, + 'borg_restore_password' => $configurationManager->borgRestorePassword, + 'daily_backup_time' => $configurationManager->getDailyBackupTime(), 'is_daily_backup_running' => $configurationManager->isDailyBackupRunning(), - 'timezone' => $configurationManager->GetTimezone(), - 'skip_domain_validation' => $configurationManager->shouldDomainValidationBeSkipped(), - 'talk_port' => $configurationManager->GetTalkPort(), - 'collabora_dictionaries' => $configurationManager->GetCollaboraDictionaries(), - 'collabora_additional_options' => $configurationManager->GetAdditionalCollaboraOptions(), + 'timezone' => $configurationManager->timezone, + 'skip_domain_validation' => $configurationManager->shouldDomainValidationBeSkipped($skip_domain_validation), + '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'); $app->get('/login', function (Request $request, Response $response, array $args) use ($container) { $view = Twig::fromRequest($request); - /** @var \AIO\Docker\DockerActionManager $dockerActionManger */ - $dockerActionManger = $container->get(\AIO\Docker\DockerActionManager::class); + /** @var \AIO\Docker\DockerActionManager $dockerActionManager */ + $dockerActionManager = $container->get(\AIO\Docker\DockerActionManager::class); return $view->render($response, 'login.twig', [ - 'is_login_allowed' => $dockerActionManger->isLoginAllowed(), + 'is_login_allowed' => $dockerActionManager->isLoginAllowed(), ]); }); $app->get('/setup', function (Request $request, Response $response, array $args) use ($container) { @@ -178,17 +180,17 @@ $app->get('/', function (\Psr\Http\Message\RequestInterface $request, Response $ $setup = $container->get(\AIO\Data\Setup::class); if($setup->CanBeInstalled()) { return $response - ->withHeader('Location', '/setup') + ->withHeader('Location', 'setup') ->withStatus(302); } if($authManager->IsAuthenticated()) { return $response - ->withHeader('Location', '/containers') + ->withHeader('Location', 'containers') ->withStatus(302); } else { return $response - ->withHeader('Location', '/login') + ->withHeader('Location', 'login') ->withStatus(302); } }); 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/Container/Container.php b/php/src/Container/Container.php index baee1c00..6e5d2b54 100644 --- a/php/src/Container/Container.php +++ b/php/src/Container/Container.php @@ -5,121 +5,56 @@ namespace AIO\Container; use AIO\Data\ConfigurationManager; use AIO\Docker\DockerActionManager; use AIO\ContainerDefinitionFetcher; +use JsonException; readonly class Container { public function __construct( - private string $identifier, - private string $displayName, - private string $containerName, - private string $restartPolicy, - private int $maxShutdownTime, - private ContainerPorts $ports, - private string $internalPorts, - private ContainerVolumes $volumes, - private ContainerEnvironmentVariables $containerEnvironmentVariables, + public string $identifier, + public string $displayName, + public string $containerName, + public string $restartPolicy, + public int $maxShutdownTime, + public ContainerPorts $ports, + public string $internalPorts, + public ContainerVolumes $volumes, + public ContainerEnvironmentVariables $containerEnvironmentVariables, /** @var string[] */ - private array $dependsOn, + public array $dependsOn, private string $uiSecret, /** @var string[] */ - private array $devices, - private bool $enableNvidiaGpu, + public array $devices, + public bool $enableNvidiaGpu, /** @var string[] */ - private array $capAdd, - private int $shmSize, - private bool $apparmorUnconfined, + public array $capAdd, + public int $shmSize, + public bool $apparmorUnconfined, /** @var string[] */ - private array $backupVolumes, - private array $nextcloudExecCommands, - private bool $readOnlyRootFs, - private array $tmpfs, - private bool $init, - private string $imageTag, - private AioVariables $aioVariables, - private string $documentation, + public array $backupVolumes, + public array $nextcloudExecCommands, + public bool $readOnlyRootFs, + public array $tmpfs, + public bool $init, + public string $imageTag, + public AioVariables $aioVariables, + public string $documentation, private DockerActionManager $dockerActionManager ) { } - public function GetIdentifier() : string { - return $this->identifier; - } - - public function GetDisplayName() : string { - return $this->displayName; - } - - public function GetContainerName() : string { - return $this->containerName; - } - - public function GetRestartPolicy() : string { - return $this->restartPolicy; - } - - public function GetImageTag() : string { - return $this->imageTag; - } - - public function GetReadOnlySetting() : bool { - return $this->readOnlyRootFs; - } - - public function GetInit() : bool { - return $this->init; - } - - public function GetShmSize() : int { - return $this->shmSize; - } - - public function isApparmorUnconfined() : bool { - return $this->apparmorUnconfined; - } - - public function GetMaxShutdownTime() : int { - return $this->maxShutdownTime; - } - public function GetUiSecret() : string { return $this->dockerActionManager->GetAndGenerateSecretWrapper($this->uiSecret); } - public function GetTmpfs() : array { - return $this->tmpfs; - } - - public function GetDevices() : array { - return $this->devices; - } - - public function isNvidiaGpuEnabled() : bool { - return $this->enableNvidiaGpu; - } - - public function GetCapAdds() : array { - return $this->capAdd; - } - - public function GetBackupVolumes() : array { - return $this->backupVolumes; - } - - public function GetPorts() : ContainerPorts { - return $this->ports; - } - - public function GetInternalPort() : string { - return $this->internalPorts; - } - - public function GetVolumes() : ContainerVolumes { - return $this->volumes; - } - + /** + * @throws JsonException + */ public function GetRunningState() : ContainerState { return $this->dockerActionManager->GetContainerRunningState($this); } + /** + * @throws JsonException + */ public function GetRestartingState() : ContainerState { return $this->dockerActionManager->GetContainerRestartingState($this); } @@ -131,27 +66,4 @@ readonly class Container { public function GetStartingState() : ContainerState { return $this->dockerActionManager->GetContainerStartingState($this); } - - /** - * @return string[] - */ - public function GetDependsOn() : array { - return $this->dependsOn; - } - - public function GetNextcloudExecCommands() : array { - return $this->nextcloudExecCommands; - } - - public function GetEnvironmentVariables() : ContainerEnvironmentVariables { - return $this->containerEnvironmentVariables; - } - - public function GetAioVariables() : AioVariables { - return $this->aioVariables; - } - - public function GetDocumentation() : string { - return $this->documentation; - } } diff --git a/php/src/ContainerDefinitionFetcher.php b/php/src/ContainerDefinitionFetcher.php index a404e3a3..d2519ed7 100644 --- a/php/src/ContainerDefinitionFetcher.php +++ b/php/src/ContainerDefinitionFetcher.php @@ -25,7 +25,7 @@ readonly class ContainerDefinitionFetcher { $containers = $this->FetchDefinition(); foreach ($containers as $container) { - if ($container->GetIdentifier() === $id) { + if ($container->identifier === $id) { return $container; } } @@ -38,13 +38,13 @@ readonly class ContainerDefinitionFetcher { */ private function GetDefinition(): array { - $data = json_decode(file_get_contents(__DIR__ . '/../containers.json'), true); + $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(file_get_contents($path), true); + $additionalData = json_decode((string)file_get_contents($path), true, 512, JSON_THROW_ON_ERROR); $data = array_merge_recursive($data, $additionalData); if (isset($additionalData['aio_services_v1'][0]['display_name']) && $additionalData['aio_services_v1'][0]['display_name'] !== '') { // Store container_name of community containers in variable for later @@ -56,39 +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; } } @@ -110,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; } @@ -165,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; } } @@ -243,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 ed3be505..c40ee98c 100644 --- a/php/src/Controller/ConfigurationController.php +++ b/php/src/Controller/ConfigurationController.php @@ -17,28 +17,30 @@ readonly class ConfigurationController { public function SetConfig(Request $request, Response $response, array $args): Response { try { + $this->configurationManager->startTransaction(); if (isset($request->getParsedBody()['domain'])) { $domain = $request->getParsedBody()['domain'] ?? ''; - $this->configurationManager->SetDomain($domain); + $skipDomainValidation = isset($request->getParsedBody()['skip_domain_validation']); + $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'])) { @@ -53,76 +55,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'])) { @@ -136,35 +109,37 @@ 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->DeleteBorgBackupLocationVars(); + $this->configurationManager->deleteBorgBackupLocationItems(); } - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } catch (InvalidSettingConfigurationException $ex) { $response->getBody()->write($ex->getMessage()); return $response->withStatus(422); + } finally { + $this->configurationManager->commitTransaction(); } } } diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index 678bbdc9..81b920d0 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -23,7 +23,7 @@ readonly class DockerController { $container = $this->containerDefinitionFetcher->GetContainerById($id); // Start all dependencies first and then itself - foreach($container->GetDependsOn() as $dependency) { + foreach($container->dependsOn as $dependency) { $this->PerformRecursiveContainerStart($dependency, $pullImage); } @@ -46,7 +46,7 @@ readonly class DockerController { $container = $this->containerDefinitionFetcher->GetContainerById($id); // Pull all dependencies first and then itself - foreach($container->GetDependsOn() as $dependency) { + foreach($container->dependsOn as $dependency) { $this->PerformRecursiveImagePull($dependency); } @@ -85,13 +85,11 @@ readonly class DockerController { public function StartBackupContainerBackup(Request $request, Response $response, array $args) : Response { $forceStopNextcloud = true; $this->startBackup($forceStopNextcloud); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } public function startBackup(bool $forceStopNextcloud = false) : void { - $config = $this->configurationManager->GetConfig(); - $config['backup-mode'] = 'backup'; - $this->configurationManager->WriteConfig($config); + $this->configurationManager->backupMode = 'backup'; $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); @@ -102,28 +100,34 @@ readonly class DockerController { public function StartBackupContainerCheck(Request $request, Response $response, array $args) : Response { $this->checkBackup(); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); + } + + public function StartBackupContainerList(Request $request, Response $response, array $args) : Response { + $this->listBackup(); + return $response->withStatus(201)->withHeader('Location', '.'); } public function checkBackup() : void { - $config = $this->configurationManager->GetConfig(); - $config['backup-mode'] = 'check'; - $this->configurationManager->WriteConfig($config); + $this->configurationManager->backupMode = 'check'; + + $id = 'nextcloud-aio-borgbackup'; + $this->PerformRecursiveContainerStart($id); + } + + private function listBackup() : void { + $this->configurationManager->backupMode = 'list'; $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); } public function StartBackupContainerRestore(Request $request, Response $response, array $args) : Response { - $config = $this->configurationManager->GetConfig(); - $config['backup-mode'] = 'restore'; - $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; @@ -132,30 +136,26 @@ readonly class DockerController { $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } public function StartBackupContainerCheckRepair(Request $request, Response $response, array $args) : Response { - $config = $this->configurationManager->GetConfig(); - $config['backup-mode'] = 'check-repair'; - $this->configurationManager->WriteConfig($config); + $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 - $config = $this->configurationManager->GetConfig(); - $config['backup-mode'] = 'check'; - $this->configurationManager->WriteConfig($config); + $this->configurationManager->backupMode = 'check'; - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } public function StartBackupContainerTest(Request $request, Response $response, array $args) : Response { - $config = $this->configurationManager->GetConfig(); - $config['backup-mode'] = 'test'; - $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); @@ -163,7 +163,7 @@ readonly class DockerController { $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } public function StartContainer(Request $request, Response $response, array $args) : Response @@ -171,26 +171,26 @@ readonly class DockerController { $uri = $request->getUri(); $host = $uri->getHost(); $port = $uri->getPort(); + $path = $request->getParsedBody()['base_path'] ?? ''; if ($port === 8000) { error_log('The AIO_URL-port was discovered to be 8000 which is not expected. It is now set to 443.'); $port = 443; } 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 . ':' . $port; + $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']); @@ -204,14 +204,11 @@ readonly class DockerController { // Temporarily disabled as it leads much faster to docker rate limits // apcu_clear_cache(); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } 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(); @@ -223,7 +220,7 @@ readonly class DockerController { public function StartWatchtowerContainer(Request $request, Response $response, array $args) : Response { $this->startWatchtower(); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } public function startWatchtower() : void { @@ -239,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'); } @@ -250,7 +247,7 @@ readonly class DockerController { // We want to stop the Nextcloud container after 10s and not wait for the configured stop_grace_period $this->dockerActionManager->StopContainer($container, $forceStopNextcloud); } - foreach($container->GetDependsOn() as $dependency) { + foreach($container->dependsOn as $dependency) { $this->PerformRecursiveContainerStop($dependency, $forceStopNextcloud); } } @@ -261,7 +258,7 @@ readonly class DockerController { $forceStopNextcloud = true; $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } public function stopTopContainer() : void { @@ -272,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/Controller/LoginController.php b/php/src/Controller/LoginController.php index 196e7138..412ff9df 100644 --- a/php/src/Controller/LoginController.php +++ b/php/src/Controller/LoginController.php @@ -19,33 +19,33 @@ readonly class LoginController { public function TryLogin(Request $request, Response $response, array $args) : Response { if (!$this->dockerActionManager->isLoginAllowed()) { $response->getBody()->write("The login is blocked since Nextcloud is running."); - return $response->withHeader('Location', '/')->withStatus(422); + return $response->withHeader('Location', '.')->withStatus(422); } $password = $request->getParsedBody()['password'] ?? ''; if($this->authManager->CheckCredentials($password)) { $this->authManager->SetAuthState(true); - return $response->withHeader('Location', '/')->withStatus(201); + return $response->withHeader('Location', '.')->withStatus(201); } $response->getBody()->write("The password is incorrect."); - return $response->withHeader('Location', '/')->withStatus(422); + return $response->withHeader('Location', '.')->withStatus(422); } public function GetTryLogin(Request $request, Response $response, array $args) : Response { $token = $request->getQueryParams()['token'] ?? ''; if($this->authManager->CheckToken($token)) { $this->authManager->SetAuthState(true); - return $response->withHeader('Location', '/')->withStatus(302); + return $response->withHeader('Location', '../..')->withStatus(302); } - return $response->withHeader('Location', '/')->withStatus(302); + return $response->withHeader('Location', '../..')->withStatus(302); } public function Logout(Request $request, Response $response, array $args) : Response { $this->authManager->SetAuthState(false); return $response - ->withHeader('Location', '/') + ->withHeader('Location', '../..') ->withStatus(302); } } diff --git a/php/src/Cron/BackupNotification.php b/php/src/Cron/BackupNotification.php index 17da93b2..6fbab65f 100644 --- a/php/src/Cron/BackupNotification.php +++ b/php/src/Cron/BackupNotification.php @@ -1,33 +1,33 @@ -get(\AIO\Docker\DockerActionManager::class); -/** @var \AIO\ContainerDefinitionFetcher $containerDefinitionFetcher */ -$containerDefinitionFetcher = $container->get(\AIO\ContainerDefinitionFetcher::class); - -$id = 'nextcloud-aio-nextcloud'; -$nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id); - -$backupExitCode = $dockerActionManger->GetBackupcontainerExitCode(); - -if ($backupExitCode === 0) { - if (getenv('SEND_SUCCESS_NOTIFICATIONS') === "0") { - error_log("Daily backup successful! Only logging successful backup and not sending backup notification since that has been disabled! You can get further info by looking at the backup logs in the AIO interface."); - } else { - $dockerActionManger->sendNotification($nextcloudContainer, 'Daily backup successful!', 'You can get further info by looking at the backup logs in the AIO interface.'); - } -} - -if ($backupExitCode > 0) { - $dockerActionManger->sendNotification($nextcloudContainer, 'Daily backup failed!', 'You can get further info by looking at the backup logs in the AIO interface.'); -} +get(\AIO\Docker\DockerActionManager::class); +/** @var \AIO\ContainerDefinitionFetcher $containerDefinitionFetcher */ +$containerDefinitionFetcher = $container->get(\AIO\ContainerDefinitionFetcher::class); + +$id = 'nextcloud-aio-nextcloud'; +$nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id); + +$backupExitCode = $dockerActionManager->GetBackupcontainerExitCode(); + +if ($backupExitCode === 0) { + if (getenv('SEND_SUCCESS_NOTIFICATIONS') === "0") { + error_log("Daily backup successful! Only logging successful backup and not sending backup notification since that has been disabled! You can get further info by looking at the backup logs in the AIO interface."); + } else { + $dockerActionManager->sendNotification($nextcloudContainer, 'Daily backup successful!', 'You can get further info by looking at the backup logs in the AIO interface.'); + } +} + +if ($backupExitCode > 0) { + $dockerActionManager->sendNotification($nextcloudContainer, 'Daily backup failed!', 'You can get further info by looking at the backup logs in the AIO interface.'); +} diff --git a/php/src/Cron/CheckFreeDiskSpace.php b/php/src/Cron/CheckFreeDiskSpace.php index b462195e..1b5d2d64 100644 --- a/php/src/Cron/CheckFreeDiskSpace.php +++ b/php/src/Cron/CheckFreeDiskSpace.php @@ -11,8 +11,8 @@ require __DIR__ . '/../../vendor/autoload.php'; $container = \AIO\DependencyInjection::GetContainer(); -/** @var \AIO\Docker\DockerActionManager $dockerActionManger */ -$dockerActionManger = $container->get(\AIO\Docker\DockerActionManager::class); +/** @var \AIO\Docker\DockerActionManager $dockerActionManager */ +$dockerActionManager = $container->get(\AIO\Docker\DockerActionManager::class); /** @var \AIO\ContainerDefinitionFetcher $containerDefinitionFetcher */ $containerDefinitionFetcher = $container->get(\AIO\ContainerDefinitionFetcher::class); @@ -22,5 +22,5 @@ $nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id); $df = disk_free_space(DataConst::GetDataDirectory()); if ($df !== false && (int)$df < 1024 * 1024 * 1024 * 5) { error_log("The drive that hosts the mastercontainer volume has less than 5 GB free space. Container updates and backups might not succeed due to that!"); - $dockerActionManger->sendNotification($nextcloudContainer, 'Low on space!', 'The drive that hosts the mastercontainer volume has less than 5 GB free space. Container updates and backups might not succeed due to that!'); + $dockerActionManager->sendNotification($nextcloudContainer, 'Low on space!', 'The drive that hosts the mastercontainer volume has less than 5 GB free space. Container updates and backups might not succeed due to that!'); } diff --git a/php/src/Cron/OutdatedNotification.php b/php/src/Cron/OutdatedNotification.php index e652ba3a..628f0924 100644 --- a/php/src/Cron/OutdatedNotification.php +++ b/php/src/Cron/OutdatedNotification.php @@ -10,17 +10,17 @@ require __DIR__ . '/../../vendor/autoload.php'; $container = \AIO\DependencyInjection::GetContainer(); -/** @var \AIO\Docker\DockerActionManager $dockerActionManger */ -$dockerActionManger = $container->get(\AIO\Docker\DockerActionManager::class); +/** @var \AIO\Docker\DockerActionManager $dockerActionManager */ +$dockerActionManager = $container->get(\AIO\Docker\DockerActionManager::class); /** @var \AIO\ContainerDefinitionFetcher $containerDefinitionFetcher */ $containerDefinitionFetcher = $container->get(\AIO\ContainerDefinitionFetcher::class); $id = 'nextcloud-aio-nextcloud'; $nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id); -$isNextcloudImageOutdated = $dockerActionManger->isNextcloudImageOutdated(); +$isNextcloudImageOutdated = $dockerActionManager->isNextcloudImageOutdated(); if ($isNextcloudImageOutdated === true) { - $dockerActionManger->sendNotification($nextcloudContainer, 'AIO is outdated!', 'Please open the AIO interface or ask an administrator to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which automatically updates all containers.', '/notify-all.sh'); + $dockerActionManager->sendNotification($nextcloudContainer, 'AIO is outdated!', 'Please open the AIO interface or ask an administrator to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which automatically updates all containers.', '/notify-all.sh'); } diff --git a/php/src/Cron/UpdateNotification.php b/php/src/Cron/UpdateNotification.php index e1d57f6a..2c12e2f4 100644 --- a/php/src/Cron/UpdateNotification.php +++ b/php/src/Cron/UpdateNotification.php @@ -10,21 +10,21 @@ require __DIR__ . '/../../vendor/autoload.php'; $container = \AIO\DependencyInjection::GetContainer(); -/** @var \AIO\Docker\DockerActionManager $dockerActionManger */ -$dockerActionManger = $container->get(\AIO\Docker\DockerActionManager::class); +/** @var \AIO\Docker\DockerActionManager $dockerActionManager */ +$dockerActionManager = $container->get(\AIO\Docker\DockerActionManager::class); /** @var \AIO\ContainerDefinitionFetcher $containerDefinitionFetcher */ $containerDefinitionFetcher = $container->get(\AIO\ContainerDefinitionFetcher::class); $id = 'nextcloud-aio-nextcloud'; $nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id); -$isMastercontainerUpdateAvailable = $dockerActionManger->IsMastercontainerUpdateAvailable(); -$isAnyUpdateAvailable = $dockerActionManger->isAnyUpdateAvailable(); +$isMastercontainerUpdateAvailable = $dockerActionManager->IsMastercontainerUpdateAvailable(); +$isAnyUpdateAvailable = $dockerActionManager->isAnyUpdateAvailable(); if ($isMastercontainerUpdateAvailable === true) { - $dockerActionManger->sendNotification($nextcloudContainer, 'Mastercontainer update available!', 'Please open your AIO interface to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates the mastercontainer.'); + $dockerActionManager->sendNotification($nextcloudContainer, 'Mastercontainer update available!', 'Please open your AIO interface to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates the mastercontainer.'); } if ($isAnyUpdateAvailable === true) { - $dockerActionManger->sendNotification($nextcloudContainer, 'Container updates available!', 'Please open your AIO interface to update them. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates your containers and your Nextcloud apps.'); + $dockerActionManager->sendNotification($nextcloudContainer, 'Container updates available!', 'Please open your AIO interface to update them. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates your containers and your Nextcloud apps.'); } diff --git a/php/src/Data/ConfigurationManager.php b/php/src/Data/ConfigurationManager.php index 1a2b4461..7534acda 100644 --- a/php/src/Data/ConfigurationManager.php +++ b/php/src/Data/ConfigurationManager.php @@ -9,61 +9,354 @@ 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 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 = file_get_contents(DataConst::GetConfigFile()); - return json_decode($configContent, true, 512, JSON_THROW_ON_ERROR); + $configContent = (string)file_get_contents(DataConst::GetConfigFile()); + $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,15 +368,12 @@ class ConfigurationManager } } - public function GetLastBackupTime() : string { + public function getLastBackupTime() : string { if (!file_exists(DataConst::GetBackupArchivesList())) { return ''; } - $content = file_get_contents(DataConst::GetBackupArchivesList()); - if ($content === '') { - return ''; - } + $content = (string)file_get_contents(DataConst::GetBackupArchivesList()); $lastBackupLines = explode("\n", $content); $lastBackupLine = ""; @@ -103,15 +393,12 @@ class ConfigurationManager return $lastBackupTime; } - public function GetBackupTimes() : array { + public function getBackupTimes() : array { if (!file_exists(DataConst::GetBackupArchivesList())) { return []; } - $content = file_get_contents(DataConst::GetBackupArchivesList()); - if ($content === '') { - return []; - } + $content = (string)file_get_contents(DataConst::GetBackupArchivesList()); $backupLines = explode("\n", $content); $backupTimes = []; @@ -128,12 +415,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 { @@ -144,157 +431,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->GetCollaboraSeccompDisabledState() === 'true') { - $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) : 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!"); @@ -321,8 +463,9 @@ class ConfigurationManager } // Skip domain validation if opted in to do so - if (!$this->shouldDomainValidationBeSkipped()) { - + if ($this->shouldDomainValidationBeSkipped($skipDomainValidation)) { + error_log('Skipping domain validation'); + } else { $dnsRecordIP = gethostbyname($domain); if ($dnsRecordIP === $domain) { $dnsRecordIP = ''; @@ -341,7 +484,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') { @@ -356,11 +499,11 @@ class ConfigurationManager if ($connection) { fclose($connection); } else { - throw new InvalidSettingConfigurationException("The domain is not reachable on Port 443 from within this container. Have you opened port 443/tcp in your router/firewall? If yes is the problem most likely that the router or firewall forbids local access to your domain. You can work around that by setting up a local DNS-server."); + throw new InvalidSettingConfigurationException("The domain is not reachable on Port 443 from within this container. Have you opened port 443/tcp in your router/firewall? If yes is the problem most likely that the router or firewall forbids local access to your domain. Or in other words: NAT loopback (Hairpinning) does not seem to work in your network. You can work around that by setting up a local DNS server and utilizing Split-Brain-DNS and configuring the daemon.json file of your docker daemon to use the local DNS server."); } // Get Instance ID - $instanceID = $this->GetAndGenerateSecret('INSTANCE_ID'); + $instanceID = $this->getAndGenerateSecret('INSTANCE_ID'); // set protocol if ($port !== '443') { @@ -371,6 +514,9 @@ class ConfigurationManager // Check if response is correct $ch = curl_init(); + if ($ch === false) { + throw new InvalidSettingConfigurationException('Could not init curl! Please check the logs!'); + } $testUrl = $protocol . $domain . ':443'; curl_setopt($ch, CURLOPT_URL, $testUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @@ -394,80 +540,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 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 !== '') { @@ -488,16 +590,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 @@ -508,40 +610,48 @@ class ConfigurationManager } } - public function DeleteBorgBackupLocationVars() : void { - $config = $this->GetConfig(); - $config['borg_backup_host_location'] = ''; - $config['borg_remote_repo'] = ''; - $this->WriteConfig($config); + public function deleteBorgBackupLocationItems() : void { + // Delete the variables + $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())) { + if (unlink(DataConst::GetBackupKeyFile())) { + error_log('borg.config file deleted to be able to start over.'); + } + } } /** * @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."); } @@ -558,218 +668,69 @@ 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); + $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 ""; } - return trim(file_get_contents(DataConst::GetBackupPublicKey())); + 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 GetBorgBackupMode() : string { - $config = $this->GetConfig(); - if(!isset($config['backup-mode'])) { - $config['backup-mode'] = ''; - } - - return $config['backup-mode']; - } - - 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->GetCollaboraSeccompDisabledState() !== 'true') { + 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); - } - /** * @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!"); } @@ -791,11 +752,11 @@ class ConfigurationManager file_put_contents(DataConst::GetDailyBackupTimeFile(), $time); } - public function GetDailyBackupTime() : string { + public function getDailyBackupTime() : string { if (!file_exists(DataConst::GetDailyBackupTimeFile())) { return ''; } - $dailyBackupFile = file_get_contents(DataConst::GetDailyBackupTimeFile()); + $dailyBackupFile = (string)file_get_contents(DataConst::GetDailyBackupTimeFile()); $dailyBackupFileArray = explode("\n", $dailyBackupFile); return $dailyBackupFileArray[0]; } @@ -804,7 +765,7 @@ class ConfigurationManager if (!file_exists(DataConst::GetDailyBackupTimeFile())) { return false; } - $dailyBackupFile = file_get_contents(DataConst::GetDailyBackupTimeFile()); + $dailyBackupFile = (string)file_get_contents(DataConst::GetDailyBackupTimeFile()); $dailyBackupFileArray = explode("\n", $dailyBackupFile); if (isset($dailyBackupFileArray[1]) && $dailyBackupFileArray[1] === 'automaticUpdatesAreNotEnabled') { return false; @@ -813,7 +774,7 @@ class ConfigurationManager } } - public function DeleteDailyBackupTime() : void { + public function deleteDailyBackupTime() : void { if (file_exists(DataConst::GetDailyBackupTimeFile())) { unlink(DataConst::GetDailyBackupTimeFile()); } @@ -822,7 +783,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) { @@ -843,49 +804,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 ''; } - $additionalBackupDirectories = file_get_contents(DataConst::GetAdditionalBackupDirectoriesFile()); - return $additionalBackupDirectories; + 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!"); } @@ -893,26 +833,31 @@ 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 { - if (getenv('SKIP_DOMAIN_VALIDATION') === 'true') { + public function shouldDomainValidationBeSkipped(bool $skipDomainValidation) : bool { + if ($skipDomainValidation || getenv('SKIP_DOMAIN_VALIDATION') === 'true') { return true; } return false; } - public function GetNextcloudStartupApps() : string { + public function getApacheAdditionalNetwork() : string { + $network = getenv('APACHE_ADDITIONAL_NETWORK'); + if (is_string($network)) { + return trim($network); + } + return ''; + } + + public function getNextcloudStartupApps() : string { $apps = getenv('NEXTCLOUD_STARTUP_APPS'); if (is_string($apps)) { return trim($apps); @@ -920,19 +865,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!"); } @@ -940,22 +876,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!"); } @@ -963,66 +896,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 { + 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()); @@ -1040,7 +926,7 @@ class ConfigurationManager apcu_add($filePath, $fileContents); } } - $json = is_string($fileContents) ? json_decode($fileContents, true) : false; + $json = is_string($fileContents) ? json_decode($fileContents, true, 512, JSON_THROW_ON_ERROR) : false; if(is_array($json) && is_array($json['aio_services_v1'])) { foreach ($json['aio_services_v1'] as $service) { $documentation = is_string($service['documentation']) ? $service['documentation'] : ''; @@ -1058,55 +944,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 4f697325..9272e3d4 100644 --- a/php/src/Data/DataConst.php +++ b/php/src/Data/DataConst.php @@ -8,7 +8,7 @@ class DataConst { return '/mnt/docker-aio-config/data/'; } - return realpath(__DIR__ . '/../../data/'); + return (string)realpath(__DIR__ . '/../../data/'); } public static function GetSessionDirectory() : string { @@ -16,7 +16,7 @@ class DataConst { return '/mnt/docker-aio-config/session/'; } - return realpath(__DIR__ . '/../../session/'); + return (string)realpath(__DIR__ . '/../../session/'); } public static function GetConfigFile() : string { @@ -56,6 +56,18 @@ class DataConst { } public static function GetCommunityContainersDirectory() : string { - return realpath(__DIR__ . '/../../../community-containers/'); + return (string)realpath(__DIR__ . '/../../../community-containers/'); + } + + public static function GetCollaboraSeccompProfilePath() : string { + return (string)realpath(__DIR__ . '/../../cool-seccomp-profile.json'); + } + + 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 d46bc5c9..86b36619 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -7,12 +7,13 @@ use AIO\Container\ContainerState; use AIO\Container\VersionState; use AIO\ContainerDefinitionFetcher; use AIO\Data\ConfigurationManager; +use AIO\Data\DataConst; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use http\Env\Response; readonly class DockerActionManager { - private const string API_VERSION = 'v1.41'; + private const string API_VERSION = 'v1.44'; private Client $guzzleClient; public function __construct( @@ -25,19 +26,25 @@ readonly class DockerActionManager { } private function BuildApiUrl(string $url): string { - return sprintf('http://127.0.0.1/%s/%s', self::API_VERSION, $url); + $apiVersion = getenv('DOCKER_API_VERSION'); + if ($apiVersion === false || empty($apiVersion)) { + $apiVersion = self::API_VERSION; + } else { + $apiVersion = 'v'. $apiVersion; + } + return sprintf('http://127.0.0.1/%s/%s', $apiVersion, $url); } private function BuildImageName(Container $container): string { - $tag = $container->GetImageTag(); + $tag = $container->imageTag; if ($tag === '%AIO_CHANNEL%') { $tag = $this->GetCurrentChannel(); } - return $container->GetContainerName() . ':' . $tag; + return $container->containerName . ':' . $tag; } public function GetContainerRunningState(Container $container): ContainerState { - $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier()))); + $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier))); try { $response = $this->guzzleClient->get($url); } catch (RequestException $e) { @@ -47,7 +54,7 @@ readonly class DockerActionManager { throw $e; } - $responseBody = json_decode((string)$response->getBody(), true); + $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); if ($responseBody['State']['Running'] === true) { return ContainerState::Running; @@ -57,7 +64,7 @@ readonly class DockerActionManager { } public function GetContainerRestartingState(Container $container): ContainerState { - $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier()))); + $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier))); try { $response = $this->guzzleClient->get($url); } catch (RequestException $e) { @@ -67,7 +74,7 @@ readonly class DockerActionManager { throw $e; } - $responseBody = json_decode((string)$response->getBody(), true); + $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); if ($responseBody['State']['Restarting'] === true) { return ContainerState::Restarting; @@ -77,16 +84,16 @@ readonly class DockerActionManager { } public function GetContainerUpdateState(Container $container): VersionState { - $tag = $container->GetImageTag(); + $tag = $container->imageTag; if ($tag === '%AIO_CHANNEL%') { $tag = $this->GetCurrentChannel(); } - $runningDigests = $this->GetRepoDigestsOfContainer($container->GetIdentifier()); + $runningDigests = $this->GetRepoDigestsOfContainer($container->identifier); if ($runningDigests === null) { return VersionState::Different; } - $remoteDigest = $this->GetLatestDigestOfTag($container->GetContainerName(), $tag); + $remoteDigest = $this->GetLatestDigestOfTag($container->containerName, $tag); if ($remoteDigest === null) { return VersionState::Equal; } @@ -105,12 +112,12 @@ readonly class DockerActionManager { return $runningState; } - $containerName = $container->GetIdentifier(); - $internalPort = $container->GetInternalPort(); + $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') { @@ -127,7 +134,7 @@ readonly class DockerActionManager { } public function DeleteContainer(Container $container): void { - $url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->GetIdentifier()))); + $url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->identifier))); try { $this->guzzleClient->delete($url); } catch (RequestException $e) { @@ -148,28 +155,28 @@ readonly class DockerActionManager { $response = ""; $separator = "\r\n"; $line = strtok($responseBody, $separator); - $response = substr($line, 8) . $separator; + $response = substr((string)$line, 8) . $separator; while ($line !== false) { $line = strtok($separator); - $response .= substr($line, 8) . $separator; + $response .= substr((string)$line, 8) . $separator; } return $response; } public function StartContainer(Container $container): void { - $url = $this->BuildApiUrl(sprintf('containers/%s/start', urlencode($container->GetIdentifier()))); + $url = $this->BuildApiUrl(sprintf('containers/%s/start', urlencode($container->identifier))); try { $this->guzzleClient->post($url); } catch (RequestException $e) { - throw new \Exception("Could not start container " . $container->GetIdentifier() . ": " . $e->getResponse()?->getBody()->getContents()); + throw new \Exception("Could not start container " . $container->identifier . ": " . $e->getResponse()?->getBody()->getContents()); } } public function CreateVolumes(Container $container): void { $url = $this->BuildApiUrl('volumes/create'); - foreach ($container->GetVolumes()->GetVolumes() as $volume) { + foreach ($container->volumes->GetVolumes() as $volume) { $forbiddenChars = [ '/', ]; @@ -195,10 +202,10 @@ readonly class DockerActionManager { public function CreateContainer(Container $container): void { $volumes = []; - foreach ($container->GetVolumes()->GetVolumes() as $volume) { + foreach ($container->volumes->GetVolumes() as $volume) { // // NEXTCLOUD_MOUNT gets added via bind-mount later on - // if ($container->GetIdentifier() === 'nextcloud-aio-nextcloud') { - // if ($volume->name === $this->configurationManager->GetNextcloudMount()) { + // if ($container->identifier === 'nextcloud-aio-nextcloud') { + // if ($volume->name === $this->configurationManager->nextcloudMount) { // continue; // } // } @@ -221,45 +228,38 @@ readonly class DockerActionManager { $requestBody['HostConfig']['Binds'] = $volumes; } - $aioVariables = $container->GetAioVariables()->GetVariables(); - foreach ($aioVariables as $variable) { - $config = $this->configurationManager->GetConfig(); - $variableArray = explode('=', $variable); - $config[$variableArray[0]] = $variableArray[1]; - $this->configurationManager->WriteConfig($config); - sleep(1); - } + $this->configurationManager->setAioVariables($container->aioVariables->GetVariables()); - $envs = $container->GetEnvironmentVariables()->GetVariables(); + $envs = $container->containerEnvironmentVariables->GetVariables(); // Special thing for the nextcloud container - if ($container->GetIdentifier() === 'nextcloud-aio-nextcloud') { + if ($container->identifier === 'nextcloud-aio-nextcloud') { $envs[] = $this->GetAllNextcloudExecCommands(); } foreach ($envs as $key => $env) { - $envs[$key] = $this->replaceEnvPlaceholders($env); + $envs[$key] = $this->configurationManager->replaceEnvPlaceholders($env); } if (count($envs) > 0) { $requestBody['Env'] = $envs; } - $requestBody['HostConfig']['RestartPolicy']['Name'] = $container->GetRestartPolicy(); + $requestBody['HostConfig']['RestartPolicy']['Name'] = $container->restartPolicy; - $requestBody['HostConfig']['ReadonlyRootfs'] = $container->GetReadOnlySetting(); + $requestBody['HostConfig']['ReadonlyRootfs'] = $container->readOnlyRootFs; $exposedPorts = []; - if ($container->GetInternalPort() !== 'host') { - foreach ($container->GetPorts()->GetPorts() as $value) { + if ($container->internalPorts !== 'host') { + foreach ($container->ports->GetPorts() as $value) { $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; @@ -271,21 +271,25 @@ readonly class DockerActionManager { if (count($exposedPorts) > 0) { $requestBody['ExposedPorts'] = $exposedPorts; - foreach ($container->GetPorts()->GetPorts() as $value) { + foreach ($container->ports->GetPorts() as $value) { $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; + } } $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; @@ -302,8 +306,8 @@ readonly class DockerActionManager { } $devices = []; - foreach ($container->GetDevices() as $device) { - if ($device === '/dev/dri' && !$this->configurationManager->isDriDeviceEnabled()) { + foreach ($container->devices as $device) { + if ($device === '/dev/dri' && !$this->configurationManager->nextcloudEnableDriDevice) { continue; } $devices[] = ["PathOnHost" => $device, "PathInContainer" => $device, "CgroupPermissions" => "rwm"]; @@ -313,7 +317,7 @@ readonly class DockerActionManager { $requestBody['HostConfig']['Devices'] = $devices; } - if ($container->isNvidiaGpuEnabled() && $this->configurationManager->isNvidiaGpuEnabled()) { + if ($container->enableNvidiaGpu && $this->configurationManager->enableNvidiaGpu) { $requestBody['HostConfig']['Runtime'] = 'nvidia'; $requestBody['HostConfig']['DeviceRequests'] = [ [ @@ -324,13 +328,13 @@ readonly class DockerActionManager { ]; } - $shmSize = $container->GetShmSize(); + $shmSize = $container->shmSize; if ($shmSize > 0) { $requestBody['HostConfig']['ShmSize'] = $shmSize; } $tmpfs = []; - foreach ($container->GetTmpfs() as $tmp) { + foreach ($container->tmpfs as $tmp) { $mode = ""; if (str_contains($tmp, ':')) { $mode = explode(':', $tmp)[1]; @@ -342,9 +346,14 @@ readonly class DockerActionManager { $requestBody['HostConfig']['Tmpfs'] = $tmpfs; } - $requestBody['HostConfig']['Init'] = $container->GetInit(); + $requestBody['HostConfig']['Init'] = $container->init; - $capAdds = $container->GetCapAdds(); + $maxShutDownTime = $container->maxShutdownTime; + if ($maxShutDownTime > 0) { + $requestBody['StopTimeout'] = $maxShutDownTime; + } + + $capAdds = $container->capAdd; if (count($capAdds) > 0) { $requestBody['HostConfig']['CapAdd'] = $capAdds; } @@ -356,14 +365,14 @@ readonly class DockerActionManager { // Disable SELinux for AIO containers so that it does not break them $requestBody['HostConfig']['SecurityOpt'] = ["label:disable"]; - if ($container->isApparmorUnconfined()) { + if ($container->apparmorUnconfined) { $requestBody['HostConfig']['SecurityOpt'] = ["apparmor:unconfined", "label:disable"]; } $mounts = []; // Special things for the backup container which should not be exposed in the containers.json - if (str_starts_with($container->GetIdentifier(), 'nextcloud-aio-borgbackup')) { + if (str_starts_with($container->identifier, 'nextcloud-aio-borgbackup')) { // Additional backup directories foreach ($this->getAllBackupVolumes() as $additionalBackupVolumes) { if ($additionalBackupVolumes !== '') { @@ -372,9 +381,9 @@ readonly class DockerActionManager { } // Make volumes read only in case of borgbackup container. The viewer makes them writeable - $isReadOnly = $container->GetIdentifier() === 'nextcloud-aio-borgbackup'; + $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]; @@ -383,25 +392,41 @@ readonly class DockerActionManager { } } } - // Special things for the talk container which should not be exposed in the containers.json - } elseif ($container->GetIdentifier() === 'nextcloud-aio-talk') { - // This is needed due to a bug in libwebsockets which cannot handle unlimited ulimits + + // Special things for the talk container which should not be exposed in the containers.json + } elseif ($container->identifier === 'nextcloud-aio-talk') { + // This is needed due to a bug in libwebsockets used in Janus which cannot handle unlimited ulimits $requestBody['HostConfig']['Ulimits'] = [["Name" => "nofile", "Hard" => 200000, "Soft" => 200000]]; // // Special things for the nextcloud container which should not be exposed in the containers.json - // } elseif ($container->GetIdentifier() === 'nextcloud-aio-nextcloud') { - // foreach ($container->GetVolumes()->GetVolumes() as $volume) { - // if ($volume->name !== $this->configurationManager->GetNextcloudMount()) { + // } elseif ($container->identifier === 'nextcloud-aio-nextcloud') { + // foreach ($container->volumes->GetVolumes() as $volume) { + // if ($volume->name !== $this->configurationManager->nextcloudMount) { // continue; // } // $mounts[] = ["Type" => "bind", "Source" => $volume->name, "Target" => $volume->mountPoint, "ReadOnly" => !$volume->isWritable, "BindOptions" => [ "Propagation" => "rshared"]]; // } - // Special things for the caddy community container - } elseif ($container->GetIdentifier() === 'nextcloud-aio-caddy') { + + // Special things for the caddy community container + } elseif ($container->identifier === 'nextcloud-aio-caddy') { $requestBody['HostConfig']['ExtraHosts'] = ['host.docker.internal:host-gateway']; - // Special things for the collabora container which should not be exposed in the containers.json - } elseif ($container->GetIdentifier() === 'nextcloud-aio-collabora') { - if ($this->configurationManager->GetAdditionalCollaboraOptions() !== '') { - $requestBody['Cmd'] = [$this->configurationManager->GetAdditionalCollaboraOptions()]; + + // Special things for the collabora container which should not be exposed in the containers.json + } elseif ($container->identifier === 'nextcloud-aio-collabora') { + 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->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)); } } @@ -411,12 +436,13 @@ readonly class DockerActionManager { // All AIO-managed containers should not be updated externally via watchtower but gracefully by AIO's backup and update feature. // Also DIUN should not send update notifications. See https://crazymax.dev/diun/providers/docker/#docker-labels - $requestBody['Labels'] = ["com.centurylinklabs.watchtower.enable" => "false", "diun.enable" => "false", "org.label-schema.vendor" => "Nextcloud"]; + // Additionally set a default org.label-schema.vendor and com.docker.compose.project + $requestBody['Labels'] = ["com.centurylinklabs.watchtower.enable" => "false", "wud.watch" => "false", "diun.enable" => "false", "org.label-schema.vendor" => "Nextcloud", "com.docker.compose.project" => "nextcloud-aio"]; // Containers should have a fixed host name. See https://github.com/nextcloud/all-in-one/discussions/6589 - $requestBody['Hostname'] = $container->GetIdentifier(); + $requestBody['Hostname'] = $container->identifier; - $url = $this->BuildApiUrl('containers/create?name=' . $container->GetIdentifier()); + $url = $this->BuildApiUrl('containers/create?name=' . $container->identifier); try { $this->guzzleClient->request( 'POST', @@ -426,18 +452,18 @@ readonly class DockerActionManager { ] ); } catch (RequestException $e) { - throw new \Exception("Could not create container " . $container->GetIdentifier() . ": " . $e->getResponse()?->getBody()->getContents()); + throw new \Exception("Could not create container " . $container->identifier . ": " . $e->getResponse()?->getBody()->getContents()); } } public function isRegistryReachable(Container $container): bool { - $tag = $container->GetImageTag(); + $tag = $container->imageTag; if ($tag === '%AIO_CHANNEL%') { $tag = $this->GetCurrentChannel(); } - $remoteDigest = $this->GetLatestDigestOfTag($container->GetContainerName(), $tag); + $remoteDigest = $this->GetLatestDigestOfTag($container->containerName, $tag); if ($remoteDigest === null) { return false; @@ -449,7 +475,7 @@ readonly class DockerActionManager { public function PullImage(Container $container, bool $pullImage = true): void { // Skip database image pull if the last shutdown was not clean - if ($container->GetIdentifier() === 'nextcloud-aio-database') { + if ($container->identifier === 'nextcloud-aio-database') { if ($this->GetDatabasecontainerExitCode() > 0) { $pullImage = false; error_log('Not pulling the latest database image because the container was not correctly shut down.'); @@ -461,7 +487,7 @@ readonly class DockerActionManager { if ($pullImage) { if (!$this->isRegistryReachable($container)) { $pullImage = false; - error_log('Not pulling the ' . $container->GetContainerName() . ' image for the ' . $container->GetIdentifier() . ' container because the registry does not seem to be reachable.'); + error_log('Not pulling the ' . $container->containerName . ' image for the ' . $container->identifier . ' container because the registry does not seem to be reachable.'); } } @@ -480,92 +506,28 @@ readonly class DockerActionManager { } catch (\Throwable $e) { $imageIsThere = false; } - try { - $this->guzzleClient->post($url); - } catch (RequestException $e) { - $message = "Could not pull image " . $imageName . ": " . $e->getResponse()?->getBody()->getContents(); - if ($imageIsThere === false) { - throw new \Exception($message); - } else { - error_log($message); + + $maxRetries = 3; + for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { + try { + $this->guzzleClient->post($url); + break; + } catch (RequestException $e) { + $message = "Could not pull image " . $imageName . " (attempt $attempt/$maxRetries): " . $e->getResponse()?->getBody()->getContents(); + if ($attempt === $maxRetries) { + if ($imageIsThere === false) { + throw new \Exception($message); + } else { + error_log($message); + } + } else { + error_log($message . ' Retrying...'); + sleep(1); + } } } } - // 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(), - 'TALK_PORT' => $this->configurationManager->GetTalkPort(), - 'NEXTCLOUD_MOUNT' => $this->configurationManager->GetNextcloudMount(), - 'BACKUP_RESTORE_PASSWORD' => $this->configurationManager->GetBorgRestorePassword(), - 'CLAMAV_ENABLED' => $this->configurationManager->isClamavEnabled() ? 'yes' : '', - 'TALK_RECORDING_ENABLED' => $this->configurationManager->isTalkRecordingEnabled() ? 'yes' : '', - 'ONLYOFFICE_ENABLED' => $this->configurationManager->isOnlyofficeEnabled() ? 'yes' : '', - 'COLLABORA_ENABLED' => $this->configurationManager->isCollaboraEnabled() ? 'yes' : '', - 'TALK_ENABLED' => $this->configurationManager->isTalkEnabled() ? 'yes' : '', - 'UPDATE_NEXTCLOUD_APPS' => ($this->configurationManager->isDailyBackupRunning() && $this->configurationManager->areAutomaticUpdatesEnabled()) ? 'yes' : '', - 'TIMEZONE' => $this->configurationManager->GetTimezone() === '' ? 'Etc/UTC' : $this->configurationManager->GetTimezone(), - 'COLLABORA_DICTIONARIES' => $this->configurationManager->GetCollaboraDictionaries() === '' ? 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru' : $this->configurationManager->GetCollaboraDictionaries(), - 'IMAGINARY_ENABLED' => $this->configurationManager->isImaginaryEnabled() ? 'yes' : '', - 'FULLTEXTSEARCH_ENABLED' => $this->configurationManager->isFulltextsearchEnabled() ? 'yes' : '', - 'DOCKER_SOCKET_PROXY_ENABLED' => $this->configurationManager->isDockerSocketProxyEnabled() ? 'yes' : '', - 'NEXTCLOUD_UPLOAD_LIMIT' => $this->configurationManager->GetNextcloudUploadLimit(), - 'NEXTCLOUD_MEMORY_LIMIT' => $this->configurationManager->GetNextcloudMemoryLimit(), - 'NEXTCLOUD_MAX_TIME' => $this->configurationManager->GetNextcloudMaxTime(), - 'BORG_RETENTION_POLICY' => $this->configurationManager->GetBorgRetentionPolicy(), - 'FULLTEXTSEARCH_JAVA_OPTIONS' => $this->configurationManager->GetFulltextsearchJavaOptions(), - 'NEXTCLOUD_TRUSTED_CACERTS_DIR' => $this->configurationManager->GetTrustedCacertsDir(), - 'ADDITIONAL_DIRECTORIES_BACKUP' => $this->configurationManager->GetAdditionalBackupDirectoriesString() !== '' ? 'yes' : '', - 'BORGBACKUP_HOST_LOCATION' => $this->configurationManager->GetBorgBackupHostLocation(), - 'APACHE_MAX_SIZE' => (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); @@ -573,7 +535,7 @@ readonly class DockerActionManager { if ($container->GetUpdateState() === VersionState::Different) { $updateAvailable = '1'; } - foreach ($container->GetDependsOn() as $dependency) { + foreach ($container->dependsOn as $dependency) { $updateAvailable .= $this->isContainerUpdateAvailable($dependency); } return $updateAvailable; @@ -581,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'; @@ -597,10 +559,10 @@ readonly class DockerActionManager { $container = $this->containerDefinitionFetcher->GetContainerById($id); $backupVolumes = ''; - foreach ($container->GetBackupVolumes() as $backupVolume) { + foreach ($container->backupVolumes as $backupVolume) { $backupVolumes .= $backupVolume . ' '; } - foreach ($container->GetDependsOn() as $dependency) { + foreach ($container->dependsOn as $dependency) { $backupVolumes .= $this->getBackupVolumes($dependency); } return $backupVolumes; @@ -616,10 +578,10 @@ readonly class DockerActionManager { $container = $this->containerDefinitionFetcher->GetContainerById($id); $nextcloudExecCommands = ''; - foreach ($container->GetNextcloudExecCommands() as $execCommand) { + foreach ($container->nextcloudExecCommands as $execCommand) { $nextcloudExecCommands .= $execCommand . PHP_EOL; } - foreach ($container->GetDependsOn() as $dependency) { + foreach ($container->dependsOn as $dependency) { $nextcloudExecCommands .= $this->GetNextcloudExecCommands($dependency); } return $nextcloudExecCommands; @@ -633,11 +595,11 @@ readonly class DockerActionManager { private function GetRepoDigestsOfContainer(string $containerName): ?array { try { $containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); - $containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true); + $containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $imageName = $containerOutput['Image']; $imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName)); - $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true); + $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); if (!isset($imageOutput['RepoDigests'])) { error_log('RepoDigests is not set of container ' . $containerName); @@ -681,7 +643,7 @@ readonly class DockerActionManager { $containerName = 'nextcloud-aio-mastercontainer'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); try { - $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true); + $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $imageNameArray = explode(':', $output['Config']['Image']); if (count($imageNameArray) === 2) { $imageName = $imageNameArray[0]; @@ -708,7 +670,7 @@ readonly class DockerActionManager { $containerName = 'nextcloud-aio-mastercontainer'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); try { - $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true); + $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $tagArray = explode(':', $output['Config']['Image']); if (count($tagArray) === 2) { $tag = $tagArray[1]; @@ -751,7 +713,7 @@ readonly class DockerActionManager { public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh'): void { if ($this->GetContainerStartingState($container) === ContainerState::Running) { - $containerName = $container->GetIdentifier(); + $containerName = $container->identifier; // schedule the exec $url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName))); @@ -772,7 +734,9 @@ readonly class DockerActionManager { ], ] )->getBody()->getContents(), - true + true, + 512, + JSON_THROW_ON_ERROR, ); $id = $response['Id']; @@ -874,14 +838,14 @@ readonly class DockerActionManager { // Add a secondary alias for domaincheck container, to keep it as similar to actual apache controller as possible. // If a reverse-proxy is relying on container name as hostname this allows it to operate as usual and still validate the domain // The domaincheck container and apache container are never supposed to be active at the same time because they use the same APACHE_PORT anyway, so this doesn't add any new constraints. - $alias = ($container->GetIdentifier() === 'nextcloud-aio-domaincheck') ? 'nextcloud-aio-apache' : ''; + $alias = ($container->identifier === 'nextcloud-aio-domaincheck') ? 'nextcloud-aio-apache' : ''; - $this->ConnectContainerIdToNetwork($container->GetIdentifier(), $container->GetInternalPort(), alias: $alias); + $this->ConnectContainerIdToNetwork($container->identifier, $container->internalPorts, alias: $alias); - if ($container->GetIdentifier() === 'nextcloud-aio-apache' || $container->GetIdentifier() === 'nextcloud-aio-domaincheck') { - $apacheAdditionalNetwork = $this->configurationManager->GetApacheAdditionalNetwork(); + if ($container->identifier === 'nextcloud-aio-apache' || $container->identifier === 'nextcloud-aio-domaincheck') { + $apacheAdditionalNetwork = $this->configurationManager->getApacheAdditionalNetwork(); if ($apacheAdditionalNetwork !== '') { - $this->ConnectContainerIdToNetwork($container->GetIdentifier(), $container->GetInternalPort(), $apacheAdditionalNetwork, false, $alias); + $this->ConnectContainerIdToNetwork($container->identifier, $container->internalPorts, $apacheAdditionalNetwork, false, $alias); } } } @@ -890,9 +854,9 @@ readonly class DockerActionManager { if ($forceStopContainer) { $maxShutDownTime = 10; } else { - $maxShutDownTime = $container->GetMaxShutdownTime(); + $maxShutDownTime = $container->maxShutdownTime; } - $url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->GetIdentifier()), $maxShutDownTime)); + $url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->identifier), $maxShutDownTime)); try { $this->guzzleClient->post($url); } catch (RequestException $e) { @@ -914,7 +878,7 @@ readonly class DockerActionManager { throw $e; } - $responseBody = json_decode((string)$response->getBody(), true); + $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); $exitCode = $responseBody['State']['ExitCode']; if (is_int($exitCode)) { @@ -936,7 +900,7 @@ readonly class DockerActionManager { throw $e; } - $responseBody = json_decode((string)$response->getBody(), true); + $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); $exitCode = $responseBody['State']['ExitCode']; if (is_int($exitCode)) { @@ -968,7 +932,7 @@ readonly class DockerActionManager { $imageName = $imageName . ':' . $this->GetCurrentChannel(); try { $imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName)); - $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true); + $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); if (!isset($imageOutput['Created'])) { error_log('Created is not set of image ' . $imageName); @@ -982,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/src/Docker/DockerHubManager.php b/php/src/Docker/DockerHubManager.php index 9bf4ad29..256d592e 100644 --- a/php/src/Docker/DockerHubManager.php +++ b/php/src/Docker/DockerHubManager.php @@ -30,7 +30,7 @@ readonly class DockerHubManager { 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:' . $name . ':pull' ); $body = $authTokenRequest->getBody()->getContents(); - $decodedBody = json_decode($body, true); + $decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); if(isset($decodedBody['token'])) { $authToken = $decodedBody['token']; $manifestRequest = $this->guzzleClient->request( diff --git a/php/src/Docker/GitHubContainerRegistryManager.php b/php/src/Docker/GitHubContainerRegistryManager.php index d885ae09..eeecfb28 100644 --- a/php/src/Docker/GitHubContainerRegistryManager.php +++ b/php/src/Docker/GitHubContainerRegistryManager.php @@ -31,7 +31,7 @@ readonly class GitHubContainerRegistryManager 'https://ghcr.io/token?scope=repository:' . $name . ':pull' ); $body = $authTokenRequest->getBody()->getContents(); - $decodedBody = json_decode($body, true); + $decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); if (isset($decodedBody['token'])) { $authToken = $decodedBody['token']; $manifestRequest = $this->guzzleClient->request( diff --git a/php/src/Middleware/AuthMiddleware.php b/php/src/Middleware/AuthMiddleware.php index f8d44857..724f1776 100644 --- a/php/src/Middleware/AuthMiddleware.php +++ b/php/src/Middleware/AuthMiddleware.php @@ -27,7 +27,24 @@ readonly class AuthMiddleware { if(!in_array($request->getUri()->getPath(), $publicRoutes)) { if(!$this->authManager->IsAuthenticated()) { $status = 302; - $headers = ['Location' => '/']; + + // Check the url of the request: split the string by '/' and count the number of elements + // Note that the path that gets to this middleware is not aware of any base path managed by a reverse proxy, so if the url is 'https://example.com/AIO/somepage', the path will be 'https://mastercontainer/somepage' + if (count(explode('/', $request->getUri()->getPath())) < 2) { + // If there are less than 2 elements it means we are somewhere in the root folder (no '/', so no subfolder), so we redirect to the same folder level to offload the redirection to the appropriate page to 'index.php' (specifically, once in the root level the login page will be loaded since we are not authenticated) + $location = '.'; + } else { + // If there are 2 or more elements it means we are in a subfolder, so we need to go back to the root folder + // In the best case we need to go back by 1 level only + $location = '..'; + // In the worst case we need to go back by n levels, where n is the number of elements - 2 (the first element is not a folder, the second element is already accounted for by the initial '..') + for ($i = 1; $i < count(explode('/', $request->getUri()->getPath())) - 2; $i++) { + // For each extra level we need to go back by another level + $location = $location . '/..'; + } + } + + $headers = ['Location' => $location]; $response = new Response($status, $headers); return $response; } diff --git a/php/src/Twig/ClassExtension.php b/php/src/Twig/ClassExtension.php index ff5ffe44..7f478994 100644 --- a/php/src/Twig/ClassExtension.php +++ b/php/src/Twig/ClassExtension.php @@ -7,6 +7,7 @@ use Twig\TwigFunction; class ClassExtension extends TwigExtension { + #[\Override] public function getFunctions() : array { return array( diff --git a/php/src/Twig/CsrfExtension.php b/php/src/Twig/CsrfExtension.php index feac9c72..51334864 100644 --- a/php/src/Twig/CsrfExtension.php +++ b/php/src/Twig/CsrfExtension.php @@ -12,6 +12,7 @@ class CsrfExtension extends AbstractExtension implements GlobalsInterface { ) { } + #[\Override] public function getGlobals() : array { // CSRF token name and value diff --git a/php/templates/already-installed.twig b/php/templates/already-installed.twig index fa18f988..e16e6792 100644 --- a/php/templates/already-installed.twig +++ b/php/templates/already-installed.twig @@ -3,11 +3,11 @@ {% block body %} {% endblock %} diff --git a/php/templates/components/container-state.twig b/php/templates/components/container-state.twig index 4cf5dd4e..07580e66 100644 --- a/php/templates/components/container-state.twig +++ b/php/templates/components/container-state.twig @@ -3,24 +3,24 @@ {% if c.GetStartingState().value == 'starting' %} - {{ c.GetDisplayName() }} - (Starting) + {{ c.displayName }} + (Starting) {% elseif c.GetRunningState().value == 'running' %} - {{ c.GetDisplayName() }} - (Running) + {{ c.displayName }} + (Running) {% else %} - {{ c.GetDisplayName() }} - (Stopped) + {{ c.displayName }} + (Stopped) {% endif %} - {% if c.GetDocumentation() != '' %} - (docs) + {% if c.documentation != '' %} + (docs) {% endif %} {% if c.GetUiSecret() != '' %}
- Show password for {{ c.GetDisplayName() }} + Show password for {{ c.displayName }}
{% endif %} diff --git a/php/templates/containers.twig b/php/templates/containers.twig index 8db6beb6..8e437bc2 100644 --- a/php/templates/containers.twig +++ b/php/templates/containers.twig @@ -6,9 +6,9 @@
-
+ @@ -17,7 +17,8 @@
-

Nextcloud AIO v11.10.0

+ {% set aio_version = include('includes/aio-version.twig') %} +

Nextcloud AIO v{{ aio_version }}

{# Add 2nd tab warning #} @@ -26,7 +27,7 @@ {# js for optional containers and additional containers forms #} - + {% set hasBackupLocation = borg_backup_host_location or borg_remote_repo %} {% set isAnyRunning = false %} @@ -36,7 +37,7 @@ {% set isBackupOrRestoreRunning = false %} {% set isApacheStarting = false %} {# Setting newMajorVersion to '' will hide corresponding options/elements, can be set to an integer like 26 in order to show corresponding elements. If set, also increase installLatestMajor in https://github.com/nextcloud/all-in-one/blob/main/php/src/Controller/DockerController.php #} - {% set newMajorVersionString = '25 Autumn' %} + {% set newMajorVersionString = '' %} {% if is_backup_container_running == true %} {% if borg_backup_mode == 'backup' or borg_backup_mode == 'restore' %} @@ -45,29 +46,29 @@ {% endif %} {% for container in containers %} - {% if container.GetDisplayName() != '' and container.GetRunningState().value == 'running' %} + {% if container.displayName != '' and container.GetRunningState().value == 'running' %} {% set isAnyRunning = true %} {% endif %} - {% if container.GetDisplayName() != '' and container.GetRestartingState().value == 'restarting' %} + {% if container.displayName != '' and container.GetRestartingState().value == 'restarting' %} {% set isAnyRestarting = true %} {% endif %} - {% if container.GetIdentifier() == 'nextcloud-aio-watchtower' and container.GetRunningState().value == 'running' %} + {% if container.identifier == 'nextcloud-aio-watchtower' and container.GetRunningState().value == 'running' %} {% set isWatchtowerRunning = true %} {% endif %} - {% if container.GetIdentifier() == 'nextcloud-aio-domaincheck' and container.GetRunningState().value == 'running' %} + {% if container.identifier == 'nextcloud-aio-domaincheck' and container.GetRunningState().value == 'running' %} {% set isDomaincheckRunning = true %} {% endif %} - {% if container.GetIdentifier() == 'nextcloud-aio-apache' and container.GetStartingState().value == 'starting' %} + {% if container.identifier == 'nextcloud-aio-apache' and container.GetStartingState().value == 'starting' %} {% set isApacheStarting = true %} {% endif %} {% endfor %} {% if is_daily_backup_running == true %} -

Daily backup currently running. (Mastercontainer logs) (Borg backup container logs)

+

Daily backup currently running. (Mastercontainer logs) (Borg backup container logs)

{% if automatic_updates == true %}

This will update your containers, the mastercontainer and, on Saturdays, your Nextcloud apps if the backup is successful.

{% if is_mastercontainer_update_available == true %} -

When the mastercontainer is updated it will restart, making it unavailable for a moment. (Logs)

+

When the mastercontainer is updated it will restart, making it unavailable for a moment. (Logs)

{% endif %} {% endif %} {% if has_update_available == false %} @@ -78,7 +79,7 @@

Reload ↻

If the daily backup is stuck somehow, you can unstick it by running sudo docker exec nextcloud-aio-mastercontainer rm /mnt/docker-aio-config/data/daily_backup_running and afterwards reloading this interface.

{% elseif isWatchtowerRunning == true %} -

Mastercontainer update currently running. Once the update is complete the mastercontainer will restart, making it unavailable for a moment. Please wait until it's done. (Logs)

+

Mastercontainer update currently running. Once the update is complete the mastercontainer will restart, making it unavailable for a moment. Please wait until it's done. (Logs)

Reload ↻

{% else %} {% if is_backup_container_running == false and domain == "" %} @@ -88,7 +89,7 @@ {% elseif is_mastercontainer_update_available == true %}

Mastercontainer update

⚠️ A mastercontainer update is available. Please click on the button below to update it. Afterwards, you will be able to proceed with the setup.

- + @@ -108,10 +109,13 @@ {% if skip_domain_validation == true %}

Please note: The domain validation is disabled so any domain will be accepted here! Make sure you do not make a typo here as you will not be able to change it afterwards!

{% endif %} - + + {% if skip_domain_validation == true %} + + {% endif %} {% if skip_domain_validation == false %} @@ -137,7 +141,7 @@ {% if hasBackupLocation %} {% if borg_backup_mode in ['test', 'check'] %} {% if backup_exit_code > 0 %} -

Last {{ borg_backup_mode }} failed! (Logs)

+

Last {{ borg_backup_mode }} failed! (Logs)

{% if borg_backup_mode == 'test' %}

Please adjust the path and/or the encryption password in order to make it work!

{% elseif borg_backup_mode == 'check' %} @@ -145,7 +149,7 @@
Reveal repair option

Below is the option to repair the integrity of your backup. Please note: Please only use this after you have read the documentation above! (It will run the command 'borg check --repair' for you.)

-
+ @@ -153,10 +157,10 @@
{% endif %} {% elseif backup_exit_code == 0 %} -

Last {{ borg_backup_mode }} successful! (Logs)

+

Last {{ borg_backup_mode }} successful! (Logs)

{% if borg_backup_mode == 'test' %}

Feel free to check the integrity of the backup archive below before starting the restore process in order to make ensure that the restore will work. This can take a long time though depending on the size of the backup archive and is thus not required.

- + @@ -164,7 +168,7 @@ {% endif %}

Choose the backup that you want to restore and click on the button below to restore the selected backup. This will restore the whole AIO instance. Please note that the current AIO passphrase will be kept and the previous AIO passphrase will not be restored from backup!

Important: If the backup that you want to restore contained any community container, you need to restore the same backup a second time after this attempt so that the community container data is also correctly restored.

- +


@@ -210,7 +214,7 @@ {% endif %} {% else %}

Everything set! Click on the button below to test the path and encryption password:

- + @@ -223,14 +227,14 @@ {% if was_start_button_clicked == true %} {% if current_channel starts with 'latest' or current_channel starts with 'beta' or current_channel starts with 'develop' %} -

You are running the {{ current_channel }} channel. (Logs)

+

You are running the {{ current_channel }} channel. (Logs)

{% else %}

No channel was found. This means that AIO is not able to update itself and its component and will also not be able to report about updates. Updates need to be done externally.

{% endif %} {% endif %} {% if is_backup_container_running == true %} -

Backup container is currently running: {{ borg_backup_mode }} (Logs)

+

Backup container is currently running: {{ borg_backup_mode }} (Logs)

Reload ↻

{% endif %} @@ -259,7 +263,7 @@ {% else %}

It seems at least one container was not able to start correctly and is currently restarting.

To break this endless loop, you can stop the containers below and investigate the issue in the container logs before starting the containers again.

- + @@ -277,7 +281,7 @@
    {# @var containers \AIO\Container\Container[] #} {% for container in containers %} - {% if container.GetDisplayName() != '' %} + {% if container.displayName != '' %} {% include 'components/container-state.twig' with {'c': container} only %} {% endif %} {% endfor %} @@ -312,7 +316,7 @@

    You can find all changes here

    {% endif %} {% endif %} - + @@ -327,31 +331,34 @@ {% endif %} {% if is_mastercontainer_update_available == true %}

    ⚠️ A mastercontainer update is available. Please click on the button below to update it.

    - + {% else %} {% if was_start_button_clicked == false %} -
    + + {% if newMajorVersionString != '' %}
    {% endif %}
    {% elseif has_update_available == false %} -
    + +
    {% else %} -
    + + {% if bypass_container_update == true %} {% endif %} @@ -376,7 +383,7 @@ remote borg repo url and submit it. You will be provided with an SSH public key for authorization at the remote afterwards.

    - +

    @@ -393,13 +400,13 @@ {% if is_backup_container_running == false %}

    Backup and restore

    {% if backup_exit_code > 0 %} -

    Last {{ borg_backup_mode }} failed! (Logs)

    +

    Last {{ borg_backup_mode }} failed! (Logs)

    {% if borg_backup_mode == "check" %}

    The backup check was not successful. This might indicate a corrupt archive (look at the logs). If that should be the case, you can try to fix it by following this documentation

    Reveal repair option

    Below is the option to repair the integrity of your backup. Please note: Please only use this after you have read the documentation above! (It will run the command 'borg check --repair' for you.)

    - + @@ -417,7 +424,7 @@ {% endif %}

    You may change the backup path again since the initial backup was not successful. After submitting the new value, you need to click on Create Backup to test the new value.

    - +

    @@ -427,9 +434,9 @@ {% endif %} {% elseif backup_exit_code == 0 %} {% if borg_backup_mode == "backup" %} -

    Last {{ borg_backup_mode }} successful on {{ last_backup_time }} UTC! (Logs)

    +

    Last {{ borg_backup_mode }} successful on {{ last_backup_time }} UTC! (Logs)

    {% else %} -

    Last {{ borg_backup_mode }} successful! (Logs)

    +

    Last {{ borg_backup_mode }} successful! (Logs)

    {% endif %} {% endif %} {% endif %} @@ -464,36 +471,19 @@ {% if isApacheStarting != true %}

    Backup creation

    Clicking on the button below will create a backup.

    - + - {% if has_backup_run_once == false %} -

    Reset backup location

    -

    - If the configured backup host location {{ borg_backup_host_location }} - {% if borg_remote_repo %} - or the remote repo {{ borg_remote_repo }} - {% endif %} - is wrong, you can reset it by clicking on the button below. -

    -
    - - - - -
    - {% endif %} - {% if has_backup_run_once == true %}

    Backup Viewer

    There is now a community container that allows to access your backups in a web session. See this documentation.

    Backup check

    Click on the button below to perform a backup integrity check. This is an option that verifies that your backup is intact. It shouldn't be needed in most situations.

    -
    + @@ -501,7 +491,7 @@

    Backup restore

    Choose the backup that you want to restore and click on the button below to restore the selected backup. This will overwrite all your files with the chosen backup so you should consider creating a backup first. You can run an integrity check before restoring your files but this shouldn't be needed in most situations. Please note that this will not restore additionally chosen backup directories! The restore process should be pretty fast as rsync, which only transfers changed files, is used to restore the chosen backup.

    - +
    +

    Update backup list

    +
    + Click here to reveal this option +

    If you use an external snapshot tool to restore the server that runs AIO, you might run into a problem that the above listed available backups are not up-to-date to restore your server from. You can click the button below to update this list.

    +
    + + + +
    +
    +

    Daily backup and automatic updates

    {% if daily_backup_time == "" %}

    By entering a time below and submitting it, you can enable daily backups. It will create them at the entered time in 24h format. E.g. 04:00 will create backups at 4 am UTC and 16:00 at 4 pm UTC. When creating the backup, containers will be stopped and restarted after the backup is complete.

    -
    + @@ -529,7 +530,7 @@ Also your containers, the mastercontainer and, on Saturdays, your Nextcloud apps will be automatically updated. {% endif %}

    To change your backup time first disable Daily Backups, then enter your new backup time, and then re-enable them.

    - + @@ -539,7 +540,7 @@

    Back up additional directories and docker volumes of your host

    Below you can enter directories and docker volumes of your host that will be backed up into the same borg backup archive. Make sure to press the submit button after changing anything.

    - + @@ -552,6 +553,21 @@

    This option is currently set. You can disable it again by clearing the field and submitting your changes.

    {% endif %} {% endif %} + +

    Reset backup location

    +

    + If the configured backup host location {{ borg_backup_host_location }} + {% if borg_remote_repo %} + or the remote repo {{ borg_remote_repo }} + {% endif %} + is wrong or if you want to reset the backup location due to other reasons, you can do so by clicking on the button below. +

    + + + + + +
    {% endif %} {% if has_backup_run_once == true %}
    @@ -566,7 +582,7 @@
    Click here to change your AIO passphrase

    You can change your AIO passphrase below:

    -
    + @@ -592,7 +608,7 @@ {% if timezone == "" %}

    To get the correct time values for certain Nextcloud features, set the timezone for Nextcloud to the one that your users mainly use. Please note that this setting does not apply to the mastercontainer and any backup option.

    You can configure the timezone for Nextcloud below (Do not forget to submit the value!):

    - + @@ -601,7 +617,7 @@

    You need to make sure that the timezone that you enter is valid. An example is Europe/Berlin. You can get valid values by looking at the 'TZ identifier' column of this list: click here. The default is Etc/UTC if nothing is entered.

    {% else %}

    The timezone for Nextcloud is currently set to {{ timezone }}. You can change the timezone by clicking on the button below.

    - + @@ -620,6 +636,8 @@ {% endif %} + +
{% endblock %} diff --git a/php/templates/includes/aio-version.twig b/php/templates/includes/aio-version.twig new file mode 100644 index 00000000..1b62f917 --- /dev/null +++ b/php/templates/includes/aio-version.twig @@ -0,0 +1 @@ +12.6.1 diff --git a/php/templates/includes/community-containers.twig b/php/templates/includes/community-containers.twig index f74e3756..66cceb2b 100644 --- a/php/templates/includes/community-containers.twig +++ b/php/templates/includes/community-containers.twig @@ -8,7 +8,7 @@ {% endif %}
Show/Hide available Community Containers - + diff --git a/php/templates/includes/optional-containers.twig b/php/templates/includes/optional-containers.twig index 6bea68db..eabcb139 100644 --- a/php/templates/includes/optional-containers.twig +++ b/php/templates/includes/optional-containers.twig @@ -5,10 +5,109 @@ {% else %}

Please note: Make sure to save your changes by clicking Save changes below the list of optional containers. The changes will not be auto-saved.

{% endif %} - + +

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

{% if isAnyRunning == true %} - - + + @@ -156,46 +228,46 @@ {% endif %} {% if is_collabora_enabled == true and isAnyRunning == false and was_start_button_clicked == true %} -

Collabora dictionaries

+

Nextcloud Office dictionaries

{% if collabora_dictionaries == "" %} -

In order to get the correct dictionaries in Collabora, you may configure the dictionaries below:

-
+

In order to get the correct dictionaries in Nextcloud Office, you may configure the dictionaries below:

+ - +

You need to make sure that the dictionaries that you enter are valid. An example is de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru.

{% else %} -

The dictionaries for Collabora are currently set to {{ collabora_dictionaries }}. You can reset them again by clicking on the button below.

-
+

The dictionaries for Nextcloud Office are currently set to {{ collabora_dictionaries }}. You can reset them again by clicking on the button below.

+ - +
{% endif %} -

Additional Collabora options

+

Additional Nextcloud Office options

{% if collabora_additional_options == "" %} -

You can configure additional options for collabora below.

+

You can configure additional options for Nextcloud Office below.

(This can be used for configuring the net.content_security_policy and more. Make sure to submit the value!)

-
+ - +

You need to make sure that the options that you enter are valid. An example is --o:net.content_security_policy=frame-ancestors *.example.com:*;.

{% else %} -

The additioinal options for Collabora are currently set to {{ collabora_additional_options }}. You can reset them again by clicking on the button below.

-
+

The additioinal options for Nextcloud Office are currently set to {{ collabora_additional_options }}. You can reset them again by clicking on the button below.

+ - +
{% endif %} {% endif %} diff --git a/php/templates/layout.twig b/php/templates/layout.twig index e20ca3e0..79c615d9 100644 --- a/php/templates/layout.twig +++ b/php/templates/layout.twig @@ -1,8 +1,8 @@ AIO - - + + diff --git a/php/templates/login.twig b/php/templates/login.twig index cf5cc0c3..1c5420c2 100644 --- a/php/templates/login.twig +++ b/php/templates/login.twig @@ -3,14 +3,14 @@ {% block body %}
-### Apache +##### Apache
@@ -188,7 +264,7 @@ To make the config work you can run the following command:
-### Caddy (recommended) +##### Caddy (recommended)
@@ -211,7 +287,7 @@ The Caddyfile is a text file called `Caddyfile` (no extension) which – if you
-### Caddy with ACME DNS-challenge +##### Caddy with ACME DNS-challenge
@@ -239,7 +315,7 @@ You can get AIO running using the ACME DNS-challenge. Here is how to do it.
-### OpenLiteSpeed +##### OpenLiteSpeed
@@ -249,7 +325,7 @@ You can find the OpenLiteSpeed reverse proxy guide by @MorrowShore here: https:/
-### Citrix ADC VPX / Citrix Netscaler +##### Citrix ADC VPX / Citrix Netscaler
@@ -259,7 +335,7 @@ For a reverse proxy example guide for Citrix ADC VPX / Citrix Netscaler, see thi
-### Cloudflare Tunnel +##### Cloudflare Tunnel
@@ -278,7 +354,7 @@ Although it does not seem like it is the case but from AIO perspective a Cloudfl
-### HaProxy +##### HAProxy
@@ -375,7 +451,7 @@ backend Nextcloud
-### Nginx, Freenginx, Openresty, Angie +##### Nginx, Freenginx, Openresty, Angie
@@ -474,7 +550,7 @@ server {
-### NPMplus (Fork of Nginx-Proxy-Manager - NPM) +##### NPMplus (Fork of Nginx-Proxy-Manager - NPM)
@@ -488,24 +564,19 @@ Note: this will cause that a non root user can bind privileged ports. Second, see these screenshots for a working config: -![grafik](https://github.com/user-attachments/assets/c32c8fe8-7417-4f8f-9625-24b95651e630) +image -![grafik](https://github.com/user-attachments/assets/f14bba5c-69ce-4514-a2ac-5e5d7fb97792) +image - +image -![grafik](https://github.com/user-attachments/assets/75d7f539-35d1-4a3e-8c51-43123f698893) +image -![grafik](https://github.com/user-attachments/assets/e494edb5-8b70-4d45-bc9b-374219230041) - -`proxy_set_header Accept-Encoding $http_accept_encoding;` - -⚠️ **Please note:** Nextcloud will complain that X-XXS-Protection is set to the wrong value, this is intended by NPMplus.
⚠️ **Please note:** look into [this](#adapting-the-sample-web-server-configurations-below) to adapt the above example configuration.
-### Nginx-Proxy-Manager - NPM +##### Nginx-Proxy-Manager - NPM
@@ -538,7 +609,7 @@ Also change `@` to a mail address of yours.
-### Nginx-Proxy +##### Nginx-Proxy
@@ -554,7 +625,7 @@ Apart from that, there is a [manual-install](https://github.com/nextcloud/all-in
-### Node.js with Express +##### Node.js with Express
@@ -641,7 +712,7 @@ httpServer.on('upgrade', (req, socket, head) => {
-### Synology Reverse Proxy +##### Synology Reverse Proxy
@@ -659,7 +730,27 @@ See these screenshots for a working config:
-### Traefik 2 +##### Tailscale (Serve) + +
+ +Click here to expand + +Tailscale can be used to provide private access to your Nextcloud AIO instance without opening ports on your firewall. With **Tailscale Serve**, your Nextcloud is accessible only to devices on your Tailscale network (tailnet) via a secure HTTPS domain. + +For a detailed setup guide using Tailscale Serve with Nextcloud AIO, see this guide by [@Perseus333](https://github.com/Perseus333): https://github.com/nextcloud/all-in-one/discussions/6817 + +The guide covers: +- Setting up system-wide (non-containerized) Tailscale as a reverse proxy +- Configuring Nextcloud AIO to work with Tailscale Serve +- Using Tailscale's MagicDNS to provide automatic HTTPS certificates +- Private access via your tailnet (e.g., `yourserver.tail0a12b3.ts.net`) + +⚠️ **Please note:** This guide covers **Tailscale Serve** for private tailnet access. If you need public Internet access, consider using **Tailscale Funnel**. + +
+ +##### Traefik 2
@@ -751,7 +842,7 @@ The examples below define the dynamic configuration in YAML files. If you rather
-### Traefik 3 +##### Traefik 3
@@ -774,6 +865,12 @@ The examples below define the dynamic configuration in YAML files. If you rather transport: respondingTimeouts: readTimeout: 24h # Allows uploads > 100MB; prevents connection reset due to chunking (public upload-only links) + http: + # Required for Nextcloud to correctly handle encoded URL characters (%2F, %3F and %25 in this case) in newer Traefik versions (v3.6.4+). + encodedCharacters: + allowEncodedSlash: true + allowEncodedQuestionMark: true + allowEncodedPercent: true # If you want to enable HTTP/3 support, uncomment the line below # http3: {} @@ -837,7 +934,7 @@ The examples below define the dynamic configuration in YAML files. If you rather
-### IIS with ARR and URL Rewrite +##### IIS with ARR and URL Rewrite
@@ -907,18 +1004,7 @@ Add the following `web.config` file to the root of the site you created as the r
-### Tailscale - -
- -click here to expand - -For a reverse proxy example guide for Tailscale, see this guide by [@Perseus333](https://github.com/Perseus333): https://github.com/nextcloud/all-in-one/discussions/6817 - -
- - -### Others +##### Others
@@ -928,7 +1014,7 @@ Config examples for other reverse proxies are currently not documented. Pull req
-## 2. Use this startup command +### 2. Use this startup command After adjusting your reverse proxy config, use the following command to start AIO:
@@ -951,6 +1037,27 @@ sudo docker run \ ghcr.io/nextcloud-releases/all-in-one:latest ``` +
+ +Explanation of the command + +- `sudo docker run` This command spins up a new docker container. Docker commands can optionally be used without `sudo` if the user is added to the docker group (this is not the same as docker rootless, see FAQ in the normal readme). +- `--init` This option makes sure that no zombie-processes are created, ever. See [the Docker documentation](https://docs.docker.com/reference/cli/docker/container/run/#init). +- `--sig-proxy=false` This option allows to exit the container shell that gets attached automatically when using `docker run` by using `[CTRL] + [C]` without shutting down the container. +- `--name nextcloud-aio-mastercontainer` This is the name of the container. This line is not allowed to be changed, since mastercontainer updates would fail. +- `--restart always` This is the "restart policy". `always` means that the container should always get started with the Docker daemon. See the Docker documentation for further detail about restart policies: https://docs.docker.com/config/containers/start-containers-automatically/ +- `--publish 8080:8080` This means that port 8080 of the container should get published on the host using port 8080. This port is used for the AIO interface and uses a self-signed certificate by default. You can also use a different host port if port 8080 is already used on your host, for example `--publish 8081:8080` (only the first port can be changed for the host, the second port is for the container and must remain at 8080). +- `--env APACHE_PORT=11000` This is the port that is published on the host that runs Docker and Nextcloud AIO at which the reverse proxy should point at. +- `--env APACHE_IP_BINDING=0.0.0.0` This can be modified to allow access to the published port on the host only from certain ip-addresses. [See this documentation](#3-limit-the-access-to-the-apache-container) +- `--env APACHE_ADDITIONAL_NETWORK=""` This can be used to put the sibling apache container that is created by AIO into a specified network - useful if your reverse proxy runs as a container on the same host. [See this documentation](#adapting-the-sample-web-server-configurations-below) +- `--env SKIP_DOMAIN_VALIDATION=false` This can be set to `true` if the domain validation does not work and you are sure that you configured everything correctly after you followed [the debug documentation](#7-how-to-debug-things). Also see [this documentation](https://github.com/nextcloud/all-in-one#how-to-skip-the-domain-validation). +- `--volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config` This means that the files that are created by the mastercontainer will be stored in a docker volume that is called `nextcloud_aio_mastercontainer`. This line is not allowed to be changed, since built-in backups would fail later on. +- `--volume /var/run/docker.sock:/var/run/docker.sock:ro` The docker socket is mounted into the container which is used for spinning up all the other containers and for further features. It needs to be adjusted on Windows/macOS and on docker rootless. See the applicable documentation on this. If adjusting, don't forget to also set `WATCHTOWER_DOCKER_SOCKET_PATH`! If you dislike this, see https://github.com/nextcloud/all-in-one/tree/main/manual-install. +- `ghcr.io/nextcloud-releases/all-in-one:latest` This is the docker container image that is used. +- Further options can be set using environment variables, for example `--env NEXTCLOUD_DATADIR="/mnt/ncdata"` (This is an example for Linux. See [this](https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir) for other OS' and for an explanation of what this value does. This specific one needs to be specified upon the first startup if you want to change it to a specific path instead of the default Docker volume). To see explanations and examples for further variables (like changing the location of Nextcloud's datadir or mounting some locations as external storage into the Nextcloud container), read through this readme and look at the docker-compose file: https://github.com/nextcloud/all-in-one/blob/main/compose.yaml + +
+ Note: you may be interested in adjusting Nextcloud’s datadir to store the files in a different location than the default docker volume. See [this documentation](https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir) on how to do it. You should also think about limiting the Apache container to listen only on localhost in case the reverse proxy is running on the same host and in the host network, by providing an additional environmental variable to this docker run command. See [point 3](#3-limit-the-access-to-the-apache-container). @@ -989,38 +1096,38 @@ On Synology DSM see https://github.com/nextcloud/all-in-one#how-to-run-aio-on-sy Simply translate the docker run command into a docker-compose file. You can have a look at [this file](https://github.com/nextcloud/all-in-one/blob/main/compose.yaml) for some inspiration but you will need to modify it either way. You can find further examples here: https://github.com/nextcloud/all-in-one/discussions/588 -## 3. Limit the access to the Apache container +### 3. Limit the access to the Apache container Use this environment variable during the initial startup of the mastercontainer to make the apache container only listen on localhost: `--env APACHE_IP_BINDING=127.0.0.1`. **Attention:** This is only recommended to be set if you use `localhost` in your reverse proxy config to connect to your AIO instance. If you use an ip-address instead of localhost, you should set it to `0.0.0.0`. -## 4. Open the AIO interface +### 4. Open the AIO interface After starting AIO, you should be able to access the AIO Interface via `https://ip.address.of.the.host:8080` and type in and validate the domain that you have configured.
⚠️ **Important:** do always use an ip-address if you access this port and not a domain as HSTS might block access to it later! (It is also expected that this port uses a self-signed certificate due to security concerns which you need to accept in your browser)
Enter your domain in the AIO interface that you've used in the reverse proxy config and you should be done. Please do not forget to open/forward port `3478/TCP` and `3478/UDP` in your firewall/router for the Talk container! -## 5. Optional: Configure AIO for reverse proxies that connect to nextcloud using an ip-address and not localhost nor 127.0.0.1 +### 5. Optional: Configure AIO for reverse proxies that connect to nextcloud using an ip-address and not localhost nor 127.0.0.1 If your reverse proxy connects to nextcloud using an ip-address and not localhost or 127.0.0.1* you must make the following configuration changes -*: The IP address it uses to connect to AIO is not in a private IP range such as these: `127.0.0.1/8,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8,fd00::/8,::1` +*: The IP address it uses to connect to AIO is not in a private IP range such as these: `127.0.0.0/8,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8,100.64.0.0/10,fd00::/8,::1/128` -### Nextcloud trusted proxies +#### Nextcloud trusted proxies Add the IP it uses connect to AIO to the Nextcloud trusted_proxies like this: ``` sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:system:set trusted_proxies 2 --value=ip.address.of.proxy ``` -### Collabora WOPI allow list +#### Collabora WOPI allow list If your reverse proxy connects to Nextcloud with an IP address that is different from the one for your domain* and you are using the Collabora server then you must also add the IP to the WOPI request allow list via `Administration Settings > Administration > Office > Allow list for WOPI requests`. -*: For example, the reverse proxy has a public globally routable IP and connects to your AIO instance via Tailscale with an IP in the `100.64.0.0/10` range, or you are using a Cloudflare tunnel ([cloudflare notes](https://github.com/nextcloud/all-in-one?tab=readme-ov-file#notes-on-cloudflare-proxytunnel): You must add all [Cloudflare IP-Ranges](https://www.cloudflare.com/ips/) to the WOPI allowlist.) +*: For example, the reverse proxy has a public globally routable IP and connects to your AIO instance via Tailscale with an IP in the `100.64.0.0/10` range, or you are using a Cloudflare tunnel ([cloudflare notes](https://github.com/nextcloud/all-in-one#notes-on-cloudflare-proxytunnel): You must add all [Cloudflare IP-Ranges](https://www.cloudflare.com/ips/) to the WOPI allowlist.) -### External reverse proxies connecting via VPN (e.g. Tailscale) +#### External reverse proxies connecting via VPN (e.g. Tailscale) If your reverse proxy is outside your LAN and connecting via VPN such as Tailscale, you may want to set `APACHE_IP_BINDING=AIO.VPN.host.IP` to ensure only traffic coming from the VPN can connect. -## 6. Optional: get a valid certificate for the AIO interface +### 6. Optional: get a valid certificate for the AIO interface If you want to also access your AIO interface publicly with a valid certificate, you can add e.g. the following config to your Caddyfile: @@ -1037,7 +1144,8 @@ https://:8443 { Afterwards should the AIO interface be accessible via `https://ip.address.of.the.host:8443`. You can alternatively change the domain to a different subdomain by using `https://:443` instead of `https://:8443` in the Caddyfile and use that to access the AIO interface. -## 7. How to debug things? +### 7. How to debug things? + @@ -1055,9 +1163,10 @@ If something does not work, follow the steps below: 1. Check if you have a public IPv4- and public IPv6-address. If you only have a public IPv6-address (e.g. due to DS-Lite), make sure to enable IPv6 in Docker and your whole networking infrastructure (e.g. also by adding an AAAA DNS-entry to your domain)! 1. [Enable Hairpin NAT in your router](https://github.com/nextcloud/all-in-one/discussions/5849) or [set up a local DNS server and add a custom dns-record](https://github.com/nextcloud/all-in-one#how-can-i-access-nextcloud-locally) that allows the server to reach itself locally 1. Try to configure everything from scratch - if it still does not work by following https://github.com/nextcloud/all-in-one#how-to-properly-reset-the-instance. -1. As last resort, you may disable the domain validation by adding `--env SKIP_DOMAIN_VALIDATION=true` to the docker run command. But only use this if you are completely sure that you've correctly configured everything! +1. As last resort, you may disable the domain validation by adding `--env SKIP_DOMAIN_VALIDATION=true` to the docker run command. But only use this if you are completely sure that you've correctly configured everything! Also see [this documentation](https://github.com/nextcloud/all-in-one#how-to-skip-the-domain-validation). + +### 8. Removing the reverse proxy -## 8. Removing the reverse proxy If you, at some point, want to remove the reverse proxy, here are some general steps: 1. Stop all running containers in the AIO Interface. 2. Stop and remove the mastercontainer. @@ -1066,7 +1175,7 @@ If you, at some point, want to remove the reverse proxy, here are some general s sudo docker rm nextcloud-aio-mastercontainer ``` 3. Remove the software and configuration file that you used for the reverse proxy (see section 1). -4. Restart the mastercontainer with the [docker run command from the main readme](https://github.com/nextcloud/all-in-one?tab=readme-ov-file#how-to-use-this) but add the two options: +4. Restart the mastercontainer with the [docker run command from the main readme](https://github.com/nextcloud/all-in-one#how-to-use-this) but add the two options: ``` --env APACHE_IP_BINDING=0.0.0.0 \ --env APACHE_PORT=443 \ @@ -1075,3 +1184,10 @@ If you, at some point, want to remove the reverse proxy, here are some general s *The first command ensures that the Apache container is listening on all available network interfaces and the second command configures it to listen to port 443.* 5. Restart all other containers in the AIO interface. + +--- + +## Footnotes: + +[^talkPort]: Ports 3478/TCP and 3478/UDP are also required if using Nextcloud Talk (but they're less likely to conflict with existing services). +[^shared]: Other Nextcloud Server deployment methods (but not AIO) can be deployed behind shared hostnames and accessed via subfolder-based URLs. For example, this is supported with Bare Metal (Archive) and the micro-services Docker image, among others. Note that pure subfolder deployments are less and less required these days, with the broad support for virtual host based access (including at the reverse proxy level), which easily facilitates port IP address and external port sharing. diff --git a/tests/QA/060-environmental-variables.md b/tests/QA/060-environmental-variables.md index 818ecf7c..b984c0e3 100644 --- a/tests/QA/060-environmental-variables.md +++ b/tests/QA/060-environmental-variables.md @@ -6,6 +6,7 @@ - [ ] When starting the mastercontainer with `--env TALK_PORT=3479` on a clean instance, the talk container should use this port later on. Using a value here that is not a port will not allow the mastercontainer to start correctly. Also it should stop if apache_port and talk_port are set to the same value. - [ ] Make also sure that reverse proxies work by following https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md#reverse-proxy-documentation and following [001-initial-setup.md](./001-initial-setup.md) and [002-new-instance.md](./002-new-instance.md) - [ ] When starting the mastercontainer with `--env SKIP_DOMAIN_VALIDATION=true` on a clean instance, it should skip the domain verification. So it should accept any domain that you type in then. +- [ ] When starting the mastercontainer with `--env DOCKER_API_VERSION=1.44` it should use the mentioned docker API version internally for all requests - [ ] When starting the mastercontainer with `--env NEXTCLOUD_DATADIR="/mnt/testdata"` it should map that location from `/mnt/testdata` to `/mnt/ncdata` inside the Nextcloud container. Not having adjusted the permissions correctly before starting the Nextcloud container the first time will not allow the Nextcloud container to start correctly. See https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir for allowed values. - [ ] When starting the mastercontainer with `--env NEXTCLOUD_MOUNT="/mnt/"` it should map `/mnt/` to `/mnt/` inside the Nextcloud container. See https://github.com/nextcloud/all-in-one#how-to-allow-the-nextcloud-container-to-access-directories-on-the-host for allowed values. - [ ] When starting the mastercontainer with `--env NEXTCLOUD_UPLOAD_LIMIT=11G` it should change Nextclouds upload limit to 11G. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-upload-limit-for-nextcloud for allowed values. diff --git a/zizmor.yml b/zizmor.yml new file mode 100644 index 00000000..7601baa4 --- /dev/null +++ b/zizmor.yml @@ -0,0 +1,8 @@ +rules: + excessive-permissions: + disable: true + dangerous-triggers: + ignore: + - build_images.yml + artipacked: + disable: true