diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..2ec8e4a955 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[templates/*.templ] +indent_style = tab + +[Dockerfile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..5505d7c4a7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +templates/*.templ linguist-language=Dockerfile diff --git a/.github/workflows/automatic-issue-label.yml b/.github/workflows/automatic-issue-label.yml new file mode 100644 index 0000000000..f6056649b4 --- /dev/null +++ b/.github/workflows/automatic-issue-label.yml @@ -0,0 +1,19 @@ +name: Label new issues +on: + issues: + types: + - reopened + - opened + +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: "0. to triage" diff --git a/.github/workflows/bot-create-manual-reminder.yml b/.github/workflows/bot-create-manual-reminder.yml new file mode 100644 index 0000000000..afd30668de --- /dev/null +++ b/.github/workflows/bot-create-manual-reminder.yml @@ -0,0 +1,18 @@ +name: 'Create reminder from comment' + +permissions: + issues: write + pull-requests: write + +on: + issue_comment: + types: [created, edited] + +jobs: + reminder: + if: github.repository == 'roundcube/roundcubemail-docker' + runs-on: ubuntu-latest + + steps: + - name: 👀 check for reminder + uses: agrc/create-reminder-action@9ff30cde74284045941af16a04362938957253b1 # v1.1.17 diff --git a/.github/workflows/bot-manual-reminder.yml b/.github/workflows/bot-manual-reminder.yml new file mode 100644 index 0000000000..e650cf17a9 --- /dev/null +++ b/.github/workflows/bot-manual-reminder.yml @@ -0,0 +1,18 @@ +name: 'Notify manually requested reminders' + +on: + schedule: + - cron: '0 * * * *' + +permissions: + issues: write + pull-requests: write + +jobs: + reminder: + if: github.repository == 'roundcube/roundcubemail-docker' + runs-on: ubuntu-latest + + steps: + - name: check reminders and notify + uses: agrc/reminder-action@96f2ec2e1a7a53ead156504922e9bc36d64f49ee # v1.0.16 diff --git a/.github/workflows/bot-remind-stale-pull-requests.yml b/.github/workflows/bot-remind-stale-pull-requests.yml new file mode 100644 index 0000000000..4592e3e750 --- /dev/null +++ b/.github/workflows/bot-remind-stale-pull-requests.yml @@ -0,0 +1,20 @@ +name: "Send comment to stale PRs" +on: + schedule: + # Run everyday at midnight + - cron: "0 0 * * *" + +jobs: + review-reminder: + if: github.repository == 'roundcube/roundcubemail-docker' + runs-on: ubuntu-latest + steps: + - uses: sojusan/github-action-reminder@85a7d4ea6d5535e88e47baa242918a6a654de65d # v1.1.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + reminder_message: "🛎️ This PR has had no activity in two weeks." + # Remind after two weeks of inactivity + inactivity_deadline_hours: 336 + default_users_to_notify: | + @pabzm + @thomascube diff --git a/.github/workflows/build-1.5.yml b/.github/workflows/build-1.5.yml new file mode 100644 index 0000000000..4bd2d3c333 --- /dev/null +++ b/.github/workflows/build-1.5.yml @@ -0,0 +1,91 @@ +name: Build & Publish 1.5.x + +permissions: + contents: read + +on: + push: + branches: + - 'master' + paths: + - 'apache-1.5.x/**' + - 'fpm-1.5.x/**' + - 'fpm-alpine-1.5.x/**' + - '.github/workflows/*-1.5.yml' + + schedule: + # Rebuild images each monday early morning to ensure a fresh base OS. + - cron: "23 2 * * 1" + workflow_dispatch: + +jobs: + build-and-testvariants: + name: Build image variants and run tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 10 + matrix: + include: + - variant: 'apache-1.5.x' + test-files: 'apache-postgres' + docker-tag: roundcube/roundcubemail:1.5.x-apache,roundcube/roundcubemail:1.5.11-apache + test-tag: roundcube/roundcubemail:latest-apache + - variant: 'fpm-1.5.x' + test-files: 'fpm-postgres' + docker-tag: roundcube/roundcubemail:1.5.x-fpm,roundcube/roundcubemail:1.5.11-fpm + test-tag: roundcube/roundcubemail:latest-fpm + - variant: 'fpm-alpine-1.5.x' + test-files: 'fpm-postgres' + docker-tag: roundcube/roundcubemail:1.5.x-fpm-alpine,roundcube/roundcubemail:1.5.11-fpm-alpine + test-tag: roundcube/roundcubemail:latest-fpm-alpine + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Get docker hub username + id: creds + run: echo '::set-output name=username::${{ secrets.DOCKER_PULL_USERNAME }}' + - name: Login to Docker Hub + if: steps.creds.outputs.username != '' + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + username: ${{ secrets.DOCKER_PULL_USERNAME }} + password: ${{ secrets.DOCKER_PUSH_PASSWORD }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + with: + buildkitd-flags: --debug + + - name: Build locally native image for "${{ matrix.variant }} for tests" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + push: false + load: true + tags: ${{ matrix.docker-tag }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run tests + env: + # ROUNDCUBEMAIL_TEST_IMAGE: roundcube/roundcubemail:latest-${{matrix.variant}} + ROUNDCUBEMAIL_TEST_IMAGE: ${{ matrix.test-tag }} + HTTP_PORT: ${{ matrix.http-port || '80' }} + run: | + set -exu; + for testFile in ${{ join(matrix.test-files, ' ') }}; + do + docker compose -f ./tests/docker-compose.test-${testFile}.yml \ + up --exit-code-from=sut --abort-on-container-exit + done + + - name: Build and push all images for "${{ matrix.variant }}" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + platforms: "linux/arm64,linux/arm/v6,linux/arm/v7,linux/386,linux/amd64" + push: true + tags: ${{ matrix.docker-tag }} diff --git a/.github/workflows/build-and-publish-development.yml b/.github/workflows/build-and-publish-development.yml new file mode 100644 index 0000000000..248a19f63b --- /dev/null +++ b/.github/workflows/build-and-publish-development.yml @@ -0,0 +1,77 @@ +name: Build & publish development image + +permissions: + contents: read + +on: + push: + branches: + - 'master' + paths: + - 'development/**' + - .github/workflows/*-development.yml + schedule: + # Rebuild images each monday morning to ensure a fresh base OS (but later than the main image building workflow, + # because the development image builds on one of them) + - cron: "23 4 * * 1" + workflow_dispatch: + +jobs: + build_test_publish: + name: Build, test and publish image + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up Docker + # This step is required to enable the containerd image store, which is required by the cache type=gha + uses: docker/setup-docker-action@efe9e3891a4f7307e689f2100b33a155b900a608 # v4.5.0 + with: + daemon-config: | + { + "debug": true, + "features": { + "containerd-snapshotter": true + } + } + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + with: + buildkitd-flags: --debug + + - name: Build development image for tests" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: development + load: true + tags: roundcube/roundcubemail:development + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Test built image + env: + ROUNDCUBEMAIL_TEST_IMAGE: roundcube/roundcubemail:development + run: ./development/test.sh + + # Only log into docker now, so we benefit from the automatic caching of upstream images. + - name: Get docker hub username + id: creds + run: echo '::set-output name=username::${{ secrets.DOCKER_PULL_USERNAME }}' + - name: Login to Docker Hub + if: steps.creds.outputs.username != '' + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + username: ${{ secrets.DOCKER_PULL_USERNAME }} + password: ${{ secrets.DOCKER_PUSH_PASSWORD }} + + - name: Build and push development images for all platforms + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: development + platforms: "linux/arm64,linux/arm/v6,linux/arm/v7,linux/386,linux/amd64" + push: true + tags: roundcube/roundcubemail:development + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/build-and-publish-nightly.yml b/.github/workflows/build-and-publish-nightly.yml new file mode 100644 index 0000000000..a7ce70fe55 --- /dev/null +++ b/.github/workflows/build-and-publish-nightly.yml @@ -0,0 +1,68 @@ +name: Build & Publish nightly + +permissions: + contents: read + +on: + schedule: + # Rebuild automatically each night + - cron: "4 2 * * *" + +jobs: + build-and-testvariants: + name: Build image and run tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up Docker + # This step is required to enable the containerd image store, which is required by the cache type=gha + uses: docker/setup-docker-action@efe9e3891a4f7307e689f2100b33a155b900a608 # v4.5.0 + with: + daemon-config: | + { + "debug": true, + "features": { + "containerd-snapshotter": true + } + } + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + with: + buildkitd-flags: --debug + + - name: Build nightly image for tests" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: nightly + load: true + tags: roundcube/roundcubemail:nightly + cache-from: type=gha + cache-to: type=gha,mode=max + # does not work linux/arm/v5 AND linux/mips64le - composer does not support mips64le or armv5 nor does the php image support them on the alpine variant + + - name: Run tests + env: + ROUNDCUBEMAIL_TEST_IMAGE: roundcube/roundcubemail:nightly + run: docker compose -f ./tests/docker-compose.test-apache-postgres.yml up --exit-code-from=sut --abort-on-container-exit + + # Only log into docker now, so we benefit from the automatic caching of upstream images. + - name: Get docker hub username + id: creds + run: echo '::set-output name=username::${{ secrets.DOCKER_PULL_USERNAME }}' + - name: Login to Docker Hub + if: steps.creds.outputs.username != '' + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + username: ${{ secrets.DOCKER_PULL_USERNAME }} + password: ${{ secrets.DOCKER_PUSH_PASSWORD }} + + - name: Build and push nightly images for all platforms + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: nightly + platforms: "linux/arm64,linux/arm/v6,linux/arm/v7,linux/386,linux/amd64" + push: true + tags: roundcube/roundcubemail:nightly diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0861e63fff..da6e1f8e0c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,27 @@ -name: Build Docker image +name: Build & Publish -on: [push, pull_request] +permissions: + contents: read + +on: + push: + branches: + - 'master' + paths-ignore: + - 'README.md' + - 'examples/**' + - 'apache-1.5.x/**' + - 'fpm-1.5.x/**' + - 'fpm-alpine-1.5.x/**' + - '.github/workflows/*-1.5.yml' + - 'development/**' + - 'nightly/**' + tags: + - '1.6.*' + schedule: + # Rebuild images each monday early morning to ensure a fresh base OS. + - cron: "23 2 * * 1" + workflow_dispatch: jobs: build-and-testvariants: @@ -8,32 +29,144 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false + max-parallel: 10 matrix: include: - - { variant: 'apache', test-files: ['apache-postgres'], docker-tag: 'roundcube-test-apache' } - - { variant: 'fpm', test-files: ['fpm-postgres'], docker-tag: 'roundcube-test-fpm' } - - { variant: 'fpm-alpine', test-files: ['fpm-postgres'], docker-tag: 'roundcube-test-fpm-alpine' } + # The tags are pretty repetetive, but we can't properly script things using GitHub actions, and want to avoid scripting all the things manually, so we live with it. + - variant: 'apache' + test-files: 'apache-postgres' + docker-tag: | + roundcube/roundcubemail:1.6.x-apache + roundcube/roundcubemail:1.6.14-apache + roundcube/roundcubemail:latest-apache + roundcube/roundcubemail:latest + docker-tag-nonroot: | + roundcube/roundcubemail:1.6.x-apache-nonroot + roundcube/roundcubemail:1.6.14-apache-nonroot + roundcube/roundcubemail:latest-apache-nonroot + roundcube/roundcubemail:latest-nonroot + test-tag: roundcube/roundcubemail:latest-apache + test-tag-nonroot: roundcube/roundcubemail:latest-apache-nonroot + http-port-nonroot: '8000' + - variant: 'fpm' + test-files: 'fpm-postgres' + docker-tag: | + roundcube/roundcubemail:1.6.x-fpm + roundcube/roundcubemail:1.6.14-fpm + roundcube/roundcubemail:latest-fpm + docker-tag-nonroot: | + roundcube/roundcubemail:1.6.x-fpm-nonroot + roundcube/roundcubemail:1.6.14-fpm-nonroot + roundcube/roundcubemail:latest-fpm-nonroot + test-tag: roundcube/roundcubemail:latest-fpm + test-tag-nonroot: roundcube/roundcubemail:latest-fpm-nonroot + - variant: 'fpm-alpine' + test-files: 'fpm-postgres' + docker-tag: | + roundcube/roundcubemail:1.6.x-fpm-alpine + roundcube/roundcubemail:1.6.14-fpm-alpine + roundcube/roundcubemail:latest-fpm-alpine + docker-tag-nonroot: | + roundcube/roundcubemail:1.6.x-fpm-alpine-nonroot + roundcube/roundcubemail:1.6.14-fpm-alpine-nonroot + roundcube/roundcubemail:latest-fpm-alpine-nonroot + test-tag: roundcube/roundcubemail:latest-fpm-alpine + test-tag-nonroot: roundcube/roundcubemail:latest-fpm-alpine-nonroot + target: 'root' steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up Docker + # This step is required to enable the containerd image store, which is required by the cache type=gha + uses: docker/setup-docker-action@efe9e3891a4f7307e689f2100b33a155b900a608 # v4.5.0 + with: + daemon-config: | + { + "debug": true, + "features": { + "containerd-snapshotter": true + } + } + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + with: + buildkitd-flags: --debug + + - name: Build rootful image to test for "${{ matrix.variant }}" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + load: true + tags: ${{ matrix.docker-tag }} + target: root + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Test rootful image for "${{ matrix.variant }}" + env: + ROUNDCUBEMAIL_TEST_IMAGE: ${{ matrix.test-tag }} + HTTP_PORT: '80' + run: | + set -exu; + for testFile in ${{ join(matrix.test-files, ' ') }}; + do + docker compose -f ./tests/docker-compose.test-${testFile}.yml down -v + docker compose -f ./tests/docker-compose.test-${testFile}.yml up --exit-code-from=sut --abort-on-container-exit + done + + # Only log into docker now, so we benefit from the automatic caching of upstream images. - name: Get docker hub username id: creds run: echo '::set-output name=username::${{ secrets.DOCKER_PULL_USERNAME }}' - name: Login to Docker Hub if: steps.creds.outputs.username != '' - uses: docker/login-action@v1 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: username: ${{ secrets.DOCKER_PULL_USERNAME }} - password: ${{ secrets.DOCKER_PULL_PASSWORD }} - - name: Build image variant "${{ matrix.variant }}" - run: cd ${{ matrix.variant }} && docker build ./ -t ${{ matrix.docker-tag }} - - name: Run tests + password: ${{ secrets.DOCKER_PUSH_PASSWORD }} + + - name: Build and push rootful images for "${{ matrix.variant }} for all platforms" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + platforms: "linux/arm64,linux/arm/v6,linux/arm/v7,linux/386,linux/amd64," + push: true + tags: ${{ matrix.docker-tag }} + target: root + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build nonroot image to test for "${{ matrix.variant }}" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + load: true + tags: ${{ matrix.docker-tag-nonroot }} + target: nonroot + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Test nonroot image for "${{ matrix.variant }}" env: - ROUNDCUBEMAIL_TEST_IMAGE: ${{ matrix.docker-tag }} + ROUNDCUBEMAIL_TEST_IMAGE: ${{ matrix.test-tag-nonroot }} + HTTP_PORT: ${{ matrix.http-port-nonroot || '80' }} run: | set -exu; for testFile in ${{ join(matrix.test-files, ' ') }}; do - docker-compose -f ./tests/docker-compose.test-${testFile}.yml \ - up --exit-code-from=sut --abort-on-container-exit + docker compose -f ./tests/docker-compose.test-${testFile}.yml down -v + docker compose -f ./tests/docker-compose.test-${testFile}.yml up --exit-code-from=sut --abort-on-container-exit done + + - name: Build and push nonroot images for "${{ matrix.variant }} for all platforms" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + platforms: "linux/arm64,linux/arm/v6,linux/arm/v7,linux/386,linux/amd64," + push: true + tags: ${{ matrix.docker-tag-nonroot }} + target: nonroot + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/test-1.5.yml b/.github/workflows/test-1.5.yml new file mode 100644 index 0000000000..de56497a93 --- /dev/null +++ b/.github/workflows/test-1.5.yml @@ -0,0 +1,61 @@ +name: Build & Test 1.5.x + +permissions: + contents: read + +on: + pull_request: {} + push: + branches: + - '!master' + paths: + - apache-1.5.x/** + - fpm-1.5.x/** + - fpm-alpin-1.5.x/** + - '.github/workflows/*-1.5.yml' + +jobs: + build-and-testvariants: + name: Build image variants and run tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 10 + matrix: + include: + - variant: 'apache-1.5.x' + test-files: 'apache-postgres' + docker-tag: roundcube/roundcubemail:test-apache-1.5.x + - variant: 'fpm-1.5.x' + test-files: 'fpm-postgres' + docker-tag: roundcube/roundcubemail:test-fpm-1.5.x + - variant: 'fpm-alpine-1.5.x' + test-files: 'fpm-postgres' + docker-tag: roundcube/roundcubemail:test-fpm-alpine-1.5.x + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Get docker hub username + id: creds + run: echo '::set-output name=username::${{ secrets.DOCKER_PULL_USERNAME }}' + - name: Login to Docker Hub + if: steps.creds.outputs.username != '' + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + username: ${{ secrets.DOCKER_PULL_USERNAME }} + password: ${{ secrets.DOCKER_PULL_PASSWORD }} + + - name: Build image "${{ matrix.variant }} + run: cd ${{ matrix.variant }} && docker buildx build ./ -t ${{ matrix.docker-tag }} + - name: Run tests + run: | + set -exu; + # Set these here so the values are visible in the logs for debugging. + export ROUNDCUBEMAIL_TEST_IMAGE="${{ matrix.docker-tag }}" + export HTTP_PORT="${{ matrix.http-port || '80' }}" + export SKIP_POST_SETUP_SCRIPT_TEST="yes" + for testFile in ${{ join(matrix.test-files, ' ') }}; + do + docker compose -f ./tests/docker-compose.test-${testFile}.yml \ + up --exit-code-from=sut --abort-on-container-exit + done diff --git a/.github/workflows/test-development.yml b/.github/workflows/test-development.yml new file mode 100644 index 0000000000..cac878ff67 --- /dev/null +++ b/.github/workflows/test-development.yml @@ -0,0 +1,30 @@ +name: Build & test development image + +permissions: + contents: read + +on: + pull_request: + paths: + - "development/**" + - ".github/workflows/*-development.yml" + +jobs: + build-and-test: + name: Build and test development image + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Build development image for tests" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: development + load: true + tags: roundcube/roundcubemail:development + + - name: Test built image + env: + ROUNDCUBEMAIL_TEST_IMAGE: roundcube/roundcubemail:development + run: ./development/test.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..14de905e3a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,96 @@ +name: Build & Test + +permissions: + contents: read + +on: + pull_request: + paths-ignore: + - "development/**" + - .github/workflows/*-development.yml + - "nightly/**" + - "examples/**" + - "README.md" + - apache-1.5.x/** + - fpm-1.5.x/** + - fpm-alpine-1.5.x/** + - .github/workflows/*-1.5.yml + +jobs: + build-and-testvariants: + name: Build image variants and run tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 10 + matrix: + include: + - variant: 'apache' + test-files: 'apache-postgres' + docker-tag: roundcube/roundcubemail:test-apache + docker-tag-nonroot: roundcube/roundcubemail:test-apache-nonroot + http-port-nonroot: '8000' + - variant: 'fpm' + test-files: 'fpm-postgres' + docker-tag: roundcube/roundcubemail:test-fpm + docker-tag-nonroot: roundcube/roundcubemail:test-fpm-nonroot + - variant: 'fpm-alpine' + test-files: 'fpm-postgres' + docker-tag: roundcube/roundcubemail:test-fpm-alpine + docker-tag-nonroot: roundcube/roundcubemail:test-fpm-alpine-nonroot + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up Docker + # This step is required to enable the containerd image store, which is required by the cache type=gha + uses: docker/setup-docker-action@efe9e3891a4f7307e689f2100b33a155b900a608 # v4.5.0 + with: + daemon-config: | + { + "debug": true, + "features": { + "containerd-snapshotter": true + } + } + + - name: Build rootful image for "${{ matrix.variant }}" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + load: true + tags: ${{ matrix.docker-tag }} + target: root + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Test rootful image + env: + ROUNDCUBEMAIL_TEST_IMAGE: ${{ matrix.docker-tag }} + HTTP_PORT: '80' + run: | + set -exu; + for testFile in ${{ join(matrix.test-files, ' ') }}; + do + docker compose -f ./tests/docker-compose.test-${testFile}.yml down -v + docker compose -f ./tests/docker-compose.test-${testFile}.yml up --exit-code-from=sut --abort-on-container-exit + done + + - name: Build nonroot image for "${{ matrix.variant }}" + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + with: + context: ${{ matrix.variant }} + load: true + tags: ${{ matrix.docker-tag-nonroot }} + target: nonroot + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Test nonroot image + env: + ROUNDCUBEMAIL_TEST_IMAGE: ${{ matrix.docker-tag-nonroot }} + HTTP_PORT: ${{ matrix.http-port-nonroot || '80' }} + run: | + set -exu; + for testFile in ${{ join(matrix.test-files, ' ') }}; + do + docker compose -f ./tests/docker-compose.test-${testFile}.yml down -v + docker compose -f ./tests/docker-compose.test-${testFile}.yml up --exit-code-from=sut --abort-on-container-exit + done diff --git a/.github/workflows/update-sh.yml b/.github/workflows/update-sh.yml new file mode 100644 index 0000000000..eed759569b --- /dev/null +++ b/.github/workflows/update-sh.yml @@ -0,0 +1,40 @@ +name: update.sh + +permissions: + # Git push permissions are needed + contents: write + pull-requests: write + +on: + push: + branches: + - master + schedule: + - cron: '11 0 * * *' + workflow_dispatch: + +jobs: + run_update_sh: + name: Run update.sh script + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + token: ${{ secrets.WOKFLOW_TOKEN }} + - name: Run update.sh script + run: ./update.sh + - name: Commit files + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Exit early if no changes are present. + test $(git status --porcelain | wc -l) -gt 0 || { echo "No changes to commit, happily cancelling this script."; exit 0; } + # Use a distinct branch-name (nano-seconds should be good enough). + BRANCH="changes-from-update.sh-$(date +'%Y-%m-%d_%H%M%S_%N')" + git switch -C "$BRANCH" + git config --local user.email "workflow@github.com" + git config --local user.name "GitHub Workflow" + git add -A + git commit -m "Update roundcube version (via update.sh)" + git push --set-upstream origin "$BRANCH" + gh pr create -B master -H "$BRANCH" --title 'Changes from update.sh' --body "These are the changes of the automated run of ./update.sh" --assignee pabzm diff --git a/README.md b/README.md index 4265cdbea2..ae1fadfdf6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -[![Build Status](https://travis-ci.org/roundcube/roundcubemail-docker.svg)](https://travis-ci.org/roundcube/roundcubemail-docker) +[![Build & Test](https://github.com/roundcube/roundcubemail-docker/actions/workflows/test.yml/badge.svg)](https://github.com/roundcube/roundcubemail-docker/actions/workflows/test.yml) [![Docker Pulls](https://img.shields.io/docker/pulls/roundcube/roundcubemail.svg)](https://hub.docker.com/r/roundcube/roundcubemail/) # Running Roundcube in a Docker Container The simplest method is to run the official image: -``` +```sh docker run -e ROUNDCUBEMAIL_DEFAULT_HOST=mail -e ROUNDCUBEMAIL_SMTP_SERVER=mail -p 8000:80 -d roundcube/roundcubemail ``` @@ -24,27 +24,34 @@ We also publish full version tags (e.g. `1.3.10`) but these just represent the v The following env variables can be set to configure your Roundcube Docker instance: -`ROUNDCUBEMAIL_DEFAULT_HOST` - Hostname of the IMAP server to connect to. For encypted connections, prefix the host with `tls://` (STARTTLS) or `ssl://` (SSL/TLS). +`ROUNDCUBEMAIL_DEFAULT_HOST` - Hostname of the IMAP server to connect to. For encrypted connections, prefix the host with `tls://` (STARTTLS) or `ssl://` (SSL/TLS). `ROUNDCUBEMAIL_DEFAULT_PORT` - IMAP port number; defaults to `143` -`ROUNDCUBEMAIL_SMTP_SERVER` - Hostname of the SMTP server to send mails. For encypted connections, prefix the host with `tls://` (STARTTLS) or `ssl://` (SSL/TLS). +`ROUNDCUBEMAIL_SMTP_SERVER` - Hostname of the SMTP server to send mails. For encrypted connections, prefix the host with `tls://` (STARTTLS) or `ssl://` (SSL/TLS). + +`ROUNDCUBEMAIL_SMTP_PORT` - SMTP port number; defaults to `587` + +`ROUNDCUBEMAIL_USERNAME_DOMAIN` - Automatically add this domain to user names for login. See [defaults.inc.php](https://github.com/roundcube/roundcubemail/blob/master/config/defaults.inc.php) for more information. -`ROUNDCUBEMAIL_SMTP_PORT` - SMTP port number; defaults to `587` +`ROUNDCUBEMAIL_REQUEST_PATH` - Specify request path with reverse proxy; defaults to `/`. See [defaults.inc.php](https://github.com/roundcube/roundcubemail/blob/master/config/defaults.inc.php) for possible values. `ROUNDCUBEMAIL_PLUGINS` - List of built-in plugins to activate. Defaults to `archive,zipdownload` -`ROUNDCUBEMAIL_SKIN` - Configures the default theme. Defaults to `larry` +`ROUNDCUBEMAIL_COMPOSER_PLUGINS` - The list of composer packages to install on startup. Use `ROUNDCUBEMAIL_PLUGINS` to enable them. -`ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE` - File upload size limit; defaults to `5M` +`ROUNDCUBEMAIL_SKIN` - Configures the default theme. Defaults to `elastic` + +`ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE` - File upload size limit; defaults to `5M`. (*Note: this variable does not work in the `nonroot`-image!*) `ROUNDCUBEMAIL_SPELLCHECK_URI` - Fully qualified URL to a Google XML spell check API like [google-spell-pspell](https://github.com/roundcube/google-spell-pspell) -`ROUNDCUBEMAIL_ASPELL_DICTS` - List of aspell dictionaries to install for spell checking (comma-separated, e.g. `de,fr,pl`). +`ROUNDCUBEMAIL_ASPELL_DICTS` - List of aspell dictionaries to install for spell checking (comma-separated, e.g. `de,fr,pl`). (*Note: this variable does not work in the `nonroot`-image!*) By default, the image will use a local SQLite database for storing user account metadata. -It'll be created inside the `/var/roundcube/db` directory and can be backed up from there. Please note that -this option should not be used for production environments. +It'll be created inside the container directory `/var/roundcube/db`. In order to persist the database, a volume +mount should be added to this path. +(For production environments, please assess individually if SQLite is the right database choice.) ### Connect to a Database @@ -67,27 +74,60 @@ has privileges to create tables. Run it with a link to the MySQL host and the username/password variables: -``` +```sh docker run --link=mysql:mysql -d roundcube/roundcubemail ``` +## Nonroot image + +We provide `nonroot`-images that run all processes as a normal user instead of as root. This limits possible damage in case of a mis-configuration or breach. + +Not running any process as root disables a few features that require to install packages or write to system files on container start. Specifically you cannot use the environment variables `ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE` and `ROUNDCUBEMAIL_ASPELL_DICTS`. + +* To specify a maximum upload filesize, write the required php configuration options into a file and bind-mount that to `/usr/local/etc/php/conf.d/$filename`. See `examples/docker-compose-nonroot.yaml` and `examples/nonroot-custom-php-config.ini` for an example. +* To install additionall aspell dictionaries you will have to build your own container image on top of ours and install them during the build. + +## Persistent data + +The Roundcube containers do not store any data persistently by default. There are, however, +some directories that could be mounted as volume or bind mount to share data between containers +or to inject additional data into the container: + +* `/var/www/html`: Roundcube installation directory + This is the document root of Roundcube. Plugins and additional skins are stored here amongst the Roundcube sources. + Share this directory when using the FPM variant and let a webserver container serve the static files from here. + +* `/var/roundcube/config`: Location for additional config files + See the [Advanced configuration](#advanced-configuration) section for details. + +* `/var/roundcube/db`: storage location of the SQLite database + Only needed if using `ROUNDCUBEMAIL_DB_TYPE=sqlite` to persist the Roundcube database. + +* `/var/roundcube/enigma`: storage location of the enigma plugin + If enabled, the "enigma" plugin stores OpenPGP keys here. + +* `/tmp/roundcube-temp`: Roundcube's temp folder + Temp files like uploaded attachments or thumbnail images are stored here. + Share this directory via a volume when running multiple replicas of the roundcube container. + ## Docker Secrets When running the Roundcube container in a Docker Swarm, you can use [Docker Secrets](https://docs.docker.com/engine/swarm/secrets/) -to share credentials accross all instances. The following secrets are currently supported by Roundcube: +to share credentials across all instances. The following secrets are currently supported by Roundcube: * `roundcube_des_key`: Unique and random key for encryption purposes * `roundcube_db_user`: Database connection username (mappend to `ROUNDCUBEMAIL_DB_USER`) * `roundcube_db_password`: Database connection password (mappend to `ROUNDCUBEMAIL_DB_PASSWORD`) +* `roundcube_oauth_client_secret`: OAuth client secret (mappend to `ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET`) ## Advanced configuration Apart from the above described environment variables, the Docker image also allows to add custom config files -which are merged into Roundcube's default config. Therefore the image defines a volume `/var/roundcube/config` +which are merged into Roundcube's default config. Therefore the image defines the path `/var/roundcube/config` where additional config files (`*.php`) are searched and included. Mount a local directory with your config files - check for valid PHP syntax - when starting the Docker container: -``` +```sh docker run -v ./config/:/var/roundcube/config/ -d roundcube/roundcubemail ``` @@ -98,15 +138,34 @@ For example, it may be used to increase the PHP memory limit (`memory_limit=128M ## Installing Roundcube Plugins -With the latest updates, the Roundcube images contain the [Composer](https://getcomposer.org) binary -which is used to install plugins. You can add and activate plugins by executing `composer require ` -inside a running Roundcube container: +With the latest updates, the Roundcube image is now able to install plugins. +You need to fill `ROUNDCUBEMAIL_COMPOSER_PLUGINS` with the list of composer packages to install. +And set them in `ROUNDCUBEMAIL_PLUGINS` in order to enable the installed plugins. +For example: + +```yaml + ROUNDCUBEMAIL_COMPOSER_PLUGINS: "weird-birds/thunderbird_labels,jfcherng-roundcube/show-folder-size,germancoding/tls_icon:^1.2" + ROUNDCUBEMAIL_PLUGINS: thunderbird_labels, show_folder_size, tls_icon ``` -$ docker exec -it roundcubemail composer require johndoh/contextmenu --update-no-dev -``` -If you have mounted the container's volume `/var/www/html` the plugins installed persist on your host system. Otherwise they need to be (re-)installed every time you update or restart the Roundcube container. +To overwrite the default config of a plugin you might need to use a post-setup script (see below) that moves a custom config file into the plugin's directory. + +## Pre-setup and post-setup tasks + +In order to execute custom tasks before or after Roundcubemail is set up in the container, you can bind-mount directories to `/entrypoint-tasks/pre-setup/` and `/entrypoint-tasks/post-setup/`. Then all executable files in those directories are executed at the beginning or the end of the actual entrypoint-script, respectively. If an executable exits with a code > 1, the entrypoint script exits, too. + +Each executable receives the container's `CMD` as arguments. + +They are executed in alphabetical order (the way `bash` understands it in `en_US` locale). + +If the Roundcubemail-setup is skipped due to a custom `CMD`, these tasks are skipped as well. + + +## HTTPS + +Currently all images are configured to speak HTTP. To provide HTTPS please run an additional reverse proxy in front of them, which handles certificates and terminates TLS. Alternatively you could derive from our images (or use the advanced configuration methods) to make Apache or nginx provide HTTPS – but please refrain from opening issues asking for support with such a setup. + ## Examples @@ -115,35 +174,40 @@ A few example setups using `docker-compose` can be found in our [Github reposito ## Building a Docker image Use the `Dockerfile` in this repository to build your own Docker image. -It pulls the latest build of Roundcube Webmail from the Github download page and builds it on top of a `php:7.3-apache` Docker image. +It pulls the latest build of Roundcube Webmail from the Github download page and builds it on top of a `php:7.4-apache` Docker image. Build it from one of the variants directories with -``` +```sh docker build -t roundcubemail . ``` You can also create your own Docker image by extending from this image. -For instance, you could extend this image to add composer and install requirements for builtin plugins or even external plugins: +For instance, you could extend this image to add composer and install requirements for special plugins: ```Dockerfile FROM roundcube/roundcubemail:latest +# COMPOSER_ALLOW_SUPERUSER is needed to run plugins when using a container +ENV COMPOSER_ALLOW_SUPERUSER=1 + RUN set -ex; \ apt-get update; \ apt-get install -y --no-install-recommends \ git \ ; \ - \ - composer \ - --working-dir=/usr/src/roundcubemail/ \ - --prefer-dist \ - --prefer-stable \ - --update-no-dev \ - --no-interaction \ - --optimize-autoloader \ - require \ - johndoh/contextmenu \ - ; \ ``` + +# License + +This program is free software: you can redistribute it and/or modify it under the terms +of the GNU General Public License as published by the Free Software Foundation, either +version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +For more details about licensing and the exceptions for skins and plugins +see [roundcube.net/license][license]. diff --git a/apache-1.5.x/Dockerfile b/apache-1.5.x/Dockerfile new file mode 100644 index 0000000000..2f3897e6eb --- /dev/null +++ b/apache-1.5.x/Dockerfile @@ -0,0 +1,118 @@ +FROM php:7.4-apache +LABEL maintainer="Thomas Bruederli " + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apt-get update && apt-get -y upgrade && apt-get clean + +RUN set -ex; \ + apt-get update; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + \ + apt-get install -y --no-install-recommends \ + libfreetype6-dev \ + libicu-dev \ + libjpeg62-turbo-dev \ + libldap2-dev \ + libmagickwand-dev \ + libpng-dev \ + libpq-dev \ + libsqlite3-dev \ + libzip-dev \ + libpspell-dev \ + libonig-dev \ + libldap-common \ + ; \ + \ + debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ + docker-php-ext-configure gd --with-jpeg --with-freetype; \ + docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \ + docker-php-ext-install \ + exif \ + gd \ + intl \ + ldap \ + pdo_mysql \ + pdo_pgsql \ + pdo_sqlite \ + zip \ + pspell \ + ; \ + pecl install imagick redis; \ + docker-php-ext-enable imagick opcache redis; \ + \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark; \ + ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ + | awk '/=>/ { print $3 }' \ + | sort -u \ + | xargs -r dpkg-query -S \ + | cut -d: -f1 \ + | sort -u \ + | xargs -rt apt-mark manual; \ + \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/* + +# installto.sh dependencies +RUN set -ex; \ + \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + aspell \ + aspell-en \ + rsync \ + ; \ + rm -rf /var/lib/apt/lists/* + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +RUN a2enmod rewrite + +# Define Roundcubemail version +ENV ROUNDCUBEMAIL_VERSION 1.5.11 + +# Define the GPG key used for the bundle verification process +ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" + +# Download package and extract to web volume +RUN set -ex; \ + fetchDeps="gnupg dirmngr locales libc-l10n"; \ + apt-get -qq update; \ + apt-get install -y --no-install-recommends $fetchDeps; \ + curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ + curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ + export GNUPGHOME="$(mktemp -d)"; \ + # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 + echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ + curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ + LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ + LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ + gpg --batch --import /tmp/pubkey.asc; \ + rm /tmp/pubkey.asc; \ + gpg --batch --verify roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ + gpgconf --kill all; \ + mkdir /usr/src/roundcubemail; \ + tar -xf roundcubemail.tar.gz -C /usr/src/roundcubemail --strip-components=1 --no-same-owner; \ + rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ + rm -rf /usr/src/roundcubemail/installer; \ + chown -R www-data:www-data /usr/src/roundcubemail/logs + +# copy the latest version of initdb.sh which supports the --update flag +RUN curl -fL https://raw.githubusercontent.com/roundcube/roundcubemail/master/bin/initdb.sh > /usr/src/roundcubemail/bin/initdb.sh && chmod +x /usr/src/roundcubemail/bin/initdb.sh + +# include the wait-for-it.sh script +RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini + +COPY --chmod=0755 docker-entrypoint.sh / + +RUN mkdir -p /var/roundcube/config + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["apache2-foreground"] diff --git a/nightly/docker-entrypoint.sh b/apache-1.5.x/docker-entrypoint.sh similarity index 87% rename from nightly/docker-entrypoint.sh rename to apache-1.5.x/docker-entrypoint.sh index 75009693b1..39379453b0 100755 --- a/nightly/docker-entrypoint.sh +++ b/apache-1.5.x/docker-entrypoint.sh @@ -18,7 +18,7 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then INSTALLDIR=`pwd` echo >&2 "roundcubemail found in $INSTALLDIR - installing update..." (cd /usr/src/roundcubemail && bin/installto.sh -y $INSTALLDIR) - composer.phar update --no-dev + composer update --no-dev fi if [ -f /run/secrets/roundcube_db_user ]; then @@ -81,6 +81,8 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then \$config['log_driver'] = 'stdout'; \$config['zipdownload_selection'] = true; \$config['des_key'] = '${GENERATED_DES_KEY}'; + \$config['enable_spellcheck'] = true; + \$config['spellcheck_engine'] = 'pspell'; include(__DIR__ . '/config.docker.inc.php'); " > config/config.inc.php @@ -95,10 +97,8 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then \$config['db_dsnr'] = '${ROUNDCUBEMAIL_DSNR}'; \$config['default_host'] = '${ROUNDCUBEMAIL_DEFAULT_HOST}'; \$config['default_port'] = '${ROUNDCUBEMAIL_DEFAULT_PORT}'; - \$config['imap_host'] = '${ROUNDCUBEMAIL_DEFAULT_HOST}:${ROUNDCUBEMAIL_DEFAULT_PORT}'; \$config['smtp_server'] = '${ROUNDCUBEMAIL_SMTP_SERVER}'; \$config['smtp_port'] = '${ROUNDCUBEMAIL_SMTP_PORT}'; - \$config['smtp_host'] = '${ROUNDCUBEMAIL_SMTP_SERVER}:${ROUNDCUBEMAIL_SMTP_PORT}'; \$config['temp_dir'] = '${ROUNDCUBEMAIL_TEMP_DIR}'; \$config['skin'] = '${ROUNDCUBEMAIL_SKIN}'; \$config['plugins'] = array_filter(array_unique(array_merge(\$config['plugins'], ['${ROUNDCUBEMAIL_PLUGINS_PHP}']))); @@ -110,13 +110,18 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo "\$config['des_key'] = getenv('ROUNDCUBEMAIL_DES_KEY');" >> config/config.docker.inc.php fi + if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}"]; then + echo "\$config['spellcheck_engine'] = 'googie';" >> config/config.docker.inc.php + echo "\$config['spellcheck_uri'] = '${ROUNDCUBEMAIL_SPELLCHECK_URI}';" >> config/config.docker.inc.php + fi + # include custom config files for fn in `ls /var/roundcube/config/*.php 2>/dev/null || true`; do echo "include('$fn');" >> config/config.docker.inc.php done # initialize or update DB - bin/initdb.sh --dir=$PWD/SQL --create || bin/updatedb.sh --dir=$PWD/SQL --package=roundcube || echo "Failed to initialize database. Please run $PWD/bin/initdb.sh and $PWD/bin/updatedb.sh manually." + bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." if [ ! -z "${ROUNDCUBEMAIL_TEMP_DIR}" ]; then mkdir -p ${ROUNDCUBEMAIL_TEMP_DIR} && chown www-data ${ROUNDCUBEMAIL_TEMP_DIR} @@ -133,6 +138,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen /usr/sbin/locale-gen fi + + if [ ! -z "${ROUNDCUBEMAIL_ASPELL_DICTS}" ]; then + ASPELL_PACKAGES=`echo -n "aspell-${ROUNDCUBEMAIL_ASPELL_DICTS}" | sed -E "s/[, ]+/ aspell-/g"` + which apt-get && apt-get install -y $ASPELL_PACKAGES + which apk && apk add --no-cache $ASPELL_PACKAGES + fi + fi exec "$@" diff --git a/nightly/php.ini b/apache-1.5.x/php.ini similarity index 100% rename from nightly/php.ini rename to apache-1.5.x/php.ini diff --git a/apache/Dockerfile b/apache/Dockerfile index 319759160a..8b9a8ff40e 100644 --- a/apache/Dockerfile +++ b/apache/Dockerfile @@ -1,7 +1,20 @@ -FROM php:7.4-apache +FROM php:8.4-apache AS root LABEL maintainer="Thomas Bruederli " +LABEL org.opencontainers.image.source="https://github.com/roundcube/roundcubemail-docker" + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apt-get update && apt-get -y upgrade && apt-get clean RUN set -ex; \ + if [ "apache" = "apache" ]; then \ + a2enmod rewrite; \ + # Make Apache use public_html/ as document root to protect files outside of it \ + # against unauthorized access. \ + # This is possible and recommended since a while, and will be required for Roundcubemail v1.7. \ + sed -i -e 's|\(DocumentRoot /var/www/html\)$|\1/public_html|' /etc/apache2/sites-available/000-default.conf; \ + fi; \ apt-get update; \ \ savedAptMark="$(apt-mark showmanual)"; \ @@ -11,6 +24,7 @@ RUN set -ex; \ libicu-dev \ libjpeg62-turbo-dev \ libldap2-dev \ + libsasl2-dev \ libmagickwand-dev \ libpng-dev \ libpq-dev \ @@ -20,10 +34,21 @@ RUN set -ex; \ libonig-dev \ libldap-common \ ; \ +# installto.sh & web install dependencies + fetchDeps="gnupg locales libc-l10n"; \ + installDeps="aspell aspell-en rsync unzip"; \ + apt-get install -y --no-install-recommends \ + $installDeps \ + $fetchDeps \ + ; \ \ +# Extract sources to avoid using pecl (https://github.com/docker-library/php/issues/374#issuecomment-690698974) + pecl bundle -d /usr/src/php/ext imagick; \ + pecl bundle -d /usr/src/php/ext redis; \ + pecl bundle -d /usr/src/php/ext pspell; \ debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ docker-php-ext-configure gd --with-jpeg --with-freetype; \ - docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \ + docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch" --with-ldap-sasl; \ docker-php-ext-install \ exif \ gd \ @@ -34,15 +59,21 @@ RUN set -ex; \ pdo_sqlite \ zip \ pspell \ + imagick \ + redis \ ; \ - pecl install imagick redis; \ docker-php-ext-enable imagick opcache redis; \ + docker-php-source delete; \ + rm -r /tmp/pear; \ +# Display installed modules + php -m; \ \ # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark; \ - ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ - | awk '/=>/ { print $3 }' \ + apt-mark manual $savedAptMark $installDeps $fetchDeps; \ + extdir="$(php -r 'echo ini_get("extension_dir");')"; \ + ldd "$extdir"/*.so \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ | sort -u \ | xargs -r dpkg-query -S \ | cut -d: -f1 \ @@ -50,45 +81,36 @@ RUN set -ex; \ | xargs -rt apt-mark manual; \ \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - rm -rf /var/lib/apt/lists/* - -# installto.sh dependencies -RUN set -ex; \ - \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - aspell \ - aspell-en \ - rsync \ - ; \ - rm -rf /var/lib/apt/lists/* + rm -rf /var/lib/apt/lists/*; \ + ldd "$extdir"/*.so | grep -qzv "=> not found" || (echo "Sanity check failed: missing libraries:"; ldd "$extdir"/*.so | grep " => not found"; exit 1); \ + ldd "$extdir"/*.so | grep -q "libzip.so.* => .*/libzip.so.*" || (echo "Sanity check failed: libzip.so is not referenced"; ldd "$extdir"/*.so; exit 1); \ + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ] || (echo "Sanity check failed: php returned errors; $err"; exit 1;); \ +# include the wait-for-it.sh script (latest commit) + curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh -o /wait-for-it.sh; \ + chmod +x /wait-for-it.sh; COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -RUN a2enmod rewrite +# Use the default production configuration +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -# expose these volumes -VOLUME /var/roundcube/config -VOLUME /var/roundcube/db -VOLUME /var/www/html -VOLUME /tmp/roundcube-temp +COPY --chmod=0755 docker-entrypoint.sh / # Define Roundcubemail version -ENV ROUNDCUBEMAIL_VERSION 1.5.2 +ENV ROUNDCUBEMAIL_VERSION 1.6.14 # Define the GPG key used for the bundle verification process ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" # Download package and extract to web volume RUN set -ex; \ - fetchDeps="gnupg dirmngr locales libc-l10n"; \ - apt-get -qq update; \ - apt-get install -y --no-install-recommends $fetchDeps; \ curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ export GNUPGHOME="$(mktemp -d)"; \ - # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 - echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ @@ -100,15 +122,27 @@ RUN set -ex; \ tar -xf roundcubemail.tar.gz -C /usr/src/roundcubemail --strip-components=1 --no-same-owner; \ rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ rm -rf /usr/src/roundcubemail/installer; \ - chown -R www-data:www-data /usr/src/roundcubemail/logs + chown -R www-data:www-data /usr/src/roundcubemail/logs; \ +# Create the config dir + mkdir -p /var/roundcube/config /var/roundcube/enigma; \ + chown -R www-data:www-data /var/roundcube; \ + chmod +t /var/roundcube -# include the wait-for-it.sh script -RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["apache2-foreground"] -# use custom PHP settings -COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -COPY docker-entrypoint.sh / +#### non-root stage -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["apache2-foreground"] +FROM root AS nonroot + +# Prepare locale config for locale-gen +RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen; \ + /usr/sbin/locale-gen + +RUN sed -i 's/^Listen 80$/Listen 8000/' /etc/apache2/ports.conf +RUN sed -i /etc/apache2/sites-enabled/000-default.conf -e 's/:80>/:8000>/' + +EXPOSE 8000 + +USER 33:33 diff --git a/apache/docker-entrypoint.sh b/apache/docker-entrypoint.sh index 50afaa656c..600d0f04bd 100755 --- a/apache/docker-entrypoint.sh +++ b/apache/docker-entrypoint.sh @@ -3,7 +3,37 @@ # PWD=`pwd` -if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then +run_entrypoint_tasks() { + phase="$1" + shift + shopt -s nullglob + echo "Running $phase-setup tasks:" + for file in /entrypoint-tasks/"$phase-setup"/*; do + if test ! -f "$file"; then + echo "Ignoring $file because it is not a regular file." + continue; + fi + if test ! -x "$file"; then + echo "Ignoring $file because it is not executable." + continue; + fi + echo "Running $phase-setup task $file:" + "$file" "$@" + # Exit in case of an error in an executable. + exit_code=$? + if test $exit_code -ne 0; then + echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!" + exit $exit_code + fi + echo 'Done.' + done + shopt -u nullglob +} + +if [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then + run_entrypoint_tasks pre "$@" + + INSTALLDIR=`pwd` # docroot is empty if ! [ -e index.php -a -e bin/installto.sh ]; then echo >&2 "roundcubemail not found in $PWD - copying now..." @@ -12,13 +42,19 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then ( set -x; ls -A; sleep 10 ) fi tar cf - --one-file-system -C /usr/src/roundcubemail . | tar xf - - echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $PWD" + echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $INSTALLDIR" # update Roundcube in docroot else - INSTALLDIR=`pwd` echo >&2 "roundcubemail found in $INSTALLDIR - installing update..." (cd /usr/src/roundcubemail && bin/installto.sh -y $INSTALLDIR) - composer update --no-dev + # Re-install composer modules (including plugins) + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --no-dev \ + --no-interaction \ + --optimize-autoloader \ + install fi if [ -f /run/secrets/roundcube_db_user ]; then @@ -27,6 +63,9 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then if [ -f /run/secrets/roundcube_db_password ]; then ROUNDCUBEMAIL_DB_PASSWORD=`cat /run/secrets/roundcube_db_password` fi + if [ -f /run/secrets/roundcube_oauth_client_secret ]; then + ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET=`cat /run/secrets/roundcube_oauth_client_secret` + fi if [ ! -z "${!POSTGRES_ENV_POSTGRES_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "pgsql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=pgsql}" @@ -35,9 +74,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_USER:=${POSTGRES_ENV_POSTGRES_USER}}" : "${ROUNDCUBEMAIL_DB_PASSWORD:=${POSTGRES_ENV_POSTGRES_PASSWORD}}" : "${ROUNDCUBEMAIL_DB_NAME:=${POSTGRES_ENV_POSTGRES_DB:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi elif [ ! -z "${!MYSQL_ENV_MYSQL_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "mysql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=mysql}" : "${ROUNDCUBEMAIL_DB_HOST:=mysql}" @@ -49,9 +92,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD}}" fi : "${ROUNDCUBEMAIL_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi else # use local SQLite DB in /var/roundcube/db : "${ROUNDCUBEMAIL_DB_TYPE:=sqlite}" @@ -70,6 +117,42 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_PLUGINS:=archive,zipdownload}" : "${ROUNDCUBEMAIL_SKIN:=elastic}" : "${ROUNDCUBEMAIL_TEMP_DIR:=/tmp/roundcube-temp}" + : "${ROUNDCUBEMAIL_REQUEST_PATH:=/}" + : "${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER:=$INSTALLDIR}" + + if [ ! -z "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" ]; then + echo "Installing plugins from the list" + echo "Plugins: ${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" + + # Change ',' into a space + ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH=`echo "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" | tr ',' ' '` + + composer \ + --working-dir=${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + ${ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH}; + fi + + if [ ! -d skins/${ROUNDCUBEMAIL_SKIN} ]; then + # Installing missing skin + echo "Installing missing skin: ${ROUNDCUBEMAIL_SKIN}" + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + roundcube/${ROUNDCUBEMAIL_SKIN}; + fi if [ ! -e config/config.inc.php ]; then GENERATED_DES_KEY=`head /dev/urandom | base64 | head -c 24` @@ -95,12 +178,12 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo " config/config.docker.inc.php @@ -110,18 +193,27 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo "\$config['des_key'] = getenv('ROUNDCUBEMAIL_DES_KEY');" >> config/config.docker.inc.php fi - if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}"]; then + if [ ! -z "${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}" ]; then + echo "\$config['oauth_client_secret'] = '${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}';" >> config/config.docker.inc.php + fi + + if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}" ]; then echo "\$config['spellcheck_engine'] = 'googie';" >> config/config.docker.inc.php echo "\$config['spellcheck_uri'] = '${ROUNDCUBEMAIL_SPELLCHECK_URI}';" >> config/config.docker.inc.php fi + # If the "enigma" plugin is enabled but has no storage configured, inject a default value for the mandatory setting. + if $(echo $ROUNDCUBEMAIL_PLUGINS | grep -Eq '\benigma\b') && ! grep -qr enigma_pgp_homedir /var/roundcube/config/; then + echo "\$config['enigma_pgp_homedir'] = '/var/roundcube/enigma';" >> config/config.docker.inc.php + fi + # include custom config files for fn in `ls /var/roundcube/config/*.php 2>/dev/null || true`; do echo "include('$fn');" >> config/config.docker.inc.php done # initialize or update DB - bin/initdb.sh --dir=$PWD/SQL --create || bin/updatedb.sh --dir=$PWD/SQL --package=roundcube || echo "Failed to initialize database. Please run $PWD/bin/initdb.sh and $PWD/bin/updatedb.sh manually." + bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." if [ ! -z "${ROUNDCUBEMAIL_TEMP_DIR}" ]; then mkdir -p ${ROUNDCUBEMAIL_TEMP_DIR} && chown www-data ${ROUNDCUBEMAIL_TEMP_DIR} @@ -134,17 +226,17 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_LOCALE:=en_US.UTF-8 UTF-8}" - if [ -e /usr/sbin/locale-gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then - echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen - /usr/sbin/locale-gen + if [ -e /usr/sbin/locale-gen ] && [ ! -f /etc/locale.gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then + echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen && /usr/sbin/locale-gen fi if [ ! -z "${ROUNDCUBEMAIL_ASPELL_DICTS}" ]; then ASPELL_PACKAGES=`echo -n "aspell-${ROUNDCUBEMAIL_ASPELL_DICTS}" | sed -E "s/[, ]+/ aspell-/g"` - which apt-get && apt-get install -y $ASPELL_PACKAGES + which apt-get && apt-get update && apt-get install -y $ASPELL_PACKAGES which apk && apk add --no-cache $ASPELL_PACKAGES fi + run_entrypoint_tasks post "$@" fi exec "$@" diff --git a/apache/nonroot-add.txt b/apache/nonroot-add.txt new file mode 100644 index 0000000000..b55717cf2b --- /dev/null +++ b/apache/nonroot-add.txt @@ -0,0 +1,4 @@ +RUN sed -i 's/^Listen 80$/Listen 8000/' /etc/apache2/ports.conf +RUN sed -i /etc/apache2/sites-enabled/000-default.conf -e 's/:80>/:8000>/' + +EXPOSE 8000 diff --git a/development/Dockerfile b/development/Dockerfile new file mode 100644 index 0000000000..2b9f6e31e1 --- /dev/null +++ b/development/Dockerfile @@ -0,0 +1,33 @@ +FROM docker.io/roundcube/roundcubemail:latest-apache-nonroot + +ENV DEBIAN_FRONTEND=noninteractive + +USER root + +RUN apt-get update \ + && apt-get install -y --no-install-recommends npm git sudo sqlite3 \ + && apt-get clean + +COPY --chmod=0755 docker-entrypoint.sh / + +# Prevent the upstream image from overwriting our code +RUN rm -r /usr/src/roundcubemail + +RUN install -d -o www-data -g www-data /var/roundcube + +# Pre-download js dependencies (these don't change much over time). +ENV CACHEDIR=/var/cache/roundcubemail/jsdeps +RUN install -o www-data -d "$CACHEDIR" +# Create NPM's home directory so it can write cache files and logs. +RUN install -o www-data -d /var/www/.npm + +USER www-data + +# We need the code from the repo, not from a released tarball. +RUN cd /tmp \ + && curl -OLs https://github.com/roundcube/roundcubemail/archive/refs/heads/master.zip \ + && unzip -q master.zip \ + && ./roundcubemail-master/bin/install-jsdeps.sh \ + && rm -rf master.zip roundcubemail-master + +VOLUME /var/www/html diff --git a/development/docker-entrypoint.sh b/development/docker-entrypoint.sh new file mode 100644 index 0000000000..2e9fb714c0 --- /dev/null +++ b/development/docker-entrypoint.sh @@ -0,0 +1,53 @@ +#!/bin/bash -ex + +if [[ ! -f index.php ]]; then + echo "Error: No source code in /var/www/html – you must mount your code base to that path!" + exit 1 +fi + +if [[ "$1" != apache2* && "$1" != php-fpm && "$1" != bin* ]]; then + exec $@ +fi + +# Ensure two very essential requirements are set in the config file. +if ! grep -q "log_driver.*stdout" config/config.inc.php; then + echo "\$config['log_driver'] = 'stdout';" >> config/config.inc.php +fi +if ! grep -q "db_dsnw" config/config.inc.php; then + echo "\$config['db_dsnw'] = 'sqlite:////var/roundcube/sqlite.db?mode=0644';" >> config/config.inc.php +fi + +# Run the steps necessary to actually use the repository code. + +# Install dependencies +if [[ ! -f composer.json ]]; then + # For older versions of Roundcubemail. + cp -v composer.json-dist composer.json +fi +composer --prefer-dist --no-interaction --optimize-autoloader install + +# Download external Javascript dependencies. +bin/install-jsdeps.sh + +# Translate elastic's styles to CSS. +if grep -q css-elastic Makefile; then + make css-elastic +else + # Older versions + ( + npm install less && \ + npm install less-plugin-clean-css && \ + cd skins/elastic && \ + npx lessc --clean-css="--s1 --advanced" styles/styles.less > styles/styles.min.css && \ + npx lessc --clean-css="--s1 --advanced" styles/print.less > styles/print.min.css && \ + npx lessc --clean-css="--s1 --advanced" styles/embed.less > styles/embed.min.css \ + ) +fi + +# Update cache-buster parameters in CSS-URLs. +bin/updatecss.sh + +# Initialize or update the database. +bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." + +exec apache2-foreground diff --git a/development/test.sh b/development/test.sh new file mode 100755 index 0000000000..9c1d14009d --- /dev/null +++ b/development/test.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +EXPECTED_STRING='Error: No source code in /var/www/html – you must mount your code base to that path!' + +ContID="$(docker run -d roundcube/roundcubemail:development)" +sleep 5 + +if $(docker logs $ContID 2>&1 | grep -q "$EXPECTED_STRING"); then + docker rm -f $ContID 2>&1 >/dev/null + echo "✅ Test successful " + exit 0 +fi + +echo "⚠️ Error: The container output did not contain the expected string '$EXPECTED_STRING', the test failed!" +echo "Container output:" +docker logs $ContID + +docker rm -f $ContID 2>&1 >/dev/null +exit 1 diff --git a/examples/README.md b/examples/README.md index 8adaeab63f..eb6c206de1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,12 +16,12 @@ See [docker-compose-mysql.yaml](./docker-compose-mysql.yaml). Directly serves Roundcube webmail via HTTP and connects to a MySQL database container. The Roundcube sources and the database files are stored on connected volumes. -## Roundcube served from PHP-FPM via Nginx using a Postrgres DB +## Roundcube served from PHP-FPM via Nginx using a Postgres DB See [docker-compose-fpm.yaml](./docker-compose-fpm.yaml) or [docker-compose-fpm-alpine.yaml](./docker-compose-fpm-alpine.yaml). An Nginx webserver serves Roundcube from a PHP-FPM container via CGI and static files from the shared Roundcube sources. -A Posrgres database container is used to store Roundcube's session and user data. +A Postgres database container is used to store Roundcube's session and user data. The Roundcube sources and the database files are stored on connected volumes. ## Installing Roundcube Plugins diff --git a/examples/docker-compose-fpm-alpine.yaml b/examples/docker-compose-fpm-alpine.yaml index 23188ba478..5a70def799 100644 --- a/examples/docker-compose-fpm-alpine.yaml +++ b/examples/docker-compose-fpm-alpine.yaml @@ -9,8 +9,6 @@ services: - roundcubedb links: - roundcubedb - ports: - - 9000:9000 volumes: - ./www:/var/www/html environment: @@ -27,10 +25,8 @@ services: image: postgres:alpine container_name: roundcubedb # restart: unless-stopped - ports: - - 5432:5432 volumes: - - ./db/postgres:/var/lib/postgresql/data + - ./db/postgres:/var/lib/postgresql # for postgres versions <18 use /var/lib/postgresql/data environment: - POSTGRES_DB=roundcube - POSTGRES_USER=roundcube @@ -42,8 +38,6 @@ services: # restart: unless-stopped ports: - 9008:80 - # If you need SSL connection - # - '443:443' depends_on: - roundcubemail links: @@ -53,9 +47,6 @@ services: - ./nginx/templates:/etc/nginx/templates # Provide a custom nginx conf # - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - # If you need SSL connection, you can provide your own certificates - # - ./certs:/etc/letsencrypt - # - ./certs-data:/data/letsencrypt environment: - NGINX_HOST=localhost # set your local domain or your live domain - NGINX_PHP_CGI=roundcubemail:9000 # same as roundcubemail container name diff --git a/examples/docker-compose-fpm.yaml b/examples/docker-compose-fpm.yaml index 550a32d5b7..0c593e0eba 100644 --- a/examples/docker-compose-fpm.yaml +++ b/examples/docker-compose-fpm.yaml @@ -9,8 +9,6 @@ services: - roundcubedb links: - roundcubedb - ports: - - 9000:9000 volumes: - ./www:/var/www/html environment: @@ -27,10 +25,8 @@ services: image: postgres:latest container_name: roundcubedb # restart: unless-stopped - ports: - - 5432:5432 volumes: - - ./db/postgres:/var/lib/postgresql/data + - ./db/postgres:/var/lib/postgresql # for postgres versions <18 use /var/lib/postgresql/data environment: - POSTGRES_DB=roundcube - POSTGRES_USER=roundcube @@ -42,8 +38,6 @@ services: # restart: unless-stopped ports: - 9009:80 - # If you need SSL connection - # - '443:443' depends_on: - roundcubemail links: @@ -53,9 +47,6 @@ services: - ./nginx/templates:/etc/nginx/templates # Provide a custom nginx conf # - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - # If you need SSL connection, you can provide your own certificates - # - ./certs:/etc/letsencrypt - # - ./certs-data:/data/letsencrypt environment: - NGINX_HOST=localhost # set your local domain or your live domain - NGINX_PHP_CGI=roundcubemail:9000 # same as roundcubemail container name diff --git a/examples/docker-compose-mysql.yaml b/examples/docker-compose-mysql.yaml index ae5fe0b8f0..18a768801d 100644 --- a/examples/docker-compose-mysql.yaml +++ b/examples/docker-compose-mysql.yaml @@ -2,14 +2,11 @@ version: '2' services: roundcubedb: - image: mysql:5.7 + image: mysql:latest container_name: roundcubedb # restart: unless-stopped volumes: - ./db/mysql:/var/lib/mysql - ports: - - 34010:5432 - - 33006:3306 environment: - MYSQL_ROOT_PASSWORD=roundcube-mysql-pw - MYSQL_DATABASE=roundcubemail @@ -34,8 +31,8 @@ services: - ROUNDCUBEMAIL_DEFAULT_HOST=tls://mail.example.org - ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.example.org -### Optional: add a full mail server stack to use with Roundcube like https://hub.docker.com/r/tvial/docker-mailserver +### Optional: add a full mail server stack to use with Roundcube like https://github.com/docker-mailserver/docker-mailserver # mailserver: -# image: tvial/docker-mailserver:latest +# image: mailserver/docker-mailserver:latest # hostname: mail.example.org -# ... # for more options see https://github.com/tomav/docker-mailserver#examples +# ... # for more options see https://github.com/docker-mailserver/docker-mailserver#examples diff --git a/examples/docker-compose-nonroot.yaml b/examples/docker-compose-nonroot.yaml new file mode 100644 index 0000000000..45918acbda --- /dev/null +++ b/examples/docker-compose-nonroot.yaml @@ -0,0 +1,12 @@ +services: + roundcubemail: + image: roundcube/roundcubemail:latest-nonroot + container_name: roundcubemail + volumes: + - ./db/sqlite:/var/roundcube/db + - ./nonroot-custom-php-config.ini:/usr/local/etc/php/conf.d/zzz-nonroot-custom-php-config.ini + ports: + - 9003:8000 + environment: + - ROUNDCUBEMAIL_DEFAULT_HOST=tls://mail.example.org + - ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.example.org diff --git a/examples/docker-compose-plugins.yaml b/examples/docker-compose-plugins.yaml new file mode 100644 index 0000000000..ee21202109 --- /dev/null +++ b/examples/docker-compose-plugins.yaml @@ -0,0 +1,25 @@ +version: '3' + +services: + roundcubemail: + image: roundcube/roundcubemail:latest + container_name: roundcubemail +# restart: unless-stopped + volumes: + - ./www:/var/www/html + - ./db/sqlite:/var/roundcube/db + ports: + - 9002:80 + environment: + ROUNDCUBEMAIL_DB_TYPE: sqlite + ROUNDCUBEMAIL_SKIN: elastic + ROUNDCUBEMAIL_DEFAULT_HOST: tls://mail.example.org + ROUNDCUBEMAIL_SMTP_SERVER: tls://mail.example.org + ROUNDCUBEMAIL_COMPOSER_PLUGINS: "weird-birds/thunderbird_labels,jfcherng-roundcube/show-folder-size,germancoding/tls_icon:^1.2" + ROUNDCUBEMAIL_PLUGINS: thunderbird_labels, show_folder_size, tls_icon + +### Optional: add a full mail server stack to use with Roundcube like https://hub.docker.com/r/tvial/docker-mailserver +# mailserver: +# image: tvial/docker-mailserver:latest +# hostname: mail.example.org +# ... # for more options see https://github.com/tomav/docker-mailserver#examples diff --git a/examples/kubernetes.yaml b/examples/kubernetes.yaml index ac73108389..ac39f6c19e 100644 --- a/examples/kubernetes.yaml +++ b/examples/kubernetes.yaml @@ -114,7 +114,7 @@ spec: ports: - containerPort: 5432 volumeMounts: - - mountPath: /var/lib/postgresql/data + - mountPath: /var/lib/postgresql # for postgres versions <18 use /var/lib/postgresql/data name: roundcubedb-volume restartPolicy: Always serviceAccountName: "" @@ -145,7 +145,7 @@ spec: - name: roundcubemail image: roundcube/roundcubemail:latest-fpm-alpine imagePullPolicy: "" - env: + env: &env - name: ROUNDCUBEMAIL_DB_TYPE value: pgsql - name: ROUNDCUBEMAIL_DB_HOST @@ -278,3 +278,25 @@ spec: targetPort: 80 selector: service: roundcubenginx +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: cleandb +spec: + schedule: "@daily" + concurrencyPolicy: Replace + jobTemplate: + spec: + template: + metadata: + name: cleandb + spec: + restartPolicy: OnFailure + containers: + - name: roundcubemail + image: roundcube/roundcubemail:latest-fpm-alpine + imagePullPolicy: "" + env: *env + args: + - bin/cleandb.sh diff --git a/examples/nginx/templates/default.conf.template b/examples/nginx/templates/default.conf.template index ae7ee209eb..bcb0fa4d99 100644 --- a/examples/nginx/templates/default.conf.template +++ b/examples/nginx/templates/default.conf.template @@ -3,7 +3,12 @@ server { server_name php-docker.local; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; - root /var/www/html; + root /var/www/html/public_html; + + location ~ /(temp|logs)/ { + deny all; + return 403; + } location ~ \.php$ { try_files $uri =404; diff --git a/examples/nonroot-custom-php-config.ini b/examples/nonroot-custom-php-config.ini new file mode 100644 index 0000000000..f5a244b827 --- /dev/null +++ b/examples/nonroot-custom-php-config.ini @@ -0,0 +1,3 @@ +; Use this to specify a maximum upload filesize in the nonroot-image. +;post_max_size=128M +;upload_max_filesize=128M diff --git a/fpm-1.5.x/Dockerfile b/fpm-1.5.x/Dockerfile new file mode 100644 index 0000000000..fd6a522bc6 --- /dev/null +++ b/fpm-1.5.x/Dockerfile @@ -0,0 +1,117 @@ +FROM php:7.4-fpm +LABEL maintainer="Thomas Bruederli " + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apt-get update && apt-get -y upgrade && apt-get clean + +RUN set -ex; \ + apt-get update; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + \ + apt-get install -y --no-install-recommends \ + libfreetype6-dev \ + libicu-dev \ + libjpeg62-turbo-dev \ + libldap2-dev \ + libmagickwand-dev \ + libpng-dev \ + libpq-dev \ + libsqlite3-dev \ + libzip-dev \ + libpspell-dev \ + libonig-dev \ + libldap-common \ + ; \ + \ + debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ + docker-php-ext-configure gd --with-jpeg --with-freetype; \ + docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \ + docker-php-ext-install \ + exif \ + gd \ + intl \ + ldap \ + pdo_mysql \ + pdo_pgsql \ + pdo_sqlite \ + zip \ + pspell \ + ; \ + pecl install imagick redis; \ + docker-php-ext-enable imagick opcache redis; \ + \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark; \ + ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ + | awk '/=>/ { print $3 }' \ + | sort -u \ + | xargs -r dpkg-query -S \ + | cut -d: -f1 \ + | sort -u \ + | xargs -rt apt-mark manual; \ + \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/* + +# installto.sh dependencies +RUN set -ex; \ + \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + aspell \ + aspell-en \ + rsync \ + ; \ + rm -rf /var/lib/apt/lists/* + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + + +# Define Roundcubemail version +ENV ROUNDCUBEMAIL_VERSION 1.5.11 + +# Define the GPG key used for the bundle verification process +ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" + +# Download package and extract to web volume +RUN set -ex; \ + fetchDeps="gnupg dirmngr locales libc-l10n"; \ + apt-get -qq update; \ + apt-get install -y --no-install-recommends $fetchDeps; \ + curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ + curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ + export GNUPGHOME="$(mktemp -d)"; \ + # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 + echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ + curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ + LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ + LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ + gpg --batch --import /tmp/pubkey.asc; \ + rm /tmp/pubkey.asc; \ + gpg --batch --verify roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ + gpgconf --kill all; \ + mkdir /usr/src/roundcubemail; \ + tar -xf roundcubemail.tar.gz -C /usr/src/roundcubemail --strip-components=1 --no-same-owner; \ + rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ + rm -rf /usr/src/roundcubemail/installer; \ + chown -R www-data:www-data /usr/src/roundcubemail/logs + +# copy the latest version of initdb.sh which supports the --update flag +RUN curl -fL https://raw.githubusercontent.com/roundcube/roundcubemail/master/bin/initdb.sh > /usr/src/roundcubemail/bin/initdb.sh && chmod +x /usr/src/roundcubemail/bin/initdb.sh + +# include the wait-for-it.sh script +RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini + +COPY --chmod=0755 docker-entrypoint.sh / + +RUN mkdir -p /var/roundcube/config + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["php-fpm"] diff --git a/fpm-1.5.x/docker-entrypoint.sh b/fpm-1.5.x/docker-entrypoint.sh new file mode 100755 index 0000000000..39379453b0 --- /dev/null +++ b/fpm-1.5.x/docker-entrypoint.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# set -ex + +# PWD=`pwd` + +if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then + # docroot is empty + if ! [ -e index.php -a -e bin/installto.sh ]; then + echo >&2 "roundcubemail not found in $PWD - copying now..." + if [ "$(ls -A)" ]; then + echo >&2 "WARNING: $PWD is not empty - press Ctrl+C now if this is an error!" + ( set -x; ls -A; sleep 10 ) + fi + tar cf - --one-file-system -C /usr/src/roundcubemail . | tar xf - + echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $PWD" + # update Roundcube in docroot + else + INSTALLDIR=`pwd` + echo >&2 "roundcubemail found in $INSTALLDIR - installing update..." + (cd /usr/src/roundcubemail && bin/installto.sh -y $INSTALLDIR) + composer update --no-dev + fi + + if [ -f /run/secrets/roundcube_db_user ]; then + ROUNDCUBEMAIL_DB_USER=`cat /run/secrets/roundcube_db_user` + fi + if [ -f /run/secrets/roundcube_db_password ]; then + ROUNDCUBEMAIL_DB_PASSWORD=`cat /run/secrets/roundcube_db_password` + fi + + if [ ! -z "${!POSTGRES_ENV_POSTGRES_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "pgsql" ]; then + : "${ROUNDCUBEMAIL_DB_TYPE:=pgsql}" + : "${ROUNDCUBEMAIL_DB_HOST:=postgres}" + : "${ROUNDCUBEMAIL_DB_PORT:=5432}" + : "${ROUNDCUBEMAIL_DB_USER:=${POSTGRES_ENV_POSTGRES_USER}}" + : "${ROUNDCUBEMAIL_DB_PASSWORD:=${POSTGRES_ENV_POSTGRES_PASSWORD}}" + : "${ROUNDCUBEMAIL_DB_NAME:=${POSTGRES_ENV_POSTGRES_DB:-roundcubemail}}" + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + elif [ ! -z "${!MYSQL_ENV_MYSQL_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "mysql" ]; then + : "${ROUNDCUBEMAIL_DB_TYPE:=mysql}" + : "${ROUNDCUBEMAIL_DB_HOST:=mysql}" + : "${ROUNDCUBEMAIL_DB_PORT:=3306}" + : "${ROUNDCUBEMAIL_DB_USER:=${MYSQL_ENV_MYSQL_USER:-root}}" + if [ "$ROUNDCUBEMAIL_DB_USER" = 'root' ]; then + : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_ROOT_PASSWORD}}" + else + : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD}}" + fi + : "${ROUNDCUBEMAIL_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-roundcubemail}}" + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + else + # use local SQLite DB in /var/roundcube/db + : "${ROUNDCUBEMAIL_DB_TYPE:=sqlite}" + : "${ROUNDCUBEMAIL_DB_DIR:=/var/roundcube/db}" + : "${ROUNDCUBEMAIL_DB_NAME:=sqlite}" + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}:///$ROUNDCUBEMAIL_DB_DIR/${ROUNDCUBEMAIL_DB_NAME}.db?mode=0646}" + + mkdir -p $ROUNDCUBEMAIL_DB_DIR + chown www-data:www-data $ROUNDCUBEMAIL_DB_DIR + fi + + : "${ROUNDCUBEMAIL_DEFAULT_HOST:=localhost}" + : "${ROUNDCUBEMAIL_DEFAULT_PORT:=143}" + : "${ROUNDCUBEMAIL_SMTP_SERVER:=localhost}" + : "${ROUNDCUBEMAIL_SMTP_PORT:=587}" + : "${ROUNDCUBEMAIL_PLUGINS:=archive,zipdownload}" + : "${ROUNDCUBEMAIL_SKIN:=elastic}" + : "${ROUNDCUBEMAIL_TEMP_DIR:=/tmp/roundcube-temp}" + + if [ ! -e config/config.inc.php ]; then + GENERATED_DES_KEY=`head /dev/urandom | base64 | head -c 24` + touch config/config.inc.php + + echo "Write root config to $PWD/config/config.inc.php" + echo " config/config.inc.php + + elif ! grep -q "config.docker.inc.php" config/config.inc.php; then + echo "include(__DIR__ . '/config.docker.inc.php');" >> config/config.inc.php + fi + + ROUNDCUBEMAIL_PLUGINS_PHP=`echo "${ROUNDCUBEMAIL_PLUGINS}" | sed -E "s/[, ]+/', '/g"` + echo "Write Docker config to $PWD/config/config.docker.inc.php" + echo " config/config.docker.inc.php + + if [ -e /run/secrets/roundcube_des_key ]; then + echo "\$config['des_key'] = file_get_contents('/run/secrets/roundcube_des_key');" >> config/config.docker.inc.php + elif [ ! -z "${ROUNDCUBEMAIL_DES_KEY}" ]; then + echo "\$config['des_key'] = getenv('ROUNDCUBEMAIL_DES_KEY');" >> config/config.docker.inc.php + fi + + if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}"]; then + echo "\$config['spellcheck_engine'] = 'googie';" >> config/config.docker.inc.php + echo "\$config['spellcheck_uri'] = '${ROUNDCUBEMAIL_SPELLCHECK_URI}';" >> config/config.docker.inc.php + fi + + # include custom config files + for fn in `ls /var/roundcube/config/*.php 2>/dev/null || true`; do + echo "include('$fn');" >> config/config.docker.inc.php + done + + # initialize or update DB + bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." + + if [ ! -z "${ROUNDCUBEMAIL_TEMP_DIR}" ]; then + mkdir -p ${ROUNDCUBEMAIL_TEMP_DIR} && chown www-data ${ROUNDCUBEMAIL_TEMP_DIR} + fi + + if [ ! -z "${ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE}" ]; then + echo "upload_max_filesize=${ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE}" >> /usr/local/etc/php/conf.d/roundcube-override.ini + echo "post_max_size=${ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE}" >> /usr/local/etc/php/conf.d/roundcube-override.ini + fi + + : "${ROUNDCUBEMAIL_LOCALE:=en_US.UTF-8 UTF-8}" + + if [ -e /usr/sbin/locale-gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then + echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen + /usr/sbin/locale-gen + fi + + if [ ! -z "${ROUNDCUBEMAIL_ASPELL_DICTS}" ]; then + ASPELL_PACKAGES=`echo -n "aspell-${ROUNDCUBEMAIL_ASPELL_DICTS}" | sed -E "s/[, ]+/ aspell-/g"` + which apt-get && apt-get install -y $ASPELL_PACKAGES + which apk && apk add --no-cache $ASPELL_PACKAGES + fi + +fi + +exec "$@" diff --git a/fpm-1.5.x/php.ini b/fpm-1.5.x/php.ini new file mode 100644 index 0000000000..7b2147df12 --- /dev/null +++ b/fpm-1.5.x/php.ini @@ -0,0 +1,10 @@ +memory_limit=64M +display_errors=Off +log_errors=On +upload_max_filesize=5M +post_max_size=6M +zlib.output_compression=Off +session.auto_start=Off +session.gc_maxlifetime=21600 +session.gc_divisor=500 +session.gc_probability=1 diff --git a/fpm-alpine-1.5.x/Dockerfile b/fpm-alpine-1.5.x/Dockerfile new file mode 100644 index 0000000000..c81e340da7 --- /dev/null +++ b/fpm-alpine-1.5.x/Dockerfile @@ -0,0 +1,110 @@ +FROM php:7.4-fpm-alpine +LABEL maintainer="Thomas Bruederli " + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apk upgrade --no-cache + +# entrypoint.sh and installto.sh dependencies +RUN set -ex; \ + \ + apk add --no-cache \ + bash \ + coreutils \ + rsync \ + tzdata \ + aspell \ + aspell-en + +RUN set -ex; \ + \ + apk add --no-cache --virtual .build-deps \ + $PHPIZE_DEPS \ + icu-dev \ + freetype-dev \ + imagemagick-dev \ + libjpeg-turbo-dev \ + libpng-dev \ + libzip-dev \ + libtool \ + openldap-dev \ + postgresql-dev \ + sqlite-dev \ + aspell-dev \ + ; \ + \ + docker-php-ext-configure gd --with-jpeg --with-freetype; \ + docker-php-ext-configure ldap; \ + docker-php-ext-install \ + exif \ + gd \ + intl \ + ldap \ + pdo_mysql \ + pdo_pgsql \ + pdo_sqlite \ + zip \ + pspell \ + ; \ + pecl install imagick redis; \ + docker-php-ext-enable imagick opcache redis; \ + \ + runDeps="$( \ + scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + )"; \ + apk add --virtual .roundcubemail-phpext-rundeps imagemagick $runDeps; \ + apk del .build-deps + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + + +# Define Roundcubemail version +ENV ROUNDCUBEMAIL_VERSION 1.5.11 + +# Define the GPG key used for the bundle verification process +ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" + +# Download package and extract to web volume +RUN set -ex; \ + apk add --no-cache --virtual .fetch-deps \ + gnupg \ + ; \ + \ + curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ + curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ + export GNUPGHOME="$(mktemp -d)"; \ + # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 + echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ + curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ + LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ + LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ + gpg --batch --import /tmp/pubkey.asc; \ + rm /tmp/pubkey.asc; \ + gpg --batch --verify roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ + gpgconf --kill all; \ + mkdir /usr/src/roundcubemail; \ + tar -xf roundcubemail.tar.gz -C /usr/src/roundcubemail --strip-components=1 --no-same-owner; \ + rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ + rm -rf /usr/src/roundcubemail/installer; \ + chown -R www-data:www-data /usr/src/roundcubemail/logs; \ + apk del .fetch-deps + +# copy the latest version of initdb.sh which supports the --update flag +RUN curl -fL https://raw.githubusercontent.com/roundcube/roundcubemail/master/bin/initdb.sh > /usr/src/roundcubemail/bin/initdb.sh && chmod +x /usr/src/roundcubemail/bin/initdb.sh + +# include the wait-for-it.sh script +RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini + +COPY --chmod=0755 docker-entrypoint.sh / + +RUN mkdir -p /var/roundcube/config + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["php-fpm"] diff --git a/fpm-alpine-1.5.x/docker-entrypoint.sh b/fpm-alpine-1.5.x/docker-entrypoint.sh new file mode 100755 index 0000000000..39379453b0 --- /dev/null +++ b/fpm-alpine-1.5.x/docker-entrypoint.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# set -ex + +# PWD=`pwd` + +if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then + # docroot is empty + if ! [ -e index.php -a -e bin/installto.sh ]; then + echo >&2 "roundcubemail not found in $PWD - copying now..." + if [ "$(ls -A)" ]; then + echo >&2 "WARNING: $PWD is not empty - press Ctrl+C now if this is an error!" + ( set -x; ls -A; sleep 10 ) + fi + tar cf - --one-file-system -C /usr/src/roundcubemail . | tar xf - + echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $PWD" + # update Roundcube in docroot + else + INSTALLDIR=`pwd` + echo >&2 "roundcubemail found in $INSTALLDIR - installing update..." + (cd /usr/src/roundcubemail && bin/installto.sh -y $INSTALLDIR) + composer update --no-dev + fi + + if [ -f /run/secrets/roundcube_db_user ]; then + ROUNDCUBEMAIL_DB_USER=`cat /run/secrets/roundcube_db_user` + fi + if [ -f /run/secrets/roundcube_db_password ]; then + ROUNDCUBEMAIL_DB_PASSWORD=`cat /run/secrets/roundcube_db_password` + fi + + if [ ! -z "${!POSTGRES_ENV_POSTGRES_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "pgsql" ]; then + : "${ROUNDCUBEMAIL_DB_TYPE:=pgsql}" + : "${ROUNDCUBEMAIL_DB_HOST:=postgres}" + : "${ROUNDCUBEMAIL_DB_PORT:=5432}" + : "${ROUNDCUBEMAIL_DB_USER:=${POSTGRES_ENV_POSTGRES_USER}}" + : "${ROUNDCUBEMAIL_DB_PASSWORD:=${POSTGRES_ENV_POSTGRES_PASSWORD}}" + : "${ROUNDCUBEMAIL_DB_NAME:=${POSTGRES_ENV_POSTGRES_DB:-roundcubemail}}" + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + elif [ ! -z "${!MYSQL_ENV_MYSQL_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "mysql" ]; then + : "${ROUNDCUBEMAIL_DB_TYPE:=mysql}" + : "${ROUNDCUBEMAIL_DB_HOST:=mysql}" + : "${ROUNDCUBEMAIL_DB_PORT:=3306}" + : "${ROUNDCUBEMAIL_DB_USER:=${MYSQL_ENV_MYSQL_USER:-root}}" + if [ "$ROUNDCUBEMAIL_DB_USER" = 'root' ]; then + : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_ROOT_PASSWORD}}" + else + : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD}}" + fi + : "${ROUNDCUBEMAIL_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-roundcubemail}}" + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + else + # use local SQLite DB in /var/roundcube/db + : "${ROUNDCUBEMAIL_DB_TYPE:=sqlite}" + : "${ROUNDCUBEMAIL_DB_DIR:=/var/roundcube/db}" + : "${ROUNDCUBEMAIL_DB_NAME:=sqlite}" + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}:///$ROUNDCUBEMAIL_DB_DIR/${ROUNDCUBEMAIL_DB_NAME}.db?mode=0646}" + + mkdir -p $ROUNDCUBEMAIL_DB_DIR + chown www-data:www-data $ROUNDCUBEMAIL_DB_DIR + fi + + : "${ROUNDCUBEMAIL_DEFAULT_HOST:=localhost}" + : "${ROUNDCUBEMAIL_DEFAULT_PORT:=143}" + : "${ROUNDCUBEMAIL_SMTP_SERVER:=localhost}" + : "${ROUNDCUBEMAIL_SMTP_PORT:=587}" + : "${ROUNDCUBEMAIL_PLUGINS:=archive,zipdownload}" + : "${ROUNDCUBEMAIL_SKIN:=elastic}" + : "${ROUNDCUBEMAIL_TEMP_DIR:=/tmp/roundcube-temp}" + + if [ ! -e config/config.inc.php ]; then + GENERATED_DES_KEY=`head /dev/urandom | base64 | head -c 24` + touch config/config.inc.php + + echo "Write root config to $PWD/config/config.inc.php" + echo " config/config.inc.php + + elif ! grep -q "config.docker.inc.php" config/config.inc.php; then + echo "include(__DIR__ . '/config.docker.inc.php');" >> config/config.inc.php + fi + + ROUNDCUBEMAIL_PLUGINS_PHP=`echo "${ROUNDCUBEMAIL_PLUGINS}" | sed -E "s/[, ]+/', '/g"` + echo "Write Docker config to $PWD/config/config.docker.inc.php" + echo " config/config.docker.inc.php + + if [ -e /run/secrets/roundcube_des_key ]; then + echo "\$config['des_key'] = file_get_contents('/run/secrets/roundcube_des_key');" >> config/config.docker.inc.php + elif [ ! -z "${ROUNDCUBEMAIL_DES_KEY}" ]; then + echo "\$config['des_key'] = getenv('ROUNDCUBEMAIL_DES_KEY');" >> config/config.docker.inc.php + fi + + if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}"]; then + echo "\$config['spellcheck_engine'] = 'googie';" >> config/config.docker.inc.php + echo "\$config['spellcheck_uri'] = '${ROUNDCUBEMAIL_SPELLCHECK_URI}';" >> config/config.docker.inc.php + fi + + # include custom config files + for fn in `ls /var/roundcube/config/*.php 2>/dev/null || true`; do + echo "include('$fn');" >> config/config.docker.inc.php + done + + # initialize or update DB + bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." + + if [ ! -z "${ROUNDCUBEMAIL_TEMP_DIR}" ]; then + mkdir -p ${ROUNDCUBEMAIL_TEMP_DIR} && chown www-data ${ROUNDCUBEMAIL_TEMP_DIR} + fi + + if [ ! -z "${ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE}" ]; then + echo "upload_max_filesize=${ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE}" >> /usr/local/etc/php/conf.d/roundcube-override.ini + echo "post_max_size=${ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE}" >> /usr/local/etc/php/conf.d/roundcube-override.ini + fi + + : "${ROUNDCUBEMAIL_LOCALE:=en_US.UTF-8 UTF-8}" + + if [ -e /usr/sbin/locale-gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then + echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen + /usr/sbin/locale-gen + fi + + if [ ! -z "${ROUNDCUBEMAIL_ASPELL_DICTS}" ]; then + ASPELL_PACKAGES=`echo -n "aspell-${ROUNDCUBEMAIL_ASPELL_DICTS}" | sed -E "s/[, ]+/ aspell-/g"` + which apt-get && apt-get install -y $ASPELL_PACKAGES + which apk && apk add --no-cache $ASPELL_PACKAGES + fi + +fi + +exec "$@" diff --git a/fpm-alpine-1.5.x/php.ini b/fpm-alpine-1.5.x/php.ini new file mode 100644 index 0000000000..7b2147df12 --- /dev/null +++ b/fpm-alpine-1.5.x/php.ini @@ -0,0 +1,10 @@ +memory_limit=64M +display_errors=Off +log_errors=On +upload_max_filesize=5M +post_max_size=6M +zlib.output_compression=Off +session.auto_start=Off +session.gc_maxlifetime=21600 +session.gc_divisor=500 +session.gc_probability=1 diff --git a/fpm-alpine/Dockerfile b/fpm-alpine/Dockerfile index 08fc22d79f..a7192bc3fe 100644 --- a/fpm-alpine/Dockerfile +++ b/fpm-alpine/Dockerfile @@ -1,8 +1,14 @@ -FROM php:7.4-fpm-alpine +FROM php:8.4-fpm-alpine3.21 AS root LABEL maintainer="Thomas Bruederli " +LABEL org.opencontainers.image.source="https://github.com/roundcube/roundcubemail-docker" + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apk upgrade --no-cache -# entrypoint.sh and installto.sh dependencies RUN set -ex; \ + if [ "fpm-alpine" = "apache" ]; then a2enmod rewrite; fi; \ \ apk add --no-cache \ bash \ @@ -10,7 +16,8 @@ RUN set -ex; \ rsync \ tzdata \ aspell \ - aspell-en + aspell-en \ + unzip RUN set -ex; \ \ @@ -24,13 +31,18 @@ RUN set -ex; \ libzip-dev \ libtool \ openldap-dev \ + cyrus-sasl-dev \ postgresql-dev \ sqlite-dev \ aspell-dev \ ; \ \ +# Extract sources to avoid using pecl (https://github.com/docker-library/php/issues/374#issuecomment-690698974) + pecl bundle -d /usr/src/php/ext imagick; \ + pecl bundle -d /usr/src/php/ext redis; \ + pecl bundle -d /usr/src/php/ext pspell; \ docker-php-ext-configure gd --with-jpeg --with-freetype; \ - docker-php-ext-configure ldap; \ + docker-php-ext-configure ldap --with-ldap-sasl; \ docker-php-ext-install \ exif \ gd \ @@ -41,45 +53,52 @@ RUN set -ex; \ pdo_sqlite \ zip \ pspell \ + imagick \ + redis \ ; \ - pecl install imagick redis; \ docker-php-ext-enable imagick opcache redis; \ + docker-php-source delete; \ + rm -r /tmp/pear; \ +# Display installed modules + php -m; \ \ + extdir="$(php -r 'echo ini_get("extension_dir");')"; \ runDeps="$( \ - scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \ + scanelf --needed --nobanner --format '%n#p' --recursive $extdir \ | tr ',' '\n' \ | sort -u \ | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ )"; \ apk add --virtual .roundcubemail-phpext-rundeps imagemagick $runDeps; \ - apk del .build-deps + apk del .build-deps; \ + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ] || (echo "Sanity check failed: php returned errors; $err"; exit 1;); \ +# include the wait-for-it.sh script (latest commit) + curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh -o /wait-for-it.sh; \ + chmod +x /wait-for-it.sh; COPY --from=composer:2 /usr/bin/composer /usr/bin/composer +# Use the default production configuration +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -# expose these volumes -VOLUME /var/roundcube/config -VOLUME /var/roundcube/db -VOLUME /var/www/html -VOLUME /tmp/roundcube-temp +COPY --chmod=0755 docker-entrypoint.sh / # Define Roundcubemail version -ENV ROUNDCUBEMAIL_VERSION 1.5.2 +ENV ROUNDCUBEMAIL_VERSION 1.6.14 # Define the GPG key used for the bundle verification process ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" # Download package and extract to web volume RUN set -ex; \ - apk add --no-cache --virtual .fetch-deps \ - gnupg \ - ; \ - \ + apk add --no-cache gnupg; \ curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ export GNUPGHOME="$(mktemp -d)"; \ - # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 - echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ @@ -92,15 +111,17 @@ RUN set -ex; \ rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ rm -rf /usr/src/roundcubemail/installer; \ chown -R www-data:www-data /usr/src/roundcubemail/logs; \ - apk del .fetch-deps +# Create the config dir + mkdir -p /var/roundcube/config /var/roundcube/enigma; \ + chown -R www-data:www-data /var/roundcube; \ + chmod +t /var/roundcube -# include the wait-for-it.sh script -RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["php-fpm"] -# use custom PHP settings -COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -COPY docker-entrypoint.sh / +#### non-root stage -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["php-fpm"] +FROM root AS nonroot + +USER 82:82 diff --git a/fpm-alpine/docker-entrypoint.sh b/fpm-alpine/docker-entrypoint.sh index 50afaa656c..600d0f04bd 100755 --- a/fpm-alpine/docker-entrypoint.sh +++ b/fpm-alpine/docker-entrypoint.sh @@ -3,7 +3,37 @@ # PWD=`pwd` -if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then +run_entrypoint_tasks() { + phase="$1" + shift + shopt -s nullglob + echo "Running $phase-setup tasks:" + for file in /entrypoint-tasks/"$phase-setup"/*; do + if test ! -f "$file"; then + echo "Ignoring $file because it is not a regular file." + continue; + fi + if test ! -x "$file"; then + echo "Ignoring $file because it is not executable." + continue; + fi + echo "Running $phase-setup task $file:" + "$file" "$@" + # Exit in case of an error in an executable. + exit_code=$? + if test $exit_code -ne 0; then + echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!" + exit $exit_code + fi + echo 'Done.' + done + shopt -u nullglob +} + +if [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then + run_entrypoint_tasks pre "$@" + + INSTALLDIR=`pwd` # docroot is empty if ! [ -e index.php -a -e bin/installto.sh ]; then echo >&2 "roundcubemail not found in $PWD - copying now..." @@ -12,13 +42,19 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then ( set -x; ls -A; sleep 10 ) fi tar cf - --one-file-system -C /usr/src/roundcubemail . | tar xf - - echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $PWD" + echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $INSTALLDIR" # update Roundcube in docroot else - INSTALLDIR=`pwd` echo >&2 "roundcubemail found in $INSTALLDIR - installing update..." (cd /usr/src/roundcubemail && bin/installto.sh -y $INSTALLDIR) - composer update --no-dev + # Re-install composer modules (including plugins) + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --no-dev \ + --no-interaction \ + --optimize-autoloader \ + install fi if [ -f /run/secrets/roundcube_db_user ]; then @@ -27,6 +63,9 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then if [ -f /run/secrets/roundcube_db_password ]; then ROUNDCUBEMAIL_DB_PASSWORD=`cat /run/secrets/roundcube_db_password` fi + if [ -f /run/secrets/roundcube_oauth_client_secret ]; then + ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET=`cat /run/secrets/roundcube_oauth_client_secret` + fi if [ ! -z "${!POSTGRES_ENV_POSTGRES_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "pgsql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=pgsql}" @@ -35,9 +74,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_USER:=${POSTGRES_ENV_POSTGRES_USER}}" : "${ROUNDCUBEMAIL_DB_PASSWORD:=${POSTGRES_ENV_POSTGRES_PASSWORD}}" : "${ROUNDCUBEMAIL_DB_NAME:=${POSTGRES_ENV_POSTGRES_DB:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi elif [ ! -z "${!MYSQL_ENV_MYSQL_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "mysql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=mysql}" : "${ROUNDCUBEMAIL_DB_HOST:=mysql}" @@ -49,9 +92,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD}}" fi : "${ROUNDCUBEMAIL_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi else # use local SQLite DB in /var/roundcube/db : "${ROUNDCUBEMAIL_DB_TYPE:=sqlite}" @@ -70,6 +117,42 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_PLUGINS:=archive,zipdownload}" : "${ROUNDCUBEMAIL_SKIN:=elastic}" : "${ROUNDCUBEMAIL_TEMP_DIR:=/tmp/roundcube-temp}" + : "${ROUNDCUBEMAIL_REQUEST_PATH:=/}" + : "${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER:=$INSTALLDIR}" + + if [ ! -z "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" ]; then + echo "Installing plugins from the list" + echo "Plugins: ${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" + + # Change ',' into a space + ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH=`echo "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" | tr ',' ' '` + + composer \ + --working-dir=${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + ${ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH}; + fi + + if [ ! -d skins/${ROUNDCUBEMAIL_SKIN} ]; then + # Installing missing skin + echo "Installing missing skin: ${ROUNDCUBEMAIL_SKIN}" + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + roundcube/${ROUNDCUBEMAIL_SKIN}; + fi if [ ! -e config/config.inc.php ]; then GENERATED_DES_KEY=`head /dev/urandom | base64 | head -c 24` @@ -95,12 +178,12 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo " config/config.docker.inc.php @@ -110,18 +193,27 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo "\$config['des_key'] = getenv('ROUNDCUBEMAIL_DES_KEY');" >> config/config.docker.inc.php fi - if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}"]; then + if [ ! -z "${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}" ]; then + echo "\$config['oauth_client_secret'] = '${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}';" >> config/config.docker.inc.php + fi + + if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}" ]; then echo "\$config['spellcheck_engine'] = 'googie';" >> config/config.docker.inc.php echo "\$config['spellcheck_uri'] = '${ROUNDCUBEMAIL_SPELLCHECK_URI}';" >> config/config.docker.inc.php fi + # If the "enigma" plugin is enabled but has no storage configured, inject a default value for the mandatory setting. + if $(echo $ROUNDCUBEMAIL_PLUGINS | grep -Eq '\benigma\b') && ! grep -qr enigma_pgp_homedir /var/roundcube/config/; then + echo "\$config['enigma_pgp_homedir'] = '/var/roundcube/enigma';" >> config/config.docker.inc.php + fi + # include custom config files for fn in `ls /var/roundcube/config/*.php 2>/dev/null || true`; do echo "include('$fn');" >> config/config.docker.inc.php done # initialize or update DB - bin/initdb.sh --dir=$PWD/SQL --create || bin/updatedb.sh --dir=$PWD/SQL --package=roundcube || echo "Failed to initialize database. Please run $PWD/bin/initdb.sh and $PWD/bin/updatedb.sh manually." + bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." if [ ! -z "${ROUNDCUBEMAIL_TEMP_DIR}" ]; then mkdir -p ${ROUNDCUBEMAIL_TEMP_DIR} && chown www-data ${ROUNDCUBEMAIL_TEMP_DIR} @@ -134,17 +226,17 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_LOCALE:=en_US.UTF-8 UTF-8}" - if [ -e /usr/sbin/locale-gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then - echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen - /usr/sbin/locale-gen + if [ -e /usr/sbin/locale-gen ] && [ ! -f /etc/locale.gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then + echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen && /usr/sbin/locale-gen fi if [ ! -z "${ROUNDCUBEMAIL_ASPELL_DICTS}" ]; then ASPELL_PACKAGES=`echo -n "aspell-${ROUNDCUBEMAIL_ASPELL_DICTS}" | sed -E "s/[, ]+/ aspell-/g"` - which apt-get && apt-get install -y $ASPELL_PACKAGES + which apt-get && apt-get update && apt-get install -y $ASPELL_PACKAGES which apk && apk add --no-cache $ASPELL_PACKAGES fi + run_entrypoint_tasks post "$@" fi exec "$@" diff --git a/fpm/Dockerfile b/fpm/Dockerfile index 62e0939443..a489f83d8d 100644 --- a/fpm/Dockerfile +++ b/fpm/Dockerfile @@ -1,7 +1,20 @@ -FROM php:7.4-fpm +FROM php:8.4-fpm AS root LABEL maintainer="Thomas Bruederli " +LABEL org.opencontainers.image.source="https://github.com/roundcube/roundcubemail-docker" + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apt-get update && apt-get -y upgrade && apt-get clean RUN set -ex; \ + if [ "fpm" = "apache" ]; then \ + a2enmod rewrite; \ + # Make Apache use public_html/ as document root to protect files outside of it \ + # against unauthorized access. \ + # This is possible and recommended since a while, and will be required for Roundcubemail v1.7. \ + sed -i -e 's|\(DocumentRoot /var/www/html\)$|\1/public_html|' /etc/apache2/sites-available/000-default.conf; \ + fi; \ apt-get update; \ \ savedAptMark="$(apt-mark showmanual)"; \ @@ -11,6 +24,7 @@ RUN set -ex; \ libicu-dev \ libjpeg62-turbo-dev \ libldap2-dev \ + libsasl2-dev \ libmagickwand-dev \ libpng-dev \ libpq-dev \ @@ -20,10 +34,21 @@ RUN set -ex; \ libonig-dev \ libldap-common \ ; \ +# installto.sh & web install dependencies + fetchDeps="gnupg locales libc-l10n"; \ + installDeps="aspell aspell-en rsync unzip"; \ + apt-get install -y --no-install-recommends \ + $installDeps \ + $fetchDeps \ + ; \ \ +# Extract sources to avoid using pecl (https://github.com/docker-library/php/issues/374#issuecomment-690698974) + pecl bundle -d /usr/src/php/ext imagick; \ + pecl bundle -d /usr/src/php/ext redis; \ + pecl bundle -d /usr/src/php/ext pspell; \ debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ docker-php-ext-configure gd --with-jpeg --with-freetype; \ - docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \ + docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch" --with-ldap-sasl; \ docker-php-ext-install \ exif \ gd \ @@ -34,15 +59,21 @@ RUN set -ex; \ pdo_sqlite \ zip \ pspell \ + imagick \ + redis \ ; \ - pecl install imagick redis; \ docker-php-ext-enable imagick opcache redis; \ + docker-php-source delete; \ + rm -r /tmp/pear; \ +# Display installed modules + php -m; \ \ # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark; \ - ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ - | awk '/=>/ { print $3 }' \ + apt-mark manual $savedAptMark $installDeps $fetchDeps; \ + extdir="$(php -r 'echo ini_get("extension_dir");')"; \ + ldd "$extdir"/*.so \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ | sort -u \ | xargs -r dpkg-query -S \ | cut -d: -f1 \ @@ -50,44 +81,36 @@ RUN set -ex; \ | xargs -rt apt-mark manual; \ \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - rm -rf /var/lib/apt/lists/* - -# installto.sh dependencies -RUN set -ex; \ - \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - aspell \ - aspell-en \ - rsync \ - ; \ - rm -rf /var/lib/apt/lists/* + rm -rf /var/lib/apt/lists/*; \ + ldd "$extdir"/*.so | grep -qzv "=> not found" || (echo "Sanity check failed: missing libraries:"; ldd "$extdir"/*.so | grep " => not found"; exit 1); \ + ldd "$extdir"/*.so | grep -q "libzip.so.* => .*/libzip.so.*" || (echo "Sanity check failed: libzip.so is not referenced"; ldd "$extdir"/*.so; exit 1); \ + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ] || (echo "Sanity check failed: php returned errors; $err"; exit 1;); \ +# include the wait-for-it.sh script (latest commit) + curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh -o /wait-for-it.sh; \ + chmod +x /wait-for-it.sh; COPY --from=composer:2 /usr/bin/composer /usr/bin/composer +# Use the default production configuration +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -# expose these volumes -VOLUME /var/roundcube/config -VOLUME /var/roundcube/db -VOLUME /var/www/html -VOLUME /tmp/roundcube-temp +COPY --chmod=0755 docker-entrypoint.sh / # Define Roundcubemail version -ENV ROUNDCUBEMAIL_VERSION 1.5.2 +ENV ROUNDCUBEMAIL_VERSION 1.6.14 # Define the GPG key used for the bundle verification process ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" # Download package and extract to web volume RUN set -ex; \ - fetchDeps="gnupg dirmngr locales libc-l10n"; \ - apt-get -qq update; \ - apt-get install -y --no-install-recommends $fetchDeps; \ curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ export GNUPGHOME="$(mktemp -d)"; \ - # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 - echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ @@ -99,15 +122,24 @@ RUN set -ex; \ tar -xf roundcubemail.tar.gz -C /usr/src/roundcubemail --strip-components=1 --no-same-owner; \ rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ rm -rf /usr/src/roundcubemail/installer; \ - chown -R www-data:www-data /usr/src/roundcubemail/logs + chown -R www-data:www-data /usr/src/roundcubemail/logs; \ +# Create the config dir + mkdir -p /var/roundcube/config /var/roundcube/enigma; \ + chown -R www-data:www-data /var/roundcube; \ + chmod +t /var/roundcube -# include the wait-for-it.sh script -RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["php-fpm"] -# use custom PHP settings -COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -COPY docker-entrypoint.sh / +#### non-root stage -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["php-fpm"] +FROM root AS nonroot + +# Prepare locale config for locale-gen +RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen; \ + /usr/sbin/locale-gen + + + +USER 33:33 diff --git a/fpm/docker-entrypoint.sh b/fpm/docker-entrypoint.sh index 50afaa656c..600d0f04bd 100755 --- a/fpm/docker-entrypoint.sh +++ b/fpm/docker-entrypoint.sh @@ -3,7 +3,37 @@ # PWD=`pwd` -if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then +run_entrypoint_tasks() { + phase="$1" + shift + shopt -s nullglob + echo "Running $phase-setup tasks:" + for file in /entrypoint-tasks/"$phase-setup"/*; do + if test ! -f "$file"; then + echo "Ignoring $file because it is not a regular file." + continue; + fi + if test ! -x "$file"; then + echo "Ignoring $file because it is not executable." + continue; + fi + echo "Running $phase-setup task $file:" + "$file" "$@" + # Exit in case of an error in an executable. + exit_code=$? + if test $exit_code -ne 0; then + echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!" + exit $exit_code + fi + echo 'Done.' + done + shopt -u nullglob +} + +if [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then + run_entrypoint_tasks pre "$@" + + INSTALLDIR=`pwd` # docroot is empty if ! [ -e index.php -a -e bin/installto.sh ]; then echo >&2 "roundcubemail not found in $PWD - copying now..." @@ -12,13 +42,19 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then ( set -x; ls -A; sleep 10 ) fi tar cf - --one-file-system -C /usr/src/roundcubemail . | tar xf - - echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $PWD" + echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $INSTALLDIR" # update Roundcube in docroot else - INSTALLDIR=`pwd` echo >&2 "roundcubemail found in $INSTALLDIR - installing update..." (cd /usr/src/roundcubemail && bin/installto.sh -y $INSTALLDIR) - composer update --no-dev + # Re-install composer modules (including plugins) + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --no-dev \ + --no-interaction \ + --optimize-autoloader \ + install fi if [ -f /run/secrets/roundcube_db_user ]; then @@ -27,6 +63,9 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then if [ -f /run/secrets/roundcube_db_password ]; then ROUNDCUBEMAIL_DB_PASSWORD=`cat /run/secrets/roundcube_db_password` fi + if [ -f /run/secrets/roundcube_oauth_client_secret ]; then + ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET=`cat /run/secrets/roundcube_oauth_client_secret` + fi if [ ! -z "${!POSTGRES_ENV_POSTGRES_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "pgsql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=pgsql}" @@ -35,9 +74,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_USER:=${POSTGRES_ENV_POSTGRES_USER}}" : "${ROUNDCUBEMAIL_DB_PASSWORD:=${POSTGRES_ENV_POSTGRES_PASSWORD}}" : "${ROUNDCUBEMAIL_DB_NAME:=${POSTGRES_ENV_POSTGRES_DB:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi elif [ ! -z "${!MYSQL_ENV_MYSQL_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "mysql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=mysql}" : "${ROUNDCUBEMAIL_DB_HOST:=mysql}" @@ -49,9 +92,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD}}" fi : "${ROUNDCUBEMAIL_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi else # use local SQLite DB in /var/roundcube/db : "${ROUNDCUBEMAIL_DB_TYPE:=sqlite}" @@ -70,6 +117,42 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_PLUGINS:=archive,zipdownload}" : "${ROUNDCUBEMAIL_SKIN:=elastic}" : "${ROUNDCUBEMAIL_TEMP_DIR:=/tmp/roundcube-temp}" + : "${ROUNDCUBEMAIL_REQUEST_PATH:=/}" + : "${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER:=$INSTALLDIR}" + + if [ ! -z "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" ]; then + echo "Installing plugins from the list" + echo "Plugins: ${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" + + # Change ',' into a space + ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH=`echo "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" | tr ',' ' '` + + composer \ + --working-dir=${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + ${ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH}; + fi + + if [ ! -d skins/${ROUNDCUBEMAIL_SKIN} ]; then + # Installing missing skin + echo "Installing missing skin: ${ROUNDCUBEMAIL_SKIN}" + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + roundcube/${ROUNDCUBEMAIL_SKIN}; + fi if [ ! -e config/config.inc.php ]; then GENERATED_DES_KEY=`head /dev/urandom | base64 | head -c 24` @@ -95,12 +178,12 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo " config/config.docker.inc.php @@ -110,18 +193,27 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo "\$config['des_key'] = getenv('ROUNDCUBEMAIL_DES_KEY');" >> config/config.docker.inc.php fi - if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}"]; then + if [ ! -z "${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}" ]; then + echo "\$config['oauth_client_secret'] = '${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}';" >> config/config.docker.inc.php + fi + + if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}" ]; then echo "\$config['spellcheck_engine'] = 'googie';" >> config/config.docker.inc.php echo "\$config['spellcheck_uri'] = '${ROUNDCUBEMAIL_SPELLCHECK_URI}';" >> config/config.docker.inc.php fi + # If the "enigma" plugin is enabled but has no storage configured, inject a default value for the mandatory setting. + if $(echo $ROUNDCUBEMAIL_PLUGINS | grep -Eq '\benigma\b') && ! grep -qr enigma_pgp_homedir /var/roundcube/config/; then + echo "\$config['enigma_pgp_homedir'] = '/var/roundcube/enigma';" >> config/config.docker.inc.php + fi + # include custom config files for fn in `ls /var/roundcube/config/*.php 2>/dev/null || true`; do echo "include('$fn');" >> config/config.docker.inc.php done # initialize or update DB - bin/initdb.sh --dir=$PWD/SQL --create || bin/updatedb.sh --dir=$PWD/SQL --package=roundcube || echo "Failed to initialize database. Please run $PWD/bin/initdb.sh and $PWD/bin/updatedb.sh manually." + bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." if [ ! -z "${ROUNDCUBEMAIL_TEMP_DIR}" ]; then mkdir -p ${ROUNDCUBEMAIL_TEMP_DIR} && chown www-data ${ROUNDCUBEMAIL_TEMP_DIR} @@ -134,17 +226,17 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_LOCALE:=en_US.UTF-8 UTF-8}" - if [ -e /usr/sbin/locale-gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then - echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen - /usr/sbin/locale-gen + if [ -e /usr/sbin/locale-gen ] && [ ! -f /etc/locale.gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then + echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen && /usr/sbin/locale-gen fi if [ ! -z "${ROUNDCUBEMAIL_ASPELL_DICTS}" ]; then ASPELL_PACKAGES=`echo -n "aspell-${ROUNDCUBEMAIL_ASPELL_DICTS}" | sed -E "s/[, ]+/ aspell-/g"` - which apt-get && apt-get install -y $ASPELL_PACKAGES + which apt-get && apt-get update && apt-get install -y $ASPELL_PACKAGES which apk && apk add --no-cache $ASPELL_PACKAGES fi + run_entrypoint_tasks post "$@" fi exec "$@" diff --git a/nightly/Dockerfile b/nightly/Dockerfile index 4a1fa23778..07a916d88f 100644 --- a/nightly/Dockerfile +++ b/nightly/Dockerfile @@ -1,100 +1,23 @@ -FROM php:7.4-apache AS base -LABEL maintainer="Thomas Bruederli " - -RUN set -ex; \ - apt-get update; \ - \ - savedAptMark="$(apt-mark showmanual)"; \ - \ - apt-get install -y --no-install-recommends \ - libfreetype6-dev \ - libicu-dev \ - libjpeg62-turbo-dev \ - libldap2-dev \ - libmagickwand-dev \ - libpng-dev \ - libpq-dev \ - libsqlite3-dev \ - libzip-dev \ - ; \ - \ - debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ - docker-php-ext-configure gd; \ - docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \ - docker-php-ext-install \ - exif \ - gd \ - intl \ - ldap \ - pdo_mysql \ - pdo_pgsql \ - pdo_sqlite \ - zip \ - ; \ - pecl install imagick redis; \ - docker-php-ext-enable imagick opcache redis; \ - \ -# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies - apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark; \ - ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ - | awk '/=>/ { print $3 }' \ - | sort -u \ - | xargs -r dpkg-query -S \ - | cut -d: -f1 \ - | sort -u \ - | xargs -rt apt-mark manual; \ - \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - rm -rf /var/lib/apt/lists/* - -# installto.sh dependencies -RUN set -ex; \ - \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - rsync \ - ; \ - rm -rf /var/lib/apt/lists/* - -# ... and composer.phar -ADD https://getcomposer.org/installer /tmp/composer-installer.php - -RUN php /tmp/composer-installer.php --install-dir=/usr/local/bin/; \ - rm /tmp/composer-installer.php - -RUN a2enmod rewrite - -### Temporary build image -FROM base AS builder +### Temporary build container to use npm in. +FROM docker.io/roundcube/roundcubemail:latest-apache AS builder # install nodejs and lessc compiler -RUN apt-get -qq update; \ - apt-get install -y --no-install-recommends unzip gnupg dirmngr; \ - curl -sL https://deb.nodesource.com/setup_14.x | bash -; \ - apt-get install -y nodejs; \ - npm install -g less; \ - npm install -g uglify-js; \ - npm install -g lessc; \ - npm install -g less-plugin-clean-css; \ - npm install -g csso-cli +RUN apt-get -qq update && apt-get install -y --no-install-recommends npm git # Download source and build package into src directory RUN set -ex; \ + rm -rf /usr/src/roundcubemail; \ curl -o roundcubemail.tar.gz -SL https://github.com/roundcube/roundcubemail/archive/master.tar.gz; \ tar -xzf roundcubemail.tar.gz -C /usr/src/; \ rm roundcubemail.tar.gz; \ mv /usr/src/roundcubemail-master /usr/src/roundcubemail; \ cd /usr/src/roundcubemail; \ - rm -rf installer tests public_html .ci .github .gitignore .editorconfig .tx .travis.yml; \ - (cd /usr/src/roundcubemail/skins/elastic; \ - lessc --clean-css="--s1 --advanced" styles/styles.less > styles/styles.min.css; \ - lessc --clean-css="--s1 --advanced" styles/print.less > styles/print.css; \ - lessc --clean-css="--s1 --advanced" styles/embed.less > styles/embed.css); \ - mv composer.json-dist composer.json; \ - composer.phar require kolab/net_ldap3 --no-install; \ - composer.phar require bjeavons/zxcvbn-php --no-install; \ - composer.phar install --no-dev --prefer-dist; \ + rm -rf installer tests .ci .github .gitignore .editorconfig .tx .travis.yml; \ + [ -f public_html/installer.php ] && rm -f public_html/installer.php; \ + make css-elastic; \ + composer require kolab/net_ldap3 --no-install; \ + composer require bjeavons/zxcvbn-php --no-install; \ + composer install --no-dev --prefer-dist; \ bin/install-jsdeps.sh; \ bin/updatecss.sh; \ rm -rf vendor/masterminds/html5/test \ @@ -110,24 +33,7 @@ RUN set -ex; \ temp/js_cache ### Final image -FROM base +FROM docker.io/roundcube/roundcubemail:latest-apache RUN mkdir -p /usr/src COPY --from=builder /usr/src/roundcubemail /usr/src/roundcubemail - -# expose these volumes -VOLUME /var/roundcube/config -VOLUME /var/roundcube/db -VOLUME /var/www/html -VOLUME /tmp/roundcube-temp - -# include the wait-for-it.sh script -RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh - -# use custom PHP settings -COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini - -COPY docker-entrypoint.sh / - -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["apache2-foreground"] diff --git a/templates/Dockerfile-alpine.templ b/templates/Dockerfile-alpine.templ index 7a9bacc3dd..a5f4212601 100644 --- a/templates/Dockerfile-alpine.templ +++ b/templates/Dockerfile-alpine.templ @@ -1,8 +1,14 @@ -FROM php:7.4-%%VARIANT%% +FROM php:8.4-%%VARIANT%%3.21 AS root LABEL maintainer="Thomas Bruederli " +LABEL org.opencontainers.image.source="https://github.com/roundcube/roundcubemail-docker" + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apk upgrade --no-cache -# entrypoint.sh and installto.sh dependencies RUN set -ex; \ + if [ "%%VARIANT%%" = "apache" ]; then a2enmod rewrite; fi; \ \ apk add --no-cache \ bash \ @@ -10,7 +16,8 @@ RUN set -ex; \ rsync \ tzdata \ aspell \ - aspell-en + aspell-en \ + unzip RUN set -ex; \ \ @@ -24,13 +31,18 @@ RUN set -ex; \ libzip-dev \ libtool \ openldap-dev \ + cyrus-sasl-dev \ postgresql-dev \ sqlite-dev \ aspell-dev \ ; \ \ +# Extract sources to avoid using pecl (https://github.com/docker-library/php/issues/374#issuecomment-690698974) + pecl bundle -d /usr/src/php/ext imagick; \ + pecl bundle -d /usr/src/php/ext redis; \ + pecl bundle -d /usr/src/php/ext pspell; \ docker-php-ext-configure gd --with-jpeg --with-freetype; \ - docker-php-ext-configure ldap; \ + docker-php-ext-configure ldap --with-ldap-sasl; \ docker-php-ext-install \ exif \ gd \ @@ -41,27 +53,39 @@ RUN set -ex; \ pdo_sqlite \ zip \ pspell \ + imagick \ + redis \ ; \ - pecl install imagick redis; \ docker-php-ext-enable imagick opcache redis; \ + docker-php-source delete; \ + rm -r /tmp/pear; \ +# Display installed modules + php -m; \ \ + extdir="$(php -r 'echo ini_get("extension_dir");')"; \ runDeps="$( \ - scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \ + scanelf --needed --nobanner --format '%n#p' --recursive $extdir \ | tr ',' '\n' \ | sort -u \ | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ )"; \ apk add --virtual .roundcubemail-phpext-rundeps imagemagick $runDeps; \ - apk del .build-deps + apk del .build-deps; \ + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ] || (echo "Sanity check failed: php returned errors; $err"; exit 1;); \ +# include the wait-for-it.sh script (latest commit) + curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh -o /wait-for-it.sh; \ + chmod +x /wait-for-it.sh; COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -%%EXTRAS%% -# expose these volumes -VOLUME /var/roundcube/config -VOLUME /var/roundcube/db -VOLUME /var/www/html -VOLUME /tmp/roundcube-temp +# Use the default production configuration +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini + +COPY --chmod=0755 docker-entrypoint.sh / # Define Roundcubemail version ENV ROUNDCUBEMAIL_VERSION %%VERSION%% @@ -71,15 +95,10 @@ ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" # Download package and extract to web volume RUN set -ex; \ - apk add --no-cache --virtual .fetch-deps \ - gnupg \ - ; \ - \ + apk add --no-cache gnupg; \ curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ export GNUPGHOME="$(mktemp -d)"; \ - # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 - echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ @@ -92,15 +111,17 @@ RUN set -ex; \ rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ rm -rf /usr/src/roundcubemail/installer; \ chown -R www-data:www-data /usr/src/roundcubemail/logs; \ - apk del .fetch-deps +# Create the config dir + mkdir -p /var/roundcube/config /var/roundcube/enigma; \ + chown -R www-data:www-data /var/roundcube; \ + chmod +t /var/roundcube -# include the wait-for-it.sh script -RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["%%CMD%%"] -# use custom PHP settings -COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -COPY docker-entrypoint.sh / +#### non-root stage -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["%%CMD%%"] +FROM root AS nonroot + +USER 82:82 diff --git a/templates/Dockerfile-debian.templ b/templates/Dockerfile-debian.templ index 905e78f2eb..416c29717c 100644 --- a/templates/Dockerfile-debian.templ +++ b/templates/Dockerfile-debian.templ @@ -1,7 +1,20 @@ -FROM php:7.4-%%VARIANT%% +FROM php:8.4-%%VARIANT%% AS root LABEL maintainer="Thomas Bruederli " +LABEL org.opencontainers.image.source="https://github.com/roundcube/roundcubemail-docker" + +# This should be done by the upstream images, but as long as they don't do it, +# we rather use our own hands than suffer from outdated packages. +# Kept as standalone command to make it stand out and be easy to remove. +RUN apt-get update && apt-get -y upgrade && apt-get clean RUN set -ex; \ + if [ "%%VARIANT%%" = "apache" ]; then \ + a2enmod rewrite; \ + # Make Apache use public_html/ as document root to protect files outside of it \ + # against unauthorized access. \ + # This is possible and recommended since a while, and will be required for Roundcubemail v1.7. \ + sed -i -e 's|\(DocumentRoot /var/www/html\)$|\1/public_html|' /etc/apache2/sites-available/000-default.conf; \ + fi; \ apt-get update; \ \ savedAptMark="$(apt-mark showmanual)"; \ @@ -11,6 +24,7 @@ RUN set -ex; \ libicu-dev \ libjpeg62-turbo-dev \ libldap2-dev \ + libsasl2-dev \ libmagickwand-dev \ libpng-dev \ libpq-dev \ @@ -20,10 +34,21 @@ RUN set -ex; \ libonig-dev \ libldap-common \ ; \ +# installto.sh & web install dependencies + fetchDeps="gnupg locales libc-l10n"; \ + installDeps="aspell aspell-en rsync unzip"; \ + apt-get install -y --no-install-recommends \ + $installDeps \ + $fetchDeps \ + ; \ \ +# Extract sources to avoid using pecl (https://github.com/docker-library/php/issues/374#issuecomment-690698974) + pecl bundle -d /usr/src/php/ext imagick; \ + pecl bundle -d /usr/src/php/ext redis; \ + pecl bundle -d /usr/src/php/ext pspell; \ debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ docker-php-ext-configure gd --with-jpeg --with-freetype; \ - docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \ + docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch" --with-ldap-sasl; \ docker-php-ext-install \ exif \ gd \ @@ -34,15 +59,21 @@ RUN set -ex; \ pdo_sqlite \ zip \ pspell \ + imagick \ + redis \ ; \ - pecl install imagick redis; \ docker-php-ext-enable imagick opcache redis; \ + docker-php-source delete; \ + rm -r /tmp/pear; \ +# Display installed modules + php -m; \ \ # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies apt-mark auto '.*' > /dev/null; \ - apt-mark manual $savedAptMark; \ - ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ - | awk '/=>/ { print $3 }' \ + apt-mark manual $savedAptMark $installDeps $fetchDeps; \ + extdir="$(php -r 'echo ini_get("extension_dir");')"; \ + ldd "$extdir"/*.so \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ | sort -u \ | xargs -r dpkg-query -S \ | cut -d: -f1 \ @@ -50,27 +81,24 @@ RUN set -ex; \ | xargs -rt apt-mark manual; \ \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ - rm -rf /var/lib/apt/lists/* - -# installto.sh dependencies -RUN set -ex; \ - \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - aspell \ - aspell-en \ - rsync \ - ; \ - rm -rf /var/lib/apt/lists/* + rm -rf /var/lib/apt/lists/*; \ + ldd "$extdir"/*.so | grep -qzv "=> not found" || (echo "Sanity check failed: missing libraries:"; ldd "$extdir"/*.so | grep " => not found"; exit 1); \ + ldd "$extdir"/*.so | grep -q "libzip.so.* => .*/libzip.so.*" || (echo "Sanity check failed: libzip.so is not referenced"; ldd "$extdir"/*.so; exit 1); \ + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ] || (echo "Sanity check failed: php returned errors; $err"; exit 1;); \ +# include the wait-for-it.sh script (latest commit) + curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh -o /wait-for-it.sh; \ + chmod +x /wait-for-it.sh; COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -%%EXTRAS%% -# expose these volumes -VOLUME /var/roundcube/config -VOLUME /var/roundcube/db -VOLUME /var/www/html -VOLUME /tmp/roundcube-temp +# Use the default production configuration +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# use custom PHP settings +COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini + +COPY --chmod=0755 docker-entrypoint.sh / # Define Roundcubemail version ENV ROUNDCUBEMAIL_VERSION %%VERSION%% @@ -80,14 +108,9 @@ ENV ROUNDCUBEMAIL_KEYID "F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5" # Download package and extract to web volume RUN set -ex; \ - fetchDeps="gnupg dirmngr locales libc-l10n"; \ - apt-get -qq update; \ - apt-get install -y --no-install-recommends $fetchDeps; \ curl -o roundcubemail.tar.gz -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz; \ curl -o roundcubemail.tar.gz.asc -fSL https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBEMAIL_VERSION}/roundcubemail-${ROUNDCUBEMAIL_VERSION}-complete.tar.gz.asc; \ export GNUPGHOME="$(mktemp -d)"; \ - # workaround for "Cannot assign requested address", see e.g. https://github.com/inversepath/usbarmory-debian-base_image/issues/9 - echo "disable-ipv6" > "$GNUPGHOME/dirmngr.conf"; \ curl -fSL https://roundcube.net/download/pubkey.asc -o /tmp/pubkey.asc; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o 'Key fingerprint') != 1 ]; then echo 'The key file should contain only one GPG key'; exit 1; fi; \ LC_ALL=C.UTF-8 gpg -n --show-keys --with-fingerprint --keyid-format=long /tmp/pubkey.asc | if [ $(grep -c -o "${ROUNDCUBEMAIL_KEYID}") != 1 ]; then echo 'The key ID should be the roundcube one'; exit 1; fi; \ @@ -99,15 +122,24 @@ RUN set -ex; \ tar -xf roundcubemail.tar.gz -C /usr/src/roundcubemail --strip-components=1 --no-same-owner; \ rm -r "$GNUPGHOME" roundcubemail.tar.gz.asc roundcubemail.tar.gz; \ rm -rf /usr/src/roundcubemail/installer; \ - chown -R www-data:www-data /usr/src/roundcubemail/logs + chown -R www-data:www-data /usr/src/roundcubemail/logs; \ +# Create the config dir + mkdir -p /var/roundcube/config /var/roundcube/enigma; \ + chown -R www-data:www-data /var/roundcube; \ + chmod +t /var/roundcube -# include the wait-for-it.sh script -RUN curl -fL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && chmod +x /wait-for-it.sh +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["%%CMD%%"] -# use custom PHP settings -COPY php.ini /usr/local/etc/php/conf.d/roundcube-defaults.ini -COPY docker-entrypoint.sh / +#### non-root stage -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["%%CMD%%"] +FROM root AS nonroot + +# Prepare locale config for locale-gen +RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen; \ + /usr/sbin/locale-gen + +%%NONROOT_ADD%% + +USER 33:33 diff --git a/templates/docker-entrypoint.sh b/templates/docker-entrypoint.sh index 50afaa656c..600d0f04bd 100644 --- a/templates/docker-entrypoint.sh +++ b/templates/docker-entrypoint.sh @@ -3,7 +3,37 @@ # PWD=`pwd` -if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then +run_entrypoint_tasks() { + phase="$1" + shift + shopt -s nullglob + echo "Running $phase-setup tasks:" + for file in /entrypoint-tasks/"$phase-setup"/*; do + if test ! -f "$file"; then + echo "Ignoring $file because it is not a regular file." + continue; + fi + if test ! -x "$file"; then + echo "Ignoring $file because it is not executable." + continue; + fi + echo "Running $phase-setup task $file:" + "$file" "$@" + # Exit in case of an error in an executable. + exit_code=$? + if test $exit_code -ne 0; then + echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!" + exit $exit_code + fi + echo 'Done.' + done + shopt -u nullglob +} + +if [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then + run_entrypoint_tasks pre "$@" + + INSTALLDIR=`pwd` # docroot is empty if ! [ -e index.php -a -e bin/installto.sh ]; then echo >&2 "roundcubemail not found in $PWD - copying now..." @@ -12,13 +42,19 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then ( set -x; ls -A; sleep 10 ) fi tar cf - --one-file-system -C /usr/src/roundcubemail . | tar xf - - echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $PWD" + echo >&2 "Complete! ROUNDCUBEMAIL has been successfully copied to $INSTALLDIR" # update Roundcube in docroot else - INSTALLDIR=`pwd` echo >&2 "roundcubemail found in $INSTALLDIR - installing update..." (cd /usr/src/roundcubemail && bin/installto.sh -y $INSTALLDIR) - composer update --no-dev + # Re-install composer modules (including plugins) + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --no-dev \ + --no-interaction \ + --optimize-autoloader \ + install fi if [ -f /run/secrets/roundcube_db_user ]; then @@ -27,6 +63,9 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then if [ -f /run/secrets/roundcube_db_password ]; then ROUNDCUBEMAIL_DB_PASSWORD=`cat /run/secrets/roundcube_db_password` fi + if [ -f /run/secrets/roundcube_oauth_client_secret ]; then + ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET=`cat /run/secrets/roundcube_oauth_client_secret` + fi if [ ! -z "${!POSTGRES_ENV_POSTGRES_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "pgsql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=pgsql}" @@ -35,9 +74,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_USER:=${POSTGRES_ENV_POSTGRES_USER}}" : "${ROUNDCUBEMAIL_DB_PASSWORD:=${POSTGRES_ENV_POSTGRES_PASSWORD}}" : "${ROUNDCUBEMAIL_DB_NAME:=${POSTGRES_ENV_POSTGRES_DB:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi elif [ ! -z "${!MYSQL_ENV_MYSQL_*}" ] || [ "$ROUNDCUBEMAIL_DB_TYPE" == "mysql" ]; then : "${ROUNDCUBEMAIL_DB_TYPE:=mysql}" : "${ROUNDCUBEMAIL_DB_HOST:=mysql}" @@ -49,9 +92,13 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD}}" fi : "${ROUNDCUBEMAIL_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-roundcubemail}}" - : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" - - /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + if [[ "$ROUNDCUBEMAIL_DB_HOST" == unix* ]]; then + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}/${ROUNDCUBEMAIL_DB_NAME}}" + # DO NOT USE wait-for-it.sh + else + : "${ROUNDCUBEMAIL_DSNW:=${ROUNDCUBEMAIL_DB_TYPE}://${ROUNDCUBEMAIL_DB_USER}:${ROUNDCUBEMAIL_DB_PASSWORD}@${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT}/${ROUNDCUBEMAIL_DB_NAME}}" + /wait-for-it.sh ${ROUNDCUBEMAIL_DB_HOST}:${ROUNDCUBEMAIL_DB_PORT} -t 30 + fi else # use local SQLite DB in /var/roundcube/db : "${ROUNDCUBEMAIL_DB_TYPE:=sqlite}" @@ -70,6 +117,42 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_PLUGINS:=archive,zipdownload}" : "${ROUNDCUBEMAIL_SKIN:=elastic}" : "${ROUNDCUBEMAIL_TEMP_DIR:=/tmp/roundcube-temp}" + : "${ROUNDCUBEMAIL_REQUEST_PATH:=/}" + : "${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER:=$INSTALLDIR}" + + if [ ! -z "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" ]; then + echo "Installing plugins from the list" + echo "Plugins: ${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" + + # Change ',' into a space + ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH=`echo "${ROUNDCUBEMAIL_COMPOSER_PLUGINS}" | tr ',' ' '` + + composer \ + --working-dir=${ROUNDCUBEMAIL_COMPOSER_PLUGINS_FOLDER} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + ${ROUNDCUBEMAIL_COMPOSER_PLUGINS_SH}; + fi + + if [ ! -d skins/${ROUNDCUBEMAIL_SKIN} ]; then + # Installing missing skin + echo "Installing missing skin: ${ROUNDCUBEMAIL_SKIN}" + composer \ + --working-dir=${INSTALLDIR} \ + --prefer-dist \ + --prefer-stable \ + --update-no-dev \ + --no-interaction \ + --optimize-autoloader \ + require \ + -- \ + roundcube/${ROUNDCUBEMAIL_SKIN}; + fi if [ ! -e config/config.inc.php ]; then GENERATED_DES_KEY=`head /dev/urandom | base64 | head -c 24` @@ -95,12 +178,12 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo " config/config.docker.inc.php @@ -110,18 +193,27 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then echo "\$config['des_key'] = getenv('ROUNDCUBEMAIL_DES_KEY');" >> config/config.docker.inc.php fi - if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}"]; then + if [ ! -z "${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}" ]; then + echo "\$config['oauth_client_secret'] = '${ROUNDCUBEMAIL_OAUTH_CLIENT_SECRET}';" >> config/config.docker.inc.php + fi + + if [ ! -z "${ROUNDCUBEMAIL_SPELLCHECK_URI}" ]; then echo "\$config['spellcheck_engine'] = 'googie';" >> config/config.docker.inc.php echo "\$config['spellcheck_uri'] = '${ROUNDCUBEMAIL_SPELLCHECK_URI}';" >> config/config.docker.inc.php fi + # If the "enigma" plugin is enabled but has no storage configured, inject a default value for the mandatory setting. + if $(echo $ROUNDCUBEMAIL_PLUGINS | grep -Eq '\benigma\b') && ! grep -qr enigma_pgp_homedir /var/roundcube/config/; then + echo "\$config['enigma_pgp_homedir'] = '/var/roundcube/enigma';" >> config/config.docker.inc.php + fi + # include custom config files for fn in `ls /var/roundcube/config/*.php 2>/dev/null || true`; do echo "include('$fn');" >> config/config.docker.inc.php done # initialize or update DB - bin/initdb.sh --dir=$PWD/SQL --create || bin/updatedb.sh --dir=$PWD/SQL --package=roundcube || echo "Failed to initialize database. Please run $PWD/bin/initdb.sh and $PWD/bin/updatedb.sh manually." + bin/initdb.sh --dir=$PWD/SQL --update || echo "Failed to initialize/update the database. Please start with an empty database and restart the container." if [ ! -z "${ROUNDCUBEMAIL_TEMP_DIR}" ]; then mkdir -p ${ROUNDCUBEMAIL_TEMP_DIR} && chown www-data ${ROUNDCUBEMAIL_TEMP_DIR} @@ -134,17 +226,17 @@ if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then : "${ROUNDCUBEMAIL_LOCALE:=en_US.UTF-8 UTF-8}" - if [ -e /usr/sbin/locale-gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then - echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen - /usr/sbin/locale-gen + if [ -e /usr/sbin/locale-gen ] && [ ! -f /etc/locale.gen ] && [ ! -z "${ROUNDCUBEMAIL_LOCALE}" ]; then + echo "${ROUNDCUBEMAIL_LOCALE}" > /etc/locale.gen && /usr/sbin/locale-gen fi if [ ! -z "${ROUNDCUBEMAIL_ASPELL_DICTS}" ]; then ASPELL_PACKAGES=`echo -n "aspell-${ROUNDCUBEMAIL_ASPELL_DICTS}" | sed -E "s/[, ]+/ aspell-/g"` - which apt-get && apt-get install -y $ASPELL_PACKAGES + which apt-get && apt-get update && apt-get install -y $ASPELL_PACKAGES which apk && apk add --no-cache $ASPELL_PACKAGES fi + run_entrypoint_tasks post "$@" fi exec "$@" diff --git a/templates/travis.yml b/templates/travis.yml deleted file mode 100644 index 4c829187f9..0000000000 --- a/templates/travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -os: linux -dist: trusty - -services: docker - -language: bash - -branches: - only: - - master - -before_script: - - env | sort - - dir="${VARIANT}" - - echo "$DOCKER_PULL_PASSWORD" | docker login -u "$DOCKER_PULL_USERNAME" --password-stdin - -script: - - cd "$dir" - - docker-compose build - - docker images - - travis_retry docker-compose up -d && sleep 60 - - docker-compose ps - - docker-compose logs - - docker-compose ps "roundcubedb" | grep "Up" - - docker-compose ps "roundcubemail" | grep "Up" - -notifications: - email: false - -env:%%ENV%% diff --git a/tests/docker-compose.test-apache-postgres.yml b/tests/docker-compose.test-apache-postgres.yml index ac2fc33dc9..f3d96265dc 100644 --- a/tests/docker-compose.test-apache-postgres.yml +++ b/tests/docker-compose.test-apache-postgres.yml @@ -2,10 +2,12 @@ version: "2" services: roundcubemail: - image: ${ROUNDCUBEMAIL_TEST_IMAGE:-roundcube-test-apache} + image: ${ROUNDCUBEMAIL_TEST_IMAGE:-roundcube/roundcubemail:latest-apache} + ports: + - 80:${HTTP_PORT:-80} healthcheck: # To make it obvious in logs "ping=ping" is added - test: ["CMD", "curl", "--fail", "http://localhost/?ping=ping"] + test: ["CMD", "curl", "--fail", "http://localhost:${HTTP_PORT:-80}/?ping=ping"] interval: 2s timeout: 3s retries: 30 @@ -23,6 +25,10 @@ services: - ROUNDCUBEMAIL_DB_NAME=roundcube # same as pgsql POSTGRES_DB env name - ROUNDCUBEMAIL_DB_USER=roundcube # same as pgsql POSTGRES_USER env name - ROUNDCUBEMAIL_DB_PASSWORD=roundcube # same as pgsql POSTGRES_PASSWORD env name + - ROUNDCUBEMAIL_SKIN=larry # Install non-default skin + volumes: + - "./pre-setup/:/entrypoint-tasks/pre-setup/" + - "./post-setup/:/entrypoint-tasks/post-setup/" roundcubedb: image: postgres:alpine @@ -54,6 +60,9 @@ services: roundcubedb: condition: service_healthy command: /tests/run.sh + environment: + - ROUNDCUBE_URL=http://roundcubemail:${HTTP_PORT:-80}/ + - SKIP_POST_SETUP_SCRIPT_TEST=${SKIP_POST_SETUP_SCRIPT_TEST:-no} volumes: - ./run.sh:/tests/run.sh:ro working_dir: /tests diff --git a/tests/docker-compose.test-fpm-postgres.yml b/tests/docker-compose.test-fpm-postgres.yml index 638853b76d..3a32b1a323 100644 --- a/tests/docker-compose.test-fpm-postgres.yml +++ b/tests/docker-compose.test-fpm-postgres.yml @@ -2,7 +2,7 @@ version: "2" services: roundcubemail-fpm: - image: ${ROUNDCUBEMAIL_TEST_IMAGE:-roundcube-test-fpm} + image: ${ROUNDCUBEMAIL_TEST_IMAGE:-roundcube/roundcubemail:latest-fpm} healthcheck: # Check until the FPM port is in in the LISTEN list # test: ["CMD-SHELL", "netstat -an | grep -q -F \":9000\""] @@ -21,12 +21,16 @@ services: - roundcubemail-fpm volumes: - www-vol:/var/www/html + - "./pre-setup/:/entrypoint-tasks/pre-setup/" + - "./post-setup/:/entrypoint-tasks/post-setup/" environment: - ROUNDCUBEMAIL_DB_TYPE=pgsql - ROUNDCUBEMAIL_DB_HOST=roundcubedb # same as pgsql container name - ROUNDCUBEMAIL_DB_NAME=roundcube # same as pgsql POSTGRES_DB env name - ROUNDCUBEMAIL_DB_USER=roundcube # same as pgsql POSTGRES_USER env name - ROUNDCUBEMAIL_DB_PASSWORD=roundcube # same as pgsql POSTGRES_PASSWORD env name + - ROUNDCUBEMAIL_PLUGINS=enigma + - ROUNDCUBEMAIL_SKIN=larry # Install non-default skin roundcubedb: image: postgres:alpine @@ -87,8 +91,9 @@ services: working_dir: /tests environment: ROUNDCUBE_URL: http://roundcubenginx/ + SKIP_POST_SETUP_SCRIPT_TEST: ${SKIP_POST_SETUP_SCRIPT_TEST:-no} networks: roundcube_test_net: volumes: - www-vol: \ No newline at end of file + www-vol: diff --git a/tests/nginx-default.conf b/tests/nginx-default.conf index 80f2f0dd94..8fb8d10fd3 100644 --- a/tests/nginx-default.conf +++ b/tests/nginx-default.conf @@ -4,7 +4,7 @@ server { server_tokens off; autoindex off; - root /var/www/html; + root /var/www/html/public_html; location / { index index.php; diff --git a/tests/post-setup/script.sh b/tests/post-setup/script.sh new file mode 100755 index 0000000000..62a2440102 --- /dev/null +++ b/tests/post-setup/script.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +# Check that the file, which a pre-setup-script should have created, is present. +test -f /tmp/something + +# Leave a marker that can be checked from the outside. +echo yes > public_html/post_setup_script.txt diff --git a/tests/pre-setup/a-script b/tests/pre-setup/a-script new file mode 100755 index 0000000000..86f8b95fa0 --- /dev/null +++ b/tests/pre-setup/a-script @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e + +touch /tmp/something +touch /tmp/anotherfile diff --git a/tests/pre-setup/b-not b/tests/pre-setup/b-not new file mode 100644 index 0000000000..e982dfd3cd --- /dev/null +++ b/tests/pre-setup/b-not @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +# This script should not be run by the entrypoint-script! +# +# Create a regular file, so we can check that it doesn't exist in a later running script. +touch /tmp/shouldnotexist diff --git a/tests/pre-setup/c-check b/tests/pre-setup/c-check new file mode 100755 index 0000000000..ebbf79e7f3 --- /dev/null +++ b/tests/pre-setup/c-check @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +# Remove a file we previously created in "a-script.sh". If this fails, there's a problem in the order of script +# execution. +rm /tmp/anotherfile + +if test -f /tmp/shouldnotexist; then + echo "Error: File /tmp/shouldnotexist should not exist but does!" + exit 1 +fi diff --git a/tests/run.sh b/tests/run.sh index 09df47ffc8..51e57038c2 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -14,7 +14,7 @@ findText () { return 1 } -echo 'Installing pckages' +echo 'Installing packages' apk add --no-cache --update html2text curl @@ -31,4 +31,11 @@ findText 'Login' "${HOMEPAGE_TEXT}" findText 'Warning: This webmail service requires Javascript!' "${HOMEPAGE_TEXT}" echo 'Homepage is okay' -echo 'End.' \ No newline at end of file +if test "$SKIP_POST_SETUP_SCRIPT_TEST" != "yes"; then + echo 'Checking post-setup-script marker' + POST_SETUP_SCRIPT_TEXT=$(curl -s --fail "${ROUNDCUBE_URL}post_setup_script.txt") + findText 'yes' "${POST_SETUP_SCRIPT_TEXT}" + echo 'post-setup-script marker is ok' +fi + +echo 'End.' diff --git a/update.sh b/update.sh index 6de65bd2d4..e0ee96fc86 100755 --- a/update.sh +++ b/update.sh @@ -13,12 +13,6 @@ declare -A BASE=( [fpm-alpine]='alpine' ) -declare -A EXTRAS=( - [apache]='¬RUN a2enmod rewrite' - [fpm]='' - [fpm-alpine]='' -) - VERSION="${1:-$(curl -fsS https://roundcube.net/VERSION.txt)}" #set -x @@ -33,12 +27,22 @@ for variant in apache fpm fpm-alpine; do cp templates/php.ini "$dir/php.ini" sed -E -e ' s/%%VARIANT%%/'"$variant"'/; - s/%%EXTRAS%%/'"${EXTRAS[$variant]}"'/; s/%%VERSION%%/'"$VERSION"'/; s/%%CMD%%/'"${CMD[$variant]}"'/; ' $template | tr '¬' '\n' > "$dir/Dockerfile" + if [[ -f "$dir/nonroot-add.txt" ]]; then + sed -i -e '/%%NONROOT_ADD%%/ {' -e 'r '"$dir/nonroot-add.txt" -e 'd' -e '}' $dir/Dockerfile + else + sed -i 's/%%NONROOT_ADD%%//' $dir/Dockerfile + fi + echo "✓ Wrote $dir/Dockerfile" done +# Use perl to avoid problems with BSD vs. GNU sed, which have incompatible +# argument syntax for editing files in-place. +perl -pi -e "s/1\.[0-9]\.[0-9]+-/${VERSION}-/" .github/workflows/build.yml +echo "Updating version in build.yml workflow" + echo "Done."