diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5cb147211..2205bfcdf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,356 +4,182 @@ on: workflow_dispatch: pull_request: push: - tags: - - "v*.*.*" - -env: - REGISTRY_IMAGE: stalwartlabs/mail-server jobs: build: - name: Building for ${{ matrix.target }} on ${{ matrix.host_os }} - runs-on: ${{ matrix.host_os }} - if: '!cancelled()' - strategy: + name: Build / ${{matrix.target}} + + runs-on: ${{matrix.host_os}} + + strategy: matrix: include: + - target: aarch64-unknown-linux-gnu + host_os: ubuntu-20.04 + rustflags: -C linker=aarch64-linux-gnu-gcc - target: x86_64-unknown-linux-gnu host_os: ubuntu-20.04 - - target: x86_64-apple-darwin - host_os: macos-latest + rustflags: - target: x86_64-pc-windows-msvc host_os: windows-2022 - #- target: aarch64-unknown-linux-gnu - # host_os: ubuntu-20.04 - #- target: x86_64-unknown-linux-musl - # host_os: ubuntu-20.04 - #- target: aarch64-unknown-linux-musl - # host_os: ubuntu-20.04 - #- target: aarch64-apple-darwin - # host_os: macos-latest - #- target: aarch64-pc-windows-msvc - # host_os: windows-2022 - #- target: aarch64-pc-windows-msvc - # host_os: windows-2022 - #- target: arm-unknown-linux-musleabihf - # host_os: ubuntu-20.04 - #- target: arm-unknown-linux-gnueabihf - # host_os: ubuntu-20.04 - #- target: armv7-unknown-linux-musleabihf - # host_os: ubuntu-20.04 - #- target: armv7-unknown-linux-gnueabihf - # host_os: ubuntu-20.04 + rustflags: + - target: x86_64-apple-darwin + host_os: macos-latest + rustflags: steps: - name: Checkout - uses: actions/checkout@v2 - with: - persist-credentials: false + uses: actions/checkout@v4 - - name: Install dependencies (Linux) - if: ${{ contains(matrix.host_os, 'ubuntu') }} + - name: Install Dependencies (Linux) + if: startsWith(matrix.host_os, 'ubuntu') + shell: bash run: | - sudo apt-get update -y - sudo apt-get install -yq protobuf-compiler wget - wget https://github.com/apple/foundationdb/releases/download/7.1.0/foundationdb-clients_7.1.0-1_amd64.deb - sudo dpkg -i --force-architecture foundationdb-clients_7.1.0-1_amd64.deb + sudo apt-get update && + sudo apt-get install -yq build-essential g++-aarch64-linux-gnu binutils-aarch64-linux-gnu - - name: Install dependencies (MacOs) - if: ${{ contains(matrix.host_os, 'macos') }} + - name: Install Dependencies (Linux, x86) + if: startsWith(matrix.host_os, 'ubuntu') && startsWith(matrix.target, 'x86_64') + shell: bash run: | - brew install protobuf - brew install wget - wget https://github.com/apple/foundationdb/releases/download/7.1.32/FoundationDB-7.1.32_x86_64.pkg - sudo installer -allowUntrusted -dumplog -pkg FoundationDB-7.1.32_x86_64.pkg -target / + curl -LO https://github.com/apple/foundationdb/releases/download/7.1.0/foundationdb-clients_7.1.0-1_amd64.deb + sudo dpkg -i --force-architecture foundationdb-clients_7.1.0-1_amd64.deb + echo "USE_FOUNDATIONDB=1" >> "$GITHUB_ENV" - - name: Install dependencies (Windows) - if: ${{ contains(matrix.host_os, 'windows') }} - uses: arduino/setup-protoc@v1 + - name: Install Dependencies (MacOS) + if: startsWith(matrix.host_os, 'macos') + shell: bash + run: | + curl -LO https://github.com/apple/foundationdb/releases/download/7.1.34/FoundationDB-7.1.34_x86_64.pkg + sudo installer -allowUntrusted -dumplog -pkg FoundationDB-7.1.34_x86_64.pkg -target / + echo "USE_FOUNDATIONDB=1" >> "$GITHUB_ENV" - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - override: true - target: ${{ matrix.target }} - toolchain: stable - - name: Rust Cache uses: Swatinem/rust-cache@v2 with: - key: ${{ matrix.host_os }}-${{ matrix.target }}-mail + key: ${{matrix.host_os}}-${{matrix.target}}-mail - - name: Building binary (Unix version) - if: ${{ !contains(matrix.host_os, 'windows') }} + - name: Build + shell: bash run: | - cargo build --manifest-path=crates/main/Cargo.toml --target=${{ matrix.target }} --no-default-features --features foundationdb --release - cd target/${{ matrix.target }}/release && tar czvf ../../../stalwart-mail-foundationdb-${{ matrix.target }}.tar.gz stalwart-mail && cd - - cargo build --manifest-path=crates/main/Cargo.toml --target=${{ matrix.target }} --release - cargo build --manifest-path=crates/cli/Cargo.toml --target=${{ matrix.target }} --release - cargo build --manifest-path=crates/install/Cargo.toml --target=${{ matrix.target }} --release - cd target/${{ matrix.target }}/release - tar czvf ../../../stalwart-mail-sqlite-${{ matrix.target }}.tar.gz stalwart-mail - tar czvf ../../../stalwart-cli-${{ matrix.target }}.tar.gz stalwart-cli - tar czvf ../../../stalwart-install-${{ matrix.target }}.tar.gz stalwart-install - cd - - - - name: Building binary (Windows version) - if: ${{ contains(matrix.host_os, 'windows') }} - run: | - cargo build --manifest-path=crates/main/Cargo.toml --target=${{ matrix.target }} --release - cargo build --manifest-path=crates/cli/Cargo.toml --target=${{ matrix.target }} --release - cargo build --manifest-path=crates/install/Cargo.toml --target=${{ matrix.target }} --release - cd target/${{ matrix.target }}/release - 7z a ../../../stalwart-mail-sqlite-${{ matrix.target }}.zip stalwart-mail.exe - 7z a ../../../stalwart-cli-${{ matrix.target }}.zip stalwart-cli.exe - 7z a ../../../stalwart-install-${{ matrix.target }}.zip stalwart-install.exe - cd - - - - name: Publish Release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v1 + set -xe + + target="${{matrix.target}}" + rustup target add "${target}" + + root="${PWD}" + mkdir artifacts archives + ext="${{startsWith(matrix.host_os, 'windows') == true && '.exe' || ''}}" + + # Workaround a Windows moment + export PATH="/c/Strawberry/c/bin:/c/Strawberry/perl/site/bin:/c/Strawberry/perl/bin:$PATH" + + build() { + RUSTFLAGS="${{matrix.rustflags}}" cargo build --release --target "${target}" "$@" + } + + artifact() { + local file="${1}${ext}" + local name="${root}/archives/${2:-$1}-${target}" + if [ "${ext}" = ".exe" ]; then + 7z a "${name}.zip" "${file}" + else + tar czvf "${name}.tar.gz" "${file}" + fi + mv "${file}" "${root}/artifacts/${2:-$1}" + } + + mkdir -p "${root}/target/${target}/release" && cd "$_" + + if [ "$USE_FOUNDATIONDB" = 1 ]; then + build -p mail-server --no-default-features --features foundationdb + artifact stalwart-mail stalwart-mail-foundationdb + fi + + build -p mail-server -p stalwart-cli -p stalwart-install + artifact stalwart-mail stalwart-mail-sqlite + artifact stalwart-cli + artifact stalwart-install + + - name: Upload Archives + uses: actions/upload-artifact@v3 with: - files: 'stalwart-*' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: archives + path: ./archives - cross_build_tools: - runs-on: ubuntu-latest - name: Building tools for ${{ matrix.target }} on ${{ matrix.distro }} - if: '!cancelled()' - - strategy: - matrix: - include: - - arch: aarch64 - distro: ubuntu20.04 - target: aarch64-unknown-linux-gnu - steps: - - uses: actions/checkout@v3 - - uses: uraimo/run-on-arch-action@v2 - name: Build artifact - id: build + - name: Upload Artifacts + uses: actions/upload-artifact@v3 with: - arch: ${{ matrix.arch }} - distro: ${{ matrix.distro }} - - # Not required, but speeds up builds - githubToken: ${{ github.token }} - - # Create an artifacts directory - setup: | - mkdir -p "${PWD}/artifacts" - - # Mount the artifacts directory as /artifacts in the container - dockerRunArgs: | - --volume "${PWD}/artifacts:/artifacts" - - # Pass some environment variables to the container - env: | - target: ${{ matrix.target }} - - # The shell to run commands with in the container - shell: /bin/sh - - install: | - apt-get update -yq - apt-get install -yq build-essential cmake wget curl - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal - - run: | - export PATH="$HOME/.cargo/bin:$PATH" - cargo build --manifest-path=crates/cli/Cargo.toml --target=${target} --release - cargo build --manifest-path=crates/install/Cargo.toml --target=${target} --release - cd target/${target}/release - tar czvf /artifacts/stalwart-cli-${target}.tar.gz stalwart-cli - tar czvf /artifacts/stalwart-install-${target}.tar.gz stalwart-install - cd - - - - name: Move packages - run: | - mv ${PWD}/artifacts/* . + name: ${{matrix.target}} + path: ./artifacts - - name: Publish Release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v1 - with: - files: 'stalwart-*' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + release: + name: Release - cross_build: + if: github.event_name == 'push' + needs: build runs-on: ubuntu-latest - name: Building for ${{ matrix.target }} on ${{ matrix.distro }} - if: '!cancelled()' - strategy: - matrix: - include: - - arch: aarch64 - distro: ubuntu20.04 - target: aarch64-unknown-linux-gnu + permissions: + contents: write + steps: - - uses: actions/checkout@v3 - - uses: uraimo/run-on-arch-action@v2 - name: Build artifact - id: build + - name: Download Artifacts + uses: actions/download-artifact@v3 with: - arch: ${{ matrix.arch }} - distro: ${{ matrix.distro }} + name: archives + path: ./archives - # Not required, but speeds up builds - githubToken: ${{ github.token }} + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: ./archives/* + prerelease: ${{!startsWith(github.ref, 'refs/tags/') == true && true || null}} + tag_name: ${{!startsWith(github.ref, 'refs/tags/') == true && 'nightly' || null}} - # Create an artifacts directory - setup: | - mkdir -p "${PWD}/artifacts" - # Mount the artifacts directory as /artifacts in the container - dockerRunArgs: | - --volume "${PWD}/artifacts:/artifacts" + docker_build: + name: Docker Build - # Pass some environment variables to the container - env: | - target: ${{ matrix.target }} + if: github.event_name == 'push' + runs-on: ubuntu-latest - # The shell to run commands with in the container - shell: /bin/sh + permissions: + packages: write - install: | - apt-get update -yq - apt-get install -yq build-essential cmake protobuf-compiler wget curl - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal + steps: + - name: Checkout + uses: actions/checkout@v4 - run: | - export PATH="$HOME/.cargo/bin:$PATH" - cargo build --manifest-path=crates/main/Cargo.toml --target=${target} --release - cd target/${target}/release - tar czvf /artifacts/stalwart-mail-sqlite-${target}.tar.gz stalwart-mail - cd - + - name: Set Up Docker Buildx + uses: docker/setup-buildx-action@v2 - - name: Move packages - run: | - mv ${PWD}/artifacts/* . + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - - name: Publish Release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v1 + - name: Log In to GitHub Container Registry + uses: docker/login-action@v2 with: - files: 'stalwart-*' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - build_docker: - needs: - - build - - cross_build - - cross_build_tools - name: Build Docker image for ${{ matrix.platform }} - runs-on: ubuntu-latest - if: '!cancelled()' - strategy: - fail-fast: false - matrix: - platform: - - linux/amd64 - - linux/arm64 - steps: - - - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Checkout - uses: actions/checkout@v3 - - - name: Docker meta + registry: ghcr.io + username: ${{github.actor}} + password: ${{github.token}} + + - name: Extract Metadata for Docker id: meta uses: docker/metadata-action@v4 with: - images: ${{ env.REGISTRY_IMAGE }} + images: | + ghcr.io/${{github.repository}} tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push by digest + type=ref,event=tag + type=edge,branch=main + + - name: Build and Push Docker Images id: build uses: docker/build-push-action@v4 with: context: . - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true - #cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ env.PLATFORM_PAIR }} - #cache-to: type=registry,ref=s${{ env.REGISTRY_IMAGE }}:buildcache-${{ env.PLATFORM_PAIR }},mode=max - cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }} - cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }} - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v3 - with: - name: digests - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - merge_docker: - name: Merge and push Docker manifest - runs-on: ubuntu-latest - needs: - - build_docker - steps: - - - name: Download digests - uses: actions/download-artifact@v3 - with: - name: digests - path: /tmp/digests - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY_IMAGE }} - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{steps.meta.outputs.tags}} + labels: ${{steps.meta.outputs.labels}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3065b08a3..e1c6d52a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,8 +4,6 @@ on: workflow_dispatch: pull_request: push: - tags: - - '*' jobs: style: @@ -13,98 +11,55 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt - profile: minimal - override: true - - - name: cargo fmt -- --check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + - name: Check Style + run: cargo fmt --all --check test: name: Test - needs: [style] + needs: style runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install dependencies run: | sudo apt-get update -y - sudo apt-get install -yq protobuf-compiler - wget https://github.com/glauth/glauth/releases/download/v2.2.0/glauth-linux-arm64 + curl -LO https://github.com/glauth/glauth/releases/download/v2.2.0/glauth-linux-arm64 chmod a+rx glauth-linux-arm64 nohup ./glauth-linux-arm64 -c tests/resources/ldap.cfg & - wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20230629051228.0.0_amd64.deb -O minio.deb + curl -Lo minio.deb https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20230629051228.0.0_amd64.deb sudo dpkg -i minio.deb mkdir ~/minio nohup minio server ~/minio --console-address :9090 & - wget https://dl.min.io/client/mc/release/linux-amd64/mc + curl -LO https://dl.min.io/client/mc/release/linux-amd64/mc chmod a+rx mc ./mc alias set myminio http://localhost:9000 minioadmin minioadmin ./mc mb tmp - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true + - name: Rust Cache + uses: Swatinem/rust-cache@v2 - name: JMAP Protocol Tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=crates/jmap-proto/Cargo.toml + run: cargo test -p jmap_proto -- --nocapture - name: IMAP Protocol Tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=crates/imap-proto/Cargo.toml + run: cargo test -p imap_proto -- --nocapture - name: Full-text search Tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=crates/store/Cargo.toml - - #- name: Store Tests - # uses: actions-rs/cargo@v1 - # with: - # command: test - # args: --manifest-path=tests/Cargo.toml store -- --nocapture + run: cargo test -p store -- --nocapture - name: Directory Tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=tests/Cargo.toml directory -- --nocapture + run: cargo test -p tests directory -- --nocapture - name: SMTP Tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=tests/Cargo.toml smtp -- --nocapture + run: cargo test -p tests smtp -- --nocapture - name: IMAP Tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=tests/Cargo.toml imap -- --nocapture + run: cargo test -p tests imap -- --nocapture - name: JMAP Tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=tests/Cargo.toml jmap -- --nocapture + run: cargo test -p tests jmap -- --nocapture diff --git a/Dockerfile b/Dockerfile index b80f05bbb..2a01c6892 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,37 @@ -FROM debian:bullseye-slim - -RUN apt-get update -y && apt-get install -yq ca-certificates curl - -COPY resources/docker/configure.sh /usr/local/bin/configure.sh -COPY resources/docker/entrypoint.sh /usr/local/bin/entrypoint.sh - -RUN sed -i -e 's/__C__/all-in-one/g' /usr/local/bin/configure.sh && \ - sed -i -e 's/__R__/mail-server/g' /usr/local/bin/configure.sh && \ - sed -i -e 's/__N__/mail-sqlite/g' /usr/local/bin/configure.sh && \ - sed -i -e 's/__B__/stalwart-mail/g' /usr/local/bin/entrypoint.sh - -RUN chmod a+rx /usr/local/bin/*.sh - -RUN /usr/local/bin/configure.sh --download - -RUN useradd stalwart-mail -s /sbin/nologin -M -RUN mkdir -p /opt/stalwart-mail -RUN chown stalwart-mail:stalwart-mail /opt/stalwart-mail - -VOLUME [ "/opt/stalwart-mail" ] - -EXPOSE 8080 25 587 465 143 993 4190 - -ENTRYPOINT ["/bin/sh", "/usr/local/bin/entrypoint.sh"] +FROM --platform=$BUILDPLATFORM docker.io/lukemathwalker/cargo-chef:latest-rust-slim-bookworm AS chef +WORKDIR /build + +FROM --platform=$BUILDPLATFORM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path /recipe.json + +FROM --platform=$BUILDPLATFORM chef AS builder +ARG TARGETPLATFORM +RUN case "${TARGETPLATFORM}" in \ + "linux/arm64") echo "aarch64-unknown-linux-gnu" > /target.txt && echo "-C linker=aarch64-linux-gnu-gcc" > /flags.txt ;; \ + "linux/amd64") echo "x86_64-unknown-linux-gnu" > /target.txt && echo "-C linker=x86_64-linux-gnu-gcc" > /flags.txt ;; \ + *) exit 1 ;; \ + esac +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -yq build-essential \ + g++-aarch64-linux-gnu binutils-aarch64-linux-gnu +RUN rustup target add "$(cat /target.txt)" +COPY --from=planner /recipe.json /recipe.json +RUN RUSTFLAGS="$(cat /flags.txt)" cargo chef cook --target "$(cat /target.txt)" --release --recipe-path /recipe.json +COPY . . +RUN RUSTFLAGS="$(cat /flags.txt)" cargo build --target "$(cat /target.txt)" --release -p mail-server -p stalwart-cli -p stalwart-install +RUN mv "/build/target/$(cat /target.txt)/release" "/output" + +FROM docker.io/debian:bookworm-slim +WORKDIR /opt/stalwart-mail +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -yq ca-certificates +COPY --from=builder /output/stalwart-mail /usr/local/bin +COPY --from=builder /output/stalwart-cli /usr/local/bin +COPY --from=builder /output/stalwart-install /usr/local/bin +COPY ./resources/docker/entrypoint.sh /usr/local/bin +RUN chmod -R 755 /usr/local/bin +CMD ["/usr/local/bin/stalwart-mail"] +ENTRYPOINT ["/usr/local/bin/entrypoint.sh", "/opt/stalwart-mail/etc/config.toml"] diff --git a/resources/docker/configure.sh b/resources/docker/configure.sh deleted file mode 100644 index a7395db75..000000000 --- a/resources/docker/configure.sh +++ /dev/null @@ -1,615 +0,0 @@ -#!/usr/bin/env sh -# shellcheck shell=dash - -# Stalwart Mail install script -- based on the rustup installation script. - -set -e -set -u - -readonly BASE_URL="https://github.com/stalwartlabs/mail-server/releases/latest/download" -readonly BIN_DIR="/usr/local/bin" - -main() { - downloader --check - need_cmd uname - need_cmd mktemp - need_cmd chmod - need_cmd mkdir - need_cmd rm - need_cmd rmdir - need_cmd tar - - # Make sure we are running as root - if [ "$(id -u)" -ne 0 ] ; then - err "❌ Install failed: This program needs to run as root." - fi - - # Detect OS - local _os="unknown" - local _uname="$(uname)" - _account="stalwart-mail" - if [ "${_uname}" = "Linux" ]; then - _os="linux" - elif [ "${_uname}" = "Darwin" ]; then - _os="macos" - _account="_stalwart-mail" - fi - - # Start configuration mode - if [ "$#" -eq 1 ] && [ "$1" = "--download" ] ; then - # Detect platform architecture - get_architecture || return 1 - local _arch="$RETVAL" - assert_nz "$_arch" "arch" - - # Download binaries - say "⏳ Downloading Stalwart binary for ${_arch}..." - local _file="${BIN_DIR}/stalwart-install.tar.gz" - local _url="https://github.com/stalwartlabs/__R__/releases/latest/download/stalwart-__N__-${_arch}.tar.gz" - ensure downloader "$_url" "$_file" "$_arch" - ensure tar zxvf "$_file" -C "$BIN_DIR" - ignore rm "$_file" - - say "⏳ Downloading configure tool for ${_arch}..." - local _file="${BIN_DIR}/stalwart-install.tar.gz" - local _url="${BASE_URL}/stalwart-install-${_arch}.tar.gz" - ensure downloader "$_url" "$_file" "$_arch" - ensure tar zxvf "$_file" -C "$BIN_DIR" - ignore rm "$_file" - - say "⏳ Downloading CLI tool for ${_arch}..." - local _file="${BIN_DIR}/stalwart-cli.tar.gz" - local _url="${BASE_URL}/stalwart-cli-${_arch}.tar.gz" - ensure downloader "$_url" "$_file" "$_arch" - ensure tar zxvf "$_file" -C "$BIN_DIR" - ignore rm "$_file" - else - ignore $BIN_DIR/stalwart-install -c __C__ -p /opt/stalwart-mail -d - fi - - return 0 -} - -get_architecture() { - local _ostype _cputype _bitness _arch _clibtype - _ostype="$(uname -s)" - _cputype="$(uname -m)" - _clibtype="gnu" - - if [ "$_ostype" = Linux ]; then - if [ "$(uname -o)" = Android ]; then - _ostype=Android - fi - if ldd --version 2>&1 | grep -q 'musl'; then - _clibtype="musl" - fi - fi - - if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then - # Darwin `uname -m` lies - if sysctl hw.optional.x86_64 | grep -q ': 1'; then - _cputype=x86_64 - fi - fi - - if [ "$_ostype" = SunOS ]; then - # Both Solaris and illumos presently announce as "SunOS" in "uname -s" - # so use "uname -o" to disambiguate. We use the full path to the - # system uname in case the user has coreutils uname first in PATH, - # which has historically sometimes printed the wrong value here. - if [ "$(/usr/bin/uname -o)" = illumos ]; then - _ostype=illumos - fi - - # illumos systems have multi-arch userlands, and "uname -m" reports the - # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86 - # systems. Check for the native (widest) instruction set on the - # running kernel: - if [ "$_cputype" = i86pc ]; then - _cputype="$(isainfo -n)" - fi - fi - - case "$_ostype" in - - Android) - _ostype=linux-android - ;; - - Linux) - check_proc - _ostype=unknown-linux-$_clibtype - _bitness=$(get_bitness) - ;; - - FreeBSD) - _ostype=unknown-freebsd - ;; - - NetBSD) - _ostype=unknown-netbsd - ;; - - DragonFly) - _ostype=unknown-dragonfly - ;; - - Darwin) - _ostype=apple-darwin - ;; - - illumos) - _ostype=unknown-illumos - ;; - - MINGW* | MSYS* | CYGWIN* | Windows_NT) - _ostype=pc-windows-gnu - ;; - - *) - err "unrecognized OS type: $_ostype" - ;; - - esac - - case "$_cputype" in - - i386 | i486 | i686 | i786 | x86) - _cputype=i686 - ;; - - xscale | arm) - _cputype=arm - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - fi - ;; - - armv6l) - _cputype=arm - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - else - _ostype="${_ostype}eabihf" - fi - ;; - - armv7l | armv8l) - _cputype=armv7 - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - else - _ostype="${_ostype}eabihf" - fi - ;; - - aarch64 | arm64) - _cputype=aarch64 - ;; - - x86_64 | x86-64 | x64 | amd64) - _cputype=x86_64 - ;; - - mips) - _cputype=$(get_endianness mips '' el) - ;; - - mips64) - if [ "$_bitness" -eq 64 ]; then - # only n64 ABI is supported for now - _ostype="${_ostype}abi64" - _cputype=$(get_endianness mips64 '' el) - fi - ;; - - ppc) - _cputype=powerpc - ;; - - ppc64) - _cputype=powerpc64 - ;; - - ppc64le) - _cputype=powerpc64le - ;; - - s390x) - _cputype=s390x - ;; - riscv64) - _cputype=riscv64gc - ;; - *) - err "unknown CPU type: $_cputype" - - esac - - # Detect 64-bit linux with 32-bit userland - if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then - case $_cputype in - x86_64) - if [ -n "${RUSTUP_CPUTYPE:-}" ]; then - _cputype="$RUSTUP_CPUTYPE" - else { - # 32-bit executable for amd64 = x32 - if is_host_amd64_elf; then { - echo "This host is running an x32 userland; as it stands, x32 support is poor," 1>&2 - echo "and there isn't a native toolchain -- you will have to install" 1>&2 - echo "multiarch compatibility with i686 and/or amd64, then select one" 1>&2 - echo "by re-running this script with the RUSTUP_CPUTYPE environment variable" 1>&2 - echo "set to i686 or x86_64, respectively." 1>&2 - echo 1>&2 - echo "You will be able to add an x32 target after installation by running" 1>&2 - echo " rustup target add x86_64-unknown-linux-gnux32" 1>&2 - exit 1 - }; else - _cputype=i686 - fi - }; fi - ;; - mips64) - _cputype=$(get_endianness mips '' el) - ;; - powerpc64) - _cputype=powerpc - ;; - aarch64) - _cputype=armv7 - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - else - _ostype="${_ostype}eabihf" - fi - ;; - riscv64gc) - err "riscv64 with 32-bit userland unsupported" - ;; - esac - fi - - # Detect armv7 but without the CPU features Rust needs in that build, - # and fall back to arm. - # See https://github.com/rust-lang/rustup.rs/issues/587. - if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then - if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then - # At least one processor does not have NEON. - _cputype=arm - fi - fi - - _arch="${_cputype}-${_ostype}" - - RETVAL="$_arch" -} - -check_proc() { - # Check for /proc by looking for the /proc/self/exe link - # This is only run on Linux - if ! test -L /proc/self/exe ; then - err "fatal: Unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc." - fi -} - -get_bitness() { - need_cmd head - # Architecture detection without dependencies beyond coreutils. - # ELF files start out "\x7fELF", and the following byte is - # 0x01 for 32-bit and - # 0x02 for 64-bit. - # The printf builtin on some shells like dash only supports octal - # escape sequences, so we use those. - local _current_exe_head - _current_exe_head=$(head -c 5 /proc/self/exe ) - if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then - echo 32 - elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then - echo 64 - else - err "unknown platform bitness" - fi -} - -is_host_amd64_elf() { - need_cmd head - need_cmd tail - # ELF e_machine detection without dependencies beyond coreutils. - # Two-byte field at offset 0x12 indicates the CPU, - # but we're interested in it being 0x3E to indicate amd64, or not that. - local _current_exe_machine - _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1) - [ "$_current_exe_machine" = "$(printf '\076')" ] -} - -get_endianness() { - local cputype=$1 - local suffix_eb=$2 - local suffix_el=$3 - - # detect endianness without od/hexdump, like get_bitness() does. - need_cmd head - need_cmd tail - - local _current_exe_endianness - _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)" - if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then - echo "${cputype}${suffix_el}" - elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then - echo "${cputype}${suffix_eb}" - else - err "unknown platform endianness" - fi -} - -say() { - printf 'stalwart-mail: %s\n' "$1" -} - -err() { - say "$1" >&2 - exit 1 -} - -need_cmd() { - if ! check_cmd "$1"; then - err "need '$1' (command not found)" - fi -} - -check_cmd() { - command -v "$1" > /dev/null 2>&1 -} - -assert_nz() { - if [ -z "$1" ]; then err "assert_nz $2"; fi -} - -# Run a command that should never fail. If the command fails execution -# will immediately terminate with an error showing the failing -# command. -ensure() { - if ! "$@"; then err "command failed: $*"; fi -} - -# This wraps curl or wget. Try curl first, if not installed, -# use wget instead. -downloader() { - local _dld - local _ciphersuites - local _err - local _status - local _retry - if check_cmd curl; then - _dld=curl - elif check_cmd wget; then - _dld=wget - else - _dld='curl or wget' # to be used in error message of need_cmd - fi - - if [ "$1" = --check ]; then - need_cmd "$_dld" - elif [ "$_dld" = curl ]; then - check_curl_for_retry_support - _retry="$RETVAL" - get_ciphersuites_for_curl - _ciphersuites="$RETVAL" - if [ -n "$_ciphersuites" ]; then - _err=$(curl $_retry --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1) - _status=$? - else - echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" - if ! check_help_for "$3" curl --proto --tlsv1.2; then - echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" - _err=$(curl $_retry --silent --show-error --fail --location "$1" --output "$2" 2>&1) - _status=$? - else - _err=$(curl $_retry --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1) - _status=$? - fi - fi - if [ -n "$_err" ]; then - if echo "$_err" | grep -q 404; then - err "❌ Binary for platform '$3' not found, this platform may be unsupported." - else - echo "$_err" >&2 - fi - fi - return $_status - elif [ "$_dld" = wget ]; then - if [ "$(wget -V 2>&1|head -2|tail -1|cut -f1 -d" ")" = "BusyBox" ]; then - echo "Warning: using the BusyBox version of wget. Not enforcing strong cipher suites for TLS or TLS v1.2, this is potentially less secure" - _err=$(wget "$1" -O "$2" 2>&1) - _status=$? - else - get_ciphersuites_for_wget - _ciphersuites="$RETVAL" - if [ -n "$_ciphersuites" ]; then - _err=$(wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" 2>&1) - _status=$? - else - echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" - if ! check_help_for "$3" wget --https-only --secure-protocol; then - echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" - _err=$(wget "$1" -O "$2" 2>&1) - _status=$? - else - _err=$(wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" 2>&1) - _status=$? - fi - fi - fi - if [ -n "$_err" ]; then - if echo "$_err" | grep -q ' 404 Not Found'; then - err "❌ Binary for platform '$3' not found, this platform may be unsupported." - else - echo "$_err" >&2 - fi - fi - return $_status - else - err "Unknown downloader" # should not reach here - fi -} - -# Check if curl supports the --retry flag, then pass it to the curl invocation. -check_curl_for_retry_support() { - local _retry_supported="" - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "curl" "--retry"; then - _retry_supported="--retry 3" - fi - - RETVAL="$_retry_supported" - -} - -check_help_for() { - local _arch - local _cmd - local _arg - _arch="$1" - shift - _cmd="$1" - shift - - local _category - if "$_cmd" --help | grep -q 'For all options use the manual or "--help all".'; then - _category="all" - else - _category="" - fi - - case "$_arch" in - - *darwin*) - if check_cmd sw_vers; then - case $(sw_vers -productVersion) in - 10.*) - # If we're running on macOS, older than 10.13, then we always - # fail to find these options to force fallback - if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then - # Older than 10.13 - echo "Warning: Detected macOS platform older than 10.13" - return 1 - fi - ;; - 11.*) - # We assume Big Sur will be OK for now - ;; - *) - # Unknown product version, warn and continue - echo "Warning: Detected unknown macOS major version: $(sw_vers -productVersion)" - echo "Warning TLS capabilities detection may fail" - ;; - esac - fi - ;; - - esac - - for _arg in "$@"; do - if ! "$_cmd" --help $_category | grep -q -- "$_arg"; then - return 1 - fi - done - - true # not strictly needed -} - -# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites -# if support by local tools is detected. Detection currently supports these curl backends: -# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. -get_ciphersuites_for_curl() { - if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then - # user specified custom cipher suites, assume they know what they're doing - RETVAL="$RUSTUP_TLS_CIPHERSUITES" - return - fi - - local _openssl_syntax="no" - local _gnutls_syntax="no" - local _backend_supported="yes" - if curl -V | grep -q ' OpenSSL/'; then - _openssl_syntax="yes" - elif curl -V | grep -iq ' LibreSSL/'; then - _openssl_syntax="yes" - elif curl -V | grep -iq ' BoringSSL/'; then - _openssl_syntax="yes" - elif curl -V | grep -iq ' GnuTLS/'; then - _gnutls_syntax="yes" - else - _backend_supported="no" - fi - - local _args_supported="no" - if [ "$_backend_supported" = "yes" ]; then - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then - _args_supported="yes" - fi - fi - - local _cs="" - if [ "$_args_supported" = "yes" ]; then - if [ "$_openssl_syntax" = "yes" ]; then - _cs=$(get_strong_ciphersuites_for "openssl") - elif [ "$_gnutls_syntax" = "yes" ]; then - _cs=$(get_strong_ciphersuites_for "gnutls") - fi - fi - - RETVAL="$_cs" -} - -# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites -# if support by local tools is detected. Detection currently supports these wget backends: -# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. -get_ciphersuites_for_wget() { - if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then - # user specified custom cipher suites, assume they know what they're doing - RETVAL="$RUSTUP_TLS_CIPHERSUITES" - return - fi - - local _cs="" - if wget -V | grep -q '\-DHAVE_LIBSSL'; then - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then - _cs=$(get_strong_ciphersuites_for "openssl") - fi - elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then - _cs=$(get_strong_ciphersuites_for "gnutls") - fi - fi - - RETVAL="$_cs" -} - -# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2 -# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad -# DH params often found on servers (see RFC 7919). Sequence matches or is -# similar to Firefox 68 ESR with weak cipher suites disabled via about:config. -# $1 must be openssl or gnutls. -get_strong_ciphersuites_for() { - if [ "$1" = "openssl" ]; then - # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet. - echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" - elif [ "$1" = "gnutls" ]; then - # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't. - # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order. - echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM" - fi -} - -# This is just for indicating that commands' results are being -# intentionally ignored. Usually, because it's being executed -# as part of error handling. -ignore() { - "$@" -} - -main "$@" || exit 1 diff --git a/resources/docker/entrypoint.sh b/resources/docker/entrypoint.sh index d962583e5..5b74fa4ec 100644 --- a/resources/docker/entrypoint.sh +++ b/resources/docker/entrypoint.sh @@ -1,10 +1,14 @@ #!/usr/bin/env sh # shellcheck shell=dash +CONFIG="$1" +shift + # If the configuration file does not exist wait until it does. -while [ ! -f /opt/stalwart-mail/etc/config.toml ] || grep -q "__CERT_PATH__" /opt/stalwart-mail/etc/common/tls.toml; do + +while [ ! -f "${CONFIG}" ] || grep -q "__CERT_PATH__" /opt/stalwart-mail/etc/common/tls.toml; do sleep 1 done # If the configuration file exists, start the server. -exec /usr/local/bin/__B__ --config /opt/stalwart-mail/etc/config.toml +exec "$@" --config "${CONFIG}"