From b1dcd73ebe6091ca96161a3486a591603f1c2bf3 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Fri, 24 Feb 2023 14:16:51 +0100 Subject: [PATCH] help run mox with docker in the Dockerfile, allow running on privileged ports and expose those ports. add a docker-compose.yml with instructions for the quickstart. fix running imaptest somewhat. after a short while it will hit the rate limiter. in quickstart, recognize we are running under docker, and print slightly different commands to set permissions, and skip generating the systemd service file. als fix cleaning up the right paths during failure in quickstart. for issue #3 --- Dockerfile | 34 +++++++++++++++++++++----- Dockerfile.imaptest | 4 ++-- Dockerfile.moximaptest | 11 +++++++++ Makefile | 1 - README.md | 3 +++ docker-compose-imaptest.yml | 7 ++++-- docker-compose.yml | 30 +++++++++++++++++++++++ quickstart.go | 48 +++++++++++++++++++------------------ 8 files changed, 104 insertions(+), 34 deletions(-) create mode 100644 Dockerfile.moximaptest create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 3cbd281..301b85a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,33 @@ FROM golang:1-alpine AS build WORKDIR /build -RUN apk add make COPY . . -env GOPROXY=off -RUN make build +RUN GOPROXY=off CGO_ENABLED=0 go build -trimpath -FROM alpine:3.17 +# Using latest may break at some point, but will hopefully be convenient most of the time. +FROM alpine:latest WORKDIR /mox -COPY --from=build /build/mox /mox/mox -CMD ["/mox/mox", "serve"] +COPY --from=build /build/mox /bin/mox + +RUN apk add --no-cache libcap-utils + +# Allow binding to privileged ports, <1024. +RUN setcap 'cap_net_bind_service=+ep' /bin/mox + +# SMTP for incoming message delivery. +EXPOSE 25/tcp +# SMTP/submission with TLS. +EXPOSE 465/tcp +# SMTP/submission without initial TLS. +EXPOSE 587/tcp +# HTTP for internal account and admin pages. +EXPOSE 80/tcp +# HTTPS for ACME (Let's Encrypt), MTA-STS and autoconfig. +EXPOSE 443/tcp +# IMAP with TLS. +EXPOSE 993/tcp +# IMAP without initial TLS. +EXPOSE 143/tcp +# Prometheus metrics. +EXPOSE 8010/tcp + +CMD ["/bin/mox", "serve"] diff --git a/Dockerfile.imaptest b/Dockerfile.imaptest index e76ac98..8f8b26c 100644 --- a/Dockerfile.imaptest +++ b/Dockerfile.imaptest @@ -1,6 +1,6 @@ -FROM alpine:3.17 +FROM alpine:latest -RUN apk update && apk add wget build-base +RUN apk --no-cache add build-base WORKDIR /src RUN wget http://dovecot.org/nightly/dovecot-latest.tar.gz && tar -zxvf dovecot-latest.tar.gz && cd dovecot-0.0.0-* && ./configure && make install && cd .. RUN wget http://dovecot.org/nightly/imaptest/imaptest-latest.tar.gz && tar -zxvf imaptest-latest.tar.gz && cd dovecot-0.0-imaptest-0.0.0-* && ./configure --with-dovecot=$(ls -d ../dovecot-0.0.0-*) && make install diff --git a/Dockerfile.moximaptest b/Dockerfile.moximaptest new file mode 100644 index 0000000..a3d2fad --- /dev/null +++ b/Dockerfile.moximaptest @@ -0,0 +1,11 @@ +FROM golang:1-alpine AS build +WORKDIR /build +COPY . . +RUN GOPROXY=off CGO_ENABLED=0 go build -trimpath + +# Using latest may break at some point, but will hopefully be convenient most of the time. +FROM alpine:latest +WORKDIR /mox +COPY --from=build /build/mox /bin/mox + +CMD ["/bin/mox", "serve"] diff --git a/Makefile b/Makefile index a5c1af0..0875217 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,6 @@ integration-start: # run from within "make integration-start" integration-test: CGO_ENABLED=0 go test -tags integration - go tool cover -html=cover.out -o cover.html imaptest-build: -MOX_UID=$$(id -u) MOX_GID=$$(id -g) docker-compose -f docker-compose-imaptest.yml build --no-cache mox diff --git a/README.md b/README.md index 015dd3b..1ae1003 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ Verify you have a working mox binary: Note: Mox only compiles/works on unix systems, not on Plan 9 or Windows. +You can also run mox with docker image "moxmail/mox" on hub.docker.com, with +tags like "latest", "0.0.1", etc. See docker-compose.yml in this repository. + # Quickstart diff --git a/docker-compose-imaptest.yml b/docker-compose-imaptest.yml index 88579e6..a967b95 100644 --- a/docker-compose-imaptest.yml +++ b/docker-compose-imaptest.yml @@ -1,7 +1,9 @@ version: '3.7' services: mox: - build: . + build: + context: . + dockerfile: Dockerfile.moximaptest user: ${MOX_UID}:${MOX_GID} volumes: - ./testdata/imaptest/data:/mox/data @@ -9,7 +11,8 @@ services: - ./testdata/imaptest/domains.conf:/mox/domains.conf - ./testdata/imaptest/imaptest.mbox:/mox/imaptest.mbox working_dir: /mox - command: sh -c 'echo testtest | ./mox setaccountpassword mjl@mox.example && ./mox serve' + tty: true # For job control + command: sh -c 'export MOXCONF=mox.conf; set -m; mox serve & sleep 1; echo testtest | mox setaccountpassword mjl@mox.example; fg' healthcheck: test: netstat -nlt | grep ':1143 ' interval: 1s diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8fc4568 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +# Before launching mox, run the quickstart to create config files: +# +# MOX_UID=0 MOX_GID=0 docker-compose run mox mox quickstart you@yourdomain.example +# +# After following the instructions, start mox as the newly created mox user: +# +# MOX_UID=$(id -u mox) MOX_GID=$(id -g mox) docker-compose up + +version: '3.7' +services: + mox: + # Replace latest with the version you want to run. + image: moxmail/mox:latest + user: ${MOX_UID}:${MOX_GID} + environment: + - MOX_DOCKER=... # Quickstart won't try to write systemd service file. + # Mox needs host networking because it needs access to the IPs of the + # machine, and the IPs of incoming connections for spam filtering. + network_mode: 'host' + command: sh -c "umask 007 && exec mox serve" + volumes: + - ./config:/mox/config + - ./data:/mox/data + working_dir: /mox + restart: on-failure + healthcheck: + test: netstat -nlt | grep ':25 ' + interval: 1s + timeout: 1s + retries: 10 diff --git a/quickstart.go b/quickstart.go index 3c69d16..0b3b4a3 100644 --- a/quickstart.go +++ b/quickstart.go @@ -8,7 +8,6 @@ import ( "log" "net" "os" - "os/user" "path/filepath" "runtime" "sort" @@ -345,7 +344,8 @@ This likely means one of two things: dc := config.Dynamic{} sc := config.Static{DataDir: "../data"} - os.MkdirAll(sc.DataDir, 0770) + dataDir := "data" // ../data is relative to config/ + os.MkdirAll(dataDir, 0770) sc.LogLevel = "info" sc.Hostname = hostname.Name() sc.ACME = map[string]config.ACME{ @@ -491,7 +491,7 @@ This likely means one of two things: if err != nil { fatalf("open account: %s", err) } - cleanupPaths = append(cleanupPaths, sc.DataDir, filepath.Join(sc.DataDir, "accounts"), filepath.Join(sc.DataDir, "accounts", username), filepath.Join(sc.DataDir, "accounts", username, "index.db")) + cleanupPaths = append(cleanupPaths, dataDir, filepath.Join(dataDir, "accounts"), filepath.Join(dataDir, "accounts", username), filepath.Join(dataDir, "accounts", username, "index.db")) password := pwgen() if err := acc.SetPassword(password); err != nil { @@ -534,34 +534,36 @@ and permissions. `) - userName := "root" - groupName := "root" - if u, err := user.Current(); err != nil { - log.Printf("get current user: %v", err) - } else { - userName = u.Username - if g, err := user.LookupGroupId(u.Gid); err != nil { - log.Printf("get current group: %v", err) - } else { - groupName = g.Name - } - } - fmt.Printf(`Assuming the mox binary is in the current directory, and you will run mox under -user name "mox", and the admin user is the current user, the following command -sets the correct permissions: + if os.Getenv("MOX_DOCKER") == "" { + fmt.Printf(`Assuming the mox binary is in the current directory, and you will run mox under +user name "mox", and the admin user is the current user, the following commands +set the correct permissions: sudo useradd -d $PWD mox - sudo chown %s:mox . mox - sudo chown -R mox:%s config data + sudo chown $(id -nu):mox . mox + sudo chown -R mox:$(id -ng) config data sudo chmod 751 . sudo chmod 750 mox sudo chmod -R u=rwX,g=rwX,o= config data sudo chmod g+s $(find . -type d) -`, userName, groupName) +`) + } else { + fmt.Printf(`Assuming you will run mox under user name "mox", and the admin user is the +current user, the following commands set the correct permissions: - // For now, we only give service config instructions for linux. - if runtime.GOOS == "linux" { + sudo useradd -d $PWD mox + sudo chown $(id -nu):mox . + sudo chown -R mox:$(id -ng) config data + sudo chmod 751 . + sudo chmod -R u=rwX,g=rwX,o= config data + sudo chmod g+s $(find . -type d) + +`) + } + + // For now, we only give service config instructions for linux when not running in docker. + if runtime.GOOS == "linux" && os.Getenv("MOX_DOCKER") == "" { pwd, err := os.Getwd() if err != nil { log.Printf("current working directory: %v", err)