diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index f0338b79..aca2e718 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,12 +1,15 @@ --- -name: 🐛 Bug report -about: Help us improving by reporting a bug -labels: bug, 0. Needs triage +name: 🐛 Bug report - no questions and no support! +about: Help us improving by reporting a bug - this category is not for questions and also not for support! Please use one of the options below for questions and support +labels: 0. Needs triage --- @@ -20,11 +23,17 @@ labels: bug, 0. Needs triage ### Actual behavior -### Host OS +### Other information +#### Host OS +#### Output of `sudo docker info` -#### Nextcloud AIO version +#### Docker run command or docker-compose file that you used -#### Current channel +#### Output of `sudo docker logs nextcloud-aio-mastercontainer` -#### Other valuable info +#### Output of `sudo docker inspect nextcloud-aio-mastercontainer` + +#### Output of `sudo docker ps -a` + +#### Other valuable info diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index e60db836..2cc54fb4 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,7 +1,7 @@ --- name: 📖 Existing feature/documentation enhancement about: Suggest an enhancement of an existing feature/documentation - for other types, please use the feature request option below -labels: enhancement, 0. Needs triage +labels: 0. Needs triage --- diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bb669bb6..72ae238a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,14 +1,14 @@ blank_issues_enabled: false contact_links: + - name: 📘 Documentation on Nextcloud AIO + url: https://github.com/nextcloud/all-in-one#faq + about: Please read the docs first before submitting any report or request! + - name: ⛑️ Questions and support + url: https://help.nextcloud.com/tag/aio + about: For questions, support and help - name: 💡 Suggest a new feature or discuss one url: https://github.com/nextcloud/all-in-one/discussions/categories/ideas about: For new feature requests and discussion of existing ones - - name: ❓ Questions on AIO - url: https://github.com/nextcloud/all-in-one/discussions/categories/questions - about: For questions regarding AIO - - name: ⛑️ Community Support and Help - url: https://help.nextcloud.com/tag/aio - about: For other types of questions - name: 💼 Nextcloud Enterprise url: https://portal.nextcloud.com/ - about: If you are a Nextcloud Enterprise customer, or need Professional support, so it can be resolved directly by our dedicated engineers more quickly \ No newline at end of file + about: If you are a Nextcloud Enterprise customer, or need Professional support, so it can be resolved directly by our dedicated engineers more quickly diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c16ef12d..7fe1067e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,185 +1,62 @@ version: 2 updates: - package-ecosystem: "github-actions" - directory: "/" + directory: ".github/workflows" schedule: interval: "daily" time: "12:00" open-pull-requests-limit: 10 + rebase-strategy: "disabled" + labels: + - 3. to review + - dependencies + cooldown: + default-days: 7 - package-ecosystem: composer directory: "/php/" schedule: interval: "daily" time: "12:00" open-pull-requests-limit: 10 + rebase-strategy: "auto" labels: - 3. to review - dependencies - package-ecosystem: "docker" - directory: "/Containers/apache" + directories: + - "/Containers/alpine" + - "/Containers/apache" + - "/Containers/borgbackup" + - "/Containers/clamav" + - "/Containers/collabora" + - "/Containers/docker-socket-proxy" + - "/Containers/domaincheck" + - "/Containers/fulltextsearch" + - "/Containers/imaginary" + - "/Containers/mastercontainer" + - "/Containers/nextcloud" + - "/Containers/notify-push" + - "/Containers/onlyoffice" + - "/Containers/postgresql" + - "/Containers/redis" + - "/Containers/talk" + - "/Containers/talk-recording" + - "/Containers/watchtower" + - "/Containers/whiteboard" schedule: interval: "daily" - time: "12:00" + time: "04:00" open-pull-requests-limit: 10 + rebase-strategy: "disabled" labels: - 3. to review - dependencies -- package-ecosystem: "docker" - directory: "/Containers/borgbackup" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/collabora" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/domaincheck" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/mastercontainer" - schedule: - interval: "daily" - time: "12:00" ignore: - dependency-name: "php" update-types: ["version-update:semver-major", "version-update:semver-minor"] - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/nextcloud" - schedule: - interval: "daily" - time: "12:00" - ignore: - - dependency-name: "php" - update-types: ["version-update:semver-major", "version-update:semver-minor"] - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/postgresql" - schedule: - interval: "daily" - time: "12:00" - ignore: - dependency-name: "postgres" update-types: ["version-update:semver-major"] - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/redis" - schedule: - interval: "daily" - time: "12:00" - ignore: - dependency-name: "redis" update-types: ["version-update:semver-major"] - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/talk" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/talk-recording" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/watchtower" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/clamav" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/onlyoffice" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/imaginary" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/fulltextsearch" - schedule: - interval: "daily" - time: "12:00" - ignore: - dependency-name: "elasticsearch" update-types: ["version-update:semver-major"] - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/notify-push" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies -- package-ecosystem: "docker" - directory: "/Containers/docker-socket-proxy" - schedule: - interval: "daily" - time: "12:00" - open-pull-requests-limit: 10 - labels: - - 3. to review - - dependencies 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 eeaeb427..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@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check spelling - uses: codespell-project/actions-codespell@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/command-rebase.yml b/.github/workflows/command-rebase.yml deleted file mode 100644 index 2a4125d8..00000000 --- a/.github/workflows/command-rebase.yml +++ /dev/null @@ -1,51 +0,0 @@ -# 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 - -name: Rebase command - -on: - issue_comment: - types: created - -permissions: - contents: read - -jobs: - rebase: - runs-on: ubuntu-latest - permissions: - contents: none - - # On pull requests and if the comment starts with `/rebase` - if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase') - - steps: - - name: Add reaction on start - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reaction-type: "+1" - - - name: Checkout the latest code - uses: actions/checkout@v4 # v3.5.2 - with: - fetch-depth: 0 - token: ${{ secrets.COMMAND_BOT_PAT }} - - - name: Automatic Rebase - uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8 - env: - GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }} - - - name: Add reaction on failure - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - if: failure() - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reaction-type: "-1" diff --git a/.github/workflows/community-containers.yml b/.github/workflows/community-containers.yml index 2df5173d..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@v4 + 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 b5f907e9..3805a0d0 100644 --- a/.github/workflows/dependency-updates.yml +++ b/.github/workflows/dependency-updates.yml @@ -8,12 +8,12 @@ on: jobs: dependency_updates: name: Run dependency update script - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: shivammathur/setup-php@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: - php-version: 8.3 + php-version: 8.4 extensions: apcu - name: Run dependency update script run: | @@ -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@v6 + 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 b1a2cd87..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@v4 + 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 1548cb2d..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@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Turnstyle - uses: softprops/turnstyle@v2 + uses: softprops/turnstyle@e565d2d86403c5d23533937e95980570545e5586 # v2 with: continue-after-seconds: 180 env: @@ -32,7 +32,7 @@ jobs: # See https://github.com/helm/chart-releaser-action/issues/6 - name: Set up Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 with: version: v3.6.3 @@ -41,7 +41,7 @@ jobs: helm lint ./nextcloud-aio-helm-chart - name: Run chart-releaser - uses: helm/chart-releaser-action@v1.6.0 + uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0 with: mark_as_latest: false charts_dir: . diff --git a/.github/workflows/imaginary-update.yml b/.github/workflows/imaginary-update.yml index b8d26dbe..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@v4 + - 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@v6 + 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 86e269e9..8c0a7f45 100644 --- a/.github/workflows/json-validator.yml +++ b/.github/workflows/json-validator.yml @@ -1,35 +1,37 @@ -name: Json Validator - -on: - pull_request: - paths: - - '**.json' - push: - branches: - - main - paths: - - '**.json' - -jobs: - json-validator: - name: Json Validator - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Validate Json - run: | - sudo apt-get update - sudo apt-get install python3-pip -y --no-install-recommends - sudo pip3 install json-spec - if ! json validate --schema-file=php/containers-schema.json --document-file=php/containers.json; then - exit 1 - fi - JSON_FILES="$(find ./community-containers -name '*.json')" - mapfile -t JSON_FILES <<< "$JSON_FILES" - for file in "${JSON_FILES[@]}"; do - json validate --schema-file=php/containers-schema.json --document-file="$file" 2>&1 | tee -a ./json-validator.log - done - if grep -q "document does not validate with schema.\|invalid JSONFile" ./json-validator.log; then - exit 1 - fi +name: Json Validator + +on: + pull_request: + paths: + - '**.json' + push: + branches: + - main + paths: + - '**.json' + +jobs: + json-validator: + name: Json Validator + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Validate Json + run: | + sudo apt-get update + sudo apt-get install python3-venv -y --no-install-recommends + python3 -m venv venv + . venv/bin/activate + pip3 install json-spec + if ! json validate --schema-file=php/containers-schema.json --document-file=php/containers.json; then + exit 1 + fi + JSON_FILES="$(find ./community-containers -name '*.json')" + mapfile -t JSON_FILES <<< "$JSON_FILES" + for file in "${JSON_FILES[@]}"; do + json validate --schema-file=php/containers-schema.json --document-file="$file" 2>&1 | tee -a ./json-validator.log + done + if grep -q "document does not validate with schema.\|invalid JSONFile" ./json-validator.log; then + exit 1 + fi diff --git a/.github/workflows/lint-helm.yml b/.github/workflows/lint-helm.yml index 41779d5c..61e51450 100644 --- a/.github/workflows/lint-helm.yml +++ b/.github/workflows/lint-helm.yml @@ -11,12 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Install Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 with: version: v3.11.1 diff --git a/.github/workflows/lint-php.yml b/.github/workflows/lint-php.yml index 69db96dd..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 @@ -27,16 +30,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [ "8.3" ] + php-versions: [ "8.4" ] name: php-lint steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 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@a4e22b60bbb9c1021113f2860347b0759f66fe5d # 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 e4e2cc32..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@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 6535d016..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@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run nextcloud-update script run: | # Inspired by https://github.com/nextcloud/docker/blob/master/update.sh @@ -25,7 +25,7 @@ jobs: | sort -V \ | tail -1 )" - sed -i "s|pecl install APCu.*\;|pecl install APCu-$apcu_version\;|" ./Containers/nextcloud/Dockerfile + sed -i "s|\(pecl install[^;]*APCu-\)[0-9.]*|\1$apcu_version|" ./Containers/nextcloud/Dockerfile # Memcached memcached_version="$( @@ -36,7 +36,7 @@ jobs: | sort -V \ | tail -1 )" - sed -i "s|pecl install memcached.* |pecl install memcached-$memcached_version |" ./Containers/nextcloud/Dockerfile + sed -i "s|\(pecl install[^;]*memcached-\)[0-9.]*|\1$memcached_version|" ./Containers/nextcloud/Dockerfile # Redis redis_version="$( @@ -47,18 +47,18 @@ jobs: | sort -V \ | tail -1 )" - sed -i "s|pecl install redis.* |pecl install redis-$redis_version |" ./Containers/nextcloud/Dockerfile + sed -i "s|\(pecl install[^;]*redis-\)[0-9.]*|\1$redis_version|" ./Containers/nextcloud/Dockerfile # Imagick imagick_version="$( - git ls-remote --tags https://github.com/mkoppanen/imagick.git \ + git ls-remote --tags https://github.com/imagick/imagick.git \ | cut -d/ -f3 \ | grep -viE '[a-z]' \ | tr -d '^{}' \ | sort -V \ | tail -1 )" - sed -i "s|pecl install imagick.*\;|pecl install imagick-$imagick_version\;|" ./Containers/nextcloud/Dockerfile + sed -i "s|\(pecl install[^;]*imagick-\)[0-9.]*|\1$imagick_version|" ./Containers/nextcloud/Dockerfile # Igbinary igbinary_version="$( @@ -69,7 +69,7 @@ jobs: | sort -V \ | tail -1 )" - sed -i "s|pecl install igbinary.*\;|pecl install igbinary-$igbinary_version\;|" ./Containers/nextcloud/Dockerfile + sed -i "s|\(pecl install[^;]*igbinary-\)[0-9.]*|\1$igbinary_version|" ./Containers/nextcloud/Dockerfile # Nextcloud NC_MAJOR="$(grep "ENV NEXTCLOUD_VERSION" ./Containers/nextcloud/Dockerfile | grep -oP '[23][0-9]')" @@ -79,7 +79,7 @@ jobs: fi - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + 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 aed16094..38b0fa8d 100644 --- a/.github/workflows/php-deprecation-detector.yml +++ b/.github/workflows/php-deprecation-detector.yml @@ -16,11 +16,11 @@ jobs: name: PHP Deprecation Detector runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up php - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: - php-version: 8.3 + php-version: 8.4 extensions: apcu coverage: none 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-on-workflow-dispatch.yml b/.github/workflows/playwright-on-workflow-dispatch.yml new file mode 100644 index 00000000..6d2f6d32 --- /dev/null +++ b/.github/workflows/playwright-on-workflow-dispatch.yml @@ -0,0 +1,91 @@ +name: Playwright Tests + +on: + workflow_dispatch: + +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: 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 /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 /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/psalm-update-baseline.yml b/.github/workflows/psalm-update-baseline.yml index fbe362c1..bcbb12c3 100644 --- a/.github/workflows/psalm-update-baseline.yml +++ b/.github/workflows/psalm-update-baseline.yml @@ -10,12 +10,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up php - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: - php-version: 8.3 + php-version: 8.4 extensions: apcu coverage: none @@ -30,7 +30,7 @@ jobs: continue-on-error: true - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + 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 e4775674..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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up php - uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d # v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2.36.0 with: - php-version: 8.3 + 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 697b1807..b051c355 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -15,9 +15,9 @@ jobs: name: Check Shell runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Shellcheck - uses: ludeeus/action-shellcheck@2.0.0 + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 with: check_together: 'yes' env: diff --git a/.github/workflows/talk.yml b/.github/workflows/talk.yml index 15ea5fad..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@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run talk-container-update run: | # Recording @@ -36,7 +36,7 @@ jobs: # Janus janus_version="$( - git ls-remote https://github.com/meetecho/janus-gateway v0.*.* \ + git ls-remote https://github.com/meetecho/janus-gateway v1.*.* \ | cut -d/ -f3 \ | sort -V \ | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" \ @@ -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@v6 + 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 4544f993..27b8776d 100644 --- a/.github/workflows/twig-lint.yml +++ b/.github/workflows/twig-lint.yml @@ -24,12 +24,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@7bf05c6b704e0b9bfee22300130a31b5ea68d593 # v2 with: - php-version: 8.3 + php-version: 8.4 extensions: apcu coverage: none diff --git a/.github/workflows/update-copyright.yml b/.github/workflows/update-copyright.yml new file mode 100644 index 00000000..103851c9 --- /dev/null +++ b/.github/workflows/update-copyright.yml @@ -0,0 +1,11 @@ +name: Update Copyright + +on: + workflow_dispatch: + +jobs: + update-copyright: + name: update copyright + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/update-helm.yml b/.github/workflows/update-helm.yml index 62ee2650..92cbb978 100644 --- a/.github/workflows/update-helm.yml +++ b/.github/workflows/update-helm.yml @@ -11,16 +11,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: update helm chart run: | - DOCKER_TAG="$(curl -L -s 'https://registry.hub.docker.com/v2/repositories/nextcloud/all-in-one/tags?page_size=1024' | jq '."results"[]["name"]' | sed 's|"||g' | grep '^20[0-9_]\+' | grep -v latest | sort -r | head -1)" + set -x + GHCR_TOKEN="$(curl https://ghcr.io/token?scope=repository:nextcloud-releases/nce-php-fpm-mgmt:pull | jq '.token' | sed 's|"||g')" + DOCKER_TAG="$(curl -H "Authorization: Bearer ${GHCR_TOKEN}" -L -s 'https://ghcr.io/v2/nextcloud-releases/all-in-one/tags/list?page_size=1024' | jq '.tags' | sed 's|"||g;s|[[:space:]]||g;s|,||g' | grep '^20[0-9_]\+' | grep -v latest | sort -r | head -1)" export DOCKER_TAG - if [ -n "$DOCKER_TAG" ] && ! grep -q "$DOCKER_TAG" ./nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-deployment.yaml; then + set +x + if [ -n "$DOCKER_TAG" ] && ! grep -q "aio-nextcloud:$DOCKER_TAG" ./nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-deployment.yaml; then sudo bash nextcloud-aio-helm-chart/update-helm.sh "$DOCKER_TAG" fi - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + 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 c241708d..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@v4 + 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@v6 + 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 new file mode 100644 index 00000000..1098b4c4 --- /dev/null +++ b/Containers/alpine/Dockerfile @@ -0,0 +1,7 @@ +# syntax=docker/dockerfile:latest +FROM alpine:3.23.3 + +RUN set -ex; \ + apk upgrade --no-cache -a + +LABEL org.label-schema.vendor="Nextcloud" diff --git a/Containers/apache/Caddyfile b/Containers/apache/Caddyfile index 8e7e4a21..4b92d807 100644 --- a/Containers/apache/Caddyfile +++ b/Containers/apache/Caddyfile @@ -15,6 +15,7 @@ } https://{$ADDITIONAL_TRUSTED_DOMAIN}:443, +http://{$APACHE_HOST}:23973, # For Collabora callback and WOPI requests, see containers.json {$PROTOCOL}://{$NC_DOMAIN}:{$APACHE_PORT} { header -Server header -X-Powered-By @@ -40,7 +41,7 @@ https://{$ADDITIONAL_TRUSTED_DOMAIN}:443, route /onlyoffice/* { uri strip_prefix /onlyoffice reverse_proxy {$ONLYOFFICE_HOST}:80 { - header_up X-Forwarded-Host {http.request.host}/onlyoffice + header_up X-Forwarded-Host {http.request.hostport}/onlyoffice header_up X-Forwarded-Proto https } } @@ -51,6 +52,12 @@ https://{$ADDITIONAL_TRUSTED_DOMAIN}:443, reverse_proxy {$TALK_HOST}:8081 } + # Whiteboard + route /whiteboard/* { + uri strip_prefix /whiteboard + reverse_proxy {$WHITEBOARD_HOST}:3002 + } + # Nextcloud route { header Strict-Transport-Security max-age=31536000; diff --git a/Containers/apache/Dockerfile b/Containers/apache/Dockerfile index b118fd95..9ccadfb8 100644 --- a/Containers/apache/Dockerfile +++ b/Containers/apache/Dockerfile @@ -1,8 +1,8 @@ # syntax=docker/dockerfile:latest -FROM caddy:2.8.4-alpine AS caddy +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.62-alpine3.20 +FROM httpd:2.4.66-alpine3.23 COPY --from=caddy /usr/bin/caddy /usr/bin/caddy @@ -81,10 +81,12 @@ RUN set -ex; \ \ echo "root:$(openssl rand -base64 12)" | chpasswd -USER www-data +USER 33 ENTRYPOINT ["/start.sh"] CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"] HEALTHCHECK CMD /healthcheck.sh -LABEL com.centurylinklabs.watchtower.enable="false" +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/apache/healthcheck.sh b/Containers/apache/healthcheck.sh index 15235352..e9c1fad4 100644 --- a/Containers/apache/healthcheck.sh +++ b/Containers/apache/healthcheck.sh @@ -3,7 +3,3 @@ nc -z "$NEXTCLOUD_HOST" 9000 || exit 0 nc -z 127.0.0.1 8000 || exit 1 nc -z 127.0.0.1 "$APACHE_PORT" || exit 1 -if ! nc -z "$NC_DOMAIN" 443; then - echo "Could not reach $NC_DOMAIN on port 443." - exit 1 -fi diff --git a/Containers/apache/start.sh b/Containers/apache/start.sh index 9d69eb47..02a2f2ad 100644 --- a/Containers/apache/start.sh +++ b/Containers/apache/start.sh @@ -46,7 +46,9 @@ echo "$CADDYFILE" > /tmp/Caddyfile # Change the trusted_proxies in case of reverse proxies if [ "$APACHE_PORT" != '443' ]; then - CADDYFILE="$(sed 's|# trusted_proxies placeholder|trusted_proxies static private_ranges|' /tmp/Caddyfile)" + # Here the 100.64.0.0/10 range gets added which is the CGNAT range used by Tailscale nodes + # See https://github.com/nextcloud/all-in-one/pull/6703 for reference + CADDYFILE="$(sed 's|# trusted_proxies placeholder|trusted_proxies static private_ranges 100.64.0.0/10|' /tmp/Caddyfile)" else CADDYFILE="$(sed "s|# trusted_proxies placeholder|trusted_proxies static $IPv4_ADDRESS|" /tmp/Caddyfile)" fi @@ -64,6 +66,11 @@ caddy fmt --overwrite /tmp/Caddyfile # Add caddy path mkdir -p /mnt/data/caddy/ +# Fix caddy startup +if [ -d "/mnt/data/caddy/locks" ]; then + rm -rf /mnt/data/caddy/locks/* +fi + # Fix apache startup rm -f /usr/local/apache2/logs/httpd.pid diff --git a/Containers/apache/supervisord.conf b/Containers/apache/supervisord.conf index d6a93803..7ab935e4 100644 --- a/Containers/apache/supervisord.conf +++ b/Containers/apache/supervisord.conf @@ -9,8 +9,8 @@ logfile_backups=10 loglevel=error [program:apache] -# stdout_logfile=/dev/stdout -# stdout_logfile_maxbytes=0 +# Stdout logging is disabled as otherwise the logs are spammed +stdout_logfile=NONE stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 command=apachectl -DFOREGROUND diff --git a/Containers/borgbackup/Dockerfile b/Containers/borgbackup/Dockerfile index d46eda1a..6e3180cb 100644 --- a/Containers/borgbackup/Dockerfile +++ b/Containers/borgbackup/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.20.2 +FROM alpine:3.23.3 RUN set -ex; \ \ @@ -11,15 +11,19 @@ RUN set -ex; \ rsync \ fuse \ py3-llfuse \ - jq + jq \ + openssh-client VOLUME /root COPY --chmod=770 *.sh / +COPY borg_excludes / ENTRYPOINT ["/start.sh"] # hadolint ignore=DL3002 USER root -LABEL com.centurylinklabs.watchtower.enable="false" +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 f2e9d04e..b7b96147 100644 --- a/Containers/borgbackup/backupscript.sh +++ b/Containers/borgbackup/backupscript.sh @@ -34,19 +34,23 @@ for volume in "${DEFAULT_VOLUMES[@]}"; do done # Check if target is mountpoint -if ! mountpoint -q /mnt/borgbackup; then - echo "/mnt/borgbackup is not a mountpoint which is not allowed." +if [ -z "$BORG_REMOTE_REPO" ] && ! mountpoint -q "$MOUNT_DIR"; then + echo "$MOUNT_DIR is not a mountpoint which is not allowed." exit 1 fi -# Check if target is empty -if [ "$BORG_MODE" != backup ] && [ "$BORG_MODE" != test ] && ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then - echo "The repository is empty. Cannot perform check or restore." +# Check if repo is uninitialized +if [ "$BORG_MODE" != backup ] && [ "$BORG_MODE" != test ] && ! borg info > /dev/null; then + if [ -n "$BORG_REMOTE_REPO" ]; then + echo "The repository is uninitialized or cannot connect to remote. Cannot perform check or restore." + else + echo "The repository is uninitialized. Cannot perform check or restore." + fi exit 1 fi # Do not continue if this file exists (needed for simple external blocking) -if [ -f "$BORG_BACKUP_DIRECTORY/aio-lockfile" ]; then +if [ -z "$BORG_REMOTE_REPO" ] && [ -f "$BORG_BACKUP_DIRECTORY/aio-lockfile" ]; then echo "Not continuing because aio-lockfile exists – it seems like a script is externally running which is locking the backup archive." echo "If this should not be the case, you can fix this by deleting the 'aio-lockfile' file from the backup archive directory." exit 1 @@ -57,6 +61,15 @@ if [ "$BORG_MODE" = backup ] || [ "$BORG_MODE" = restore ]; then touch "/nextcloud_aio_volumes/nextcloud_aio_database_dump/backup-is-running" fi +if [ -n "$BORG_REMOTE_REPO" ] && ! [ -f "$BORGBACKUP_KEY" ]; then + echo "First run, creating borg ssh key" + ssh-keygen -f "$BORGBACKUP_KEY" -N "" + echo "You should configure the remote to accept this public key" +fi +if [ -n "$BORG_REMOTE_REPO" ] && [ -f "$BORGBACKUP_KEY.pub" ]; then + echo "Your public ssh key for borgbackup is: $(cat "$BORGBACKUP_KEY.pub")" +fi + # Do the backup if [ "$BORG_MODE" = backup ]; then @@ -71,8 +84,8 @@ if [ "$BORG_MODE" = backup ]; then echo "database-dump is missing. Cannot perform backup!" echo "Please check the database container logs!" exit 1 - elif ! [ -f "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/.ocdata" ]; then - echo "The .ocdata file is missing in Nextcloud datadir which means it is invalid!" + elif ! [ -f "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/.ocdata" ] && ! [ -f "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/.ncdata" ]; then + echo "The .ncdata or .ocdata file is missing in Nextcloud datadir which means it is invalid!" echo "Is the drive where the datadir is located on still mounted?" exit 1 fi @@ -100,15 +113,22 @@ if [ "$BORG_MODE" = backup ]; then exit 1 fi - # Create backup folder - mkdir -p "$BORG_BACKUP_DIRECTORY" + if [ -z "$BORG_REMOTE_REPO" ]; then + # Create backup folder + mkdir -p "$BORG_BACKUP_DIRECTORY" + fi - # Initialize the repository if the target is empty - if ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then + # Initialize the repository if can't get info from target + if ! borg info > /dev/null; then # Don't initialize if already initialized if [ -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config" ]; then - echo "No borg config file was found in the targeted directory." - echo "This might happen if the targeted directory is located on an external drive and the drive not connected anymore. You should check this." + if [ -n "$BORG_REMOTE_REPO" ]; then + echo "Borg could not get info from the remote repo." + echo "This might be a failure to connect to the remote server. See the above borg info output for details." + else + echo "Borg could not get info from the targeted directory." + echo "This might happen if the targeted directory is located on an external drive and the drive not connected anymore. You should check this." + fi echo "If you instead want to initialize a new backup repository, you may delete the 'borg.config' file that is stored in the mastercontainer volume manually, which will allow you to initialize a new borg repository in the chosen directory:" echo "sudo docker exec nextcloud-aio-mastercontainer rm /mnt/docker-aio-config/data/borg.config" exit 1 @@ -116,28 +136,39 @@ if [ "$BORG_MODE" = backup ]; then echo "Initializing repository..." NEW_REPOSITORY=1 - if ! borg init --debug --encryption=repokey-blake2 "$BORG_BACKUP_DIRECTORY"; then + if ! borg init --debug --encryption=repokey-blake2; then echo "Could not initialize borg repository." - rm -f "$BORG_BACKUP_DIRECTORY/config" exit 1 fi - borg config "$BORG_BACKUP_DIRECTORY" additional_free_space 2G - # Fix too large Borg cache - # https://borgbackup.readthedocs.io/en/stable/faq.html#the-borg-cache-eats-way-too-much-disk-space-what-can-i-do - BORG_ID="$(borg config "$BORG_BACKUP_DIRECTORY" id)" - rm -r "/root/.cache/borg/$BORG_ID/chunks.archive.d" - touch "/root/.cache/borg/$BORG_ID/chunks.archive.d" + if [ -z "$BORG_REMOTE_REPO" ]; then + # borg config only works for local repos; it's up to the remote to ensure the disk isn't full + borg config :: additional_free_space 2G - # Make a backup from the borg config file - if ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then - echo "The borg config file wasn't created. Something is wrong." + # Fix too large Borg cache + # https://borgbackup.readthedocs.io/en/stable/faq.html#the-borg-cache-eats-way-too-much-disk-space-what-can-i-do + BORG_ID="$(borg config :: id)" + rm -r "/root/.cache/borg/$BORG_ID/chunks.archive.d" + touch "/root/.cache/borg/$BORG_ID/chunks.archive.d" + fi + + if ! borg info > /dev/null; then + echo "Borg can't get info from the repo it created. Something is wrong." exit 1 fi + rm -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config" - if ! cp "$BORG_BACKUP_DIRECTORY/config" "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config"; then - echo "Could not copy config file to second place. Cannot perform backup." - exit 1 + if [ -n "$BORG_REMOTE_REPO" ]; then + # `borg config` does not support remote repos so instead create a dummy file and rely on the remote to avoid + # corruption of the config file (which contains the encryption key). We don't actually use the contents of + # this file anywhere, so a touch is all we need so we remember we already initialized the repo. + touch "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config" + else + # Make a backup from the borg config file + if ! cp "$BORG_BACKUP_DIRECTORY/config" "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config"; then + echo "Could not copy config file to second place. Cannot perform backup." + exit 1 + fi fi echo "Repository successfully initialized." @@ -149,13 +180,27 @@ if [ "$BORG_MODE" = backup ]; then # Borg options # auto,zstd compression seems to has the best ratio based on: # https://forum.level1techs.com/t/optimal-compression-for-borg-backups/145870/6 - BORG_OPTS=(-v --stats --compression "auto,zstd" --exclude-caches) + BORG_OPTS=(-v --stats --compression "auto,zstd") if [ "$NEW_REPOSITORY" = 1 ]; then BORG_OPTS+=(--progress) fi # Exclude the nextcloud log and audit log for GDPR reasons - BORG_EXCLUDE=(--exclude "/nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/nextcloud.log*" --exclude "/nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/audit.log") + BORG_EXCLUDE=(--exclude "/nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/nextcloud.log*" --exclude "/nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/audit.log" --exclude "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/lost+found") + BORG_INCLUDE=() + + # Exclude datadir if .noaiobackup file was found + # shellcheck disable=SC2144 + 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 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/") + BORG_INCLUDE+=(--pattern="+/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/appdata_*/preview/.noaiobackup") + echo "⚠️⚠️⚠️ '.noaiobackup' file was found in the preview directory. Excluding the preview directory from backup!" + fi # Make sure that there is always a borg.config file before creating a new backup if ! [ -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config" ]; then @@ -167,9 +212,9 @@ if [ "$BORG_MODE" = backup ]; then # Create the backup echo "Starting the backup..." get_start_time - if ! borg create "${BORG_OPTS[@]}" "${BORG_EXCLUDE[@]}" "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-nextcloud-aio" "/nextcloud_aio_volumes/"; then + if ! borg create "${BORG_OPTS[@]}" "${BORG_INCLUDE[@]}" "${BORG_EXCLUDE[@]}" "::$CURRENT_DATE-nextcloud-aio" "/nextcloud_aio_volumes/" --exclude-from /borg_excludes; then echo "Deleting the failed backup archive..." - borg delete --stats "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-nextcloud-aio" + borg delete --stats "::$CURRENT_DATE-nextcloud-aio" echo "Backup failed!" echo "You might want to check the backup integrity via the AIO interface." if [ "$NEW_REPOSITORY" = 1 ]; then @@ -188,14 +233,14 @@ if [ "$BORG_MODE" = backup ]; then # Prune archives echo "Pruning the archives..." - if ! borg prune --stats --glob-archives '*_*-nextcloud-aio' "${BORG_PRUNE_OPTS[@]}" "$BORG_BACKUP_DIRECTORY"; then + if ! borg prune --stats --glob-archives '*_*-nextcloud-aio' "${BORG_PRUNE_OPTS[@]}"; then echo "Failed to prune archives!" exit 1 fi # Compact archives echo "Compacting the archives..." - if ! borg compact "$BORG_BACKUP_DIRECTORY"; then + if ! borg compact; then echo "Failed to compact archives!" exit 1 fi @@ -212,19 +257,19 @@ if [ "$BORG_MODE" = backup ]; then fi done echo "Starting the backup for additional volumes..." - if ! borg create "${BORG_OPTS[@]}" "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-docker-volumes" "/docker_volumes/"; then + if ! borg create "${BORG_OPTS[@]}" "::$CURRENT_DATE-additional-docker-volumes" "/docker_volumes/"; then echo "Deleting the failed backup archive..." - borg delete --stats "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-docker-volumes" + borg delete --stats "::$CURRENT_DATE-additional-docker-volumes" echo "Backup of additional docker-volumes failed!" exit 1 fi echo "Pruning additional volumes..." - if ! borg prune --stats --glob-archives '*_*-additional-docker-volumes' "${BORG_PRUNE_OPTS[@]}" "$BORG_BACKUP_DIRECTORY"; then + if ! borg prune --stats --glob-archives '*_*-additional-docker-volumes' "${BORG_PRUNE_OPTS[@]}"; then echo "Failed to prune additional docker-volumes archives!" exit 1 fi echo "Compacting additional volumes..." - if ! borg compact "$BORG_BACKUP_DIRECTORY"; then + if ! borg compact; then echo "Failed to compact additional docker-volume archives!" exit 1 fi @@ -242,19 +287,19 @@ if [ "$BORG_MODE" = backup ]; then EXCLUDE_DIRS+=(--exclude "/host_mounts/$directory/") done echo "Starting the backup for additional host mounts..." - if ! borg create "${BORG_OPTS[@]}" "${EXCLUDE_DIRS[@]}" "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-host-mounts" "/host_mounts/"; then + if ! borg create "${BORG_OPTS[@]}" "${EXCLUDE_DIRS[@]}" "::$CURRENT_DATE-additional-host-mounts" "/host_mounts/"; then echo "Deleting the failed backup archive..." - borg delete --stats "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-host-mounts" + borg delete --stats "::$CURRENT_DATE-additional-host-mounts" echo "Backup of additional host-mounts failed!" exit 1 fi echo "Pruning additional host mounts..." - if ! borg prune --stats --glob-archives '*_*-additional-host-mounts' "${BORG_PRUNE_OPTS[@]}" "$BORG_BACKUP_DIRECTORY"; then + if ! borg prune --stats --glob-archives '*_*-additional-host-mounts' "${BORG_PRUNE_OPTS[@]}"; then echo "Failed to prune additional host-mount archives!" exit 1 fi echo "Compacting additional host mounts..." - if ! borg compact "$BORG_BACKUP_DIRECTORY"; then + if ! borg compact; then echo "Failed to compact additional host-mount archives!" exit 1 fi @@ -276,17 +321,38 @@ fi if [ "$BORG_MODE" = restore ]; then get_start_time - # Perform the restore + # Pick archive to restore if [ -n "$SELECTED_RESTORE_TIME" ]; then - SELECTED_ARCHIVE="$(borg list "$BORG_BACKUP_DIRECTORY" | grep "nextcloud-aio" | grep "$SELECTED_RESTORE_TIME" | awk -F " " '{print $1}' | head -1)" + SELECTED_ARCHIVE="$(borg list | grep "nextcloud-aio" | grep "$SELECTED_RESTORE_TIME" | awk -F " " '{print $1}' | head -1)" else - SELECTED_ARCHIVE="$(borg list "$BORG_BACKUP_DIRECTORY" | grep "nextcloud-aio" | awk -F " " '{print $1}' | sort -r | head -1)" + SELECTED_ARCHIVE="$(borg list | grep "nextcloud-aio" | awk -F " " '{print $1}' | sort -r | head -1)" fi echo "Restoring '$SELECTED_ARCHIVE'..." - mkdir -p /tmp/borg - if ! borg mount "$BORG_BACKUP_DIRECTORY::$SELECTED_ARCHIVE" /tmp/borg; then - echo "Could not mount the backup!" - exit 1 + + ADDITIONAL_RSYNC_EXCLUDES=() + ADDITIONAL_BORG_EXCLUDES=() + ADDITIONAL_FIND_EXCLUDES=() + # Exclude datadir if .noaiobackup file was found + # shellcheck disable=SC2144 + if [ -f "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/.noaiobackup" ]; then + # Keep these 3 in sync. Beware, the pattern syntax and the paths differ + 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 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" + # Exclude previews from restore if selected to speed up process or exclude preview folder if .noaiobackup file was found + elif [ -n "$RESTORE_EXCLUDE_PREVIEWS" ] || [ -f /nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/appdata_*/preview/.noaiobackup ]; then + # Keep these 3 in sync. Beware, the pattern syntax and the paths differ + ADDITIONAL_RSYNC_EXCLUDES=(--exclude "nextcloud_aio_nextcloud_data/appdata_*/preview/**") + ADDITIONAL_BORG_EXCLUDES=(--exclude "sh:nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/appdata_*/preview/**") + ADDITIONAL_FIND_EXCLUDES=(-o -regex 'nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/appdata_[^/]*/preview\(/.*\)?') + echo "⚠️⚠️⚠️ Excluding previews 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-app-data preview' after the restore." + echo "See https://github.com/nextcloud/all-in-one#how-to-run-occ-commands" fi # Save Additional Backup dirs @@ -299,27 +365,12 @@ if [ "$BORG_MODE" = restore ]; then DAILY_BACKUPTIME="$(cat /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/daily_backup_time)" fi - # Restore everything except the configuration file - if ! rsync --stats --archive --human-readable -vv --delete \ - --exclude "nextcloud_aio_apache/caddy/**" \ - --exclude "nextcloud_aio_mastercontainer/caddy/**" \ - --exclude "nextcloud_aio_nextcloud/data/nextcloud.log*" \ - --exclude "nextcloud_aio_nextcloud/data/audit.log" \ - --exclude "nextcloud_aio_mastercontainer/certs/**" \ - --exclude "nextcloud_aio_mastercontainer/data/configuration.json" \ - --exclude "nextcloud_aio_mastercontainer/data/daily_backup_running" \ - --exclude "nextcloud_aio_mastercontainer/data/session_date_file" \ - --exclude "nextcloud_aio_mastercontainer/session/**" \ - /tmp/borg/nextcloud_aio_volumes/ /nextcloud_aio_volumes/; then - RESTORE_FAILED=1 - echo "Something failed while restoring from backup." - fi - # Save current aio password AIO_PASSWORD="$(jq '.password' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" - # Save current path + # Save current backup location vars BORG_LOCATION="$(jq '.borg_backup_host_location' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" + REMOTE_REPO="$(jq '.borg_remote_repo' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" # Save current nextcloud datadir if grep -q '"nextcloud_datadir":' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json; then @@ -328,21 +379,116 @@ if [ "$BORG_MODE" = restore ]; then NEXTCLOUD_DATADIR='""' fi - # Restore the configuration file - if ! rsync --archive --human-readable -vv \ - /tmp/borg/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json \ - /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json; then - RESTORE_FAILED=1 - echo "Something failed while restoring the configuration.json." + if [ -z "$BORG_REMOTE_REPO" ]; then + mkdir -p /tmp/borg + if ! borg mount "::$SELECTED_ARCHIVE" /tmp/borg; then + echo "Could not mount the backup!" + exit 1 + fi + + # Restore everything except the configuration file + # + # These exclude patterns need to be kept in sync with the borg_excludes file and the find excludes in this file, + # which use a different syntax (patterns appear in 3 places in total) + if ! rsync --stats --archive --human-readable -vv --delete \ + --exclude "nextcloud_aio_apache/caddy/**" \ + --exclude "nextcloud_aio_mastercontainer/caddy/**" \ + --exclude "nextcloud_aio_nextcloud/data/nextcloud.log*" \ + --exclude "nextcloud_aio_nextcloud/data/audit.log" \ + --exclude "nextcloud_aio_mastercontainer/certs/**" \ + --exclude "nextcloud_aio_mastercontainer/data/configuration.json" \ + --exclude "nextcloud_aio_mastercontainer/data/daily_backup_running" \ + --exclude "nextcloud_aio_mastercontainer/data/session_date_file" \ + --exclude "nextcloud_aio_mastercontainer/session/**" \ + --exclude "nextcloud_aio_nextcloud_data/lost+found" \ + "${ADDITIONAL_RSYNC_EXCLUDES[@]}" \ + /tmp/borg/nextcloud_aio_volumes/ /nextcloud_aio_volumes/; then + RESTORE_FAILED=1 + echo "Something failed while restoring from backup." + fi + + # Restore the configuration file + if ! rsync --archive --human-readable -vv \ + /tmp/borg/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json \ + /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json; then + RESTORE_FAILED=1 + echo "Something failed while restoring the configuration.json." + fi + + if ! umount /tmp/borg; then + echo "Failed to unmount the borg archive but should still be able to restore successfully" + fi + else + # Restore nearly everything + # + # borg mount is really slow for remote repos (did not check whether it's slow for local repos too), + # using extract to /tmp would require temporarily storing a second copy of the data. + # So instead extract directly on top of the destination with exclude patterns for the config, but + # then we do still need to delete local files which are not present in the archive. + # + # Older backups may still contain files we've since excluded, so we have to exclude on extract as well. + cd / # borg extract has no destination arg and extracts to CWD + if ! borg extract "::$SELECTED_ARCHIVE" --progress --exclude-from /borg_excludes "${ADDITIONAL_BORG_EXCLUDES[@]}" --pattern '+nextcloud_aio_volumes/**' + then + RESTORE_FAILED=1 + echo "Failed to extract backup archive." + else + # Delete files/dirs present locally, but not in the backup archive, excluding conf files + # https://unix.stackexchange.com/a/759341 + # This comm does not support -z, but I doubt any file names would have \n in them + # + # These find patterns need to be kept in sync with the borg_excludes file and the rsync excludes in this + # file, which use a different syntax (patterns appear in 3 places in total) + echo "Deleting local files which do not exist in the backup" + if ! find nextcloud_aio_volumes \ + -not \( \ + -path nextcloud_aio_volumes/nextcloud_aio_apache/caddy \ + -o -path "nextcloud_aio_volumes/nextcloud_aio_apache/caddy/*" \ + -o -path nextcloud_aio_volumes/nextcloud_aio_mastercontainer/caddy \ + -o -path "nextcloud_aio_volumes/nextcloud_aio_mastercontainer/caddy/*" \ + -o -path nextcloud_aio_volumes/nextcloud_aio_mastercontainer/certs \ + -o -path "nextcloud_aio_volumes/nextcloud_aio_mastercontainer/certs/*" \ + -o -path nextcloud_aio_volumes/nextcloud_aio_mastercontainer/session \ + -o -path "nextcloud_aio_volumes/nextcloud_aio_mastercontainer/session/*" \ + -o -path "nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/nextcloud.log*" \ + -o -path nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/audit.log \ + -o -path nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/daily_backup_running \ + -o -path nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/session_date_file \ + -o -path "nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/id_borg*" \ + -o -path "nextcloud_aio_nextcloud_data/lost+found" \ + "${ADDITIONAL_FIND_EXCLUDES[@]}" \ + \) \ + | LC_ALL=C sort \ + | LC_ALL=C comm -23 - \ + <(borg list "::$SELECTED_ARCHIVE" --short --exclude-from /borg_excludes --pattern '+nextcloud_aio_volumes/**' | LC_ALL=C sort) \ + > /tmp/local_files_not_in_backup + then + RESTORE_FAILED=1 + echo "Failed to delete local files not in backup archive." + else + # More robust than e.g. xargs as I got a ~"args line too long" error while testing that, but it's slower + # https://stackoverflow.com/a/21848934 + while IFS= read -r file + do rm -vrf -- "$file" || DELETE_FAILED=1 + done < /tmp/local_files_not_in_backup + + if [ "$DELETE_FAILED" = 1 ]; then + RESTORE_FAILED=1 + echo "Failed to delete (some) local files not in backup archive." + fi + fi + fi fi # Set backup-mode to restore since it was a restore CONTENTS="$(jq '."backup-mode" = "restore"' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" echo -E "${CONTENTS}" > /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json - # Reset the backup path to the currently used one + # Reset the backup location vars to the currently used one CONTENTS="$(jq ".borg_backup_host_location = $BORG_LOCATION" /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" echo -E "${CONTENTS}" > /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json + CONTENTS="$(jq ".borg_remote_repo = $REMOTE_REPO" /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" + echo -E "${CONTENTS}" > /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json # Reset the AIO password to the currently used one CONTENTS="$(jq ".password = $AIO_PASSWORD" /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" @@ -366,8 +512,6 @@ if [ "$BORG_MODE" = restore ]; then chmod 770 "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/daily_backup_time" fi - umount /tmp/borg - if [ "$RESTORE_FAILED" = 1 ]; then exit 1 fi @@ -384,6 +528,12 @@ if [ "$BORG_MODE" = restore ]; then touch "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/fingerprint.update" chmod 777 "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/fingerprint.update" + # Add file to Netcloud container to trigger a preview scan the next time it starts + if [ -n "$RESTORE_EXCLUDE_PREVIEWS" ]; then + touch "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/trigger-preview.scan" + chmod 777 "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data/trigger-preview.scan" + fi + # Delete redis cache rm -f "/mnt/redis/dump.rdb" fi @@ -394,9 +544,9 @@ if [ "$BORG_MODE" = check ]; then echo "Checking the backup integrity..." # Perform the check - if ! borg check -v --verify-data "$BORG_BACKUP_DIRECTORY"; then + if ! borg check -v --verify-data; then echo "Some errors were found while checking the backup integrity!" - echo "Check the AIO interface for advices on how to proceed now!" + echo "Check the AIO interface for advice on how to proceed now!" exit 1 fi @@ -412,7 +562,7 @@ if [ "$BORG_MODE" = "check-repair" ]; then echo "Checking the backup integrity and repairing it..." # Perform the check-repair - if ! echo YES | borg check -v --repair "$BORG_BACKUP_DIRECTORY"; then + if ! echo YES | borg check -v --repair; then echo "Some errors were found while checking and repairing the backup integrity!" exit 1 fi @@ -425,24 +575,49 @@ fi # Do the backup test if [ "$BORG_MODE" = test ]; then - if ! [ -d "$BORG_BACKUP_DIRECTORY" ]; then - echo "No 'borg' directory in the given backup directory found!" - echo "Only the files/folders below have been found in the given directory." - ls -a "$MOUNT_DIR" - echo "Please adjust the directory so that the borg archive is positioned in a folder named 'borg' inside the given directory!" - exit 1 - elif ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then - echo "A 'borg' directory was found but could not find the borg archive." - echo "Only the files/folders below have been found in the borg directory." - ls -a "$BORG_BACKUP_DIRECTORY" - echo "The archive and most importantly the config file must be positioned directly in the 'borg' subfolder." - exit 1 - elif ! borg list "$BORG_BACKUP_DIRECTORY"; then + if [ -n "$BORG_REMOTE_REPO" ]; then + if ! borg info > /dev/null; then + echo "Borg could not get info from the remote repo." + echo "See the above borg info output for details." + exit 1 + fi + else + if ! [ -d "$BORG_BACKUP_DIRECTORY" ]; then + echo "No 'borg' directory in the given backup directory found!" + echo "Only the files/folders below have been found in the given directory." + ls -a "$MOUNT_DIR" + echo "Please adjust the directory so that the borg archive is positioned in a folder named 'borg' inside the given directory!" + exit 1 + elif ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then + echo "A 'borg' directory was found but could not find the borg archive." + echo "Only the files/folders below have been found in the borg directory." + ls -a "$BORG_BACKUP_DIRECTORY" + echo "The archive and most importantly the config file must be positioned directly in the 'borg' subfolder." + exit 1 + fi + fi + + if ! borg list >/dev/null; then echo "The entered path seems to be valid but could not open the backup archive." echo "Most likely the entered password was wrong so please adjust it accordingly!" exit 1 else - echo "Everything looks fine so feel free to continue!" - exit 0 + if ! borg list | grep "nextcloud-aio"; then + echo "The backup archive does not contain a valid Nextcloud AIO backup." + echo "Most likely was the archive not created via Nextcloud AIO." + exit 1 + else + echo "Everything looks fine so feel free to continue!" + exit 0 + 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/borg_excludes b/Containers/borgbackup/borg_excludes new file mode 100644 index 00000000..bbe6adaa --- /dev/null +++ b/Containers/borgbackup/borg_excludes @@ -0,0 +1,11 @@ +# These patterns need to be kept in sync with rsync and find excludes in backupscript.sh, +# which use a different syntax (patterns appear in 3 places in total) +nextcloud_aio_volumes/nextcloud_aio_apache/caddy/ +nextcloud_aio_volumes/nextcloud_aio_mastercontainer/caddy/ +nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/nextcloud.log* +nextcloud_aio_volumes/nextcloud_aio_nextcloud/data/audit.log +nextcloud_aio_volumes/nextcloud_aio_mastercontainer/certs/ +nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/daily_backup_running +nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/session_date_file +nextcloud_aio_volumes/nextcloud_aio_mastercontainer/session/ +nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/id_borg* \ No newline at end of file diff --git a/Containers/borgbackup/start.sh b/Containers/borgbackup/start.sh index e8d93f58..bb7a8a6a 100644 --- a/Containers/borgbackup/start.sh +++ b/Containers/borgbackup/start.sh @@ -2,7 +2,7 @@ # Variables export MOUNT_DIR="/mnt/borgbackup" -export BORG_BACKUP_DIRECTORY="$MOUNT_DIR/borg" +export BORG_BACKUP_DIRECTORY="$MOUNT_DIR/borg" # necessary even when remote to store the aio-lockfile # Validate BORG_PASSWORD if [ -z "$BORG_PASSWORD" ] && [ -z "$BACKUP_RESTORE_PASSWORD" ]; then @@ -18,10 +18,22 @@ else fi export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes +if [ -n "$BORG_REMOTE_REPO" ]; then + export BORG_REPO="$BORG_REMOTE_REPO" + + # Location to create the borg ssh pub/priv key + export BORGBACKUP_KEY="/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/id_borg" + + # Accept any host key the first time connecting to the remote. Strictly speaking should be provided by user but you'd + # have to be very unlucky to get MitM'ed on your first connection. + export BORG_RSH="ssh -o StrictHostKeyChecking=accept-new -i $BORGBACKUP_KEY" +else + export BORG_REPO="$BORG_BACKUP_DIRECTORY" +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 @@ -36,8 +48,8 @@ fi rm -f "/nextcloud_aio_volumes/nextcloud_aio_database_dump/backup-is-running" # Get a list of all available borg archives -if borg list "$BORG_BACKUP_DIRECTORY" &>/dev/null; then - borg list "$BORG_BACKUP_DIRECTORY" | grep "nextcloud-aio" | awk -F " " '{print $1","$3,$4}' > "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/backup_archives.list" +if borg list &>/dev/null; then + borg list | grep "nextcloud-aio" | awk -F " " '{print $1","$3,$4}' > "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/backup_archives.list" else echo "" > "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/backup_archives.list" fi diff --git a/Containers/clamav/Dockerfile b/Containers/clamav/Dockerfile index 6120f818..6910ae1c 100644 --- a/Containers/clamav/Dockerfile +++ b/Containers/clamav/Dockerfile @@ -1,26 +1,38 @@ # syntax=docker/dockerfile:latest -# Probably from this file: https://github.com/Cisco-Talos/clamav-docker/blob/main/clamav/1.3/alpine/Dockerfile -FROM clamav/clamav:1.3.1-61 - -COPY clamav.conf /clamav.conf -COPY --chmod=775 start.script /start.script +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a; \ - apk add --no-cache tzdata bash; \ - mkdir -p /var/run/clamav /run/lock; \ - chown -R clamav:clamav /var/run/clamav /run/clamav /var/log/clamav /var/lock /run/lock; \ - chmod 777 -R /var/run/clamav /run/clamav /var/log/clamav /var/lock /run/lock /tmp; \ - sed -i "/^set -eu/r /start.script" /init-unprivileged; \ - rm /start.script; \ - grep -q 'clamd --foreground &' /init-unprivileged; \ - sed -i "s|clamd --foreground \&|clamd --foreground --config-file /tmp/clamd.conf \&|" /init-unprivileged; \ - cat /init-unprivileged + 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|#\?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|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 +COPY --chmod=664 supervisord.conf /supervisord.conf + +USER 100 +RUN set -ex; \ + freshclam --foreground --stdout VOLUME /var/lib/clamav - -USER clamav - -LABEL com.centurylinklabs.watchtower.enable="false" - -ENTRYPOINT ["/init-unprivileged"] +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/clamav.conf b/Containers/clamav/clamav.conf deleted file mode 100644 index 0b781bd3..00000000 --- a/Containers/clamav/clamav.conf +++ /dev/null @@ -1,5 +0,0 @@ -# AIO settings -MaxDirectoryRecursion 30 -MaxFileSize 10G -PCREMaxFileSize 10G -StreamMaxLength 10G diff --git a/Containers/clamav/healthcheck.sh b/Containers/clamav/healthcheck.sh new file mode 100644 index 00000000..fe8b5daa --- /dev/null +++ b/Containers/clamav/healthcheck.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [ "$(echo "PING" | nc 127.0.0.1 3310)" != "PONG" ]; then + echo "ERROR: Unable to contact server" + exit 1 +fi + +echo "Clamd is up" +exit 0 diff --git a/Containers/clamav/start.script b/Containers/clamav/start.script deleted file mode 100644 index c9a530ab..00000000 --- a/Containers/clamav/start.script +++ /dev/null @@ -1,4 +0,0 @@ -# Adjust settings -cat /etc/clamav/clamd.conf > /tmp/clamd.conf -CLAMAV_FILE="$(sed "s|10G|$MAX_SIZE|" /clamav.conf)" -echo "$CLAMAV_FILE" >> /tmp/clamd.conf diff --git a/Containers/clamav/start.sh b/Containers/clamav/start.sh new file mode 100644 index 00000000..609120c1 --- /dev/null +++ b/Containers/clamav/start.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Print out clamav version for compliance reasons +clamscan --version + +echo "Clamav started" + +exec "$@" diff --git a/Containers/clamav/supervisord.conf b/Containers/clamav/supervisord.conf new file mode 100644 index 00000000..1895ceb6 --- /dev/null +++ b/Containers/clamav/supervisord.conf @@ -0,0 +1,30 @@ +[supervisord] +nodaemon=true +nodaemon=true +logfile=/var/log/supervisord/supervisord.log +pidfile=/var/run/supervisord/supervisord.pid +childlogdir=/var/log/supervisord/ +logfile_maxbytes=50MB +logfile_backups=10 +loglevel=error + +[program:freshclam] +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +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=/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 26b41db7..d1693da0 100644 --- a/Containers/collabora/Dockerfile +++ b/Containers/collabora/Dockerfile @@ -1,21 +1,15 @@ # syntax=docker/dockerfile:latest -# From a file located probably somewhere here: https://github.com/CollaboraOnline/online/tree/master/docker -FROM collabora/code:24.04.5.2.1 +# From a file located probably somewhere here: https://github.com/CollaboraOnline/online/blob/master/docker/from-packages/Dockerfile +FROM collabora/code:25.04.8.2.1 USER root ARG DEBIAN_FRONTEND=noninteractive -# hadolint ignore=DL3008 -RUN set -ex; \ - \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - tzdata \ - netcat-openbsd \ - ; \ - rm -rf /var/lib/apt/lists/*; +COPY --chmod=775 healthcheck.sh /healthcheck.sh -USER 100 +USER 1001 -HEALTHCHECK CMD nc -z 127.0.0.1 9980 || exit 1 -LABEL com.centurylinklabs.watchtower.enable="false" +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/healthcheck.sh b/Containers/collabora/healthcheck.sh new file mode 100644 index 00000000..45e9278b --- /dev/null +++ b/Containers/collabora/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/docker-socket-proxy/Dockerfile b/Containers/docker-socket-proxy/Dockerfile index 2a0a6a80..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.0.3-alpine +FROM haproxy:3.3.2-alpine # hadolint ignore=DL3002 USER root @@ -18,4 +18,6 @@ COPY --chmod=664 haproxy.cfg /haproxy.cfg ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh -LABEL com.centurylinklabs.watchtower.enable="false" +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/docker-socket-proxy/haproxy.cfg b/Containers/docker-socket-proxy/haproxy.cfg index 66a0c594..632df434 100644 --- a/Containers/docker-socket-proxy/haproxy.cfg +++ b/Containers/docker-socket-proxy/haproxy.cfg @@ -4,16 +4,18 @@ global maxconn 10 defaults - timeout connect 10s - timeout client 10s - timeout server 10s + timeout connect 30s + timeout client 30s + timeout server 1800s frontend http mode http bind :::2375 v4v6 http-request deny unless { src 127.0.0.1 } || { src ::1 } || { src NC_IPV4_PLACEHOLDER } || { src NC_IPV6_PLACEHOLDER } # docker system _ping - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } METH_GET + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping$ } METH_GET + # docker inspect image: GET images/%s/json + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images/.*/json } METH_GET # container inspect: GET containers/%s/json http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/nc_app_[a-zA-Z0-9_.-]+/json } METH_GET # container inspect: GET containers/%s/logs @@ -22,7 +24,12 @@ frontend http http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/nc_app_[a-zA-Z0-9_.-]+/((start)|(stop)) } METH_POST # container rm: DELETE containers/%s http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/nc_app_[a-zA-Z0-9_.-]+ } METH_DELETE - + # container update/exec: POST containers/%s/update containers/%s/exec + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/nc_app_[a-zA-Z0-9_.-]+/((update)|(exec)) } METH_POST + # container put: PUT containers/%s/archive + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/nc_app_[a-zA-Z0-9_.-]+/archive } METH_PUT + # run exec instance: POST exec/%s + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec/[a-zA-Z0-9_.-]+/start } METH_POST # container create: POST containers/create?name=%s # ACL to restrict container name to nc_app_[a-zA-Z0-9_.-]+ @@ -33,19 +40,19 @@ frontend http # ACL to deny if there are any binds acl binds_present req.body -m reg -i "\"HostConfig\"\s*:.*\"Binds\"\s*:" # ACL to restrict the type of Mounts to volume - acl type_not_volume req.body -m reg -i "\"Mounts\":\s*\[[^\]]*(\"Type\":\s*\"(?!volume\b)\w+\"[^\]]*)+\]" + acl type_not_volume req.body -m reg -i "\"Mounts\"\s*:\s*\[[^\]]*(\"Type\"\s*:\s*\"(?!volume\b)\w+\"[^\]]*)+\]" http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/create } nc_app_container_name !one_mount_volume binds_present type_not_volume METH_POST - # ACL to restrict container creation, that it has HostConfig.Privileged not set - acl no_privileged_flag req.body -m reg -i "\"HostConfig\":\s?{[^}]*\"Privileged\"" + # ACL to restrict container creation, that it has HostConfig.Privileged(by searching for "Privileged" word in all payload) + acl no_privileged_flag req.body -m reg -i "\"Privileged\"" # ACL to allow mount volume with strict pattern for name: nc_app_[a-zA-Z0-9_.-]+_data - acl nc_app_volume_data_only req.body -m reg -i "\"Mounts\":\s?\[\s?{[^}]*\"Source\":\s?\"nc_app_[a-zA-Z0-9_.-]+_data\"" + acl nc_app_volume_data_only req.body -m reg -i "\"Mounts\"\s*:\s*\[\s*{[^}]*\"Source\"\s*:\s*\"nc_app_[a-zA-Z0-9_.-]+_data\"" http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/create } nc_app_container_name !no_privileged_flag nc_app_volume_data_only METH_POST # end of container create # volume create: POST volumes/create # restrict name - acl nc_app_volume_data req.body -m reg -i "\"Name\":\s?\"nc_app_[a-zA-Z0-9_.-]+_data\"" + acl nc_app_volume_data req.body -m reg -i "\"Name\"\s*:\s*\"nc_app_[a-zA-Z0-9_.-]+_data\"" # do not allow to use "device" word e.g., "--opt device=:/path/to/dir" acl volume_no_device req.body -m reg -i "\"device\"" http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes/create } nc_app_volume_data !volume_no_device METH_POST diff --git a/Containers/domaincheck/Dockerfile b/Containers/domaincheck/Dockerfile index 1dae9f9d..374aba4a 100644 --- a/Containers/domaincheck/Dockerfile +++ b/Containers/domaincheck/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.20.2 +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a; \ apk add --no-cache bash lighttpd netcat-openbsd; \ @@ -17,4 +17,6 @@ USER www-data ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD nc -z 127.0.0.1 $APACHE_PORT || exit 1 -LABEL com.centurylinklabs.watchtower.enable="false" +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 45a4aa9d..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.14.3 +FROM elasticsearch:8.19.10 USER root @@ -14,10 +14,14 @@ RUN set -ex; \ apt-get install -y --no-install-recommends \ tzdata \ ; \ - rm -rf /var/lib/apt/lists/*; \ - elasticsearch-plugin install --batch ingest-attachment + rm -rf /var/lib/apt/lists/*; + +COPY --chmod=775 healthcheck.sh /healthcheck.sh USER 1000:0 -HEALTHCHECK CMD nc -z 127.0.0.1 9200 || exit 1 -LABEL com.centurylinklabs.watchtower.enable="false" +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/fulltextsearch/healthcheck.sh b/Containers/fulltextsearch/healthcheck.sh new file mode 100644 index 00000000..5e888ea6 --- /dev/null +++ b/Containers/fulltextsearch/healthcheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +nc -z 127.0.0.1 9200 || exit 1 diff --git a/Containers/imaginary/Dockerfile b/Containers/imaginary/Dockerfile index 8c37d694..b108ac18 100644 --- a/Containers/imaginary/Dockerfile +++ b/Containers/imaginary/Dockerfile @@ -1,9 +1,10 @@ # syntax=docker/dockerfile:latest -FROM golang:1.22.5-alpine3.20 AS go +FROM golang:1.25.6-alpine3.23 AS go -ENV IMAGINARY_HASH=6cd9edd1d3fb151eb773c14552886e4fc8e50138 +ENV IMAGINARY_HASH=6a274b488759a896aff02f52afee6e50b5e3a3ee RUN set -ex; \ + apk upgrade --no-cache -a; \ apk add --no-cache \ vips-dev \ vips-magick \ @@ -13,7 +14,7 @@ RUN set -ex; \ build-base; \ go install github.com/h2non/imaginary@"$IMAGINARY_HASH"; -FROM alpine:3.20.2 +FROM alpine:3.23.3 RUN set -ex; \ apk upgrade --no-cache -a; \ apk add --no-cache \ @@ -30,14 +31,17 @@ RUN set -ex; \ COPY --from=go /go/bin/imaginary /usr/local/bin/imaginary COPY --chmod=775 start.sh /start.sh +COPY --chmod=775 healthcheck.sh /healthcheck.sh ENV PORT=9000 -USER nobody +USER 65534 # https://github.com/h2non/imaginary#memory-issues ENV MALLOC_ARENA_MAX=2 ENTRYPOINT ["/start.sh"] -HEALTHCHECK CMD nc -z 127.0.0.1 "$PORT" || exit 1 -LABEL com.centurylinklabs.watchtower.enable="false" +HEALTHCHECK CMD /healthcheck.sh +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/imaginary/healthcheck.sh b/Containers/imaginary/healthcheck.sh new file mode 100644 index 00000000..46d700fc --- /dev/null +++ b/Containers/imaginary/healthcheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +nc -z 127.0.0.1 "$PORT" || exit 1 diff --git a/Containers/mastercontainer/Dockerfile b/Containers/mastercontainer/Dockerfile index 7fe76414..f3079ca7 100644 --- a/Containers/mastercontainer/Dockerfile +++ b/Containers/mastercontainer/Dockerfile @@ -1,20 +1,30 @@ # syntax=docker/dockerfile:latest # Docker CLI is a requirement -FROM docker:27.1.1-cli AS docker +FROM docker:29.2.0-cli AS docker # Caddy is a requirement -FROM caddy:2.8.4-alpine AS caddy +FROM caddy:2.10.2-alpine AS caddy -# From https://github.com/docker-library/php/blob/master/8.3/alpine3.20/fpm/Dockerfile -FROM php:8.3.9-fpm-alpine3.20 +# 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/supervisord.conf /supervisord.conf +COPY Containers/mastercontainer/mastercontainer.conf /etc/apache2/sites-available/mastercontainer.conf + WORKDIR /var/www/docker-aio # hadolint ignore=SC2086,DL3047,DL3003,DL3004 @@ -42,7 +52,7 @@ RUN set -ex; \ apk add --no-cache --virtual .build-deps \ autoconf \ build-base; \ - pecl install APCu-5.1.23; \ + pecl install APCu-5.1.28; \ docker-php-ext-enable apcu; \ rm -r /tmp/pear; \ runDeps="$( \ @@ -64,12 +74,11 @@ RUN set -ex; \ wget https://getcomposer.org/installer -O - | php -- --install-dir=/usr/local/bin --filename=composer; \ chmod +x /usr/local/bin/composer; \ cd /var/www/docker-aio; \ - git clone https://github.com/nextcloud-releases/all-in-one.git --depth 1 .; \ - find ./ -maxdepth 1 -mindepth 1 -not -path ./php -not -path ./community-containers -exec rm -r {} \; ; \ + 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; \ @@ -116,10 +125,10 @@ RUN set -ex; \ mkdir /var/log/supervisord; \ mkdir /var/run/supervisord; -COPY --chmod=775 *.sh / -COPY --chmod=664 Caddyfile /Caddyfile -COPY --chmod=664 supervisord.conf /supervisord.conf -COPY mastercontainer.conf /etc/apache2/sites-available/mastercontainer.conf +# 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 new file mode 100644 index 00000000..de6b535d --- /dev/null +++ b/Containers/mastercontainer/README.md @@ -0,0 +1,69 @@ +# Nextcloud All-in-One `mastercontainer` + +This folder contains the OCI/Docker container definition, along with associated resources and +configuration files, for building the `mastercontainer` as part of the Nextcloud All-in-One +project. This container hosts [the Nextcloud AIO interface]( +https://github.com/nextcloud/all-in-one/tree/main/php)[^app], and a dedicated PHP environment +for it (which is completely independent of the Nextcloud Server). + +## Overview + +The mastercontainer acts as the central orchestration service for the deployment and management +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. + - 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 + use an [ACME HTTP-01](https://letsencrypt.org/docs/challenge-types/#http-01-challenge) challenge. +- Miscellaneous support services specific to AIO (backup management, health checks, etc.) + +## Key Responsibilities + +- Orchestrates the deployment and lifecycle of all Nextcloud service containers +- Handles initial setup and container configuration +- Coordinates image updates +- Monitors general system health + +It triggers the initial installation and ensures the smooth operation of the Nextcloud +All-in-One stack. + +## Contents + +- **Dockerfile**: Instructions for building the mastercontainer image. +- **Entrypoint script**: The `start.sh` script is used for container initialization and runtime + configuration before starting supervisord. +- [**Nextcloud All-in-One Controller App**](https://github.com/nextcloud/all-in-one/tree/main/php): The + core AIO orchestrator that handles configuration and settings for the containers. +- **Supervisor**: The `supervisord.conf` file defines the long-running services hosted within + the container (php-fpm, cron, etc.) + +## Usage + +This container should be used as the trigger image when deploying the Nextcloud All-in-One +stack in a Docker or other OCI-compliant container environment. For detailed deployment +instructions, refer to the [project documentation]( +https://github.com/nextcloud/all-in-one). + +## Related Resources + +- [Main Repository](https://github.com/nextcloud/all-in-one) +- [Documentation](https://github.com/nextcloud/all-in-one#readme) + +## Contributing + +Contributions are welcome! Please follow the Nextcloud project's guidelines and submit pull +requests or issues via the main repository. + +## License + +This folder and its contents are licensed under the +[GNU AGPLv3](https://www.gnu.org/licenses/agpl-3.0.html), in line with the rest of Nextcloud +All-in-One. + +[^app]: The Nextcloud All-in-One interface allows users to install, configure, and +manage their Nextcloud instance and related containers via a secure web interface and API. +It automates and simplifies complex tasks such as container orchestration, backups, updates, +and service management for users deploying Nextcloud in Docker environments. 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 56302c80..89ef3cd5 100644 --- a/Containers/mastercontainer/daily-backup.sh +++ b/Containers/mastercontainer/daily-backup.sh @@ -2,6 +2,13 @@ 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" && ! grep -q "wasStartButtonClicked.*true" "$CONFIG_FILE"); then + echo "Initial configuration via AIO interface not done yet. Exiting..." + exit 0 +fi + # Daily backup and backup check cannot be run at the same time if [ "$DAILY_BACKUP" = 1 ] && [ "$CHECK_BACKUP" = 1 ]; then echo "Daily backup and backup check cannot be run at the same time. Exiting..." @@ -13,14 +20,19 @@ 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 - while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-apache$" && ! nc -z nextcloud-aio-apache "$APACHE_PORT"; do + # Connect mastercontainer to nextcloud-aio network to make sure that nextcloud-aio-apache is reachable + # Prevent issues like https://github.com/nextcloud/all-in-one/discussions/5222 + 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 "$LOCAL_APACHE_PORT"; do echo "Waiting for apache to become available" sleep 30 done @@ -38,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 @@ -52,17 +64,23 @@ if [ "$AUTOMATIC_UPDATES" = 1 ]; then done fi +# Update container images to reduce downtime later on +if [ "$AUTOMATIC_UPDATES" = 1 ]; then + echo "Updating container images..." + 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 @@ -75,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/mastercontainer.conf b/Containers/mastercontainer/mastercontainer.conf index 6a7d37dd..7d294694 100644 --- a/Containers/mastercontainer/mastercontainer.conf +++ b/Containers/mastercontainer/mastercontainer.conf @@ -1,5 +1,5 @@ -Listen 8000 -Listen 8080 +Listen 127.0.0.1:8000 +Listen 8080 https # Deny access to .ht files @@ -7,8 +7,8 @@ Listen 8080 # Http host - - ServerName localhost + + ServerName 127.0.0.1 # Add error log CustomLog /proc/self/fd/1 proxy diff --git a/Containers/mastercontainer/start.sh b/Containers/mastercontainer/start.sh index f4121c92..a65e29ae 100644 --- a/Containers/mastercontainer/start.sh +++ b/Containers/mastercontainer/start.sh @@ -33,17 +33,25 @@ if [ "$*" != "" ]; then fi # Check if socket is available and readable -if ! [ -a "/var/run/docker.sock" ]; then +if ! [ -e "/var/run/docker.sock" ]; then print_red "Docker socket is not available. Cannot continue." echo "Please make sure to mount the docker socket into /var/run/docker.sock inside the container!" echo "If you did this by purpose because you don't want the container to have access to the docker socket, see https://github.com/nextcloud/all-in-one/tree/main/manual-install." + echo "And https://github.com/nextcloud/all-in-one/blob/main/manual-install/latest.yml" exit 1 elif ! mountpoint -q "/mnt/docker-aio-config"; then print_red "/mnt/docker-aio-config is not a mountpoint. Cannot proceed!" echo "Please make sure to mount the nextcloud_aio_mastercontainer docker volume into /mnt/docker-aio-config inside the container!" echo "If you are on TrueNas SCALE, see https://github.com/nextcloud/all-in-one#can-i-run-aio-on-truenas-scale" exit 1 -elif ! sudo -u www-data test -r /var/run/docker.sock; then +elif mountpoint -q /var/www/docker-aio/php/containers.json; then + print_red "/var/www/docker-aio/php/containers.json is a mountpoint. Cannot proceed!" + echo "This is a not-supported customization of the mastercontainer!" + echo "Please remove this bind-mount from the mastercontainer." + 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 -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) @@ -61,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 @@ -91,27 +139,34 @@ else fi # Check Storage drivers -STORAGE_DRIVER="$(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" - echo "Warning: It seems like the storage driver vfs is used. This will lead to problems with disk space and performance and is disrecommended!" + print_red "Warning: It seems like the storage driver vfs is used. This will lead to problems with disk space and performance and is disrecommended!" elif echo "$STORAGE_DRIVER" | grep -q fuse-overlayfs; then echo "$STORAGE_DRIVER" - echo "Warning: It seems like the storage driver fuse-overlayfs is used. Please check if you can switch to overlay2 instead." + print_red "Warning: It seems like the storage driver fuse-overlayfs is used. Please check if you can switch to overlay2 instead." +fi + +# Check if snap install +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 @@ -130,7 +185,7 @@ It is set to '$NEXTCLOUD_DATADIR'." fi if [ -n "$NEXTCLOUD_MOUNT" ]; then if ! echo "$NEXTCLOUD_MOUNT" | grep -q "^/" || [ "$NEXTCLOUD_MOUNT" = "/" ]; then - print_red "You've set NEXCLOUD_MOUNT but not to an allowed value. + print_red "You've set NEXTCLOUD_MOUNT but not to an allowed value. The string must start with '/' and must not be equal to '/'. It is set to '$NEXTCLOUD_MOUNT'." exit 1 @@ -186,6 +241,14 @@ It is set to '$APACHE_IP_BINDING'." exit 1 fi fi +if [ -n "$APACHE_ADDITIONAL_NETWORK" ]; then + if ! echo "$APACHE_ADDITIONAL_NETWORK" | grep -q "^[a-zA-Z0-9._-]\+$"; then + print_red "You've set APACHE_ADDITIONAL_NETWORK but not to an allowed value. +It needs to be a string with letters, numbers, hyphens and underscores. +It is set to '$APACHE_ADDITIONAL_NETWORK'." + exit 1 + fi +fi if [ -n "$TALK_PORT" ]; then if ! check_if_number "$TALK_PORT"; then print_red "You provided an Talk port but did not only use numbers. @@ -243,28 +306,18 @@ It is set to '$NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS'." fi fi if [ -n "$AIO_COMMUNITY_CONTAINERS" ]; then - read -ra AIO_CCONTAINERS <<< "$AIO_COMMUNITY_CONTAINERS" - for container in "${AIO_CCONTAINERS[@]}"; do - if ! [ -d "/var/www/docker-aio/community-containers/$container" ]; then - print_red "The community container $container was not found!" - FAIL_CCONTAINERS=1 - fi - done - if [ -n "$FAIL_CCONTAINERS" ]; then - print_red "You've set AIO_COMMUNITY_CONTAINERS but at least one container was not found. -It is set to '$AIO_COMMUNITY_CONTAINERS'." - exit 1 - fi + print_red "You've set AIO_COMMUNITY_CONTAINERS but the option was removed. +The community containers get managed via the AIO interface now." fi -# Check DNS resolution -# Prevents issues like https://github.com/nextcloud/all-in-one/discussions/565 -curl https://nextcloud.com &>/dev/null -if [ "$?" = 6 ]; then - print_red "Could not resolve the host nextcloud.com." - echo "Most likely the DNS resolving does not work." +# Check if ghcr.io is reachable +# Solves issues like https://github.com/nextcloud/all-in-one/discussions/5268 +if ! curl --no-progress-meter https://ghcr.io/v2/ >/dev/null; then + print_red "Could not reach https://ghcr.io." + echo "Most likely is something blocking access to it." echo "You should be able to fix this by following https://dockerlabs.collabnix.com/intermediate/networking/Configuring_DNS.html" - echo "Apart from that, there has been this: https://github.com/nextcloud/all-in-one/discussions/2065" + echo "Another solution is using 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 fi @@ -275,6 +328,13 @@ if [ -n "$TZ" ]; then # Disable exit since it seems to be by default set on unraid and we dont want to break these instances # exit 1 fi +# Check that http proxy or no_proxy variable is not set which AIO does not support +if [ -n "$HTTP_PROXY" ] || [ -n "$http_proxy" ] || [ -n "$HTTPS_PROXY" ] || [ -n "$https_proxy" ] || [ -n "$NO_PROXY" ] || [ -n "$no_proxy" ]; then + print_red "The environmental variable HTTP_PROXY, http_proxy, HTTPS_PROXY, https_proxy, NO_PROXY or no_proxy has been set which is not supported by AIO." + echo "If you need this, then you should 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 +fi if mountpoint -q /etc/localtime; then print_red "/etc/localtime has been mounted into the container which is not allowed because AIO only supports running in the default Etc/UTC timezone!" echo "The correct timezone can be set in the AIO interface later on!" @@ -355,6 +415,11 @@ export TZ=Etc/UTC # Fix apache startup rm -f /var/run/apache2/httpd.pid +# Fix caddy startup +if [ -d "/mnt/docker-aio-config/caddy/locks" ]; then + rm -rf /mnt/docker-aio-config/caddy/locks/* +fi + # Fix the Caddyfile format caddy fmt --overwrite /Caddyfile @@ -362,4 +427,4 @@ caddy fmt --overwrite /Caddyfile chmod 777 /root # Start supervisord -/usr/bin/supervisord -c /supervisord.conf +exec /usr/bin/supervisord -c /supervisord.conf diff --git a/Containers/mastercontainer/supervisord.conf b/Containers/mastercontainer/supervisord.conf index f64c9725..fa5d0845 100644 --- a/Containers/mastercontainer/supervisord.conf +++ b/Containers/mastercontainer/supervisord.conf @@ -9,16 +9,16 @@ loglevel=error user=root [program:php-fpm] -# stdout_logfile=/dev/stdout -# stdout_logfile_maxbytes=0 +# Stdout logging is disabled as otherwise the logs are spammed +stdout_logfile=NONE stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 command=php-fpm user=root [program:apache] -# stdout_logfile=/dev/stdout -# stdout_logfile_maxbytes=0 +# Stdout logging is disabled as otherwise the logs are spammed +stdout_logfile=NONE stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 command=httpd -DFOREGROUND @@ -58,9 +58,7 @@ user=root [program:domain-validator] # Logging is disabled as otherwise all attempts will be logged which spams the logs -# stdout_logfile=/dev/stdout -# stdout_logfile_maxbytes=0 -# stderr_logfile=/dev/stderr -# stderr_logfile_maxbytes=0 +stdout_logfile=NONE +stderr_logfile=NONE command=php -S 127.0.0.1:9876 /var/www/docker-aio/php/domain-validator.php user=www-data diff --git a/Containers/nextcloud/Dockerfile b/Containers/nextcloud/Dockerfile index 7b74eda0..c6d9bf7e 100644 --- a/Containers/nextcloud/Dockerfile +++ b/Containers/nextcloud/Dockerfile @@ -1,22 +1,27 @@ # syntax=docker/dockerfile:latest -FROM php:8.2.21-fpm-alpine3.20 +FROM php:8.3.30-fpm-alpine3.23 ENV PHP_MEMORY_LIMIT=512M -ENV PHP_UPLOAD_LIMIT=10G +ENV PHP_UPLOAD_LIMIT=16G ENV PHP_MAX_TIME=3600 ENV SOURCE_LOCATION=/usr/src/nextcloud +ENV REDIS_DB_INDEX=0 # AIO settings start # Do not remove or change this line! -ENV NEXTCLOUD_VERSION=29.0.4 +ENV NEXTCLOUD_VERSION=32.0.5 ENV AIO_TOKEN=123456 ENV AIO_URL=localhost # AIO settings end # Do not remove or change this line! -COPY --chmod=775 *.sh / -COPY --chmod=774 upgrade.exclude /upgrade.exclude -COPY config/*.php / -COPY supervisord.conf /supervisord.conf -COPY root.motd /root.motd +COPY --chmod=775 Containers/nextcloud/*.sh / +COPY --chmod=774 Containers/nextcloud/upgrade.exclude /upgrade.exclude +COPY Containers/nextcloud/config/*.php / +COPY Containers/nextcloud/supervisord.conf /supervisord.conf + +# AIO cloning start # Do not remove or change this line! +COPY app /usr/src/nextcloud/apps/nextcloud-aio +COPY Containers/nextcloud/root.motd /root.motd +# AIO cloning end # Do not remove or change this line! VOLUME /mnt/ncdata VOLUME /var/www/html @@ -77,19 +82,18 @@ RUN set -ex; \ ; \ \ # pecl will claim success even if one install fails, so we need to perform each install separately - pecl install igbinary-3.2.15; \ - pecl install APCu-5.1.23; \ - pecl install memcached-3.2.0 \ - --configureoptions 'enable-memcached-igbinary="yes"'; \ - pecl install redis-6.0.2 \ - --configureoptions 'enable-redis-igbinary="yes" enable-redis-zstd="yes" enable-redis-lz4="yes"'; \ - pecl install imagick-3.7.0; \ + pecl install -o igbinary-3.2.16; \ + 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.3.0; \ + pecl install -o imagick-3.8.1; \ \ docker-php-ext-enable \ igbinary \ apcu \ memcached \ redis \ + imagick \ ; \ rm -r /tmp/pear; \ \ @@ -108,8 +112,9 @@ RUN set -ex; \ } >> /usr/local/etc/php/conf.d/docker-php-ext-igbinary.ini; \ \ # set recommended PHP.ini settings -# see https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/server_tuning.html#enable-php-opcache +# see https://docs.nextcloud.com/server/stable/admin_manual/installation/server_tuning.html#enable-php-opcache and below { \ + echo 'opcache.max_accelerated_files=10000'; \ echo 'opcache.memory_consumption=256'; \ echo 'opcache.interned_strings_buffer=64'; \ echo 'opcache.save_comments=1'; \ @@ -118,23 +123,27 @@ RUN set -ex; \ echo 'opcache.jit_buffer_size=8M'; \ } > /usr/local/etc/php/conf.d/opcache-recommended.ini; \ \ - echo 'apc.enable_cli=1' >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini; \ + { \ + echo 'apc.enable_cli=1'; \ + echo 'apc.shm_size=64M'; \ + } >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini; \ \ { \ echo 'memory_limit=${PHP_MEMORY_LIMIT}'; \ echo 'upload_max_filesize=${PHP_UPLOAD_LIMIT}'; \ echo 'post_max_size=${PHP_UPLOAD_LIMIT}'; \ echo 'max_execution_time=${PHP_MAX_TIME}'; \ - echo 'max_input_time=${PHP_MAX_TIME}'; \ - echo 'default_socket_timeout=600'; \ + echo 'max_input_time=-1'; \ + echo 'default_socket_timeout=${PHP_MAX_TIME}'; \ } > /usr/local/etc/php/conf.d/nextcloud.ini; \ \ { \ echo 'session.save_handler = redis'; \ - echo 'session.save_path = "tcp://${REDIS_HOST}:6379?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'; \ + echo 'session.gc_maxlifetime = 86400'; \ } > /usr/local/etc/php/conf.d/redis-session.ini; \ \ mkdir -p /var/www/data; \ @@ -148,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)"; \ @@ -208,8 +217,8 @@ RUN set -ex; \ /var/log/supervisord \ /var/run/supervisord \ ; \ - chown www-data:root -R /var/log/supervisord; \ - chown www-data:root -R /var/run/supervisord; \ + chmod 777 -R /var/log/supervisord; \ + chmod 777 -R /var/run/supervisord; \ \ apk add --no-cache \ bash \ @@ -237,33 +246,23 @@ RUN set -ex; \ sed -i 's/^pm.max_children =.*/pm.max_children = 5000/' /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; \ \ -# AIO cloning start # Do not remove or change this line! - rm -rf /tmp/nextcloud-aio && \ - mkdir -p /tmp/nextcloud-aio && \ - cd /tmp/nextcloud-aio && \ - git clone https://github.com/nextcloud-releases/all-in-one.git --depth 1 .; \ - mkdir -p /usr/src/nextcloud/apps/nextcloud-aio; \ - cp -r ./app/* /usr/src/nextcloud/apps/nextcloud-aio/; \ - echo "[ -n \"\$TERM\" ] && cat /root.motd" >> /root/.bashrc; \ -# AIO cloning end # Do not remove or change this line! + echo "[ -n \"\$TERM\" ] && [ -f /root.motd ] && cat /root.motd" >> /root/.bashrc; \ \ chown www-data:root -R /usr/src && \ - chown www-data:root -R /usr/local/etc/php/conf.d && \ - chown www-data:root -R /usr/local/etc/php-fpm.d && \ + chmod 777 -R /usr/local/etc/php/conf.d && \ + chmod 777 -R /usr/local/etc/php-fpm.d && \ chmod -R 777 /tmp; \ - rm -rf /usr/src/nextcloud/apps/updatenotification; \ + chmod -R 777 /etc/openldap; \ \ mkdir -p /nc-updater; \ - chown -R www-data:www-data /nc-updater; \ - chmod -R 770 /nc-updater; \ - \ -# Give root a random password - echo "root:$(openssl rand -base64 12)" | chpasswd + chmod -R 777 /nc-updater # hadolint ignore=DL3002 USER root ENTRYPOINT ["/start.sh"] CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"] -HEALTHCHECK --start-period=60s CMD sudo -E -u www-data bash /healthcheck.sh -LABEL com.centurylinklabs.watchtower.enable="false" +HEALTHCHECK CMD /healthcheck.sh +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/nextcloud/README.md b/Containers/nextcloud/README.md new file mode 100644 index 00000000..574afd03 --- /dev/null +++ b/Containers/nextcloud/README.md @@ -0,0 +1,35 @@ +# Nextcloud All-in-One ``nextcloud`` Container + +This folder contains the OCI/Docker container definition, along with associated resources and configuration files, for building the `nextcloud` container as part of the [Nextcloud All-in-One](https://github.com/nextcloud/all-in-one) project. This container hosts PHP and the Nextcloud Server application. + +## Overview + +The Nextcloud container provides the core Nextcloud application environment, including the necessary dependencies and configuration for seamless integration into the All-in-One stack. The container hosts: + +- The PHP SAPI/backend (php-fpm) +- Nextcloud background jobs and scheduled tasks, which are handled via cron +- Miscellaneous minor support services specific to AIO's Nextcloud deployment (health and exec) + +## Contents + +- **Dockerfile**: Instructions for building the Nextcloud container image. +- **Entrypoint script**: The `start.sh` script is used for container initialization and runtime configuration before starting supervisord. +- **Nextcloud configuration files**: Specific to running in a containerized setting and/or within AIO. +- **Supervisor**: The `supervisord.conf` file defines the long-running services hosted within the container (php-fpm, cron, etc.). + +## Usage + +This container is intended to be used as part of the All-in-One deployment and is not meant to be used on its own. Among other requirements, it needs a web server container (which AIO provides in a dedicated Apache container). It is designed to be orchestrated by the [All-in-One mastercontainer](https://github.com/nextcloud/all-in-one/tree/main/Containers/mastercontainer) or used within an [AIO Manual Installation](https://github.com/nextcloud/all-in-one/tree/main/manual-install) or [AIO Helm chart](https://github.com/nextcloud/all-in-one/tree/main/nextcloud-aio-helm-chart). + +## Documentation + +- [Nextcloud All-in-One Documentation](https://github.com/nextcloud/all-in-one#readme) +- [Nextcloud Documentation](https://docs.nextcloud.com/) + +## Contributing + +Contributions are welcome! Please follow the Nextcloud project's guidelines and submit pull requests or issues via the main repository. + +## License + +This folder and its contents are licensed under the [GNU AGPLv3](https://www.gnu.org/licenses/agpl-3.0.html), in line with the rest of Nextcloud All-in-One. diff --git a/Containers/nextcloud/config/apps.config.php b/Containers/nextcloud/config/apps.config.php index 6ccf8932..99bf5e40 100644 --- a/Containers/nextcloud/config/apps.config.php +++ b/Containers/nextcloud/config/apps.config.php @@ -13,6 +13,9 @@ $CONFIG = array ( ), ), ); -if (getenv('APPS_ALLOWLIST') !== false) { +if (getenv('APPS_ALLOWLIST')) { $CONFIG['appsallowlist'] = explode(" ", getenv('APPS_ALLOWLIST')); } +if (getenv('NEXTCLOUD_APP_STORE_URL')) { + $CONFIG['appstoreurl'] = getenv('NEXTCLOUD_APP_STORE_URL'); +} 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/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/proxy.config.php b/Containers/nextcloud/config/proxy.config.php new file mode 100644 index 00000000..c283f86e --- /dev/null +++ b/Containers/nextcloud/config/proxy.config.php @@ -0,0 +1,13 @@ + 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') ?: '', - 'sse_c_key' => getenv('OBJECTSTORE_S3_SSE_C_KEY') ?: '', 'region' => getenv('OBJECTSTORE_S3_REGION') ?: '', 'hostname' => getenv('OBJECTSTORE_S3_HOST') ?: '', 'port' => getenv('OBJECTSTORE_S3_PORT') ?: '', 'storageClass' => getenv('OBJECTSTORE_S3_STORAGE_CLASS') ?: '', 'objectPrefix' => getenv("OBJECTSTORE_S3_OBJECT_PREFIX") ? getenv("OBJECTSTORE_S3_OBJECT_PREFIX") : "urn:oid:", - 'autocreate' => (strtolower($autocreate) === 'false' || $autocreate == false) ? false : true, - 'use_ssl' => (strtolower($use_ssl) === 'false' || $use_ssl == false) ? false : true, + 'autocreate' => strtolower($autocreate) !== 'false', + 'use_ssl' => strtolower($use_ssl) !== 'false', // required for some non Amazon S3 implementations - 'use_path_style' => $use_path == true && strtolower($use_path) !== 'false', + 'use_path_style' => strtolower($use_path) === 'true', // required for older protocol versions - 'legacy_auth' => $use_legacyauth == true && strtolower($use_legacyauth) !== 'false' + 'legacy_auth' => strtolower($use_legacyauth) === 'true', + 'use_nextcloud_bundle' => 1, ) ) ); -} + + $sse_c_key = getenv('OBJECTSTORE_S3_SSE_C_KEY'); + if ($sse_c_key) { + $CONFIG['objectstore']['arguments']['sse_c_key'] = $sse_c_key; + } +} 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 fd8fc382..d4b4f253 100644 --- a/Containers/nextcloud/entrypoint.sh +++ b/Containers/nextcloud/entrypoint.sh @@ -11,39 +11,106 @@ directory_empty() { } run_upgrade_if_needed_due_to_app_update() { + if php /var/www/html/occ status | grep maintenance | grep -q true; then + php /var/www/html/occ maintenance:mode --off + fi if php /var/www/html/occ status | grep needsDbUpgrade | grep -q true; then - # Disable integrity check temporarily until next update - php /var/www/html/occ config:system:set integrity.check.disabled --type bool --value true php /var/www/html/occ upgrade php /var/www/html/occ app:enable nextcloud-aio --force fi } -# Only start container if redis is accessible +# 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 +fi + +# Only start container if Redis is accessible # shellcheck disable=SC2153 -while ! nc -z "$REDIS_HOST" "6379"; do - echo "Waiting for redis to start..." +while ! nc -z "$REDIS_HOST" "$REDIS_PORT"; do + echo "Waiting for Redis to start..." sleep 5 done # Check permissions in ncdata -touch "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" &>/dev/null -if ! [ -f "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" ]; then - echo "The www-data user doesn't seem to have access rights in the datadir. -Most likely are the files located on a drive that does not follow linux permissions. -Please adjust the permissions like mentioned below. -The found permissions are: -$(stat -c "%u:%g %a" "$NEXTCLOUD_DATA_DIR") -(userID:groupID permissions) -but they should be: -33:0 750 -(userID:groupID permissions) -Also make sure that the parent directories on the host of the directory that you've chosen as datadir are publicly readable with e.g. 'sudo chmod +r /mnt' (adjust the command accordingly to your case) and the same for all subdirectories. -Additionally, if you want to use a Fuse-mount as datadir, set 'allow_other' as additional mount option. -For SMB/CIFS mounts as datadir, see https://github.com/nextcloud/all-in-one#can-i-use-a-cifssmb-share-as-nextclouds-datadir" +test_file="$NEXTCLOUD_DATA_DIR/this-is-a-test-file" +touch "$test_file" +if ! [ -f "$test_file" ]; then + echo "The www-data user does not appear to have access rights to the data directory." + echo "It is possible that the files are on a filesystem that does not support standard Linux permissions," + echo "or the permissions simply need to be adjusted. Please change the permissions as described below." + echo "Current permissions are:" + stat -c "%u:%g %a" "$NEXTCLOUD_DATA_DIR" + echo "(userID:groupID permissions)" + echo "They should be:" + echo "33:0 750" + echo "(userID:groupID permissions)" + echo "Also, ensure that all parent directories on the host of your chosen data directory are publicly readable." + echo "For example: sudo chmod +r /mnt (adjust this command as needed)." + echo "If you want to use a FUSE mount as the data directory, add 'allow_other' as an additional mount option." + echo "For SMB/CIFS mounts as the data directory, see:" + echo " https://github.com/nextcloud/all-in-one#can-i-use-a-cifssmb-share-as-nextclouds-datadir" exit 1 fi -rm "$NEXTCLOUD_DATA_DIR/this-is-a-test-file" +rm -f "$test_file" if [ -f /var/www/html/version.php ]; then # shellcheck disable=SC2016 @@ -65,26 +132,31 @@ fi # Don't start the container if Nextcloud is not compatible with the PHP version if [ -f "/var/www/html/lib/versioncheck.php" ] && ! php /var/www/html/lib/versioncheck.php; then - echo "It seems like your installed Nextcloud is not compatible with the by the container provided PHP version." - echo "This most likely happened because you tried to restore an old Nextcloud version from backup that is not compatible with the PHP version that comes with the container." - echo "Please try to restore a more recent backup which contains a Nextcloud version that is compatible with the PHP version that comes with the container." - echo "If you do not have a more recent backup, feel free to have a look at this documentation: https://github.com/nextcloud/all-in-one/blob/main/manual-upgrade.md" + echo "Your installed Nextcloud version is not compatible with the PHP version provided by this image." + echo "This typically occurs when you restore an older Nextcloud backup that does not support the" + echo "PHP version included in this image." + echo "Please restore a more recent backup that includes a compatible Nextcloud version." + echo "If you do not have a more recent backup, refer to the manual upgrade documentation:" + echo " https://github.com/nextcloud/all-in-one/blob/main/manual-upgrade.md" exit 1 fi # Do not start the container if the last update failed if [ -f "$NEXTCLOUD_DATA_DIR/update.failed" ]; then echo "The last Nextcloud update failed." - echo "Please restore from backup and try again!" - echo "If you do not have a backup in place, you can simply delete the update.failed file in the datadir which will allow the container to start again." + echo "Please restore from a backup and try again." + echo "If you do not have a backup, you can delete the update.failed file in the data directory" + echo "to allow the container to start again." exit 1 fi # Do not start the container if the install failed if [ -f "$NEXTCLOUD_DATA_DIR/install.failed" ]; then echo "The initial Nextcloud installation failed." - echo "Please reset AIO properly and try again. For further clues what went wrong, check the logs above." - echo "See https://github.com/nextcloud/all-in-one#how-to-properly-reset-the-instance" + echo "For more information about what went wrong, check the logs above." + echo "Please reset AIO properly and try again." + echo "See:" + echo " https://github.com/nextcloud/all-in-one#how-to-properly-reset-the-instance" exit 1 fi @@ -110,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/ @@ -128,45 +203,54 @@ if ! [ -f "$NEXTCLOUD_DATA_DIR/skip.update" ]; then rm -r /usr/src/tmp rm -r /usr/src/temp-nextcloud # shellcheck disable=SC2016 - image_version="$(php -r "require $SOURCE_LOCATION/version.php; echo implode('.', \$OC_Version);")" + image_version="$(php -r "require '$SOURCE_LOCATION/version.php'; echo implode('.', \$OC_Version);")" IMAGE_MAJOR="${image_version%%.*}" set +ex # Do not skip major versions end # Do not remove or change this line! fi if [ "$installed_version" != "0.0.0.0" ]; then +# Check connection to appstore start # Do not remove or change this line! while true; do - echo -e "Checking connection to appstore" - CURL_STATUS="$(curl -LI "https://apps.nextcloud.com/" -o /dev/null -w '%{http_code}\n' -s)" + echo -e "Checking connection to the app store..." + APPSTORE_URL="https://apps.nextcloud.com/api/v1" + if grep -q appstoreurl /var/www/html/config/config.php; then + set -x + APPSTORE_URL="$(grep appstoreurl /var/www/html/config/config.php | grep -oP 'https://.*v[0-9]+')" + set +x + fi + # Default appstoreurl parameter in config.php defaults to 'https://apps.nextcloud.com/api/v1' so we check for the apps.json file stored in there + CURL_STATUS="$(curl -LI "$APPSTORE_URL"/apps.json -o /dev/null -w '%{http_code}\n' -s)" if [[ "$CURL_STATUS" = "200" ]] then - echo "Appstore is reachable" + echo "App store is reachable." break else - echo "Curl didn't produce a 200 status, is appstore reachable?" + echo "Curl did not return a 200 status. Is the app store reachable?" sleep 5 fi done +# Check connection to appstore end # Do not remove or change this line! run_upgrade_if_needed_due_to_app_update php /var/www/html/occ maintenance:mode --off - echo "Getting and backing up the status of apps for later, this might take a while..." + echo "Getting and backing up the status of apps for later; this might take a while..." NC_APPS="$(find /var/www/html/custom_apps/ -type d -maxdepth 1 -mindepth 1 | sed 's|/var/www/html/custom_apps/||g')" if [ -z "$NC_APPS" ]; then - echo "No apps detected, aborting export of app status..." + echo "No apps detected. Aborting export of app status..." APPSTORAGE="no-export-done" else mapfile -t NC_APPS_ARRAY <<< "$NC_APPS" declare -Ag APPSTORAGE - echo "Disabling apps before the update in order to make the update procedure more safe. This can take a while..." + echo "Disabling apps before the update to make the update procedure safer. This can take a while..." for app in "${NC_APPS_ARRAY[@]}"; do if APPSTORAGE[$app]="$(php /var/www/html/occ config:app:get "$app" enabled)"; then php /var/www/html/occ app:disable "$app" else APPSTORAGE[$app]="" - echo "Not disabling $app because the occ command to get the enabled state was failing." + echo "Not disabling $app because the occ command to get its enabled state failed." fi done fi @@ -178,29 +262,63 @@ if ! [ -f "$NEXTCLOUD_DATA_DIR/skip.update" ]; then php /var/www/html/occ app:update --all run_upgrade_if_needed_due_to_app_update - - # Fix removing the updatenotification for old instances - UPDATENOTIFICATION_STATUS="$(php /var/www/html/occ config:app:get updatenotification enabled)" - if [ -d "/var/www/html/apps/updatenotification" ]; then - php /var/www/html/occ app:disable updatenotification - elif [ "$UPDATENOTIFICATION_STATUS" != "no" ] && [ -n "$UPDATENOTIFICATION_STATUS" ]; then - php /var/www/html/occ config:app:set updatenotification enabled --value="no" - fi fi - echo "Initializing nextcloud $image_version ..." - rsync -rlD --delete --exclude-from=/upgrade.exclude "$SOURCE_LOCATION/" /var/www/html/ + echo "Initializing Nextcloud $image_version ..." + # Copy over initial data from Nextcloud archive + rsync -rlD --delete \ + --exclude-from=/upgrade.exclude \ + "$SOURCE_LOCATION/" \ + /var/www/html/ + + # Copy custom_apps from Nextcloud archive + if ! directory_empty "$SOURCE_LOCATION/custom_apps"; then + set -x + for app in "$SOURCE_LOCATION/custom_apps"/*; do + app_id="$(basename "$app")" + mkdir -p "/var/www/html/custom_apps/$app_id" + rsync -rlD --delete \ + --include "/$app_id/" \ + --exclude '/*' \ + "$SOURCE_LOCATION/custom_apps/" \ + /var/www/html/custom_apps/ + done + set +x + fi + + # Copy these from Nextcloud archive if they don't exist yet (i.e. new install) for dir in config data custom_apps themes; do if [ ! -d "/var/www/html/$dir" ] || directory_empty "/var/www/html/$dir"; then - rsync -rlD --include "/$dir/" --exclude '/*' "$SOURCE_LOCATION/" /var/www/html/ + rsync -rlD \ + --include "/$dir/" \ + --exclude '/*' \ + "$SOURCE_LOCATION/" \ + /var/www/html/ fi done - rsync -rlD --delete --include '/config/' --exclude '/*' --exclude '/config/CAN_INSTALL' --exclude '/config/config.sample.php' --exclude '/config/config.php' "$SOURCE_LOCATION/" /var/www/html/ - rsync -rlD --include '/version.php' --exclude '/*' "$SOURCE_LOCATION/" /var/www/html/ + + rsync -rlD --delete \ + --include '/config/' \ + --exclude '/*' \ + --exclude '/config/CAN_INSTALL' \ + --exclude '/config/config.sample.php' \ + --exclude '/config/config.php' \ + "$SOURCE_LOCATION/" \ + /var/www/html/ + + rsync -rlD \ + --include '/version.php' \ + --exclude '/*' \ + "$SOURCE_LOCATION/" \ + /var/www/html/ + echo "Initializing finished" - #install + ################ + # Fresh Install + ################ + if [ "$installed_version" = "0.0.0.0" ]; then echo "New Nextcloud instance." @@ -214,21 +332,31 @@ if ! [ -f "$NEXTCLOUD_DATA_DIR/skip.update" ]; then INSTALL_OPTIONS+=(--data-dir "$NEXTCLOUD_DATA_DIR") fi - # We do our own permission check so the permission check is not needed - cat << DATADIR_PERMISSION_CONF > /var/www/html/config/datadir.permission.config.php + # Skip the default permission check (we do our own) + cat > /var/www/html/config/datadir.permission.config.php <<'EOF' false -); -DATADIR_PERMISSION_CONF + $CONFIG = array ( + 'check_data_directory_permissions' => false + ); +EOF - echo "Installing with PostgreSQL database" + echo "Installing with $DATABASE_TYPE database" # Set a default value for POSTGRES_PORT if [ -z "$POSTGRES_PORT" ]; then - POSTGRES_PORT=5432 + POSTGRES_PORT=5432 fi - INSTALL_OPTIONS+=(--database pgsql --database-name "$POSTGRES_DB" --database-user "$POSTGRES_USER" --database-pass "$POSTGRES_PASSWORD" --database-host "$POSTGRES_HOST" --database-port "$POSTGRES_PORT") + # Add database options to INSTALL_OPTIONS + # shellcheck disable=SC2153 + INSTALL_OPTIONS+=( + --database "$DATABASE_TYPE" + --database-name "$POSTGRES_DB" + --database-user "$POSTGRES_USER" + --database-pass "$POSTGRES_PASSWORD" + --database-host "$POSTGRES_HOST" + --database-port "$POSTGRES_PORT" + ) + echo "Starting Nextcloud installation..." if ! php /var/www/html/occ maintenance:install "${INSTALL_OPTIONS[@]}"; then echo "Installation of Nextcloud failed!" @@ -250,7 +378,7 @@ DATADIR_PERMISSION_CONF if [ "$try" -ge "$max_retries" ]; then echo "Installation of Nextcloud failed!" - echo "Install errors: $(cat /var/www/html/data/nextcloud.log)" + echo "Installation errors: $(cat /var/www/html/data/nextcloud.log)" touch "$NEXTCLOUD_DATA_DIR/install.failed" exit 1 fi @@ -262,6 +390,10 @@ DATADIR_PERMISSION_CONF # unset admin password unset ADMIN_PASSWORD + # Enable the updatenotification app but disable its UI and server update notifications + php /var/www/html/occ config:system:set updatechecker --type=bool --value=false + php /var/www/html/occ config:app:set updatenotification notify_groups --value="[]" + # AIO update to latest start # Do not remove or change this line! if [ "$INSTALL_LATEST_MAJOR" = yes ]; then php /var/www/html/occ config:system:set updatedirectory --value="/nc-updater" @@ -282,18 +414,19 @@ DATADIR_PERMISSION_CONF installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" INSTALLED_MAJOR="${installed_version%%.*}" IMAGE_MAJOR="${image_version%%.*}" + # If a valid upgrade path, trigger the Nextcloud built-in Updater if ! [ "$INSTALLED_MAJOR" -gt "$IMAGE_MAJOR" ]; then php /var/www/html/updater/updater.phar --no-interaction --no-backup if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then echo "Installation of Nextcloud failed!" + # TODO: Add a hint here about what to do / where to look / updater.log? touch "$NEXTCLOUD_DATA_DIR/install.failed" exit 1 fi # shellcheck disable=SC2016 installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" fi - php /var/www/html/occ app:disable updatenotification - rm -rf /var/www/html/apps/updatenotification + php /var/www/html/occ config:system:set updatechecker --type=bool --value=true php /var/www/html/occ app:enable nextcloud-aio --force php /var/www/html/occ db:add-missing-columns php /var/www/html/occ db:add-missing-primary-keys @@ -339,15 +472,13 @@ DATADIR_PERMISSION_CONF php /var/www/html/occ config:system:set activity_expire_days --value="30" --type=integer php /var/www/html/occ config:system:set simpleSignUpLink.shown --type=bool --value=false php /var/www/html/occ config:system:set share_folder --value="/Shared" - # Not needed anymore with the removal of the updatenotification app: - # php /var/www/html/occ config:app:set updatenotification notify_groups --value="[]" # Install some apps by default if [ -n "$STARTUP_APPS" ]; then read -ra STARTUP_APPS_ARRAY <<< "$STARTUP_APPS" for app in "${STARTUP_APPS_ARRAY[@]}"; do if ! echo "$app" | grep -q '^-'; then - if [ -z "$(find /var/www/html/apps -type d -maxdepth 1 -mindepth 1 -name "$app" )" ]; then + if [ -z "$(find /var/www/html/apps /var/www/html/custom_apps -type d -maxdepth 1 -mindepth 1 -name "$app" )" ]; then # If not shipped, install and enable the app php /var/www/html/occ app:install "$app" else @@ -365,11 +496,11 @@ DATADIR_PERMISSION_CONF #upgrade else touch "$NEXTCLOUD_DATA_DIR/update.failed" - echo "Upgrading nextcloud from $installed_version to $image_version..." + echo "Upgrading Nextcloud from $installed_version to $image_version..." php /var/www/html/occ config:system:delete integrity.check.disabled if ! php /var/www/html/occ upgrade || ! php /var/www/html/occ -V; then echo "Upgrade failed. Please restore from backup." - bash /notify.sh "Nextcloud update to $image_version failed!" "Please restore from backup!" + bash /notify.sh "Nextcloud update to $image_version failed!" "Please restore from backup." exit 1 fi @@ -377,7 +508,7 @@ DATADIR_PERMISSION_CONF installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" rm "$NEXTCLOUD_DATA_DIR/update.failed" - bash /notify.sh "Nextcloud update to $image_version successful!" "Feel free to inspect the Nextcloud container logs for more info." + bash /notify.sh "Nextcloud update to $image_version successful!" "You may inspect the Nextcloud container logs for more information." php /var/www/html/occ app:update --all @@ -385,7 +516,7 @@ DATADIR_PERMISSION_CONF # Restore app status if [ "${APPSTORAGE[0]}" != "no-export-done" ]; then - echo "Restoring the status of apps. This can take a while..." + echo "Restoring app statuses. This may take a while..." for app in "${!APPSTORAGE[@]}"; do if [ -n "${APPSTORAGE[$app]}" ]; then if [ "${APPSTORAGE[$app]}" != "no" ]; then @@ -397,13 +528,13 @@ DATADIR_PERMISSION_CONF php /var/www/html/occ maintenance:mode --off fi run_upgrade_if_needed_due_to_app_update - echo "The $app app could not get enabled. Probably because it is not compatible with the new Nextcloud version." + echo "The $app app could not be re-enabled, probably because it is not compatible with the new Nextcloud version." if [ "$app" = apporder ]; then CUSTOM_HINT="The apporder app was deprecated. A possible replacement is the side_menu app, aka 'Custom menu'." else - CUSTOM_HINT="Most likely because it is not compatible with the new Nextcloud version." + CUSTOM_HINT="Most likely, it is not compatible with the new Nextcloud version." fi - bash /notify.sh "Could not enable the $app app after the Nextcloud update!" "$CUSTOM_HINT Feel free to look at the Nextcloud update logs and force-enable the app again from the app-store UI." + bash /notify.sh "Could not re-enable the $app app after the Nextcloud update!" "$CUSTOM_HINT Feel free to review the Nextcloud update logs and force-enable the app again if you wish." continue fi # Only restore the group settings, if the app was enabled (and is thus compatible with the new NC version) @@ -419,14 +550,21 @@ DATADIR_PERMISSION_CONF run_upgrade_if_needed_due_to_app_update + # Enable the updatenotification app but disable its UI and server update notifications + php /var/www/html/occ config:system:set updatechecker --type=bool --value=false + php /var/www/html/occ app:enable updatenotification + php /var/www/html/occ config:app:set updatenotification notify_groups --value="[]" + # Apply optimization - echo "Doing some optimizations..." - php /var/www/html/occ maintenance:repair + echo "Performing some optimizations..." if [ "$NEXTCLOUD_SKIP_DATABASE_OPTIMIZATION" != yes ]; then + php /var/www/html/occ maintenance:repair --include-expensive php /var/www/html/occ db:add-missing-indices php /var/www/html/occ db:add-missing-columns php /var/www/html/occ db:add-missing-primary-keys yes | php /var/www/html/occ db:convert-filecache-bigint + else + php /var/www/html/occ maintenance:repair fi fi fi @@ -449,10 +587,10 @@ if [ -z "$OBJECTSTORE_S3_BUCKET" ] && [ -z "$OBJECTSTORE_SWIFT_URL" ]; then # Check if appdata is present # If not, something broke (e.g. changing ncdatadir after aio was first started) if [ -z "$(find "$NEXTCLOUD_DATA_DIR/" -maxdepth 1 -mindepth 1 -type d -name "appdata_*")" ]; then - echo "Appdata is not present. Did you maybe change the datadir after the initial Nextcloud installation? This is not supported!" + echo "Appdata is not present. Did you change the datadir after the initial Nextcloud installation? This is not supported!" echo "See https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir" - echo "If you adjusted the datadir to be located on an external drive, make sure that the drive is still mounted!" - echo "In the datadir was found:" + echo "If you moved the datadir to an external drive, make sure that the drive is still mounted." + echo "The following was found in the datadir:" ls -la "$NEXTCLOUD_DATA_DIR/" exit 1 fi @@ -473,6 +611,12 @@ if [ -f "$NEXTCLOUD_DATA_DIR/fingerprint.update" ]; then rm "$NEXTCLOUD_DATA_DIR/fingerprint.update" fi +# Perform preview scan if previews were excluded from restore +if [ -f "$NEXTCLOUD_DATA_DIR/trigger-preview.scan" ]; then + php /var/www/html/occ files:scan-app-data preview -vvv + rm "$NEXTCLOUD_DATA_DIR/trigger-preview.scan" +fi + # AIO one-click settings start # Do not remove or change this line! # Apply one-click-instance settings echo "Applying one-click-instance settings..." @@ -499,6 +643,13 @@ php /var/www/html/occ config:system:set upgrade.cli-upgrade-link --value="https: php /var/www/html/occ config:system:set logfile --value="/var/www/html/data/nextcloud.log" php /var/www/html/occ config:app:set admin_audit logfile --value="/var/www/html/data/audit.log" php /var/www/html/occ config:system:set updatedirectory --value="/nc-updater" +if [ -n "$NEXTCLOUD_SKELETON_DIRECTORY" ]; then + if [ "$NEXTCLOUD_SKELETON_DIRECTORY" = "empty" ]; then + php /var/www/html/occ config:system:set skeletondirectory --value="" + else + php /var/www/html/occ config:system:set skeletondirectory --value="$NEXTCLOUD_SKELETON_DIRECTORY" + fi +fi if [ -n "$SERVERINFO_TOKEN" ] && [ -z "$(php /var/www/html/occ config:app:get serverinfo token)" ]; then php /var/www/html/occ config:app:set serverinfo token --value="$SERVERINFO_TOKEN" fi @@ -514,6 +665,7 @@ php /var/www/html/occ config:system:set allow_local_remote_servers --type=bool - php /var/www/html/occ config:system:set davstorage.request_timeout --value="$PHP_MAX_TIME" --type=int php /var/www/html/occ config:system:set trusted_domains 1 --value="$NC_DOMAIN" php /var/www/html/occ config:system:set overwrite.cli.url --value="https://$NC_DOMAIN/" +php /var/www/html/occ config:system:set documentation_url.server_logs --value="https://github.com/nextcloud/all-in-one/discussions/5425" php /var/www/html/occ config:system:set htaccess.RewriteBase --value="/" php /var/www/html/occ maintenance:update:htaccess @@ -563,12 +715,17 @@ if [ -n "$ADDITIONAL_TRUSTED_PROXY" ]; then php /var/www/html/occ config:system:set trusted_proxies 2 --value="$ADDITIONAL_TRUSTED_PROXY" fi -# Get ipv4-address of Nextcloud -IPv4_ADDRESS="$(dig nextcloud-aio-nextcloud A +short +search | head -1)" +# Get ipv4-address of Nextcloud +if [ -z "$NEXTCLOUD_HOST" ]; then + export NEXTCLOUD_HOST="nextcloud-aio-nextcloud" +fi +IPv4_ADDRESS="$(dig "$NEXTCLOUD_HOST" A +short +search | head -1)" # Bring it in CIDR notation # shellcheck disable=SC2001 IPv4_ADDRESS="$(echo "$IPv4_ADDRESS" | sed 's|[0-9]\+$|0/16|')" -php /var/www/html/occ config:system:set trusted_proxies 10 --value="$IPv4_ADDRESS" +if [ -n "$IPv4_ADDRESS" ]; then + php /var/www/html/occ config:system:set trusted_proxies 10 --value="$IPv4_ADDRESS" +fi if [ -n "$ADDITIONAL_TRUSTED_DOMAIN" ]; then php /var/www/html/occ config:system:set trusted_domains 2 --value="$ADDITIONAL_TRUSTED_DOMAIN" @@ -582,6 +739,10 @@ if [ "$COLLABORA_ENABLED" = 'yes' ]; then COLLABORA_HOST="$NC_DOMAIN" fi set +x + # Remove richdcoumentscode if it should be incorrectly installed + if [ -d "/var/www/html/custom_apps/richdocumentscode" ]; then + php /var/www/html/occ app:remove richdocumentscode + fi if ! [ -d "/var/www/html/custom_apps/richdocuments" ]; then php /var/www/html/occ app:install richdocuments elif [ "$(php /var/www/html/occ config:app:get richdocuments enabled)" != "yes" ]; then @@ -603,7 +764,7 @@ if [ "$COLLABORA_ENABLED" = 'yes' ]; then fi fi else - echo "Warning: No ipv4-address found for $COLLABORA_HOST." + echo "Warning: No IPv4 address found for $COLLABORA_HOST." fi if [ -n "$COLLABORA_IPv6_ADDRESS" ]; then if ! echo "$COLLABORA_ALLOW_LIST" | grep -q "$COLLABORA_IPv6_ADDRESS"; then @@ -614,10 +775,10 @@ if [ "$COLLABORA_ENABLED" = 'yes' ]; then fi fi else - echo "No ipv6-address found for $COLLABORA_HOST." + 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 @@ -628,7 +789,7 @@ if [ "$COLLABORA_ENABLED" = 'yes' ]; then fi php /var/www/html/occ config:app:set richdocuments wopi_allowlist --value="$COLLABORA_ALLOW_LIST" else - echo "Warning: wopi_allowlist is empty which should not be the case!" + echo "Warning: wopi_allowlist is empty; this should not be the case!" fi else if [ "$REMOVE_DISABLED_APPS" = yes ] && [ -d "/var/www/html/custom_apps/richdocuments" ]; then @@ -638,23 +799,52 @@ fi # OnlyOffice if [ "$ONLYOFFICE_ENABLED" = 'yes' ]; then - while ! nc -z "$ONLYOFFICE_HOST" 80; do - echo "waiting for OnlyOffice to become available..." + # Determine OnlyOffice port based on host pattern + if echo "$ONLYOFFICE_HOST" | grep -q "nextcloud-.*-onlyoffice"; then + ONLYOFFICE_PORT=80 + else + ONLYOFFICE_PORT=443 + fi + + 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 ! [ -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 + 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 + + # 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 - 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" - php /var/www/html/occ config:app:set onlyoffice DocumentServerUrl --value="https://$NC_DOMAIN/onlyoffice" else - if [ "$REMOVE_DISABLED_APPS" = yes ] && [ -d "/var/www/html/custom_apps/onlyoffice" ] && [ -n "$ONLYOFFICE_SECRET" ] && [ "$(php /var/www/html/occ config:system:get onlyoffice jwt_secret)" = "$ONLYOFFICE_SECRET" ]; then + # Remove OnlyOffice app if disabled and removal is requested + if [ "$REMOVE_DISABLED_APPS" = yes ] && \ + [ -d "/var/www/html/custom_apps/onlyoffice" ] && \ + [ -n "$ONLYOFFICE_SECRET" ] && \ + [ "$(php /var/www/html/occ config:system:get onlyoffice jwt_secret)" = "$ONLYOFFICE_SECRET" ]; then php /var/www/html/occ app:remove onlyoffice fi fi @@ -700,14 +890,16 @@ fi if [ -d "/var/www/html/custom_apps/spreed" ]; then if [ "$TALK_RECORDING_ENABLED" = 'yes' ]; then while ! nc -z "$TALK_RECORDING_HOST" 1234; do - echo "waiting for Talk Recording to become available..." + echo "Waiting for Talk Recording to become available..." sleep 5 done # TODO: migrate to occ command if that becomes available 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 @@ -715,12 +907,12 @@ fi if [ "$CLAMAV_ENABLED" = 'yes' ]; then count=0 while ! nc -z "$CLAMAV_HOST" 3310 && [ "$count" -lt 90 ]; do - echo "waiting for clamav to become available..." + echo "Waiting for ClamAV to become available..." count=$((count+5)) 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 @@ -733,9 +925,13 @@ 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" + fi fi else if [ "$REMOVE_DISABLED_APPS" = yes ] && [ -d "/var/www/html/custom_apps/files_antivirus" ]; then @@ -746,6 +942,7 @@ fi # Imaginary if [ "$IMAGINARY_ENABLED" = 'yes' ]; then php /var/www/html/occ config:system:set enabledPreviewProviders 0 --value="OC\\Preview\\Imaginary" + php /var/www/html/occ config:system:set enabledPreviewProviders 23 --value="OC\\Preview\\ImaginaryPDF" php /var/www/html/occ config:system:set preview_imaginary_url --value="http://$IMAGINARY_HOST:9000" php /var/www/html/occ config:system:set preview_imaginary_key --value="$IMAGINARY_SECRET" else @@ -755,50 +952,63 @@ else php /var/www/html/occ config:system:delete enabledPreviewProviders 20 php /var/www/html/occ config:system:delete enabledPreviewProviders 21 php /var/www/html/occ config:system:delete enabledPreviewProviders 22 + php /var/www/html/occ config:system:delete enabledPreviewProviders 23 fi fi # Fulltextsearch if [ "$FULLTEXTSEARCH_ENABLED" = 'yes' ]; then - while ! nc -z "$FULLTEXTSEARCH_HOST" 9200; do - echo "waiting for Fulltextsearch to become available..." + count=0 + while ! nc -z "$FULLTEXTSEARCH_HOST" "$FULLTEXTSEARCH_PORT" && [ "$count" -lt 90 ]; do + echo "Waiting for Fulltextsearch to become available..." + count=$((count+5)) sleep 5 done - 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 - php /var/www/html/occ app:enable fulltextsearch - elif [ "$SKIP_UPDATE" != 1 ]; then - php /var/www/html/occ app:update fulltextsearch - fi - if ! [ -d "/var/www/html/custom_apps/fulltextsearch_elasticsearch" ]; then - php /var/www/html/occ app:install fulltextsearch_elasticsearch - elif [ "$(php /var/www/html/occ config:app:get fulltextsearch_elasticsearch enabled)" != "yes" ]; then - php /var/www/html/occ app:enable fulltextsearch_elasticsearch - elif [ "$SKIP_UPDATE" != 1 ]; then - php /var/www/html/occ app:update fulltextsearch_elasticsearch - fi - if ! [ -d "/var/www/html/custom_apps/files_fulltextsearch" ]; then - php /var/www/html/occ app:install files_fulltextsearch - elif [ "$(php /var/www/html/occ config:app:get files_fulltextsearch enabled)" != "yes" ]; then - php /var/www/html/occ app:enable files_fulltextsearch - elif [ "$SKIP_UPDATE" != 1 ]; 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://elastic:$FULLTEXTSEARCH_PASSWORD@$FULLTEXTSEARCH_HOST:9200\",\"elastic_index\":\"nextcloud-aio\"}" - php /var/www/html/occ files_fulltextsearch:configure "{\"files_pdf\":\"1\",\"files_office\":\"1\"}" + if [ "$count" -ge 90 ]; then + echo "Fulltextsearch did not start in time. Skipping initialization and disabling fulltextsearch apps." + php /var/www/html/occ app:disable fulltextsearch + 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 + php /var/www/html/occ app:enable fulltextsearch + elif [ "$SKIP_UPDATE" != 1 ]; then + php /var/www/html/occ app:update fulltextsearch + fi + if ! [ -d "/var/www/html/custom_apps/fulltextsearch_elasticsearch" ]; then + php /var/www/html/occ app:install fulltextsearch_elasticsearch + elif [ "$(php /var/www/html/occ config:app:get fulltextsearch_elasticsearch enabled)" != "yes" ]; then + php /var/www/html/occ app:enable fulltextsearch_elasticsearch + elif [ "$SKIP_UPDATE" != 1 ]; then + php /var/www/html/occ app:update fulltextsearch_elasticsearch + fi + if ! [ -d "/var/www/html/custom_apps/files_fulltextsearch" ]; then + php /var/www/html/occ app:install files_fulltextsearch + elif [ "$(php /var/www/html/occ config:app:get files_fulltextsearch enabled)" != "yes" ]; then + php /var/www/html/occ app:enable files_fulltextsearch + elif [ "$SKIP_UPDATE" != 1 ]; 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\":\"$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 - echo "Waiting 10s before activating FTS..." - sleep 10 - echo "Activating fulltextsearch..." - if php /var/www/html/occ fulltextsearch:test && php /var/www/html/occ fulltextsearch:index "{\"errors\": \"reset\"}" --no-readline; then - touch "$NEXTCLOUD_DATA_DIR/fts-index.done" - else - echo "Fulltextsearch failed. Could not index." - echo "Feel free to follow https://github.com/nextcloud/all-in-one/discussions/1709 if you want to skip the indexing in the future." + # Do the index + if ! [ -f "$NEXTCLOUD_DATA_DIR/fts-index.done" ]; then + echo "Waiting 10 seconds before activating fulltextsearch..." + sleep 10 + echo "Activating fulltextsearch..." + if php /var/www/html/occ fulltextsearch:test && php /var/www/html/occ fulltextsearch:index "{\"errors\": \"reset\"}" --no-readline; then + touch "$NEXTCLOUD_DATA_DIR/fts-index.done" + else + echo "Fulltextsearch failed. Could not index." + echo "If you want to skip indexing in the future, see https://github.com/nextcloud/all-in-one/discussions/1709" + fi fi fi else @@ -816,21 +1026,39 @@ else fi # Docker socket proxy -if version_greater "$installed_version" "27.1.2.0"; then - if [ "$DOCKER_SOCKET_PROXY_ENABLED" = 'yes' ]; then - if ! [ -d "/var/www/html/custom_apps/app_api" ]; then - php /var/www/html/occ app:install app_api - elif [ "$(php /var/www/html/occ config:app:get app_api enabled)" != "yes" ]; then - php /var/www/html/occ app:enable app_api - elif [ "$SKIP_UPDATE" != 1 ]; then - php /var/www/html/occ app:update app_api - fi - else - if [ "$REMOVE_DISABLED_APPS" = yes ] && [ -d "/var/www/html/custom_apps/app_api" ]; then - php /var/www/html/occ app:remove app_api +# app_api is a shipped app +if [ -d "/var/www/html/custom_apps/app_api" ]; then + php /var/www/html/occ app:disable app_api + rm -r "/var/www/html/custom_apps/app_api" +fi +if [ "$DOCKER_SOCKET_PROXY_ENABLED" = 'yes' ]; then + if [ "$(php /var/www/html/occ config:app:get app_api enabled)" != "yes" ]; then + php /var/www/html/occ app:enable app_api + fi +else + if [ "$REMOVE_DISABLED_APPS" = yes ]; then + if [ "$(php /var/www/html/occ config:app:get app_api enabled)" != "no" ]; then + php /var/www/html/occ app:disable app_api fi fi fi +# Whiteboard app +if [ "$WHITEBOARD_ENABLED" = 'yes' ]; then + if ! [ -d "/var/www/html/custom_apps/whiteboard" ]; then + php /var/www/html/occ app:install whiteboard + elif [ "$(php /var/www/html/occ config:app:get whiteboard enabled)" != "yes" ]; then + php /var/www/html/occ app:enable whiteboard + elif [ "$SKIP_UPDATE" != 1 ]; then + php /var/www/html/occ app:update whiteboard + fi + php /var/www/html/occ config:app:set whiteboard collabBackendUrl --value="https://$NC_DOMAIN/whiteboard" + php /var/www/html/occ config:app:set whiteboard jwt_secret_key --value="$WHITEBOARD_SECRET" +else + if [ "$REMOVE_DISABLED_APPS" = yes ] && [ -d "/var/www/html/custom_apps/whiteboard" ]; then + php /var/www/html/occ app:remove whiteboard + fi +fi + # Remove the update skip file always rm -f "$NEXTCLOUD_DATA_DIR"/skip.update diff --git a/Containers/nextcloud/notify-all.sh b/Containers/nextcloud/notify-all.sh index b11130d1..f4dfa0fd 100644 --- a/Containers/nextcloud/notify-all.sh +++ b/Containers/nextcloud/notify-all.sh @@ -20,7 +20,7 @@ mapfile -t NC_USERS <<< "$NC_USERS" for user in "${NC_USERS[@]}" do echo "Posting '$SUBJECT' to: $user" - "${COMMAND[@]}" notification:generate "$user" "$NC_DOMAIN: $SUBJECT" -l "$MESSAGE" + "${COMMAND[@]}" notification:generate "$user" "$NC_DOMAIN: $SUBJECT" -l "$MESSAGE" --object-type='update' --object-id="$SUBJECT" done echo "Done!" diff --git a/Containers/nextcloud/notify.sh b/Containers/nextcloud/notify.sh index f74ec16b..2ac4ceac 100644 --- a/Containers/nextcloud/notify.sh +++ b/Containers/nextcloud/notify.sh @@ -28,7 +28,7 @@ done for admin in "${NC_ADMIN_USER[@]}" do echo "Posting '$SUBJECT' to: $admin" - "${COMMAND[@]}" notification:generate "$admin" "$NC_DOMAIN: $SUBJECT" -l "$MESSAGE" + "${COMMAND[@]}" notification:generate "$admin" "$NC_DOMAIN: $SUBJECT" -l "$MESSAGE" --object-type='update' --object-id="$SUBJECT" done echo "Done!" diff --git a/Containers/nextcloud/root.motd b/Containers/nextcloud/root.motd index d3cae8a9..00cb4805 100644 --- a/Containers/nextcloud/root.motd +++ b/Containers/nextcloud/root.motd @@ -1,4 +1,4 @@ Warning: You have logged in into the Nextcloud container as root user. See https://github.com/nextcloud/all-in-one#how-to-run-occ-commands if you want to run occ commands. -Apart from that, you can use 'sudo -u www-data -E php occ ' in order to run occ commands. +Apart from that, you can use 'sudo -E -u www-data php occ ' in order to run occ commands. Of course needs to be substituted with the command that you want to use. diff --git a/Containers/nextcloud/run-exec-commands.sh b/Containers/nextcloud/run-exec-commands.sh index ab3abab7..e8066881 100644 --- a/Containers/nextcloud/run-exec-commands.sh +++ b/Containers/nextcloud/run-exec-commands.sh @@ -1,7 +1,10 @@ #!/bin/bash -# Wait 15s for domain to be reachable -sleep 15 +# Wait until the apache container is ready +while ! nc -z "$APACHE_HOST" "$APACHE_PORT"; do + echo "Waiting for $APACHE_HOST to become available..." + sleep 15 +done if [ -n "$NEXTCLOUD_EXEC_COMMANDS" ]; then echo "#!/bin/bash" > /tmp/nextcloud-exec-commands @@ -16,11 +19,13 @@ 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 -sleep inf +signal_handler() { + exit 0 +} + +trap signal_handler SIGINT SIGTERM + +sleep inf & +wait $! diff --git a/Containers/nextcloud/start.sh b/Containers/nextcloud/start.sh index 0bbea739..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 @@ -17,10 +17,15 @@ done POSTGRES_USER="oc_$POSTGRES_USER" export POSTGRES_USER +# Check that db type is not empty +if [ -z "$DATABASE_TYPE" ]; then + export DATABASE_TYPE=postgres +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 @@ -51,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 @@ -81,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/nextcloud/supervisord.conf b/Containers/nextcloud/supervisord.conf index 184074af..1db885e9 100644 --- a/Containers/nextcloud/supervisord.conf +++ b/Containers/nextcloud/supervisord.conf @@ -39,5 +39,7 @@ stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 -command=nc -lk 9001 +# Restart the netcat command once a day to ensure that it stays reachable +# See https://github.com/nextcloud/all-in-one/issues/6334 +command=timeout 86400 nc -lk 9001 user=www-data diff --git a/Containers/notify-push/Dockerfile b/Containers/notify-push/Dockerfile index 2da52eef..838c847c 100644 --- a/Containers/notify-push/Dockerfile +++ b/Containers/notify-push/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:latest -FROM alpine:3.20.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; \ @@ -21,4 +22,6 @@ USER 33 ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh -LABEL com.centurylinklabs.watchtower.enable="false" +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 6180162f..26d74333 100644 --- a/Containers/notify-push/start.sh +++ b/Containers/notify-push/start.sh @@ -1,13 +1,13 @@ #!/bin/bash if [ -z "$NEXTCLOUD_HOST" ]; then - echo "NEXTCLOUD_HOST need to be provided. Exiting!" + echo "NEXTCLOUD_HOST needs to be provided. Exiting!" exit 1 elif [ -z "$POSTGRES_HOST" ]; then - echo "POSTGRES_HOST need to be provided. Exiting!" + echo "POSTGRES_HOST needs to be provided. Exiting!" exit 1 elif [ -z "$REDIS_HOST" ]; then - echo "REDIS_HOST need to be provided. Exiting!" + echo "REDIS_HOST needs to be provided. Exiting!" exit 1 fi @@ -48,9 +48,43 @@ echo "notify-push was started" if [ -z "$POSTGRES_PORT" ]; then POSTGRES_PORT=5432 fi +# Set a default for redis db index +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 +elif [ "$DATABASE_TYPE" != postgres ] && [ "$DATABASE_TYPE" != mysql ]; then + echo "DB type must be either postgres or mysql" + exit 1 +fi + +# Use the correct Postgres username +if [ "$POSTGRES_USER" = nextcloud ]; then + POSTGRES_USER="oc_$POSTGRES_USER" + 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 + 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="postgres://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" -export REDIS_URL="redis://:$REDIS_HOST_PASSWORD@$REDIS_HOST" +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 8c899516..13b4d456 100644 --- a/Containers/onlyoffice/Dockerfile +++ b/Containers/onlyoffice/Dockerfile @@ -1,8 +1,12 @@ # syntax=docker/dockerfile:latest # From https://github.com/ONLYOFFICE/Docker-DocumentServer/blob/master/Dockerfile -FROM onlyoffice/documentserver:8.1.1.1 +FROM onlyoffice/documentserver:9.2.1.1 # USER root is probably used -HEALTHCHECK CMD nc -z 127.0.0.1 80 || exit 1 -LABEL com.centurylinklabs.watchtower.enable="false" +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/onlyoffice/healthcheck.sh b/Containers/onlyoffice/healthcheck.sh new file mode 100644 index 00000000..7a9d79d0 --- /dev/null +++ b/Containers/onlyoffice/healthcheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +nc -z 127.0.0.1 80 || exit 1 diff --git a/Containers/postgresql/Dockerfile b/Containers/postgresql/Dockerfile index ff8de132..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/16/alpine3.20/Dockerfile -FROM postgres:16.3-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 @@ -39,8 +39,10 @@ RUN set -ex; \ VOLUME /mnt/data -USER postgres +USER 999 ENTRYPOINT ["/start.sh"] HEALTHCHECK CMD /healthcheck.sh -LABEL com.centurylinklabs.watchtower.enable="false" +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/postgresql/healthcheck.sh b/Containers/postgresql/healthcheck.sh index f72aeecf..9f303a3a 100644 --- a/Containers/postgresql/healthcheck.sh +++ b/Containers/postgresql/healthcheck.sh @@ -2,4 +2,6 @@ test -f "/mnt/data/backup-is-running" && exit 0 +psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:11000/$POSTGRES_DB" -c "select now()" && exit 0 + psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:5432/$POSTGRES_DB" -c "select now()" || exit 1 diff --git a/Containers/postgresql/start.sh b/Containers/postgresql/start.sh index 10e46550..551bb10e 100644 --- a/Containers/postgresql/start.sh +++ b/Containers/postgresql/start.sh @@ -99,7 +99,7 @@ if ( [ -f "$DATADIR/PG_VERSION" ] && [ "$PG_MAJOR" != "$(cat "$DATADIR/PG_VERSIO fi # Get the Owner - DB_OWNER="$(grep -a "$GREP_STRING" "$DUMP_FILE" | head -1 | grep -oP 'Owner:.*$' | sed 's|Owner:||;s| ||g')" + DB_OWNER="$(grep -a "$GREP_STRING" "$DUMP_FILE" | head -1 | grep -oP 'Owner:.*$' | sed 's|Owner:||;s|[[:space:]]||g')" if [ "$DB_OWNER" = "$POSTGRES_USER" ]; then echo "Unfortunately was the found database owner of the dump file the same as the POSTGRES_USER $POSTGRES_USER" echo "It is not possible to import a database dump from this database owner." @@ -128,7 +128,9 @@ EOSQL fi # Shut down the database to be able to start it again - pg_ctl stop -m fast + # The smart mode disallows new connections, then waits for all existing clients to disconnect and any online backup to finish + # Wait for 1800s to make sure that a checkpoint is completed successfully + pg_ctl stop -m smart -t 1800 # Change database port back to default export PGPORT=5432 diff --git a/Containers/redis/Dockerfile b/Containers/redis/Dockerfile index d8a97f33..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.5-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 @@ -14,8 +14,12 @@ RUN set -ex; \ # Get rid of unused binaries rm -f /usr/local/bin/gosu; -USER redis +COPY --chmod=775 healthcheck.sh /healthcheck.sh + +USER 999 ENTRYPOINT ["/start.sh"] -HEALTHCHECK CMD redis-cli -a $REDIS_HOST_PASSWORD PING || exit 1 -LABEL com.centurylinklabs.watchtower.enable="false" +HEALTHCHECK CMD /healthcheck.sh +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/redis/healthcheck.sh b/Containers/redis/healthcheck.sh new file mode 100644 index 00000000..6588229f --- /dev/null +++ b/Containers/redis/healthcheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +redis-cli -a "$REDIS_HOST_PASSWORD" PING || exit 1 diff --git a/Containers/talk-recording/Dockerfile b/Containers/talk-recording/Dockerfile index e61a1680..8df5b89e 100644 --- a/Containers/talk-recording/Dockerfile +++ b/Containers/talk-recording/Dockerfile @@ -1,11 +1,13 @@ # syntax=docker/dockerfile:latest -FROM python:3.12.4-alpine3.20 +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.1 +ENV RECORDING_VERSION=v0.2.1 ENV ALLOW_ALL=false ENV HPB_PROTOCOL=https +ENV NC_PROTOCOL=https ENV SKIP_VERIFY=false ENV HPB_PATH=/standalone-signaling/ @@ -28,7 +30,7 @@ RUN set -ex; \ build-base \ linux-headers \ geckodriver; \ - useradd -d /tmp --system recording; \ + useradd -d /tmp --system recording -u 122; \ # Give root a random password echo "root:$(openssl rand -base64 12)" | chpasswd; \ git clone --recursive https://github.com/nextcloud/nextcloud-talk-recording --depth=1 --single-branch --branch "$RECORDING_VERSION" /src; \ @@ -48,10 +50,13 @@ RUN set -ex; \ build-base \ linux-headers; +VOLUME /tmp WORKDIR /tmp -USER recording +USER 122 ENTRYPOINT ["/start.sh"] CMD ["python", "-m", "nextcloud.talk.recording", "--config", "/conf/recording.conf"] -HEALTHCHECK CMD nc -z 127.0.0.1 1234 || exit 1 -LABEL com.centurylinklabs.watchtower.enable="false" +HEALTHCHECK CMD /healthcheck.sh +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/talk-recording/healthcheck.sh b/Containers/talk-recording/healthcheck.sh new file mode 100644 index 00000000..8397ab3c --- /dev/null +++ b/Containers/talk-recording/healthcheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +nc -z 127.0.0.1 1234 || exit 1 diff --git a/Containers/talk-recording/recording.conf b/Containers/talk-recording/recording.conf index 99515528..cc8bd495 100644 --- a/Containers/talk-recording/recording.conf +++ b/Containers/talk-recording/recording.conf @@ -1,3 +1,5 @@ +# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later [logs] # Log level based on numeric values of Python logging levels: # - Critical: 50 @@ -12,6 +14,11 @@ # IP and port to listen on for HTTP requests. #listen = 127.0.0.1:8000 +[app] +# Comma separated list of trusted proxies (IPs or CIDR networks) that may set +# the "X-Forwarded-For" header. +#trustedproxies = + [backend] # Allow any hostname as backend endpoint. This is extremely insecure and should # only be used during development. @@ -100,6 +107,18 @@ # ffmpeg. The options given here fully override the default global options. #common = ffmpeg -loglevel level+warning -n +# The (additional) options given to ffmpeg for the audio input. The options +# given here extend the default options for the audio input, although they do +# not override them. +# Default options: '-f pulse -i {AUDIO_SOURCE}' +#inputaudio = + +# The (additional) options given to ffmpeg for the video input. The options +# given here extend the default options for the video input, although they do +# not override them. +# Default options: '-f x11grab -draw_mouse 0 -video_size {WIDTH}x{HEIGHT} -i {VIDEO_SOURCE}' +#inputvideo = + # The options given to ffmpeg to encode the audio output. The options given here # fully override the default options for the audio output. #outputaudio = -c:a libopus @@ -120,4 +139,31 @@ # will use Google Chrome, or Chromium if Google Chrome is not installed. # Allowed values: firefox, chrome # Defaults to firefox -# browser = firefox +#browser = firefox + +# Path to the Selenium driver to use for recordings. +# If set the driver must match the browser being used (for example, +# "/usr/bin/geckodriver" for "firefox"). If no driver is explicitly set Selenium +# Manager will try to find the right one in $PATH, downloading it as a fallback. +# Note that Selenium Manager does not work in some architectures (for example, +# Linux on arm64/aarch64), so in those architectures the driver must be +# explicitly set. +#driverPath = + +# Path to the browser executable to use for recordings. +# If set the executable must match the browser being used (for example, +# "/usr/bin/firefox-esr" for "firefox"). If no executable is explicitly set +# Selenium Manager will try to find the right one in $PATH. Depending on the +# installed Selenium version if the executable is not found Selenium Manager may +# also download the browser as a fallback. +# Note that Selenium Manager does not work in some architectures (for example, +# Linux on arm64/aarch64); in those architectures the Selenium driver will try +# to find the executable, but the executable may need to be explicitly set if +# not found by the driver. +#browserPath = + +[stats] +# Comma-separated list of IP addresses (or CIDR networks) that are allowed to +# access the stats endpoint. +# Leave commented to only allow access from "127.0.0.1". +#allowed_ips = diff --git a/Containers/talk-recording/start.sh b/Containers/talk-recording/start.sh index 14a893a4..b49e5e9c 100644 --- a/Containers/talk-recording/start.sh +++ b/Containers/talk-recording/start.sh @@ -16,6 +16,9 @@ if [ -z "$HPB_DOMAIN" ]; then export HPB_DOMAIN="$NC_DOMAIN" fi +# Delete all contents on startup to start fresh +rm -fr /tmp/{*,.*} + cat << RECORDING_CONF > "/conf/recording.conf" [logs] # 30 means Warning @@ -36,7 +39,7 @@ videoheight = 1080 directory = /tmp [backend-1] -url = ${HPB_PROTOCOL}://${NC_DOMAIN} +url = ${NC_PROTOCOL}://${NC_DOMAIN} secret = ${RECORDING_SECRET} skipverify = ${SKIP_VERIFY} @@ -56,6 +59,8 @@ extensionvideo = .webm [recording] browser = firefox +driverPath = /usr/bin/geckodriver +browserPath = /usr/bin/firefox RECORDING_CONF exec "$@" diff --git a/Containers/talk/Dockerfile b/Containers/talk/Dockerfile index d3d3e623..e8d3d72f 100644 --- a/Containers/talk/Dockerfile +++ b/Containers/talk/Dockerfile @@ -1,12 +1,13 @@ # syntax=docker/dockerfile:latest -FROM nats:2.10.18-scratch AS nats -FROM eturnal/eturnal:1.12.0 AS eturnal -FROM strukturag/nextcloud-spreed-signaling:1.3.2 AS signaling -FROM alpine:3.20.2 AS janus +FROM nats:2.12.4-scratch AS nats +FROM eturnal/eturnal:1.12.2-alpine AS eturnal +FROM strukturag/nextcloud-spreed-signaling:2.0.4 AS signaling +FROM alpine:3.23.3 AS janus -ARG JANUS_VERSION=v0.14.3 +ARG JANUS_VERSION=v1.3.3 WORKDIR /src RUN set -ex; \ + apk upgrade --no-cache -a; \ apk add --no-cache \ ca-certificates \ git \ @@ -34,8 +35,9 @@ RUN set -ex; \ make configs; \ rename -v ".jcfg.sample" ".jcfg" /usr/local/etc/janus/*.jcfg.sample -FROM alpine:3.20.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 COPY --from=eturnal --chmod=777 --chown=1000:1000 /opt/eturnal /opt/eturnal COPY --from=nats --chmod=777 --chown=1000:1000 /nats-server /usr/local/bin/nats-server @@ -99,9 +101,11 @@ RUN set -ex; \ ln -s /opt/eturnal/bin/stun /usr/local/bin/stun; \ ln -s /opt/eturnal/bin/eturnalctl /usr/local/bin/eturnalctl -USER eturnal +USER 1000 ENTRYPOINT ["/start.sh"] CMD ["supervisord", "-c", "/supervisord.conf"] HEALTHCHECK CMD /healthcheck.sh -LABEL com.centurylinklabs.watchtower.enable="false" +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/talk/server.conf.in b/Containers/talk/server.conf.in index b748cacd..8f437e30 100644 --- a/Containers/talk/server.conf.in +++ b/Containers/talk/server.conf.in @@ -55,6 +55,15 @@ blockkey = -encryption-key- # value as configured in the respective internal services. internalsecret = the-shared-secret-for-internal-clients +[federation] +# If set to "true", certificate validation of federation targets will be skipped. +# This should only be enabled during development, e.g. to work with self-signed +# certificates. +#skipverify = false + +# Timeout in seconds for requests to federation targets. +#timeout = 10 + [backend] # Type of backend configuration. # Defaults to "static". @@ -75,7 +84,8 @@ internalsecret = the-shared-secret-for-internal-clients # For backend type "etcd": # Key prefix of backend entries. All keys below will be watched and assumed to # contain a JSON document with the following entries: -# - "url": Url of the Nextcloud instance. +# - "urls": List of urls of the Nextcloud instance. +# - "url": Url of the Nextcloud instance (deprecated). # - "secret": Shared secret for requests from and to the backend servers. # # Additional optional entries: @@ -84,8 +94,8 @@ internalsecret = the-shared-secret-for-internal-clients # - "sessionlimit": Number of sessions that are allowed to connect. # # Example: -# "/signaling/backend/one" -> {"url": "https://nextcloud.domain1.invalid", ...} -# "/signaling/backend/two" -> {"url": "https://domain2.invalid/nextcloud", ...} +# "/signaling/backend/one" -> {"urls": ["https://nextcloud.domain1.invalid"], ...} +# "/signaling/backend/two" -> {"urls": ["https://domain2.invalid/nextcloud"], ...} #backendprefix = /signaling/backend # Allow any hostname as backend endpoint. This is extremely insecure and should @@ -113,8 +123,8 @@ connectionsperhost = 8 # Backend configurations as defined in the "[backend]" section above. The # section names must match the ids used in "backends" above. #[backend-id] -# URL of the Nextcloud instance -#url = https://cloud.domain.invalid +# Comma-separated list of urls of the Nextcloud instance +#urls = https://cloud.domain.invalid # Shared secret for requests from and to the backend servers. Leave empty to use # the common shared secret from above. @@ -134,8 +144,8 @@ connectionsperhost = 8 #maxscreenbitrate = 2097152 #[another-backend] -# URL of the Nextcloud instance -#url = https://cloud.otherdomain.invalid +# Comma-separated list of urls of the Nextcloud instance +#urls = https://cloud.otherdomain.invalid # Shared secret for requests from and to the backend servers. Leave empty to use # the common shared secret from above. @@ -170,6 +180,13 @@ connectionsperhost = 8 # proxy server that is used. #maxscreenbitrate = 2097152 +# List of IP addresses / subnets that are allowed to be used by clients in +# candidates. The allowed list has preference over the blocked list below. +#allowedcandidates = 10.0.0.0/8 + +# List of IP addresses / subnets to filter from candidates received by clients. +#blockedcandidates = 1.2.3.0/24 + # For type "proxy": timeout in seconds for requests to the proxy server. #proxytimeout = 2 diff --git a/Containers/talk/start.sh b/Containers/talk/start.sh index e73525b8..f89949f3 100644 --- a/Containers/talk/start.sh +++ b/Containers/talk/start.sh @@ -30,14 +30,23 @@ if [ -n "$IPv4_ADDRESS_TALK" ] && [ "$IPv4_ADDRESS_TALK_RELAY" = "$IPv4_ADDRESS_ IPv4_ADDRESS_TALK="" fi +set -x +IP_BINDING="::" +if grep -q "1" /sys/module/ipv6/parameters/disable \ +|| grep -q "1" /proc/sys/net/ipv6/conf/all/disable_ipv6 \ +|| grep -q "1" /proc/sys/net/ipv6/conf/default/disable_ipv6; then + IP_BINDING="0.0.0.0" +fi +set +x + # Turn cat << TURN_CONF > "/conf/eturnal.yml" eturnal: listen: - - ip: "::" + - ip: "$IP_BINDING" port: $TALK_PORT transport: udp - - ip: "::" + - ip: "$IP_BINDING" port: $TALK_PORT transport: tcp log_dir: stdout @@ -86,9 +95,10 @@ backends = backend-1 allowall = false timeout = 10 connectionsperhost = 8 +skipverify = ${SKIP_CERT_VERIFY} [backend-1] -url = https://${NC_DOMAIN} +urls = https://${NC_DOMAIN} secret = ${SIGNALING_SECRET} maxstreambitrate = ${TALK_MAX_STREAM_BITRATE} maxscreenbitrate = ${TALK_MAX_SCREEN_BITRATE} diff --git a/Containers/watchtower/Dockerfile b/Containers/watchtower/Dockerfile index cacebde7..fc9ea093 100644 --- a/Containers/watchtower/Dockerfile +++ b/Containers/watchtower/Dockerfile @@ -1,13 +1,21 @@ # syntax=docker/dockerfile:latest -# From https://github.com/containrrr/watchtower/blob/main/dockerfiles/Dockerfile.self-contained -FROM containrrr/watchtower:1.7.1 AS watchtower +FROM golang:1.25.6-alpine3.23 AS go -FROM alpine:3.20.2 +ENV WATCHTOWER_COMMIT_HASH=f522ce27e1fbe4618da54833025a95be62aa838a -RUN apk upgrade --no-cache -a; \ - apk add --no-cache bash +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 -COPY --from=watchtower /watchtower /watchtower +FROM alpine:3.23.3 + +RUN set -ex; \ + apk upgrade --no-cache -a; \ + apk add --no-cache bash ca-certificates tzdata + +COPY --from=go /go/bin/watchtower /watchtower COPY --chmod=775 start.sh /start.sh @@ -15,4 +23,6 @@ COPY --chmod=775 start.sh /start.sh USER root ENTRYPOINT ["/start.sh"] -LABEL com.centurylinklabs.watchtower.enable="false" +LABEL com.centurylinklabs.watchtower.enable="false" \ + wud.watch="false" \ + org.label-schema.vendor="Nextcloud" diff --git a/Containers/watchtower/start.sh b/Containers/watchtower/start.sh index 2c7a1835..cf16e7a4 100644 --- a/Containers/watchtower/start.sh +++ b/Containers/watchtower/start.sh @@ -1,7 +1,7 @@ #!/bin/bash # Check if socket is available and readable -if ! [ -a "/var/run/docker.sock" ]; then +if ! [ -e "/var/run/docker.sock" ]; then echo "Docker socket is not available. Cannot continue." exit 1 elif ! test -r /var/run/docker.sock; then @@ -9,6 +9,13 @@ elif ! test -r /var/run/docker.sock; then exit 1 fi +if [ -f /run/.containerenv ]; then + # If running under podman disable memory_swappiness setting in watchtower. + # It is a necessary workaround until https://github.com/containers/podman/issues/23824 gets fixed. + echo "Running under Podman. Setting WATCHTOWER_DISABLE_MEMORY_SWAPPINESS to 1." + export WATCHTOWER_DISABLE_MEMORY_SWAPPINESS=1 +fi + if [ -n "$CONTAINER_TO_UPDATE" ]; then exec /watchtower --cleanup --debug --run-once "$CONTAINER_TO_UPDATE" else diff --git a/Containers/whiteboard/Dockerfile b/Containers/whiteboard/Dockerfile new file mode 100644 index 00000000..c83dd46b --- /dev/null +++ b/Containers/whiteboard/Dockerfile @@ -0,0 +1,27 @@ +# syntax=docker/dockerfile:latest +# Probably from this file: https://github.com/nextcloud/whiteboard/blob/main/Dockerfile +FROM ghcr.io/nextcloud-releases/whiteboard:v1.5.4 + +USER root +RUN set -ex; \ + 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 +COPY --chmod=775 healthcheck.sh /healthcheck.sh + +HEALTHCHECK CMD /healthcheck.sh + +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 new file mode 100644 index 00000000..5909db82 --- /dev/null +++ b/Containers/whiteboard/healthcheck.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +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 new file mode 100644 index 00000000..e0babd7f --- /dev/null +++ b/Containers/whiteboard/start.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Only start container if nextcloud is accessible +while ! nc -z "$REDIS_HOST" "$REDIS_PORT"; do + echo "Waiting for redis to start..." + sleep 5 +done + +# Set a default for redis db index +if [ -z "$REDIS_DB_INDEX" ]; then + REDIS_DB_INDEX=0 +fi + +# 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 e064273d..832d3ccd 100644 --- a/app/appinfo/info.xml +++ b/app/appinfo/info.xml @@ -5,7 +5,7 @@ Nextcloud All-in-One Provides a login link for admins. Add a link to the admin settings that gives access to the Nextcloud All-in-One admin interface - 0.6.0 + 0.8.0 agpl Azul AllInOne @@ -13,20 +13,11 @@ monitoring https://github.com/nextcloud/all-in-one/issues - + OCA\AllInOne\Settings\Admin - - diff --git a/app/templates/admin.php b/app/templates/admin.php index 8256dfab..4812ad90 100644 --- a/app/templates/admin.php +++ b/app/templates/admin.php @@ -10,7 +10,7 @@ declare(strict_types=1); */ /** @var array $_ */ ?> diff --git a/community-containers/borgbackup-viewer/borgbackup-viewer.json b/community-containers/borgbackup-viewer/borgbackup-viewer.json new file mode 100644 index 00000000..7f9bb0a0 --- /dev/null +++ b/community-containers/borgbackup-viewer/borgbackup-viewer.json @@ -0,0 +1,71 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-borgbackup-viewer", + "image_tag": "v1", + "display_name": "Borg Backup Viewer", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/borgbackup-viewer", + "image": "ghcr.io/szaimen/aio-borgbackup-viewer", + "internal_port": "5801", + "ports": [ + { + "ip_binding": "", + "port_number": "5801", + "protocol": "tcp" + } + ], + "environment": [ + "BORG_HOST_ID=nextcloud-aio-borgbackup-viewer", + "WEB_AUTHENTICATION_USERNAME=nextcloud", + "WEB_AUTHENTICATION_PASSWORD=%BORGBACKUP_VIEWER_PASSWORD%", + "WEB_LISTENING_PORT=5801", + "BORG_PASSPHRASE=%BORGBACKUP_PASSWORD%", + "BORG_REPO=/mnt/borgbackup/borg" + ], + "secrets": [ + "BORGBACKUP_VIEWER_PASSWORD", + "BORGBACKUP_PASSWORD" + ], + "ui_secret": "BORGBACKUP_VIEWER_PASSWORD", + "volumes": [ + { + "source": "nextcloud_aio_backup_cache", + "destination": "/root", + "writeable": true + }, + { + "source": "%NEXTCLOUD_DATADIR%", + "destination": "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data", + "writeable": true + }, + { + "source": "nextcloud_aio_mastercontainer", + "destination": "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer", + "writeable": true + }, + { + "source": "%BORGBACKUP_HOST_LOCATION%", + "destination": "/mnt/borgbackup", + "writeable": true + }, + { + "source": "nextcloud_aio_elasticsearch", + "destination": "/nextcloud_aio_volumes/nextcloud_aio_elasticsearch", + "writeable": true + }, + { + "source": "nextcloud_aio_redis", + "destination": "/mnt/redis", + "writeable": true + } + ], + "devices": [ + "/dev/fuse" + ], + "cap_add": [ + "SYS_ADMIN" + ], + "apparmor_unconfined": true + } + ] +} diff --git a/community-containers/borgbackup-viewer/readme.md b/community-containers/borgbackup-viewer/readme.md new file mode 100644 index 00000000..ddd11be7 --- /dev/null +++ b/community-containers/borgbackup-viewer/readme.md @@ -0,0 +1,17 @@ +## Borgbackup Viewer +This container allows to view the local borg repository in a web session. It also allows you to restore files and folders from the backup by using desktop programs in a web browser. + +### Notes +- 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 `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 + +### Repository +https://github.com/szaimen/aio-borgbackup-viewer + +### Maintainer +https://github.com/szaimen + diff --git a/community-containers/caddy/caddy.json b/community-containers/caddy/caddy.json index 47fef1db..e27df683 100644 --- a/community-containers/caddy/caddy.json +++ b/community-containers/caddy/caddy.json @@ -4,8 +4,8 @@ "container_name": "nextcloud-aio-caddy", "display_name": "Caddy with geoblocking", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy", - "image": "szaimen/aio-caddy", - "image_tag": "v2", + "image": "ghcr.io/szaimen/aio-caddy", + "image_tag": "v4", "internal_port": "443", "restart": "unless-stopped", "ports": [ @@ -13,17 +13,14 @@ "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_PORT=%APACHE_PORT%", + "APACHE_IP_BINDING=%APACHE_IP_BINDING%", + "NEXTCLOUD_EXPORTER_CADDY_PASSWORD=%NEXTCLOUD_EXPORTER_CADDY_PASSWORD%" ], "volumes": [ { @@ -37,9 +34,14 @@ "writeable": false } ], + "secrets": [ + "NEXTCLOUD_EXPORTER_CADDY_PASSWORD" + ], "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 fcb71964..fd2f30ef 100644 --- a/community-containers/caddy/readme.md +++ b/community-containers/caddy/readme.md @@ -1,17 +1,24 @@ ## Caddy with geoblocking -This container bundles caddy and auto-configures it for you. It also covers https://github.com/nextcloud/all-in-one/tree/main/community-containers/vaultwarden by listening on `bw.$NC_DOMAIN`, if installed. It also covers https://github.com/nextcloud/all-in-one/tree/main/community-containers/stalwart by listening on `mail.$NC_DOMAIN`, if installed. It also covers https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyfin by listening on `media.$NC_DOMAIN`, if installed. It also covers https://github.com/nextcloud/all-in-one/tree/main/community-containers/lldap by listening on `ldap.$NC_DOMAIN`, if installed. It also covers https://github.com/nextcloud/all-in-one/tree/main/community-containers/nocodb by listening on `tables.$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. -- If you want to use this with 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 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 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. -- If you want to use this with https://github.com/nextcloud/all-in-one/tree/main/community-containers/lldap, make sure that you point `ldap.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for lldap. -- If you want to use this with 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. +- 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. +- If you want to use this with [lldap](https://github.com/nextcloud/all-in-one/tree/main/community-containers/lldap), make sure that you point `ldap.your-nc-domain.com` to your server using a cname record so that caddy can get a certificate automatically for lldap. +- 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. +- 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/calcardbackup.json b/community-containers/calcardbackup/calcardbackup.json new file mode 100644 index 00000000..738ca2f0 --- /dev/null +++ b/community-containers/calcardbackup/calcardbackup.json @@ -0,0 +1,37 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-calcardbackup", + "display_name": "Calendar and contacts backup", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/calcardbackup", + "image": "waja/calcardbackup", + "image_tag": "latest", + "restart": "unless-stopped", + "environment": [ + "CRON_TIME=0 0 * * *", + "INIT_BACKUP=yes", + "BACKUP_DIR=/backup", + "NC_DIR=/nextcloud", + "NC_HOST=%NC_DOMAIN%", + "DB_HOST=nextcloud-aio-database", + "DB_PORT=5432", + "CALCARD_OPTS=-ltm 5" + ], + "volumes": [ + { + "source": "nextcloud_aio_calcardbackup", + "destination": "/backup", + "writeable": true + }, + { + "source": "nextcloud_aio_nextcloud", + "destination": "/nextcloud", + "writeable": false + } + ], + "backup_volumes": [ + "nextcloud_aio_calcardbackup" + ] + } + ] +} \ No newline at end of file diff --git a/community-containers/calcardbackup/readme.md b/community-containers/calcardbackup/readme.md new file mode 100644 index 00000000..42a218b4 --- /dev/null +++ b/community-containers/calcardbackup/readme.md @@ -0,0 +1,15 @@ +## calcardbackup +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 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 + +### Repository +https://github.com/waja/docker-calcardbackup + +### Maintainer +https://github.com/pailloM + diff --git a/community-containers/container-management/container-management.json b/community-containers/container-management/container-management.json new file mode 100644 index 00000000..9563139d --- /dev/null +++ b/community-containers/container-management/container-management.json @@ -0,0 +1,41 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-container-management", + "display_name": "Container Management", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/container-management", + "image": "ghcr.io/szaimen/aio-container-management", + "image_tag": "v1", + "internal_port": "5804", + "restart": "unless-stopped", + "ports": [ + { + "ip_binding": "", + "port_number": "5804", + "protocol": "tcp" + } + ], + "volumes": [ + { + "source": "%WATCHTOWER_DOCKER_SOCKET_PATH%", + "destination": "/var/run/docker.sock", + "writeable": false + } + ], + "environment": [ + "TZ=%TIMEZONE%", + "SECURE_CONNECTION=1", + "WEB_AUTHENTICATION=1", + "USER_ID=0", + "GROUP_ID=0", + "WEB_AUTHENTICATION_USERNAME=container-management", + "WEB_AUTHENTICATION_PASSWORD=%CONTAINER_MANAGEMENT_PASSWORD%", + "WEB_LISTENING_PORT=5804" + ], + "secrets": [ + "CONTAINER_MANAGEMENT_PASSWORD" + ], + "ui_secret": "CONTAINER_MANAGEMENT_PASSWORD" + } + ] +} diff --git a/community-containers/container-management/readme.md b/community-containers/container-management/readme.md new file mode 100644 index 00000000..e8c17313 --- /dev/null +++ b/community-containers/container-management/readme.md @@ -0,0 +1,15 @@ +## Container-Management +This container allows to manage insides of other containers via a GUI inside a Web session by allowing to run docker commands from inside this container. + +### Notes +- After adding and starting the container, you need to visit `https://ip.address.of.this.server:5804` in order to log in with the user `container-management` 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 you can use any docker command. ⚠️ Be very carefully while doing that as can break your instance! +- There are also some pre-made scripts that make configuring some of the community containers easier. For example scripts for [LLDAP](https://github.com/nextcloud/all-in-one/tree/main/community-containers/lldap) and [Facerecognition](https://github.com/nextcloud/all-in-one/tree/main/community-containers/facerecognition). +- ⚠️ 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 + +### Repository +https://github.com/szaimen/aio-container-management + +### Maintainer +https://github.com/szaimen diff --git a/community-containers/dlna/readme.md b/community-containers/dlna/readme.md index 7a041b5e..47502dc8 100644 --- a/community-containers/dlna/readme.md +++ b/community-containers/dlna/readme.md @@ -3,7 +3,6 @@ This container bundles DLNA server for your Nextcloud files to be accessible by ### Notes - This container will work only if the Nextcloud installation is in your home network, it is not suitable for installations on remote servers. -- This is not working with Docker Desktop since it requires the `host` networking mode in docker, and it doesn't really share the host's network interfaces in this system - If you have a firewall like ufw configured, you might need to open at least port 9999 TCP and 1900 UDP first in order to make it work. - 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/facerecognition/facerecognition.json b/community-containers/facerecognition/facerecognition.json index 89e8fc8b..d97d5c08 100644 --- a/community-containers/facerecognition/facerecognition.json +++ b/community-containers/facerecognition/facerecognition.json @@ -10,17 +10,21 @@ "restart": "unless-stopped", "environment": [ "TZ=%TIMEZONE%", - "API_KEY=some-super-secret-api-key", + "API_KEY=%FACERECOGNITION_API_KEY%", "FACE_MODEL=3" ], "aio_variables": [ "nextcloud_memory_limit=2048M" ], + "secrets": [ + "FACERECOGNITION_API_KEY" + ], + "enable_nvidia_gpu": false, "nextcloud_exec_commands": [ "php /var/www/html/occ app:install facerecognition", "php /var/www/html/occ app:enable facerecognition", "php /var/www/html/occ config:system:set facerecognition.external_model_url --value nextcloud-aio-facerecognition:5000", - "php /var/www/html/occ config:system:set facerecognition.external_model_api_key --value some-super-secret-api-key", + "php /var/www/html/occ config:system:set facerecognition.external_model_api_key --value %FACERECOGNITION_API_KEY%", "php /var/www/html/occ face:setup -m 5", "php /var/www/html/occ face:setup -M 1G", "php /var/www/html/occ config:app:set facerecognition analysis_image_area --value 4320000", diff --git a/community-containers/facerecognition/readme.md b/community-containers/facerecognition/readme.md index fa22e0bb..474ed1e2 100644 --- a/community-containers/facerecognition/readme.md +++ b/community-containers/facerecognition/readme.md @@ -3,7 +3,9 @@ This container bundles the external model of facerecognition and auto-configures ### Notes - This container needs imaginary in order to analyze modern file format images. Make sure to enable imaginary in the AIO interface before adding this container. -- Facerecognition is by default disabled for all users, if you want to enable facerecognition for all users, you can run the following before adding this container: +- The image analysis is currently set to fixed value of `1G`. See [this](https://github.com/search?q=repo%3Anextcloud%2Fall-in-one+1G+path%3A%2F%5Ecommunity-containers%5C%2Ffacerecognition%5C%2F%2F&type=code) +- Facerecognition is by default disabled for all users. If you want to enable facerecognition for all users, you can run the following commands before adding this container:
+**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. This script below can be run from inside the container-management container via `bash /facerecognition.sh`. ```bash # Go into the container sudo docker exec --user www-data -it nextcloud-aio-nextcloud bash @@ -21,7 +23,8 @@ This container bundles the external model of facerecognition and auto-configures # Exit the container shell exit ``` -- If facerecognition shall analyze shared files & folders (`sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:app:set facerecognition handle_shared_files --value true`), groupfolders (`sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:app:set facerecognition handle_group_files --value true`) and/or external storages (`sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:app:set facerecognition handle_external_files --value true`) in Nextcloud, you need to enable support for it manually first by running the mentioned commands before adding this container. See https://github.com/matiasdelellis/facerecognition/wiki/Settings#hidden-settings for further notes on each of these settings. +- If facerecognition shall analyze shared files & folders (`sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:app:set facerecognition handle_shared_files --value true`), groupfolders (`sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:app:set facerecognition handle_group_files --value true`) and/or external storages (`sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:app:set facerecognition handle_external_files --value true`) in Nextcloud, you need to enable support for it manually first by running the mentioned commands before adding this container. See https://github.com/matiasdelellis/facerecognition/wiki/Settings#hidden-settings for further notes on each of these settings.
+**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 ### Repository diff --git a/community-containers/fail2ban/fail2ban.json b/community-containers/fail2ban/fail2ban.json index 5305ad85..78bf0a85 100644 --- a/community-containers/fail2ban/fail2ban.json +++ b/community-containers/fail2ban/fail2ban.json @@ -4,7 +4,7 @@ "container_name": "nextcloud-aio-fail2ban", "display_name": "Fail2ban", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/fail2ban", - "image": "szaimen/aio-fail2ban", + "image": "ghcr.io/szaimen/aio-fail2ban", "image_tag": "v1", "internal_port": "host", "restart": "unless-stopped", @@ -25,6 +25,16 @@ "source": "nextcloud_aio_vaultwarden_logs", "destination": "/vaultwarden", "writeable": false + }, + { + "source": "nextcloud_aio_jellyfin", + "destination": "/jellyfin", + "writeable": false + }, + { + "source": "nextcloud_aio_jellyseerr", + "destination": "/jellyseerr", + "writeable": false } ] } diff --git a/community-containers/fail2ban/readme.md b/community-containers/fail2ban/readme.md index a3d5aca2..28ab21e3 100644 --- a/community-containers/fail2ban/readme.md +++ b/community-containers/fail2ban/readme.md @@ -1,10 +1,10 @@ ## Fail2ban -This container bundles fail2ban and auto-configures it for you in order to block ip-addresses automatically. It also covers https://github.com/nextcloud/all-in-one/tree/main/community-containers/vaultwarden, if installed. +This container bundles fail2ban and auto-configures it for you in order to block ip-addresses automatically. It also covers https://github.com/nextcloud/all-in-one/tree/main/community-containers/vaultwarden, https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyfin, and https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyseerr, if installed. ### Notes -- This is not working on Docker Desktop since it needs `network_mode: host` in order to work correctly. - If you get an error like `"ip6tables v1.8.9 (legacy): can't initialize ip6tables table filter': Table does not exist (do you need to insmod?)"`, you need to enable ip6tables on your host via `sudo modprobe ip6table_filter`. - If you get an error like `stderr: 'iptables: No chain/target/match by that name.'` and `stderr: 'ip6tables: No chain/target/match by that name.'`, you need to follow https://github.com/szaimen/aio-fail2ban/issues/9#issuecomment-2026898790 in order to resolve this. +- You can unban ip addresses like so for example: `docker exec -it nextcloud-aio-fail2ban fail2ban-client set nextcloud unbanip 203.113.167.162`. **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 ### Repository diff --git a/community-containers/helloworld/helloworld.json b/community-containers/helloworld/helloworld.json new file mode 100644 index 00000000..fed10008 --- /dev/null +++ b/community-containers/helloworld/helloworld.json @@ -0,0 +1,12 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-helloworld", + "display_name": "Hello world", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/helloworld", + "image": "ghcr.io/docjyj/aio-helloworld", + "image_tag": "%AIO_CHANNEL%", + "restart": "unless-stopped" + } + ] +} diff --git a/community-containers/helloworld/readme.md b/community-containers/helloworld/readme.md new file mode 100644 index 00000000..83c557ac --- /dev/null +++ b/community-containers/helloworld/readme.md @@ -0,0 +1,8 @@ +## Hello World +This container is a template for creating a community container. + +### Repository +https://github.com/docjyj/aio-helloworld + +### Maintainer +https://github.com/docjyj diff --git a/community-containers/jellyfin/jellyfin.json b/community-containers/jellyfin/jellyfin.json index f16e5a0a..f0840913 100644 --- a/community-containers/jellyfin/jellyfin.json +++ b/community-containers/jellyfin/jellyfin.json @@ -25,12 +25,13 @@ { "source": "%NEXTCLOUD_MOUNT%", "destination": "%NEXTCLOUD_MOUNT%", - "writeable": false + "writeable": true } ], "devices": [ "/dev/dri" ], + "enable_nvidia_gpu": true, "backup_volumes": [ "nextcloud_aio_jellyfin" ] diff --git a/community-containers/jellyfin/readme.md b/community-containers/jellyfin/readme.md index f744069b..2a78bc1e 100644 --- a/community-containers/jellyfin/readme.md +++ b/community-containers/jellyfin/readme.md @@ -3,12 +3,12 @@ This container bundles Jellyfin and auto-configures it for you. ### Notes - This container is incompatible with the [Plex](https://github.com/nextcloud/all-in-one/tree/main/community-containers/plex) community container. So make sure that you do not enable both at the same time! -- This container does not work on Docker Desktop since it needs `network_mode: host` in order to work correctly. - After adding and starting the container, you can directly visit http://ip.address.of.server:8096/ and access your new Jellyfin instance! - This container should usually only be run in home networks as it exposes unencrypted services like DLNA by default which can be disabld via the web interface though. - In order to access your Jellyfin outside the local network, you have to set up your own reverse proxy. You can set up a reverse proxy following [these instructions](https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md) and [Jellyfin's networking documentation](https://jellyfin.org/docs/general/networking/#running-jellyfin-behind-a-reverse-proxy), OR use the [Caddy](https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy) community container that will automatically configure `media.$NC_DOMAIN` to redirect to your Jellyfin. - ⚠️ After the initial start, Jellyfin shows a configuration page to set up the root password, etc. **Be careful to initialize your Jellyfin before adding the DNS record.** - If you have a firewall like ufw configured, you might need to open all Jellyfin ports in there first in order to make it work. Especially port 8096 is important! +- If you want to secure the installation with fail2ban, you might want to check out https://github.com/nextcloud/all-in-one/tree/main/community-containers/fail2ban - The data of Jellyfin will be automatically included in AIO's backup solution! - See [here](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/jellyseerr/jellyseerr.json b/community-containers/jellyseerr/jellyseerr.json new file mode 100644 index 00000000..64472a8a --- /dev/null +++ b/community-containers/jellyseerr/jellyseerr.json @@ -0,0 +1,35 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-jellyseerr", + "display_name": "Jellyseerr", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyseerr", + "image": "fallenbagel/jellyseerr", + "image_tag": "latest", + "internal_port": "5055", + "restart": "unless-stopped", + "init": false, + "ports": [ + { + "ip_binding": "%APACHE_IP_BINDING%", + "port_number": "5055", + "protocol": "tcp" + } + ], + "environment": [ + "PORT=5055", + "TZ=%TIMEZONE%" + ], + "volumes": [ + { + "source": "nextcloud_aio_jellyseerr", + "destination": "/app/config", + "writeable": true + } + ], + "backup_volumes": [ + "nextcloud_aio_jellyseerr" + ] + } + ] +} diff --git a/community-containers/jellyseerr/readme.md b/community-containers/jellyseerr/readme.md new file mode 100644 index 00000000..0d8e049d --- /dev/null +++ b/community-containers/jellyseerr/readme.md @@ -0,0 +1,16 @@ +## Jellyseerr +This container bundles Jellyseerr and auto-configures it for you. + +### Notes +- This container is only intended to be used inside home networks as it uses http for its management page by default. +- After adding and starting the container, you can directly visit `http://ip.address.of.server:5055` and access your new Jellyseerr instance, which can be used to manage Plex, Jellyfin, and Emby. +- In order to access your Jellyseerr outside the local network, you have to set up your own reverse proxy. You can set up a reverse proxy following [these instructions](https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md) and [Jellyseerr's reverse proxy documentation.](https://docs.jellyseerr.dev/extending-jellyseerr/reverse-proxy), OR use the Caddy community container that will automatically configure requests.$NC_DOMAIN to redirect to your Jellyseerr. Note that it is recommended to [enable CSRF protection in Jellyseerr](https://docs.jellyseerr.dev/using-jellyseerr/settings/general#enable-csrf-protection) for added security if you plan to use Jellyseerr outside the local network, but make sure to read up on it and understand the caveats first. +- If you want to secure the installation with fail2ban, you might want to check out https://github.com/nextcloud/all-in-one/tree/main/community-containers/fail2ban. Note that [enabling the proxy support option in Jellyseerr](https://docs.jellyseerr.dev/using-jellyseerr/settings/general#enable-proxy-support) is required for this to work properly. +- The config of Jellyseerr will be automatically included in AIO's backup solution! +- See [here](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/Fallenbagel/jellyseerr + +### Maintainer +https://github.com/Anvil5465 diff --git a/community-containers/languagetool/languagetool.json b/community-containers/languagetool/languagetool.json new file mode 100644 index 00000000..03eb6b39 --- /dev/null +++ b/community-containers/languagetool/languagetool.json @@ -0,0 +1,16 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-languagetool", + "display_name": "LanguageTool for Collabora", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/languagetool", + "image": "erikvl87/languagetool", + "image_tag": "latest", + "internal_port": "8010", + "restart": "unless-stopped", + "environment": [ + "TZ=%TIMEZONE%" + ] + } + ] +} diff --git a/community-containers/languagetool/readme.md b/community-containers/languagetool/readme.md new file mode 100644 index 00000000..4c2ca98c --- /dev/null +++ b/community-containers/languagetool/readme.md @@ -0,0 +1,13 @@ +## LanguageTool for Collabora +This container bundles a LanguageTool for Collabora which adds spell checking functionality to Collabora. + +### Notes +- Make sure to have collabora enabled via the AIO interface +- After adding this container via the AIO Interface, while all containers are still stopped, you need to scroll down to the `Additional Collabora options` section and enter `--o:languagetool.enabled=true --o:languagetool.base_url=http://nextcloud-aio-languagetool:8010/v2`. +- 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/Erikvl87/docker-languagetool + +### Maintainer +https://github.com/szaimen diff --git a/community-containers/libretranslate/libretranslate.json b/community-containers/libretranslate/libretranslate.json index 80312a9a..dad8d007 100644 --- a/community-containers/libretranslate/libretranslate.json +++ b/community-containers/libretranslate/libretranslate.json @@ -2,9 +2,9 @@ "aio_services_v1": [ { "container_name": "nextcloud-aio-libretranslate", - "display_name": "LibreTranslate", + "display_name": "LibreTranslate (deprecated)", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/libretranslate", - "image": "szaimen/aio-libretranslate", + "image": "ghcr.io/szaimen/aio-libretranslate", "image_tag": "v1", "internal_port": "5000", "restart": "unless-stopped", diff --git a/community-containers/libretranslate/readme.md b/community-containers/libretranslate/readme.md index ffab72ab..f9893f34 100644 --- a/community-containers/libretranslate/readme.md +++ b/community-containers/libretranslate/readme.md @@ -1,6 +1,11 @@ ## LibreTranslate This container bundles LibreTranslate and auto-configures it for you. +> [!WARNING] +> The LibreTranslate container and app is deprecated! +> Please use the [translate2 app](https://apps.nextcloud.com/apps/translate2) instead. +> You can activate it by first enabling the Docker-Socket-Proxy in the AIO-interface and then heading over to `https://your-nc-domain.com/settings/apps/tools` and installing and enabling the `Local Machine Translation` app. + ### Notes - After the initial startup is done, you might want to change the default language to translate from and to via: ```bash diff --git a/community-containers/lldap/lldap.json b/community-containers/lldap/lldap.json index 3592f179..32f8e7ec 100644 --- a/community-containers/lldap/lldap.json +++ b/community-containers/lldap/lldap.json @@ -27,6 +27,7 @@ "LLDAP_JWT_SECRET", "LLDAP_LDAP_USER_PASS" ], + "ui_secret": "LLDAP_LDAP_USER_PASS", "volumes": [ { "source": "nextcloud_aio_lldap", diff --git a/community-containers/lldap/readme.md b/community-containers/lldap/readme.md index 27934d28..586aea9e 100644 --- a/community-containers/lldap/readme.md +++ b/community-containers/lldap/readme.md @@ -3,7 +3,7 @@ This container bundles LLDAP server and auto-configures your Nextcloud instance ### Notes - In order to access your LLDAP web interface outside the local network, you have to set up your own reverse proxy. You can set up a reverse proxy following [these instructions](https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md) OR use the [Caddy](https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy) community container that will automatically configure `ldap.$NC_DOMAIN` to redirect to your Lldap. You need to point the reverse proxy at port 17170 of this server. -- After adding and starting the container, you can log in to the lldap web interface by using the username `admin` and the password that you can retrieve via `sudo docker inspect nextcloud-aio-lldap | grep LLDAP_JWT_SECRET`. +- After adding and starting the container, you can log in to the lldap web interface by using the username `admin` and the secret that you can see next to the container in the AIO interface. - To configure Nextcloud, you can use the generic configuration proposed below. - For advanced configurations, see how to configure a client with lldap https://github.com/lldap/lldap#client-configuration - Also, see how Nextcloud's LDAP application works https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html @@ -18,12 +18,10 @@ Functionality with this configuration: > For simplicity, this configuration is done via the command line (don't worry, it's very simple). -First, you need to retrieve the LLDAP admin password, this will be used later on. Which you need to type in or copy and paste: -```bash -sudo docker inspect nextcloud-aio-lldap | grep LLDAP_LDAP_USER_PASS -``` +First, you need to retrieve the LLDAP admin password that you can see next to the container in the AIO interface. There you can configure smtp first and then invite users via mail. -Now go into the Nextcloud container: +Now go into the Nextcloud container:
+**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. This script below can be run from inside the container-management container via `bash /lldap.sh`. ```bash sudo docker exec --user www-data -it nextcloud-aio-nextcloud bash ``` diff --git a/community-containers/local-ai/local-ai.json b/community-containers/local-ai/local-ai.json index 60032cdf..e906b5a7 100644 --- a/community-containers/local-ai/local-ai.json +++ b/community-containers/local-ai/local-ai.json @@ -4,41 +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": "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 } ], + "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 8fef4473..02722bd0 100644 --- a/community-containers/local-ai/readme.md +++ b/community-containers/local-ai/readme.md @@ -1,29 +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/go-skynet/model-gallery/blob/main/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:go-skynet/model-gallery/stablediffusion.yaml - name: Stable_diffusion +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! -# Port of OpenAI's Whisper model in C/C++ -- url: github:go-skynet/model-gallery/whisper-base.yaml - name: whisper-1 - -# A commercially licensable model based on GPT-J and trained by Nomic AI on the v0 GPT4All dataset. -- url: github:go-skynet/model-gallery/gpt4all-j.yaml - name: gpt4all-j -``` -- You need to add gpt4all-j under Text Generation (Default completion model to use) in Connected Accounts in the Administration Settings in Nextcloud, the default does not work. -- Additionally after doing so, you might want to enable or disable specific features for your models in the integration_openai settings: `https://your-nc-domain.com/settings/admin/connected-accounts` +- 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/makemkv.json b/community-containers/makemkv/makemkv.json new file mode 100644 index 00000000..22132cb8 --- /dev/null +++ b/community-containers/makemkv/makemkv.json @@ -0,0 +1,59 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-makekv", + "display_name": "MakeMKV", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/makemkv", + "image": "jlesage/makemkv", + "image_tag": "latest", + "internal_port": "5802", + "restart": "unless-stopped", + "ports": [ + { + "ip_binding": "", + "port_number": "5802", + "protocol": "tcp" + } + ], + "volumes": [ + { + "source": "nextcloud_aio_makemkv", + "destination": "/config", + "writeable": true + }, + { + "source": "%NEXTCLOUD_DATADIR%", + "destination": "/storage", + "writeable": false + }, + { + "source": "%NEXTCLOUD_MOUNT%", + "destination": "/output", + "writeable": true + }, + { + "source": "/dev", + "destination": "/dev", + "writeable": false + } + ], + "environment": [ + "TZ=%TIMEZONE%", + "SECURE_CONNECTION=1", + "WEB_AUTHENTICATION=1", + "USER_ID=33", + "GROUP_ID=33", + "WEB_AUTHENTICATION_USERNAME=makemkv", + "WEB_AUTHENTICATION_PASSWORD=%MAKEMKV_PASSWORD%", + "WEB_LISTENING_PORT=5802" + ], + "secrets": [ + "MAKEMKV_PASSWORD" + ], + "ui_secret": "MAKEMKV_PASSWORD", + "backup_volumes": [ + "nextcloud_aio_makemkv" + ] + } + ] +} diff --git a/community-containers/makemkv/readme.md b/community-containers/makemkv/readme.md new file mode 100644 index 00000000..e78510ee --- /dev/null +++ b/community-containers/makemkv/readme.md @@ -0,0 +1,20 @@ +## MakeMKV +This container bundles MakeMKV and auto-configures it for you. + +### Notes +- 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#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. +- The config data of MakeMKV will be automatically included in AIOs backup solution! +- ⚠️ 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 + +### Repository +https://github.com/jlesage/docker-makemkv + +### Maintainer +https://github.com/szaimen diff --git a/community-containers/memories/memories.json b/community-containers/memories/memories.json index ce29a558..cb84a722 100644 --- a/community-containers/memories/memories.json +++ b/community-containers/memories/memories.json @@ -17,11 +17,17 @@ "source": "%NEXTCLOUD_DATADIR%", "destination": "/mnt/ncdata", "writeable": false + }, + { + "source": "%NEXTCLOUD_MOUNT%", + "destination": "%NEXTCLOUD_MOUNT%", + "writeable": false } ], "devices": [ "/dev/dri" ], + "enable_nvidia_gpu": true, "nextcloud_exec_commands": [ "php /var/www/html/occ app:install memories", "php /var/www/html/occ app:enable memories", diff --git a/community-containers/memories/readme.md b/community-containers/memories/readme.md index bb8e066d..88a44c4d 100644 --- a/community-containers/memories/readme.md +++ b/community-containers/memories/readme.md @@ -2,7 +2,7 @@ This container bundles the hardware-transcoding container of memories and auto-configures it for you. ### Notes -- In order to actually enable the hardware transcoding, you need to add the following flag to AIO apart from adding this container: https://github.com/nextcloud/all-in-one#how-to-enable-hardware-transcoding-for-nextcloud +- In order to actually enable the hardware transcoding, you need to add the following flag to AIO apart from adding this container: https://github.com/nextcloud/all-in-one#how-to-enable-hardware-acceleration-for-nextcloud - 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/minio/minio.json b/community-containers/minio/minio.json new file mode 100644 index 00000000..50613202 --- /dev/null +++ b/community-containers/minio/minio.json @@ -0,0 +1,41 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-minio", + "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", + "internal_port": "9000", + "environment": [ + "MINIO_ROOT_USER=nextcloud", + "MINIO_ROOT_PASSWORD=%MINIO_ROOT_PASSWORD%" + ], + "secrets": [ + "MINIO_ROOT_PASSWORD" + ], + "volumes": [ + { + "source": "nextcloud_aio_minio", + "destination": "/data", + "writeable": true + } + ], + "backup_volumes": [ + "nextcloud_aio_minio" + ], + "nextcloud_exec_commands": [ + "php /var/www/html/occ config:system:set objectstore class --value 'OC\\Files\\ObjectStore\\S3'", + "php /var/www/html/occ config:system:set objectstore arguments autocreate --value true --type bool", + "php /var/www/html/occ config:system:set objectstore arguments use_path_style --value true --type bool", + "php /var/www/html/occ config:system:set objectstore arguments use_ssl --value false --type bool", + "php /var/www/html/occ config:system:set objectstore arguments region --value ''", + "php /var/www/html/occ config:system:set objectstore arguments bucket --value nextcloud", + "php /var/www/html/occ config:system:set objectstore arguments key --value nextcloud", + "php /var/www/html/occ config:system:set objectstore arguments secret --value %MINIO_ROOT_PASSWORD%", + "php /var/www/html/occ config:system:set objectstore arguments port --value 9000", + "php /var/www/html/occ config:system:set objectstore arguments hostname --value nextcloud-aio-minio" + ] + } + ] +} diff --git a/community-containers/minio/readme.md b/community-containers/minio/readme.md new file mode 100644 index 00000000..be41d5bd --- /dev/null +++ b/community-containers/minio/readme.md @@ -0,0 +1,18 @@ +## Minio +This container bundles minio s3 storage and auto-configures it for you. + +>[!WARNING] +> Enabling this container will remove access to all the files formerly written to the data directory. +> So only enable this on a clean instance directly after installing AIO. +> All additional users that are added via Nextcloud afterwards are going to work correctly. +> Also, after enabling and using it, make sure to not disable the container as you cannot migrate from s3 to local storage anymore and s3 is a critical part of your infrastructure from then on. + +### Notes +- The data of Minio 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 + +### Repository +https://github.com/szaimen/aio-minio + +### Maintainer +https://github.com/szaimen diff --git a/community-containers/nextcloud-exporter/nextcloud-exporter.json b/community-containers/nextcloud-exporter/nextcloud-exporter.json new file mode 100644 index 00000000..e5bf74b8 --- /dev/null +++ b/community-containers/nextcloud-exporter/nextcloud-exporter.json @@ -0,0 +1,35 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-nextcloud-exporter", + "display_name": "Prometheus Nextcloud Exporter", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/nextcloud-exporter", + "image": "ghcr.io/xperimental/nextcloud-exporter", + "image_tag": "0.9.0", + "internal_port": "9205", + "restart": "unless-stopped", + "ports": [ + { + "ip_binding": "127.0.0.1", + "port_number": "9205", + "protocol": "tcp" + } + ], + "environment": [ + "TZ=%TIMEZONE%", + "NEXTCLOUD_SERVER=https://%NC_DOMAIN%", + "NEXTCLOUD_AUTH_TOKEN=%NEXTCLOUD_EXPORTER_TOKEN%", + "NEXTCLOUD_LISTEN_ADDRESS=0.0.0.0:9205", + "NEXTCLOUD_TIMEOUT=5s" + ], + "ui_secret": "NEXTCLOUD_EXPORTER_CADDY_PASSWORD", + "secrets": [ + "NEXTCLOUD_EXPORTER_TOKEN", + "NEXTCLOUD_EXPORTER_CADDY_PASSWORD" + ], + "nextcloud_exec_commands": [ + "php /var/www/html/occ config:app:set serverinfo token --value %NEXTCLOUD_EXPORTER_TOKEN%" + ] + } + ] +} diff --git a/community-containers/nextcloud-exporter/readme.md b/community-containers/nextcloud-exporter/readme.md new file mode 100644 index 00000000..3efa6257 --- /dev/null +++ b/community-containers/nextcloud-exporter/readme.md @@ -0,0 +1,72 @@ +## Prometheus Nextcloud Exporter + +A Prometheus exporter that collects metrics from your Nextcloud instance for monitoring and alerting. + +### How to install + +See the [Community Containers documentation](https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers) for instructions on how to install this in your Nextcloud All-in-One setup. + +### Security & Access + +**Important:** This container is configured to bind only to `127.0.0.1` (localhost) for security reasons. Prometheus exporters typically don't include authentication, so direct network exposure is not recommended. + +#### Access Options + +1. **With Caddy Container (Recommended)**: If you also install the [Caddy community container](https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy), it will automatically configure secure HTTPS access to your metrics with authentication at `metrics.your-domain.com` + + **Getting Authentication Credentials**: + - **Username**: Always `metrics` + - **Password**: After deploying the nextcloud-exporter container, the automatically generated password will be displayed in the AIO interface. Look for it in the container section below the container name "Prometheus Nextcloud Exporter". + +2. **Custom Reverse Proxy**: Set up your own reverse proxy (nginx, Apache, etc.) to provide HTTPS and authentication. See configuration guides: + - [NGINX Authentication](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html) + [Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) + - [Apache Authentication](https://httpd.apache.org/docs/2.4/howto/auth.html) + [Reverse Proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html) + - [Traefik BasicAuth](https://doc.traefik.io/traefik/middlewares/http/basicauth/) + - [Prometheus Security Best Practices](https://prometheus.io/docs/operating/security/) + +3. **Direct Local Access**: Access metrics directly from the server at `http://127.0.0.1:9205/metrics` (no authentication) + +### What it monitors +- User activity (active users hourly, daily) +- File counts and storage usage +- System health and database size +- App statistics and update availability +- Nextcloud performance metrics + +### Prometheus Configuration + +For **local server access** (if Prometheus runs on the same server): +```yaml +scrape_configs: + - job_name: 'nextcloud' + scrape_interval: 90s + static_configs: + - targets: ['127.0.0.1:9205'] + metrics_path: /metrics + scheme: http +``` + +For **Caddy integration** (secure external access): +```yaml +scrape_configs: + - job_name: 'nextcloud' + scrape_interval: 90s + static_configs: + - targets: ['metrics.your-domain.com'] + metrics_path: / + scheme: https + basic_auth: + username: 'metrics' + password: 'your-generated-password' +``` + +### Visualization + +Compatible with Grafana for creating monitoring dashboards: +- Pre-built dashboard available: [Grafana Dashboard #20716](https://grafana.com/grafana/dashboards/20716-nextcloud/) + +### Repository +https://github.com/xperimental/nextcloud-exporter + +### Maintainer +https://github.com/grotax diff --git a/community-containers/nocodb/nocodb.json b/community-containers/nocodb/nocodb.json index 8a915c2f..e93d173c 100644 --- a/community-containers/nocodb/nocodb.json +++ b/community-containers/nocodb/nocodb.json @@ -2,10 +2,10 @@ "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": "docjyj/aio-nocodb", - "image_tag": "%AIO_CHANNEL%", + "image": "nocodb/nocodb", + "image_tag": "latest", "internal_port": "10028", "restart": "unless-stopped", "ports": [ @@ -28,6 +28,7 @@ "NOCODB_JWT_SECRET", "NOCODB_USER_PASS" ], + "ui_secret": "NOCODB_USER_PASS", "volumes": [ { "source": "nextcloud_aio_nocodb", diff --git a/community-containers/nocodb/readme.md b/community-containers/nocodb/readme.md index 8d528928..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. > @@ -17,12 +22,12 @@ This is an alternative of **Airtable**. - You need to configure a reverse proxy in order to run this container since nocodb needs a dedicated (sub)domain! For that, you might have a look at https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy. - Currently, only `tables.$NC_DOMAIN` is supported as subdomain! So if Nextcloud is using `your-domain.com`, nocodb will use `tables.your-domain.com`. - The data of NocoDb will be automatically included in AIOs backup solution! -- After adding and starting the container, you need to run `docker inspect nextcloud-aio-nocodb | grep NC_ADMIN_PASS` to obtain the system administrator password (username: `admin@noco.db`). With this information, you can log in to the web interface at `https://tables.$NC_DOMAIN/#/signin` +- After adding and starting the container, you can log in to the web interface at `https://tables.$NC_DOMAIN/#/signin` with the username `admin@noco.db` and the password that you can see in the AIO interface next to the container. - See https://docs.nocodb.com/ for usage of NocoDb - 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/docjyJ/aio-nocodb +https://github.com/nocodb/nocodb ### Maintainer https://github.com/docjyJ 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/npmplus/npmplus.json b/community-containers/npmplus/npmplus.json index 24f1c381..4b666c03 100644 --- a/community-containers/npmplus/npmplus.json +++ b/community-containers/npmplus/npmplus.json @@ -4,7 +4,7 @@ "container_name": "nextcloud-aio-npmplus", "display_name": "NPMplus", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/npmplus", - "image": "zoeyvid/npmplus", + "image": "ghcr.io/zoeyvid/npmplus", "image_tag": "latest", "internal_port": "host", "restart": "unless-stopped", diff --git a/community-containers/npmplus/readme.md b/community-containers/npmplus/readme.md index 654296eb..a71b4af2 100644 --- a/community-containers/npmplus/readme.md +++ b/community-containers/npmplus/readme.md @@ -3,16 +3,14 @@ This container contains a fork of the Nginx Proxy Manager, which is a WebUI for ### Notes - This container is incompatible with the [caddy](https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy) community container. So make sure that you do not enable both at the same time! -- Only works on linux since it uses network mode host -- You can ignore the NPM configuration of the reverse-proxy.md. The NPMplus fork already contains the changes of the advanced tab. - Make sure that no other service is using port `443 (tcp/upd)` or `81 (tcp)` on your host as otherwise the containers will fail to start. You can check this with `sudo netstat -tulpn | grep "443\|81"` before installing AIO. - Please change the default login data first, after you can read inside the logs that the default config for AIO is created and there are no errors. - After the container was started the first time, please check the logs for errors. Then you can open NPMplus on `https://:81` and change the password. -- The default password is `iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi` and the default email is `admin@example.com` +- The default password is `iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi` and the default email is `admin@example.org` - If you want to use NPMplus behind a domain and outside localhost just create a new proxy host inside the NPMplus which proxies to `https`, `127.0.0.1` and port `81` - all other settings should be the same as for the AIO host. -- If you want to set env options from this [compose.yaml](https://github.com/ZoeyVid/NPMplus/blob/develop/compose.yaml), please set them inside the `.env` file which you can find in the `nextcloud_aio_npmplus` volume +- If you want to set env options from this [compose.yaml](https://github.com/ZoeyVid/NPMplus/blob/develop/compose.yaml), please set them inside the `.env` file which you can find in the `nextcloud_aio_npmplus` volume **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 - The data (certs, configs, etc.) of NPMplus will be automatically included in AIOs backup solution! -- **Important:** you always need to enable https for your hosts, since `DISABLE_HTTP` is set to true +- **Important:** you always need to enable https for your hosts, since `DISABLE_HTTP` is set to true by default - See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack ### Repository and Documentation diff --git a/community-containers/pi-hole/pi-hole.json b/community-containers/pi-hole/pi-hole.json index da07fb8c..2cecb9ec 100644 --- a/community-containers/pi-hole/pi-hole.json +++ b/community-containers/pi-hole/pi-hole.json @@ -28,9 +28,9 @@ ], "environment": [ "TZ=%TIMEZONE%", - "WEBPASSWORD=%PIHOLE_WEBPASSWORD%", - "DNSMASQ_LISTENING=all", - "WEB_PORT=8573" + "FTLCONF_webserver_api_password=%PIHOLE_WEBPASSWORD%", + "FTLCONF_dns_listeningMode=all", + "FTLCONF_webserver_port=8573" ], "volumes": [ { @@ -48,6 +48,7 @@ "nextcloud_aio_pihole", "nextcloud_aio_pihole_dnsmasq" ], + "ui_secret": "PIHOLE_WEBPASSWORD", "secrets": [ "PIHOLE_WEBPASSWORD" ] diff --git a/community-containers/pi-hole/readme.md b/community-containers/pi-hole/readme.md index cdfdf641..7254f585 100644 --- a/community-containers/pi-hole/readme.md +++ b/community-containers/pi-hole/readme.md @@ -6,7 +6,7 @@ This container bundles pi-hole and auto-configures it for you. - Make sure that no dns server is already running by checking with `sudo netstat -tulpn | grep 53`. Otherwise the container will not be able to start! - The DHCP functionality of Pi-hole has been disabled! - The data of pi-hole will be automatically included in AIOs backup solution! -- After adding and starting the container, you can visit `http://ip.address.of.this.server:8573/admin` in order to log in with the admin key that you can retrieve when running `sudo docker inspect nextcloud-aio-pihole | grep WEBPASSWORD`. There you can configure the pi-hole setup. Also you can add local dns records. +- After adding and starting the container, you can visit `http://ip.address.of.this.server:8573/admin` in order to log in with the admin key that you can see next to the container in the AIO interface. There you can configure the pi-hole setup. Also you can add local dns records. - You can configure your home network now to use pi-hole as its dns server by configuring your router. - Additionally, you can configure the docker daemon to use that by editing `/etc/docker/daemon.json` and adding ` { "dns" : [ "ip.address.of.this.server" , "8.8.8.8" ] } `. - 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/plex/plex.json b/community-containers/plex/plex.json index 168bc4a9..5fc8712a 100644 --- a/community-containers/plex/plex.json +++ b/community-containers/plex/plex.json @@ -33,6 +33,7 @@ "devices": [ "/dev/dri" ], + "enable_nvidia_gpu": true, "backup_volumes": [ "nextcloud_aio_plex" ] diff --git a/community-containers/plex/readme.md b/community-containers/plex/readme.md index c9553645..7f8434ab 100644 --- a/community-containers/plex/readme.md +++ b/community-containers/plex/readme.md @@ -4,7 +4,6 @@ This container bundles Plex and auto-configures it for you. ### Notes - This container is incompatible with the [Jellyfin](https://github.com/nextcloud/all-in-one/tree/main/community-containers/jellyfin) community container. So make sure that you do not enable both at the same time! - This is not working on arm64 since Plex does only provide x64 docker images. -- This is not working on Docker Desktop since it needs `network_mode: host` in order to work correctly. - This container should usually only be run in home networks as it exposes unencrypted services like DLNA by default which can be disabld via the web interface though. - If you have a firewall like ufw configured, you might need to open all Plex ports in there first in order to make it work. Especially port 32400 is important! - After adding and starting the container, you need to visit http://ip.address.of.server:32400/manage in order to claim your server with a plex account diff --git a/community-containers/readme.md b/community-containers/readme.md index 0708b60f..1631a952 100644 --- a/community-containers/readme.md +++ b/community-containers/readme.md @@ -5,19 +5,16 @@ This directory features containers that are built for AIO which allows to add ad All containers that are in this directory are community maintained so the responsibility is on the community to keep them updated and secure. There is no guarantee that this will be the case in the future. ## How to use this? -Before adding any additional container, make sure to create a backup via the AIO interface! - -Afterwards, you might want to add additional community containers to the default AIO stack. You can do so by adding `--env AIO_COMMUNITY_CONTAINERS="container1 container2"` to the docker run command of the mastercontainer (but before the last line `nextcloud/all-in-one:latest`! If it was started already, you will need to stop the mastercontainer, remove it (no data will be lost) and recreate it using the docker run command that you initially used) and customize the value to your fitting. It must match the folder names in this directory! ⚠️⚠️⚠️ Please review the folder for documentation on each of the containers before adding them! Not reviewing the documentation for each of them first might break starting the AIO containers because e.g. fail2ban only works on Linux and not on Docker Desktop! **Hint:** If the containers where running already, in order to actually start the added container, you need to click on `Stop containers` and the `Update and start containers` in order to actually start it. +Starting with v11 of AIO, the management of Community Containers is done via the AIO interface (it is the last section in the AIO interface, so only visible if you scroll down). +⚠️⚠️⚠️ Please review the folder for documentation on each of the containers before adding them! Not reviewing the documentation for each of them first might break starting the AIO containers because e.g. fail2ban only works on Linux and not on Docker Desktop! **Hint:** If the containers where running already, in order to actually start the added container, you need to click on `Stop containers` and the `Update and start containers` in order to actually start it. ## How to add containers? Simply submit a PR by creating a new folder in this directory: https://github.com/nextcloud/all-in-one/tree/main/community-containers with the name of your container. It must include a json file with the same name and with correct syntax and a readme.md with additional information. You might get inspired by caddy, fail2ban, local-ai, libretranslate, plex, pi-hole or vaultwarden (subfolders in this directory). For a full-blown example of the json file, see https://github.com/nextcloud/all-in-one/blob/main/php/containers.json. The json-schema that it validates against can be found here: https://github.com/nextcloud/all-in-one/blob/main/php/containers-schema.json. ### Is there a list of ideas for new community containers? -Yes, see [this list](https://github.com/nextcloud/all-in-one/discussions/categories/ideas?discussions_q=is%3Aopen+category%3AIdeas+label%3A%22help+wanted%22+sort%3Atop) for already existing ideas for new community containers. Feel free to pick one up and add it to this folder by following the instructions above. +Yes, see [this list](https://github.com/nextcloud/all-in-one/issues/5251) for already existing ideas for new community containers. Feel free to pick one up and add it to this folder by following the instructions above. ## How to remove containers from AIOs stack? -In some cases, you might want to remove some community containers from the AIO stack again. Here is how to do this. +You can remove containers now via the web interface. -First, do a backup from the AIO interface in order to save the current state. Do not start the containers again afterwards! Now simply recreate the mastercontainer and remove any container from the `--env AIO_COMMUNITY_CONTAINERS="container1 container2"` that you do not actually need. If you want to remove all, simply use `--env AIO_COMMUNITY_CONTAINERS=" "`. - -After removing the containers, there might be some data left on your server that you might want to remove. You can get rid of the data by first running `sudo docker rm nextcloud-aio-container1`, (adjust `container1` accordingly) per community-container that you removed. Then run `sudo docker image prune -a` in order to remove all images that are not used anymore. As last step you can get rid of persistent data of these containers that is stored in volumes. You can check if there is some by running `sudo docker volume ls` and look for any volume that matches the ones that you removed. If so, you can remove them with `sudo docker volume rm nextcloud_aio_volume-id` (of course you need to adjust the `volume-id`). +After removing the containers, there might be some data left on your server that you might want to remove. You can get rid of the data by first running `sudo docker rm nextcloud-aio-container1`, (adjust `container1` accordingly) per community-container that you removed. Then run `sudo docker image prune -a` in order to remove all images that are not used anymore. As last step you can get rid of persistent data of these containers that is stored in volumes. You can check if there is some by running `sudo docker volume ls` and look for any volume that matches the ones that you removed. If so, you can remove them with `sudo docker volume rm nextcloud_aio_volume-id` (of course you need to adjust the `volume-id`). **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 diff --git a/community-containers/scrutiny/readme.md b/community-containers/scrutiny/readme.md new file mode 100644 index 00000000..3bb728f7 --- /dev/null +++ b/community-containers/scrutiny/readme.md @@ -0,0 +1,16 @@ +## Scrutiny +This container bundles Scrutiny which is a frontend for SMART stats and auto-configures it for you. + +### Notes +- 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 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 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 +https://github.com/szaimen/aio-scrutiny + +### Maintainer +https://github.com/szaimen diff --git a/community-containers/scrutiny/scrutiny.json b/community-containers/scrutiny/scrutiny.json new file mode 100644 index 00000000..b367e497 --- /dev/null +++ b/community-containers/scrutiny/scrutiny.json @@ -0,0 +1,56 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-scrutiny", + "display_name": "Scrutiny", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/scrutiny", + "image": "ghcr.io/szaimen/aio-scrutiny", + "image_tag": "v2", + "internal_port": "8000", + "init": false, + "restart": "unless-stopped", + "ports": [ + { + "ip_binding": "", + "port_number": "8000", + "protocol": "tcp" + } + ], + "cap_add": [ + "SYS_RAWIO", + "SYS_ADMIN" + ], + "environment": [ + "TZ=%TIMEZONE%", + "SCRUTINY_WEB_LISTEN_PORT=8000", + "COLLECTOR_API_ENDPOINT=http://127.0.0.1:8000" + ], + "volumes": [ + { + "source": "nextcloud_aio_scrutiny", + "destination": "/opt/scrutiny/config", + "writeable": true + }, + { + "source": "nextcloud_aio_scrutiny_db", + "destination": "/opt/scrutiny/influxdb", + "writeable": true + }, + { + "source": "/run/udev", + "destination": "/run/udev", + "writeable": false + }, + { + "source": "/dev", + "destination": "/dev", + "writeable": false + } + ], + "backup_volumes": [ + "nextcloud_aio_scrutiny", + "nextcloud_aio_scrutiny_db" + ] + } + ] +} diff --git a/community-containers/smbserver/readme.md b/community-containers/smbserver/readme.md new file mode 100644 index 00000000..20d90c9f --- /dev/null +++ b/community-containers/smbserver/readme.md @@ -0,0 +1,14 @@ +## SMB-server +This container bundles an SMB-server and allows to configure it via a graphical shell script. + +### Notes +- This container should only be run in home networks +- 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 + +### Repository +https://github.com/szaimen/aio-smbserver/ + +### Maintainer +https://github.com/szaimen diff --git a/community-containers/smbserver/smbserver.json b/community-containers/smbserver/smbserver.json new file mode 100644 index 00000000..d095eb7a --- /dev/null +++ b/community-containers/smbserver/smbserver.json @@ -0,0 +1,60 @@ +{ + "aio_services_v1": [ + { + "container_name": "nextcloud-aio-smbserver", + "display_name": "SMB-server", + "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/smbserver", + "image": "ghcr.io/szaimen/aio-smbserver", + "image_tag": "v1", + "internal_port": "5803", + "restart": "unless-stopped", + "ports": [ + { + "ip_binding": "", + "port_number": "5803", + "protocol": "tcp" + }, + { + "ip_binding": "", + "port_number": "445", + "protocol": "tcp" + }, + { + "ip_binding": "", + "port_number": "139", + "protocol": "tcp" + } + ], + "volumes": [ + { + "source": "nextcloud_aio_smbserver", + "destination": "/smbserver", + "writeable": true + }, + { + "source": "%NEXTCLOUD_DATADIR%", + "destination": "/mnt/ncdata", + "writeable": true + }, + { + "source": "%NEXTCLOUD_MOUNT%", + "destination": "/mnt", + "writeable": true + } + ], + "environment": [ + "TZ=%TIMEZONE%", + "WEB_AUTHENTICATION_USERNAME=smbserver", + "WEB_AUTHENTICATION_PASSWORD=%SMBSERVER_PASSWORD%", + "WEB_LISTENING_PORT=5803" + ], + "secrets": [ + "SMBSERVER_PASSWORD" + ], + "ui_secret": "SMBSERVER_PASSWORD", + "backup_volumes": [ + "nextcloud_aio_smbserver" + ] + } + ] +} diff --git a/community-containers/stalwart/readme.md b/community-containers/stalwart/readme.md index cdd1db04..b34f04db 100644 --- a/community-containers/stalwart/readme.md +++ b/community-containers/stalwart/readme.md @@ -1,23 +1,17 @@ -> [!WARNING] -> The Stalwart server is under development. +> [!CAUTION] +> Be aware that the mail server is the most difficult service to deploy. > -> The stability of Stalwart services is not guaranteed. -> Do not use this feature as a main mail server without a redundancy system and without knowledge. -> -> To learn or use as a secondary server enjoy it and please report bugs at [docjyj/aio-stalwart](https://github.com/docjyj/aio-stalwart/issues). +> Do not use this feature as a main mail server or without a redundancy system and without knowledge. ## Stalwart mail server This container bundles stalwart mail server and auto-configures it for you. ### Notes -- This is only intended to run on a VPS with static ip-address. -- Check with `sudo netstat -tulpn` that no other service is using port 25, 143, 465, 587, 993 nor 4190 yet as otherwise the container will fail to start. -- You need to configure a reverse proxy in order to run this container since stalwart needs a dedicated (sub)domain! For that, you might have a look at https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy. -- Currently, only `mail.$NC_DOMAIN` is supported as subdomain! So if Nextcloud is using `your-domain.com`, Stalwart will use `mail.your-domain.com`. -- The data of Stalwart will be automatically included in AIOs backup solution! -- After adding and starting the container, you need to run `docker inspect nextcloud-aio-stalwart | grep STALWART_USER_PASS` to obtain the system administrator password (username: `admin`). With this information, you can log in to the web interface at `https://mail.your-domain.com/login` -- See https://stalw.art/docs/install/docker/ for next steps. -- Additionally, you might want to install and configure [snappymail](https://apps.nextcloud.com/apps/snappymail) or [mail](https://apps.nextcloud.com/apps/mail) inside Nextcloud in order to use your mail accounts for sending and retrieving mails. +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-stalwart#getting-started for getting start with this container. - See https://stalw.art/docs/faq for further faq and docs on the project - 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/stalwart/stalwart.json b/community-containers/stalwart/stalwart.json index 891bd9da..b9a48091 100644 --- a/community-containers/stalwart/stalwart.json +++ b/community-containers/stalwart/stalwart.json @@ -4,8 +4,8 @@ "container_name": "nextcloud-aio-stalwart", "display_name": "Stalwart", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/stalwart", - "image": "docjyj/aio-stalwart", - "image_tag": "%AIO_CHANNEL%", + "image": "ghcr.io/docjyj/aio-stalwart", + "image_tag": "v3", "internal_port": "10003", "restart": "unless-stopped", "ports": [ @@ -48,11 +48,13 @@ "environment": [ "TZ=%TIMEZONE%", "NC_DOMAIN=%NC_DOMAIN%", - "STALWART_USER_PASS=%STALWART_USER_PASS%" + "STALWART_USER_PASS=%STALWART_USER_PASS%", + "CLAMAV_ENABLED=%CLAMAV_ENABLED%" ], "secrets": [ "STALWART_USER_PASS" ], + "ui_secret": "STALWART_USER_PASS", "volumes": [ { "source": "nextcloud_aio_stalwart", diff --git a/community-containers/stalwart/upgrading.md b/community-containers/stalwart/upgrading.md deleted file mode 100644 index 958f4355..00000000 --- a/community-containers/stalwart/upgrading.md +++ /dev/null @@ -1,29 +0,0 @@ -> [!NOTE] -> Unless the starting script tells you, you have no action to do to update. - -# UPGRADING - -During a major server update, this message will be displayed: - -> Your data is in an old format. -> -> Make a backup and see https://github.com/nextcloud/all-in-one/blob/main/community-containers/stalwart/upgrading.md -> -> To avoid any loss of data, Stalwart will not launch. - -If there is no update, delete the `/opt/stalwart-mail/aio.lock` file from the container. Beware of data loss. - -See https://github.com/stalwartlabs/mail-server/blob/main/UPGRADING.md - -> [!CAUTION] -> Before each update don't forget to make a backup. - -## Upgrading from 0.7.x to 0.8.x - -Before upgrading, do a backup of your data ! - -```bash -sudo docker run --rm -v nextcloud_aio_stalwart:/opt/stalwart-mail -it --entrypoint /usr/local/bin/stalwart-mail stalwartlabs/mail-server:v0.7.3 --config /opt/stalwart-mail/etc/config.toml --export /opt/stalwart-mail/export -sudo docker run --rm -v nextcloud_aio_stalwart:/opt/stalwart-mail -it --entrypoint /usr/local/bin/stalwart-mail stalwartlabs/mail-server:v0.8.0 --config /opt/stalwart-mail/etc/config.toml --import /opt/stalwart-mail/export -sudo docker run --rm -v nextcloud_aio_stalwart:/opt/stalwart-mail -it --entrypoint /bin/rm alpine /opt/stalwart-mail/aio.lock -``` diff --git a/community-containers/vaultwarden/readme.md b/community-containers/vaultwarden/readme.md index 450f8838..81f37010 100644 --- a/community-containers/vaultwarden/readme.md +++ b/community-containers/vaultwarden/readme.md @@ -6,7 +6,7 @@ This container bundles vaultwarden and auto-configures it for you. - Currently, only `bw.$NC_DOMAIN` is supported as subdomain! So if Nextcloud is using `your-domain.com`, vaultwarden will use `bw.your-domain.com`. The reverse proxy and domain must be configured accordingly! - If you want to secure the installation with fail2ban, you might want to check out https://github.com/nextcloud/all-in-one/tree/main/community-containers/fail2ban - The data of Vaultwarden will be automatically included in AIOs backup solution! -- After adding and starting the container, you need to visit `https://bw.your-domain.com/admin` in order to log in with the admin key that you can retrieve when running `sudo docker inspect nextcloud-aio-vaultwarden | grep ADMIN_TOKEN`. There you can configure smtp first and then invite users via mail. After this is done, you might disable the admin panel via the reverse proxy by blocking connections to the subdirectory. +- After adding and starting the container, you need to visit `https://bw.your-domain.com/admin` in order to log in with the admin key that you can see next to the container in the AIO interface. There you can configure smtp first and then invite users via mail. After this is done, you might disable the admin panel via the reverse proxy by blocking connections to the subdirectory. - If using the caddy community container, the vaultwarden admin interface can be disabled by creating a `block-vaultwarden-admin` file in the `nextcloud-aio-caddy` folder when you open the Nextcloud files app with the default `admin` user. Afterwards restart all containers from the AIO interface and the admin interface should be disabled! You can unlock the admin interface by removing the file again and afterwards restarting the containers via the AIO interface. - 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/vaultwarden/vaultwarden.json b/community-containers/vaultwarden/vaultwarden.json index 0bacff7a..b94996fc 100644 --- a/community-containers/vaultwarden/vaultwarden.json +++ b/community-containers/vaultwarden/vaultwarden.json @@ -4,7 +4,7 @@ "container_name": "nextcloud-aio-vaultwarden", "display_name": "Vaultwarden", "documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/vaultwarden", - "image": "vaultwarden/server", + "image": "ghcr.io/dani-garcia/vaultwarden", "image_tag": "alpine", "internal_port": "8812", "restart": "unless-stopped", @@ -40,6 +40,7 @@ "backup_volumes": [ "nextcloud_aio_vaultwarden" ], + "ui_secret": "VAULTWARDEN_ADMIN_TOKEN", "secrets": [ "VAULTWARDEN_ADMIN_TOKEN" ] diff --git a/compose.yaml b/compose.yaml index f3b53452..c18d92d3 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,53 +1,81 @@ +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: nextcloud/all-in-one:latest - init: true - restart: always + 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 + init: true # This setting makes sure that signals from main process inside the container are correctly forwarded to children. See https://docs.docker.com/reference/compose-file/services/#init + restart: always # This makes sure that the container starts always together with the host OS. See https://docs.docker.com/reference/compose-file/services/#restart container_name: nextcloud-aio-mastercontainer # This line is not allowed to be changed as otherwise AIO will not work correctly volumes: - nextcloud_aio_mastercontainer:/mnt/docker-aio-config # This line is not allowed to be changed as otherwise the built-in backup solution will not work - /var/run/docker.sock:/var/run/docker.sock:ro # May be changed on macOS, Windows or docker rootless. See the applicable documentation. If adjusting, don't forget to also set 'WATCHTOWER_DOCKER_SOCKET_PATH'! - network_mode: bridge # add to the same network as docker run would do + network_mode: bridge # This adds the container to the same network as docker run would do. Comment this line and uncomment the line below and the networks section at the end of the file if you want to define a custom MTU size for the docker network + # networks: ["nextcloud-aio"] ports: - 80:80 # Can be removed when running behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else). See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md - - 8080:8080 + - 8080:8080 # This is the AIO interface, served via https and self-signed certificate. See https://github.com/nextcloud/all-in-one#explanation-of-used-ports - 8443:8443 # Can be removed when running behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else). See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md + # security_opt: ["label:disable"] # Is needed when using SELinux. See https://github.com/nextcloud/all-in-one#are-there-known-problems-when-selinux-is-enabled # environment: # Is needed when using any of the options below # AIO_DISABLE_BACKUP_SECTION: false # Setting this to true allows to hide the backup section in the AIO interface. See https://github.com/nextcloud/all-in-one#how-to-disable-the-backup-section # APACHE_PORT: 11000 # Is needed when running behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else). See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md # APACHE_IP_BINDING: 127.0.0.1 # Should be set when running behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else) that is running on the same host. See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md + # 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 - # NEXTCLOUD_UPLOAD_LIMIT: 10G # Can be adjusted if you need more. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-upload-limit-for-nextcloud + # NEXTCLOUD_UPLOAD_LIMIT: 16G # Can be adjusted if you need more. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-upload-limit-for-nextcloud # NEXTCLOUD_MAX_TIME: 3600 # Can be adjusted if you need more. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-max-execution-time-for-nextcloud # NEXTCLOUD_MEMORY_LIMIT: 512M # Can be adjusted if you need more. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-php-memory-limit-for-nextcloud - # NEXTCLOUD_TRUSTED_CACERTS_DIR: /path/to/my/cacerts # CA certificates in this directory will be trusted by the OS of the nexcloud container (Useful e.g. for LDAPS) See See https://github.com/nextcloud/all-in-one#how-to-trust-user-defined-certification-authorities-ca + # NEXTCLOUD_TRUSTED_CACERTS_DIR: /path/to/my/cacerts # CA certificates in this directory will be trusted by the OS of the nextcloud container (Useful e.g. for LDAPS) See https://github.com/nextcloud/all-in-one#how-to-trust-user-defined-certification-authorities-ca # NEXTCLOUD_STARTUP_APPS: deck twofactor_totp tasks calendar contacts notes # Allows to modify the Nextcloud apps that are installed on starting AIO the first time. See https://github.com/nextcloud/all-in-one#how-to-change-the-nextcloud-apps-that-are-installed-on-the-first-startup # NEXTCLOUD_ADDITIONAL_APKS: imagemagick # This allows to add additional packages to the Nextcloud container permanently. Default is imagemagick but can be overwritten by modifying this value. See https://github.com/nextcloud/all-in-one#how-to-add-os-packages-permanently-to-the-nextcloud-container # NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS: imagick # This allows to add additional php extensions to the Nextcloud container permanently. Default is imagick but can be overwritten by modifying this value. See https://github.com/nextcloud/all-in-one#how-to-add-php-extensions-permanently-to-the-nextcloud-container - # NEXTCLOUD_ENABLE_DRI_DEVICE: true # This allows to enable the /dev/dri device in the Nextcloud container. ⚠️⚠️⚠️ 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-transcoding-for-nextcloud + # 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 - # TALK_PORT: 3478 # This allows to adjust the port that the talk container is using. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-talk-port + # 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' - # security_opt: ["label:disable"] # Is needed when using SELinux - # # Optional: Caddy reverse proxy. See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md - # # You can find further examples here: https://github.com/nextcloud/all-in-one/discussions/588 - # caddy: - # image: caddy:alpine - # restart: always - # container_name: caddy - # volumes: - # - ./Caddyfile:/etc/caddy/Caddyfile - # - ./certs:/certs - # - ./config:/config - # - ./data:/data - # - ./sites:/srv - # network_mode: "host" +# # Optional: Caddy reverse proxy. See https://github.com/nextcloud/all-in-one/discussions/575 +# # Alternatively, use Tailscale if you don't have a domain yet. See https://github.com/nextcloud/all-in-one/discussions/6817 +# # Hint: You need to uncomment APACHE_PORT: 11000 above, adjust cloud.example.com to your domain and uncomment the necessary docker volumes at the bottom of this file in order to make it work +# # You can find further examples here: https://github.com/nextcloud/all-in-one/discussions/588 +# caddy: +# image: caddy:alpine +# restart: always +# container_name: caddy +# volumes: +# - caddy_certs:/certs +# - caddy_config:/config +# - caddy_data:/data +# - caddy_sites:/srv +# network_mode: "host" +# configs: +# - source: Caddyfile +# target: /etc/caddy/Caddyfile +# configs: +# Caddyfile: +# content: | +# # Adjust cloud.example.com to your domain below +# https://cloud.example.com:443 { +# reverse_proxy localhost:11000 +# } volumes: # If you want to store the data on a different drive, see https://github.com/nextcloud/all-in-one#how-to-store-the-filesinstallation-on-a-separate-drive nextcloud_aio_mastercontainer: name: nextcloud_aio_mastercontainer # This line is not allowed to be changed as otherwise the built-in backup solution will not work + # caddy_certs: + # caddy_config: + # caddy_data: + # caddy_sites: +# # Adjust the MTU size of the docker network. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-mtu-size-of-the-docker-network +# networks: +# nextcloud-aio: +# name: nextcloud-aio +# driver_opts: +# com.docker.network.driver.mtu: 1440 diff --git a/develop.md b/develop.md index 08aa9e8a..457b3a76 100644 --- a/develop.md +++ b/develop.md @@ -11,7 +11,7 @@ sudo docker run \ --publish 8443:8443 \ --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ -nextcloud/all-in-one:develop +ghcr.io/nextcloud-releases/all-in-one:develop ``` And you are done :) It will now also select the developer channel for all other containers automatically. @@ -19,14 +19,19 @@ It will now also select the developer channel for all other containers automatic ## How to publish new releases? Simply use https://github.com/nextcloud/all-in-one/issues/180 as template. +## How to update existing instances to a new major Nextcloud version? +Simply use https://github.com/nextcloud/all-in-one/issues/6198 as template. + ## How to build new containers Go to https://github.com/nextcloud-releases/all-in-one/actions/workflows/repo-sync.yml and run the workflow that will first sync the repo and then build new container that automatically get published to `develop` and `develop-arm64`. ## How to test things correctly? -Before testing, make sure that at least the amd64 containers are built successfully by checking the last workflow here: https://github.com/nextcloud-releases/all-in-one/actions/workflows/build_images.yml. +Before testing, make sure that at least the amd64 containers are built successfully by checking the last workflow here: https://github.com/nextcloud-releases/all-in-one/actions/workflows/build_images.yml. There is a testing-VM available for the maintainer of AIO that allows for some final testing before releasing new version. See [this](https://cloud.nextcloud.com/apps/collectives/Nextcloud%20Handbook/Technical/AIO%20testing%20VM?fileId=6350152) for details. +Additionally, there are now E2E tests available that can be run via https://github.com/nextcloud/all-in-one/actions/workflows/playwright.yml + ## How to promote builds from develop to beta 1. Verify that no job is running here: https://github.com/nextcloud-releases/all-in-one/actions/workflows/build_images.yml 2. Go to https://github.com/nextcloud-releases/all-in-one/actions/workflows/promote-to-beta.yml, click on `Run workflow`. @@ -36,8 +41,32 @@ This is documented here: https://github.com/nextcloud-releases/all-in-one/tree/m ## How to promote builds from beta to latest +1. Verify that GitHub Services are running correctly: https://www.githubstatus.com/ 1. Verify that no job is running here: https://github.com/nextcloud-releases/all-in-one/actions/workflows/promote-to-beta.yml -2. Go to https://github.com/nextcloud-releases/all-in-one/actions/workflows/promote-to-latest.yml, click on `Run workflow`. +1. Go to https://github.com/nextcloud-releases/all-in-one/actions/workflows/promote-to-latest.yml, click on `Run workflow`. ## How to connect to the database? Simply run `sudo docker exec -it nextcloud-aio-database psql -U oc_nextcloud nextcloud_database` and you should be in. + +## How to locally build and test changes to mastercontainer +1. Ensure you are on the developer channel per the instructions above. +1. Use the commands below from the project root to build the mastercontainer image: +``` +docker buildx build --file Containers/mastercontainer/Dockerfile --tag ghcr.io/nextcloud-releases/all-in-one:develop --load . +``` +1. Start a container with above built image. +1. Since the hash of a locally built image doesn't match the latest release mastercontainer, it prompts for a mandatory update. To temporarily bypass the update suffix `?bypass_mastercontainer_update` to the URL. Eg: `https://localhost:8080/containers?bypass_mastercontainer_update` + +## How to locally build and test changes to other containers using the bypass_container_update param +1. Ensure you are on the developer channel per the instructions above. +1. Use the commands below from the project root to build the container image: +``` +# For the "nextcloud" container +docker buildx build --file Containers/nextcloud/Dockerfile --tag ghcr.io/nextcloud-releases/aio-nextcloud:develop --load . + +# 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 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/docker-ipv6-support.md b/docker-ipv6-support.md index 3a3dbb33..a784e7c7 100644 --- a/docker-ipv6-support.md +++ b/docker-ipv6-support.md @@ -4,19 +4,23 @@ First of all upgrade your docker installation to v27.0.1 or higher. 1. Then edit `/etc/docker/daemon.json` (or `~/.config/docker/daemon.json` in case of docker-rootless), add the below json: - ```json - { - "default-network-opts": {"bridge":{"com.docker.network.enable_ipv6":"true"}} - } - ``` +> [!WARNING] +> This will enable ipv6 for all new docker networks by default! You can alternatively create the `nextcloud-aio` network with ipv6 support by hand manually via docker network create or via compose.yaml. - Save the file. +```json +{ + "default-network-opts": {"bridge":{"com.docker.network.enable_ipv6":"true"}} +} +``` + +And save the file. 2. Reload the Docker configuration file. - ```console - sudo systemctl restart docker - ``` +```console +sudo systemctl restart docker +``` + 3. Make sure that ipv6 is enabled for the internal `nextcloud-aio` network by running `sudo docker network inspect nextcloud-aio | grep EnableIPv6`. On a new instance, this command should return that it did not find a network with this name. Then you can run `sudo docker network create nextcloud-aio` in order to create the network with ipv6-support. However if it finds the network and its value `EnableIPv6` is set to false, make sure to follow https://github.com/nextcloud/all-in-one/discussions/4989 in order to recreate the network and enable ipv6 for it. ## Docker Desktop (Windows and macOS) @@ -25,9 +29,12 @@ Then, on Windows and macOS which use Docker Desktop, you need to go into the set 1. You need to now adjust this json file: - ``` - "default-network-opts": {"bridge":{"com.docker.network.enable_ipv6":"true"}} - ``` +> [!WARNING] +> This will enable ipv6 for all new docker networks by default! You can alternatively create the `nextcloud-aio` network with ipv6 support by hand manually via docker network create or via compose.yaml. + +```json +"default-network-opts": {"bridge":{"com.docker.network.enable_ipv6":"true"}} +``` 2. Add these values to the json and make sure to keep the other currently values and that you don't see `Unexpected token in JSON at position ...` before attempting to restart by clicking on `Apply & restart`. 3. Make sure that ipv6 is enabled for the internal `nextcloud-aio` network by running `sudo docker network inspect nextcloud-aio | grep EnableIPv6`. On a new instance, this command should return that it did not find a network with this name. Then you can run `sudo docker network create nextcloud-aio` in order to create the network with ipv6-support. However if it finds the network and its value `EnableIPv6` is set to false, make sure to follow https://github.com/nextcloud/all-in-one/discussions/4989 in order to recreate the network and enable ipv6 for it. diff --git a/docker-rootless.md b/docker-rootless.md index 754c44ae..f77b4a54 100644 --- a/docker-rootless.md +++ b/docker-rootless.md @@ -18,7 +18,8 @@ You can run AIO with docker rootless by following the steps below. Almost all commands in this project's documentation use `sudo docker ...`. Since `sudo` is not needed in case of docker rootless, you simply remove `sudo` from the commands and they should work. ### Note regarding permissions -All files outside the containers get created, written to and accessed as the user that is running the docker daemon or a subuid of it. So for the built-in backup to work you need to allow this user to write to the target directory. E.g. with `sudo chown -R USERNAME:GROUPNAME /mnt/backup`. The same applies when changing Nextcloud's datadir. E.g. `sudo chown -R USERNAME:GROUPNAME /mnt/ncdata`. When you want to use the NEXTCLOUD_MOUNT option for local external storage, you need to adjust the permissions of the chosen folders to be accessible/writeable by the userid `100032:100032` (if running `grep ^$(whoami): /etc/subuid` as the user that is running the docker daemon returns 100000 as first value). +All files outside the containers get created, written to and accessed as the user that is running the docker daemon or a subuid of it. So for the built-in backup to work you need to allow this user to write to the target directory. E.g. with `sudo chown -R USERNAME:GROUPNAME /mnt/backup`. The same applies when changing Nextcloud's datadir via NEXTCLOUD_DATADIR. E.g. `sudo chown -R USERNAME:GROUPNAME /mnt/ncdata`. When you want to use the NEXTCLOUD_MOUNT option for local external storage, you need to adjust the permissions of the chosen folders to be accessible/writeable by the userid `100032:100032` (if running `grep ^$(whoami): /etc/subuid` as the user that is running the docker daemon returns 100000 as first value). + ### Note regarding docker network driver By default rootless docker uses the `slirp4netns` IP driver and the `builtin` port driver. As mentioned in [the documentation](https://docs.docker.com/engine/security/rootless/#networking-errors), this combination doesn't provide "Source IP propagation". This means that Apache and Nextcloud will see all connections as coming from the docker gateway (e.g 172.19.0.1), which can lead to the Nextcloud brute force protection blocking all connection attempts. To expose the correct source IP, you will need to configure docker to also use `slirp4netns` as the port driver (see also [this guide](https://rootlesscontaine.rs/getting-started/docker/#changing-the-port-forwarder)). @@ -29,9 +30,10 @@ As stated in the documentation, this change will likely lead to decreased networ with the following content: ``` [Service] + Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns" Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns" ``` * Restart the docker daemon ``` systemctl --user restart docker - ``` \ No newline at end of file + ``` diff --git a/local-instance.md b/local-instance.md index aa4bd8e6..8abbddb6 100644 --- a/local-instance.md +++ b/local-instance.md @@ -1,19 +1,32 @@ # Local instance -It is possible due to several reasons that you do not want or cannot open Nextcloud to the public internet. However AIO requires a valid certificate to work correctly. Below is discussed how you can achieve both: Having a valid certificate for Nextcloud and only using it locally. +It is possible due to several reasons that you do not want or cannot open Nextcloud to the public internet. Perhaps you were hoping to access AIO directly from an `ip.add.r.ess` (unsupported) or without a valid domain. However, AIO requires a valid certificate to work correctly. Below is discussed how you can achieve both: Having a valid certificate for Nextcloud and only using it locally. -## 1. The recommended way -The recommended way is the following: +### Content +- [1. Tailscale](#1-tailscale) +- [2. The normal way](#2-the-normal-way) +- [3. Use the ACME DNS-challenge](#3-use-the-acme-dns-challenge) +- [4. Use Cloudflare](#4-use-cloudflare) +- [5. Buy a certificate and use that](#5-buy-a-certificate-and-use-that) + +## 1. Tailscale +This is the recommended way. 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 + +## 2. The normal way +The normal way is the following: 1. Set up your domain correctly to point to your home network 1. Set up a reverse proxy by following the [reverse proxy documentation](./reverse-proxy.md) but only open port 80 (which is needed for the ACME challenge to work - however no real traffic will use this port). 1. Set up a local DNS-server like a pi-hole and configure it to be your local DNS-server for the whole network. Then in the Pi-hole interface, add a custom DNS-record for your domain and overwrite the A-record (and possibly the AAAA-record, too) to point to the private ip-address of your reverse proxy (see https://github.com/nextcloud/all-in-one#how-can-i-access-nextcloud-locally) 1. Enter the ip-address of your local dns-server in the daemon.json file for docker so that you are sure that all docker containers use the correct local dns-server. 1. Now, entering the domain in the AIO-interface should work as expected and should allow you to continue with the setup -## 2. 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 +**Hint:** You may have a look at [this video](https://youtu.be/zk-y2wVkY4c) for a more complete but possibly outdated example. -## 3. Use Cloudflare +## 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 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. -## 4. Buy a certificate and use that +## 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 f92f0288..e9362ccc 100644 --- a/manual-install/latest.yml +++ b/manual-install/latest.yml @@ -10,35 +10,45 @@ services: nextcloud-aio-talk: condition: service_started required: false - nextcloud-aio-nextcloud: - condition: service_started - required: false nextcloud-aio-notify-push: condition: service_started required: false - image: nextcloud/aio-apache:latest + nextcloud-aio-whiteboard: + condition: service_started + required: false + nextcloud-aio-nextcloud: + condition: service_started + required: false + image: ghcr.io/nextcloud-releases/aio-apache:latest + user: "33" init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 ports: - ${APACHE_IP_BINDING}:${APACHE_PORT}:${APACHE_PORT}/tcp - ${APACHE_IP_BINDING}:${APACHE_PORT}:${APACHE_PORT}/udp environment: - - NC_DOMAIN=${NC_DOMAIN} + - NC_DOMAIN - NEXTCLOUD_HOST=nextcloud-aio-nextcloud - APACHE_HOST=nextcloud-aio-apache - COLLABORA_HOST=nextcloud-aio-collabora - TALK_HOST=nextcloud-aio-talk - - APACHE_PORT=${APACHE_PORT} + - APACHE_PORT - ONLYOFFICE_HOST=nextcloud-aio-onlyoffice - TZ=${TIMEZONE} - - APACHE_MAX_SIZE=${APACHE_MAX_SIZE} + - APACHE_MAX_SIZE - APACHE_MAX_TIME=${NEXTCLOUD_MAX_TIME} - NOTIFY_PUSH_HOST=nextcloud-aio-notify-push + - WHITEBOARD_HOST=nextcloud-aio-whiteboard volumes: - nextcloud_aio_nextcloud:/var/www/html:ro - nextcloud_aio_apache:/mnt/data:rw restart: unless-stopped - networks: - - nextcloud-aio read_only: true tmpfs: - /var/log/supervisord @@ -50,8 +60,16 @@ services: - NET_RAW nextcloud-aio-database: - image: nextcloud/aio-postgresql:latest + image: ghcr.io/nextcloud-releases/aio-postgresql:latest + user: "999" init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 expose: - "5432" volumes: @@ -66,8 +84,6 @@ services: stop_grace_period: 1800s restart: unless-stopped shm_size: 268435456 - networks: - - nextcloud-aio read_only: true tmpfs: - /var/run/postgresql @@ -94,8 +110,15 @@ services: nextcloud-aio-imaginary: condition: service_started required: false - image: nextcloud/aio-nextcloud:latest + image: ghcr.io/nextcloud-releases/aio-nextcloud:latest init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 expose: - "9000" - "9001" @@ -105,71 +128,87 @@ services: - ${NEXTCLOUD_MOUNT}:${NEXTCLOUD_MOUNT}:rw - ${NEXTCLOUD_TRUSTED_CACERTS_DIR}:/usr/local/share/ca-certificates:ro environment: + - NEXTCLOUD_HOST=nextcloud-aio-nextcloud - POSTGRES_HOST=nextcloud-aio-database - POSTGRES_PORT=5432 - POSTGRES_PASSWORD=${DATABASE_PASSWORD} - POSTGRES_DB=nextcloud_database - POSTGRES_USER=nextcloud - REDIS_HOST=nextcloud-aio-redis + - REDIS_PORT=6379 - REDIS_HOST_PASSWORD=${REDIS_PASSWORD} - - NC_DOMAIN=${NC_DOMAIN} + - APACHE_HOST=nextcloud-aio-apache + - APACHE_PORT + - NC_DOMAIN - ADMIN_USER=admin - ADMIN_PASSWORD=${NEXTCLOUD_PASSWORD} - NEXTCLOUD_DATA_DIR=/mnt/ncdata - OVERWRITEHOST=${NC_DOMAIN} - OVERWRITEPROTOCOL=https - - TURN_SECRET=${TURN_SECRET} - - SIGNALING_SECRET=${SIGNALING_SECRET} - - ONLYOFFICE_SECRET=${ONLYOFFICE_SECRET} - - NEXTCLOUD_MOUNT=${NEXTCLOUD_MOUNT} - - CLAMAV_ENABLED=${CLAMAV_ENABLED} + - TURN_SECRET + - SIGNALING_SECRET + - ONLYOFFICE_SECRET + - NEXTCLOUD_MOUNT + - CLAMAV_ENABLED - CLAMAV_HOST=nextcloud-aio-clamav - - ONLYOFFICE_ENABLED=${ONLYOFFICE_ENABLED} - - COLLABORA_ENABLED=${COLLABORA_ENABLED} + - ONLYOFFICE_ENABLED + - COLLABORA_ENABLED - COLLABORA_HOST=nextcloud-aio-collabora - - TALK_ENABLED=${TALK_ENABLED} + - TALK_ENABLED - ONLYOFFICE_HOST=nextcloud-aio-onlyoffice - - UPDATE_NEXTCLOUD_APPS=${UPDATE_NEXTCLOUD_APPS} + - UPDATE_NEXTCLOUD_APPS - TZ=${TIMEZONE} - - TALK_PORT=${TALK_PORT} - - IMAGINARY_ENABLED=${IMAGINARY_ENABLED} + - 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_ENABLED} + - FULLTEXTSEARCH_ENABLED - FULLTEXTSEARCH_HOST=nextcloud-aio-fulltextsearch + - FULLTEXTSEARCH_PROTOCOL=http + - FULLTEXTSEARCH_PORT=9200 + - FULLTEXTSEARCH_USER=elastic + - FULLTEXTSEARCH_INDEX=nextcloud-aio - PHP_MAX_TIME=${NEXTCLOUD_MAX_TIME} - TRUSTED_CACERTS_DIR=${NEXTCLOUD_TRUSTED_CACERTS_DIR} - STARTUP_APPS=${NEXTCLOUD_STARTUP_APPS} - ADDITIONAL_APKS=${NEXTCLOUD_ADDITIONAL_APKS} - ADDITIONAL_PHP_EXTENSIONS=${NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS} - - INSTALL_LATEST_MAJOR=${INSTALL_LATEST_MAJOR} - - TALK_RECORDING_ENABLED=${TALK_RECORDING_ENABLED} - - RECORDING_SECRET=${RECORDING_SECRET} + - INSTALL_LATEST_MAJOR + - TALK_RECORDING_ENABLED + - RECORDING_SECRET - TALK_RECORDING_HOST=nextcloud-aio-talk-recording - - FULLTEXTSEARCH_PASSWORD=${FULLTEXTSEARCH_PASSWORD} - - REMOVE_DISABLED_APPS=${REMOVE_DISABLED_APPS} - - APACHE_PORT=${APACHE_PORT} - - IMAGINARY_SECRET=${IMAGINARY_SECRET} + - FULLTEXTSEARCH_PASSWORD + - REMOVE_DISABLED_APPS + - IMAGINARY_SECRET + - WHITEBOARD_SECRET + - WHITEBOARD_ENABLED stop_grace_period: 600s restart: unless-stopped - networks: - - nextcloud-aio cap_drop: - NET_RAW nextcloud-aio-notify-push: - image: nextcloud/aio-notify-push:latest + image: ghcr.io/nextcloud-releases/aio-notify-push:latest + user: "33" init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 expose: - "7867" volumes: - nextcloud_aio_nextcloud:/nextcloud:ro environment: - - NC_DOMAIN=${NC_DOMAIN} + - NC_DOMAIN - 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 @@ -177,15 +216,21 @@ services: - POSTGRES_DB=nextcloud_database - POSTGRES_USER=nextcloud restart: unless-stopped - networks: - - nextcloud-aio read_only: true cap_drop: - NET_RAW nextcloud-aio-redis: - image: nextcloud/aio-redis:latest + image: ghcr.io/nextcloud-releases/aio-redis:latest + user: "999" init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 expose: - "6379" environment: @@ -194,20 +239,26 @@ services: volumes: - nextcloud_aio_redis:/data:rw restart: unless-stopped - networks: - - nextcloud-aio read_only: true cap_drop: - NET_RAW nextcloud-aio-collabora: - image: nextcloud/aio-collabora:latest + command: ${ADDITIONAL_COLLABORA_OPTIONS} + image: ghcr.io/nextcloud-releases/aio-collabora:latest init: true + healthcheck: + start_period: 60s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 9 expose: - "9980" environment: - - aliasgroup1=https://${NC_DOMAIN}:443 - - extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:mount_jail_tree=false --o:logging.level=warning --o:home_mode.enable=true ${COLLABORA_SECCOMP_POLICY} --o:remote_font_config.url=https://${NC_DOMAIN}/apps/richdocuments/settings/fonts.json + - aliasgroup1=https://${NC_DOMAIN}:443,http://nextcloud-aio-apache:23973 + - 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} @@ -215,36 +266,43 @@ services: restart: unless-stopped profiles: - collabora - networks: - - nextcloud-aio cap_add: - MKNOD - SYS_ADMIN + - SYS_CHROOT + - FOWNER + - CHOWN cap_drop: - NET_RAW nextcloud-aio-talk: - image: nextcloud/aio-talk:latest + image: ghcr.io/nextcloud-releases/aio-talk:latest + user: "1000" init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 ports: - ${TALK_PORT}:${TALK_PORT}/tcp - ${TALK_PORT}:${TALK_PORT}/udp expose: - "8081" environment: - - NC_DOMAIN=${NC_DOMAIN} + - NC_DOMAIN - TALK_HOST=nextcloud-aio-talk - - TURN_SECRET=${TURN_SECRET} - - SIGNALING_SECRET=${SIGNALING_SECRET} + - TURN_SECRET + - SIGNALING_SECRET - TZ=${TIMEZONE} - - TALK_PORT=${TALK_PORT} + - TALK_PORT - INTERNAL_SECRET=${TALK_INTERNAL_SECRET} restart: unless-stopped profiles: - talk - talk-recording - networks: - - nextcloud-aio read_only: true tmpfs: - /var/log/supervisord @@ -256,55 +314,76 @@ services: - NET_RAW nextcloud-aio-talk-recording: - image: nextcloud/aio-talk-recording:latest + image: ghcr.io/nextcloud-releases/aio-talk-recording:latest + user: "122" init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 expose: - "1234" environment: - - NC_DOMAIN=${NC_DOMAIN} + - NC_DOMAIN - TZ=${TIMEZONE} - - RECORDING_SECRET=${RECORDING_SECRET} + - RECORDING_SECRET - INTERNAL_SECRET=${TALK_INTERNAL_SECRET} + volumes: + - nextcloud_aio_talk_recording:/tmp:rw shm_size: 2147483648 restart: unless-stopped profiles: - talk-recording - networks: - - nextcloud-aio read_only: true tmpfs: - - /tmp - /conf cap_drop: - NET_RAW nextcloud-aio-clamav: - image: nextcloud/aio-clamav:latest + image: ghcr.io/nextcloud-releases/aio-clamav:latest + user: "100" init: false + healthcheck: + start_period: 60s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 9 expose: - "3310" environment: - TZ=${TIMEZONE} - MAX_SIZE=${NEXTCLOUD_UPLOAD_LIMIT} - - CLAMD_STARTUP_TIMEOUT=90 volumes: - nextcloud_aio_clamav:/var/lib/clamav:rw restart: unless-stopped profiles: - clamav - networks: - - nextcloud-aio read_only: true tmpfs: - - /var/lock - - /var/log/clamav - /tmp + - /var/log/clamav + - /run/clamav + - /var/log/supervisord + - /var/run/supervisord cap_drop: - NET_RAW nextcloud-aio-onlyoffice: - image: nextcloud/aio-onlyoffice:latest + image: ghcr.io/nextcloud-releases/aio-onlyoffice:latest init: true + healthcheck: + start_period: 60s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 9 expose: - "80" environment: @@ -317,19 +396,25 @@ services: restart: unless-stopped profiles: - onlyoffice - networks: - - nextcloud-aio cap_drop: - NET_RAW nextcloud-aio-imaginary: - image: nextcloud/aio-imaginary:latest + image: ghcr.io/nextcloud-releases/aio-imaginary:latest + user: "65534" init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 expose: - "9000" environment: - TZ=${TIMEZONE} - - IMAGINARY_SECRET=${IMAGINARY_SECRET} + - IMAGINARY_SECRET restart: unless-stopped cap_add: - SYS_NICE @@ -337,35 +422,69 @@ services: - NET_RAW profiles: - imaginary - networks: - - nextcloud-aio read_only: true tmpfs: - /tmp nextcloud-aio-fulltextsearch: - image: nextcloud/aio-fulltextsearch:latest + image: ghcr.io/nextcloud-releases/aio-fulltextsearch:latest init: false + healthcheck: + start_period: 60s + test: /healthcheck.sh + interval: 10s + timeout: 5s + start_interval: 5s + retries: 5 expose: - "9200" environment: - TZ=${TIMEZONE} - - ES_JAVA_OPTS=-Xms512M -Xmx512M - - bootstrap.memory_lock=true + - ES_JAVA_OPTS=${FULLTEXTSEARCH_JAVA_OPTIONS} + - bootstrap.memory_lock=false - cluster.name=nextcloud-aio - discovery.type=single-node - - logger.org.elasticsearch.discovery=WARN + - logger.level=WARN - http.port=9200 - xpack.license.self_generated.type=basic - xpack.security.enabled=false - - FULLTEXTSEARCH_PASSWORD=${FULLTEXTSEARCH_PASSWORD} + - FULLTEXTSEARCH_PASSWORD volumes: - nextcloud_aio_elasticsearch:/usr/share/elasticsearch/data:rw restart: unless-stopped profiles: - fulltextsearch - networks: - - nextcloud-aio + cap_drop: + - NET_RAW + + nextcloud-aio-whiteboard: + image: ghcr.io/nextcloud-releases/aio-whiteboard:latest + user: "65534" + init: true + healthcheck: + start_period: 0s + test: /healthcheck.sh + interval: 30s + timeout: 30s + start_interval: 5s + retries: 3 + expose: + - "3002" + tmpfs: + - /tmp + environment: + - TZ=${TIMEZONE} + - NEXTCLOUD_URL=https://${NC_DOMAIN} + - 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 + profiles: + - whiteboard + read_only: true cap_drop: - NET_RAW @@ -386,9 +505,11 @@ volumes: name: nextcloud_aio_onlyoffice nextcloud_aio_redis: name: nextcloud_aio_redis + nextcloud_aio_talk_recording: + name: nextcloud_aio_talk_recording nextcloud_aio_nextcloud_data: name: nextcloud_aio_nextcloud_data networks: - nextcloud-aio: - name: nextcloud-aio + default: + driver: bridge diff --git a/manual-install/readme.md b/manual-install/readme.md index 26e802e9..ea2c2978 100644 --- a/manual-install/readme.md +++ b/manual-install/readme.md @@ -6,11 +6,13 @@ 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 ghcr.io is not possible. See [this issue](https://github.com/nextcloud/all-in-one/discussions/5268). ### Disadvantages - You lose the AIO interface - You lose update notifications and automatic updates - You lose all AIO backup and restore features +- You lose the built-in [Docker Socket Proxy container](https://github.com/nextcloud/docker-socket-proxy#readme) (needed for [Nextcloud App API](https://github.com/nextcloud/app_api#nextcloud-appapi)) - You lose all community containers: https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers - **You need to know what you are doing, especially when modifying the compose.yaml file** - For updating, you need to strictly follow the at the bottom described update routine @@ -22,17 +24,17 @@ First, install docker and docker-compose (v2) if not already done. Then simply r git clone https://github.com/nextcloud/all-in-one.git cd all-in-one/manual-install ``` -Then copy the sample.conf to default environment file, e.g. `cp sample.conf .env`, open the new conf file, e.g. with `nano .env`, edit all values that are marked with `# TODO!`, close and save the file. (Note: there is no clamav image for arm64).
-⚠️ **Warning**: Do not use the symbols `@` and `:` in your passwords. These symbols are used to build database connection strings. You will experience issues when using these symbols! +Then copy the sample.conf to default environment file, e.g. `cp sample.conf .env`, open the new conf file, e.g. with `nano .env`, edit all values that are marked with `# TODO!`, close and save the file.
+⚠️ **Warning**: Do not use the symbols `@` and `:` in your passwords. These symbols are used to build database connection strings. You will experience issues when using these symbols! Also please note that values inside the latest.yaml that are not exposed as variables are not officially supported to be changed. See for example [this report](https://github.com/nextcloud/all-in-one/issues/5612). Now copy the provided yaml file to a compose.yaml file by running `cp latest.yml compose.yaml`. Now you should be ready to go with `sudo docker compose up`. ## Docker profiles -The default profile of `latest.yml` only provide the minimum necessary services: nextcloud, database, redis and apache. To get optional services collabora, talk, talk-recording, clamav, imaginary or fulltextsearch use additional arguments for each of them, for example `--profile collabora`. (Note: there is no clamav image for arm64). +The default profile of `latest.yml` only provide the minimum necessary services: nextcloud, database, redis and apache. To get optional services collabora, talk, whiteboard, talk-recording, clamav, imaginary or fulltextsearch use additional arguments for each of them, for example `--profile collabora`. -For a complete all-in-one with collabora use `sudo docker compose --profile collabora --profile talk --profile talk-recording --profile clamav --profile imaginary --profile fulltextsearch up`. (Note: there is no clamav image for arm64). +For a complete all-in-one with collabora use `sudo docker compose --profile collabora --profile talk --profile talk-recording --profile clamav --profile imaginary --profile fulltextsearch --profile whiteboard up`. ## How to update? Since the AIO containers may change in the future, it is highly recommended to strictly follow the following procedure whenever you want to upgrade your containers. diff --git a/manual-install/sample.conf b/manual-install/sample.conf index 1f4e6b48..9ee01ab1 100644 --- a/manual-install/sample.conf +++ b/manual-install/sample.conf @@ -10,6 +10,7 @@ SIGNALING_SECRET= # TODO! This needs to be a unique and good password! TALK_INTERNAL_SECRET= # TODO! This needs to be a unique and good password! TIMEZONE=Europe/Berlin # TODO! This is the timezone that your containers will use. TURN_SECRET= # TODO! This needs to be a unique and good password! +WHITEBOARD_SECRET= # TODO! This needs to be a unique and good password! CLAMAV_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. COLLABORA_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. @@ -18,12 +19,14 @@ IMAGINARY_ENABLED="no" # Setting this to "yes" (with quotes) enables th ONLYOFFICE_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. TALK_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. TALK_RECORDING_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. +WHITEBOARD_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. APACHE_IP_BINDING=0.0.0.0 # This can be changed to e.g. 127.0.0.1 if you want to run AIO behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else) and if that is running on the same host and using localhost to connect -APACHE_MAX_SIZE=10737418240 # This needs to be an integer and in sync with NEXTCLOUD_UPLOAD_LIMIT +APACHE_MAX_SIZE=17179869184 # This needs to be an integer and in sync with NEXTCLOUD_UPLOAD_LIMIT APACHE_PORT=443 # Changing this to a different value than 443 will allow you to run it behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else). +ADDITIONAL_COLLABORA_OPTIONS=['--o:security.seccomp=true'] # You can add additional collabora options here by using the array syntax. COLLABORA_DICTIONARIES="de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru" # You can change this in order to enable other dictionaries for collabora -COLLABORA_SECCOMP_POLICY=--o:security.seccomp=true # Changing the value to false allows to disable the seccomp feature of the Collabora container. +FULLTEXTSEARCH_JAVA_OPTIONS="-Xms512M -Xmx512M" # Allows to adjust the fulltextsearch java options. INSTALL_LATEST_MAJOR=no # Setting this to yes will install the latest Major Nextcloud version upon the first installation NEXTCLOUD_ADDITIONAL_APKS=imagemagick # This allows to add additional packages to the Nextcloud container permanently. Default is imagemagick but can be overwritten by modifying this value. NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS=imagick # This allows to add additional php extensions to the Nextcloud container permanently. Default is imagick but can be overwritten by modifying this value. @@ -33,7 +36,7 @@ NEXTCLOUD_MEMORY_LIMIT=512M # This allows to change the PHP memory limi NEXTCLOUD_MOUNT=/mnt/ # This allows the Nextcloud container to access directories on the host. It must never be equal to the value of NEXTCLOUD_DATADIR! NEXTCLOUD_STARTUP_APPS="deck twofactor_totp tasks calendar contacts notes" # Allows to modify the Nextcloud apps that are installed on starting AIO the first time NEXTCLOUD_TRUSTED_CACERTS_DIR=/usr/local/share/ca-certificates/my-custom-ca # Nextcloud container will trust all the Certification Authorities, whose certificates are included in the given directory. -NEXTCLOUD_UPLOAD_LIMIT=10G # This allows to change the upload limit of the Nextcloud container +NEXTCLOUD_UPLOAD_LIMIT=16G # This allows to change the upload limit of the Nextcloud container REMOVE_DISABLED_APPS=yes # Setting this to no keep Nextcloud apps that are disabled via their switch and not uninstall them if they should be installed in Nextcloud. -TALK_PORT=3478 # This allows to adjust the port that the talk container is using. +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. diff --git a/manual-install/update-yaml.sh b/manual-install/update-yaml.sh index d8bb0cc2..928275da 100644 --- a/manual-install/update-yaml.sh +++ b/manual-install/update-yaml.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/bash -ex -set -ex +type {jq,sudo} || { echo "Commands not found. Please install them"; exit 127; } jq -c . ./php/containers.json > /tmp/containers.json sed -i 's|aio_services_v1|services|g' /tmp/containers.json @@ -14,10 +14,14 @@ cat /tmp/containers.json OUTPUT="$(cat /tmp/containers.json)" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].internal_port)')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].secrets)')" +OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].ui_secrets)')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].devices)')" +OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].enable_nvidia_gpu)')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].backup_volumes)')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].nextcloud_exec_commands)')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].image_tag)')" +OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].networks)')" +OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[].documentation)')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "nextcloud-aio-watchtower"))')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "nextcloud-aio-domaincheck"))')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "nextcloud-aio-borgbackup"))')" @@ -25,7 +29,7 @@ OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "next OUTPUT="$(echo "$OUTPUT" | jq '.services[] |= if has("depends_on") then .depends_on |= if contains(["nextcloud-aio-docker-socket-proxy"]) then del(.[index("nextcloud-aio-docker-socket-proxy")]) else . end else . end')" OUTPUT="$(echo "$OUTPUT" | jq '.services[] |= if has("depends_on") then .depends_on |= map({ (.): { "condition": "service_started", "required": false } }) else . end' | jq '.services[] |= if has("depends_on") then .depends_on |= reduce .[] as $item ({}; . + $item) else . end')" -snap install yq +sudo snap install yq mkdir -p ./manual-install echo "$OUTPUT" | yq -P > ./manual-install/containers.yml @@ -33,6 +37,7 @@ cd manual-install || exit sed -i "s|'||g" containers.yml sed -i '/display_name:/d' containers.yml sed -i '/THIS_IS_AIO/d' containers.yml +sed -i "s|%COLLABORA_SECCOMP_POLICY% ||g" containers.yml sed -i '/stop_grace_period:/s/$/s/' containers.yml sed -i '/: \[\]/d' containers.yml sed -i 's|- source: |- |' containers.yml @@ -41,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" @@ -70,25 +77,26 @@ do done sed -i 's|_ENABLED=|_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically.|' sample.conf -sed -i 's|CLAMAV_ENABLED=no.*|CLAMAV_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. Note: arm64 has no clamav support|' sample.conf +sed -i 's|CLAMAV_ENABLED=no.*|CLAMAV_ENABLED="no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically.|' sample.conf sed -i 's|TALK_ENABLED=no|TALK_ENABLED="yes"|' sample.conf sed -i 's|COLLABORA_ENABLED=no|COLLABORA_ENABLED="yes"|' sample.conf sed -i 's|COLLABORA_DICTIONARIES=|COLLABORA_DICTIONARIES="de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru" # You can change this in order to enable other dictionaries for collabora|' sample.conf sed -i 's|NEXTCLOUD_DATADIR=|NEXTCLOUD_DATADIR=nextcloud_aio_nextcloud_data # You can change this to e.g. "/mnt/ncdata" to map it to a location on your host. It needs to be adjusted before the first startup and never afterwards!|' sample.conf sed -i 's|NEXTCLOUD_MOUNT=|NEXTCLOUD_MOUNT=/mnt/ # This allows the Nextcloud container to access directories on the host. It must never be equal to the value of NEXTCLOUD_DATADIR!|' sample.conf -sed -i 's|NEXTCLOUD_UPLOAD_LIMIT=|NEXTCLOUD_UPLOAD_LIMIT=10G # This allows to change the upload limit of the Nextcloud container|' sample.conf +sed -i 's|NEXTCLOUD_UPLOAD_LIMIT=|NEXTCLOUD_UPLOAD_LIMIT=16G # This allows to change the upload limit of the Nextcloud container|' sample.conf sed -i 's|NEXTCLOUD_MEMORY_LIMIT=|NEXTCLOUD_MEMORY_LIMIT=512M # This allows to change the PHP memory limit of the Nextcloud container|' sample.conf -sed -i 's|APACHE_MAX_SIZE=|APACHE_MAX_SIZE=10737418240 # This needs to be an integer and in sync with NEXTCLOUD_UPLOAD_LIMIT|' sample.conf +sed -i 's|APACHE_MAX_SIZE=|APACHE_MAX_SIZE=17179869184 # This needs to be an integer and in sync with NEXTCLOUD_UPLOAD_LIMIT|' sample.conf sed -i 's|NEXTCLOUD_MAX_TIME=|NEXTCLOUD_MAX_TIME=3600 # This allows to change the upload time limit of the Nextcloud container|' sample.conf sed -i 's|NEXTCLOUD_TRUSTED_CACERTS_DIR=|NEXTCLOUD_TRUSTED_CACERTS_DIR=/usr/local/share/ca-certificates/my-custom-ca # Nextcloud container will trust all the Certification Authorities, whose certificates are included in the given directory.|' sample.conf sed -i 's|UPDATE_NEXTCLOUD_APPS=|UPDATE_NEXTCLOUD_APPS="no" # When setting to "yes" (with quotes), it will automatically update all installed Nextcloud apps upon container startup on saturdays.|' sample.conf sed -i 's|APACHE_PORT=|APACHE_PORT=443 # Changing this to a different value than 443 will allow you to run it behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else).|' sample.conf sed -i 's|APACHE_IP_BINDING=|APACHE_IP_BINDING=0.0.0.0 # This can be changed to e.g. 127.0.0.1 if you want to run AIO behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else) and if that is running on the same host and using localhost to connect|' sample.conf -sed -i 's|TALK_PORT=|TALK_PORT=3478 # This allows to adjust the port that the talk container is using.|' sample.conf +sed -i 's|TALK_PORT=|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!|' sample.conf sed -i 's|NC_DOMAIN=|NC_DOMAIN=yourdomain.com # TODO! Needs to be changed to the domain that you want to use for Nextcloud.|' sample.conf sed -i 's|NEXTCLOUD_PASSWORD=|NEXTCLOUD_PASSWORD= # TODO! This is the password of the initially created Nextcloud admin with username "admin".|' sample.conf sed -i 's|TIMEZONE=|TIMEZONE=Europe/Berlin # TODO! This is the timezone that your containers will use.|' sample.conf sed -i 's|COLLABORA_SECCOMP_POLICY=|COLLABORA_SECCOMP_POLICY=--o:security.seccomp=true # Changing the value to false allows to disable the seccomp feature of the Collabora container.|' sample.conf +sed -i 's|FULLTEXTSEARCH_JAVA_OPTIONS=|FULLTEXTSEARCH_JAVA_OPTIONS="-Xms512M -Xmx512M" # Allows to adjust the fulltextsearch java options.|' sample.conf sed -i 's|NEXTCLOUD_STARTUP_APPS=|NEXTCLOUD_STARTUP_APPS="deck twofactor_totp tasks calendar contacts notes" # Allows to modify the Nextcloud apps that are installed on starting AIO the first time|' sample.conf sed -i 's|NEXTCLOUD_ADDITIONAL_APKS=|NEXTCLOUD_ADDITIONAL_APKS=imagemagick # This allows to add additional packages to the Nextcloud container permanently. Default is imagemagick but can be overwritten by modifying this value.|' sample.conf sed -i 's|NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS=|NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS=imagick # This allows to add additional php extensions to the Nextcloud container permanently. Default is imagick but can be overwritten by modifying this value.|' sample.conf @@ -124,6 +132,13 @@ echo "$OUTPUT" > containers.yml sed -i '/container_name/d' containers.yml sed -i 's|^ $||' containers.yml +# Additional config for collabora +cat << EOL > /tmp/additional-collabora.config + command: \${ADDITIONAL_COLLABORA_OPTIONS} +EOL +sed -i "/^ nextcloud-aio-collabora:/r /tmp/additional-collabora.config" containers.yml +sed -i "/^COLLABORA_DICTIONARIES.*/i ADDITIONAL_COLLABORA_OPTIONS=['--o:security.seccomp=true'] # You can add additional collabora options here by using the array syntax." sample.conf + VOLUMES="$(grep -oP 'nextcloud_aio_[a-z_]+' containers.yml | sort -u)" mapfile -t VOLUMES <<< "$VOLUMES" echo "" >> containers.yml @@ -139,13 +154,12 @@ done cat << NETWORK >> containers.yml networks: - nextcloud-aio: - name: nextcloud-aio + default: + driver: bridge NETWORK -cat containers.yml > latest.yml +mv containers.yml latest.yml sed -i "/image:/s/$/:latest/" latest.yml - -rm containers.yml +sed -i 's/\( *- \(\w*\)\)=\${\2\}/\1/' latest.yml set +ex diff --git a/manual-upgrade.md b/manual-upgrade.md index 517c217f..fbc07d3e 100644 --- a/manual-upgrade.md +++ b/manual-upgrade.md @@ -7,7 +7,11 @@ The only way to fix this on your side is upgrading regularly (e.g. by enabling d --- -## Method 1 +## Method 1 using `assaflavie/runlike` + +> [!Warning] +> Please note that this method is apparently currently broken. See https://help.nextcloud.com/t/manual-upgrade-keeps-failing/217164/10 +> So please refer to method 2 using Portainer. 1. Start all containers from the AIO interface - Now, it will report that Nextcloud is restarting because it is not able to start due to the above mentioned problem @@ -31,13 +35,13 @@ The only way to fix this on your side is upgrading regularly (e.g. by enabling d | To change | Replace with | |----------------------------------------|-----------------------------------------------------| -| `nextcloud/aio-nextcloud:latest` | `nextcloud/aio-nextcloud:php{version}-latest` | -| `nextcloud/aio-nextcloud:latest-arm64` | `nextcloud/aio-nextcloud:php{version}-latest-arm64` | +| `ghcr.io/nextcloud-releases/aio-nextcloud:latest` | `ghcr.io/nextcloud-releases/aio-nextcloud:php{version}-latest` | +| `ghcr.io/nextcloud-releases/aio-nextcloud:latest-arm64` | `ghcr.io/nextcloud-releases/aio-nextcloud:php{version}-latest-arm64` | - - e.g. `nextcloud/aio-nextcloud:php8.0-latest` or `nextcloud/aio-nextcloud:php8.0-latest-arm64` - - However, if you are unsure check the docker hub (https://hub.docker.com/r/nextcloud/aio-nextcloud/tags) + - e.g. `ghcr.io/nextcloud-releases/aio-nextcloud:php8.0-latest` or `ghcr.io/nextcloud-releases/aio-nextcloud:php8.0-latest-arm64` + - However, if you are unsure check the ghcr.io (https://github.com/nextcloud-releases/all-in-one/pkgs/container/aio-nextcloud/versions?filters%5Bversion_type%5D=tagged) and docker hub: https://hub.docker.com/r/nextcloud/aio-nextcloud/tags?name=php - Using nano and the arrow keys to navigate: - `sudo nano /tmp/nextcloud-aio-nextcloud` making changes as above, then `[Ctrl]+[o]` -> `[Enter]` and `[Ctrl]+[x]` to save and exit. 6. Next, stop and remove the current container: @@ -54,14 +58,11 @@ The only way to fix this on your side is upgrading regularly (e.g. by enabling d --- -## Method 2 +## Method 2 using Portainer #### *Approach using portainer if method 1 does not work for you* Prerequisite: have all containers from AIO interface running. -
-Click to expand - ##### 1. Install portainer if not installed: ```bash docker volume create portainer_data @@ -93,8 +94,8 @@ Make **note** of the version which is compatible, rounding down to 1 digit after - In this example we would want php 8.1 since anything with 8.2 or above is incompatible ##### 5. Find the correct container version -In general it should be ```nextcloud/aio-nextcloud:php8.x-latest-arm64``` or `nextcloud/aio-nextcloud:php8.x-latest` replacing `x` with the version you require. -However, if you are unsure check the docker hub (https://hub.docker.com/r/nextcloud/aio-nextcloud/tags) +In general it should be ```ghcr.io/nextcloud-releases/aio-nextcloud:php8.x-latest-arm64``` or `ghcr.io/nextcloud-releases/aio-nextcloud:php8.x-latest` replacing `x` with the version you require. +However, if you are unsure check the ghcr.io (https://github.com/nextcloud-releases/all-in-one/pkgs/container/aio-nextcloud/versions?filters%5Bversion_type%5D=tagged) and docker hub: https://hub.docker.com/r/nextcloud/aio-nextcloud/tags?name=php ##### 6. Replace the container - Navigate to the ```nextcloud-aio-nextcloud``` container within portainer @@ -107,7 +108,7 @@ However, if you are unsure check the docker hub (https://hub.docker.com/r/nextcl 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. --- @@ -119,5 +120,3 @@ docker rm portainer docker volume rm portainer_data ``` - Make sure you close port 9443 on your firewall and delete any necessary reverse proxy hosts. - -
diff --git a/migration.md b/migration.md index b8bf8734..5e9b8b25 100644 --- a/migration.md +++ b/migration.md @@ -1,20 +1,20 @@ # How to migrate from an already existing Nextcloud installation to Nextcloud AIO? -There are basically three ways how to migrate from an already existing Nextcloud installation to Nextcloud AIO: +There are basically three ways how to migrate from an already existing Nextcloud installation to Nextcloud AIO (if you ran AIO on the former installation already, you can follow [these steps](https://github.com/nextcloud/all-in-one#how-to-migrate-from-aio-to-aio)): -1. Migrate only the files which is the easiest way +1. Migrate only the files which is the easiest way (this excludes all calendar data for example) 1. Migrate the files and the database which is much more complicated (and doesn't work on former snap installations) 1. Use the user_migration app that allows to migrate some of the user's data from a former instance to a new instance but needs to be done manually for each user ## Migrate only the files -**Please note**: If you used groupfolders or encrypted your files before, you will need to restore the database, as well! +**Please note**: If you used groupfolders or encrypted your files before, you will need to restore the database, as well! (This will also exclude all calendar data for example). The procedure for migrating only the files works like this: 1. Take a backup of your former instance (especially from your datadirectory, see `'datadirectory'` in your `config.php`) 1. Install Nextcloud AIO on a new server/linux installation, enter your domain and wait until all containers are running 1. Recreate all users that were present on your former installation 1. Take a backup using Nextcloud AIO's built-in backup solution (so that you can easily restore to this state again) (Note: this will stop all containers and is expected: don't start the container again at this point!) -1. Restore the datadirectory of your former instance: for `/path/to/nextcloud/data/` run `sudo docker cp --follow-link /path/to/nextcloud/data/. nextcloud-aio-nextcloud:/mnt/ncdata/` Note: the `/.` and `/` at the end are necessary. +1. Restore the datadirectory of your former instance: for `/path/to/old/nextcloud/data/` run `sudo docker cp --follow-link /path/to/old/nextcloud/data/. nextcloud-aio-nextcloud:/mnt/ncdata/` Note: the `/.` and `/` at the end are necessary. 1. Next, run `sudo docker run --rm --volume nextcloud_aio_nextcloud_data:/mnt/ncdata:rw alpine chown -R 33:0 /mnt/ncdata/` and `sudo docker run --rm --volume nextcloud_aio_nextcloud_data:/mnt/ncdata:rw alpine chmod -R 750 /mnt/ncdata/` to apply the correct permissions. (Or if `NEXTCLOUD_DATADIR` was provided, apply `chown -R 33:0` and `chmod -R 750` to the chosen path.) 1. Start the containers again and wait until all containers are running 1. Run `sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ files:scan-app-data && sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ files:scan --all` in order to scan all files in the datadirectory. @@ -57,9 +57,9 @@ The procedure for migrating the files and the database works like this: ``` **Please note:** The exact name of the database export file is important! (`database-dump.sql`)
And of course you need to to use the correct name that the Postgresql database has for the export (if `$PG_DATABASE` doesn't work directly). -1. At this point, you can finally install Nextcloud AIO on a new server/linux installation, enter your domain in the AIO interface (use the same domain that you used on your former installation) and wait until all containers are running. Then you should check the included Nextcloud version by running `sudo docker inspect nextcloud-aio-nextcloud | grep NEXTCLOUD_VERSION`. Also install all apps via the apps management site that were installed on the old Nextcloud installation. Otherwise they will show as installed, but will not work. -1. Next, take a backup using Nextcloud AIO's built-in backup solution (so that you can easily restore to this state again) (Note: this will stop all containers and is expected: don't start the container again at this point!) -1. Now, we are slowly starting to import your files and database. First, you need to modify the datadirectory that is stored inside the database export: +1. At this point, you can finally install Nextcloud AIO on a new server/linux installation, enter your domain in the AIO interface (use the same domain that you used on your former installation) and wait until all containers are running. Then you should check the included Nextcloud version by running `sudo docker inspect nextcloud-aio-nextcloud | grep NEXTCLOUD_VERSION`. On the AIO interface, use the passphrase to connect to your newly created Nextcloud instance's admin account. There, install all the Nextcloud apps that were installed on the old Nextcloud installation. If you don't, the migration will show them as installed, but they won't work. +1. Next, take a backup using Nextcloud AIO's built-in backup solution (so that you can easily restore to this state again). Once finished, all containers are automatically stopped and is expected: **don't start the container again at this point!** +1. Now, with the containers still stopped, we are slowly starting to import your files and database. First, you need to modify the datadirectory that is stored inside the database export: 1. Find out what the directory of your old Nextcloud installation is by e.g. opening the config.php file and looking at the value `datadirectory`. 1. Now, create a copy of the database file so that you can simply restore it if you should make a mistake while editing: `cp database-dump.sql database-dump.sql.backup` 1. Next, open the database export with e.g. nano: `nano database-dump.sql` @@ -81,7 +81,6 @@ The same applies for the second statement, check with `grep " OWNER TO nextcloud 1. Next, run `sudo docker run --rm --volume nextcloud_aio_nextcloud_data:/mnt/ncdata:rw alpine chown -R 33:0 /mnt/ncdata/` and `sudo docker run --rm --volume nextcloud_aio_nextcloud_data:/mnt/ncdata:rw alpine chmod -R 750 /mnt/ncdata/` to apply the correct permissions on the datadirectory. (Or if `NEXTCLOUD_DATADIR` was provided, apply `chown -R 33:0` and `chmod -R 750` to the chosen path.) 1. Edit the Nextcloud AIO config.php file using `sudo docker run -it --rm --volume nextcloud_aio_nextcloud:/var/www/html:rw alpine sh -c "apk add --no-cache nano && nano /var/www/html/config/config.php"` and modify only `passwordsalt`, `secret`, `instanceid` and set it to the old values that you used on your old installation. If you are brave, feel free to modify further values e.g. add your old LDAP config or S3 storage config. (Some things like Mail server config can be added back using Nextcloud's webinterface later on). 1. When you are done and saved your changes to the file, finally start the containers again and wait until all containers are running. -1. As last step, install all apps again that were installed before on your old instance by using the webinterface. Now the whole Nextcloud instance should work again.
If not, feel free to restore the AIO instance from backup and start at step 8 again. diff --git a/multiple-instances.md b/multiple-instances.md index f98f867a..00386e1b 100644 --- a/multiple-instances.md +++ b/multiple-instances.md @@ -149,7 +149,7 @@ apt install --no-install-recommends qemu-system qemu-utils libvirt-clients libvi --env TALK_PORT=3478 \ --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ - nextcloud/all-in-one:latest + ghcr.io/nextcloud-releases/all-in-one:latest ``` The last command may take a few minutes. When it's finished, you should see a success message, saying "Initial startup of Nextcloud All-in-One complete!". Now exit the console session with `Ctrl + [c]`. This concludes the setup for this particular VM. diff --git a/nextcloud-aio-helm-chart/Chart.yaml b/nextcloud-aio-helm-chart/Chart.yaml index bb0248fa..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: 9.4.1 +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 c15f9e1b..cb31e601 100755 --- a/nextcloud-aio-helm-chart/readme.md +++ b/nextcloud-aio-helm-chart/readme.md @@ -1,5 +1,11 @@ # Nextcloud AIO Helm-chart +> [!NOTE] +> 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. + You can run the containers that are build for AIO with Kubernetes using this Helm chart. This comes with a few downsides, that are discussed below. ### Advantages 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 8a4fbc4b..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,8 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-apache name: nextcloud-aio-apache @@ -18,28 +17,22 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-apache spec: - initContainers: - - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - chmod - - "777" - - /nextcloud-aio-nextcloud - - /nextcloud-aio-apache - volumeMounts: - - name: nextcloud-aio-apache - mountPath: /nextcloud-aio-apache - - name: nextcloud-aio-nextcloud - mountPath: /nextcloud-aio-nextcloud + securityContext: + # The items below only work in pod context + fsGroup: 33 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 33 + runAsGroup: 33 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} containers: - env: - name: ADDITIONAL_TRUSTED_DOMAIN @@ -66,7 +59,23 @@ spec: value: nextcloud-aio-talk - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-apache:20240808_083748" + - name: WHITEBOARD_HOST + value: nextcloud-aio-whiteboard + image: ghcr.io/nextcloud-releases/aio-apache:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-apache ports: - containerPort: {{ .Values.APACHE_PORT }} @@ -74,9 +83,15 @@ spec: - containerPort: {{ .Values.APACHE_PORT }} protocol: UDP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} + add: ["NET_BIND_SERVICE"] volumeMounts: - mountPath: /var/www/html name: nextcloud-aio-nextcloud 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 4b1a7667..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,8 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 b9595b84..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,8 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-clamav name: nextcloud-aio-clamav @@ -19,57 +18,77 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-clamav spec: + securityContext: + # The items below only work in pod context + fsGroup: 100 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 100 + runAsGroup: 100 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} initContainers: - name: init-subpath - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - mkdir - "-p" - /nextcloud-aio-clamav/data - - /nextcloud-aio-clamav - volumeMounts: - - name: nextcloud-aio-clamav - mountPath: /nextcloud-aio-clamav - - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - chown - - 100:100 - - "-R" - - /nextcloud-aio-clamav volumeMounts: - name: nextcloud-aio-clamav mountPath: /nextcloud-aio-clamav + securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false + capabilities: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} containers: - env: - - name: CLAMD_STARTUP_TIMEOUT - value: "90" - name: MAX_SIZE value: "{{ .Values.NEXTCLOUD_UPLOAD_LIMIT }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-clamav:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-clamav:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 9 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 9 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-clamav ports: - containerPort: 3310 protocol: TCP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} volumeMounts: - mountPath: /var/lib/clamav subPath: data 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 a0096210..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,8 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 06aaffc0..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,8 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-collabora name: nextcloud-aio-collabora @@ -17,26 +16,46 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-collabora spec: containers: - - env: + - args: {{ .Values.ADDITIONAL_COLLABORA_OPTIONS | default list | toJson }} + env: - name: DONT_GEN_SSL_CERT value: "1" - name: TZ value: "{{ .Values.TIMEZONE }}" - name: aliasgroup1 - value: https://{{ .Values.NC_DOMAIN }}:443 + value: https://{{ .Values.NC_DOMAIN }}:443,http://nextcloud-aio-apache:23973 - 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:home_mode.enable=true {{ .Values.COLLABORA_SECCOMP_POLICY }} --o:remote_font_config.url=https://{{ .Values.NC_DOMAIN }}/apps/richdocuments/settings/fonts.json + 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: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-collabora:20240808_083748" + {{- 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: + - /healthcheck.sh + failureThreshold: 9 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 9 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-collabora ports: - containerPort: 9980 @@ -45,7 +64,8 @@ spec: capabilities: add: - MKNOD - - SYS_ADMIN - drop: - - NET_RAW + - CAP_SYS_ADMIN + - SYS_CHROOT + - FOWNER + - CHOWN {{- end }} 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 57a8181f..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,8 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 7185f3f5..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,8 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-database name: nextcloud-aio-database @@ -18,46 +17,41 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-database spec: + securityContext: + # The items below only work in pod context + fsGroup: 999 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 999 + runAsGroup: 999 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} initContainers: - name: init-subpath - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - mkdir - "-p" - /nextcloud-aio-database/data - - /nextcloud-aio-database - - /nextcloud-aio-database-dump volumeMounts: - - name: nextcloud-aio-database-dump - mountPath: /nextcloud-aio-database-dump - - name: nextcloud-aio-database - mountPath: /nextcloud-aio-database - - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - chown - - 999:999 - - "-R" - - /nextcloud-aio-database - - /nextcloud-aio-database-dump - volumeMounts: - - name: nextcloud-aio-database-dump - mountPath: /nextcloud-aio-database-dump - name: nextcloud-aio-database mountPath: /nextcloud-aio-database + securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false + capabilities: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} containers: - env: - name: PGTZ @@ -70,15 +64,34 @@ spec: value: nextcloud - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-postgresql:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-postgresql:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-database ports: - containerPort: 5432 protocol: TCP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} volumeMounts: - mountPath: /var/lib/postgresql/data subPath: data 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 f83fb954..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,8 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 bd38cbd6..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,8 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-fulltextsearch name: nextcloud-aio-fulltextsearch @@ -19,18 +18,13 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-fulltextsearch spec: initContainers: - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - chmod - "777" @@ -41,34 +35,46 @@ spec: containers: - env: - name: ES_JAVA_OPTS - value: -Xms512M -Xmx512M + value: "{{ .Values.FULLTEXTSEARCH_JAVA_OPTIONS | default "-Xms512M -Xmx512M" }}" - name: FULLTEXTSEARCH_PASSWORD value: "{{ .Values.FULLTEXTSEARCH_PASSWORD }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - name: bootstrap.memory_lock - value: "true" + value: "false" - name: cluster.name value: nextcloud-aio - name: discovery.type value: single-node - name: http.port value: "9200" - - name: logger.org.elasticsearch.discovery + - name: logger.level value: WARN - name: xpack.license.self_generated.type value: basic - name: xpack.security.enabled value: "false" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-fulltextsearch:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-fulltextsearch:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 5 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 5 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 name: nextcloud-aio-fulltextsearch ports: - containerPort: 9200 protocol: TCP - securityContext: - capabilities: - drop: - - NET_RAW volumeMounts: - mountPath: /usr/share/elasticsearch/data name: nextcloud-aio-elasticsearch 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 6c8e415d..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,8 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 62a6d035..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,8 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-imaginary name: nextcloud-aio-imaginary @@ -17,26 +16,54 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-imaginary spec: + securityContext: + # The items below only work in pod context + fsGroup: 65534 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 65534 + runAsGroup: 65534 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} containers: - env: - name: IMAGINARY_SECRET value: "{{ .Values.IMAGINARY_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-imaginary:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-imaginary:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-imaginary ports: - containerPort: 9000 protocol: TCP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - add: - - SYS_NICE - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} {{- end }} 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 d71a39ce..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,8 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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-namespace-namespace.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-namespace-namespace.yaml index cf705b7c..212715e9 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-namespace-namespace.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-namespace-namespace.yaml @@ -4,4 +4,8 @@ kind: Namespace metadata: name: "{{ .Values.NAMESPACE }}" namespace: "{{ .Values.NAMESPACE }}" + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + labels: + pod-security.kubernetes.io/enforce: restricted + {{- end }} {{- end }} diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-networkpolicy.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-networkpolicy.yaml index 8e6986b8..c54f8803 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-networkpolicy.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-networkpolicy.yaml @@ -15,6 +15,22 @@ spec: - from: - podSelector: {} egress: - - to: - - podSelector: {} + - {} # Allows all egress traffic +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + namespace: "{{ .Values.NAMESPACE }}" + name: nextcloud-aio-webserver-allow +spec: + podSelector: + matchExpressions: + - key: io.kompose.service + operator: In + values: + - nextcloud-aio-apache + policyTypes: + - Ingress + ingress: + - {} # Allows all ingress traffic {{- end }} diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-data-persistentvolumeclaim.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-data-persistentvolumeclaim.yaml index 5be12896..62794e3b 100755 --- a/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-data-persistentvolumeclaim.yaml +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-nextcloud-data-persistentvolumeclaim.yaml @@ -6,7 +6,9 @@ metadata: name: nextcloud-aio-nextcloud-data namespace: "{{ .Values.NAMESPACE }}" spec: - {{- if .Values.STORAGE_CLASS }} + {{- if .Values.STORAGE_CLASS_DATA }} + storageClassName: {{ .Values.STORAGE_CLASS_DATA }} + {{- else if .Values.STORAGE_CLASS }} storageClassName: {{ .Values.STORAGE_CLASS }} {{- end }} accessModes: 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 83267591..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,8 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-nextcloud name: nextcloud-aio-nextcloud @@ -18,33 +17,28 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-nextcloud spec: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} # AIO-config - do not change this comment! + securityContext: + # The items below only work in pod context + fsGroup: 33 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 33 + runAsGroup: 33 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} + {{- end }} # AIO-config - do not change this comment! +# AIO settings start # Do not remove or change this line! initContainers: - - name: "delete-lost-found" - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - rm - - "-rf" - - "/nextcloud-aio-nextcloud/lost+found" - volumeMounts: - - name: nextcloud-aio-nextcloud-trusted-cacerts - mountPath: /nextcloud-aio-nextcloud-trusted-cacerts - - name: nextcloud-aio-nextcloud - mountPath: /nextcloud-aio-nextcloud - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - chmod - "777" @@ -55,6 +49,7 @@ spec: mountPath: /nextcloud-aio-nextcloud-trusted-cacerts - name: nextcloud-aio-nextcloud mountPath: /nextcloud-aio-nextcloud +# AIO settings end # Do not remove or change this line! containers: - env: - name: SMTP_HOST @@ -85,6 +80,8 @@ spec: value: "{{ .Values.SERVERINFO_TOKEN }}" - name: NEXTCLOUD_DEFAULT_QUOTA value: "{{ .Values.NEXTCLOUD_DEFAULT_QUOTA }}" + - name: NEXTCLOUD_SKELETON_DIRECTORY + value: "{{ .Values.NEXTCLOUD_SKELETON_DIRECTORY }}" - name: NEXTCLOUD_MAINTENANCE_WINDOW value: "{{ .Values.NEXTCLOUD_MAINTENANCE_WINDOW }}" - name: ADDITIONAL_APKS @@ -95,14 +92,14 @@ spec: value: "{{ .Values.NEXTCLOUD_PASSWORD }}" - name: ADMIN_USER value: admin + - name: APACHE_HOST + value: nextcloud-aio-apache - name: APACHE_PORT value: "{{ .Values.APACHE_PORT }}" - name: CLAMAV_ENABLED 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 @@ -111,8 +108,16 @@ spec: value: "{{ .Values.FULLTEXTSEARCH_ENABLED }}" - name: FULLTEXTSEARCH_HOST value: nextcloud-aio-fulltextsearch + - name: FULLTEXTSEARCH_INDEX + value: nextcloud-aio - name: FULLTEXTSEARCH_PASSWORD value: "{{ .Values.FULLTEXTSEARCH_PASSWORD }}" + - name: FULLTEXTSEARCH_PORT + value: "9200" + - name: FULLTEXTSEARCH_PROTOCOL + value: http + - name: FULLTEXTSEARCH_USER + value: elastic - name: IMAGINARY_ENABLED value: "{{ .Values.IMAGINARY_ENABLED }}" - name: IMAGINARY_HOST @@ -125,6 +130,8 @@ spec: value: "{{ .Values.NC_DOMAIN }}" - name: NEXTCLOUD_DATA_DIR value: /mnt/ncdata + - name: NEXTCLOUD_HOST + value: nextcloud-aio-nextcloud - name: ONLYOFFICE_ENABLED value: "{{ .Values.ONLYOFFICE_ENABLED }}" - name: ONLYOFFICE_HOST @@ -155,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 @@ -177,17 +186,42 @@ spec: value: "{{ .Values.TIMEZONE }}" - name: UPDATE_NEXTCLOUD_APPS value: "{{ .Values.UPDATE_NEXTCLOUD_APPS }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-nextcloud:20240808_083748" + - name: WHITEBOARD_ENABLED + value: "{{ .Values.WHITEBOARD_ENABLED }}" + - name: WHITEBOARD_SECRET + value: "{{ .Values.WHITEBOARD_SECRET }}" + 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 + allowPrivilegeEscalation: false + capabilities: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} + {{- end }} # AIO-config - do not change this comment! + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-nextcloud ports: - containerPort: 9000 protocol: TCP - containerPort: 9001 protocol: TCP - securityContext: - capabilities: - drop: - - NET_RAW volumeMounts: - mountPath: /var/www/html name: nextcloud-aio-nextcloud 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 cc8bca41..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,8 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 fd4505bd..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,8 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-notify-push name: nextcloud-aio-notify-push @@ -18,25 +17,22 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-notify-push spec: - initContainers: - - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - chmod - - "777" - - /nextcloud-aio-nextcloud - volumeMounts: - - name: nextcloud-aio-nextcloud - mountPath: /nextcloud-aio-nextcloud + securityContext: + # The items below only work in pod context + fsGroup: 33 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 33 + runAsGroup: 33 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} containers: - env: - name: NC_DOMAIN @@ -57,15 +53,38 @@ spec: value: nextcloud-aio-redis - name: REDIS_HOST_PASSWORD value: "{{ .Values.REDIS_PASSWORD }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-notify-push:20240808_083748" + - name: REDIS_PORT + value: "6379" + - name: TZ + value: "{{ .Values.TIMEZONE }}" + image: ghcr.io/nextcloud-releases/aio-notify-push:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-notify-push ports: - containerPort: 7867 protocol: TCP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} volumeMounts: - mountPath: /nextcloud name: nextcloud-aio-nextcloud 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 dc658984..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,8 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 d28179f9..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,8 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-onlyoffice name: nextcloud-aio-onlyoffice @@ -19,18 +18,13 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-onlyoffice spec: initContainers: - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:20260122_105751 command: - chmod - "777" @@ -48,15 +42,27 @@ spec: value: "{{ .Values.ONLYOFFICE_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-onlyoffice:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-onlyoffice:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 9 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 9 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-onlyoffice ports: - containerPort: 80 protocol: TCP - securityContext: - capabilities: - drop: - - NET_RAW volumeMounts: - mountPath: /var/lib/onlyoffice name: nextcloud-aio-onlyoffice 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 995e4f2c..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,8 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 3c08ead5..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,8 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-redis name: nextcloud-aio-redis @@ -18,40 +17,56 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-redis spec: - initContainers: - - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - chmod - - "777" - - /nextcloud-aio-redis - volumeMounts: - - name: nextcloud-aio-redis - mountPath: /nextcloud-aio-redis + securityContext: + # The items below only work in pod context + fsGroup: 999 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 999 + runAsGroup: 999 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} containers: - env: - name: REDIS_HOST_PASSWORD value: "{{ .Values.REDIS_PASSWORD }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-redis:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-redis:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-redis ports: - containerPort: 6379 protocol: TCP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} volumeMounts: - mountPath: /data name: nextcloud-aio-redis 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 e1eb0ea4..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,8 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 b66a1b7d..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,8 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk name: nextcloud-aio-talk @@ -17,11 +16,22 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk spec: + securityContext: + # The items below only work in pod context + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} containers: - env: - name: TALK_MAX_STREAM_BITRATE @@ -42,7 +52,21 @@ spec: value: "{{ .Values.TURN_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-talk:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-talk:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-talk ports: - containerPort: {{ .Values.TALK_PORT }} @@ -52,7 +76,12 @@ spec: - containerPort: 8081 protocol: TCP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} {{- end }} 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 24e6e975..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,8 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk-recording name: nextcloud-aio-talk-recording @@ -14,14 +13,27 @@ spec: selector: matchLabels: io.kompose.service: nextcloud-aio-talk-recording + strategy: + type: Recreate template: metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk-recording spec: + securityContext: + # The items below only work in pod context + fsGroup: 122 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 122 + runAsGroup: 122 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} containers: - env: - name: INTERNAL_SECRET @@ -32,13 +44,39 @@ spec: value: "{{ .Values.RECORDING_SECRET }}" - name: TZ value: "{{ .Values.TIMEZONE }}" - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/aio-talk-recording:20240808_083748" + image: ghcr.io/nextcloud-releases/aio-talk-recording:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 name: nextcloud-aio-talk-recording ports: - containerPort: 1234 protocol: TCP securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false capabilities: - drop: - - NET_RAW + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} + volumeMounts: + - mountPath: /tmp + name: nextcloud-aio-talk-recording + volumes: + - name: nextcloud-aio-talk-recording + persistentVolumeClaim: + claimName: nextcloud-aio-talk-recording {{- end }} diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-persistentvolumeclaim.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-persistentvolumeclaim.yaml new file mode 100755 index 00000000..59961448 --- /dev/null +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-talk-recording-persistentvolumeclaim.yaml @@ -0,0 +1,18 @@ +{{- if eq .Values.TALK_RECORDING_ENABLED "yes" }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + io.kompose.service: nextcloud-aio-talk-recording + name: nextcloud-aio-talk-recording + namespace: "{{ .Values.NAMESPACE }}" +spec: + {{- if .Values.STORAGE_CLASS }} + storageClassName: {{ .Values.STORAGE_CLASS }} + {{- end }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.TALK_RECORDING_STORAGE_SIZE }} +{{- end }} 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 e242050b..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,8 +3,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 29527887..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,8 +4,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + kompose.version: 1.38.0 (a8f5d1cbd) labels: io.kompose.service: nextcloud-aio-talk name: nextcloud-aio-talk-public @@ -13,7 +12,6 @@ metadata: spec: ipFamilyPolicy: PreferDualStack type: LoadBalancer - externalTrafficPolicy: Local ports: - name: "{{ .Values.TALK_PORT }}" port: {{ .Values.TALK_PORT }} @@ -29,8 +27,7 @@ apiVersion: v1 kind: Service metadata: annotations: - kompose.cmd: kompose convert -c -f latest.yml --namespace "{{ .Values.NAMESPACE }}" - kompose.version: 1.34.0 (cbf2835db) + 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 new file mode 100755 index 00000000..5788cfa0 --- /dev/null +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-deployment.yaml @@ -0,0 +1,81 @@ +{{- if eq .Values.WHITEBOARD_ENABLED "yes" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.version: 1.38.0 (a8f5d1cbd) + labels: + io.kompose.service: nextcloud-aio-whiteboard + name: nextcloud-aio-whiteboard + namespace: "{{ .Values.NAMESPACE }}" +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: nextcloud-aio-whiteboard + template: + metadata: + annotations: + kompose.version: 1.38.0 (a8f5d1cbd) + labels: + io.kompose.service: nextcloud-aio-whiteboard + spec: + securityContext: + # The items below only work in pod context + fsGroup: 65534 + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: 65534 + runAsGroup: 65534 + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} + containers: + - env: + - name: BACKUP_DIR + value: /tmp + - name: JWT_SECRET_KEY + value: "{{ .Values.WHITEBOARD_SECRET }}" + - name: NEXTCLOUD_URL + value: https://{{ .Values.NC_DOMAIN }} + - name: REDIS_HOST + 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:20260122_105751 + readinessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + livenessProbe: + exec: + command: + - /healthcheck.sh + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 30 + name: nextcloud-aio-whiteboard + ports: + - containerPort: 3002 + protocol: TCP + securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false + capabilities: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} +{{- end }} diff --git a/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-service.yaml b/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-service.yaml new file mode 100755 index 00000000..299f1ec3 --- /dev/null +++ b/nextcloud-aio-helm-chart/templates/nextcloud-aio-whiteboard-service.yaml @@ -0,0 +1,19 @@ +{{- if eq .Values.WHITEBOARD_ENABLED "yes" }} +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.version: 1.38.0 (a8f5d1cbd) + labels: + io.kompose.service: nextcloud-aio-whiteboard + name: nextcloud-aio-whiteboard + namespace: "{{ .Values.NAMESPACE }}" +spec: + ipFamilyPolicy: PreferDualStack + ports: + - name: "3002" + port: 3002 + targetPort: 3002 + selector: + io.kompose.service: nextcloud-aio-whiteboard +{{- end }} diff --git a/nextcloud-aio-helm-chart/update-helm.sh b/nextcloud-aio-helm-chart/update-helm.sh index f23d9572..9e5aba86 100755 --- a/nextcloud-aio-helm-chart/update-helm.sh +++ b/nextcloud-aio-helm-chart/update-helm.sh @@ -1,9 +1,11 @@ #!/bin/bash +[ -z "$1" ] && { echo "Error: Docker tag is not specified. Usage: ./nextcloud-aio-helm-chart/update-helm.sh "; exit 2; } + DOCKER_TAG="$1" # The logic needs the files in ./helm-chart -mv ./nextcloud-aio-helm-chart ./helm-chart +cp -r ./nextcloud-aio-helm-chart ./helm-chart # Clean rm -f ./helm-chart/values.yaml @@ -15,13 +17,17 @@ chmod +x kompose sudo mv ./kompose /usr/local/bin/kompose # Install yq -snap install yq +sudo snap install yq set -ex # Conversion of docker-compose cd manual-install 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|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 @@ -36,17 +42,24 @@ sed -i "s|\${TALK_PORT}:\${TALK_PORT}/|$TALK_PORT:$TALK_PORT/|g" latest.yml sed -i "s|- \${APACHE_PORT}|- $APACHE_PORT|" latest.yml sed -i "s|- \${TALK_PORT}|- $TALK_PORT|" latest.yml sed -i "s|\${NEXTCLOUD_DATADIR}|$NEXTCLOUD_DATADIR|" latest.yml +sed -i "s|\${ADDITIONAL_COLLABORA_OPTIONS}|ADDITIONAL_COLLABORA_OPTIONS_PLACEHOLDER|" latest.yml sed -i "/name: nextcloud-aio/,$ d" latest.yml sed -i "/NEXTCLOUD_DATADIR/d" latest.yml sed -i "/\${NEXTCLOUD_MOUNT}/d" latest.yml sed -i "/^volumes:/a\ \ nextcloud_aio_nextcloud_trusted_cacerts:\n \ \ \ \ name: nextcloud_aio_nextcloud_trusted_cacerts" latest.yml sed -i "s|\${NEXTCLOUD_TRUSTED_CACERTS_DIR}:|nextcloud_aio_nextcloud_trusted_cacerts:|g#" latest.yml -sed -i 's|\${|{{ .Values.|g' latest.yml -sed -i 's|}| }}|g' latest.yml +sed -i 's/\${/{{ .Values./g; s/}/ }}/g' latest.yml yq -i 'del(.services.[].profiles)' latest.yml # Delete read_only and tmpfs setting while https://github.com/kubernetes/kubernetes/issues/48912 is not fixed yq -i 'del(.services.[].read_only)' latest.yml yq -i 'del(.services.[].tmpfs)' latest.yml +# Remove cap_drop in order to add it later again easier +yq -i 'del(.services.[].cap_drop)' latest.yml +# Remove SYS_NICE for imaginary as it is not supported with RPSS +yq -i 'del(.services."nextcloud-aio-imaginary".cap_add)' latest.yml +# cap SYS_ADMIN is called CAP_SYS_ADMIN in k8s +sed -i "s|- SYS_ADMIN$|- CAP_SYS_ADMIN|" latest.yml + cat latest.yml kompose convert -c -f latest.yml --namespace nextcloud-aio-namespace cd latest @@ -59,11 +72,7 @@ find ./ -name '*networkpolicy.yaml' -exec sed -i "s|manual-install-nextcloud-aio cat << EOL > /tmp/initcontainers initContainers: - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:$DOCKER_TAG command: - chmod - "777" @@ -72,81 +81,51 @@ EOL cat << EOL > /tmp/initcontainers.database initContainers: - name: init-subpath - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:$DOCKER_TAG command: - mkdir - "-p" - /nextcloud-aio-database/data - volumeMountsInitContainer: - - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - chown - - 999:999 - - "-R" - volumeMountsInitContainer: + volumeMounts: + - name: nextcloud-aio-database + mountPath: /nextcloud-aio-database + securityContext: EOL cat << EOL > /tmp/initcontainers.clamav initContainers: - name: init-subpath - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:$DOCKER_TAG command: - mkdir - "-p" - /nextcloud-aio-clamav/data - volumeMountsInitContainer: - - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - chown - - 100:100 - - "-R" - volumeMountsInitContainer: + volumeMounts: + - name: nextcloud-aio-clamav + mountPath: /nextcloud-aio-clamav + securityContext: EOL cat << EOL > /tmp/initcontainers.nextcloud +# AIO settings start # Do not remove or change this line! initContainers: - - name: "delete-lost-found" - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} - command: - - rm - - "-rf" - - "/nextcloud-aio-nextcloud/lost+found" - volumeMountsInitRmLostFound: - name: init-volumes - {{- if or .Values.IMAGE_MIRROR_PREFIX .Values.ALPINE_IMAGE_ORG }} - image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.ALPINE_IMAGE_ORG}}/alpine" - {{- else }} - image: alpine - {{- end }} + image: ghcr.io/nextcloud-releases/aio-alpine:$DOCKER_TAG command: - chmod - "777" volumeMountsInitContainer: +# AIO settings end # Do not remove or change this line! EOL + # shellcheck disable=SC1083 DEPLOYMENTS="$(find ./ -name '*deployment.yaml')" mapfile -t DEPLOYMENTS <<< "$DEPLOYMENTS" for variable in "${DEPLOYMENTS[@]}"; do + if grep -q livenessProbe "$variable"; then + sed -n "/.*livenessProbe/,/timeoutSeconds.*/p" "$variable" > /tmp/liveness.probe + cat /tmp/liveness.probe + sed -i "s|livenessProbe|readinessProbe|" /tmp/liveness.probe + sed -i "/^ image:/r /tmp/liveness.probe" "$variable" + fi if grep -q volumeMounts "$variable"; then if echo "$variable" | grep -q database; then sed -i "/^ spec:/r /tmp/initcontainers.database" "$variable" @@ -154,7 +133,7 @@ for variable in "${DEPLOYMENTS[@]}"; do sed -i "/^ spec:/r /tmp/initcontainers.clamav" "$variable" elif echo "$variable" | grep -q "nextcloud-deployment.yaml"; then sed -i "/^ spec:/r /tmp/initcontainers.nextcloud" "$variable" - else + elif echo "$variable" | grep -q "fulltextsearch" || echo "$variable" | grep -q "onlyoffice" || echo "$variable" | grep -q "collabora"; then sed -i "/^ spec:/r /tmp/initcontainers" "$variable" fi volumeNames="$(grep -A1 mountPath "$variable" | grep -v mountPath | sed 's|.*name: ||' | sed '/^--$/d')" @@ -164,7 +143,6 @@ for variable in "${DEPLOYMENTS[@]}"; do if [ "$volumeName" != "nextcloud-aio-nextcloud-data" ]; then sed -i "/^.*volumeMountsInitContainer:/i\ \ \ \ \ \ \ \ \ \ \ \ - /$volumeName" "$variable" sed -i "/volumeMountsInitContainer:/a\ \ \ \ \ \ \ \ \ \ \ \ - name: $volumeName\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ mountPath: /$volumeName" "$variable" - sed -i "/volumeMountsInitRmLostFound:/a\ \ \ \ \ \ \ \ \ \ \ \ - name: $volumeName\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ mountPath: /$volumeName" "$variable" # Workaround for the database volume if [ "$volumeName" = nextcloud-aio-database ]; then sed -i "/mountPath: \/var\/lib\/postgresql\/data/a\ \ \ \ \ \ \ \ \ \ \ \ \ \ subPath: data" "$variable" @@ -175,7 +153,6 @@ for variable in "${DEPLOYMENTS[@]}"; do fi done sed -i "s|volumeMountsInitContainer:|volumeMounts:|" "$variable" - sed -i "s|volumeMountsInitRmLostFound:|volumeMounts:|" "$variable" if grep -q claimName "$variable"; then claimNames="$(grep claimName "$variable")" mapfile -t claimNames <<< "$claimNames" @@ -186,6 +163,39 @@ for variable in "${DEPLOYMENTS[@]}"; do done fi fi + if grep -q runAsUser "$variable" || echo "$variable" | grep -q "nextcloud-deployment.yaml"; then + if echo "$variable" | grep -q "nextcloud-deployment.yaml"; then + USER=33 + GROUP=33 + echo ' {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} # AIO-config - do not change this comment!' > /tmp/pod.securityContext + else + USER="$(grep runAsUser "$variable" | grep -oP '[0-9]+')" + GROUP="$USER" + rm -f /tmp/pod.securityContext + fi + sed -i "/runAsUser:/d" "$variable" + sed -i "/capabilities:/d" "$variable" + if [ -n "$USER" ]; then + cat << EOL >> /tmp/pod.securityContext + securityContext: + # The items below only work in pod context + fsGroup: $USER + fsGroupChangePolicy: "OnRootMismatch" + # The items below work in both contexts + runAsUser: $USER + runAsGroup: $GROUP + runAsNonRoot: true + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + seccompProfile: + type: RuntimeDefault + {{- end }} +EOL + if echo "$variable" | grep -q "nextcloud-deployment.yaml"; then + echo " {{- end }} # AIO-config - do not change this comment!" >> /tmp/pod.securityContext + fi + sed -i "/^ spec:$/r /tmp/pod.securityContext" "$variable" + fi + fi done # shellcheck disable=SC1083 find ./ -name '*.yaml' -exec sed -i 's|nextcloud-aio-namespace|"\{\{ .Values.NAMESPACE \}\}"|' \{} \; @@ -196,6 +206,8 @@ find ./ -name '*deployment.yaml' -exec sed -i "s|manual-install-nextcloud-aio|ne # shellcheck disable=SC1083 find ./ -name '*deployment.yaml' -exec sed -i "/medium: Memory/d" \{} \; # shellcheck disable=SC1083 +find ./ -name '*.yaml' -exec sed -i "/kompose.cmd/d" \{} \; +# shellcheck disable=SC1083 find ./ -name '*deployment.yaml' -exec sed -i "s|emptyDir:|emptyDir: \{\}|" \{} \; # shellcheck disable=SC1083 find ./ -name '*deployment.yaml' -exec sed -i "/hostPort:/d" \{} \; @@ -210,6 +222,10 @@ find ./ -name '*persistentvolumeclaim.yaml' -exec sed -i "/accessModes:/i\ \ sto # shellcheck disable=SC1083 find ./ -name '*persistentvolumeclaim.yaml' -exec sed -i "/accessModes:/i\ \ {{- end }}" \{} \; # shellcheck disable=SC1083 +find ./ -name 'nextcloud-aio-nextcloud-data-persistentvolumeclaim.yaml' -exec sed -i "/{{- if .Values.STORAGE_CLASS }}/i\ {{- if .Values.STORAGE_CLASS_DATA }}\n storageClassName: {{ .Values.STORAGE_CLASS_DATA }}" \{} \; +# shellcheck disable=SC1083 +find ./ -name 'nextcloud-aio-nextcloud-data-persistentvolumeclaim.yaml' -exec sed -i "s/{{- if .Values.STORAGE_CLASS }}/{{- else if .Values.STORAGE_CLASS }}/" \{} \; +# shellcheck disable=SC1083 find ./ -name '*deployment.yaml' -exec sed -i "/restartPolicy:/d" \{} \; # shellcheck disable=SC1083 find ./ -name '*apache*' -exec sed -i "s|$APACHE_PORT|{{ .Values.APACHE_PORT }}|" \{} \; @@ -236,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 @@ -247,6 +263,15 @@ find ./ \( -not -name '*service.yaml' -name '*.yaml' \) -exec sed -i "/^status:/ find ./ \( -not -name '*persistentvolumeclaim.yaml' -name '*.yaml' \) -exec sed -i "/resources:/d" \{} \; # shellcheck disable=SC1083 find ./ -name "*namespace.yaml" -exec sed -i "1i\\{{- if and \(ne .Values.NAMESPACE \"default\"\) \(ne .Values.NAMESPACE_DISABLED \"yes\"\) }}" \{} \; +# Additional config +cat << EOL > /tmp/additional-namespace.config + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + labels: + pod-security.kubernetes.io/enforce: restricted + {{- end }} +EOL +# shellcheck disable=SC1083 +find ./ -name "*namespace.yaml" -exec sed -i "/namespace.*/r /tmp/additional-namespace.config" \{} \; # shellcheck disable=SC1083 find ./ -name "*namespace.yaml" -exec sed -i "$ a {{- end }}" \{} \; # shellcheck disable=SC1083 @@ -290,11 +315,15 @@ cat << EOL > /tmp/additional.config value: "{{ .Values.SERVERINFO_TOKEN }}" - name: NEXTCLOUD_DEFAULT_QUOTA value: "{{ .Values.NEXTCLOUD_DEFAULT_QUOTA }}" + - name: NEXTCLOUD_SKELETON_DIRECTORY + value: "{{ .Values.NEXTCLOUD_SKELETON_DIRECTORY }}" - name: NEXTCLOUD_MAINTENANCE_WINDOW value: "{{ .Values.NEXTCLOUD_MAINTENANCE_WINDOW }}" EOL # shellcheck disable=SC1083 find ./ -name '*nextcloud-deployment.yaml' -exec sed -i "/^.*\- env:/r /tmp/additional.config" \{} \; +# shellcheck disable=SC1083 +find ./ -name '*fulltextsearch-deployment.yaml' -exec sed -i 's/{{ .Values.FULLTEXTSEARCH_JAVA_OPTIONS }}/{{ .Values.FULLTEXTSEARCH_JAVA_OPTIONS | default "-Xms512M -Xmx512M" }}/' \{} \; # Additional config cat << EOL > /tmp/additional-apache.config @@ -314,8 +343,20 @@ 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 '*deployment.yaml' -exec sed -i '/image: nextcloud/s/$/"/;s|image: nextcloud/|image: "{{ .Values.IMAGE_MIRROR_PREFIX }}{{ .Values.NEXTCLOUD_IMAGE_ORG }}/|;' \{} \; +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" }} @@ -335,8 +376,24 @@ spec: - from: - podSelector: {} egress: - - to: - - podSelector: {} + - {} # Allows all egress traffic +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + namespace: "{{ .Values.NAMESPACE }}" + name: nextcloud-aio-webserver-allow +spec: + podSelector: + matchExpressions: + - key: io.kompose.service + operator: In + values: + - nextcloud-aio-apache + policyTypes: + - Ingress + ingress: + - {} # Allows all ingress traffic {{- end }} EOL @@ -350,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 @@ -364,11 +421,12 @@ sed -i '/^NEXTCLOUD_MOUNT/d' /tmp/sample.conf sed -i '/_ENABLED.*/s/ yes / "yes" /' /tmp/sample.conf sed -i '/_ENABLED.*/s/ no / "no" /' /tmp/sample.conf sed -i 's|^NEXTCLOUD_TRUSTED_CACERTS_DIR: .*|NEXTCLOUD_TRUSTED_CACERTS_DIR: # Setting this to any value allows to automatically import root certificates into the Nextcloud container|' /tmp/sample.conf -sed -i 's|10737418240|"10737418240"|' /tmp/sample.conf +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' >> /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 @@ -380,13 +438,14 @@ cat << ADDITIONAL_CONFIG >> /tmp/sample.conf NAMESPACE: default # By changing this, you can adjust the namespace of the installation which allows to install multiple instances on one kubernetes cluster NAMESPACE_DISABLED: "no" # By setting this to "yes", you can disabled the creation of the namespace so that you can use a pre-created one -NETWORK_POLICY_ENABLED: "no" # By setting this to "yes", you can enable a network policy that limits network access to the same namespace. ⚠️ Attention: this breaks if you use an ingress!!! So it should be disabled if you do so! +NETWORK_POLICY_ENABLED: "no" # By setting this to "yes", you can enable a network policy that limits network access to the same namespace. Except the Web server service which is reachable from all endpoints. SUBSCRIPTION_KEY: # This allows to set the Nextcloud Enterprise key via ENV SERVERINFO_TOKEN: # This allows to set the serverinfo app token for monitoring your Nextcloud via the serverinfo app APPS_ALLOWLIST: # This allows to configure allowed apps that will be shown in Nextcloud's Appstore. You need to enter the app-IDs of the apps here and separate them with spaces. E.g. 'files richdocuments' ADDITIONAL_TRUSTED_PROXY: # Allows to add one additional ip-address to Nextcloud's trusted proxies and to the Office WOPI-allowlist automatically. Set it e.g. like this: 'your.public.ip-address'. You can also use an ip-range here. ADDITIONAL_TRUSTED_DOMAIN: # Allows to add one domain to Nextcloud's trusted domains and also generates a certificate automatically for it NEXTCLOUD_DEFAULT_QUOTA: "10 GB" # Allows to adjust the default quota that will be taken into account in Nextcloud for new users. Setting it to "unlimited" will set it to unlimited +NEXTCLOUD_SKELETON_DIRECTORY: # Allows to adjust the sekeleton dir for Nextcloud. Setting it to "empty" will set the value to an empty string "" which will turn off the setting for new users in Nextcloud. NEXTCLOUD_MAINTENANCE_WINDOW: # Allows to define the maintenance window for Nextcloud. See https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/background_jobs_configuration.html#parameters for possible values SMTP_HOST: # (empty by default): The hostname of the SMTP server. SMTP_SECURE: # (empty by default): Set to 'ssl' to use SSL, or 'tls' to use STARTTLS. @@ -398,10 +457,6 @@ MAIL_FROM_ADDRESS: # (not set by default): Set the local-part for the 'f MAIL_DOMAIN: # (not set by default): Set a different domain for the emails than the domain where Nextcloud is installed. TALK_MAX_STREAM_BITRATE: "1048576" # This allows to adjust the max stream bitrate of the talk hpb TALK_MAX_SCREEN_BITRATE: "2097152" # This allows to adjust the max stream bitrate of the talk hpb - -IMAGE_MIRROR_PREFIX: # Setting this allows you to pull Nextcloud images through a mirror registry. -NEXTCLOUD_IMAGE_ORG: nextcloud # Setting this allows you to change the image's org name in case a different image needs to be used e.g. for compliance reasons. -ALPINE_IMAGE_ORG: # Setting this allows you to change the image's org name in case a different image needs to be used e.g. for compliance reasons. ADDITIONAL_CONFIG mv /tmp/sample.conf ../helm-chart/values.yaml @@ -432,6 +487,55 @@ find ./ -name "*nextcloud-aio-elasticsearch-persistentvolumeclaim.yaml" -exec se # shellcheck disable=SC1083 find ./ -name "*nextcloud-aio-elasticsearch-persistentvolumeclaim.yaml" -exec sed -i "$ a {{- end }}" \{} \; +cat << EOL > /tmp/security.conf + # The items below only work in container context + allowPrivilegeEscalation: false + capabilities: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} +EOL +# shellcheck disable=SC1083 +find ./ \( -not -name '*collabora-deployment.yaml*' -not -name '*apache-deployment.yaml*' -not -name '*onlyoffice-deployment.yaml*' -name "*deployment.yaml" \) -exec sed -i "/^ securityContext:$/r /tmp/security.conf" \{} \; + +# shellcheck disable=SC1083 +find ./ -name '*collabora-deployment.yaml*' -exec sed -i "/ADDITIONAL_COLLABORA_OPTIONS_PLACEHOLDER/d" \{} \; +# shellcheck disable=SC1083 +find ./ -name '*collabora-deployment.yaml*' -exec sed -i "s/- args:/- args: \{\{ .Values.ADDITIONAL_COLLABORA_OPTIONS | default list | toJson \}\}/" \{} \; + +cat << EOL > /tmp/security.conf + # The items below only work in container context + allowPrivilegeEscalation: false + capabilities: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} + add: ["NET_BIND_SERVICE"] +EOL + +# shellcheck disable=SC1083 +find ./ -name '*apache-deployment.yaml*' -exec sed -i "/^ securityContext:$/r /tmp/security.conf" \{} \; + +cat << EOL > /tmp/security.conf + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} # AIO-config - do not change this comment! + securityContext: + # The items below only work in container context + allowPrivilegeEscalation: false + capabilities: + {{- if eq (.Values.RPSS_ENABLED | default "no") "yes" }} + drop: ["ALL"] + {{- else }} + drop: ["NET_RAW"] + {{- end }} + {{- end }} # AIO-config - do not change this comment! +EOL +# shellcheck disable=SC1083 +find ./ -name '*nextcloud-deployment.yaml*' -exec sed -i "/image: .*nextcloud.*aio-nextcloud:.*/r /tmp/security.conf" \{} \; + chmod 777 -R ./ # Seems like the dir needs to match the name of the chart diff --git a/nextcloud-aio-helm-chart/values.yaml b/nextcloud-aio-helm-chart/values.yaml index 6591aae8..25fb2c92 100755 --- a/nextcloud-aio-helm-chart/values.yaml +++ b/nextcloud-aio-helm-chart/values.yaml @@ -10,6 +10,7 @@ SIGNALING_SECRET: # TODO! This needs to be a unique and good password! TALK_INTERNAL_SECRET: # TODO! This needs to be a unique and good password! TIMEZONE: Europe/Berlin # TODO! This is the timezone that your containers will use. TURN_SECRET: # TODO! This needs to be a unique and good password! +WHITEBOARD_SECRET: # TODO! This needs to be a unique and good password! CLAMAV_ENABLED: "no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. COLLABORA_ENABLED: "no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. @@ -18,11 +19,13 @@ IMAGINARY_ENABLED: "no" # Setting this to "yes" (with quotes) enables t ONLYOFFICE_ENABLED: "no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. TALK_ENABLED: "no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. TALK_RECORDING_ENABLED: "no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. +WHITEBOARD_ENABLED: "no" # Setting this to "yes" (with quotes) enables the option in Nextcloud automatically. -APACHE_MAX_SIZE: "10737418240" # This needs to be an integer and in sync with NEXTCLOUD_UPLOAD_LIMIT +APACHE_MAX_SIZE: "17179869184" # This needs to be an integer and in sync with NEXTCLOUD_UPLOAD_LIMIT APACHE_PORT: 443 # Changing this to a different value than 443 will allow you to run it behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else). +ADDITIONAL_COLLABORA_OPTIONS: ['--o:security.seccomp=true'] # You can add additional collabora options here by using the array syntax. COLLABORA_DICTIONARIES: de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru # You can change this in order to enable other dictionaries for collabora -COLLABORA_SECCOMP_POLICY: --o:security.seccomp=true # Changing the value to false allows to disable the seccomp feature of the Collabora container. +FULLTEXTSEARCH_JAVA_OPTIONS: -Xms512M -Xmx512M # Allows to adjust the fulltextsearch java options. INSTALL_LATEST_MAJOR: no # Setting this to yes will install the latest Major Nextcloud version upon the first installation NEXTCLOUD_ADDITIONAL_APKS: imagemagick # This allows to add additional packages to the Nextcloud container permanently. Default is imagemagick but can be overwritten by modifying this value. NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS: imagick # This allows to add additional php extensions to the Nextcloud container permanently. Default is imagick but can be overwritten by modifying this value. @@ -30,12 +33,13 @@ NEXTCLOUD_MAX_TIME: 3600 # This allows to change the upload time limit NEXTCLOUD_MEMORY_LIMIT: 512M # This allows to change the PHP memory limit of the Nextcloud container NEXTCLOUD_STARTUP_APPS: deck twofactor_totp tasks calendar contacts notes # Allows to modify the Nextcloud apps that are installed on starting AIO the first time NEXTCLOUD_TRUSTED_CACERTS_DIR: # Setting this to any value allows to automatically import root certificates into the Nextcloud container -NEXTCLOUD_UPLOAD_LIMIT: 10G # This allows to change the upload limit of the Nextcloud container +NEXTCLOUD_UPLOAD_LIMIT: 16G # This allows to change the upload limit of the Nextcloud container REMOVE_DISABLED_APPS: yes # Setting this to no keep Nextcloud apps that are disabled via their switch and not uninstall them if they should be installed in Nextcloud. -TALK_PORT: 3478 # This allows to adjust the port that the talk container is using. +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 +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 @@ -46,16 +50,18 @@ NEXTCLOUD_DATA_STORAGE_SIZE: 5Gi # You can change the size of the nextclou NEXTCLOUD_TRUSTED_CACERTS_STORAGE_SIZE: 1Gi # You can change the size of the nextcloud-trusted-cacerts volume that default to 1Gi with this value ONLYOFFICE_STORAGE_SIZE: 1Gi # You can change the size of the onlyoffice volume that default to 1Gi with this value REDIS_STORAGE_SIZE: 1Gi # You can change the size of the redis volume that default to 1Gi with this value +TALK_RECORDING_STORAGE_SIZE: 1Gi # You can change the size of the talk-recording volume that default to 1Gi with this value NAMESPACE: default # By changing this, you can adjust the namespace of the installation which allows to install multiple instances on one kubernetes cluster NAMESPACE_DISABLED: "no" # By setting this to "yes", you can disabled the creation of the namespace so that you can use a pre-created one -NETWORK_POLICY_ENABLED: "no" # By setting this to "yes", you can enable a network policy that limits network access to the same namespace. ⚠️ Attention: this breaks if you use an ingress!!! So it should be disabled if you do so! +NETWORK_POLICY_ENABLED: "no" # By setting this to "yes", you can enable a network policy that limits network access to the same namespace. Except the Web server service which is reachable from all endpoints. SUBSCRIPTION_KEY: # This allows to set the Nextcloud Enterprise key via ENV SERVERINFO_TOKEN: # This allows to set the serverinfo app token for monitoring your Nextcloud via the serverinfo app APPS_ALLOWLIST: # This allows to configure allowed apps that will be shown in Nextcloud's Appstore. You need to enter the app-IDs of the apps here and separate them with spaces. E.g. 'files richdocuments' ADDITIONAL_TRUSTED_PROXY: # Allows to add one additional ip-address to Nextcloud's trusted proxies and to the Office WOPI-allowlist automatically. Set it e.g. like this: 'your.public.ip-address'. You can also use an ip-range here. ADDITIONAL_TRUSTED_DOMAIN: # Allows to add one domain to Nextcloud's trusted domains and also generates a certificate automatically for it NEXTCLOUD_DEFAULT_QUOTA: "10 GB" # Allows to adjust the default quota that will be taken into account in Nextcloud for new users. Setting it to "unlimited" will set it to unlimited +NEXTCLOUD_SKELETON_DIRECTORY: # Allows to adjust the sekeleton dir for Nextcloud. Setting it to "empty" will set the value to an empty string "" which will turn off the setting for new users in Nextcloud. NEXTCLOUD_MAINTENANCE_WINDOW: # Allows to define the maintenance window for Nextcloud. See https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/background_jobs_configuration.html#parameters for possible values SMTP_HOST: # (empty by default): The hostname of the SMTP server. SMTP_SECURE: # (empty by default): Set to 'ssl' to use SSL, or 'tls' to use STARTTLS. @@ -67,7 +73,3 @@ MAIL_FROM_ADDRESS: # (not set by default): Set the local-part for the 'f MAIL_DOMAIN: # (not set by default): Set a different domain for the emails than the domain where Nextcloud is installed. TALK_MAX_STREAM_BITRATE: "1048576" # This allows to adjust the max stream bitrate of the talk hpb TALK_MAX_SCREEN_BITRATE: "2097152" # This allows to adjust the max stream bitrate of the talk hpb - -IMAGE_MIRROR_PREFIX: # Setting this allows you to pull Nextcloud images through a mirror registry. -NEXTCLOUD_IMAGE_ORG: nextcloud # Setting this allows you to change the image's org name in case a different image needs to be used e.g. for compliance reasons. -ALPINE_IMAGE_ORG: # Setting this allows you to change the image's org name in case a different image needs to be used e.g. for compliance reasons. diff --git a/php/README.md b/php/README.md index 70028349..af824818 100644 --- a/php/README.md +++ b/php/README.md @@ -33,7 +33,8 @@ docker run \ --rm \ --name nextcloud-aio-mastercontainer \ --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ -nextcloud/all-in-one:latest +--volume /var/run/docker.sock:/var/run/docker.sock \ +ghcr.io/nextcloud-releases/all-in-one:latest ``` ### 4. Start your server @@ -55,6 +56,7 @@ Note: You can restart the server by preceding the command with other environment |-----------------------------------------|----------------------------------------| | `composer run dev` | Starts the development server | | `composer run psalm` | Run Psalm static analysis | +| `composer run psalm:strict` | Run Psalm static analysis strict | | `composer run psalm:update-baseline` | Run Psalm with `--update-baseline` arg | | `composer run lint` | Run PHP Syntax check | | `composer run lint:twig` | Run Twig Syntax check | diff --git a/php/composer.json b/php/composer.json index 3ac4ea36..892bdd5d 100644 --- a/php/composer.json +++ b/php/composer.json @@ -1,37 +1,38 @@ { - "autoload": { - "psr-4": { - "AIO\\": ["src/"] - } - }, - "require": { - "php": "8.3.*", - "ext-json": "*", - "ext-sodium": "*", - "ext-curl": "*", - "slim/slim": "^4.11", - "php-di/slim-bridge": "^3.3", - "guzzlehttp/guzzle": "^7.5", - "guzzlehttp/psr7": "^2.4", - "http-interop/http-factory-guzzle": "^1.2", - "slim/twig-view": "^3.3", - "slim/csrf": "^1.3", - "ext-apcu": "*" - }, - "require-dev": { - "sserbin/twig-linter": "@dev", - "vimeo/psalm": "^5.25", - "wapmorgan/php-deprecation-detector": "dev-master" - }, - "scripts": { - "dev": [ - "Composer\\Config::disableProcessTimeout", - "php -S localhost:8080 -t public" - ], + "autoload": { + "psr-4": { + "AIO\\": ["src/"] + } + }, + "require": { + "php": "8.4.*", + "ext-json": "*", + "ext-sodium": "*", + "ext-curl": "*", + "slim/slim": "^4.11", + "php-di/slim-bridge": "^3.3", + "guzzlehttp/guzzle": "^7.5", + "guzzlehttp/psr7": "^2.4", + "http-interop/http-factory-guzzle": "^1.2", + "slim/twig-view": "^3.3", + "slim/csrf": "^1.3", + "ext-apcu": "*" + }, + "require-dev": { + "sserbin/twig-linter": "@dev", + "vimeo/psalm": "^6.0", + "wapmorgan/php-deprecation-detector": "dev-master" + }, + "scripts": { + "dev": [ + "Composer\\Config::disableProcessTimeout", + "php -S localhost:8080 -t public" + ], "psalm": "psalm --threads=1", "psalm:update-baseline": "psalm --threads=1 --monochrome --no-progress --output-format=text --update-baseline", + "psalm:strict": "psalm --threads=1 --show-info=true", "lint": "php -l src/*.php src/**/*.php public/index.php", "lint:twig": "twig-linter lint ./templates", - "php-deprecation-detector": "phpdd scan -n -t 8.3 src/*.php src/**/*.php public/index.php" + "php-deprecation-detector": "phpdd scan -n -t 8.4 src/*.php src/**/*.php public/index.php" } } diff --git a/php/composer.lock b/php/composer.lock index fa97d76c..77403624 100644 --- a/php/composer.lock +++ b/php/composer.lock @@ -4,26 +4,26 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0e1d24f3fa776163acefdebc91da39d3", + "content-hash": "19598625395cc28e64f15d2719f8f98f", "packages": [ { "name": "guzzlehttp/guzzle", - "version": "7.9.2", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -114,7 +114,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -130,20 +130,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T11:22:20+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.3", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -151,7 +151,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -197,7 +197,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.3" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -213,20 +213,20 @@ "type": "tidelift" } ], - "time": "2024-07-18T10:29:17+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -242,7 +242,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -313,7 +313,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -329,20 +329,20 @@ "type": "tidelift" } ], - "time": "2024-07-18T11:15:46+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "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,37 +385,38 @@ ], "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": "v1.3.3", + "version": "v2.0.8", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7581a4407012f5f53365e11bafc520fd7f36bc9b", + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^8.1" }, "require-dev": { - "nesbot/carbon": "^2.61", - "pestphp/pest": "^1.21.3", - "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11" + "illuminate/support": "^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -447,7 +448,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-11-08T14:08:06+00:00" + "time": "2026-01-08T16:22:46+00:00" }, { "name": "nikic/fast-route", @@ -501,16 +502,16 @@ }, { "name": "php-di/invoker", - "version": "2.3.4", + "version": "2.3.7", "source": { "type": "git", "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" + "reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/3c1ddfdef181431fbc4be83378f6d036d59e81e1", + "reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1", "shasum": "" }, "require": { @@ -520,7 +521,7 @@ "require-dev": { "athletic/athletic": "~0.1.8", "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.0 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -544,7 +545,7 @@ ], "support": { "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.7" }, "funding": [ { @@ -552,24 +553,24 @@ "type": "github" } ], - "time": "2023-09-08T09:24:21+00:00" + "time": "2025-08-30T10:22:22+00:00" }, { "name": "php-di/php-di", - "version": "7.0.7", + "version": "7.1.1", "source": { "type": "git", "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" + "reference": "f88054cc052e40dbe7b383c8817c19442d480352" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f88054cc052e40dbe7b383c8817c19442d480352", + "reference": "f88054cc052e40dbe7b383c8817c19442d480352", "shasum": "" }, "require": { - "laravel/serializable-closure": "^1.0", + "laravel/serializable-closure": "^1.0 || ^2.0", "php": ">=8.0", "php-di/invoker": "^2.0", "psr/container": "^1.1 || ^2.0" @@ -581,8 +582,8 @@ "friendsofphp/php-cs-fixer": "^3", "friendsofphp/proxy-manager-lts": "^1", "mnapoli/phpunit-easymock": "^1.3", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.6" + "phpunit/phpunit": "^9.6 || ^10 || ^11", + "vimeo/psalm": "^5|^6" }, "suggest": { "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" @@ -613,7 +614,7 @@ ], "support": { "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.1.1" }, "funding": [ { @@ -625,20 +626,20 @@ "type": "tidelift" } ], - "time": "2024-07-21T15:55:45+00:00" + "time": "2025-08-16T11:10:48+00:00" }, { "name": "php-di/slim-bridge", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/PHP-DI/Slim-Bridge.git", - "reference": "d14c95b34b3c5ba2e8c40020dd93fdcc8f3ba875" + "reference": "02ab0274a19d104d74561164f8915b62d93f3cf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Slim-Bridge/zipball/d14c95b34b3c5ba2e8c40020dd93fdcc8f3ba875", - "reference": "d14c95b34b3c5ba2e8c40020dd93fdcc8f3ba875", + "url": "https://api.github.com/repos/PHP-DI/Slim-Bridge/zipball/02ab0274a19d104d74561164f8915b62d93f3cf0", + "reference": "02ab0274a19d104d74561164f8915b62d93f3cf0", "shasum": "" }, "require": { @@ -649,6 +650,7 @@ }, "require-dev": { "laminas/laminas-diactoros": "^2.1", + "mnapoli/hard-mode": "^0.3.0", "phpunit/phpunit": ">= 7.0 < 10" }, "type": "library", @@ -664,9 +666,9 @@ "description": "PHP-DI integration in Slim", "support": { "issues": "https://github.com/PHP-DI/Slim-Bridge/issues", - "source": "https://github.com/PHP-DI/Slim-Bridge/tree/3.4.0" + "source": "https://github.com/PHP-DI/Slim-Bridge/tree/3.4.1" }, - "time": "2023-06-29T14:08:47+00:00" + "time": "2024-06-19T15:47:45+00:00" }, { "name": "psr/container", @@ -996,16 +998,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -1040,9 +1042,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "ralouphie/getallheaders", @@ -1090,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": { @@ -1110,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" }, @@ -1142,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.14.0", + "version": "4.15.1", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", - "reference": "5943393b88716eb9e82c4161caa956af63423913" + "reference": "887893516557506f254d950425ce7f5387a26970" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/5943393b88716eb9e82c4161caa956af63423913", - "reference": "5943393b88716eb9e82c4161caa956af63423913", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/887893516557506f254d950425ce7f5387a26970", + "reference": "887893516557506f254d950425ce7f5387a26970", "shasum": "" }, "require": { "ext-json": "*", "nikic/fast-route": "^1.3", - "php": "^7.4 || ^8.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", @@ -1172,7 +1172,7 @@ "psr/log": "^1.1 || ^2.0 || ^3.0" }, "require-dev": { - "adriansuter/php-autoload-override": "^1.4", + "adriansuter/php-autoload-override": "^1.4 || ^2", "ext-simplexml": "*", "guzzlehttp/psr7": "^2.6", "httpsoft/http-message": "^1.1", @@ -1182,12 +1182,12 @@ "nyholm/psr7-server": "^1.1", "phpspec/prophecy": "^1.19", "phpspec/prophecy-phpunit": "^2.1", - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^9.6", + "phpstan/phpstan": "^1 || ^2", + "phpunit/phpunit": "^9.6 || ^10 || ^11 || ^12", "slim/http": "^1.3", "slim/psr7": "^1.6", "squizlabs/php_codesniffer": "^3.10", - "vimeo/psalm": "^5.24" + "vimeo/psalm": "^5 || ^6" }, "suggest": { "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", @@ -1214,17 +1214,17 @@ { "name": "Andrew Smith", "email": "a.smith@silentworks.co.uk", - "homepage": "http://silentworks.co.uk" + "homepage": "https://silentworks.co.uk" }, { "name": "Rob Allen", "email": "rob@akrabat.com", - "homepage": "http://akrabat.com" + "homepage": "https://akrabat.com" }, { "name": "Pierre Berube", "email": "pierre@lgse.com", - "homepage": "http://www.lgse.com" + "homepage": "https://www.lgse.com" }, { "name": "Gabriel Manricks", @@ -1260,20 +1260,20 @@ "type": "tidelift" } ], - "time": "2024-06-13T08:54:48+00:00" + "time": "2025-11-21T12:23:44+00:00" }, { "name": "slim/twig-view", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/slimphp/Twig-View.git", - "reference": "1b351536b9a07ed90a3563ee9d71a987c5d74610" + "reference": "b4268d87d0e327feba5f88d32031e9123655b909" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/1b351536b9a07ed90a3563ee9d71a987c5d74610", - "reference": "1b351536b9a07ed90a3563ee9d71a987c5d74610", + "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/b4268d87d0e327feba5f88d32031e9123655b909", + "reference": "b4268d87d0e327feba5f88d32031e9123655b909", "shasum": "" }, "require": { @@ -1281,12 +1281,12 @@ "psr/http-message": "^1.1 || ^2.0", "slim/slim": "^4.12", "symfony/polyfill-php81": "^1.29", - "twig/twig": "^3.8" + "twig/twig": "^3.11" }, "require-dev": { "phpspec/prophecy-phpunit": "^2.0", "phpstan/phpstan": "^1.10.59", - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^9.6 || ^10", "psr/http-factory": "^1.0", "squizlabs/php_codesniffer": "^3.9" }, @@ -1323,22 +1323,22 @@ ], "support": { "issues": "https://github.com/slimphp/Twig-View/issues", - "source": "https://github.com/slimphp/Twig-View/tree/3.4.0" + "source": "https://github.com/slimphp/Twig-View/tree/3.4.1" }, - "time": "2024-04-28T20:36:39+00:00" + "time": "2024-09-26T05:42:02+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -1346,12 +1346,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -1376,7 +1376,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1392,24 +1392,24 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1420,8 +1420,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1455,7 +1455,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -1466,29 +1466,34 @@ "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": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1499,8 +1504,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1535,7 +1540,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -1547,83 +1552,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-19T12:30:46+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -1631,30 +1560,30 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1691,7 +1620,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -1702,35 +1631,39 @@ "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": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "twig/twig", - "version": "v3.10.3", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572", - "reference": "67f29781ffafa520b0bbfbd8384674b42db04572", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { + "phpstan/phpstan": "^2.0", "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, @@ -1774,7 +1707,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.10.3" + "source": "https://github.com/twigphp/Twig/tree/v3.23.0" }, "funding": [ { @@ -1786,49 +1719,42 @@ "type": "tidelift" } ], - "time": "2024-05-16T10:04:27+00:00" + "time": "2026-01-23T21:00:41+00:00" } ], "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.4", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f", + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "react/promise": "^2", - "vimeo/psalm": "^3.12" + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "files": [ - "lib/functions.php", - "lib/Internal/functions.php" + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" ], "psr-4": { - "Amp\\": "lib" + "Amp\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1836,10 +1762,6 @@ "MIT" ], "authors": [ - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, { "name": "Aaron Piotrowski", "email": "aaron@trowski.com" @@ -1851,6 +1773,10 @@ { "name": "Niklas Keller", "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" } ], "description": "A non-blocking concurrency framework for PHP applications.", @@ -1867,9 +1793,8 @@ "promise" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.4" + "source": "https://github.com/amphp/amp/tree/v3.1.1" }, "funding": [ { @@ -1877,41 +1802,45 @@ "type": "github" } ], - "time": "2024-03-21T18:52:26+00:00" + "time": "2025-08-27T21:42:00+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.2", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", "shasum": "" }, "require": { - "amphp/amp": "^2", - "php": ">=7.1" + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" }, "type": "library", "autoload": { "files": [ - "lib/functions.php" + "src/functions.php", + "src/Internal/functions.php" ], "psr-4": { - "Amp\\ByteStream\\": "lib" + "Amp\\ByteStream\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1940,7 +1869,7 @@ ], "support": { "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" }, "funding": [ { @@ -1948,44 +1877,39 @@ "type": "github" } ], - "time": "2024-04-13T18:00:56+00:00" + "time": "2025-03-16T17:10:27+00:00" }, { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", + "name": "amphp/cache", + "version": "v2.0.1", "source": { "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", "shasum": "" }, "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" }, "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" }, + "type": "library", "autoload": { "psr-4": { - "PackageVersions\\": "src/PackageVersions" + "Amp\\Cache\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1994,69 +1918,653 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" + "name": "Niklas Keller", + "email": "me@kelunik.com" }, { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" } ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" }, "funding": [ { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", + "url": "https://github.com/amphp", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2022-01-17T14:14:24+00:00" + "time": "2024-04-19T03:38:06+00:00" }, { - "name": "composer/pcre", - "version": "3.2.0", + "name": "amphp/dns", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90" + "url": "https://github.com/amphp/dns.git", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90", - "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/process": "^2", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "ext-json": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "addr@daverandom.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], + "support": { + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-19T15:43:40+00:00" + }, + { + "name": "amphp/parallel", + "version": "v2.3.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/parallel.git", + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parallel/zipball/296b521137a54d3a02425b464e5aee4c93db2c60", + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/pipeline": "^1", + "amphp/process": "^2", + "amphp/serialization": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "files": [ + "src/Context/functions.php", + "src/Context/Internal/functions.php", + "src/Ipc/functions.php", + "src/Worker/functions.php" + ], + "psr-4": { + "Amp\\Parallel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", + "keywords": [ + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" + ], + "support": { + "issues": "https://github.com/amphp/parallel/issues", + "source": "https://github.com/amphp/parallel/tree/v2.3.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-11-15T06:23:42+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "7b52598c2e9105ebcddf247fc523161581930367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T16:33:53+00:00" + }, + { + "name": "amphp/process", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:13:44+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/socket", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/socket.git", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^6.5 | ^7", + "league/uri-interfaces": "^2.3 | ^7", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" + ], + "psr-4": { + "Amp\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", + "keywords": [ + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" + ], + "support": { + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T14:33:03+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-08-03T19:31:26+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.8" + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.11.8", - "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - }, "phpstan": { "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-main": "3.x-dev" } }, "autoload": { @@ -2084,7 +2592,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.2.0" + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -2100,28 +2608,28 @@ "type": "tidelift" } ], - "time": "2024-07-25T09:36:02+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { "name": "composer/semver", - "version": "3.4.2", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -2165,7 +2673,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.2" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -2175,13 +2683,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-07-12T11:35:52+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", @@ -2249,6 +2753,102 @@ ], "time": "2024-05-06T16:37:16+00:00" }, + { + "name": "danog/advanced-json-rpc", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/danog/php-advanced-json-rpc.git", + "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91" + }, + "dist": { + "type": "zip", + "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 || ^6" + }, + "replace": { + "felixfbecker/php-advanced-json-rpc": "^3" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "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.3" + }, + "time": "2026-01-12T21:07:10+00:00" + }, + { + "name": "daverandom/libdns", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, { "name": "dnoegel/php-xdg-base-dir", "version": "v0.1.1", @@ -2288,29 +2888,30 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -2318,7 +2919,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2329,67 +2930,22 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-01-30T19:34:25+00:00" - }, - { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "shasum": "" - }, - "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "AdvancedJsonRpc\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "A more advanced JSONRPC implementation", - "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" - }, - "time": "2021-06-11T22:34:44+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", + "version": "v1.5.3", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", "shasum": "" }, "require": { @@ -2430,22 +2986,22 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" }, - "time": "2022-03-02T22:36:06+00:00" + "time": "2024-04-30T00:40:11+00:00" }, { "name": "fidry/cpu-core-counter", - "version": "1.1.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -2455,10 +3011,10 @@ "fidry/makefile": "^0.2.0", "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -2485,7 +3041,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -2493,20 +3049,260 @@ "type": "github" } ], - "time": "2024-02-07T09:43:46+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { - "name": "netresearch/jsonmapper", - "version": "v4.4.1", + "name": "kelunik/certificate", + "version": "v1.1.3", "source": { "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, + { + "name": "league/uri", + "version": "7.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76", + "shasum": "" + }, + "require": { + "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", + "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", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "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.8.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-01-14T17:24:56+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "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", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "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.8.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-01-15T06:54:53+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", "shasum": "" }, "require": { @@ -2542,31 +3338,33 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0" }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-09-08T10:20:00+00:00" }, { "name": "nikic/php-parser", - "version": "v4.19.1", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -2574,7 +3372,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -2598,9 +3396,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2024-03-17T08:10:35+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -2657,16 +3455,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + "reference": "2f5cbed597cb261d1ea458f3da3a9ad32e670b1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2f5cbed597cb261d1ea458f3da3a9ad32e670b1e", + "reference": "2f5cbed597cb261d1ea458f3da3a9ad32e670b1e", "shasum": "" }, "require": { @@ -2674,18 +3472,19 @@ "ext-filter": "*", "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7", - "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", + "mockery/mockery": "~1.3.5 || ~1.6.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" }, "type": "library", "extra": { @@ -2715,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.4.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.1" }, - "time": "2024-05-21T05:55:05+00:00" + "time": "2026-01-20T15:30:42+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "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.13" + "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": { @@ -2773,36 +3572,36 @@ "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.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2026-01-06T21:53:42+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -2820,35 +3619,107 @@ "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/1.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { - "name": "sebastian/diff", - "version": "6.0.2", + "name": "revolt/event-loop", + "version": "v1.0.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0", - "symfony/process": "^4.2 || ^5" + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" + }, + "time": "2025-08-27T21:33:23+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" } }, "autoload": { @@ -2881,7 +3752,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" }, "funding": [ { @@ -2889,20 +3760,20 @@ "type": "github" } ], - "time": "2024-07-03T04:53:05+00:00" + "time": "2025-02-07T04:55:46+00:00" }, { "name": "spatie/array-to-xml", - "version": "3.3.0", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", - "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", "shasum": "" }, "require": { @@ -2945,7 +3816,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" }, "funding": [ { @@ -2957,7 +3828,7 @@ "type": "github" } ], - "time": "2024-05-01T10:20:27+00:00" + "time": "2025-12-15T09:00:41+00:00" }, { "name": "sserbin/twig-linter", @@ -2965,16 +3836,16 @@ "source": { "type": "git", "url": "https://github.com/sserbin/twig-linter.git", - "reference": "0b7cc4d61b6cf423ec837a0969ea5e0c8f017ddb" + "reference": "932c7f1dcc79cd54aa011804d42aa7bbb14a970f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sserbin/twig-linter/zipball/0b7cc4d61b6cf423ec837a0969ea5e0c8f017ddb", - "reference": "0b7cc4d61b6cf423ec837a0969ea5e0c8f017ddb", + "url": "https://api.github.com/repos/sserbin/twig-linter/zipball/932c7f1dcc79cd54aa011804d42aa7bbb14a970f", + "reference": "932c7f1dcc79cd54aa011804d42aa7bbb14a970f", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "1.11.99.5", + "composer-runtime-api": "^2.0", "php": "^7.4|^8.0", "symfony/console": "^5.4 || ^6.1", "symfony/finder": "^5.4 || ^6.1", @@ -2983,7 +3854,7 @@ "require-dev": { "phpunit/phpunit": "^7.3||^8.2|^9.5", "squizlabs/php_codesniffer": "^3.3", - "vimeo/psalm": "^4.7" + "vimeo/psalm": "^4.7 || ^5.8" }, "default-branch": true, "bin": [ @@ -3013,22 +3884,22 @@ ], "support": { "issues": "https://github.com/sserbin/twig-linter/issues", - "source": "https://github.com/sserbin/twig-linter/tree/3.1.0" + "source": "https://github.com/sserbin/twig-linter/tree/3.1.2" }, - "time": "2022-06-29T11:06:19+00:00" + "time": "2025-06-03T06:31:48+00:00" }, { "name": "symfony/console", - "version": "v6.4.10", + "version": "v6.4.32", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc" + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/504974cbe43d05f83b201d6498c206f16fc0cdbc", - "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc", + "url": "https://api.github.com/repos/symfony/console/zipball/0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", "shasum": "" }, "require": { @@ -3093,7 +3964,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.10" + "source": "https://github.com/symfony/console/tree/v6.4.32" }, "funding": [ { @@ -3104,34 +3975,38 @@ "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": "2024-07-26T12:30:32+00:00" + "time": "2026-01-13T08:45:59+00:00" }, { "name": "symfony/filesystem", - "version": "v7.1.2", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "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": { @@ -3159,7 +4034,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -3170,25 +4045,29 @@ "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": "2024-06-28T10:03:55+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/finder", - "version": "v6.4.10", + "version": "v6.4.33", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "af29198d87112bebdd397bd7735fbd115997824c" + "reference": "24965ca011dac87431729640feef8bcf7b5523e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/af29198d87112bebdd397bd7735fbd115997824c", - "reference": "af29198d87112bebdd397bd7735fbd115997824c", + "url": "https://api.github.com/repos/symfony/finder/zipball/24965ca011dac87431729640feef8bcf7b5523e0", + "reference": "24965ca011dac87431729640feef8bcf7b5523e0", "shasum": "" }, "require": { @@ -3223,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.10" + "source": "https://github.com/symfony/finder/tree/v6.4.33" }, "funding": [ { @@ -3234,29 +4113,33 @@ "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": "2024-07-24T07:06:38+00:00" + "time": "2026-01-26T13:03:48+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3264,8 +4147,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3301,7 +4184,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -3312,29 +4195,33 @@ "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": "2024-05-31T15:07:36+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3342,8 +4229,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3382,7 +4269,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -3393,25 +4280,109 @@ "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": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.5.0", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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-06-24T13:30:11+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -3424,12 +4395,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -3465,7 +4436,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -3476,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": "2024-04-18T09:32:20+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v7.1.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", + "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" }, @@ -3508,12 +4484,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "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": { @@ -3552,7 +4527,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.3" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { @@ -3563,33 +4538,39 @@ "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": "2024-07-22T10:25:37+00:00" + "time": "2026-01-12T10:54:30+00:00" }, { "name": "vimeo/psalm", - "version": "5.25.0", + "version": "6.14.3", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505" + "reference": "d0b040a91f280f071c1abcb1b77ce3822058725a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", - "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d0b040a91f280f071c1abcb1b77ce3822058725a", + "reference": "d0b040a91f280f071c1abcb1b77ce3822058725a", "shasum": "" }, "require": { - "amphp/amp": "^2.4.2", - "amphp/byte-stream": "^1.5", + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/parallel": "^2.3", "composer-runtime-api": "^2", "composer/semver": "^1.4 || ^2.0 || ^3.0", "composer/xdebug-handler": "^2.0 || ^3.0", + "danog/advanced-json-rpc": "^3.1", "dnoegel/php-xdg-base-dir": "^0.1.1", "ext-ctype": "*", "ext-dom": "*", @@ -3598,27 +4579,26 @@ "ext-mbstring": "*", "ext-simplexml": "*", "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.1", - "felixfbecker/language-server-protocol": "^1.5.2", + "felixfbecker/language-server-protocol": "^1.5.3", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16", - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "netresearch/jsonmapper": "^5.0", + "nikic/php-parser": "^5.0.0", + "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": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" - }, - "conflict": { - "nikic/php-parser": "4.17.0" + "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": { "psalm/psalm": "self.version" }, "require-dev": { - "amphp/phpunit-util": "^2.0", + "amphp/phpunit-util": "^3", "bamarni/composer-bin-plugin": "^1.4", "brianium/paratest": "^6.9", + "danog/class-finder": "^0.4.8", + "dg/bypass-finals": "^1.5", "ext-curl": "*", "mockery/mockery": "^1.5", "nunomaduro/mock-final-classes": "^1.1", @@ -3626,10 +4606,10 @@ "phpstan/phpdoc-parser": "^1.6", "phpunit/phpunit": "^9.6", "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18", + "psalm/plugin-phpunit": "^0.19", "slevomat/coding-standard": "^8.4", "squizlabs/php_codesniffer": "^3.6", - "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" + "symfony/process": "^6.0 || ^7.0 || ^8.0" }, "suggest": { "ext-curl": "In order to send data to shepherd", @@ -3640,16 +4620,19 @@ "psalm-language-server", "psalm-plugin", "psalm-refactor", + "psalm-review", "psalter" ], "type": "project", "extra": { "branch-alias": { - "dev-master": "5.x-dev", - "dev-4.x": "4.x-dev", - "dev-3.x": "3.x-dev", + "dev-1.x": "1.x-dev", "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-5.x": "5.x-dev", + "dev-6.x": "6.x-dev", + "dev-master": "7.x-dev" } }, "autoload": { @@ -3664,6 +4647,10 @@ "authors": [ { "name": "Matthew Brown" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" } ], "description": "A static analysis tool for finding errors in PHP applications", @@ -3678,7 +4665,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2024-06-16T15:08:35+00:00" + "time": "2025-12-23T15:36:48+00:00" }, { "name": "wapmorgan/php-deprecation-detector", @@ -3714,18 +4701,18 @@ "type": "package", "extra": { "phar-builder": { - "compression": "BZip2", "name": "phpdd-dev.phar", - "output-dir": "./", - "entry-point": "bin/phpdd", + "events": { + "command.package.end": "cp phpdd-dev.phar phpdd-`cat bin/version.txt`.phar && chmod +x phpdd-`cat bin/version.txt`.phar && rm bin/version.txt", + "command.package.start": "git describe --tags > bin/version.txt" + }, "include": [ "bin", "data" ], - "events": { - "command.package.start": "git describe --tags > bin/version.txt", - "command.package.end": "cp phpdd-dev.phar phpdd-`cat bin/version.txt`.phar && chmod +x phpdd-`cat bin/version.txt`.phar && rm bin/version.txt" - } + "output-dir": "./", + "compression": "BZip2", + "entry-point": "bin/phpdd" } }, "autoload": { @@ -3749,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": { @@ -3791,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.", @@ -3801,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": [], @@ -3815,12 +4806,12 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "8.3.*", + "php": "8.4.*", "ext-json": "*", "ext-sodium": "*", "ext-curl": "*", "ext-apcu": "*" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/php/containers-schema.json b/php/containers-schema.json index db63fddf..5ed57e34 100644 --- a/php/containers-schema.json +++ b/php/containers-schema.json @@ -15,7 +15,7 @@ "image": { "type": "string", "minLength": 1, - "pattern": "^[a-z0-9/-]+$" + "pattern": "^(ghcr.io/)?[a-z0-9/-]+$" }, "expose": { "type": "array", @@ -47,7 +47,7 @@ }, "display_name": { "type": "string", - "pattern": "^[A-Za-z 0-9-]+$" + "pattern": "^[()A-Za-z 0-9-]+$" }, "environment": { "type": "array", @@ -68,6 +68,10 @@ "stop_grace_period": { "type": "integer" }, + "user": { + "type": "string", + "pattern": "^[0-9]{1,6}$" + }, "ports": { "type": "array", "items": { @@ -77,7 +81,7 @@ "properties": { "ip_binding": { "type": "string", - "pattern": "^(%[A-Z_]+%)?$" + "pattern": "^((%[A-Z_]+%)|127\\.0\\.0\\.1)?$" }, "port_number": { "type": "string", @@ -90,6 +94,36 @@ } } }, + "healthcheck": { + "type": "object", + "additionalProperties": false, + "minProperties": 6, + "properties": { + "interval": { + "type": "string", + "pattern": "^[0-9]+s$" + }, + "timeout": { + "type": "string", + "pattern": "^[0-9]+s$" + }, + "retries": { + "type": "integer" + }, + "start_period": { + "type": "string", + "pattern": "^[0-9]+s$" + }, + "start_interval": { + "type": "string", + "pattern": "^[0-9]+s$" + }, + "test": { + "type": "string", + "pattern": "^.*$" + } + } + }, "aio_variables": { "type": "array", "items": { @@ -111,6 +145,10 @@ "pattern": "^[A-Z_]+$" } }, + "ui_secret": { + "type": "string", + "pattern": "^[A-Z_]+$" + }, "image_tag": { "type": "string", "pattern": "^([a-z0-9.-]+|%AIO_CHANNEL%)$" @@ -126,6 +164,9 @@ "pattern": "^/dev/[a-z]+$" } }, + "enable_nvidia_gpu": { + "type": "boolean" + }, "apparmor_unconfined": { "type": "boolean" }, @@ -150,13 +191,6 @@ "pattern": "^[a-z-]+$" } }, - "networks": { - "type": "array", - "items": { - "type": "string", - "pattern": "^nextcloud-aio$" - } - }, "read_only": { "type": "boolean" }, @@ -183,7 +217,7 @@ }, "source": { "type": "string", - "pattern": "^((nextcloud_aio_[a-z_]+)|(%[A-Z_]+%))$" + "pattern": "^((nextcloud_aio_[a-z_]+)|(%[A-Z_]+%)|(/dev)|(/run/udev))$" }, "writeable": { "type": "boolean" diff --git a/php/containers.json b/php/containers.json index 4d447d83..8e9218ac 100644 --- a/php/containers.json +++ b/php/containers.json @@ -3,16 +3,27 @@ { "container_name": "nextcloud-aio-apache", "image_tag": "%AIO_CHANNEL%", + "documentation": "https://github.com/nextcloud/all-in-one/discussions/2105", "depends_on": [ "nextcloud-aio-onlyoffice", "nextcloud-aio-collabora", "nextcloud-aio-talk", - "nextcloud-aio-nextcloud", - "nextcloud-aio-notify-push" + "nextcloud-aio-notify-push", + "nextcloud-aio-whiteboard", + "nextcloud-aio-nextcloud" ], "display_name": "Apache", - "image": "nextcloud/aio-apache", + "image": "ghcr.io/nextcloud-releases/aio-apache", + "user": "33", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "ports": [ { "ip_binding": "%APACHE_IP_BINDING%", @@ -37,7 +48,8 @@ "TZ=%TIMEZONE%", "APACHE_MAX_SIZE=%APACHE_MAX_SIZE%", "APACHE_MAX_TIME=%NEXTCLOUD_MAX_TIME%", - "NOTIFY_PUSH_HOST=nextcloud-aio-notify-push" + "NOTIFY_PUSH_HOST=nextcloud-aio-notify-push", + "WHITEBOARD_HOST=nextcloud-aio-whiteboard" ], "volumes": [ { @@ -56,9 +68,6 @@ "nextcloud_aio_nextcloud", "nextcloud_aio_apache" ], - "networks": [ - "nextcloud-aio" - ], "read_only": true, "tmpfs": [ "/var/log/supervisord", @@ -75,8 +84,17 @@ "container_name": "nextcloud-aio-database", "image_tag": "%AIO_CHANNEL%", "display_name": "Database", - "image": "nextcloud/aio-postgresql", + "image": "ghcr.io/nextcloud-releases/aio-postgresql", + "user": "999", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "expose": [ "5432" ], @@ -110,9 +128,6 @@ "nextcloud_aio_database", "nextcloud_aio_database_dump" ], - "networks": [ - "nextcloud-aio" - ], "read_only": true, "tmpfs": [ "/var/run/postgresql" @@ -134,8 +149,16 @@ "nextcloud-aio-docker-socket-proxy" ], "display_name": "Nextcloud", - "image": "nextcloud/aio-nextcloud", + "image": "ghcr.io/nextcloud-releases/aio-nextcloud", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "expose": [ "9000", "9001" @@ -148,7 +171,8 @@ "TURN_SECRET", "SIGNALING_SECRET", "FULLTEXTSEARCH_PASSWORD", - "IMAGINARY_SECRET" + "IMAGINARY_SECRET", + "WHITEBOARD_SECRET" ], "volumes": [ { @@ -173,13 +197,17 @@ } ], "environment": [ + "NEXTCLOUD_HOST=nextcloud-aio-nextcloud", "POSTGRES_HOST=nextcloud-aio-database", "POSTGRES_PORT=5432", "POSTGRES_PASSWORD=%DATABASE_PASSWORD%", "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%", "AIO_TOKEN=%AIO_TOKEN%", "NC_DOMAIN=%NC_DOMAIN%", "ADMIN_USER=admin", @@ -191,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", @@ -202,13 +231,17 @@ "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", "PHP_MAX_TIME=%NEXTCLOUD_MAX_TIME%", "TRUSTED_CACERTS_DIR=%NEXTCLOUD_TRUSTED_CACERTS_DIR%", "STARTUP_APPS=%NEXTCLOUD_STARTUP_APPS%", @@ -221,22 +254,21 @@ "FULLTEXTSEARCH_PASSWORD=%FULLTEXTSEARCH_PASSWORD%", "DOCKER_SOCKET_PROXY_ENABLED=%DOCKER_SOCKET_PROXY_ENABLED%", "REMOVE_DISABLED_APPS=%REMOVE_DISABLED_APPS%", - "APACHE_PORT=%APACHE_PORT%", "ADDITIONAL_TRUSTED_PROXY=%CADDY_IP_ADDRESS%", "THIS_IS_AIO=true", - "IMAGINARY_SECRET=%IMAGINARY_SECRET%" + "IMAGINARY_SECRET=%IMAGINARY_SECRET%", + "WHITEBOARD_SECRET=%WHITEBOARD_SECRET%", + "WHITEBOARD_ENABLED=%WHITEBOARD_ENABLED%" ], "stop_grace_period": 600, "restart": "unless-stopped", "devices": [ "/dev/dri" ], + "enable_nvidia_gpu": true, "backup_volumes": [ "nextcloud_aio_nextcloud" ], - "networks": [ - "nextcloud-aio" - ], "cap_drop": [ "NET_RAW" ] @@ -245,8 +277,17 @@ "container_name": "nextcloud-aio-notify-push", "image_tag": "%AIO_CHANNEL%", "display_name": "Notify Push", - "image": "nextcloud/aio-notify-push", + "image": "ghcr.io/nextcloud-releases/aio-notify-push", + "user": "33", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "expose": [ "7867" ], @@ -265,7 +306,9 @@ "environment": [ "NC_DOMAIN=%NC_DOMAIN%", "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", @@ -274,9 +317,6 @@ "POSTGRES_USER=nextcloud" ], "restart": "unless-stopped", - "networks": [ - "nextcloud-aio" - ], "read_only": true, "cap_drop": [ "NET_RAW" @@ -286,8 +326,17 @@ "container_name": "nextcloud-aio-redis", "image_tag": "%AIO_CHANNEL%", "display_name": "Redis", - "image": "nextcloud/aio-redis", + "image": "ghcr.io/nextcloud-releases/aio-redis", + "user": "999", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "expose": [ "6379" ], @@ -309,9 +358,6 @@ "RECORDING_SECRET" ], "restart": "unless-stopped", - "networks": [ - "nextcloud-aio" - ], "read_only": true, "cap_drop": [ "NET_RAW" @@ -320,16 +366,25 @@ { "container_name": "nextcloud-aio-collabora", "image_tag": "%AIO_CHANNEL%", + "documentation": "https://github.com/nextcloud/all-in-one/discussions/1358", "display_name": "Collabora", - "image": "nextcloud/aio-collabora", + "image": "ghcr.io/nextcloud-releases/aio-collabora", "init": true, + "healthcheck": { + "start_period": "60s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 9 + }, "expose": [ "9980" ], "internal_port": "9980", "environment": [ - "aliasgroup1=https://%NC_DOMAIN%:443", - "extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:mount_jail_tree=false --o:logging.level=warning --o:home_mode.enable=true %COLLABORA_SECCOMP_POLICY% --o:remote_font_config.url=https://%NC_DOMAIN%/apps/richdocuments/settings/fonts.json", + "aliasgroup1=https://%NC_DOMAIN%:443,http://nextcloud-aio-apache:23973", + "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%", @@ -338,17 +393,17 @@ "restart": "unless-stopped", "nextcloud_exec_commands": [ "echo 'Activating Collabora config...'", - "php /var/www/html/occ richdocuments:activate-config" + "php /var/www/html/occ richdocuments:activate-config --wopi-url='http://nextcloud-aio-apache:23973' --callback-url='http://nextcloud-aio-apache:23973'" ], "profiles": [ "collabora" ], - "networks": [ - "nextcloud-aio" - ], "cap_add": [ "MKNOD", - "SYS_ADMIN" + "SYS_ADMIN", + "SYS_CHROOT", + "FOWNER", + "CHOWN" ], "cap_drop": [ "NET_RAW" @@ -357,9 +412,19 @@ { "container_name": "nextcloud-aio-talk", "image_tag": "%AIO_CHANNEL%", + "documentation": "https://github.com/nextcloud/all-in-one/discussions/1358", "display_name": "Talk", - "image": "nextcloud/aio-talk", + "image": "ghcr.io/nextcloud-releases/aio-talk", + "user": "1000", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "ports": [ { "ip_binding": "", @@ -395,9 +460,6 @@ "talk", "talk-recording" ], - "networks": [ - "nextcloud-aio" - ], "read_only": true, "tmpfs": [ "/var/log/supervisord", @@ -414,8 +476,17 @@ "container_name": "nextcloud-aio-talk-recording", "image_tag": "%AIO_CHANNEL%", "display_name": "Talk Recording", - "image": "nextcloud/aio-talk-recording", + "image": "ghcr.io/nextcloud-releases/aio-talk-recording", + "user": "122", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "expose": [ "1234" ], @@ -426,6 +497,13 @@ "RECORDING_SECRET=%RECORDING_SECRET%", "INTERNAL_SECRET=%TALK_INTERNAL_SECRET%" ], + "volumes": [ + { + "source": "nextcloud_aio_talk_recording", + "destination": "/tmp", + "writeable": true + } + ], "shm_size": 2147483648, "secrets": [ "RECORDING_SECRET", @@ -435,12 +513,12 @@ "profiles": [ "talk-recording" ], - "networks": [ - "nextcloud-aio" + "devices": [ + "/dev/dri" ], + "enable_nvidia_gpu": true, "read_only": true, "tmpfs": [ - "/tmp", "/conf" ], "cap_drop": [ @@ -450,12 +528,14 @@ { "container_name": "nextcloud-aio-borgbackup", "image_tag": "%AIO_CHANNEL%", - "image": "nextcloud/aio-borgbackup", + "image": "ghcr.io/nextcloud-releases/aio-borgbackup", "init": true, "environment": [ + "BORG_REMOTE_REPO=%BORGBACKUP_REMOTE_REPO%", "BORG_PASSWORD=%BORGBACKUP_PASSWORD%", "BORG_MODE=%BORGBACKUP_MODE%", "SELECTED_RESTORE_TIME=%SELECTED_RESTORE_TIME%", + "RESTORE_EXCLUDE_PREVIEWS=%RESTORE_EXCLUDE_PREVIEWS%", "BACKUP_RESTORE_PASSWORD=%BACKUP_RESTORE_PASSWORD%", "ADDITIONAL_DIRECTORIES_BACKUP=%ADDITIONAL_DIRECTORIES_BACKUP%", "BORGBACKUP_HOST_LOCATION=%BORGBACKUP_HOST_LOCATION%", @@ -516,7 +596,7 @@ { "container_name": "nextcloud-aio-watchtower", "image_tag": "%AIO_CHANNEL%", - "image": "nextcloud/aio-watchtower", + "image": "ghcr.io/nextcloud-releases/aio-watchtower", "init": true, "environment": [ "CONTAINER_TO_UPDATE=nextcloud-aio-mastercontainer" @@ -536,7 +616,7 @@ { "container_name": "nextcloud-aio-domaincheck", "image_tag": "%AIO_CHANNEL%", - "image": "nextcloud/aio-domaincheck", + "image": "ghcr.io/nextcloud-releases/aio-domaincheck", "init": true, "ports": [ { @@ -567,16 +647,24 @@ "container_name": "nextcloud-aio-clamav", "image_tag": "%AIO_CHANNEL%", "display_name": "ClamAV", - "image": "nextcloud/aio-clamav", + "image": "ghcr.io/nextcloud-releases/aio-clamav", + "user": "100", "init": false, + "healthcheck": { + "start_period": "60s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 9 + }, "expose": [ "3310" ], "internal_port": "3310", "environment": [ "TZ=%TIMEZONE%", - "MAX_SIZE=%NEXTCLOUD_UPLOAD_LIMIT%", - "CLAMD_STARTUP_TIMEOUT=90" + "MAX_SIZE=%NEXTCLOUD_UPLOAD_LIMIT%" ], "volumes": [ { @@ -589,14 +677,13 @@ "profiles": [ "clamav" ], - "networks": [ - "nextcloud-aio" - ], "read_only": true, "tmpfs": [ - "/var/lock", + "/tmp", "/var/log/clamav", - "/tmp" + "/run/clamav", + "/var/log/supervisord", + "/var/run/supervisord" ], "cap_drop": [ "NET_RAW" @@ -606,8 +693,16 @@ "container_name": "nextcloud-aio-onlyoffice", "image_tag": "%AIO_CHANNEL%", "display_name": "OnlyOffice", - "image": "nextcloud/aio-onlyoffice", + "image": "ghcr.io/nextcloud-releases/aio-onlyoffice", "init": true, + "healthcheck": { + "start_period": "60s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 9 + }, "expose": [ "80" ], @@ -629,16 +724,9 @@ "ONLYOFFICE_SECRET" ], "restart": "unless-stopped", - "nextcloud_exec_commands": [ - "echo 'Activating OnlyOffice config...'", - "php /var/www/html/occ onlyoffice:documentserver --check" - ], "profiles": [ "onlyoffice" ], - "networks": [ - "nextcloud-aio" - ], "cap_drop": [ "NET_RAW" ] @@ -647,8 +735,17 @@ "container_name": "nextcloud-aio-imaginary", "image_tag": "%AIO_CHANNEL%", "display_name": "Imaginary", - "image": "nextcloud/aio-imaginary", + "image": "ghcr.io/nextcloud-releases/aio-imaginary", + "user": "65534", "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, "expose": [ "9000" ], @@ -667,9 +764,6 @@ "profiles": [ "imaginary" ], - "networks": [ - "nextcloud-aio" - ], "read_only": true, "tmpfs": [ "/tmp" @@ -681,20 +775,29 @@ { "container_name": "nextcloud-aio-fulltextsearch", "image_tag": "%AIO_CHANNEL%", + "documentation": "https://github.com/nextcloud/all-in-one/discussions/1709", "display_name": "Fulltextsearch", - "image": "nextcloud/aio-fulltextsearch", + "image": "ghcr.io/nextcloud-releases/aio-fulltextsearch", "init": false, + "healthcheck": { + "start_period": "60s", + "test": "/healthcheck.sh", + "interval": "10s", + "timeout": "5s", + "start_interval": "5s", + "retries": 5 + }, "expose": [ "9200" ], "internal_port": "9200", "environment": [ "TZ=%TIMEZONE%", - "ES_JAVA_OPTS=-Xms512M -Xmx512M", - "bootstrap.memory_lock=true", + "ES_JAVA_OPTS=%FULLTEXTSEARCH_JAVA_OPTIONS%", + "bootstrap.memory_lock=false", "cluster.name=nextcloud-aio", "discovery.type=single-node", - "logger.org.elasticsearch.discovery=WARN", + "logger.level=WARN", "http.port=9200", "xpack.license.self_generated.type=basic", "xpack.security.enabled=false", @@ -711,9 +814,6 @@ "profiles": [ "fulltextsearch" ], - "networks": [ - "nextcloud-aio" - ], "secrets": [ "FULLTEXTSEARCH_PASSWORD" ], @@ -725,7 +825,7 @@ "container_name": "nextcloud-aio-docker-socket-proxy", "image_tag": "%AIO_CHANNEL%", "display_name": "Docker Socket Proxy", - "image": "nextcloud/aio-docker-socket-proxy", + "image": "ghcr.io/nextcloud-releases/aio-docker-socket-proxy", "init": true, "internal_port": "2375", "environment": [ @@ -746,6 +846,51 @@ "cap_drop": [ "NET_RAW" ] + }, + { + "container_name": "nextcloud-aio-whiteboard", + "image_tag": "%AIO_CHANNEL%", + "display_name": "Whiteboard", + "image": "ghcr.io/nextcloud-releases/aio-whiteboard", + "user": "65534", + "init": true, + "healthcheck": { + "start_period": "0s", + "test": "/healthcheck.sh", + "interval": "30s", + "timeout": "30s", + "start_interval": "5s", + "retries": 3 + }, + "expose": [ + "3002" + ], + "tmpfs": [ + "/tmp" + ], + "internal_port": "3002", + "environment": [ + "TZ=%TIMEZONE%", + "NEXTCLOUD_URL=https://%NC_DOMAIN%", + "JWT_SECRET_KEY=%WHITEBOARD_SECRET%", + "STORAGE_STRATEGY=redis", + "REDIS_HOST=nextcloud-aio-redis", + "REDIS_PORT=6379", + "REDIS_HOST_PASSWORD=%REDIS_PASSWORD%", + "BACKUP_DIR=/tmp" + ], + "secrets": [ + "WHITEBOARD_SECRET", + "REDIS_PASSWORD" + ], + "restart": "unless-stopped", + "profiles": [ + "whiteboard" + ], + "read_only": true, + "cap_drop": [ + "NET_RAW" + ] } ] } 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/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 7bf39a80..a9b7140d 100644 --- a/php/psalm-baseline.xml +++ b/php/psalm-baseline.xml @@ -1,2 +1,2 @@ - + diff --git a/php/psalm.xml b/php/psalm.xml index 59601afa..576d82d2 100644 --- a/php/psalm.xml +++ b/php/psalm.xml @@ -1,11 +1,9 @@ @@ -13,5 +11,19 @@ + + + + + + + + + + + + + + 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 new file mode 100644 index 00000000..1382bced --- /dev/null +++ b/php/public/containers-form-submit.js @@ -0,0 +1,140 @@ +document.addEventListener("DOMContentLoaded", function () { + // Hide submit button initially + 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'; + + // Store initial states for all checkboxes + const initialStateOptionsContainers = {}; + const initialStateCommunityContainers = {}; + 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 + }); + + communityContainersCheckboxes.forEach(checkbox => { + 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; + + optionsContainersCheckboxes.forEach(checkbox => { + if (checkbox.checked !== initialStateOptionsContainers[checkbox.id]) { + hasChanges = true; + } + }); + + // 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.forEach(element => { + element.style.display = hasChanges ? 'block' : 'none'; + }); + } + + // Function to compare current states to initial states + function checkForCommunityContainerChanges() { + let hasChanges = false; + + communityContainersCheckboxes.forEach(checkbox => { + if (checkbox.checked !== initialStateCommunityContainers[checkbox.id]) { + hasChanges = true; + } + }); + + // Show or hide submit button based on changes + communityFormSubmit.style.display = hasChanges ? 'block' : 'none'; + } + + // Event listener to trigger visibility check on each change + optionsContainersCheckboxes.forEach(checkbox => { + checkbox.addEventListener("change", checkForOptionContainerChanges); + }); + + communityContainersCheckboxes.forEach(checkbox => { + checkbox.addEventListener("change", checkForCommunityContainerChanges); + }); + + // Custom behaviors for specific options + function handleTalkVisibility() { + const talkRecording = document.getElementById("talk-recording"); + if (document.getElementById("talk").checked) { + talkRecording.disabled = false; + } else { + talkRecording.checked = false; + talkRecording.disabled = true; + } + checkForOptionContainerChanges(); // Check changes after toggling Talk Recording + } + + function handleDockerSocketProxyWarning() { + if (document.getElementById("docker-socket-proxy").checked) { + alert('⚠️ Warning! Enabling this container comes with possible Security problems since you are exposing the docker socket and all its privileges to the Nextcloud container. Enable this only if you are sure what you are doing!'); + } + } + + // Initialize event listeners for specific behaviors + document.getElementById("talk").addEventListener('change', handleTalkVisibility); + document.getElementById("docker-socket-proxy").addEventListener('change', handleDockerSocketProxyWarning); + + // 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/disable-whiteboard.js b/php/public/disable-whiteboard.js new file mode 100644 index 00000000..50e1215d --- /dev/null +++ b/php/public/disable-whiteboard.js @@ -0,0 +1,5 @@ +document.addEventListener("DOMContentLoaded", function(event) { + // Whiteboard + let whiteboard = document.getElementById("whiteboard"); + whiteboard.disabled = true; +}); diff --git a/php/public/img/background.png b/php/public/img/background.png deleted file mode 100644 index 1d4d5e8c..00000000 Binary files a/php/public/img/background.png and /dev/null differ diff --git a/php/public/img/jo-myoung-hee-fluid-dark.webp b/php/public/img/jo-myoung-hee-fluid-dark.webp new file mode 100644 index 00000000..314048f9 Binary files /dev/null and b/php/public/img/jo-myoung-hee-fluid-dark.webp differ diff --git a/php/public/img/jo-myoung-hee-fluid.webp b/php/public/img/jo-myoung-hee-fluid.webp new file mode 100644 index 00000000..1e9f4aad Binary files /dev/null and b/php/public/img/jo-myoung-hee-fluid.webp differ diff --git a/php/public/img/logo-blue.svg b/php/public/img/logo-blue.svg deleted file mode 100644 index cc0cdb65..00000000 --- a/php/public/img/logo-blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -image/svg+xml \ No newline at end of file diff --git a/php/public/img/logo.svg b/php/public/img/logo.svg deleted file mode 100644 index 0b2aef61..00000000 --- a/php/public/img/logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/php/public/img/nextcloud-logo.svg b/php/public/img/nextcloud-logo.svg new file mode 100644 index 00000000..94b07449 --- /dev/null +++ b/php/public/img/nextcloud-logo.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/php/public/index.php b/php/public/index.php index 1c997f3b..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,63 +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(), - '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' => $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_x64_platform' => $configurationManager->isx64Platform(), - '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(), + '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_talk_recording_enabled' => $configurationManager->isTalkRecordingEnabled(), - 'is_docker_socket_proxy_enabled' => $configurationManager->isDockerSocketProxyEnabled(), + '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->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) { @@ -165,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/options-form-submit.js b/php/public/options-form-submit.js deleted file mode 100644 index 1b70df98..00000000 --- a/php/public/options-form-submit.js +++ /dev/null @@ -1,69 +0,0 @@ -function makeOptionsFormSubmitVisible() { - let optionsFormSubmit = document.getElementById("options-form-submit"); - optionsFormSubmit.style.display = 'block'; -} - -function handleTalkVisibility() { - let talk = document.getElementById("talk"); - let talkRecording = document.getElementById("talk-recording") - if (talk.checked) { - talkRecording.disabled = false - } else { - talkRecording.checked = false - talkRecording.disabled = true - } -} - -function handleDockerSocketProxyWarning() { - let dockerSocketProxy = document.getElementById("docker-socket-proxy"); - if (dockerSocketProxy.checked) { - alert('⚠️ Warning! Enabling this container comes with possible Security problems since you are exposing the docker socket and all its privileges to the Nextcloud container. Enable this only if you are sure what you are doing!') - } -} - -document.addEventListener("DOMContentLoaded", function(event) { - // handle submit button for options form - let optionsFormSubmit = document.getElementById("options-form-submit"); - optionsFormSubmit.style.display = 'none'; - - // Clamav - let clamav = document.getElementById("clamav"); - clamav.addEventListener('change', makeOptionsFormSubmitVisible); - - // OnlyOffice - let onlyoffice = document.getElementById("onlyoffice"); - if (onlyoffice) { - onlyoffice.addEventListener('change', makeOptionsFormSubmitVisible); - } - - // Collabora - let collabora = document.getElementById("collabora"); - collabora.addEventListener('change', makeOptionsFormSubmitVisible); - - // Talk - let talk = document.getElementById("talk"); - talk.addEventListener('change', makeOptionsFormSubmitVisible); - talk.addEventListener('change', handleTalkVisibility); - - // Talk-recording - let talkRecording = document.getElementById("talk-recording"); - talkRecording.addEventListener('change', makeOptionsFormSubmitVisible); - if (!talk.checked) { - talkRecording.disabled = true - } - - // Imaginary - let imaginary = document.getElementById("imaginary"); - imaginary.addEventListener('change', makeOptionsFormSubmitVisible); - - // Fulltextsearch - let fulltextsearch = document.getElementById("fulltextsearch"); - fulltextsearch.addEventListener('change', makeOptionsFormSubmitVisible); - - // Docker socket proxy - let dockerSocketProxy = document.getElementById("docker-socket-proxy"); - if (dockerSocketProxy) { - dockerSocketProxy.addEventListener('change', makeOptionsFormSubmitVisible); - // dockerSocketProxy.addEventListener('change', handleDockerSocketProxyWarning); - } -}); diff --git a/php/public/style.css b/php/public/style.css index b9418737..b35883d0 100644 --- a/php/public/style.css +++ b/php/public/style.css @@ -1,38 +1,140 @@ +:root { + --color-nextcloud-blue: #0082c9; + --color-nextcloud-logo: var(--color-nextcloud-blue); + --color-main-background: white; + --color-input-background: white; + --color-main-text: black; + --color-main-border: black; + --color-main-border-hover: var(--color-main-border); + --color-error: #db0606; + --color-error-hover: #df2525; + --color-error-text: #c20505; + --color-success: #46ba61; + --color-running: #ffd000; + --color-primary-element: #00679e; + --color-primary-element-hover: #005a8a; + --color-primary-element-text: #ffffff; + --color-primary-element-light: #e5eff5; + --color-primary-element-light-hover: #dbe4ea; + --color-primary-element-light-text: #00293f; + --color-border-maxcontrast: #7d7d7d; + --color-loader: #f3f3f3; + --color-disabled: #d3d3d3; /* light gray background for disabled checkboxes */ + --color-border-disabled: #a9a9a9; /* darker gray border for disabled checkboxes */ + --color-text-disabled: #a9a9a9; /* matching label text color for disabled checkboxes */ + --border: .5px; + --border-hover: 2px; + --border-radius: 7px; + --border-radius-large: 12px; + --default-font-size: 13px; + --checkbox-size: 16px; + --max-width: 580px; + --container-top-margin: 20px; + --container-bottom-margin: 20px; + --container-padding: 2px; + --container-height-calculation-difference: calc(var(--container-top-margin) + var(--container-bottom-margin)); + --main-height-calculation-difference: calc(var(--container-height-calculation-difference) + calc(var(--container-padding) * 2)); + --main-padding: 50px; +} + +/* 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: 880px) { + :root { + --container-top-margin: 50px; + --container-bottom-margin: 0px; + } +} + +[data-theme="dark"] { + --color-main-background: #171717; + --color-input-background: #ebebeb; + --color-main-text: #ebebeb; + --color-nextcloud-logo: var(--color-main-text); + --color-main-border: var(--color-border-maxcontrast); + --color-main-border-hover: var(--color-main-text); + --color-error: #ff3333; + --color-error-hover: #ff6666; + --color-error-text: #ff8080; + --color-primary-element:#0091f2; + --color-primary-element-hover:#079cff; + --color-primary-element-text:#000000; + --color-primary-element-light:#14232c; + --color-primary-element-light-hover:#1e2d35; + --color-primary-element-light-text:#99d3f9; + --color-loader: var(--color-border-maxcontrast); + --border-hover: var(--border); +} + html, body { padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Cantarell, Ubuntu, Helvetica Neue, Arial, Noto Color Emoji, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;; + font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', 'Noto Sans', 'Liberation Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + background-color: var(--color-main-background); + color: var(--color-main-text); } a { text-decoration: none; - color: #0082c9; + color: var(--color-primary-element); } -.button { - padding: 6px 16px; +a:hover { + color: var(--color-primary-element-hover); +} + +a.button, +input[type="submit"] { + padding: 8px 16px; width: auto; - min-height: 34px; + height: 34px; cursor: pointer; - background-color:#0082c9; + background-color: var(--color-primary-element); font-weight: bold; - border-radius: 100px; + border-radius: var(--border-radius); margin: 3px 3px 3px 0; - font-size: 13px; - color: white; - border: 1px solid black; + font-size: var(--default-font-size); + color: var(--color-primary-element-text); + border: none; outline: none; } -.button:focus { - color:black; - border: 2px solid black; +a.button:focus, +input[type="submit"]:focus { + outline: 2px solid var(--color-main-border); } -#logout { - margin-top: 7px; +a.button:hover, +input[type="submit"]:hover { + background-color: var(--color-primary-element-hover); } + +a.button.light:hover, +input[type="submit"].light:hover { + background-color: var(--color-primary-element-light); + color: var(--color-primary-element-light-text); +} + + +a.button.light, +input[type="submit"].light { + background-color: var(--color-primary-element-light); +} + +a.button.error, +input[type="submit"].error { + background-color: var(--color-error); +} + +a.button.error:hover, +input[type="submit"].error:hover { + background-color: var(--color-error-hover); +} + + + + summary { cursor: pointer; } @@ -44,38 +146,36 @@ ul { li { padding-bottom: 5px; + text-indent: 0; + padding-left: 0; } span.error { - background-color: #e9322d; + background-color: var(--color-error); } div.toast.error { - border-left-color: #e9322d; + border-left-color: var(--color-error); } .status { display: inline-block; - height: 16px; - width: 16px; - vertical-align: text-bottom -} - -.status { + height: var(--checkbox-size); + width: var(--checkbox-size); + vertical-align: text-bottom; border-radius: 50% } - span.success { - background-color: #46ba61; + background-color: var(--color-success); } span.running { - background-color: rgb(255, 208, 0); + background-color: var(--color-running); } div.toast.success { - border-left-color: #46ba61; + border-left-color: var(--color-success); } div.toast { @@ -87,33 +187,46 @@ div.toast { margin-top: 45px; position: fixed; z-index: 1000; - border-radius: 3px; - background: none; - background-color: white; + border-radius: var(--border-radius); + background: var(--color-main-background) none; + color: var(--color-main-text); +} + +.nextcloud-logo { + margin-left: auto; + margin-right: auto; + display: block; + color: var(--color-nextcloud-logo); +} + +.fallback-text { + display: none; +} + +svg:not(:has(use)) .fallback-text { + display: block; } .login { padding: 50px; - background-color: white; + background-color: var(--color-main-background); + color: var(--color-main-text); width: 500px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); - border-radius: 16px; + border-radius: var(--border-radius-large); } .login > .monospace { - font-family: monospace; + font-family: monospace, monospace, system-ui, -apple-system, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', 'Noto Sans', 'Liberation Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 17px; } -input { - padding: 10px; - margin-bottom: 15px; -} - -.login > form > input { +.login > form > input[type="password"], +.login > form > input[type="text"], +.login > form > input[type="submit"] { width: 100%; } @@ -123,44 +236,125 @@ input { display: block; } -.login > .button { +.login a.button, +.login input[type="submit"] { margin-left: auto; margin-right: auto; display: block; text-align: center; - line-height: 33px; - margin-top: 20px; + padding: 0px; + align-content: center; } -.login-wrapper { - height: 100%; - width: 100%; - background-color: #0082c9; - background-image: linear-gradient( - 40deg - , #0082c9 0%, #30b6ff 100%); - background-size: contain; - background-image: url('/img/background.png'), linear-gradient( - 40deg - , #0082c9 0%, #30b6ff 100%); - position: relative; +.wrapper { + min-height: 100dvh; + min-width: 100vw; + position: fixed; + width: 100vw; + background-image: url("img/jo-myoung-hee-fluid.webp"); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + box-sizing: border-box; + overflow: hidden; } -.content { - padding: 20px; +html[data-theme="dark"] .wrapper { + background-image: url("img/jo-myoung-hee-fluid-dark.webp"); +} + +form { + margin: 0; +} + +input[type="text"], +input[type="password"], +select { + padding-left: 8px; + padding-right: 8px; + height: 34px; + margin-bottom: 15px; + border-radius: var(--border-radius); + border: var(--border) solid var(--color-border-maxcontrast); + background: var(--color-main-background); + color: var(--color-main-text); +} + +input[type="text"]:hover, +input[type="password"]:hover, +select:hover { + border: var(--border-hover) solid var(--color-main-border-hover); +} + +textarea { + border-radius: var(--border-radius); + border: .5px solid var(--color-main-border); max-width: 100%; +} + +input[type="text"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus { + border: 1px solid var(--color-main-border); +} + +/* Scroll bar for dark mode */ +html[data-theme="dark"] ::-webkit-scrollbar { + width: 8px; /* Width of the scroll bar */ +} + +html[data-theme="dark"] ::-webkit-scrollbar-thumb { + background-color: #444; /* Dark mode scrollbar thumb color */ + border-radius: 4px; /* Rounded corners for the thumb */ +} + +html[data-theme="dark"] ::-webkit-scrollbar-track { + background-color: #333; /* Dark mode scrollbar track color */ +} + +/* Scroll bar for light mode */ +::-webkit-scrollbar { + width: 8px; /* Width of the scroll bar */ +} + +::-webkit-scrollbar-thumb { + background-color: #888; /* Light mode scrollbar thumb color */ + border-radius: 4px; /* Rounded corners for the thumb */ +} + +::-webkit-scrollbar-track { + background-color: #f0f0f0; /* Light mode scrollbar track color */ +} + +.container { + margin: var(--container-top-margin) auto var(--container-bottom-margin) auto; + padding: var(--container-padding); + max-width: calc(var(--max-width) + calc(var(--main-padding) * 2) + 8px); + background-color: var(--color-main-background); + border-radius: var(--border-radius-large); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + max-height: calc(100dvh - var(--container-height-calculation-difference)); + overflow: hidden; +} + +main { + padding-left: var(--main-padding); + padding-right: var(--main-padding); + background-color: transparent; /* transparent, since color comes from outer container */ + color: var(--color-main-text); + max-height: calc(100dvh - var(--main-height-calculation-difference)); + overflow-y: auto; + box-sizing: border-box; word-break: break-word; - max-width: 500px; + max-width: calc(var(--max-width) + calc(var(--main-padding) * 2)); margin: 0 auto; + padding-bottom: var(--main-padding); } .logo { - background-image: url('/img/logo.svg'); + color: white; height: 50px; - background-repeat: no-repeat; - display: inline-flex; - background-size: contain; - background-position: center center; width: 62px; position: absolute; left: 12px; @@ -169,11 +363,93 @@ input { } header { - background-color: #0082c9; - background-image: linear-gradient(40deg, #0082c9 0%, #30b6ff 100%); + position: fixed; + top: 0; + width: 100%; + background-color: transparent; height: 50px; justify-content: space-between; + align-items: center; display: flex; + padding: 0 20px; + z-index: 1000; +} + +header > form { + margin-left: auto; + margin-right: 30px; +} + +/* Standard styling for enabled checkboxes */ +input[type="checkbox"]:not(:disabled) { + width: var(--checkbox-size); + height: var(--checkbox-size); + -webkit-appearance: none; /* remove default styling */ + -moz-appearance: none; + appearance: none; + border: 1px solid var(--color-primary-element); + border-radius: 2px; + cursor: pointer; + position: relative; + vertical-align: middle; /* align checkbox vertically with text */ + margin-top: -1px; /* adjust for better alignment */ +} + +/* Hover effects for enabled checkboxes */ +input[type="checkbox"]:not(:disabled):hover { + border-color: var(--color-primary-element-hover); +} + +/* Checkmark styling for enabled checkboxes */ +input[type="checkbox"]:checked:not(:disabled) { + background-color: var(--color-primary-element); + border-color: var(--color-border-maxcontrast); +} + +input[type="checkbox"]:checked:not(:disabled)::after { + content: ''; /* Creates a pseudo-element for the checkmark */ + position: absolute; /* Positions it absolutely */ + left: 4px; /* Positioning of the checkmark */ + top: 0; /* Positioning of the checkmark */ + width: 4px; /* Width of the checkmark */ + height: 9px; /* Height of the checkmark */ + border: solid white; /* Color of the checkmark */ + border-width: 0 2px 3px 0; /* Creates the checkmark shape */ + transform: rotate(45deg); /* Rotates to form a checkmark */ +} + +/* Styling for disabled checkboxes (grayed out, no hover, no pointer) */ +input[type="checkbox"]:disabled:not(:checked) { + background-color: var(--color-disabled); + border-color: var(--color-border-disabled); + cursor: default; + opacity: 0.5; /* Makes the checkbox appear faded */ +} + +/* Styling for disabled checked checkboxes (no pointer) */ +input[type="checkbox"]:disabled:checked { + cursor: default; +} + +input[type="checkbox"]:disabled:hover { + border-color: var(--color-border-disabled); /* Keeps disabled state without hover effect */ +} + +/* General Label styling */ +label { + cursor: pointer; + margin-left: 4px; + line-height: var(--checkbox-size); +} + +/* Label cursor for disabled checkboxes */ +input[type="checkbox"]:disabled + label { + cursor: default; +} + +/* Label styling for disabled, not checked checkboxes */ +input[type="checkbox"]:disabled:not(:checked) + label { + color: var(--color-text-disabled); } .loading { @@ -181,24 +457,24 @@ header { } #overlay { - position: fixed; /* Sit on top of the page content */ - display: none; /* Hidden by default */ - width: 100%; /* Full width (cover the whole page) */ - height: 100%; /* Full height (cover the whole page) */ - top: 0; - left: 0; - background-color: rgba(0,0,0,0.5); /* Black background with opacity */ - z-index: 2; + position: fixed; /* Sit on top of the page content */ + display: none; /* Hidden by default */ + width: 100%; /* Full width (cover the whole page) */ + height: 100%; /* Full height (cover the whole page) */ + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); /* Black background with opacity */ + z-index: 2; } #overlay.loading { - display: block; + display: block; } .loader { - border: 16px solid #f3f3f3; + border: 16px solid var(--color-loader); border-radius: 50%; - border-top: 16px solid #0082c9; + border-top: 16px solid var(--color-nextcloud-blue); width: 120px; height: 120px; -webkit-animation: spin 2s linear infinite; /* Safari */ @@ -206,15 +482,227 @@ header { position: absolute; top: calc(50% - 60px); left: calc(50% - 60px); - } - - /* Safari */ - @-webkit-keyframes spin { +} + +/* Safari */ +@-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } - } - - @keyframes spin { +} + +@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } - } +} + +/* General theme button styling */ +#theme-toggle { + position: fixed; /* Keep the button in the same position */ + right: 30px; /* Adjust the distance from the right */ + bottom: 30px; /* Adjust the distance from the bottom */ + background-color: transparent; /* Make the background transparent */ + border: none; /* Remove border */ + font-size: 36px; /* Adjust font size */ + cursor: pointer; /* Change cursor to pointer */ + outline: none; + z-index: 9999; /* Ensures the icon is on top of every layer */ +} + +/* Icon styling: default state */ +#theme-icon { + display: inline-block; + border-radius: 50%; /* Round shape */ + position: relative; /* For the pseudo-element positioning */ + transition: box-shadow 0.3s, background-color 0.3s; /* Smooth transition for hover effect */ + opacity: 0.6; /* Slightly transparent by default */ + filter: grayscale(100%); /* Make the icon black and white */ +} + +/* Create the inner glow effect with ::after */ +#theme-icon::after { + content: ''; /* Empty content for the pseudo-element */ + position: absolute; + top: 50%; + left: 50%; + width: 0px; /* Invisible dot */ + height: 0px; /* Invisible dot */ + background-color: transparent; /* Invisible by default */ + border-radius: 50%; /* Circle shape */ + transform: translate(-50%, -50%); /* Center the dot */ + transition: box-shadow 0.3s, background-color 0.3s; /* Smooth transition for hover */ +} + +/* Hover effect for both light and dark modes */ +#theme-toggle:hover #theme-icon { + position: relative; /* Ensures stacking order */ + filter: grayscale(0%); /* Restore full color */ + opacity: 1; /* Fully visible on hover */ +} + +/* Inner glow when hovered */ +#theme-toggle:hover #theme-icon::after { + box-shadow: 0 0 40px 40px rgba(128, 128, 128, 0.4); /* Blur effect from inside */ + background-color: rgba(128, 128, 128, 0.2); /* Light glow inside */ +} + +/* Remove hover effects when not hovering */ +#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/public/toggle-dark-mode.js b/php/public/toggle-dark-mode.js new file mode 100644 index 00000000..9df54287 --- /dev/null +++ b/php/public/toggle-dark-mode.js @@ -0,0 +1,37 @@ +// Function to toggle theme +function toggleTheme() { + const currentTheme = document.documentElement.getAttribute('data-theme'); + const newTheme = (currentTheme === 'dark') ? '' : 'dark'; // Toggle between no theme and dark theme + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + + // Change the icon based on the current theme + const themeIcon = document.getElementById('theme-icon'); + themeIcon.textContent = newTheme === 'dark' ? '☀️' : '🌙'; // Switch between moon and sun icons +} + +// Function to immediately apply saved theme without icon update +function applySavedThemeImmediately() { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme === 'dark') { + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + document.documentElement.removeAttribute('data-theme'); // Default to light theme + } +} + +// Function to apply theme-icon update +function setThemeIcon() { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme === 'dark') { + document.getElementById('theme-icon').textContent = '☀️'; // Sun icon for dark mode + } else { + document.getElementById('theme-icon').textContent = '🌙'; // Moon icon for light mode + } +} + +// Immediately apply the saved theme to avoid flickering +applySavedThemeImmediately(); + +// Apply theme when the page loads +document.addEventListener('DOMContentLoaded', setThemeIcon); diff --git a/php/src/Auth/AuthManager.php b/php/src/Auth/AuthManager.php index a4bc96b6..f6ab0d10 100644 --- a/php/src/Auth/AuthManager.php +++ b/php/src/Auth/AuthManager.php @@ -6,20 +6,20 @@ use AIO\Data\ConfigurationManager; use AIO\Data\DataConst; use \DateTime; -class AuthManager { +readonly class AuthManager { private const string SESSION_KEY = 'aio_authenticated'; - private ConfigurationManager $configurationManager; - public function __construct(ConfigurationManager $configurationManager) { - $this->configurationManager = $configurationManager; + public function __construct( + private ConfigurationManager $configurationManager + ) { } 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 9d598b4a..6e5d2b54 100644 --- a/php/src/Container/Container.php +++ b/php/src/Container/Container.php @@ -2,202 +2,68 @@ namespace AIO\Container; -use AIO\Container\State\IContainerState; use AIO\Data\ConfigurationManager; use AIO\Docker\DockerActionManager; use AIO\ContainerDefinitionFetcher; +use JsonException; -class Container { - 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; - /** @var string[] */ - private array $dependsOn; - /** @var string[] */ - private array $secrets; - /** @var string[] */ - private array $devices; - /** @var string[] */ - private array $capAdd; - private int $shmSize; - private 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; - private DockerActionManager $dockerActionManager; - +readonly class Container { public function __construct( - string $identifier, - string $displayName, - string $containerName, - string $restartPolicy, - int $maxShutdownTime, - ContainerPorts $ports, - string $internalPorts, - ContainerVolumes $volumes, - ContainerEnvironmentVariables $containerEnvironmentVariables, - array $dependsOn, - array $secrets, - array $devices, - array $capAdd, - int $shmSize, - bool $apparmorUnconfined, - array $backupVolumes, - array $nextcloudExecCommands, - bool $readOnlyRootFs, - array $tmpfs, - bool $init, - string $imageTag, - AioVariables $aioVariables, - string $documentation, - DockerActionManager $dockerActionManager + 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[] */ + public array $dependsOn, + private string $uiSecret, + /** @var string[] */ + public array $devices, + public bool $enableNvidiaGpu, + /** @var string[] */ + public array $capAdd, + public int $shmSize, + public bool $apparmorUnconfined, + /** @var string[] */ + 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 ) { - $this->identifier = $identifier; - $this->displayName = $displayName; - $this->containerName = $containerName; - $this->restartPolicy = $restartPolicy; - $this->maxShutdownTime = $maxShutdownTime; - $this->ports = $ports; - $this->internalPorts = $internalPorts; - $this->volumes = $volumes; - $this->containerEnvironmentVariables = $containerEnvironmentVariables; - $this->dependsOn = $dependsOn; - $this->secrets = $secrets; - $this->devices = $devices; - $this->capAdd = $capAdd; - $this->shmSize = $shmSize; - $this->apparmorUnconfined = $apparmorUnconfined; - $this->backupVolumes = $backupVolumes; - $this->nextcloudExecCommands = $nextcloudExecCommands; - $this->readOnlyRootFs = $readOnlyRootFs; - $this->tmpfs = $tmpfs; - $this->init = $init; - $this->imageTag = $imageTag; - $this->aioVariables = $aioVariables; - $this->documentation = $documentation; - $this->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 GetSecrets() : array { - return $this->secrets; - } - - public function GetTmpfs() : array { - return $this->tmpfs; - } - - public function GetDevices() : array { - return $this->devices; - } - - 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; - } - - public function GetRunningState() : IContainerState { - return $this->dockerActionManager->GetContainerRunningState($this); - } - - public function GetRestartingState() : IContainerState { - return $this->dockerActionManager->GetContainerRestartingState($this); - } - - public function GetUpdateState() : IContainerState { - return $this->dockerActionManager->GetContainerUpdateState($this); - } - - public function GetStartingState() : IContainerState { - return $this->dockerActionManager->GetContainerStartingState($this); + public function GetUiSecret() : string { + return $this->dockerActionManager->GetAndGenerateSecretWrapper($this->uiSecret); } /** - * @return string[] + * @throws JsonException */ - public function GetDependsOn() : array { - return $this->dependsOn; + public function GetRunningState() : ContainerState { + return $this->dockerActionManager->GetContainerRunningState($this); } - public function GetNextcloudExecCommands() : array { - return $this->nextcloudExecCommands; + /** + * @throws JsonException + */ + public function GetRestartingState() : ContainerState { + return $this->dockerActionManager->GetContainerRestartingState($this); } - public function GetEnvironmentVariables() : ContainerEnvironmentVariables { - return $this->containerEnvironmentVariables; + public function GetUpdateState() : VersionState { + return $this->dockerActionManager->GetContainerUpdateState($this); } - public function GetAioVariables() : AioVariables { - return $this->aioVariables; - } - - public function GetDocumentation() : string { - return $this->documentation; + public function GetStartingState() : ContainerState { + return $this->dockerActionManager->GetContainerStartingState($this); } } diff --git a/php/src/Container/ContainerPort.php b/php/src/Container/ContainerPort.php index 3ecf56cd..ff8a6250 100644 --- a/php/src/Container/ContainerPort.php +++ b/php/src/Container/ContainerPort.php @@ -3,17 +3,10 @@ namespace AIO\Container; class ContainerPort { - public string $port; - public string $ipBinding; - public string $protocol; - public function __construct( - string $port, - string $ipBinding, - string $protocol + public string $port, + public string $ipBinding, + public string $protocol ) { - $this->port = $port; - $this->ipBinding = $ipBinding; - $this->protocol = $protocol; } } diff --git a/php/src/Container/ContainerState.php b/php/src/Container/ContainerState.php new file mode 100644 index 00000000..f6481027 --- /dev/null +++ b/php/src/Container/ContainerState.php @@ -0,0 +1,12 @@ +name = $name; - $this->mountPoint = $mountPoint; - $this->isWritable = $isWritable; } } diff --git a/php/src/Container/State/IContainerState.php b/php/src/Container/State/IContainerState.php deleted file mode 100644 index d93dab65..00000000 --- a/php/src/Container/State/IContainerState.php +++ /dev/null @@ -1,5 +0,0 @@ -configurationManager = $configurationManager; - $this->container = $container; + private ConfigurationManager $configurationManager, + private \DI\Container $container + ) { } public function GetContainerById(string $id): Container @@ -33,7 +25,7 @@ class ContainerDefinitionFetcher $containers = $this->FetchDefinition(); foreach ($containers as $container) { - if ($container->GetIdentifier() === $id) { + if ($container->identifier === $id) { return $container; } } @@ -46,13 +38,13 @@ 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 @@ -64,42 +56,49 @@ 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) { continue; } } $ports = new ContainerPorts(); if (isset($entry['ports'])) { - foreach ($entry['ports'] as $value) { + foreach ($entry['ports'] as $value) { $ports->AddPort( new ContainerPort( $value['port_number'], @@ -114,34 +113,34 @@ 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; } @@ -169,42 +168,46 @@ 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) { continue; } } $dependsOn[] = $value; } } - + $variables = new ContainerEnvironmentVariables(); if (isset($entry['environment'])) { foreach ($entry['environment'] as $value) { @@ -239,9 +242,17 @@ class ContainerDefinitionFetcher $internalPort = $entry['internal_port']; } - $secrets = []; if (isset($entry['secrets'])) { - $secrets = $entry['secrets']; + // 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); + } + } + + $uiSecret = ''; + if (isset($entry['ui_secret'])) { + $uiSecret = $entry['ui_secret']; } $devices = []; @@ -249,6 +260,11 @@ class ContainerDefinitionFetcher $devices = $entry['devices']; } + $enableNvidiaGpu = false; + if (isset($entry['enable_nvidia_gpu'])) { + $enableNvidiaGpu = $entry['enable_nvidia_gpu']; + } + $capAdd = []; if (isset($entry['cap_add'])) { $capAdd = $entry['cap_add']; @@ -310,8 +326,9 @@ class ContainerDefinitionFetcher $volumes, $variables, $dependsOn, - $secrets, + $uiSecret, $devices, + $enableNvidiaGpu, $capAdd, $shmSize, $apparmorUnconfined, diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php index e786697a..c40ee98c 100644 --- a/php/src/Controller/ConfigurationController.php +++ b/php/src/Controller/ConfigurationController.php @@ -9,38 +9,38 @@ use AIO\Docker\DockerActionManager; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -class ConfigurationController -{ - private ConfigurationManager $configurationManager; - +readonly class ConfigurationController { public function __construct( - ConfigurationManager $configurationManager + private ConfigurationManager $configurationManager ) { - $this->configurationManager = $configurationManager; } - public function SetConfig(Request $request, Response $response, array $args) : Response { + 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'])) { + if (isset($request->getParsedBody()['borg_backup_host_location']) || isset($request->getParsedBody()['borg_remote_repo'])) { $location = $request->getParsedBody()['borg_backup_host_location'] ?? ''; - $this->configurationManager->SetBorgBackupHostLocation($location); + $borgRemoteRepo = $request->getParsedBody()['borg_remote_repo'] ?? ''; + $this->configurationManager->setBorgLocationVars($location, $borgRemoteRepo); } - if (isset($request->getParsedBody()['borg_restore_host_location']) || isset($request->getParsedBody()['borg_restore_password'])) { + 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->SetBorgRestoreHostLocationAndPassword($restoreLocation, $borgPassword); + $this->configurationManager->setBorgRestoreLocationVarsAndPassword($restoreLocation, $borgRemoteRepo, $borgPassword); } if (isset($request->getParsedBody()['daily_backup_time'])) { @@ -55,90 +55,91 @@ 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); + $this->configurationManager->isCollaboraEnabled = false; + $this->configurationManager->isOnlyofficeEnabled = false; } - 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); + $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'])) { + $cc = $this->configurationManager->listAvailableCommunityContainers(); + $enabledCC = []; + /** + * @psalm-suppress PossiblyNullIterator + */ + foreach ($request->getParsedBody() as $item) { + if (array_key_exists($item , $cc)) { + $enabledCC[] = $item; + } } + $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_borg_backup_host_location'])) { - $this->configurationManager->DeleteBorgBackupHostLocation(); + if (isset($request->getParsedBody()['delete_collabora_additional_options'])) { + $this->configurationManager->deleteAdditionalCollaboraOptions(); } - return $response->withStatus(201)->withHeader('Location', '/'); + if (isset($request->getParsedBody()['collabora_additional_options'])) { + $additionalCollaboraOptions = $request->getParsedBody()['collabora_additional_options'] ?? ''; + $this->configurationManager->collaboraAdditionalOptions = $additionalCollaboraOptions; + } + + if (isset($request->getParsedBody()['delete_borg_backup_location_vars'])) { + $this->configurationManager->deleteBorgBackupLocationItems(); + } + + 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 6d55bc94..81b920d0 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -2,74 +2,71 @@ namespace AIO\Controller; -use AIO\Container\State\RunningState; +use AIO\Container\ContainerState; use AIO\ContainerDefinitionFetcher; use AIO\Docker\DockerActionManager; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use AIO\Data\ConfigurationManager; -class DockerController -{ - private DockerActionManager $dockerActionManager; - private ContainerDefinitionFetcher $containerDefinitionFetcher; +readonly class DockerController { private const string TOP_CONTAINER = 'nextcloud-aio-apache'; - private ConfigurationManager $configurationManager; public function __construct( - DockerActionManager $dockerActionManager, - ContainerDefinitionFetcher $containerDefinitionFetcher, - ConfigurationManager $configurationManager + private DockerActionManager $dockerActionManager, + private ContainerDefinitionFetcher $containerDefinitionFetcher, + private ConfigurationManager $configurationManager ) { - $this->dockerActionManager = $dockerActionManager; - $this->containerDefinitionFetcher = $containerDefinitionFetcher; - $this->configurationManager = $configurationManager; } private function PerformRecursiveContainerStart(string $id, bool $pullImage = true) : void { $container = $this->containerDefinitionFetcher->GetContainerById($id); - foreach($container->GetDependsOn() as $dependency) { + // Start all dependencies first and then itself + foreach($container->dependsOn as $dependency) { $this->PerformRecursiveContainerStart($dependency, $pullImage); } // Don't start if container is already running // This is expected to happen if a container is defined in depends_on of multiple containers - if ($container->GetRunningState() instanceof RunningState) { + if ($container->GetRunningState() === ContainerState::Running) { error_log('Not starting ' . $id . ' because it was already started.'); return; } - // Skip database image pull if the last shutdown was not clean - if ($id === 'nextcloud-aio-database') { - if ($this->dockerActionManager->GetDatabasecontainerExitCode() > 0) { - $pullImage = false; - error_log('Not pulling the latest database image because the container was not correctly shut down.'); - } - } - - // Check if docker hub is reachable in order to make sure that we do not try to pull an image if it is down - // and try to mitigate issues that are arising due to that - if ($pullImage) { - if (!$this->dockerActionManager->isDockerHubReachable($container)) { - $pullImage = false; - error_log('Not pulling the image for the ' . $container->GetContainerName() . ' container because docker hub does not seem to be reachable.'); - } - } - $this->dockerActionManager->DeleteContainer($container); $this->dockerActionManager->CreateVolumes($container); - if ($pullImage) { - $this->dockerActionManager->PullImage($container); - } + $this->dockerActionManager->PullImage($container, $pullImage); $this->dockerActionManager->CreateContainer($container); $this->dockerActionManager->StartContainer($container); $this->dockerActionManager->ConnectContainerToNetwork($container); } + private function PerformRecursiveImagePull(string $id) : void { + $container = $this->containerDefinitionFetcher->GetContainerById($id); + + // Pull all dependencies first and then itself + foreach($container->dependsOn as $dependency) { + $this->PerformRecursiveImagePull($dependency); + } + + $this->dockerActionManager->PullImage($container, true); + } + + public function PullAllContainerImages(): void { + + $id = self::TOP_CONTAINER; + + $this->PerformRecursiveImagePull($id); + } + public function GetLogs(Request $request, Response $response, array $args) : Response { - $id = $request->getQueryParams()['id']; + $requestParams = $request->getQueryParams(); + $id = ''; + if (isset($requestParams['id']) && is_string($requestParams['id'])) { + $id = $requestParams['id']; + } if (str_starts_with($id, 'nextcloud-aio-')) { $logs = $this->dockerActionManager->GetLogs($id); } else { @@ -86,17 +83,16 @@ class DockerController } public function StartBackupContainerBackup(Request $request, Response $response, array $args) : Response { - $this->startBackup(); - return $response->withStatus(201)->withHeader('Location', '/'); + $forceStopNextcloud = true; + $this->startBackup($forceStopNextcloud); + return $response->withStatus(201)->withHeader('Location', '.'); } - public function startBackup() : void { - $config = $this->configurationManager->GetConfig(); - $config['backup-mode'] = 'backup'; - $this->configurationManager->WriteConfig($config); + public function startBackup(bool $forceStopNextcloud = false) : void { + $this->configurationManager->backupMode = 'backup'; $id = self::TOP_CONTAINER; - $this->PerformRecursiveContainerStop($id); + $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id); @@ -104,54 +100,62 @@ 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'] ?? ''; - $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; - $this->PerformRecursiveContainerStop($id); + $forceStopNextcloud = true; + $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); $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); @@ -159,7 +163,7 @@ 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 @@ -167,41 +171,44 @@ 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 = 29; + $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']); + if ($pullImage === false) { + error_log('WARNING: Not pulling container images. Instead, using local ones.'); + } // Start container - $this->startTopContainer(true); + $this->startTopContainer($pullImage); // Clear apcu cache in order to check if container updates are available // 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(); @@ -213,7 +220,7 @@ 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 { @@ -222,24 +229,36 @@ class DockerController $this->PerformRecursiveContainerStart($id); } - private function PerformRecursiveContainerStop(string $id) : void + private function PerformRecursiveContainerStop(string $id, bool $forceStopNextcloud = false) : void { $container = $this->containerDefinitionFetcher->GetContainerById($id); - foreach($container->GetDependsOn() as $dependency) { - $this->PerformRecursiveContainerStop($dependency); + + // 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) { + $this->PerformRecursiveContainerStop('nextcloud-aio-collabora'); } - // Disconnecting is not needed. This also allows to start the containers manually via docker-cli - //$this->dockerActionManager->DisconnectContainerFromNetwork($container); - $this->dockerActionManager->StopContainer($container); + // Stop itself first and then all the dependencies + if ($id !== 'nextcloud-aio-nextcloud') { + $this->dockerActionManager->StopContainer($container); + } else { + // 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->dependsOn as $dependency) { + $this->PerformRecursiveContainerStop($dependency, $forceStopNextcloud); + } } public function StopContainer(Request $request, Response $response, array $args) : Response { $id = self::TOP_CONTAINER; - $this->PerformRecursiveContainerStop($id); + $forceStopNextcloud = true; + $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); - return $response->withStatus(201)->withHeader('Location', '/'); + return $response->withStatus(201)->withHeader('Location', '.'); } public function stopTopContainer() : void { @@ -250,7 +269,7 @@ 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; } @@ -261,10 +280,10 @@ class DockerController $domaincheckContainer = $this->containerDefinitionFetcher->GetContainerById($id); $apacheContainer = $this->containerDefinitionFetcher->GetContainerById(self::TOP_CONTAINER); // Don't start if apache is already running - if ($apacheContainer->GetRunningState() instanceof RunningState) { + if ($apacheContainer->GetRunningState() === ContainerState::Running) { return; // Don't start if domaincheck is already running - } elseif ($domaincheckContainer->GetRunningState() instanceof RunningState) { + } elseif ($domaincheckContainer->GetRunningState() === ContainerState::Running) { $domaincheckWasStarted = apcu_fetch($cacheKey); // Start domaincheck again when 10 minutes are over by not returning here if($domaincheckWasStarted !== false && is_string($domaincheckWasStarted)) { diff --git a/php/src/Controller/LoginController.php b/php/src/Controller/LoginController.php index 787ec6e1..412ff9df 100644 --- a/php/src/Controller/LoginController.php +++ b/php/src/Controller/LoginController.php @@ -9,46 +9,43 @@ use AIO\Docker\DockerActionManager; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -class LoginController -{ - private AuthManager $authManager; - private DockerActionManager $dockerActionManager; - - public function __construct(AuthManager $authManager, DockerActionManager $dockerActionManager) { - $this->authManager = $authManager; - $this->dockerActionManager = $dockerActionManager; +readonly class LoginController { + public function __construct( + private AuthManager $authManager, + private DockerActionManager $dockerActionManager, + ) { } 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/PullContainerImages.php b/php/src/Cron/PullContainerImages.php new file mode 100644 index 00000000..43c87d28 --- /dev/null +++ b/php/src/Cron/PullContainerImages.php @@ -0,0 +1,20 @@ +get(\AIO\Controller\DockerController::class); + +// Pull all containers +$dockerController->PullAllContainerImages(); 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 b2093839..7534acda 100644 --- a/php/src/Data/ConfigurationManager.php +++ b/php/src/Data/ConfigurationManager.php @@ -7,55 +7,356 @@ use AIO\Controller\DockerController; class ConfigurationManager { - public function GetConfig() : array + private array $secrets = []; + + 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 { - $config = $this->GetConfig(); - if(!isset($config['secrets'][$secretId])) { - $config['secrets'][$secretId] = bin2hex(random_bytes(24)); - $this->WriteConfig($config); + /** + * 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 ''; + } + + $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 GetSecret(string $secretId) : string { - $config = $this->GetConfig(); - if(!isset($config['secrets'][$secretId])) { - $config['secrets'][$secretId] = ""; + public function getRegisteredSecret(string $secretId) : string { + if ($this->secrets[$secretId]) { + return $this->getAndGenerateSecret($secretId); } - - return $config['secrets'][$secretId]; + throw new \Exception("The secret " . $secretId . " was not registered. Please check if it is defined in secrets of containers.json."); } - private function DoubleSafeBackupSecret(string $borgBackupPassword) : void { + public function registerSecret(string $secretId) : void { + $this->secrets[$secretId] = true; + } + + private function doubleSafeBackupSecret(string $borgBackupPassword) : void { file_put_contents(DataConst::GetBackupSecretFile(), $borgBackupPassword); } @@ -67,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 = ""; @@ -91,26 +389,23 @@ class ConfigurationManager if ($lastBackupTime === "") { return ''; } - + 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 = []; foreach($backupLines as $lines) { if ($lines !== "") { $backupTimesTemp = explode(',', $lines); - $backupTimes[] = $backupTimesTemp[1]; + $backupTimes[] = $backupTimesTemp[1]; } } @@ -120,15 +415,15 @@ 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 ''; } - public function isx64Platform() : bool { + private function isx64Platform() : bool { if (php_uname('m') === 'x86_64') { return true; } else { @@ -136,140 +431,12 @@ class ConfigurationManager } } - public function isClamavEnabled() : bool { - if (!$this->isx64Platform()) { - return false; - } - - $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 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 { - $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!"); @@ -296,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 = ''; @@ -316,13 +484,13 @@ 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') { - throw new InvalidSettingConfigurationException("It seems like the ip-address of the domain is set to an internal or reserved ip-address. This is not supported. (It was found to be set to '" . $dnsRecordIP . "'). Please set it to a public ip-address so that the domain validation can work!"); + throw new InvalidSettingConfigurationException("It seems like the ip-address of the domain is set to an internal or reserved ip-address. This is not supported by the domain validation. (It was found to be set to '" . $dnsRecordIP . "'). Please set it to a public ip-address so that the domain validation can work or skip the domain validation!"); } else { - error_log("It seems like the ip-address of " . $domain . " is set to an internal or reserved ip-address. (It was found to be set to '" . $dnsRecordIP . "')"); + error_log("Info: It seems like the ip-address of " . $domain . " is set to an internal or reserved ip-address. (It was found to be set to '" . $dnsRecordIP . "')"); } } @@ -331,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') { @@ -346,10 +514,13 @@ 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); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = (string)curl_exec($ch); # Get rid of trailing \n @@ -363,130 +534,124 @@ class ConfigurationManager if ($port === '443') { $notice .= " If you should be using Cloudflare, make sure to disable the Cloudflare Proxy feature as it might block the domain validation. Same for any other firewall or service that blocks unencrypted access on port 443."; } else { - error_log('Please follow https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md#6-how-to-debug-things in order to debug things!'); + error_log('Please follow https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md#how-to-debug in order to debug things!'); } throw new InvalidSettingConfigurationException($notice); } } + $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']; + /** + * @throws InvalidSettingConfigurationException + */ + public function setBorgLocationVars(string $location, string $repo) : void { + $this->validateBorgLocationVars($location, $repo); + $this->startTransaction(); + $this->borgBackupHostLocation = $location; + $this->borgRemoteRepo = $repo; + $this->commitTransaction(); } - public function GetSelectedRestoreTime() : string { - $config = $this->GetConfig(); - if(!isset($config['selected-restore-time'])) { - $config['selected-restore-time'] = ''; + 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 !== '') { + throw new InvalidSettingConfigurationException("Location and remote repo url are mutually exclusive!"); } - return $config['selected-restore-time']; + if ($location !== '') { + $isValidPath = false; + if (str_starts_with($location, '/') && !str_ends_with($location, '/')) { + $isValidPath = true; + } elseif ($location === 'nextcloud_aio_backupdir') { + $isValidPath = true; + } + + if (!$isValidPath) { + throw new InvalidSettingConfigurationException("The path must start with '/', and must not end with '/'! Another option is to use the docker volume name 'nextcloud_aio_backupdir'."); + } + + // 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->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); + } } - public function GetAIOURL() : string { - $config = $this->GetConfig(); - if(!isset($config['AIO_URL'])) { - $config['AIO_URL'] = ''; + 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 + } elseif (!str_contains($repo, "@")) { + throw new InvalidSettingConfigurationException("The remote repo must contain '@'. $commonMsg"); + } elseif (!str_contains($repo, ":")) { + throw new InvalidSettingConfigurationException("The remote repo must contain ':'. $commonMsg"); } + } - return $config['AIO_URL']; + 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 SetBorgBackupHostLocation(string $location) : void { - $isValidPath = false; - if (str_starts_with($location, '/') && !str_ends_with($location, '/')) { - $isValidPath = true; - } elseif ($location === 'nextcloud_aio_backupdir') { - $isValidPath = true; - } - - if (!$isValidPath) { - throw new InvalidSettingConfigurationException("The path must start with '/', and must not end with '/'!"); - } - - - $config = $this->GetConfig(); - $config['borg_backup_host_location'] = $location; - $this->WriteConfig($config); - } - - public function DeleteBorgBackupHostLocation() : void { - $config = $this->GetConfig(); - $config['borg_backup_host_location'] = ''; - $this->WriteConfig($config); - } - - /** - * @throws InvalidSettingConfigurationException - */ - public function SetBorgRestoreHostLocationAndPassword(string $location, string $password) : void { - if ($location === '') { - throw new InvalidSettingConfigurationException("Please enter a path!"); - } - - $isValidPath = false; - if (str_starts_with($location, '/') && !str_ends_with($location, '/')) { - $isValidPath = true; - } elseif ($location === 'nextcloud_aio_backupdir') { - $isValidPath = true; - } - - if (!$isValidPath) { - throw new InvalidSettingConfigurationException("The path must start with '/', and must not end with '/'!"); - } + 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_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."); } @@ -503,194 +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'] = ''; + public function getBorgPublicKey() : string { + if (!file_exists(DataConst::GetBackupPublicKey())) { + return ""; } - return $config['borg_backup_host_location']; + 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 = '10G'; - 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 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!"); } @@ -698,7 +738,7 @@ class ConfigurationManager if (!preg_match("#^[0-1][0-9]:[0-5][0-9]$#", $time) && !preg_match("#^2[0-3]:[0-5][0-9]$#", $time)) { throw new InvalidSettingConfigurationException("You did not enter a correct time! One correct example is '04:00'!"); } - + if ($enableAutomaticUpdates === false) { $time .= PHP_EOL . 'automaticUpdatesAreNotEnabled'; } else { @@ -712,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]; } @@ -725,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; @@ -734,7 +774,7 @@ class ConfigurationManager } } - public function DeleteDailyBackupTime() : void { + public function deleteDailyBackupTime() : void { if (file_exists(DataConst::GetDailyBackupTimeFile())) { unlink(DataConst::GetDailyBackupTimeFile()); } @@ -743,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) { @@ -764,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!"); } @@ -814,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') !== false) { + 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); @@ -841,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!"); } @@ -861,78 +876,190 @@ 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', ''); } - public function GetApacheIPBinding() : string { - $envVariableName = 'APACHE_IP_BINDING'; - $configName = 'apache_ip_binding'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } + /** + * @throws InvalidSettingConfigurationException + */ + private function validateCollaboraAdditionalOptions(string $additionalCollaboraOptions) : void { + if ($additionalCollaboraOptions === "") { + throw new InvalidSettingConfigurationException("The additional options must not be empty!"); + } - 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; + 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!"); } } - private function GetCommunityContainers() : string { - $envVariableName = 'AIO_COMMUNITY_CONTAINERS'; - $configName = 'aio_community_containers'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); + public function isCollaboraSubscriptionEnabled() : bool { + return str_contains($this->collaboraAdditionalOptions, '--o:support_key='); } - public function GetEnabledCommunityContainers() : array { - return explode(' ', $this->GetCommunityContainers()); + /** + * Provide an extra method since the corresponding attribute setter prevents setting an empty value. + */ + public function deleteAdditionalCollaboraOptions() : void { + $this->set('collabora_additional_options', ''); } - private function GetEnabledDriDevice() : string { - $envVariableName = 'NEXTCLOUD_ENABLE_DRI_DEVICE'; - $configName = 'nextcloud_enable_dri_device'; - $defaultValue = ''; - return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue); - } - - public function isDriDeviceEnabled() : bool { - if ($this->GetEnabledDriDevice() === 'true') { - return true; - } else { - return false; + public function listAvailableCommunityContainers() : array { + $cc = []; + $dir = scandir(DataConst::GetCommunityContainersDirectory()); + if ($dir === false) { + return $cc; } - } - - 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; + // Get rid of dots from the scandir command + $dir = array_diff($dir, array('..', '.', 'readme.md')); + foreach ($dir as $id) { + $filePath = DataConst::GetCommunityContainersDirectory() . '/' . $id . '/' . $id . '.json'; + $fileContents = apcu_fetch($filePath); + if (!is_string($fileContents)) { + $fileContents = file_get_contents($filePath); + if (is_string($fileContents)) { + apcu_add($filePath, $fileContents); + } + } + $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'] : ''; + if (is_string($service['display_name'])) { + $cc[$id] = [ + 'id' => $id, + 'name' => $service['display_name'], + 'documentation' => $documentation + ]; + } + break; + } + } } + return $cc; + } + + private function camelize(string $input, string $delimiter = '_') : string { + if ($input === '') { + throw new InvalidSettingConfigurationException('input cannot be empty!'); + } + 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 2512b7fb..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,13 +16,17 @@ class DataConst { return '/mnt/docker-aio-config/session/'; } - return realpath(__DIR__ . '/../../session/'); + return (string)realpath(__DIR__ . '/../../session/'); } public static function GetConfigFile() : string { return self::GetDataDirectory() . '/configuration.json'; } + public static function GetBackupPublicKey() : string { + return self::GetDataDirectory() . '/id_borg.pub'; + } + public static function GetBackupSecretFile() : string { return self::GetDataDirectory() . '/backupsecret'; } @@ -52,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 2ab87e36..e409eef8 100644 --- a/php/src/Data/Setup.php +++ b/php/src/Data/Setup.php @@ -4,16 +4,11 @@ namespace AIO\Data; use AIO\Auth\PasswordGenerator; -class Setup -{ - private PasswordGenerator $passwordGenerator; - private ConfigurationManager $configurationManager; - +readonly class Setup { public function __construct( - PasswordGenerator $passwordGenerator, - ConfigurationManager $configurationManager) { - $this->passwordGenerator = $passwordGenerator; - $this->configurationManager = $configurationManager; + private PasswordGenerator $passwordGenerator, + private ConfigurationManager $configurationManager, + ) { } public function Setup() : string { @@ -22,7 +17,7 @@ class Setup } $password = $this->passwordGenerator->GeneratePassword(8); - $this->configurationManager->SetPassword($password); + $this->configurationManager->password = $password; return $password; } diff --git a/php/src/DependencyInjection.php b/php/src/DependencyInjection.php index e37a0917..1fedada8 100644 --- a/php/src/DependencyInjection.php +++ b/php/src/DependencyInjection.php @@ -4,6 +4,7 @@ namespace AIO; use AIO\Docker\DockerHubManager; use DI\Container; +use AIO\Docker\GitHubContainerRegistryManager; class DependencyInjection { @@ -15,6 +16,11 @@ class DependencyInjection new DockerHubManager() ); + $container->set( + GitHubContainerRegistryManager::class, + new GitHubContainerRegistryManager() + ); + $container->set( \AIO\Data\ConfigurationManager::class, new \AIO\Data\ConfigurationManager() @@ -24,7 +30,8 @@ class DependencyInjection new \AIO\Docker\DockerActionManager( $container->get(\AIO\Data\ConfigurationManager::class), $container->get(\AIO\ContainerDefinitionFetcher::class), - $container->get(DockerHubManager::class) + $container->get(DockerHubManager::class), + $container->get(GitHubContainerRegistryManager::class) ) ); $container->set( diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 8b2e8a3e..86b36619 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -3,156 +3,138 @@ namespace AIO\Docker; use AIO\Container\Container; -use AIO\Container\State\IContainerState; -use AIO\Container\State\ImageDoesNotExistState; -use AIO\Container\State\StartingState; -use AIO\Container\State\RunningState; -use AIO\Container\State\RestartingState; -use AIO\Container\State\NotRestartingState; -use AIO\Container\State\VersionDifferentState; -use AIO\Container\State\StoppedState; -use AIO\Container\State\VersionEqualState; -use AIO\Data\ConfigurationManager; -use GuzzleHttp\Exception\RequestException; +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; -class DockerActionManager -{ - private const string API_VERSION = 'v1.41'; - private \GuzzleHttp\Client $guzzleClient; - private ConfigurationManager $configurationManager; - private ContainerDefinitionFetcher $containerDefinitionFetcher; - private DockerHubManager $dockerHubManager; +readonly class DockerActionManager { + private const string API_VERSION = 'v1.44'; + private Client $guzzleClient; public function __construct( - ConfigurationManager $configurationManager, - ContainerDefinitionFetcher $containerDefinitionFetcher, - DockerHubManager $dockerHubManager + private ConfigurationManager $configurationManager, + private ContainerDefinitionFetcher $containerDefinitionFetcher, + private DockerHubManager $dockerHubManager, + private GitHubContainerRegistryManager $gitHubContainerRegistryManager ) { - $this->configurationManager = $configurationManager; - $this->containerDefinitionFetcher = $containerDefinitionFetcher; - $this->dockerHubManager = $dockerHubManager; - $this->guzzleClient = new \GuzzleHttp\Client( - [ - 'curl' => [ - CURLOPT_UNIX_SOCKET_PATH => '/var/run/docker.sock', - - ], - ] - ); + $this->guzzleClient = new Client(['curl' => [CURLOPT_UNIX_SOCKET_PATH => '/var/run/docker.sock']]); } - private function BuildApiUrl(string $url) : string { - return sprintf('http://127.0.0.1/%s/%s', self::API_VERSION, $url); + private function BuildApiUrl(string $url): string { + $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(); + private function BuildImageName(Container $container): string { + $tag = $container->imageTag; if ($tag === '%AIO_CHANNEL%') { $tag = $this->GetCurrentChannel(); } - return $container->GetContainerName() . ':' . $tag; + return $container->containerName . ':' . $tag; } - public function GetContainerRunningState(Container $container) : IContainerState - { - $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier()))); + public function GetContainerRunningState(Container $container): ContainerState { + $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier))); try { $response = $this->guzzleClient->get($url); } catch (RequestException $e) { if ($e->getCode() === 404) { - return new ImageDoesNotExistState(); + return ContainerState::ImageDoesNotExist; } 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 new RunningState(); + return ContainerState::Running; } else { - return new StoppedState(); + return ContainerState::Stopped; } } - public function GetContainerRestartingState(Container $container) : IContainerState - { - $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier()))); + public function GetContainerRestartingState(Container $container): ContainerState { + $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier))); try { $response = $this->guzzleClient->get($url); } catch (RequestException $e) { if ($e->getCode() === 404) { - return new ImageDoesNotExistState(); + return ContainerState::ImageDoesNotExist; } 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 new RestartingState(); + return ContainerState::Restarting; } else { - return new NotRestartingState(); + return ContainerState::NotRestarting; } } - public function GetContainerUpdateState(Container $container) : IContainerState - { - $tag = $container->GetImageTag(); + public function GetContainerUpdateState(Container $container): VersionState { + $tag = $container->imageTag; if ($tag === '%AIO_CHANNEL%') { $tag = $this->GetCurrentChannel(); } - $runningDigests = $this->GetRepoDigestsOfContainer($container->GetIdentifier()); + $runningDigests = $this->GetRepoDigestsOfContainer($container->identifier); if ($runningDigests === null) { - return new VersionDifferentState(); + return VersionState::Different; } - $remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($container->GetContainerName(), $tag); + $remoteDigest = $this->GetLatestDigestOfTag($container->containerName, $tag); if ($remoteDigest === null) { - return new VersionEqualstate(); + return VersionState::Equal; } - foreach($runningDigests as $runningDigest) { + foreach ($runningDigests as $runningDigest) { if ($runningDigest === $remoteDigest) { - return new VersionEqualState(); + return VersionState::Equal; } } - return new VersionDifferentState(); + return VersionState::Different; } - public function GetContainerStartingState(Container $container) : IContainerState - { + public function GetContainerStartingState(Container $container): ContainerState { $runningState = $this->GetContainerRunningState($container); - if ($runningState instanceof StoppedState) { - return new StoppedState(); - } elseif ($runningState instanceof ImageDoesNotExistState) { - return new ImageDoesNotExistState(); + if ($runningState === ContainerState::Stopped || $runningState === ContainerState::ImageDoesNotExist) { + return $runningState; } - $containerName = $container->GetIdentifier(); - $internalPort = $container->GetInternalPort(); - if($internalPort === '%APACHE_PORT%') { - $internalPort = $this->configurationManager->GetApachePort(); - } elseif($internalPort === '%TALK_PORT%') { - $internalPort = $this->configurationManager->GetTalkPort(); + $containerName = $container->identifier; + $internalPort = $container->internalPorts; + if ($internalPort === '%APACHE_PORT%') { + $internalPort = $this->configurationManager->apachePort; + } elseif ($internalPort === '%TALK_PORT%') { + $internalPort = $this->configurationManager->talkPort; } if ($internalPort !== "" && $internalPort !== 'host') { $connection = @fsockopen($containerName, (int)$internalPort, $errno, $errstr, 0.2); if ($connection) { fclose($connection); - return new RunningState(); + return ContainerState::Running; } else { - return new StartingState(); + return ContainerState::Starting; } } else { - return new RunningState(); + return ContainerState::Running; } } - public function DeleteContainer(Container $container) : void { - $url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->GetIdentifier()))); + public function DeleteContainer(Container $container): void { + $url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->identifier))); try { $this->guzzleClient->delete($url); } catch (RequestException $e) { @@ -162,8 +144,7 @@ class DockerActionManager } } - public function GetLogs(string $id) : string - { + public function GetLogs(string $id): string { $url = $this->BuildApiUrl( sprintf( 'containers/%s/logs?stdout=true&stderr=true×tamps=true', @@ -174,29 +155,28 @@ 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()))); + public function StartContainer(Container $container): void { + $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->getMessage()); + throw new \Exception("Could not start container " . $container->identifier . ": " . $e->getResponse()?->getBody()->getContents()); } } - public function CreateVolumes(Container $container): void - { + public function CreateVolumes(Container $container): void { $url = $this->BuildApiUrl('volumes/create'); - foreach($container->GetVolumes()->GetVolumes() as $volume) { + foreach ($container->volumes->GetVolumes() as $volume) { $forbiddenChars = [ '/', ]; @@ -206,7 +186,7 @@ class DockerActionManager } $firstChar = substr($volume->name, 0, 1); - if(!in_array($firstChar, $forbiddenChars)) { + if (!in_array($firstChar, $forbiddenChars)) { $this->guzzleClient->request( 'POST', $url, @@ -220,12 +200,12 @@ class DockerActionManager } } - public function CreateContainer(Container $container) : void { + 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; // } // } @@ -248,208 +228,38 @@ class DockerActionManager $requestBody['HostConfig']['Binds'] = $volumes; } - foreach($container->GetSecrets() as $secret) { - $this->configurationManager->GetAndGenerateSecret($secret); - } + $this->configurationManager->setAioVariables($container->aioVariables->GetVariables()); - $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); - } - - $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) { - // TODO: This whole block below is a hack and needs to get reworked in order to support multiple substitutions per line by default for all envs - if (str_starts_with($env, 'extra_params=')) { - $env = str_replace('%COLLABORA_SECCOMP_POLICY%', $this->configurationManager->GetCollaboraSeccompPolicy(), $env); - $env = str_replace('%NC_DOMAIN%', $this->configurationManager->GetDomain(), $env); - $envs[$key] = $env; - continue; - } - - // Original implementation - $patterns = ['/%(.*)%/']; - - if(preg_match($patterns[0], $env, $out) === 1) { - $replacements = array(); - - if($out[1] === 'NC_DOMAIN') { - $replacements[1] = $this->configurationManager->GetDomain(); - } elseif($out[1] === 'NC_BASE_DN') { - $replacements[1] = $this->configurationManager->GetBaseDN(); - } elseif ($out[1] === 'AIO_TOKEN') { - $replacements[1] = $this->configurationManager->GetToken(); - } elseif ($out[1] === 'BORGBACKUP_MODE') { - $replacements[1] = $this->configurationManager->GetBackupMode(); - } elseif ($out[1] === 'AIO_URL') { - $replacements[1] = $this->configurationManager->GetAIOURL(); - } elseif ($out[1] === 'SELECTED_RESTORE_TIME') { - $replacements[1] = $this->configurationManager->GetSelectedRestoreTime(); - } elseif ($out[1] === 'APACHE_PORT') { - $replacements[1] = $this->configurationManager->GetApachePort(); - } elseif ($out[1] === 'TALK_PORT') { - $replacements[1] = $this->configurationManager->GetTalkPort(); - } elseif ($out[1] === 'NEXTCLOUD_MOUNT') { - $replacements[1] = $this->configurationManager->GetNextcloudMount(); - } elseif ($out[1] === 'BACKUP_RESTORE_PASSWORD') { - $replacements[1] = $this->configurationManager->GetBorgRestorePassword(); - } elseif ($out[1] === 'CLAMAV_ENABLED') { - if ($this->configurationManager->isClamavEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'TALK_RECORDING_ENABLED') { - if ($this->configurationManager->isTalkRecordingEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'ONLYOFFICE_ENABLED') { - if ($this->configurationManager->isOnlyofficeEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'COLLABORA_ENABLED') { - if ($this->configurationManager->isCollaboraEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'TALK_ENABLED') { - if ($this->configurationManager->isTalkEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'UPDATE_NEXTCLOUD_APPS') { - if ($this->configurationManager->isDailyBackupRunning() && $this->configurationManager->areAutomaticUpdatesEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'TIMEZONE') { - if ($this->configurationManager->GetTimezone() === '') { - $replacements[1] = 'Etc/UTC'; - } else { - $replacements[1] = $this->configurationManager->GetTimezone(); - } - } elseif ($out[1] === 'COLLABORA_DICTIONARIES') { - if ($this->configurationManager->GetCollaboraDictionaries() === '') { - $replacements[1] = 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru'; - } else { - $replacements[1] = $this->configurationManager->GetCollaboraDictionaries(); - } - } elseif ($out[1] === 'IMAGINARY_ENABLED') { - if ($this->configurationManager->isImaginaryEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'FULLTEXTSEARCH_ENABLED') { - if ($this->configurationManager->isFulltextsearchEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'DOCKER_SOCKET_PROXY_ENABLED') { - if ($this->configurationManager->isDockerSocketProxyEnabled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'NEXTCLOUD_UPLOAD_LIMIT') { - $replacements[1] = $this->configurationManager->GetNextcloudUploadLimit(); - } elseif ($out[1] === 'NEXTCLOUD_MEMORY_LIMIT') { - $replacements[1] = $this->configurationManager->GetNextcloudMemoryLimit(); - } elseif ($out[1] === 'NEXTCLOUD_MAX_TIME') { - $replacements[1] = $this->configurationManager->GetNextcloudMaxTime(); - } elseif ($out[1] === 'BORG_RETENTION_POLICY') { - $replacements[1] = $this->configurationManager->GetBorgRetentionPolicy(); - } elseif ($out[1] === 'NEXTCLOUD_TRUSTED_CACERTS_DIR') { - $replacements[1] = $this->configurationManager->GetTrustedCacertsDir(); - } elseif ($out[1] === 'ADDITIONAL_DIRECTORIES_BACKUP') { - if ($this->configurationManager->GetAdditionalBackupDirectoriesString() !== '') { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'BORGBACKUP_HOST_LOCATION') { - $replacements[1] = $this->configurationManager->GetBorgBackupHostLocation(); - } elseif ($out[1] === 'APACHE_MAX_SIZE') { - $replacements[1] = $this->configurationManager->GetApacheMaxSize(); - } elseif ($out[1] === 'COLLABORA_SECCOMP_POLICY') { - $replacements[1] = $this->configurationManager->GetCollaboraSeccompPolicy(); - } elseif ($out[1] === 'NEXTCLOUD_STARTUP_APPS') { - $replacements[1] = $this->configurationManager->GetNextcloudStartupApps(); - } elseif ($out[1] === 'NEXTCLOUD_ADDITIONAL_APKS') { - $replacements[1] = $this->configurationManager->GetNextcloudAdditionalApks(); - } elseif ($out[1] === 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS') { - $replacements[1] = $this->configurationManager->GetNextcloudAdditionalPhpExtensions(); - } elseif ($out[1] === 'INSTALL_LATEST_MAJOR') { - if ($this->configurationManager->shouldLatestMajorGetInstalled()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - } elseif ($out[1] === 'REMOVE_DISABLED_APPS') { - if ($this->configurationManager->shouldDisabledAppsGetRemoved()) { - $replacements[1] = 'yes'; - } else { - $replacements[1] = ''; - } - // Allow to get local ip-address of database container which allows to talk to it even in host mode (the container that requires this needs to be started first then) - } elseif ($out[1] === 'AIO_DATABASE_HOST') { - $replacements[1] = gethostbyname('nextcloud-aio-database'); - // Allow to get local ip-address of caddy container and add it to trusted proxies automatically - } elseif ($out[1] === 'CADDY_IP_ADDRESS') { - $replacements[1] = ''; - $communityContainers = $this->configurationManager->GetEnabledCommunityContainers(); - if (in_array('caddy', $communityContainers, true)) { - $replacements[1] = gethostbyname('nextcloud-aio-caddy'); - } - } else { - $secret = $this->configurationManager->GetSecret($out[1]); - if ($secret === "") { - throw new \Exception("The secret " . $out[1] . " is empty. Cannot substitute its value. Please check if it is defined in secrets of containers.json."); - } - $replacements[1] = $secret; - } - - $envs[$key] = preg_replace($patterns, $replacements, $env); - } + foreach ($envs as $key => $env) { + $envs[$key] = $this->configurationManager->replaceEnvPlaceholders($env); } - if(count($envs) > 0) { + 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; @@ -459,23 +269,27 @@ class DockerActionManager $requestBody['HostConfig']['NetworkMode'] = 'host'; } - if(count($exposedPorts) > 0) { + 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; @@ -484,16 +298,16 @@ class DockerActionManager $portWithProtocol = $port . '/' . $protocol; $requestBody['HostConfig']['PortBindings'][$portWithProtocol] = [ [ - 'HostPort' => $port, - 'HostIp' => $ipBinding, + 'HostPort' => $port, + 'HostIp' => $ipBinding, ] ]; } } $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"]; @@ -503,13 +317,24 @@ class DockerActionManager $requestBody['HostConfig']['Devices'] = $devices; } - $shmSize = $container->GetShmSize(); + if ($container->enableNvidiaGpu && $this->configurationManager->enableNvidiaGpu) { + $requestBody['HostConfig']['Runtime'] = 'nvidia'; + $requestBody['HostConfig']['DeviceRequests'] = [ + [ + "Driver" => "nvidia", + "Count" => 1, + "Capabilities" => [["gpu"]], + ] + ]; + } + + $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]; @@ -518,12 +343,17 @@ class DockerActionManager $tmpfs[$tmp] = $mode; } if (count($tmpfs) > 0) { - $requestBody['HostConfig']['Tmpfs'] = $tmpfs; + $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; } @@ -535,51 +365,84 @@ 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 ($container->GetIdentifier() === 'nextcloud-aio-borgbackup') { + if (str_starts_with($container->identifier, 'nextcloud-aio-borgbackup')) { // Additional backup directories foreach ($this->getAllBackupVolumes() as $additionalBackupVolumes) { if ($additionalBackupVolumes !== '') { $mounts[] = ["Type" => "volume", "Source" => $additionalBackupVolumes, "Target" => "/nextcloud_aio_volumes/" . $additionalBackupVolumes, "ReadOnly" => false]; } } - foreach ($this->configurationManager->GetAdditionalBackupDirectoriesArray() as $additionalBackupDirectories) { + + // Make volumes read only in case of borgbackup container. The viewer makes them writeable + $isReadOnly = $container->identifier === 'nextcloud-aio-borgbackup'; + + foreach ($this->configurationManager->getAdditionalBackupDirectoriesArray() as $additionalBackupDirectories) { if ($additionalBackupDirectories !== '') { if (!str_starts_with($additionalBackupDirectories, '/')) { - $mounts[] = ["Type" => "volume", "Source" => $additionalBackupDirectories, "Target" => "/docker_volumes/" . $additionalBackupDirectories, "ReadOnly" => true]; + $mounts[] = ["Type" => "volume", "Source" => $additionalBackupDirectories, "Target" => "/docker_volumes/" . $additionalBackupDirectories, "ReadOnly" => $isReadOnly]; } else { - $mounts[] = ["Type" => "bind", "Source" => $additionalBackupDirectories, "Target" => "/host_mounts" . $additionalBackupDirectories, "ReadOnly" => true, "BindOptions" => ["NonRecursive" => true]]; + $mounts[] = ["Type" => "bind", "Source" => $additionalBackupDirectories, "Target" => "/host_mounts" . $additionalBackupDirectories, "ReadOnly" => $isReadOnly, "BindOptions" => ["NonRecursive" => true]]; } } } + // 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 + } 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()) { - // continue; - // } - // $mounts[] = ["Type" => "bind", "Source" => $volume->name, "Target" => $volume->mountPoint, "ReadOnly" => !$volume->isWritable, "BindOptions" => [ "Propagation" => "rshared"]]; - // } + // // Special things for the nextcloud container which should not be exposed in the containers.json + // } elseif ($container->identifier === 'nextcloud-aio-nextcloud') { + // foreach ($container->volumes->GetVolumes() as $volume) { + // if ($volume->name !== $this->configurationManager->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') { + } 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->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)); + } } if (count($mounts) > 0) { $requestBody['HostConfig']['Mounts'] = $mounts; } - $url = $this->BuildApiUrl('containers/create?name=' . $container->GetIdentifier()); + // 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 + // 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->identifier; + + $url = $this->BuildApiUrl('containers/create?name=' . $container->identifier); try { $this->guzzleClient->request( 'POST', @@ -589,18 +452,18 @@ class DockerActionManager ] ); } catch (RequestException $e) { - throw new \Exception("Could not create container " . $container->GetIdentifier() . ": " . $e->getMessage()); + throw new \Exception("Could not create container " . $container->identifier . ": " . $e->getResponse()?->getBody()->getContents()); } } - public function isDockerHubReachable(Container $container) : bool { - $tag = $container->GetImageTag(); + public function isRegistryReachable(Container $container): bool { + $tag = $container->imageTag; if ($tag === '%AIO_CHANNEL%') { $tag = $this->GetCurrentChannel(); } - $remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($container->GetContainerName(), $tag); + $remoteDigest = $this->GetLatestDigestOfTag($container->containerName, $tag); if ($remoteDigest === null) { return false; @@ -609,8 +472,30 @@ class DockerActionManager } } - public function PullImage(Container $container) : void - { + public function PullImage(Container $container, bool $pullImage = true): void { + + // Skip database image pull if the last shutdown was not clean + 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.'); + } + } + + // Check if registry is reachable in order to make sure that we do not try to pull an image if it is down + // and try to mitigate issues that are arising due to that + if ($pullImage) { + if (!$this->isRegistryReachable($container)) { + $pullImage = false; + error_log('Not pulling the ' . $container->containerName . ' image for the ' . $container->identifier . ' container because the registry does not seem to be reachable.'); + } + } + + // Do not continue if $pullImage is false + if (!$pullImage) { + return; + } + $imageName = $this->BuildImageName($container); $encodedImageName = urlencode($imageName); $url = $this->BuildApiUrl(sprintf('images/create?fromImage=%s', $encodedImageName)); @@ -621,32 +506,44 @@ class DockerActionManager } catch (\Throwable $e) { $imageIsThere = false; } - try { - $this->guzzleClient->post($url); - } catch (RequestException $e) { - if ($imageIsThere === false) { - throw new \Exception("Could not pull image " . $imageName . ". Please run 'sudo docker exec -it nextcloud-aio-mastercontainer docker pull " . $imageName . "' in order to find out why it failed."); + + $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); + } } } } - private function isContainerUpdateAvailable(string $id) : string - { + private function isContainerUpdateAvailable(string $id): string { $container = $this->containerDefinitionFetcher->GetContainerById($id); $updateAvailable = ""; - if ($container->GetUpdateState() instanceof VersionDifferentState) { + if ($container->GetUpdateState() === VersionState::Different) { $updateAvailable = '1'; } - foreach ($container->GetDependsOn() as $dependency) { + foreach ($container->dependsOn as $dependency) { $updateAvailable .= $this->isContainerUpdateAvailable($dependency); } return $updateAvailable; } - public function isAnyUpdateAvailable() : bool { + 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'; @@ -658,54 +555,51 @@ class DockerActionManager } } - private function getBackupVolumes(string $id) : string - { + private function getBackupVolumes(string $id): string { $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; } - private function getAllBackupVolumes() : array { + private function getAllBackupVolumes(): array { $id = 'nextcloud-aio-apache'; $backupVolumesArray = explode(' ', $this->getBackupVolumes($id)); return array_unique($backupVolumesArray); } - private function GetNextcloudExecCommands(string $id) : string - { + private function GetNextcloudExecCommands(string $id): string { $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; } - private function GetAllNextcloudExecCommands() : string - { + private function GetAllNextcloudExecCommands(): string { $id = 'nextcloud-aio-apache'; return 'NEXTCLOUD_EXEC_COMMANDS=' . $this->GetNextcloudExecCommands($id); } - private function GetRepoDigestsOfContainer(string $containerName) : ?array { + 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); @@ -719,7 +613,7 @@ class DockerActionManager $repoDigestArray = []; $oneDigestGiven = false; - foreach($imageOutput['RepoDigests'] as $repoDigest) { + foreach ($imageOutput['RepoDigests'] as $repoDigest) { $digestPosition = strpos($repoDigest, '@'); if ($digestPosition === false) { error_log('Somehow the RepoDigest of ' . $containerName . ' does not contain a @.'); @@ -739,29 +633,52 @@ class DockerActionManager } } - public function GetCurrentChannel() : string { + private function GetCurrentImageName(): string { + $cacheKey = 'aio-image-name'; + $imageName = apcu_fetch($cacheKey); + if ($imageName !== false && is_string($imageName)) { + return $imageName; + } + + $containerName = 'nextcloud-aio-mastercontainer'; + $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); + try { + $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]; + } else { + error_log("No tag was found when getting the current channel. You probably did not follow the documentation correctly. Changing the imageName to the default " . $output['Config']['Image']); + $imageName = $output['Config']['Image']; + } + apcu_add($cacheKey, $imageName); + return $imageName; + } catch (\Exception $e) { + error_log('Could not get current imageName ' . $e->getMessage()); + } + + return 'nextcloud/all-in-one'; + } + + public function GetCurrentChannel(): string { $cacheKey = 'aio-ChannelName'; $channelName = apcu_fetch($cacheKey); - if($channelName !== false && is_string($channelName)) { + if ($channelName !== false && is_string($channelName)) { return $channelName; } $containerName = 'nextcloud-aio-mastercontainer'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); try { - $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true); - $containerChecksum = $output['Image']; + $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $tagArray = explode(':', $output['Config']['Image']); - $tag = $tagArray[1]; - apcu_add($cacheKey, $tag); - /** - * @psalm-suppress TypeDoesNotContainNull - * @psalm-suppress DocblockTypeContradiction - */ - if ($tag === null) { + if (count($tagArray) === 2) { + $tag = $tagArray[1]; + } else { error_log("No tag was found when getting the current channel. You probably did not follow the documentation correctly. Changing the channel to the default 'latest'."); $tag = 'latest'; } + apcu_add($cacheKey, $tag); return $tag; } catch (\Exception $e) { error_log('Could not get current channel ' . $e->getMessage()); @@ -770,9 +687,8 @@ class DockerActionManager return 'latest'; } - public function IsMastercontainerUpdateAvailable() : bool - { - $imageName = 'nextcloud/all-in-one'; + public function IsMastercontainerUpdateAvailable(): bool { + $imageName = $this->GetCurrentImageName(); $containerName = 'nextcloud-aio-mastercontainer'; $tag = $this->GetCurrentChannel(); @@ -781,7 +697,7 @@ class DockerActionManager if ($runningDigests === null) { return true; } - $remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag); + $remoteDigest = $this->GetLatestDigestOfTag($imageName, $tag); if ($remoteDigest === null) { return false; } @@ -794,11 +710,10 @@ class DockerActionManager return true; } - public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh') : void - { - if ($this->GetContainerStartingState($container) instanceof RunningState) { + 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))); @@ -819,7 +734,9 @@ class DockerActionManager ], ] )->getBody()->getContents(), - true + true, + 512, + JSON_THROW_ON_ERROR, ); $id = $response['Id']; @@ -839,8 +756,7 @@ class DockerActionManager } } - private function DisconnectContainerFromBridgeNetwork(string $id) : void - { + private function DisconnectContainerFromBridgeNetwork(string $id): void { $url = $this->BuildApiUrl( sprintf('networks/%s/disconnect', 'bridge') @@ -860,44 +776,48 @@ class DockerActionManager } } - private function ConnectContainerIdToNetwork(string $id, string $internalPort, string $network = 'nextcloud-aio') : void - { + private function ConnectContainerIdToNetwork(string $id, string $internalPort, string $network = 'nextcloud-aio', bool $createNetwork = true, string $alias = ''): void { if ($internalPort === 'host') { return; } - $url = $this->BuildApiUrl('networks/create'); - try { - $this->guzzleClient->request( - 'POST', - $url, - [ - 'json' => [ - 'Name' => $network, - 'CheckDuplicate' => true, - 'Driver' => 'bridge', - 'Internal' => false, + if ($createNetwork) { + $url = $this->BuildApiUrl('networks/create'); + try { + $this->guzzleClient->request( + 'POST', + $url, + [ + 'json' => [ + 'Name' => $network, + 'CheckDuplicate' => true, + 'Driver' => 'bridge', + 'Internal' => false, + ] ] - ] - ); - } catch (RequestException $e) { - // 409 is undocumented and gets thrown if the network already exists. - if ($e->getCode() !== 409) { - throw new \Exception("Could not create the nextcloud-aio network: " . $e->getMessage()); + ); + } catch (RequestException $e) { + // 409 is undocumented and gets thrown if the network already exists. + if ($e->getCode() !== 409) { + throw new \Exception("Could not create the nextcloud-aio network: " . $e->getResponse()?->getBody()->getContents()); + } } } $url = $this->BuildApiUrl( sprintf('networks/%s/connect', $network) ); + $jsonPayload = ['Container' => $id]; + if ($alias !== '') { + $jsonPayload['EndpointConfig'] = ['Aliases' => [$alias]]; + } + try { $this->guzzleClient->request( 'POST', $url, [ - 'json' => [ - 'container' => $id, - ] + 'json' => $jsonPayload ] ); } catch (RequestException $e) { @@ -908,20 +828,35 @@ class DockerActionManager } } - public function ConnectMasterContainerToNetwork() : void - { + public function ConnectMasterContainerToNetwork(): void { $this->ConnectContainerIdToNetwork('nextcloud-aio-mastercontainer', ''); // Don't disconnect here since it slows down the initial login by a lot. Is getting done during cron.sh instead. // $this->DisconnectContainerFromBridgeNetwork('nextcloud-aio-mastercontainer'); } - public function ConnectContainerToNetwork(Container $container) : void - { - $this->ConnectContainerIdToNetwork($container->GetIdentifier(), $container->GetInternalPort()); + public function ConnectContainerToNetwork(Container $container): void { + // 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->identifier === 'nextcloud-aio-domaincheck') ? 'nextcloud-aio-apache' : ''; + + $this->ConnectContainerIdToNetwork($container->identifier, $container->internalPorts, alias: $alias); + + if ($container->identifier === 'nextcloud-aio-apache' || $container->identifier === 'nextcloud-aio-domaincheck') { + $apacheAdditionalNetwork = $this->configurationManager->getApacheAdditionalNetwork(); + if ($apacheAdditionalNetwork !== '') { + $this->ConnectContainerIdToNetwork($container->identifier, $container->internalPorts, $apacheAdditionalNetwork, false, $alias); + } + } } - public function StopContainer(Container $container) : void { - $url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->GetIdentifier()), $container->GetMaxShutdownTime())); + public function StopContainer(Container $container, bool $forceStopContainer = false): void { + if ($forceStopContainer) { + $maxShutDownTime = 10; + } else { + $maxShutDownTime = $container->maxShutdownTime; + } + $url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->identifier), $maxShutDownTime)); try { $this->guzzleClient->post($url); } catch (RequestException $e) { @@ -931,8 +866,7 @@ class DockerActionManager } } - public function GetBackupcontainerExitCode() : int - { + public function GetBackupcontainerExitCode(): int { $containerName = 'nextcloud-aio-borgbackup'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName))); try { @@ -944,7 +878,7 @@ 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)) { @@ -954,8 +888,7 @@ class DockerActionManager } } - public function GetDatabasecontainerExitCode() : int - { + public function GetDatabasecontainerExitCode(): int { $containerName = 'nextcloud-aio-database'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName))); try { @@ -967,7 +900,7 @@ 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)) { @@ -977,29 +910,29 @@ class DockerActionManager } } - public function isLoginAllowed() : bool { + public function isLoginAllowed(): bool { $id = 'nextcloud-aio-apache'; $apacheContainer = $this->containerDefinitionFetcher->GetContainerById($id); - if ($this->GetContainerStartingState($apacheContainer) instanceof RunningState) { + if ($this->GetContainerStartingState($apacheContainer) === ContainerState::Running) { return false; } return true; } - public function isBackupContainerRunning() : bool { + public function isBackupContainerRunning(): bool { $id = 'nextcloud-aio-borgbackup'; $backupContainer = $this->containerDefinitionFetcher->GetContainerById($id); - if ($this->GetContainerRunningState($backupContainer) instanceof RunningState) { + if ($this->GetContainerRunningState($backupContainer) === ContainerState::Running) { return true; } return false; } - private function GetCreatedTimeOfNextcloudImage() : ?string { - $imageName = 'nextcloud/aio-nextcloud' . ':' . $this->GetCurrentChannel(); + private function GetCreatedTimeOfNextcloudImage(string $imageName): ?string { + $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); @@ -1012,8 +945,16 @@ class DockerActionManager } } - public function isNextcloudImageOutdated() : bool { - $createdTime = $this->GetCreatedTimeOfNextcloudImage(); + public function GetAndGenerateSecretWrapper(string $secretId): string { + return $this->configurationManager->getAndGenerateSecret($secretId); + } + + public function isNextcloudImageOutdated(): bool { + $createdTime = $this->GetCreatedTimeOfNextcloudImage('ghcr.io/nextcloud-releases/aio-nextcloud'); + + if ($createdTime === null) { + $createdTime = $this->GetCreatedTimeOfNextcloudImage('nextcloud/aio-nextcloud'); + } if ($createdTime === null) { return false; @@ -1026,4 +967,13 @@ class DockerActionManager return false; } + + public function GetLatestDigestOfTag(string $imageName, string $tag): ?string { + $prefix = 'ghcr.io/'; + if (str_starts_with($imageName, $prefix)) { + return $this->gitHubContainerRegistryManager->GetLatestDigestOfTag(str_replace($prefix, '', $imageName), $tag); + } else { + return $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag); + } + } } diff --git a/php/src/Docker/DockerHubManager.php b/php/src/Docker/DockerHubManager.php index 36f46982..256d592e 100644 --- a/php/src/Docker/DockerHubManager.php +++ b/php/src/Docker/DockerHubManager.php @@ -6,12 +6,11 @@ use AIO\ContainerDefinitionFetcher; use AIO\Data\ConfigurationManager; use GuzzleHttp\Client; -class DockerHubManager -{ +readonly class DockerHubManager { private Client $guzzleClient; - public function __construct() - { + public function __construct( + ) { $this->guzzleClient = new Client(); } @@ -31,7 +30,7 @@ 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( @@ -59,4 +58,4 @@ class DockerHubManager return null; } } -} \ No newline at end of file +} diff --git a/php/src/Docker/GitHubContainerRegistryManager.php b/php/src/Docker/GitHubContainerRegistryManager.php new file mode 100644 index 00000000..eeecfb28 --- /dev/null +++ b/php/src/Docker/GitHubContainerRegistryManager.php @@ -0,0 +1,62 @@ +guzzleClient = new Client(); + } + + public function GetLatestDigestOfTag(string $name, string $tag): ?string + { + $cacheKey = 'ghcr-manifest-' . $name . $tag; + + $cachedVersion = apcu_fetch($cacheKey); + if ($cachedVersion !== false && is_string($cachedVersion)) { + return $cachedVersion; + } + + // If one of the links below should ever become outdated, we can still upgrade the mastercontainer via the webinterface manually by opening '/api/docker/getwatchtower' + + try { + $authTokenRequest = $this->guzzleClient->request( + 'GET', + 'https://ghcr.io/token?scope=repository:' . $name . ':pull' + ); + $body = $authTokenRequest->getBody()->getContents(); + $decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); + if (isset($decodedBody['token'])) { + $authToken = $decodedBody['token']; + $manifestRequest = $this->guzzleClient->request( + 'HEAD', + 'https://ghcr.io/v2/' . $name . '/manifests/' . $tag, + [ + 'headers' => [ + 'Accept' => 'application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json', + 'Authorization' => 'Bearer ' . $authToken, + ], + ] + ); + $responseHeaders = $manifestRequest->getHeader('docker-content-digest'); + if (count($responseHeaders) === 1) { + $latestVersion = $responseHeaders[0]; + apcu_add($cacheKey, $latestVersion, 600); + return $latestVersion; + } + } + + error_log('Could not get digest of container ' . $name . ':' . $tag); + return null; + } catch (\Exception $e) { + error_log('Could not get digest of container ' . $name . ':' . $tag . ' ' . $e->getMessage()); + return null; + } + } +} diff --git a/php/src/Middleware/AuthMiddleware.php b/php/src/Middleware/AuthMiddleware.php index c0c814b8..724f1776 100644 --- a/php/src/Middleware/AuthMiddleware.php +++ b/php/src/Middleware/AuthMiddleware.php @@ -8,12 +8,10 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -class AuthMiddleware -{ - private AuthManager $authManager; - - public function __construct(AuthManager $authManager) { - $this->authManager = $authManager; +readonly class AuthMiddleware { + public function __construct( + private AuthManager $authManager + ) { } public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -29,7 +27,24 @@ 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 6df974ae..51334864 100644 --- a/php/src/Twig/CsrfExtension.php +++ b/php/src/Twig/CsrfExtension.php @@ -3,19 +3,16 @@ namespace AIO\Twig; use Slim\Csrf\Guard; +use Twig\Extension\AbstractExtension; +use Twig\Extension\GlobalsInterface; -class CsrfExtension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface -{ - /** - * @var Guard - */ - protected Guard $csrf; - - public function __construct(Guard $csrf) - { - $this->csrf = $csrf; +class CsrfExtension extends AbstractExtension implements GlobalsInterface { + public function __construct( + protected Guard $csrf + ) { } + #[\Override] public function getGlobals() : array { // CSRF token name and value @@ -35,4 +32,4 @@ class CsrfExtension extends \Twig\Extension\AbstractExtension implements \Twig\E ] ]; } -} \ No newline at end of file +} diff --git a/php/templates/already-installed.twig b/php/templates/already-installed.twig index 708128a8..e16e6792 100644 --- a/php/templates/already-installed.twig +++ b/php/templates/already-installed.twig @@ -1,5 +1,13 @@ {% extends "layout.twig" %} {% block body %} - Already installed. + {% endblock %} diff --git a/php/templates/components/container-state.twig b/php/templates/components/container-state.twig new file mode 100644 index 00000000..07580e66 --- /dev/null +++ b/php/templates/components/container-state.twig @@ -0,0 +1,27 @@ +{# @var c \App\Containers\Container #} +
  • + + {% if c.GetStartingState().value == 'starting' %} + + {{ c.displayName }} + (Starting) + {% elseif c.GetRunningState().value == 'running' %} + + {{ c.displayName }} + (Running) + {% else %} + + {{ c.displayName }} + (Stopped) + {% endif %} + {% if c.documentation != '' %} + (docs) + {% endif %} + + {% if c.GetUiSecret() != '' %} +
    + Show password for {{ c.displayName }} + +
    + {% endif %} +
  • \ No newline at end of file diff --git a/php/templates/containers.twig b/php/templates/containers.twig index bedfd481..8e437bc2 100644 --- a/php/templates/containers.twig +++ b/php/templates/containers.twig @@ -5,680 +5,639 @@
    -
    - -
    -
    + + - +
    -
    -

    Nextcloud AIO v9.4.1

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

    Nextcloud AIO v{{ aio_version }}

    - {# Add 2nd tab warning #} - + {# Add 2nd tab warning #} + - {# timezone-prefill #} - + {# timezone-prefill #} + - {% set isAnyRunning = false %} - {% set isAnyRestarting = false %} - {% set isWatchtowerRunning = false %} - {% set isDomaincheckRunning = false %} - {% 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 newMajorVersion = '' %} + {# js for optional containers and additional containers forms #} + - {% if is_backup_container_running == true %} - {% if borg_backup_mode == 'backup' or borg_backup_mode == 'restore' %} - {% set isBackupOrRestoreRunning = true %} - {% endif %} - {% endif %} - - {% for container in containers %} - {% if container.GetDisplayName() != '' and class(container.GetRunningState()) == 'AIO\\Container\\State\\RunningState' %} - {% set isAnyRunning = true %} - {% endif %} - {% if container.GetDisplayName() != '' and class(container.GetRestartingState()) == 'AIO\\Container\\State\\RestartingState' %} - {% set isAnyRestarting = true %} - {% endif %} - {% if container.GetIdentifier() == 'nextcloud-aio-watchtower' and class(container.GetRunningState()) == 'AIO\\Container\\State\\RunningState' %} - {% set isWatchtowerRunning = true %} - {% endif %} - {% if container.GetIdentifier() == 'nextcloud-aio-domaincheck' and class(container.GetRunningState()) == 'AIO\\Container\\State\\RunningState' %} - {% set isDomaincheckRunning = true %} - {% endif %} - {% if container.GetIdentifier() == 'nextcloud-aio-apache' and class(container.GetStartingState()) == 'AIO\\Container\\State\\StartingState' %} - {% set isApacheStarting = true %} - {% endif %} - {% endfor %} - - {% if is_daily_backup_running == true %} - Daily backup currently running. (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)

    - {% endif %} - {% endif %} - {% if has_update_available == false %} - The whole process should not take more than a few minutes.

    - {% elseif automatic_updates == true %} - The whole process can take a while as your containers will be updated.

    - {% endif %} - 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)

    - Reload ↻
    - {% else %} - {% if is_backup_container_running == false and domain == "" %} - {% if isDomaincheckRunning == false %} -

    Domaincheck container is not running

    - This is not expected. Most likely this happened because port {{ apache_port }} is already in use on your server. You can check the mastercontainer logs and domaincheck container logs for further clues. You should be able to resolve this by adjusting the APACHE_PORT by following the reverse proxy documentation. Advice: have a detailed look at the changed docker run command for AIO. - {% 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.

    -
    - - - -
    - {% else %} - {% if borg_backup_host_location == '' and borg_restore_password == '' %} - The official Nextcloud installation method. Nextcloud All-in-One provides easy deployment and maintenance with most features included in this one Nextcloud instance.

    - You can either create a new AIO instance or restore a former AIO instance from backup. See the two sections below.

    - {{ include('includes/aio-config.twig') }} -

    New AIO instance

    - {% if apache_port == '443' %} - AIO is currently in "normal mode" which means that it handles the TLS proxying itself. This also means that it cannot be installed behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else). If you want to run AIO behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else), see the reverse proxy documentation. Advice: have a detailed look at the changed docker run command for AIO.

    - {% else %} - AIO is currently in "reverse proxy mode" which means that it can be installed behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else) and does not do the TLS proxying itself.

    - {% endif %} - Please type the domain that will be used for Nextcloud below in order to create a new AIO instance.

    - {% 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 == false %} - Make sure that this server is reachable on port 443 (port 443/tcp is open/forwarded in your firewall/router and 443/udp as well if you want to enable http3) and that you've correctly set up the DNS config for the domain that you enter (set the A record to your public ipv4-address and if you need ipv6, set the AAAA record to your public ipv6-address. A CNAME record is, of course, also possible). You should see hints on what went wrong in the top right corner if your domain is not accepted.

    -
    - Click here for further hints
    - If you do not have a domain yet, you can get one for free e.g. from duckdns.org and others.

    - If you have a dynamic public IP-address, you can use e.g. DDclient with a compatible domain provider for DNS updates.

    - If you only want to install AIO locally without exposing it to the public internet or if you cannot do so, feel free to follow this documentation.

    - If you should be using Cloudflare Proxy for your domain, make sure to disable the Proxy feature temporarily as it might block the domain validation attempts.

    - {% if apache_port != '443' %} - If you run into issues with your domain being accepted, see these steps for how to debug things.

    - {% endif %} - Hint: If the domain validation fails but you are completely sure that you've configured everything correctly, you may skip the domain validation by following this documentation.
    -
    - {% endif %} - -

    Restore former AIO instance from backup

    - You can alternatively restore a former AIO instance from backup.

    - {% endif %} - - {% if is_instance_restore_attempt == false %} - {% if borg_backup_host_location != '' and borg_restore_password != '' %} - {% if borg_backup_mode in ['test', 'check'] %} - {% if backup_exit_code > 0 %} - 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' %} - The backup archive seems to be corrupt. Please try to use a different intact backup archive or 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.)

    -
    - - -
    -

    -

    - {% endif %} - {% elseif backup_exit_code == 0 %} - 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.

    -
    - - -
    -
    - {% 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!

    - Please note: If the backup that you want to restore contained any community container, but you did not specify the same community containers via environmental variable while creating this new AIO instance, you need to restore the same backup a second time after this attempt so that the community container data is also correctly restored.

    -
    - - - - -
    - {% endif %} - {% elseif borg_backup_mode == 'restore' %} - {% if backup_exit_code > 0 %} - Last restore failed! (Logs)

    - The restore process has unexpectedly failed! Please adjust the path and encryption password, test it and try to restore again! - {% endif %} - {% endif %} - {% endif %} - - {% if borg_backup_host_location == '' or borg_restore_password == '' or borg_backup_mode not in ['test', 'check', ''] or backup_exit_code > 0 %} - Please enter the location of the backup archive on your host and the encryption password of the backup archive below:

    -
    - - - - - -
    - {{ include('includes/backup-dirs.twig') }} - ⚠️ Please note that the backup archive must be located in a subfolder of the folder that you enter here and the subfolder which contains the archive must be named 'borg', or the backup container will not be able to find the backup archive!

    - {% endif %} - {% else %} - Everything set! Click on the button below to test the path and encryption password:

    -
    - - -
    -
    - {% endif %} - {% endif %} -

    How to reset the AIO instance?

    - If something should be going wrong, for example during the initial installation, you can reset the instance by following this documentation.

    - {% endif %} - - {% 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)

    - {% 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 %} + {% set hasBackupLocation = borg_backup_host_location or borg_remote_repo %} + {% set isAnyRunning = false %} + {% set isAnyRestarting = false %} + {% set isWatchtowerRunning = false %} + {% set isDomaincheckRunning = false %} + {% 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 = '' %} {% if is_backup_container_running == true %} - Backup container is currently running: {{ borg_backup_mode }} (Logs)

    - Reload ↻

    + {% if borg_backup_mode == 'backup' or borg_backup_mode == 'restore' %} + {% set isBackupOrRestoreRunning = true %} + {% endif %} {% endif %} - {% if domain != "" %} - {% if isAnyRunning == true %} - {% if isApacheStarting != true %} - {% if borg_backup_host_location != '' %} -
    - Click here to reveal the initial Nextcloud credentials
    - {% endif %} - Initial Nextcloud username: admin
    - Initial Nextcloud password: - {% if borg_backup_host_location != '' %} - {# nextcloud_password needs to be duplicated due to a bug in Firefox. See https://github.com/nextcloud/all-in-one/issues/638. #} - {{ nextcloud_password }}

    - {% else %} - {{ nextcloud_password }}

    - {% endif %} - Open your Nextcloud ↗

    - {% if borg_backup_host_location == '' %} - If your Nextcloud does not open when clicking the button above, see this documentation

    - {% endif %} - {% else %} - {% if isAnyRestarting == false %} - Containers are currently starting. You might inspect the container logs by clicking on Starting next to each container for further details.

    - Reload ↻

    - {% 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.

    -
    - - - -
    - {% endif %} + {% for container in containers %} + {% if container.displayName != '' and container.GetRunningState().value == 'running' %} + {% set isAnyRunning = true %} + {% endif %} + {% if container.displayName != '' and container.GetRestartingState().value == 'restarting' %} + {% set isAnyRestarting = true %} + {% endif %} + {% if container.identifier == 'nextcloud-aio-watchtower' and container.GetRunningState().value == 'running' %} + {% set isWatchtowerRunning = true %} + {% endif %} + {% if container.identifier == 'nextcloud-aio-domaincheck' and container.GetRunningState().value == 'running' %} + {% set isDomaincheckRunning = true %} + {% endif %} + {% 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)

    + {% 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)

    {% endif %} {% endif %} - - {% if isApacheStarting == false and is_backup_container_running == false %} - {{ include('includes/aio-config.twig') }} + {% if has_update_available == false %} +

    The whole process should not take more than a few minutes.

    + {% elseif automatic_updates == true %} +

    The whole process can take a while as your containers will be updated.

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

    Containers

    -
      - {# @var containers \AIO\Container\Container[] #} - {% for container in containers %} - {% if container.GetDisplayName() != '' %} -
    • - {% if class(container.GetStartingState()) == 'AIO\\Container\\State\\StartingState' %} - - {{ container.GetDisplayName() }} (Starting) - {% if container.GetDocumentation() != '' %} - (docs) - {% endif %} - - {% elseif class(container.GetRunningState()) == 'AIO\\Container\\State\\RunningState' %} - - {{ container.GetDisplayName() }} (Running) - {% if container.GetDocumentation() != '' %} - (docs) - {% endif %} - - {% else %} - - {{ container.GetDisplayName() }} (Stopped) - {% if container.GetDocumentation() != '' %} - (docs) - {% endif %} - - {% endif %} -
    • - {% endif %} - {% endfor %} -
    - - {% if has_update_available == true %} - {% if is_mastercontainer_update_available == false %} - ⚠️ Container updates are available. Click on Stop containers and Start and update containers to update them. You should consider creating a backup first.

    - {% endif %} - {% else %} - {% if is_mastercontainer_update_available == false %} - Your containers are up-to-date.

    - {% if newMajorVersion != '' and isAnyRunning == true and isApacheStarting != true %} -
    - Note about Nextcloud {{ newMajorVersion }}
    - If you haven't upgraded to Nextcloud {{ newMajorVersion }} yet and want to do that now, feel free to follow this documentation
    -

    - {% endif %} - {% endif %} - {% endif %} - {% endif %} - - {% if isAnyRunning == true %} - {% if isApacheStarting != true %} - {% if is_mastercontainer_update_available == true %} - ⚠️ A mastercontainer update is available. Please click on the button below to stop your containers in order to update the mastercontainer.

    - {% if current_channel starts with 'latest' %} - You can find the changelog here

    - {% elseif current_channel starts with 'beta' %} - You can find the changelog here

    - {% elseif current_channel starts with 'develop' %} - You can find all changes here

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

    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)

    +

    Reload ↻

    + {% else %} + {% if is_backup_container_running == false and domain == "" %} + {% if isDomaincheckRunning == false %} +

    Domaincheck container is not running

    +

    This is not expected. Most likely this happened because port {{ apache_port }} is already in use on your server. You can check the mastercontainer logs and domaincheck container logs for further clues. You should be able to resolve this by adjusting the APACHE_PORT by following the reverse proxy documentation. Advice: have a detailed look at the changed docker run command for AIO.

    + {% 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.

    + - +
    - {% endif %} - {% else %} - {% if isBackupOrRestoreRunning == true %} - Restore or Backup currently running. Cannot start the containers until Restore or Backup is complete.

    {% else %} - {% if was_start_button_clicked == false %} -
    Clicking on the button below will download all docker containers and start them. This can take a long time depending on your internet connection. Since the overall size is a few GB, this can take around 5-10 min or more. Please be patient!

    - {% 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 newMajorVersion != '' %} -
    - {% endif %} - -
    - {% elseif has_update_available == false %} -
    - - - -
    + {% if not hasBackupLocation %} +

    The official Nextcloud installation method. Nextcloud All-in-One provides easy deployment and maintenance with most features included in this one Nextcloud instance.

    +

    You can either create a new AIO instance or restore a former AIO instance from backup. See the two sections below.

    + {{ include('includes/aio-config.twig') }} +

    New AIO instance

    + {% if apache_port == '443' %} +

    AIO is currently in "normal mode" which means that it handles the TLS proxying itself. This also means that it cannot be installed behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else). If you want to run AIO behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else), see the reverse proxy documentation. Advice: have a detailed look at the changed docker run command for AIO.

    {% else %} -
    - - - -
    +

    AIO is currently in "reverse proxy mode" which means that it can be installed behind a web server or reverse proxy (like Apache, Nginx, Caddy, Cloudflare Tunnel and else) and does not do the TLS proxying itself.

    {% endif %} - {% endif %} - {% endif %} - {% endif %} - - {% if was_start_button_clicked == true %} - - {% if is_backup_section_enabled == false %} -

    Backup and restore

    - The backup section is disabled via environmental variable.

    - {% else %} - {% if is_backup_container_running == false and borg_backup_host_location == "" and isApacheStarting != true %} -

    Backup and restore

    - Please enter the directory path below where backups will be created on the host system. It's best to choose a location on a separate drive and not on your root drive.

    -
    - +

    Please type in the domain that will be used for Nextcloud and submit it.

    + {% 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 %} +
    - {{ include('includes/backup-dirs.twig') }} + {% if skip_domain_validation == false %} +

    Make sure that this server is reachable on port 443 (port 443/tcp is open/forwarded in your firewall/router and 443/udp as well if you want to enable http3) and that you've correctly set up the DNS config for the domain that you enter (set the A record to your public ipv4-address and if you need ipv6, set the AAAA record to your public ipv6-address. A CNAME record is, of course, also possible). You should see hints on what went wrong in the top right corner if your domain is not accepted.

    +
    + Click here for further hints +

    If you do not have a domain yet, you can get one for free e.g. from duckdns.org and others. Recommended is to use Tailscale

    +

    If you have a dynamic public IP-address, you can use e.g. DDclient with a compatible domain provider for DNS updates.

    +

    If you only want to install AIO locally without exposing it to the public internet or if you cannot do so, feel free to follow this documentation.

    +

    If you should be using Cloudflare Proxy for your domain, make sure to disable the Proxy feature temporarily as it might block the domain validation attempts.

    + {% if apache_port != '443' %} +

    If you run into issues with your domain being accepted, see these steps for how to debug things.

    + {% endif %} +

    Hint: If the domain validation fails but you are completely sure that you've configured everything correctly, you may skip the domain validation by following this documentation.

    +
    + {% endif %} + +

    Restore former AIO instance from backup

    +

    You can alternatively restore a former AIO instance from backup.

    {% endif %} - {% endif %} - {% if is_backup_section_enabled == true %} - - {% if borg_backup_host_location != "" %} - {% if is_backup_container_running == false %} -

    Backup and restore

    - {% if backup_exit_code > 0 %} - 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.)

    -
    + {% if is_instance_restore_attempt == false %} + {% if hasBackupLocation %} + {% if borg_backup_mode in ['test', 'check'] %} + {% if backup_exit_code > 0 %} +

    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' %} +

    The backup archive seems to be corrupt. Please try to use a different intact backup archive or 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.)

    + + + + + +
    + {% endif %} + {% elseif backup_exit_code == 0 %} +

    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.

    +
    -
    -

    -

    - {% endif %} - {% if has_backup_run_once == false %} - 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.

    -
    - - - - -
    - {% endif %} - {% elseif backup_exit_code == 0 %} - {% if borg_backup_mode == "backup" %} - Last {{ borg_backup_mode }} successful on {{ last_backup_time }} UTC! (Logs)

    - {% else %} - Last {{ borg_backup_mode }} successful! (Logs)

    - {% endif %} - {% endif %} - {% endif %} - - {% if is_backup_container_running == false and isApacheStarting == false %} - {% if has_backup_run_once == true %} -
    - Click here to reveal all backup options (including an option for automatic updates)
    - {% endif %} -

    Backup information

    - This is your encryption password for backups: {{ borgbackup_password }}

    - Please save this password in a safe place. You won't be able to restore from backup if you lose this password!

    - All important data from your Nextcloud AIO instance such as the database, your files and the mastercontainer's configuration files, will be backed up.

    - The backup uses a tool called BorgBackup, a well-known server backup tool that efficiently backs up your files and encrypts them on the fly.

    - By using this tool, backups are incremental, differential, compressed and encrypted – so only the first backup will take a while. Further backups should be fast as only changes are taken into account.

    - Backups will be created in the following directory on the host: {{ borg_backup_host_location }}/borg

    - Be aware that this solution does not backup files and folders that are mounted into Nextcloud using the external storage app, but you can add further Docker volumes and host paths that you want to back up after the initial backup is done.

    - For information about backup retention, see this.

    - Daily backups can be enabled after the initial backup is done. Enabling this also allows you to enable an option to update all containers, Nextcloud, and its apps automatically.

    - For further documentation and options on this backup solution refer to this section and below.
    - - {% if isApacheStarting != true %} -

    Backup creation

    - Clicking on the button below will create a backup.

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

    Reset backup host location

    - If the configured backup host location {{ borg_backup_host_location }} is wrong, you can reset it by clicking on the button below.

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

    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.

    -
    - - -
    -
    - -

    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.

    -
    + +
    + {% 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.

    +
    - +
    +
    +
    - -

    Daily backup and automatic updates

    - {% if daily_backup_time == "" %} - By entering a time below, 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.

    -
    - - - -
    -
    -
    -
    - {% else %} - Daily backups will be created at {{ daily_backup_time }} UTC. A notification about the result of the backup will be sent. - {% if automatic_updates == true %} - 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.

    -
    - - - - -
    - {% endif %} - -

    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.

    -
    - - - -
    -
    - Each line and entry needs to start with a slash or letter/digit. Only a-z, A-Z, ., 0-9, _, -, and / are allowed. If the entry begins with a letter/digit slashes are not supported. Two valid entries are /directory/on/the/host and my_custom_docker_volume. You need to make sure that all given directories exist or the backup container will fail to start!

    - Be sure to individually specify all storage that you want to back up as storage will not be mounted recursively. E.g. providing / as additional backup directory will only back up files and folders that are stored on the root partition and not on the EFI partition or any other. Excluded by the backup will be caches and a few other directories. If you want to back up the root partition you should make sure to stop all services before the backup so it can run correctly. For automating this see this documentation

    - Please note that the chosen directories/volumes will not be restored when you restore your instance, so this would need to be done manually.

    - {% if additional_backup_directories != "" %} - This option is currently set. You can disable it again by clearing the field and submitting your changes.

    - {% endif %} + {% endif %} + {% elseif borg_backup_mode == 'restore' %} + {% if backup_exit_code > 0 %} +

    Last restore failed! (Logs)

    +

    The restore process has unexpectedly failed! Please adjust the path and encryption password, test it and try to restore again!

    {% endif %} {% endif %} - {% if has_backup_run_once == false %} -
    - {% else %} + {% endif %} + + {% if not hasBackupLocation or borg_backup_mode not in ['test', 'check', ''] or backup_exit_code > 0 %} + {% if borg_remote_repo and backup_exit_code > 0 %} +

    + You may still need to authorize this pubkey on your borg remote:
    {{ borg_public_key }}
    + To try again, resubmit your location and rerun the test. +

    + {% endif %} + +

    + Please enter the location of the backup archive on your host or a + remote borg repo url + if stored remotely; and the encryption password of the backup archive below and submit all values: +

    +
    +
    +
    +
    + + + +
    + {{ include('includes/backup-dirs.twig') }} +

    ⚠️ Please note that the backup archive must be located in a subfolder of the folder that you enter here and the subfolder which contains the archive must be named 'borg', or the backup container will not be able to find the backup archive!

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

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

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

    How to reset the AIO instance?

    +

    If something should be going wrong, for example during the initial installation, you can reset the instance by following this documentation.

    + {% endif %} + + {% 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)

    + {% 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)

    +

    Reload ↻

    + {% endif %} + + {% if domain != "" %} + {% if isAnyRunning == true %} + {% if isApacheStarting != true %} + {% if hasBackupLocation %} +
    + Click here to reveal the initial Nextcloud credentials + {% endif %} +

    Initial Nextcloud username: admin

    + {% if hasBackupLocation %} + {# nextcloud_password needs to be duplicated due to a bug in Firefox. See https://github.com/nextcloud/all-in-one/issues/638. #} +

    Initial Nextcloud password: {{ nextcloud_password }}

    + {% else %} +

    Initial Nextcloud password: {{ nextcloud_password }}

    + {% endif %} +

    Open your Nextcloud ↗

    + {% if not hasBackupLocation %} +

    If your Nextcloud does not open when clicking the button above, see this documentation

    + {% endif %} + {% else %} + {% if isAnyRestarting == false %} +

    Containers are currently starting. You might inspect the container logs by clicking on Starting next to each container for further details.

    +

    Reload ↻

    + {% 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.

    +
    + + + +
    + {% endif %} + {% endif %} + {% endif %} + + {% if isApacheStarting == false and is_backup_container_running == false %} + {{ include('includes/aio-config.twig') }} + {% endif %} + + {% if was_start_button_clicked == true %} +

    Containers

    +
      + {# @var containers \AIO\Container\Container[] #} + {% for container in containers %} + {% if container.displayName != '' %} + {% include 'components/container-state.twig' with {'c': container} only %} + {% endif %} + {% endfor %} +
    + + {% if has_update_available == true %} + {% if is_mastercontainer_update_available == false %} +

    ⚠️ Container updates are available. Click on Stop containers and Start and update containers to update them. You should consider creating a backup first.

    + {% endif %} + {% else %} + {% if is_mastercontainer_update_available == false %} +

    Your containers are up-to-date.

    + {% if newMajorVersionString != '' and isAnyRunning == true and isApacheStarting != true %} +
    + Note about Nextcloud Hub {{ newMajorVersionString }} +

    If you haven't upgraded to Nextcloud Hub {{ newMajorVersionString }} yet and want to do that now, feel free to follow this documentation

    {% endif %} {% endif %} {% endif %} {% endif %} - {% if is_backup_container_running == false %} - {% if isApacheStarting == false %} -

    AIO passphrase change

    -
    - Click here to change your AIO passphrase
    - You can change your AIO passphrase below:

    -
    - - + {% if isAnyRunning == true %} + {% if isApacheStarting != true %} + {% if is_mastercontainer_update_available == true %} +

    ⚠️ A mastercontainer update is available. Please click on the button below to stop your containers in order to update the mastercontainer.

    + {% if current_channel starts with 'latest' %} +

    You can find the changelog here

    + {% elseif current_channel starts with 'beta' %} +

    You can find the changelog here

    + {% elseif current_channel starts with 'develop' %} +

    You can find all changes here

    + {% endif %} + {% endif %} + + + + +
    + {% endif %} + {% else %} + {% if isBackupOrRestoreRunning == true %} +

    Restore or Backup currently running. Cannot start the containers until Restore or Backup is complete.

    + {% else %} + {% if was_start_button_clicked == false %} +

    Clicking on the button below will download all docker containers and start them. This can take a long time depending on your internet connection. Since the overall size is a few GB, this can take around 5-10 min or more. Please be patient!

    + {% endif %} + {% if is_mastercontainer_update_available == true %} +

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

    +
    - +
    - The new passphrase needs to be at least 24 characters long. Allowed characters are the latin characters a-z, A-Z, 0-9 and spaces.
    -
    - {% endif %} - {% endif %} - {% endif %} - {% if is_backup_container_running == false %} -

    Optional containers

    - In this section you can enable or disable optional containers. There are further community containers available that are not listed below. See this documentation how to add them.

    - {% if isAnyRunning == true %} - Please note: You can enable or disable the options below only when your containers are stopped.

    - {% 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 %} -
    - - - - {% if is_clamav_enabled == true %} -

    - {% else %} -

    - {% endif %} - {% if is_collabora_enabled == true %} -
    - {% else %} -
    - {% endif %} - {% if is_fulltextsearch_enabled == true %} -
    - {% else %} -

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

    - {% else %} -

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

    - {% else %} -

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

    - {% else %} -

    - {% endif %} - {% if is_onlyoffice_enabled == true %} -
    - {% else %} - {#
    #} - {% endif %} - {% if is_docker_socket_proxy_enabled == true %} -

    - {% else %} -

    - {% endif %} - - -
    - 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 advices and recommendations see this documentation
    - {% if isAnyRunning == true or is_x64_platform == false %} - - {% endif %} - {% if isAnyRunning == true %} - - - - - - - - {% endif %} - - {% if is_collabora_enabled == true and isAnyRunning == false and was_start_button_clicked == true %} -

    Collabora dictionaries

    - - {% if collabora_dictionaries == "" %} - In order to get the correct dictionaries in Collabora, 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.

    -
    - - - - -
    + {% else %} + {% if was_start_button_clicked == false %} +
    + + + + {% if newMajorVersionString != '' %} +
    + {% endif %} + +
    + {% elseif has_update_available == false %} +
    + + + + +
    + {% else %} +
    + + + + {% if bypass_container_update == true %} + + {% endif %} + +
    + {% endif %} + {% endif %} {% endif %} {% endif %} -

    Timezone change

    - {% if isAnyRunning == true %} - {% if timezone != "" %} - The timezone for Nextcloud is currently set to {{ timezone }}.

    - {% endif %} - Please note: You can change the timezone when your containers are stopped.

    - {% else %} - {% 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:

    -
    - - - - -
    - 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.

    + {% if was_start_button_clicked == true %} + + {% if is_backup_section_enabled == false %} +

    Backup and restore

    +

    The backup section is disabled via environmental variable.

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

    -
    - - - - -
    + {% if is_backup_container_running == false and not hasBackupLocation and isApacheStarting != true %} +

    Backup and restore

    +

    Please enter the directory path below where backups will be created on the host system and submit it. It's best to choose a location on a separate drive and not on your root drive.

    +

    + To store backups remotely instead, fill in the + remote borg repo url and submit it. + You will be provided with an SSH public key for authorization at the remote afterwards. +

    +
    +
    +
    + + + +
    + {{ include('includes/backup-dirs.twig') }} + {% endif %} {% endif %} + + {% if is_backup_section_enabled == true %} + + {% if hasBackupLocation %} + {% if is_backup_container_running == false %} +

    Backup and restore

    + {% if backup_exit_code > 0 %} +

    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.)

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

    The initial backup was not successful.

    + + {% if borg_remote_repo %} +

    + You may still need to authorize this pubkey on your borg remote:
    {{ borg_public_key }}
    + To try again, click Create backup. +

    + {% 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.

    +
    +
    +
    + + + +
    + {% endif %} + {% elseif backup_exit_code == 0 %} + {% if borg_backup_mode == "backup" %} +

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

    + {% else %} +

    Last {{ borg_backup_mode }} successful! (Logs)

    + {% endif %} + {% endif %} + {% endif %} + + {% if is_backup_container_running == false and isApacheStarting == false %} + {% if has_backup_run_once == true %} +
    + Click here to reveal all backup options (including an option for automatic updates) + {% endif %} +

    Backup information

    +

    This is your encryption password for backups: {{ borgbackup_password }}

    +

    Please save this password in a safe place. You won't be able to restore from backup if you lose this password!

    +

    All important data from your Nextcloud AIO instance such as the database, your files and the mastercontainer's configuration files, will be backed up.

    +

    The backup uses a tool called BorgBackup, a well-known server backup tool that efficiently backs up your files and encrypts them on the fly.

    +

    By using this tool, backups are incremental, differential, compressed and encrypted – so only the first backup will take a while. Further backups should be fast as only changes are taken into account.

    + {% if borg_remote_repo != '' %} +

    + Backups get created remotely at:
    + {{ borg_remote_repo }} + {% if has_backup_run_once == true %} +
    Your borg ssh public key is:
    {{ borg_public_key }} + {% endif %} +

    + {% else %} +

    Backups will be created in the following directory on the host: {{ borg_backup_host_location }}/borg

    + {% endif %} +

    Be aware that this solution does not backup files and folders that are mounted into Nextcloud using the external storage app, but you can add further Docker volumes and host paths that you want to back up after the initial backup is done.

    +

    For information about backup retention, see this.

    +

    Daily backups can be enabled after the initial backup is done. Enabling this also allows you to enable an option to update all containers, Nextcloud, and its apps automatically.

    +

    For further documentation and options on this backup solution refer to this section and below.

    + + {% if isApacheStarting != true %} +

    Backup creation

    +

    Clicking on the button below will create a backup.

    +
    + + + +
    + + {% 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.

    +
    + + + +
    + +

    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.

    +
    + + + +
    +
    + +
    + {% else %} +

    Daily backups will be created at {{ daily_backup_time }} UTC. A notification about the result of the backup will be sent.

    + {% if automatic_updates == true %} + 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.

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

    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.

    +
    + + + + +
    +

    Each line and entry needs to start with a slash or letter/digit. Only a-z, A-Z, ., 0-9, _, -, and / are allowed. If the entry begins with a letter/digit slashes are not supported. Two valid entries are /directory/on/the/host and my_custom_docker_volume. You need to make sure that all given directories exist or the backup container will fail to start!

    +

    Be sure to individually specify all storage that you want to back up as storage will not be mounted recursively. E.g. providing / as additional backup directory will only back up files and folders that are stored on the root partition and not on the EFI partition or any other. Excluded by the backup will be caches and a few other directories. If you want to back up the root partition you should make sure to stop all services before the backup so it can run correctly. For automating this see this documentation

    +

    Please note that the chosen directories/volumes will not be restored when you restore your instance, so this would need to be done manually.

    + {% if additional_backup_directories != "" %} +

    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 %} +
    + {% endif %} + {% endif %} + {% endif %} + {% endif %} + + {% if is_backup_container_running == false %} + {% if isApacheStarting == false %} +

    AIO passphrase change

    +
    + Click here to change your AIO passphrase +

    You can change your AIO passphrase below:

    +
    + + + + + +
    +

    The new passphrase needs to be at least 24 characters long. Allowed characters are the latin characters a-z, A-Z, 0-9 and spaces.

    +
    + {% endif %} + {% endif %} + {% endif %} + {% if is_backup_container_running == false %} + + {{ include('includes/optional-containers.twig') }} + +

    Timezone change

    + {% if isAnyRunning == true %} + {% if timezone != "" %} +

    The timezone for Nextcloud is currently set to {{ timezone }}.

    + {% endif %} +

    Please note: You can change the timezone when your containers are stopped.

    + {% else %} + {% 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!):

    +
    + + + + +
    +

    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.

    +
    + + + + +
    + {% endif %} + {% endif %} + {{ include('includes/community-containers.twig') }} {% endif %} {% endif %} {% endif %} + + {% if isApacheStarting == true or is_backup_container_running == true or isWatchtowerRunning == true or is_daily_backup_running == true %} + + {% else %} + {% endif %} - {% if isApacheStarting == true or is_backup_container_running == true or isWatchtowerRunning == true or is_daily_backup_running == true %} - - {% else %} - - {% endif %} + -
    -
    -
    +
    {% endblock %} diff --git a/php/templates/includes/aio-config.twig b/php/templates/includes/aio-config.twig index e3c34d62..fbb70230 100644 --- a/php/templates/includes/aio-config.twig +++ b/php/templates/includes/aio-config.twig @@ -1,36 +1,44 @@
    - Click here to view the current AIO config and documentation links
    + Click here to view the current AIO config and documentation links {% if was_start_button_clicked == true %} - Nextclouds config.php file is stored in the nextcloud_aio_nextcloud Docker volume and can be edited by following the config.php documentation.

    - You can run Nextcloud's usual occ commands by following the occ documentation.

    +

    Nextcloud's config.php file is stored in the nextcloud_aio_nextcloud Docker volume and can be edited by following the config.php documentation.

    +

    You can run Nextcloud's usual occ commands by following the occ documentation.

    {% endif %} - - {% if nextcloud_datadir starts with '/' %} - Nextcloud's datadir is getting stored in the {{ nextcloud_datadir }} directory. - {% else %} - Nextcloud's datadir is getting stored in the {{ nextcloud_datadir }} Docker volume. - {% endif %} - See the NEXTCLOUD_DATADIR documentation on how to change this.

    - {% if nextcloud_mount == '' %} - The Nextcloud container is confied and local external storage in Nextcloud is disabled. - {% else %} - The Nextcloud container is getting gets access to the {{ nextcloud_mount }} directory and local external storage in Nextcloud is enabled. - {% endif %} - See the NEXTCLOUD_MOUNT documentation on how to change this.

    +

    + {% if nextcloud_datadir starts with '/' %} + Nextcloud's datadir is getting stored in the {{ nextcloud_datadir }} directory. + {% else %} + Nextcloud's datadir is getting stored in the {{ nextcloud_datadir }} Docker volume. + {% endif %} + See the NEXTCLOUD_DATADIR documentation on how to change this. +

    - Nextcloud has an upload limit of {{ nextcloud_upload_limit }} configured (for public link uploads. Bigger uploads are always possible when users are logged in). See the NEXTCLOUD_UPLOAD_LIMIT documentation on how to change this.

    +

    + {% if nextcloud_mount == '' %} + The Nextcloud container is confined and local external storage in Nextcloud is disabled. + {% else %} + The Nextcloud container is getting access to the {{ nextcloud_mount }} directory and local external storage in Nextcloud is enabled. + {% endif %} + See the NEXTCLOUD_MOUNT documentation on how to change this.

    - For Nextcloud, a memory limit of {{ nextcloud_memory_limit }} per PHP process is configured. See the NEXTCLOUD_MEMORY_LIMIT documentation on how to change this.

    +

    Nextcloud has an upload limit of {{ nextcloud_upload_limit }} configured (for public link uploads. Bigger uploads are always possible when users are logged in). See the NEXTCLOUD_UPLOAD_LIMIT documentation on how to change this.

    - Nextcloud has a timeout of {{ nextcloud_max_time }} seconds configured (important for big file uploads). See the NEXTCLOUD_MAX_TIME documentation on how to change this.

    +

    For Nextcloud, a memory limit of {{ nextcloud_memory_limit }} per PHP process is configured. See the NEXTCLOUD_MEMORY_LIMIT documentation on how to change this.

    - {% if is_dri_device_enabled == true %} - The /dev/dri device which is needed for hardware transcoding is getting attached to the Nextcloud container. - {% else %} - The /dev/dri device which is needed for hardware transcoding is not attached to the Nextcloud container. - {% endif %} - See the NEXTCLOUD_ENABLE_DRI_DEVICE documentation on how to change this.

    +

    Nextcloud has a timeout of {{ nextcloud_max_time }} seconds configured (important for big file uploads). See the NEXTCLOUD_MAX_TIME documentation on how to change this.

    - For further documentation on AIO, refer to this page. You can use the browser search [CTRL]+[F] to search through the documentation. Additional documentation can be found here.
    +

    + {% if is_dri_device_enabled == true and is_nvidia_gpu_enabled == true %} + Hardware acceleration is enabled with the /dev/dri device and the Nvidia runtime. + {% elseif is_dri_device_enabled == true %} + Hardware acceleration is enabled with the /dev/dri device. + {% elseif is_nvidia_gpu_enabled == true %} + Hardware acceleration is enabled with the Nvidia runtime. + {% else %} + Hardware acceleration is not enabled. It's recommended to enable hardware transcoding for better performance. + {% endif %} + See the hardware acceleration documentation on how to change this.

    + +

    For further documentation on AIO, refer to this page. You can use the browser search [CTRL]+[F] to search through the documentation. Additional documentation can be found here.

    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/backup-dirs.twig b/php/templates/includes/backup-dirs.twig index bbc11eb8..390bf69c 100644 --- a/php/templates/includes/backup-dirs.twig +++ b/php/templates/includes/backup-dirs.twig @@ -1,6 +1,6 @@ -The folder path that you enter must start with / and must not end with /.

    -An example for Linux is /mnt/backup.

    -On Synology it could be /volume1/docker/nextcloud/backup.

    -For macOS it may be /var/backup.

    -On Windows it might be /run/desktop/mnt/host/c/backup. (This path is equivalent to 'C:\backup' on your Windows host so you need to translate the path accordingly. Hint: the path that you enter needs to start with '/run/desktop/mnt/host/'. Append to that the exact location on your windows host, e.g. 'c/backup' which is equivalent to 'C:\backup'.) ⚠️ Please note: This does not work with external drives like USB or network drives and only with internal drives like SATA or NVME drives.

    -Another option is to enter a specific volume name here: nextcloud_aio_backupdir. This volume needs to be created beforehand manually by you in order to be able to use it. See this documentation for an example.

    +

    The folder path that you enter must start with / and must not end with /.

    +

    An example for Linux is /mnt/backup.

    +

    On Synology it could be /volume1/docker/nextcloud/backup.

    +

    For macOS it may be /var/backup.

    +

    On Windows it might be /run/desktop/mnt/host/c/backup. (This path is equivalent to 'C:\backup' on your Windows host so you need to translate the path accordingly. Hint: the path that you enter needs to start with '/run/desktop/mnt/host/'. Append to that the exact location on your windows host, e.g. 'c/backup' which is equivalent to 'C:\backup'.) ⚠️ Please note: This does not work with external drives like USB or network drives and only with internal drives like SATA or NVME drives.

    +

    Another option is to enter a specific volume name here: nextcloud_aio_backupdir. This volume needs to be created beforehand manually by you in order to be able to use it. See this documentation for an example.

    diff --git a/php/templates/includes/community-containers.twig b/php/templates/includes/community-containers.twig new file mode 100644 index 00000000..66cceb2b --- /dev/null +++ b/php/templates/includes/community-containers.twig @@ -0,0 +1,42 @@ +

    Community Containers

    +

    In this section you can enable or disable optional Community Containers that are not included by default in the main installation. These containers are provided by the community and can be useful for various purposes and are automatically integrated in AIOs backup solution and update mechanisms.

    +

    ⚠️ Caution: Community Containers are maintained by the community and not officially by Nextcloud. Some containers may not be compatible with your system, may not work as expected or may discontinue. Use them at your own risk. Please read the documentation for each container first before adding any as some are also incompatible between each other! Never add all of them at the same time!

    +{% if isAnyRunning == true %} +

    Please note: You can enable or disable the options below only when your containers are stopped.

    +{% else %} +

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

    +{% endif %} +
    + Show/Hide available Community Containers +
    + + + + {% for cc in community_containers %} +

    + + +

    + {% endfor %} + + +
    +
    diff --git a/php/templates/includes/optional-containers.twig b/php/templates/includes/optional-containers.twig new file mode 100644 index 00000000..eabcb139 --- /dev/null +++ b/php/templates/includes/optional-containers.twig @@ -0,0 +1,273 @@ +

    Optional containers

    +

    In this section you can enable or disable optional containers.

    +{% if isAnyRunning == true %} +

    Please note: You can enable or disable the options below only when your containers are stopped.

    +{% 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 %} + + + + + + + + + +{% endif %} + +{% if is_collabora_enabled == true and isAnyRunning == false and was_start_button_clicked == true %} +

    Nextcloud Office dictionaries

    + + {% if collabora_dictionaries == "" %} +

    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 Nextcloud Office are currently set to {{ collabora_dictionaries }}. You can reset them again by clicking on the button below.

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

    Additional Nextcloud Office options

    + + {% if collabora_additional_options == "" %} +

    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 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 e4d5c4ca..79c615d9 100644 --- a/php/templates/layout.twig +++ b/php/templates/layout.twig @@ -1,12 +1,21 @@ AIO - - + + + +
    {% block body %}{% endblock %} +
    +
    +
    +
    + diff --git a/php/templates/login.twig b/php/templates/login.twig index 34287829..1c5420c2 100644 --- a/php/templates/login.twig +++ b/php/templates/login.twig @@ -1,28 +1,25 @@ -{% extends "layout.twig" %} - -{% block body %} - - -
    -
    -
    -{% endblock %} - +{% extends "layout.twig" %} + +{% block body %} + + +{% endblock %} diff --git a/php/templates/setup.twig b/php/templates/setup.twig index 1069980c..7cc9227a 100644 --- a/php/templates/setup.twig +++ b/php/templates/setup.twig @@ -1,14 +1,16 @@ {% extends "layout.twig" %} {% block body %} -