├── .dockerignore
├── .github
├── FUNDING.yml
└── workflows
│ └── dockerhub.yml
├── .gitignore
├── CHANGELOG
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.hub.yml
├── docker-compose.yml
├── duck-logo.svg
└── scripts
├── 00-define_variables.sh
├── 01-install_deps.sh
├── 98-install-services.sh
├── 99-uninstall-build-deps.sh
├── _antispam.sh
├── _dkim.sh
├── _haraka.sh
├── _init-env-vars.sh
├── _utils.sh
├── _wildduck.sh
├── _zonemta.sh
├── bin
├── generate_dkim
└── link_dkim_keys
└── entrypoint.sh
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Documentation and Images
2 | README.md
3 | LICENSE
4 | duck-logo.svg
5 | CHANGELOG
6 |
7 | # Docker compose file
8 | docker-compose.yml
9 | docker-compose.hub.yml
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ['https://paypal.me/astzweig']
2 |
--------------------------------------------------------------------------------
/.github/workflows/dockerhub.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker image
2 |
3 | on:
4 | workflow_dispatch:
5 | create:
6 | tags:
7 | - '[0-9]+.[0-9]+.[0-9]+'
8 |
9 | jobs:
10 | push_to_registry:
11 | name: Push Docker image to Docker Hub
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Check out the repo
15 | uses: actions/checkout@v3
16 |
17 | - name: Log in to Docker Hub
18 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
19 | with:
20 | username: ${{ secrets.DOCKERHUB_USERNAME }}
21 | password: ${{ secrets.DOCKERHUB_PASSWORD }}
22 |
23 | - name: Extract metadata (tags, labels) for Docker
24 | id: meta
25 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
26 | with:
27 | images: astzweig/wildduck
28 |
29 | - name: Build and push Docker image
30 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
31 | with:
32 | context: .
33 | push: true
34 | tags: ${{ steps.meta.outputs.tags }}
35 | labels: ${{ steps.meta.outputs.labels }}
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ===== vim =====
2 | # Swap
3 | [._]*.s[a-v][a-z]
4 | [._]*.sw[a-p]
5 | [._]s[a-rt-v][a-z]
6 | [._]ss[a-gi-z]
7 | [._]sw[a-p]
8 |
9 | # Session
10 | Session.vim
11 |
12 | # Temporary
13 | .netrwhist
14 | *~
15 | # Auto-generated tag files
16 | tags
17 | # Persistent undo
18 | [._]*.un~
19 |
20 | # ===== macOS =====
21 | # General
22 | .DS_Store
23 | .AppleDouble
24 | .LSOverride
25 |
26 | # Icon must end with two \r
27 | Icon
28 |
29 | # Thumbnails
30 | ._*
31 |
32 | # Files that might appear in the root of a volume
33 | .DocumentRevisions-V100
34 | .fseventsd
35 | .Spotlight-V100
36 | .TemporaryItems
37 | .Trashes
38 | .VolumeIcon.icns
39 | .com.apple.timemachine.donotpresent
40 |
41 | # Directories potentially created on remote AFP share
42 | .AppleDB
43 | .AppleDesktop
44 | Network Trash Folder
45 | Temporary Items
46 | .apdisk
47 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog][keepachangelog],
5 | and this project adheres to [Semantic Versioning][semver].
6 |
7 | ## [Unreleased]
8 |
9 | ## [2.0.0] - 2022-08-21
10 | ### New
11 | - Added a sponsoring button to the repository, as some nice people
12 | want to show their appreciation this way.
13 | - Added GitHub Action to automatically deploy new releases to Docker Hub.
14 | ### Changed
15 | - Update to newest version of Wildduck, ZoneMTA, Haraka and Wildduck's
16 | Haraka-Plugin. Configuration has changed, make sure to update configuration
17 | files accordingly.
18 | - Update node to v14 as Wildduck makes use of optional chaining now.
19 |
20 | ## [1.2.5] - 2020-02-25
21 | ### Changed
22 | - Update software version of wildduck and wildduck's haraka-plugin.
23 |
24 | ## [1.2.4] - 2020-01-07
25 | ### Fixed
26 | - Fixed a spelling error in the Dockerfile, causing the build process
27 | to fail, as the reference was misspelled.
28 |
29 | ## [1.2.3] - 2019-10-23
30 | ### Changed
31 | - Update software version of wildduck and haraka.
32 |
33 | ## [1.2.2] - 2019-08-25
34 | ### Fixed
35 | - Fix error that causes an invalid configuration key to be modified in
36 | order to endable or disable ENABLE_SMTP_SEND_LATER.
37 |
38 | ## [1.2.1] - 2019-08-25
39 | ### New
40 | - Add ENABLE_SMTP_SEND_LATER environment variable that allows enabling
41 | or disabling the send later feature for messages with a future date
42 | in the 'Date' header. If enabled, messages with a future date will be
43 | send on that specified date in the future. The specified date may be
44 | up to one year in the future and must be at least five minutes in the
45 | future to be enqued. If the send later feature is disabled, all
46 | messages will be send out immediately.
47 | ### Changed
48 | - Update software versions of complete mail server.
49 | - All IMAP and SMTP plugins link to the checked out instance of wildduck
50 | instead of to the npm version. If you want to use your own wildduck
51 | instance, all plugins will do so too.
52 |
53 | ## [1.1.2] - 2019-05-10
54 | ### Changed
55 | - Update software versions of complete mail server.
56 | ### Fixed
57 | - Fix error that would cause the API to still use SSL if TLS keys were
58 | given, even if API_USE_HTTPS was set to false.
59 | - Fix error that would prevent the generation of new DKIM keys.
60 |
61 | ## [1.1.1] - 2019-01-27
62 | ### Fixed
63 | ## [1.2.1] - 2019-08-25
64 | ### New
65 | - Add ENABLE_SMTP_SEND_LATER environment variable that allows enabling
66 | or disabling the send later feature for messages with a future date
67 | in the 'Date' header. If enabled, messages with a future date will be
68 | send on that specified date in the future. The specified date may be
69 | up to one year in the future and must be at least five minutes in the
70 | future to be enqued. If the send later feature is disabled, all
71 | messages will be send out immediately.
72 | ### Changed
73 | - Update software versions of complete mail server.
74 | - All IMAP and SMTP plugins link to the checked out instance of wildduck
75 | instead of to the npm version. If you want to use your own wildduck
76 | instance, all plugins will do so too.
77 |
78 | ## [1.1.2] - 2019-05-10
79 | ### Changed
80 | - Update software versions of complete mail server.
81 | ### Fixed
82 | - Fix error that would cause the API to still use SSL if TLS keys were
83 | given, even if API_USE_HTTPS was set to false.
84 | - Fix error that would prevent the generation of new DKIM keys.
85 |
86 | ## [1.1.1] - 2019-01-27
87 | ### Fixed
88 | - Fix an error that would cause the configuration files to be inside a
89 | subfolder and hence hidden from the services.
90 |
91 | ## [1.1.0] - 2019-01-27
92 | ### New
93 | - Add USE_OWN_SETTINGS environment variables to enable users to prevent
94 | the container to overwrite any value in any service configuration file.
95 |
96 | ### Changed
97 | - The container will now write the environment variables into the the
98 | different service configuration files on every start up.
99 | If you provide your own configuraton files, use the new
100 | USE_OWN_SETTINGS environment variable, to prevent that.
101 |
102 | ### Fixed
103 | - Fixed error that would cause some of the user-set environment
104 | variables to be reset to their default value because of an 'invalid'
105 | value.
106 | - Set the default value of the API_USE_HTTPS environment variable to
107 | true as stated in the [README][readme].
108 |
109 | ## [1.0.0] - 2018-08-31
110 | ### New
111 | - The docker container contains a fully fledged email system based on
112 | modern technology.
113 | - System is configurable using environment variables and API.
114 | - Add a cute logo for the project (Huge thanks to the noun project at
115 | this place).
116 | - License the project under the european union public license. See
117 | [LICENSE][github-license] for more information on that.
118 | - Describe all possible environment variables in the
119 | [README.md][readme] file.
120 |
121 | [keepachangelog]: https://keepachangelog.com/en/1.0.0/
122 | [semver]: https://semver.org/spec/v2.0.0.html
123 | [github-license]: https://github.com/astzweig/docker-wildduck/blob/master/LICENSE
124 | [readme]: https://github.com/astzweig/docker-wildduck
125 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14-alpine as builder
2 | LABEL org.label-schema.vendor = "Astzweig GmbH & Co. KG"
3 | LABEL org.label-schema.version = "2.0.0"
4 | LABEL org.label-schema.description = "A docker container to run nodemailer/wildduck mailserver."
5 | LABEL org.label-schema.vcs-url = "https://github.com/astzweig/docker-wildduck"
6 | LABEL org.label-schema.schema-version = "1.0"
7 | RUN apk add --no-cache dumb-init;
8 |
9 | # Info: If changed, please also change the variables in the next stage
10 | ARG INSTALL_DIR=/var/nodemailer
11 | ARG SCRIPTS_DIR=/root/scripts
12 | ENV INSTALL_DIR ${INSTALL_DIR}
13 | ENV SCRIPTS_DIR ${SCRIPTS_DIR}
14 |
15 | ARG WILDDUCK_GIT_REPO=https://github.com/nodemailer/wildduck.git
16 | ARG WILDDUCK_GIT_CID=9c61d147dfeaf773876ff4c06c552eeb9448d9f8
17 |
18 | ARG HARAKA_VERSION=2.8.25
19 | ARG HARAKA_WD_PLUGIN_GIT_REPO=https://github.com/nodemailer/haraka-plugin-wildduck.git
20 | ARG HARAKA_WD_PLUGIN_GIT_CID=9091e67f0716c3b078c98d51d4df93c5555958f8
21 |
22 | ARG ZONEMTA_GIT_REPO=https://github.com/zone-eu/zone-mta-template.git
23 | ARG ZONEMTA_GIT_CID=a9a175d7533faa5de074671070eeb671341f96e4
24 | ARG ZONEMTA_WD_PLUGIN_GIT_REPO=https://github.com/nodemailer/zonemta-wildduck.git
25 | ARG ZONEMTA_WD_PLUGIN_GIT_CID=4ba8f8b17a5592ef08dc6199140cb4abdaed3e2d
26 |
27 | COPY ./scripts/[0-9][0-9]-*.sh ${SCRIPTS_DIR}/
28 | # Scripts are named like: {ORDER PREFIX}-{NAME}.sh.
29 | # Run files in sequence as induced by their order prefix (00-99).
30 | RUN for file in ${SCRIPTS_DIR}/[0-9][0-9]-*.sh; do \
31 | chmod u+x "${file}"; \
32 | source "${file}"; \
33 | done
34 |
35 | COPY ./scripts/[^0-9]*.sh ${SCRIPTS_DIR}/
36 | COPY ./scripts/bin /usr/local/bin
37 | RUN chmod +x ${SCRIPTS_DIR}/entrypoint.sh; \
38 | chmod +x /usr/local/bin/*;
39 |
40 | VOLUME ["/etc/nodemailer"]
41 |
42 | ENTRYPOINT ["/usr/bin/dumb-init", "--"]
43 | CMD ${SCRIPTS_DIR}/entrypoint.sh
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | European Union Public Licence
2 | V. 1.2
3 |
4 | EUPL © the European Union 2007, 2016
5 |
6 | This European Union Public Licence (the ‘EUPL’) applies to the Work (as
7 | defined below) which is provided under the terms of this Licence. Any use of
8 | the Work, other than as authorised under this Licence is prohibited (to the
9 | extent such use is covered by a right of the copyright holder of the Work).
10 |
11 | The Work is provided under the terms of this Licence when the Licensor (as
12 | defined below) has placed the following notice immediately following the
13 | copyright notice for the Work: “Licensed under the EUPL”, or has expressed by
14 | any other means his willingness to license under the EUPL.
15 |
16 | 1. Definitions
17 |
18 | In this Licence, the following terms have the following meaning:
19 | — ‘The Licence’: this Licence.
20 | — ‘The Original Work’: the work or software distributed or communicated by the
21 | ‘Licensor under this Licence, available as Source Code and also as
22 | ‘Executable Code as the case may be.
23 | — ‘Derivative Works’: the works or software that could be created by the
24 | ‘Licensee, based upon the Original Work or modifications thereof. This
25 | ‘Licence does not define the extent of modification or dependence on the
26 | ‘Original Work required in order to classify a work as a Derivative Work;
27 | ‘this extent is determined by copyright law applicable in the country
28 | ‘mentioned in Article 15.
29 | — ‘The Work’: the Original Work or its Derivative Works.
30 | — ‘The Source Code’: the human-readable form of the Work which is the most
31 | convenient for people to study and modify.
32 |
33 | — ‘The Executable Code’: any code which has generally been compiled and which
34 | is meant to be interpreted by a computer as a program.
35 | — ‘The Licensor’: the natural or legal person that distributes or communicates
36 | the Work under the Licence.
37 | — ‘Contributor(s)’: any natural or legal person who modifies the Work under
38 | the Licence, or otherwise contributes to the creation of a Derivative Work.
39 | — ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
40 | the Work under the terms of the Licence.
41 | — ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
42 | renting, distributing, communicating, transmitting, or otherwise making
43 | available, online or offline, copies of the Work or providing access to its
44 | essential functionalities at the disposal of any other natural or legal
45 | person.
46 |
47 | 2. Scope of the rights granted by the Licence
48 |
49 | The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
50 | sublicensable licence to do the following, for the duration of copyright
51 | vested in the Original Work:
52 |
53 | — use the Work in any circumstance and for all usage,
54 | — reproduce the Work,
55 | — modify the Work, and make Derivative Works based upon the Work,
56 | — communicate to the public, including the right to make available or display
57 | the Work or copies thereof to the public and perform publicly, as the case
58 | may be, the Work,
59 | — distribute the Work or copies thereof,
60 | — lend and rent the Work or copies thereof,
61 | — sublicense rights in the Work or copies thereof.
62 |
63 | Those rights can be exercised on any media, supports and formats, whether now
64 | known or later invented, as far as the applicable law permits so.
65 |
66 | In the countries where moral rights apply, the Licensor waives his right to
67 | exercise his moral right to the extent allowed by law in order to make
68 | effective the licence of the economic rights here above listed.
69 |
70 | The Licensor grants to the Licensee royalty-free, non-exclusive usage rights
71 | to any patents held by the Licensor, to the extent necessary to make use of
72 | the rights granted on the Work under this Licence.
73 |
74 | 3. Communication of the Source Code
75 |
76 | The Licensor may provide the Work either in its Source Code form, or as
77 | Executable Code. If the Work is provided as Executable Code, the Licensor
78 | provides in addition a machine-readable copy of the Source Code of the Work
79 | along with each copy of the Work that the Licensor distributes or indicates,
80 | in a notice following the copyright notice attached to the Work, a repository
81 | where the Source Code is easily and freely accessible for as long as the
82 | Licensor continues to distribute or communicate the Work.
83 |
84 | 4. Limitations on copyright
85 |
86 | Nothing in this Licence is intended to deprive the Licensee of the benefits
87 | from any exception or limitation to the exclusive rights of the rights owners
88 | in the Work, of the exhaustion of those rights or of other applicable
89 | limitations thereto.
90 |
91 | 5. Obligations of the Licensee
92 |
93 | The grant of the rights mentioned above is subject to some restrictions and
94 | obligations imposed on the Licensee. Those obligations are the following:
95 |
96 | Attribution right: The Licensee shall keep intact all copyright, patent or
97 | trademarks notices and all notices that refer to the Licence and to the
98 | disclaimer of warranties. The Licensee must include a copy of such notices and
99 | a copy of the Licence with every copy of the Work he/she distributes or
100 | communicates. The Licensee must cause any Derivative Work to carry prominent
101 | notices stating that the Work has been modified and the date of modification.
102 |
103 | Copyleft clause: If the Licensee distributes or communicates copies of the
104 | Original Works or Derivative Works, this Distribution or Communication will be
105 | done under the terms of this Licence or of a later version of this Licence
106 | unless the Original Work is expressly distributed only under this version of
107 | the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
108 | (becoming Licensor) cannot offer or impose any additional terms or conditions
109 | on the Work or Derivative Work that alter or restrict the terms of the
110 | Licence.
111 |
112 | Compatibility clause: If the Licensee Distributes or Communicates Derivative
113 | Works or copies thereof based upon both the Work and another work licensed
114 | under a Compatible Licence, this Distribution or Communication can be done
115 | under the terms of this Compatible Licence. For the sake of this clause,
116 | ‘Compatible Licence’ refers to the licences listed in the appendix attached to
117 | this Licence. Should the Licensee's obligations under the Compatible Licence
118 | conflict with his/her obligations under this Licence, the obligations of the
119 | Compatible Licence shall prevail.
120 |
121 | Provision of Source Code: When distributing or communicating copies of the
122 | Work, the Licensee will provide a machine-readable copy of the Source Code or
123 | indicate a repository where this Source will be easily and freely available
124 | for as long as the Licensee continues to distribute or communicate the Work.
125 |
126 | Legal Protection: This Licence does not grant permission to use the trade
127 | names, trademarks, service marks, or names of the Licensor, except as required
128 | for reasonable and customary use in describing the origin of the Work and
129 | reproducing the content of the copyright notice.
130 |
131 | 6. Chain of Authorship
132 |
133 | The original Licensor warrants that the copyright in the Original Work granted
134 | hereunder is owned by him/her or licensed to him/her and that he/she has the
135 | power and authority to grant the Licence.
136 |
137 | Each Contributor warrants that the copyright in the modifications he/she
138 | brings to the Work are owned by him/her or licensed to him/her and that he/she
139 | has the power and authority to grant the Licence.
140 |
141 | Each time You accept the Licence, the original Licensor and subsequent
142 | Contributors grant You a licence to their contributions to the Work, under the
143 | terms of this Licence.
144 |
145 | 7. Disclaimer of Warranty
146 |
147 | The Work is a work in progress, which is continuously improved by numerous
148 | Contributors. It is not a finished work and may therefore contain defects or
149 | ‘bugs’ inherent to this type of development.
150 |
151 | For the above reason, the Work is provided under the Licence on an ‘as is’
152 | basis and without warranties of any kind concerning the Work, including
153 | without limitation merchantability, fitness for a particular purpose, absence
154 | of defects or errors, accuracy, non-infringement of intellectual property
155 | rights other than copyright as stated in Article 6 of this Licence.
156 |
157 | This disclaimer of warranty is an essential part of the Licence and a
158 | condition for the grant of any rights to the Work.
159 |
160 | 8. Disclaimer of Liability
161 |
162 | Except in the cases of wilful misconduct or damages directly caused to natural
163 | persons, the Licensor will in no event be liable for any direct or indirect,
164 | material or moral, damages of any kind, arising out of the Licence or of the
165 | use of the Work, including without limitation, damages for loss of goodwill,
166 | work stoppage, computer failure or malfunction, loss of data or any commercial
167 | damage, even if the Licensor has been advised of the possibility of such
168 | damage. However, the Licensor will be liable under statutory product liability
169 | laws as far such laws apply to the Work.
170 |
171 | 9. Additional agreements
172 |
173 | While distributing the Work, You may choose to conclude an additional
174 | agreement, defining obligations or services consistent with this Licence.
175 | However, if accepting obligations, You may act only on your own behalf and on
176 | your sole responsibility, not on behalf of the original Licensor or any other
177 | Contributor, and only if You agree to indemnify, defend, and hold each
178 | Contributor harmless for any liability incurred by, or claims asserted against
179 | such Contributor by the fact You have accepted any warranty or additional
180 | liability.
181 |
182 | 10. Acceptance of the Licence
183 |
184 | The provisions of this Licence can be accepted by clicking on an icon ‘I
185 | agree’ placed under the bottom of a window displaying the text of this Licence
186 | or by affirming consent in any other similar way, in accordance with the rules
187 | of applicable law. Clicking on that icon indicates your clear and irrevocable
188 | acceptance of this Licence and all of its terms and conditions.
189 |
190 | Similarly, you irrevocably accept this Licence and all of its terms and
191 | conditions by exercising any rights granted to You by Article 2 of this
192 | Licence, such as the use of the Work, the creation by You of a Derivative Work
193 | or the Distribution or Communication by You of the Work or copies thereof.
194 |
195 | 11. Information to the public
196 |
197 | In case of any Distribution or Communication of the Work by means of
198 | electronic communication by You (for example, by offering to download the Work
199 | from a remote location) the distribution channel or media (for example, a
200 | website) must at least provide to the public the information requested by the
201 | applicable law regarding the Licensor, the Licence and the way it may be
202 | accessible, concluded, stored and reproduced by the Licensee.
203 |
204 | 12. Termination of the Licence
205 |
206 | The Licence and the rights granted hereunder will terminate automatically upon
207 | any breach by the Licensee of the terms of the Licence. Such a termination
208 | will not terminate the licences of any person who has received the Work from
209 | the Licensee under the Licence, provided such persons remain in full
210 | compliance with the Licence.
211 |
212 | 13. Miscellaneous
213 |
214 | Without prejudice of Article 9 above, the Licence represents the complete
215 | agreement between the Parties as to the Work.
216 |
217 | If any provision of the Licence is invalid or unenforceable under applicable
218 | law, this will not affect the validity or enforceability of the Licence as a
219 | whole. Such provision will be construed or reformed so as necessary to make it
220 | valid and enforceable.
221 |
222 | The European Commission may publish other linguistic versions or new versions
223 | of this Licence or updated versions of the Appendix, so far this is required
224 | and reasonable, without reducing the scope of the rights granted by the
225 | Licence. New versions of the Licence will be published with a unique version
226 | number.
227 |
228 | All linguistic versions of this Licence, approved by the European Commission,
229 | have identical value. Parties can take advantage of the linguistic version of
230 | their choice.
231 |
232 | 14. Jurisdiction
233 |
234 | Without prejudice to specific agreement between parties,
235 | — any litigation resulting from the interpretation of this License, arising
236 | between the European Union institutions, bodies, offices or agencies, as a
237 | Licensor, and any Licensee, will be subject to the jurisdiction of the Court
238 | of Justice of the European Union, as laid down in article 272 of the Treaty
239 | on the Functioning of the European Union,
240 | — any litigation arising between other parties and resulting from the
241 | interpretation of this License, will be subject to the exclusive
242 | jurisdiction of the competent court where the Licensor resides or conducts
243 | its primary business.
244 |
245 | 15. Applicable Law
246 |
247 | Without prejudice to specific agreement between parties,
248 | — this Licence shall be governed by the law of the European Union Member State
249 | where the Licensor has his seat, resides or has his registered office,
250 | — this licence shall be governed by Belgian law if the Licensor has no seat,
251 | residence or registered office inside a European Union Member State.
252 |
253 | Appendix
254 |
255 | ‘Compatible Licences’ according to Article 5 EUPL are:
256 | — GNU General Public License (GPL) v. 2, v. 3
257 | — GNU Affero General Public License (AGPL) v. 3
258 | — Open Software License (OSL) v. 2.1, v. 3.0
259 | — Eclipse Public License (EPL) v. 1.0
260 | — CeCILL v. 2.0, v. 2.1
261 | — Mozilla Public Licence (MPL) v. 2
262 | — GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
263 | — Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
264 | works other than software
265 | — European Union Public Licence (EUPL) v. 1.1, v. 1.2
266 | — Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or
267 | Strong Reciprocity (LiLiQ-R+)
268 |
269 | — The European Commission may update this Appendix to later versions of the
270 | above licences without producing a new version of the EUPL, as long as they
271 | provide the rights granted in Article 2 of this Licence and protect the
272 | covered Source Code from exclusive appropriation.
273 | — All other changes or additions to this Appendix require the production of a
274 | new EUPL version.
275 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ![Logo][svg-duck-logo]
2 |
3 | # Docker Wildduck
4 | Get the [nodemailer/wildduck][github-wildduck] email server as a Docker
5 | service.
6 |
7 | _This project is part of the [Astzweig][astzweig] social responsibility
8 | program._
9 |
10 | # Table of contents
11 |
12 | * [Usage](#user-content-usage)
13 | - [From source](#user-content-from-source)
14 | - [From Docker Hub](#user-content-from-docker-hub)
15 | * [Environment variables](#user-content-environment-variables)
16 | - [General](#user-content-general)
17 | - [Wildduck API](#user-content-wildduck-api)
18 | - [API configuration profile](#user-content-api-configuration-profile)
19 | - [Wildduck IMAP](#user-content-wildduck-imap)
20 | - [Build Args](#user-content-build-args)
21 | - [Build Args](#user-content-build-args)
22 | * [Development decisions](#user-content-development-decisions)
23 | * [Roadmap](#user-content-roadmap)
24 | * [Alternatives](#user-content-alternatives)
25 | * [License](#user-content-license)
26 |
27 |
28 | ## Usage
29 | There are multiple ways to run this container and even more for
30 | experienced users. For the impatience user we have some example
31 | configurations prepared:
32 |
33 | * [From source](#user-content-from-source)
34 | * [From Docker Hub](#user-content-from-docker-hub)
35 |
36 | Both ways will result in a fully functional mailserver that - depending
37 | on your configuration - will either listen on IMAP port (143) and
38 | submission port (587) or if SSL is enabled and you provid valid keys
39 | will listen on IMAPS port (993) and SMTPS port (465).
40 |
41 | ### From source
42 | Checkout this repository on the computer/server, change into the
43 | cloned repository folder and edit [docker-compose.yml][compose1-in-repo]
44 | as you wish.
45 |
46 | ```bash
47 | $ git clone https://github.com/astzweig/docker-wildduck.git
48 | $ cd docker-wildduck
49 | $ vi docker-compose.yml
50 | ```
51 |
52 | You should at least replace the values of the variables in that file.
53 | Afterwards you can just run:
54 |
55 | ```bash
56 | $ docker-compose up -d mail
57 | ```
58 |
59 | ### From Docker Hub
60 | Copy the contents of the provided [docker-compose.yml][compose2-in-repo]
61 | file with eiter `curl` or `wget` anywhere on your server:
62 |
63 | ```bash
64 | $ curl -o 'docker-compose.yml' https://raw.githubusercontent.com/astzweig/docker-wildduck/master/docker-compose.hub.yml
65 |
66 | - or -
67 |
68 | $ wget -O 'docker-compose.yml' https://raw.githubusercontent.com/astzweig/docker-wildduck/master/docker-compose.hub.yml
69 | ```
70 |
71 | Afterwards you can just run:
72 |
73 | ```bash
74 | $ docker-compose up -d mail
75 | ```
76 |
77 | ## Environment variables
78 | The following tables shows a complete list of variables that you can
79 | use to modify the container's build or runtime behaviour. A bold font
80 | means you will have to provide a value in order for the container to
81 | work correctly. An italic font means the setting can be overridden
82 | individually for each email account.
83 |
84 | ### General
85 | | Name | Meaning |
86 | | --- | --- |
87 | | **FQDN** | The fully qualified domain name of your docker host server. It is important that you understand what a [FQDN][fqdn] is. |
88 | | MAIL_DOMAIN | The first domain you want Wildduck to receive emails for. This will also be the standard Domain when you create users and do not supply a domain name. Default: The value you supplied at FQDN |
89 | | PRODUCT_NAME | A name that will be used to advertise the email service on communication with third parties e.g. in SMTP HELO. Default: Wildduck Mail |
90 | | TLS_KEY | The in-container path to the private SSL key to use for all Wildduck services. If no value is provided, SSL (IMAPS, SMTPS, etc.) will be disabled. |
91 | | TLS_CERT | The path to the public full chain SSL key to use for all Wildduck services. |
92 | | REDIS_HOST | The connection URL of redis. Default: redis://redis:6379/8 |
93 | | MONGODB_HOST | The connection URL of mongodb. Default: mongodb://mongo:27017/wildduck |
94 | | GRAYLOG_HOST_PORT | The hostname (or IP address) and port of the graylog server, e.g. graylog:12201. If set logging to graylog will be enabled. |
95 | | ENABLE_STARTTLS | Enable StartTTLS capability of the IMAP and SMTP Server. Default: false |
96 | | USE_OWN_SETTINGS | If set to true, the boot scripts will not overwrite any value in any configuration file of any of the mail services. This is useful if you supply all configuration files yourself. Default: false |
97 |
98 | ### Wildduck API
99 | | Name | Meaning |
100 | | --- | --- |
101 | | API_ENABLE | Enable the Wildduck API. Default: true |
102 | | API_USE_HTTPS | Enable SSL for the API. Usually you want to disable it, if you use a reverse proxy. Default: false |
103 | | API_URL | The URL at which the API is available from outside docker. E.g. 'https://example.com/api'. Default: https://$FQDN:443 if API_USE_HTTPS is set to true and TLS_* variables are provided else http://$FQDN:80 |
104 | | API_TOKEN_SECRET | The token that the API will require to accept a call (given either through an HTTP header (X-ACCESS-TOKEN) or as a URL parameter (?accessToken=...). Leaving this variable empty or not defining it is a possible dangerous step, as anyone will be able to make API calls (and as such create users, etc.). This option should only be left empty by users who know what they are doing. |
105 |
106 | #### API Configuration Profile
107 |
108 | As the Wildduck API supports two factor (2fa) authentication, it also
109 | supports application specific passwords. When an application specific
110 | password is generated, the API generates also a so called
111 | [Apple configuration profile][apple-profiles] automatically. That is a
112 | file, that allows iOS and macOS devices, to set up accounts (e.g. in
113 | this case email accounts) automatically. The following variables can be
114 | used, to configure the metadata of those configuration profiles.
115 |
116 | | Name | Meaning |
117 | | --- | --- |
118 | | CONFIGPROFILE_ID | The id for the configuration profile. According to the [specification][apple-profiles] a reverse-DNS style identifier. Default: The top level domain and the domain of the FQDN in reversed order |
119 | | CONFIGPROFILE_DISPLAY_NAME | The name of the iOS mobile configuration that gets generated. Default: $PRODUCT_NAME |
120 | | CONFIGPROFILE_DISPLAY_ORGANIZATION | The name of the organization which is providing the email service. Default: Unknown |
121 | | CONFIGPROFILE_DISPLAY_DESC | The description of the contents of the profile. Maybe even a notice to the mobile user. '{email}' is replaced with the corresponding email address. Default: Install this profile to setup {email} |
122 | | CONFIGPROFILE_ACCOUNT_DESC | The description of the account that is setup by the configuration profile. '{email}' is replaced as for 'CONFIGPROFILE_DISPLAY_DESC'. Default: {email} |
123 |
124 | ### Wildduck IMAP
125 | | Name | Meaning |
126 | | --- | --- |
127 | | IMAP_PROCESSES | The number of IMAP processes to start. Default: 2 |
128 | | IMAP_RETENTION | The amount of days after which messages in Trash or Junk folder shall be deleted automatically. Default: 4 |
129 |
130 | ### Wildduck Outbound SMTP
131 | | Name | Meaning |
132 | | --- | --- |
133 | | ENABLE_SMTP_SEND_LATER | Allow messages with a future date in the `Date` header to be send later. If set to false, incoming messages will be send out immediately regardless of the future date set in the `Date` header. Default: true |
134 |
135 | ### Build ARGS
136 | These variables can be used to define the service versions that are used
137 | in the container. They change only the build time behaviour.
138 |
139 | | Name | Meaning |
140 | | --- | --- |
141 | | SCRIPTS_DIR | Path where the scripts folder is uploaded inside the container. Default: '/root/scripts' |
142 | | INSTALL_DIR | Path where the components (Wildduck, Haraka, etc.) will be installed inside the container. Default: '/var/nodemailer' |
143 | | WILDDUCK_GIT_REPO | The git repository URL of [Wildduck][github-wildduck] (or your fork of it). Default: 'https://github.com/nodemailer/wildduck.git' |
144 | | WILDDUCK_GIT_CID | The git commit ID or branch namer you want to checkout of the Wildduck git repository. Default: 'master' |
145 | | HARAKA_VERSION | The version of [Haraka][github-haraka] to download and use. Default: '2.8.21' |
146 | | HARAKA_WD_PLUGIN_GIT_REPO | The git repository URL of the [Wildduck plugin][github-haraka-wd-plugin] for [Haraka][github-haraka] (or your fork of it). Default: 'https://github.com/nodemailer/haraka-plugin-wildduck' |
147 | | HARAKA_WD_PLUGIN_GIT_CID | The git commit ID or branch name you want to checkout of the Wildduck plugin for Haraka git repository. Default: 'master' |
148 | | ZONEMTA_GIT_REPO | The git repository URL of [ZoneMTA][github-zonemta] (or your fork of it). Default: 'https://github.com/zone-eu/zone-mta-template.git' |
149 | | ZONEMTA_GIT_CID | The git commit ID or branch name you want to checkout of the ZoneMTA git repository. Default: 'master' |
150 | | ZONEMTA_WD_PLUGIN_GIT_REPO | The git repository URL of the [Wildduck plugin][github-zonemta-wd-plugin] for [ZoneMTA][github-zonemta] (or your fork of it). Default: 'https://github.com/nodemailer/zonemta-wildduck.git' |
151 | | ZONEMTA_WD_PLUGIN_GIT_CID | The git commit ID or branch name you want to checkout of the Wildduck plugin for ZoneMTA git repository. Default: 'master' |
152 |
153 | ## Development decisions
154 | - We will use shell scripts to run commands instead of writing
155 | everything in the Dockerfile. We want the build-stage scripts to form
156 | a sequence and thus we define a script naming scheme that contains an
157 | ordering prefix of two digits.
158 | - Scripts that are not meant to be executed by their own start with an
159 | underscore. They are not moved out of the scripts folder inside the
160 | container and their execution bit is not set.
161 | - Scripts that are meant to be executables in the running container
162 | will be in a sub-folder called 'bin'. They can have any name, with or
163 | without extension. They will be moved into one of the system paths.
164 | - There is exactly one 'entrypoint.sh' script, that is the default
165 | command of the container.
166 | - The container needs three components to run (Wildduck, Haraka and
167 | ZoneMTA). Each of those components has to be installed first, to be
168 | configured in a second step and to be started in a third step. We
169 | write one component script for each one of those components
170 | containing a functions for two of the steps above: a
171 | configure_{service} function and a start_{service} function. To make
172 | use of Docker image layer caching we implement the install step of
173 | each component in a single BUILD script.
174 |
175 | ## Roadmap
176 | * [x] Provide a docker container with the pre-installed services as done by
177 | the [setup scripts][setup-scripts] provided by the
178 | [Wildduck][github-wildduck] project.
179 | * [x] Provide scripts to configure the docker container using environment
180 | variables.
181 | * [ ] Create different users for the different services in the container.
182 | * [ ] Create better management tools, like a CLI user management tool.
183 |
184 | ## Alternatives
185 | Before starting to build this image we looked around for alternatives
186 | and found [houlagins][dockerhub-houlagins]'s and
187 | [hechengjin][dockerhub-hechengjin]'s containers. We still decided to go
188 | for our own solution, as neither of them provides their build files or
189 | a corresponding code repository. And - for some maybe less important -
190 | neither does any of them provide a useful documentation.
191 |
192 | ## License
193 | * Licensed under the [EUPL][eupl].
194 | * Logo: [Duck by Milky - Digital innovation from the Noun Project][duck-logo].
195 |
196 |
197 | [svg-duck-logo]: https://raw.githubusercontent.com/astzweig/docker-wildduck/master/duck-logo.svg?sanitize=true
198 | [github-wildduck]: https://github.com/nodemailer/wildduck
199 | [astzweig]: https://astzweig.de/ges-ver
200 | [compose1-in-repo]: https://raw.githubusercontent.com/astzweig/docker-wildduck/master/docker-compose.yml
201 | [compose2-in-repo]: https://raw.githubusercontent.com/astzweig/docker-wildduck/master/docker-compose.hub.yml
202 | [fqdn]: https://easyengine.io/tutorials/mail/fqdn-reverse-dns-ptr-mx-record-checks
203 | [apple-profile]: https://developer.apple.com/business/documentation/Configuration-Profile-Reference.pdf
204 | [wildduck_webmail_demo]: https://webmail.wildduck.email
205 | [github-haraka]: https://github.com/haraka/Haraka
206 | [github-haraka-wd-plugin]: https://github.com/nodemailer/haraka-plugin-wildduck
207 | [github-zonemta]: https://github.com/zone-eu/zone-mta-template
208 | [github-zonemta-wd-plugin]: https://github.com/nodemailer/zonemta-wildduck
209 | [setup-scripts]: https://github.com/nodemailer/wildduck/tree/master/setup
210 | [dockerhub-houlagins]: https://hub.docker.com/r/houlagins/wildduck/
211 | [dockerhub-hechengjin]: https://hub.docker.com/r/hechengjin/mailserver/
212 | [eupl]: https://eupl.eu/1.2/en/
213 | [duck-logo]: https://thenounproject.com/term/duck/33145/
214 |
--------------------------------------------------------------------------------
/docker-compose.hub.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | mail:
4 | image: astzweig/wildduck
5 | ports:
6 | - "25:25"
7 | - "5438:443"
8 | - "465:465"
9 | - "993:993"
10 | networks:
11 | - backend
12 | - frontend
13 | volumes:
14 | - ./ssl/certs/archive/example.com/fullchain1.pem:/etc/tls-keys/pub.pem:ro
15 | - ./ssl/certs/archive/example.com/privkey1.pem:/etc/tls-keys/prv.pem:ro
16 | - mailconfig:/etc/nodemailer
17 | depends_on:
18 | - redis
19 | - mongo
20 | environment:
21 | - FQDN=mailserver1.example.com
22 | - MAIL_DOMAIN=example.com
23 | - TLS_KEY=/etc/tls-keys/prv.pem # If you want to activate SSL for the mail services
24 | - TLS_CERT=/etc/tls-keys/pub.pem # you will need to provide these two variables.
25 | - API_USE_HTTPS=true
26 | - API_URL=https://mailserver1.astzweig.de:5438
27 | - API_TOKEN_SECRET=PLEASE_REPLACE_THIS
28 |
29 | # Uncomment the following lines, if you want a letsencrypt certificate and
30 | # your DNS provider is supported by the python lexicon dns tool.
31 | # See github.com/astzweig/docker-letsencrypt for more information on this.
32 | #
33 | # ssl:
34 | # image: astzweig/letsencrypt
35 | # volumes:
36 | # - ./ssl/certs:/etc/letsencrypt
37 | # environment:
38 | # - EMAIL=your-email-for-letsencrypt
39 | # - PROVIDER=cloudflare
40 | # - PROVIDER_DNS_DELAY=18
41 | # - LEXICON_CLOUDFLARE_USERNAME=easyname-kontakt@astzweig.de
42 | # - LEXICON_CLOUDFLARE_TOKEN=#2UBv2y*9#T#p#%8!^Y58hBHOp*gvt
43 | # - DOMAINS=example.com,*.example.com
44 |
45 | redis:
46 | image: redis:alpine
47 | volumes:
48 | - redis_backup:/data
49 | networks:
50 | - backend
51 |
52 | mongo:
53 | image: mongo
54 | volumes:
55 | - mongodbs:/data/db
56 | networks:
57 | - backend
58 |
59 | networks:
60 | backend:
61 | frontend:
62 | volumes:
63 | mailconfig:
64 | redis_backup:
65 | mongodbs:
66 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | mail:
4 | build: .
5 | image: local_wildduck:latest
6 | ports:
7 | - "25:25"
8 | - "5438:443"
9 | - "465:465"
10 | - "993:993"
11 | networks:
12 | - backend
13 | - frontend
14 | volumes:
15 | - ./ssl/certs/archive/example.com/fullchain1.pem:/etc/tls-keys/pub.pem:ro
16 | - ./ssl/certs/archive/example.com/privkey1.pem:/etc/tls-keys/prv.pem:ro
17 | - mailconfig:/etc/nodemailer
18 | depends_on:
19 | - redis
20 | - mongo
21 | environment:
22 | - FQDN=mailserver1.example.com
23 | - MAIL_DOMAIN=example.com
24 | - TLS_KEY=/etc/tls-keys/prv.pem # If you want to activate SSL for the mail services
25 | - TLS_CERT=/etc/tls-keys/pub.pem # you will need to provide these two variables.
26 | - API_USE_HTTPS=true
27 | - API_URL=https://mailserver1.astzweig.de:5438
28 | - API_TOKEN_SECRET=PLEASE_REPLACE_THIS
29 |
30 | # Uncomment the following lines, if you want a letsencrypt certificate and
31 | # your DNS provider is supported by the python lexicon dns tool.
32 | # See github.com/astzweig/docker-letsencrypt for more information on this.
33 | #
34 | # ssl:
35 | # image: astzweig/letsencrypt
36 | # volumes:
37 | # - ./ssl/certs:/etc/letsencrypt
38 | # environment:
39 | # - EMAIL=your-email-for-letsencrypt
40 | # - PROVIDER=cloudflare
41 | # - PROVIDER_DNS_DELAY=18
42 | # - LEXICON_CLOUDFLARE_USERNAME=easyname-kontakt@astzweig.de
43 | # - LEXICON_CLOUDFLARE_TOKEN=#2UBv2y*9#T#p#%8!^Y58hBHOp*gvt
44 | # - DOMAINS=example.com,*.example.com
45 |
46 | redis:
47 | image: redis:alpine
48 | volumes:
49 | - redis_backup:/data
50 | networks:
51 | - backend
52 |
53 | mongo:
54 | image: mongo
55 | volumes:
56 | - mongodbs:/data/db
57 | networks:
58 | - backend
59 |
60 | networks:
61 | backend:
62 | frontend:
63 | volumes:
64 | mailconfig:
65 | redis_backup:
66 | mongodbs:
67 |
--------------------------------------------------------------------------------
/duck-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/scripts/00-define_variables.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # === dir vars ===
3 | export WILDDUCK_INSTALL_DIR="${INSTALL_DIR}/wildduck";
4 | export HARAKA_INSTALL_DIR="${INSTALL_DIR}/haraka";
5 | export ZONEMTA_INSTALL_DIR="${INSTALL_DIR}/zonemta";
6 | export CONFIG_DIR='/etc/nodemailer';
7 | export WILDDUCK_CONFIG_DIR="${CONFIG_DIR}/wildduck";
8 | export HARAKA_CONFIG_DIR="${CONFIG_DIR}/haraka";
9 | export ZONEMTA_CONFIG_DIR="${CONFIG_DIR}/zonemta";
10 | export CLAMD_DATABSE_DIR="${CONFIG_DIR}/clamdb";
11 | export DKIM_KEYS_DIR="${CONFIG_DIR}/dkim";
12 | export SECRETS_DIR="${CONFIG_DIR}/secrets";
13 |
--------------------------------------------------------------------------------
/scripts/01-install_deps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | apk add --no-cache rspamd clamav clamav-libunrar; # antispam tools.
3 | apk add --no-cache openssl; # needed to generate dkim keys at runtime.
4 | apk add --no-cache curl; # needed to run API requests.
5 | apk add --no-cache --virtual build-deps git python3-dev npm make g++;
6 |
7 | apk add --no-cache python3; # needed to run cocof tool, to edit
8 | apk add --no-cache py-pip;
9 | pip3 install cocof; # configuration files.
10 |
--------------------------------------------------------------------------------
/scripts/98-install-services.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | get_repo_at_cid () {
4 | # Download the specified git repo and checkout the specified ref.
5 | # Delete the .git folder afterwards.
6 | # Run as:
7 | # get_repo_at_cid
8 | local REPO_URL="${1}";
9 | local DOWNLOAD_DIR="${2}"
10 | local GIT_CID="${3}";
11 |
12 | git clone "${REPO_URL}" "${DOWNLOAD_DIR}";
13 | cd "${DOWNLOAD_DIR}";
14 | git checkout "${GIT_CID}";
15 | rm -fr .git;
16 | }
17 |
18 |
19 | install_wildduck () {
20 | get_repo_at_cid "${WILDDUCK_GIT_REPO}" \
21 | "${WILDDUCK_INSTALL_DIR}" \
22 | "${WILDDUCK_GIT_CID}";
23 |
24 | cd "${WILDDUCK_INSTALL_DIR}";
25 | npm install --unsafe-perm --production;
26 |
27 | mkdir -p "${WILDDUCK_CONFIG_DIR}";
28 | mv "${WILDDUCK_INSTALL_DIR}/config"/* "${WILDDUCK_CONFIG_DIR}";
29 | return 0;
30 | }
31 |
32 |
33 | install_haraka () {
34 | npm install --unsafe-perm -g Haraka@"${HARAKA_VERSION}";
35 | haraka -i "${HARAKA_INSTALL_DIR}";
36 | cd "${HARAKA_INSTALL_DIR}";
37 | npm install --unsafe-perm --save haraka-plugin-rspamd Haraka@"${HARAKA_VERSION}";
38 | mkdir -p "${HARAKA_INSTALL_DIR}/queue";
39 |
40 | get_repo_at_cid "${HARAKA_WD_PLUGIN_GIT_REPO}" \
41 | "${HARAKA_INSTALL_DIR}/plugins/wildduck" \
42 | "${HARAKA_WD_PLUGIN_GIT_CID}";
43 |
44 | cd "${HARAKA_INSTALL_DIR}/plugins/wildduck";
45 | cocof ./package.json '[{"op": "add", "path": "/dependencies/wildduck", "value": "file:'"${WILDDUCK_INSTALL_DIR}"'"}]';
46 | npm install --unsafe-perm --production;
47 |
48 | mkdir -p "${HARAKA_CONFIG_DIR}";
49 | mv "${HARAKA_INSTALL_DIR}/config"/* "${HARAKA_CONFIG_DIR}";
50 | rm -r "${HARAKA_INSTALL_DIR}/config";
51 | ln -s "${HARAKA_CONFIG_DIR}" "${HARAKA_INSTALL_DIR}/config";
52 | return 0;
53 | }
54 |
55 |
56 | install_zonemta () {
57 | get_repo_at_cid "${ZONEMTA_GIT_REPO}" \
58 | "${ZONEMTA_INSTALL_DIR}" \
59 | "${ZONEMTA_GIT_CID}";
60 |
61 | get_repo_at_cid "${ZONEMTA_WD_PLUGIN_GIT_REPO}" \
62 | "${ZONEMTA_INSTALL_DIR}/plugins/wildduck" \
63 | "${ZONEMTA_WD_PLUGIN_GIT_CID}";
64 |
65 | cd "${ZONEMTA_INSTALL_DIR}";
66 | npm install --unsafe-perm --production;
67 |
68 | cd "${ZONEMTA_INSTALL_DIR}/plugins/wildduck";
69 | cocof ./package.json '[{"op": "add", "path": "/dependencies/wildduck", "value": "file:'"${WILDDUCK_INSTALL_DIR}"'"}]';
70 | npm install --unsafe-perm --production;
71 |
72 | # Remove example plugins.
73 | rm -r "${ZONEMTA_INSTALL_DIR}/plugins"/*.js;
74 | rm -r "${ZONEMTA_INSTALL_DIR}"/config/plugins/example-*;
75 |
76 | mkdir -p "${ZONEMTA_CONFIG_DIR}";
77 | mv "${ZONEMTA_INSTALL_DIR}/config"/* "${ZONEMTA_CONFIG_DIR}";
78 | rm -fr "${ZONEMTA_CONFIG_DIR}/plugins/dkim.toml";
79 | return 0;
80 | }
81 |
82 |
83 | mkdir -p "${INSTALL_DIR}";
84 | install_wildduck;
85 | install_haraka;
86 | install_zonemta;
87 |
--------------------------------------------------------------------------------
/scripts/99-uninstall-build-deps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | apk del build-deps;
3 |
--------------------------------------------------------------------------------
/scripts/_antispam.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | configure_antispam () {
4 | [ ! -d /run/clamav ] && mkdir /run/clamav;
5 | chown clamav:clamav /run/clamav;
6 |
7 | _create_dir_if_empty "${CLAMD_DATABSE_DIR}";
8 | chown clamav:clamav "${CLAMD_DATABSE_DIR}";
9 | echo "DatabaseDirectory ${CLAMD_DATABSE_DIR}" >> /etc/clamav/clamd.conf;
10 | echo "DatabaseDirectory ${CLAMD_DATABSE_DIR}" \
11 | >> /etc/clamav/freshclam.conf;
12 |
13 | [ ! -d /run/rspamd ] && mkdir /run/rspamd;
14 | if [ ! -f /etc/rspamd/local.d/dmarc.conf ]; then
15 | echo "servers = \"${REDIS_HOSTNAME}\";" \
16 | > /etc/rspamd/local.d/dmarc.conf;
17 | fi
18 |
19 | # Update the virus database if necessary
20 | freshclam;
21 | }
22 |
23 | start_antispam () {
24 | rspamd -i;
25 | clamd;
26 | freshclam -d -c 10;
27 | }
28 |
--------------------------------------------------------------------------------
/scripts/_dkim.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | add_dkim_for_mail_domain () {
4 | local AUTH_HEADER;
5 | [ -n "${API_TOKEN_SECRET}" ] && \
6 | AUTH_HEADER="X-Access-Token: ${API_TOKEN_SECRET}";
7 |
8 | # Wait until API server is up
9 | while ! curl --output /dev/null --silent \
10 | --fail -H "${AUTH_HEADER}" ${API_URL}/users; do
11 | sleep 2;
12 | done
13 |
14 | generate_dkim "${MAIL_DOMAIN}";
15 | }
16 |
--------------------------------------------------------------------------------
/scripts/_haraka.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | _haraka_configure_general () {
4 | mv "${HARAKA_CONFIG_DIR}/plugins" "${HARAKA_CONFIG_DIR}/plugins.bak";
5 | echo 26214400 > "${HARAKA_CONFIG_DIR}/databytes";
6 | echo "${FQDN}" > "${HARAKA_CONFIG_DIR}/me";
7 | echo "${PRODUCT_NAME} MX" > "${HARAKA_CONFIG_DIR}/smtpgreeting";
8 | }
9 |
10 | _haraka_configure_plugins_list () {
11 | echo "spf
12 | clamd
13 | rspamd
14 | dkim_verify
15 | wildduck" > "${HARAKA_CONFIG_DIR}/plugins";
16 | if [ "${_USE_SSL}" = 'true' ]; then
17 | echo 'tls' >> "${HARAKA_CONFIG_DIR}/plugins";
18 | fi
19 | }
20 |
21 |
22 | _haraka_configure_tls () {
23 | [ "${_USE_SSL}" = 'false' ] && return;
24 | echo "
25 | key=${TLS_KEY}
26 | cert=${TLS_CERT}" > "${HARAKA_CONFIG_DIR}/tls.ini";
27 | }
28 |
29 |
30 | _haraka_configure_rspamd () {
31 | echo '
32 | host = localhost
33 | port = 11333
34 | add_headers = always
35 | [dkim]
36 | enabled = true
37 |
38 | [header]
39 | bar = X-Rspamd-Bar
40 | report = X-Rspamd-Report
41 | score = X-Rspamd-Score
42 | spam = X-Rspamd-Spam
43 |
44 | [check]
45 | authenticated = true
46 | private_ip = true
47 |
48 | [reject]
49 | spam = false
50 |
51 | [soft_reject]
52 | enabled = true
53 |
54 | [rmilter_headers]
55 | enabled = true
56 |
57 | [spambar]
58 | positive = +
59 | negative = -
60 | neutral = /' > "${HARAKA_CONFIG_DIR}/rspamd.ini";
61 | }
62 |
63 |
64 | _haraka_configure_clamav () {
65 | echo '
66 | clamd_socket = /run/clamav/clamd.sock
67 | [reject]
68 | virus=true
69 | error=false' > "${HARAKA_CONFIG_DIR}/clamd.ini";
70 | }
71 |
72 |
73 | _haraka_configure_wildduck () {
74 | cp "${HARAKA_INSTALL_DIR}/plugins/wildduck/config/wildduck.yaml" \
75 | "${HARAKA_CONFIG_DIR}/wildduck.yaml";
76 |
77 | local COMS FPATH;
78 | FPATH="${HARAKA_INSTALL_DIR}/config/wildduck.yaml";
79 |
80 | COMS="[
81 | $(printf "${_COCOF_ADD}" /mongo/url "\"${MONGODB_HOST}\""),
82 | $(printf "${_COCOF_ADD}" /redis/port ${_REDIS_PORT}),
83 | $(printf "${_COCOF_ADD}" /redis/host "\"${_REDIS_HOSTNAME}\""),
84 | $(printf "${_COCOF_ADD}" /redis/db ${_REDIS_DB}),
85 | $(printf "${_COCOF_ADD}" /srs/secret "\"${_SRS_SECRET}\""),
86 | $(printf "${_COCOF_ADD}" /gelf/enabled ${_GRAYLOG_ENABLE}),
87 | $(printf "${_COCOF_ADD}" /gelf/hostname "\"${FQDN}\""),
88 | $(printf "${_COCOF_ADD}" /gelf/options/graylogPort ${_GRAYLOG_PORT:-1}),
89 | $(printf "${_COCOF_ADD}" /gelf/options/graylogHostname "\"${_GRAYLOG_HOSTNAME}\"")
90 | ]";
91 |
92 | cocof "${FPATH}" "${COMS}";
93 | }
94 |
95 |
96 | configure_haraka () {
97 | # Only configure Haraka if the user has not mounted his own
98 | # configuration files at $HARAKA_CONFIG_DIR.
99 | [ "${USE_OWN_SETTINGS}" = 'true' ] && return 0;
100 |
101 | # Haraka is a bit different here: The folder at $HARAKA_INSTALL_DIR
102 | # does not actually contain the source of Haraka, but rather an
103 | # Haraka application. Therefor it's configuration folder must
104 | # remain in this 'application' folder.
105 | # That's why we must create a symlink to $HARAKA_CONFIG_DIR.
106 | _haraka_configure_general;
107 | _haraka_configure_plugins_list;
108 | _haraka_configure_tls;
109 | _haraka_configure_rspamd;
110 | _haraka_configure_clamav;
111 | _haraka_configure_wildduck;
112 | return 0;
113 | }
114 |
115 |
116 | start_haraka () {
117 | cd "${HARAKA_INSTALL_DIR}";
118 | NODE_ENV=production node ./node_modules/.bin/haraka -c .;
119 | return $?;
120 | }
121 |
--------------------------------------------------------------------------------
/scripts/_init-env-vars.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | _persist_secret_variables () {
4 | local _VAR_DEF _VAR_VALUE _VAR_NAME _SECRET_FILE;
5 | _create_dir_if_empty "${SECRETS_DIR}";
6 | for _VAR_DEF in $(printenv); do
7 | _VAR_NAME="$(echo "${_VAR_DEF}" | sed -e 's,\([^=]*\)=.*,\1,')";
8 | _VAR_VALUE="$(echo "${_VAR_DEF}" | sed -e 's,[^=]*=\(.*\),\1,')";
9 | if [ "$(expr "${_VAR_NAME}" : '.\+_SECRET$')" -gt 0 ]; then
10 | # It seems to be a '_SECRET' variable. Persist it.
11 | _SECRET_FILE="${SECRETS_DIR}/${_VAR_NAME}";
12 | _VAR_VALUE="$(cat "${_SECRET_FILE}" 2> /dev/null || \
13 | echo "${_VAR_VALUE}")";
14 | echo "${_VAR_VALUE}" > "${_SECRET_FILE}";
15 | fi
16 | done
17 | }
18 |
19 | init_runtime_env_variables () {
20 | # Initialize environment variables. Variables prefixed with an
21 | # underscore are 'calculated' variables. That means their value is
22 | # inferred by the values of other variables.
23 |
24 | # === General ===
25 | # Simple (too general) domain recognizing regular expression.
26 | local _DOMAIN_REGEX='[^[:space:]]\{1,63\}\.\+[^[:space:].]\+$';
27 | _check_value 'FQDN' "${_DOMAIN_REGEX}" 'exit';
28 | _check_value 'MAIL_DOMAIN' "${_DOMAIN_REGEX}" "${FQDN}";
29 | _check_value 'PRODUCT_NAME' '.\+' 'Wildduck Mail';
30 | _check_value 'MONGODB_HOST' '.\+' 'mongodb://mongo:27017/wildduck';
31 |
32 | # === General: Redis ===
33 | _check_value 'REDIS_HOST' '.\+' 'redis://redis:6379/8';
34 | # Split REDIS_HOST into components as we need the components in the
35 | # configuration files.
36 | export _REDIS_PORT="$(_get_url_part "${REDIS_HOST}" port)";
37 | export _REDIS_HOSTNAME="$(_get_url_part "${REDIS_HOST}" hostname)";
38 | export _REDIS_DB="$(_get_url_part "${REDIS_HOST}" path)";
39 |
40 | # === General ===
41 | export TLS_KEY="${TLS_KEY}";
42 | export TLS_CERT="${TLS_CERT}";
43 | export _USE_SSL='false';
44 | [ -n "${TLS_KEY}" -a -n "${TLS_CERT}" ] && export _USE_SSL='true';
45 | _check_value 'ENABLE_STARTTLS' 'true\|false' 'false';
46 | if [ "${ENABLE_STARTTLS}" = 'true' -a "${_USE_SSL}" = 'false' ]; then
47 | export ENABLE_STARTTLS='false';
48 | fi
49 |
50 | # === General: Graylog ===
51 | export GRAYLOG_HOST_PORT="${GRAYLOG_HOST_PORT}";
52 | export _GRAYLOG_PORT="$(_get_url_part "${GRAYLOG_HOST_PORT}" port)";
53 | export _GRAYLOG_HOSTNAME="$(_get_url_part "${GRAYLOG_HOST_PORT}" \
54 | hostname)";
55 | export _GRAYLOG_ENABLE='false';
56 | if [ -n "${_GRAYLOG_HOSTNAME}" -a -n "${_GRAYLOG_PORT}" ]; then
57 | export _GRAYLOG_ENABLE='true';
58 | fi
59 |
60 |
61 | # === API ===
62 | local PROTO='http';
63 | _check_value 'API_ENABLE' 'true\|false' 'true';
64 | _check_value 'API_USE_HTTPS' 'true\|false' 'false';
65 | _check_value 'API_TOKEN_SECRET' '.\+' '';
66 |
67 | export _API_ACCESS_CONTROL_ENABLE='false';
68 | export _API_ACCESS_CONTROL_SECRET="$(_get_random_string)";
69 | [ -n "${API_TOKEN_SECRET}" ] && export _API_ACCESS_CONTROL_ENABLE='true';
70 |
71 | export _API_PORT=80;
72 | if [ "${API_USE_HTTPS}" = 'true' -a "${_USE_SSL}" = 'true' ]; then
73 | PROTO="${PROTO}s";
74 | export _API_PORT=443;
75 | else
76 | export API_USE_HTTPS='false';
77 | fi
78 |
79 | _check_value 'API_URL' '.\+' "${PROTO}://${FQDN}";
80 |
81 |
82 | # === Configprofile ===
83 | # default identifier for mobilconfig is the first two parts of the
84 | # reversed FQDN with '.wildduck' appended.
85 | local REV_FQDN="$(echo $FQDN | \
86 | awk '{n = split($0,v,"."); print v[n]"."v[n-1]}').wildduck";
87 | _check_value 'CONFIGPROFILE_ID' '.\+' "${REV_FQDN}";
88 | _check_value 'CONFIGPROFILE_DISPLAY_NAME' '.\+' "${PRODUCT_NAME}";
89 | _check_value 'CONFIGPROFILE_DISPLAY_ORGANIZATION' '.\+' 'Unknown';
90 | _check_value 'CONFIGPROFILE_DISPLAY_DESC' '.\+' \
91 | 'Install this profile to setup {email}';
92 | _check_value 'CONFIGPROFILE_ACCOUNT_DESC' '.\+' '{email}';
93 |
94 |
95 | # === IMAP ===
96 | _check_value 'IMAP_PROCESSES' '[[:digit:]]\+$' '2';
97 | _check_value 'IMAP_RETENTION' '[[:digit:]]\+$' '4';
98 | export _IMAP_DISABLE_STARTTLS='true';
99 | export _IMAP_PORT=143;
100 | if [ "${_USE_SSL}" = 'true' ]; then
101 | export _IMAP_PORT=993;
102 | if [ "${ENABLE_STARTTLS}" = 'true' ]; then
103 | export _IMAP_DISABLE_STARTTLS='false';
104 | fi
105 | fi
106 |
107 |
108 | # === Outbound SMTP ===
109 | export _OUTBOUND_SMTP_PORT=587;
110 | export _OUTBOUND_SMTP_SECRET="$(_get_random_string)";
111 | export _OUTBOUND_SMTP_ALLOW_FUTURE_DATE='true';
112 | [ "${_USE_SSL}" = 'true' ] && export _OUTBOUND_SMTP_PORT=465;
113 | [ "${ENABLE_SMTP_SEND_LATER}" = 'false' ] && export _OUTBOUND_SMTP_ALLOW_FUTURE_DATE='true';
114 |
115 |
116 | # === Misc ===
117 | export _COCOF_ADD='{"op": "add", "path": "%s", "value": %s}';
118 | export _TOTP_SECRET="$(_get_random_string)";
119 | export _SRS_SECRET="$(_get_random_string)";
120 | export _DKIM_SECRET="$(_get_random_string)";
121 | export _SMTP_PORT='587';
122 | [ "${_USE_SSL}" = 'true' ] && export SMTP_PORT='465';
123 |
124 | _persist_secret_variables;
125 | }
126 |
--------------------------------------------------------------------------------
/scripts/_utils.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | _check_value () {
4 | # Check if environment variables have a valid value. If not do
5 | # any of the actions and possibly reset the environment variable to
6 | # the specified default value.
7 | # Run as:
8 | # _check_value []
9 | #
10 | # Args:
11 | # VAR_NAME: The variable name of the variable holding the value to
12 | # check.
13 | # PATTERN: The basic regular expression against which to check the
14 | # value of the variable VAR_NAME.
15 | # DEFAULT_VALUE: The default value that shall be assigned to the variable
16 | # VAR_NAME if the value does not match PATTERN. If ACTION
17 | # is not provided and DEFAULT_VALUE = exit, it will be
18 | # assumed the ACTION = exit.
19 | # ACTION: The action to do, if the variable value does not match
20 | # the PATTERN. The following actions are supported:
21 | # - : Print a warning text, but only if the
22 | # variable is not empty. Set the variable
23 | # to DEFAULT_VALUE afterwards.
24 | # - warn: Print a warning text and set the
25 | # variable to DEFAULT_VALUE.
26 | # - exit: Print a warning text and exit the
27 | # program.
28 | local VAR_NAME="${1}";
29 | local PATTERN="${2}";
30 | local DEFAULT_VALUE="${3}";
31 | local ACTION="${4}";
32 | eval "local VAR_VALUE=\"\${${VAR_NAME}}\"";
33 |
34 | # If pattern does not match expr returns 0 matched characters
35 | if [ "$(expr match "${VAR_VALUE}" "${PATTERN}")" -eq 0 ]; then
36 | if [ "${ACTION}" = "warn" ] ||
37 | [ -z "${ACTION}" -a "$(expr length "${VAR_VALUE}")" -gt 0 ]; then
38 | echo 'You have supplied an invalid value for ' \
39 | "${VAR_NAME} (${VAR_VALUE:-none})." \
40 | "Setting it to default value: ${DEFAULT_VALUE}" 1>&2;
41 |
42 | elif [ "${ACTION}" = "exit" ] || \
43 | [ -z "${ACTION}" -a "${DEFAULT_VALUE}" = "exit" ]; then
44 | echo "You have to supply a valid value for ${VAR_NAME}." \
45 | "See the documentation for more information." \
46 | "Shutting down...";
47 | exit 1;
48 | fi
49 |
50 | export ${VAR_NAME}="${DEFAULT_VALUE}";
51 | fi
52 | }
53 |
54 |
55 | _is_dir_empty () {
56 | # Check if a directory at a given path is empty.
57 | # Run as:
58 | # _is_dir_empty
59 | #
60 | # Returns:
61 | # - 0: If the directory is empty.
62 | # - 2: If the directory is not empty.
63 | # - 4: If the directory does not exist.
64 | local DIR="${1}";
65 | if [ ! -d "${DIR}" ]; then
66 | return 4;
67 | elif [ "$(ls -A "${DIR}" 2> /dev/null | wc -l)" -ne 0 ]; then
68 | # Directory seems to contain elements => not empty.
69 | return 2;
70 | fi
71 | return 0;
72 | }
73 |
74 |
75 | _create_dir_if_empty () {
76 | # Create a directory at the specified path. Return an error if the
77 | # directory cannot be created or there is already a directory that
78 | # contains elements.
79 | # Run as:
80 | # _create_dir_if_empty
81 | #
82 | # Returns:
83 | # - 0: If the directory was created successfully or exists
84 | # already, but is empty.
85 | # - 1: If the directory exists already, but is not empty.
86 | local DIR="${1}" RET;
87 | _is_dir_empty "${DIR}";
88 | RET=$?;
89 | if [ "${RET}" -eq 4 ]; then
90 | mkdir -p "${DIR}";
91 | elif [ "${RET}" -eq 2 ]; then
92 | return 1;
93 | fi
94 | return 0;
95 | }
96 |
97 |
98 | _get_random_string () {
99 | # Generate a random string of given length. Default length is 32.
100 | # Run as:
101 | # _get_random_string []
102 | LEN="${1:-32}";
103 | tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c"${LEN}";
104 | }
105 |
106 |
107 | _get_url_part () {
108 | # Parse and echo the specified part of an URL.
109 | # Run as:
110 | # _get_url_part
111 | #
112 | # Args:
113 | # URL: the url of which the part shall be parsed of.
114 | # part: Any of 'hostname', 'port' or 'path'. 'path' returns the
115 | # URL path without the first slash.
116 | local URL="${1}" PART="${2}";
117 | if [ "${PART}" = 'port' ]; then
118 | echo "${URL}" | sed -e 's,^.*:,:,g' \
119 | -e 's,.*:\([0-9]*\).*,\1,g' \
120 | -e 's,[^0-9],,g';
121 | elif [ "${PART}" = 'hostname' ]; then
122 | echo "${URL}" | sed -e 's,.*://,,' \
123 | -e 's,^\([^:/]\+\)[:/]\?.*,\1,';
124 | elif [ "${PART}" = 'path' ]; then
125 | echo "${URL}" |sed -e 's,[^:/]*\(://\)\?[^/]*/\(.*\),\2,';
126 | fi
127 | }
128 |
--------------------------------------------------------------------------------
/scripts/_wildduck.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | _wildduck_configure_default () {
4 | local COMS FPATH SECURE='false';
5 | FPATH="${WILDDUCK_CONFIG_DIR}/default.toml";
6 | COMS="[
7 | $(printf "${_COCOF_ADD}" /processes "\"${IMAP_PROCESSES}\""),
8 | $(printf "${_COCOF_ADD}" /emailDomain "\"${MAIL_DOMAIN}\""),
9 | $(printf "${_COCOF_ADD}" /totp/cipher \"aes192\"),
10 | $(printf "${_COCOF_ADD}" /totp/secret "\"${_TOTP_SECRET}\""),
11 | $(printf "${_COCOF_ADD}" /u2f/appId "\"${API_URL}\""),
12 | $(printf "${_COCOF_ADD}" /smtp/setup/hostname "\"${FQDN}\""),
13 | $(printf "${_COCOF_ADD}" /smtp/setup/secure ${_USE_SSL}),
14 | $(printf "${_COCOF_ADD}" /smtp/setup/port ${_SMTP_PORT}),
15 | $(printf "${_COCOF_ADD}" /log/gelf/enabled ${_GRAYLOG_ENABLE}),
16 | $(printf "${_COCOF_ADD}" /log/gelf/hostname "\"${FQDN}\""),
17 | $(printf "${_COCOF_ADD}" /log/gelf/options/graylogPort ${_GRAYLOG_PORT:-1}),
18 | $(printf "${_COCOF_ADD}" /log/gelf/options/graylogHostname "\"${_GRAYLOG_HOSTNAME}\"")
19 | ]";
20 |
21 | cocof "${FPATH}" "${COMS}";
22 | }
23 |
24 |
25 | _wildduck_configure_api () {
26 | local COMS FPATH;
27 | FPATH="${WILDDUCK_CONFIG_DIR}/api.toml";
28 |
29 | COMS="[
30 | $(printf "${_COCOF_ADD}" /enabled ${API_ENABLE}),
31 | $(printf "${_COCOF_ADD}" /port ${_API_PORT}),
32 | $(printf "${_COCOF_ADD}" /host "\"0.0.0.0\""),
33 | $(printf "${_COCOF_ADD}" /secure ${API_USE_HTTPS}),
34 | $(printf "${_COCOF_ADD}" /accessToken "\"${API_TOKEN_SECRET}\""),
35 | $(printf "${_COCOF_ADD}" /accessControl/enabled ${_API_ACCESS_CONTROL_ENABLE}),
36 | $(printf "${_COCOF_ADD}" /accessControl/secret "\"${_API_ACCESS_CONTROL_SECRET}\""),
37 | $(printf "${_COCOF_ADD}" /mobileconfig/identifier "\"${CONFIGPROFILE_ID}\""),
38 | $(printf "${_COCOF_ADD}" /mobileconfig/displayName "\"${CONFIGPROFILE_DISPLAY_NAME}\""),
39 | $(printf "${_COCOF_ADD}" /mobileconfig/organization "\"${CONFIGPROFILE_DISPLAY_ORGANIZATION}\""),
40 | $(printf "${_COCOF_ADD}" /mobileconfig/displayDescription "\"${CONFIGPROFILE_DISPLAY_DESC}\""),
41 | $(printf "${_COCOF_ADD}" /mobileconfig/accountDescription "\"${CONFIGPROFILE_ACCOUNT_DESC}\"")
42 | ]";
43 | cocof "${FPATH}" "${COMS}";
44 | }
45 |
46 |
47 | _wildduck_configure_dbs () {
48 | local COMS FPATH;
49 | FPATH="${WILDDUCK_CONFIG_DIR}/dbs.toml";
50 |
51 | COMS="[
52 | $(printf "${_COCOF_ADD}" /mongo "\"${MONGODB_HOST}\""),
53 | $(printf "${_COCOF_ADD}" /redis/host "\"${_REDIS_HOSTNAME}\""),
54 | $(printf "${_COCOF_ADD}" /redis/port ${_REDIS_PORT}),
55 | $(printf "${_COCOF_ADD}" /redis/db ${_REDIS_DB})
56 | ]";
57 |
58 | cocof "${FPATH}" "${COMS}";
59 | }
60 |
61 |
62 | _wildduck_configure_dkim () {
63 | local COMS FPATH;
64 | FPATH="${WILDDUCK_CONFIG_DIR}/dkim.toml";
65 |
66 | COMS="[
67 | $(printf "${_COCOF_ADD}" /cipher "\"aes192\""),
68 | $(printf "${_COCOF_ADD}" /secret "\"${_DKIM_SECRET}\"")
69 | ]";
70 |
71 | cocof "${FPATH}" "${COMS}";
72 | }
73 |
74 |
75 | _wildduck_configure_imap () {
76 | local COMS FPATH;
77 | FPATH="${WILDDUCK_CONFIG_DIR}/imap.toml";
78 |
79 | COMS="[
80 | $(printf "${_COCOF_ADD}" /name "\"${PRODUCT_NAME} IMAP\""),
81 | $(printf "${_COCOF_ADD}" /enabled 'true'),
82 | $(printf "${_COCOF_ADD}" /port ${_IMAP_PORT}),
83 | $(printf "${_COCOF_ADD}" /host "\"0.0.0.0\""),
84 | $(printf "${_COCOF_ADD}" /secure ${_USE_SSL}),
85 | $(printf "${_COCOF_ADD}" /retention ${IMAP_RETENTION}),
86 | $(printf "${_COCOF_ADD}" /disableSTARTTLS ${_IMAP_DISABLE_STARTTLS}),
87 | $(printf "${_COCOF_ADD}" /setup/hostname "\"${FQDN}\""),
88 | $(printf "${_COCOF_ADD}" /setup/secure ${_USE_SSL})
89 | ]";
90 |
91 | cocof "${FPATH}" "${COMS}";
92 | }
93 |
94 |
95 | _wildduck_configure_lmtp () {
96 | local COMS FPATH;
97 | FPATH="${WILDDUCK_CONFIG_DIR}/lmtp.toml";
98 |
99 | COMS="[
100 | $(printf "${_COCOF_ADD}" /enabled false)
101 | ]";
102 |
103 | cocof "${FPATH}" "${COMS}";
104 | }
105 |
106 |
107 | _wildduck_configure_pop3 () {
108 | local COMS FPATH;
109 | FPATH="${WILDDUCK_CONFIG_DIR}/pop3.toml";
110 |
111 | COMS="[
112 | $(printf "${_COCOF_ADD}" /enabled false)
113 | ]";
114 |
115 | cocof "${FPATH}" "${COMS}";
116 | }
117 |
118 |
119 | _wildduck_configure_tls () {
120 | local COMS FPATH;
121 | FPATH="${WILDDUCK_CONFIG_DIR}/tls.toml";
122 |
123 | [ "${_USE_SSL}" = 'false' ] && return;
124 |
125 | COMS="[
126 | $(printf "${_COCOF_ADD}" /key "\"${TLS_KEY}\""),
127 | $(printf "${_COCOF_ADD}" /cert "\"${TLS_CERT}\"")
128 | ]";
129 |
130 | cocof "${FPATH}" "${COMS}";
131 | }
132 |
133 |
134 | configure_wildduck () {
135 | # only configure wildduck if the user has not mounted his own
136 | # configuration files at $WILDDUCK_CONFIG_DIR.
137 | [ "${USE_OWN_SETTINGS}" = 'true' ] && return 0;
138 |
139 | _wildduck_configure_default;
140 | _wildduck_configure_api;
141 | _wildduck_configure_dbs;
142 | _wildduck_configure_imap;
143 | _wildduck_configure_lmtp;
144 | _wildduck_configure_pop3;
145 | _wildduck_configure_tls;
146 | return 0;
147 | }
148 |
149 |
150 | start_wildduck () {
151 | cd "${WILDDUCK_INSTALL_DIR}";
152 | NODE_ENV=production node server.js \
153 | --config="${WILDDUCK_CONFIG_DIR}/default.toml";
154 | return $?;
155 | }
156 |
--------------------------------------------------------------------------------
/scripts/_zonemta.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | _zonemta_configure_interface () {
4 | local COMS FPATH;
5 | FPATH="${ZONEMTA_CONFIG_DIR}/interfaces/feeder.toml";
6 |
7 | COMS="[
8 | $(printf "${_COCOF_ADD}" /feeder/processes 2),
9 | $(printf "${_COCOF_ADD}" /feeder/port ${_OUTBOUND_SMTP_PORT}),
10 | $(printf "${_COCOF_ADD}" /feeder/host '"0.0.0.0"'),
11 | $(printf "${_COCOF_ADD}" /feeder/secure ${_USE_SSL}),
12 | $(printf "${_COCOF_ADD}" /feeder/starttls ${ENABLE_STARTTLS}),
13 | $(printf "${_COCOF_ADD}" /feeder/authentication true)
14 | ]";
15 |
16 | cocof "${FPATH}" "${COMS}";
17 | echo "# @include \"${WILDDUCK_CONFIG_DIR}/tls.toml\"" >> "${FPATH}";
18 | }
19 |
20 |
21 | _zonemta_configure_dbs () {
22 | echo "# @include \"${WILDDUCK_CONFIG_DIR}/dbs.toml\"" \
23 | > "${ZONEMTA_CONFIG_DIR}/dbs-production.toml";
24 | }
25 |
26 |
27 | _zonemta_configure_pools () {
28 | echo "[[default]]
29 | address=\"0.0.0.0\"
30 | name=\"${FQDN}\"" > "${ZONEMTA_CONFIG_DIR}/pools.toml";
31 | }
32 |
33 |
34 | _zonemta_configure_loop_breaker () {
35 | local COMS FPATH SECURE='false';
36 | FPATH="${ZONEMTA_CONFIG_DIR}/plugins/loop-breaker.toml";
37 |
38 | # According to Json Pointer RFC 6901 a slash must be encoded as
39 | # '~1'.
40 | COMS="[
41 | $(printf "${_COCOF_ADD}" '/modules~1zonemta-loop-breaker/secret' \
42 | "\"${_OUTBOUND_SMTP_SECRET}\"")
43 | ]";
44 |
45 | cocof "${FPATH}" "${COMS}";
46 | }
47 |
48 |
49 | _zonemta_configure_default_headers () {
50 | local COMS FPATH
51 | FPATH="${ZONEMTA_CONFIG_DIR}/plugins/default-headers.toml";
52 |
53 | # According to Json Pointer RFC 6901 a slash must be encoded as
54 | # '~1'.
55 | COMS="[
56 | $(printf "${_COCOF_ADD}" '/core~1default-headers/futureDate' \
57 | "${_OUTBOUND_SMTP_ALLOW_FUTURE_DATE}")
58 | ]";
59 |
60 | cocof "${FPATH}" "${COMS}";
61 | }
62 |
63 |
64 | _zonemta_configure_wildduck () {
65 | echo "[wildduck]
66 | enabled=[\"receiver\", \"sender\"]
67 | interfaces=[\"feeder\"]
68 | hostname=\"${FQDN}\"
69 | authlogExpireDays=30
70 | [wildduck.srs]
71 | enabled=true
72 | # SRS secret value. Must be the same as in the MX side
73 | secret=\"${_SRS_SECRET}\"
74 | rewriteDomain=\"${MAIL_DOMAIN}\"
75 | [wildduck.dkim]
76 | # @include \"${WILDDUCK_CONFIG_DIR}/dkim.toml\"
77 | " > "${ZONEMTA_CONFIG_DIR}/plugins/wildduck.toml";
78 | }
79 |
80 |
81 | configure_zonemta () {
82 | # only configure ZoneMTA if the user has not mounted his own
83 | # configuration files at $ZONEMTA_CONFIG_DIR.
84 | [ "${USE_OWN_SETTINGS}" = 'true' ] && return 0;
85 |
86 | _zonemta_configure_interface;
87 | _zonemta_configure_dbs;
88 | _zonemta_configure_pools;
89 | _zonemta_configure_loop_breaker;
90 | _zonemta_configure_default_headers;
91 | _zonemta_configure_wildduck;
92 | return 0;
93 | }
94 |
95 |
96 | start_zonemta () {
97 | cd "${ZONEMTA_INSTALL_DIR}";
98 | NODE_ENV=production node index.js \
99 | --config="${ZONEMTA_CONFIG_DIR}/zonemta.toml";
100 | return $?;
101 | }
102 |
--------------------------------------------------------------------------------
/scripts/bin/generate_dkim:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | source "${SCRIPTS_DIR}/00-define_variables.sh";
4 | source "${SCRIPTS_DIR}/_utils.sh";
5 | source "${SCRIPTS_DIR}/_init-env-vars.sh";
6 | init_runtime_env_variables;
7 |
8 |
9 | _generate_dkim_key () {
10 | local _DOMAIN="${1}" _DKIM_SELECTOR _DKIM_PRVK_FILENAME;
11 | local _DKIN_PUBK_FILENAME _DKIM_EXISTS;
12 | _create_dir_if_empty "${DKIM_KEYS_DIR}";
13 |
14 | _DKIM_EXISTS="$(ls -l "${DKIM_KEYS_DIR}"/${_DOMAIN}* \
15 | 2> /dev/null | wc -l)";
16 |
17 | cd "${DKIM_KEYS_DIR}";
18 | if [ "${_DKIM_EXISTS}" -eq 0 ]; then
19 | _DKIM_SELECTOR="$(node -e 'console.log(Date()
20 | .toString()
21 | .substr(4, 3)
22 | .toLowerCase() +
23 | new Date().getFullYear())'
24 | )";
25 |
26 | _DKIM_PRVK_FILENAME="${DOMAIN}_${_DKIM_SELECTOR}_dkim.pem";
27 | _DKIM_PUBK_FILENAME="${DOMAIN}_${_DKIM_SELECTOR}_dkim.cert";
28 |
29 | openssl genrsa -out "${_DKIM_PRVK_FILENAME}" 2048;
30 | chmod 400 "${_DKIM_PRVK_FILENAME}";
31 | openssl rsa \
32 | -in "${_DKIM_PRVK_FILENAME}" \
33 | -out "${_DKIM_PUBK_FILENAME}" \
34 | -pubout;
35 | link_dkim_keys;
36 | fi
37 | _add_to_db;
38 | }
39 |
40 |
41 | _add_to_db () {
42 | local _DOMAIN="${DOMAIN}" _PUB _DKIM_SELECTOR;
43 | local _DKIN_PUBK_FILENAME _DKIM_DNS _PUBK_VALUE _DKIM_JSON;
44 | local AUTH_HEADER;
45 | [ -n "${API_TOKEN_SECRET}" ] && \
46 | AUTH_HEADER="X-Access-Token: ${API_TOKEN_SECRET}";
47 |
48 | cd "${DKIM_KEYS_DIR}";
49 | # ATTENTION: Changing the path expression in the for loop will
50 | # render the regular expressions in the loop body useless.
51 | # Keep that in mind if you have to change it.
52 | for _PUB in *; do
53 | if [ "$(expr "${_PUB}" : "${_DOMAIN}_.*")" -gt 0 ]; then
54 | _DKIM_SELECTOR="$(echo "${_PUB}" | \
55 | sed -e 's,[^_]*_\([^_]*\)_[^_]*,\1,')";
56 | _DKIM_PUBK_FILENAME="${_PUB}";
57 | _DKIM_PRVK_FILENAME="$(echo "${_PUB}" | sed -e 's,\(.*\)\.cert,\1.pem,')";
58 | break;
59 | fi
60 | done
61 | [ -z "${_DKIM_SELECTOR}" ] && return 1;
62 |
63 | _PUBK_VALUE="$(grep -v -e '^-' "${_DKIM_PUBK_FILENAME}" | tr -d "\n")";
64 | _DKIM_DNS="v=DKIM1;k=rsa;p=${_PUBK_VALUE}";
65 | _DKIM_JSON="$(DOMAIN="${_DOMAIN}" \
66 | SELECTOR="${_DKIM_SELECTOR}" \
67 | KEYSDIR="${DKIM_KEYS_DIR}" \
68 | KEYNAME="${_DKIM_PRVK_FILENAME}" \
69 | node -e 'console.log(JSON.stringify({
70 | domain: process.env.DOMAIN,
71 | selector: process.env.SELECTOR,
72 | description: "Default DKIM key for "+process.env.DOMAIN,
73 | privateKey: fs.readFileSync(
74 | process.env.KEYSDIR + "/" + process.env.KEYNAME,
75 | "UTF-8"
76 | )
77 | }))'
78 | )";
79 |
80 | curl -i -XPOST ${API_URL}/dkim \
81 | --output /dev/null \
82 | --silent \
83 | -H 'Content-type: application/json' \
84 | -H "${AUTH_HEADER}" \
85 | -d "${_DKIM_JSON}";
86 |
87 | if [ $? -eq 0 ]; then
88 | echo "Please add the following TXT record to your DNS:
89 | ${_DKIM_SELECTOR}._domainkey.${_DOMAIN}. IN TXT \"${_DKIM_DNS}\"
90 | ";
91 | fi
92 | }
93 |
94 |
95 | DOMAIN="${1}";
96 | if [ -z "${DOMAIN}" ]; then
97 | read -p 'For which domain shall the DKIM key be generated? ' DOMAIN;
98 | fi
99 | _generate_dkim_key "${DOMAIN}";
100 |
--------------------------------------------------------------------------------
/scripts/bin/link_dkim_keys:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | source "${SCRIPTS_DIR}/00-define_variables.sh";
4 | source "${SCRIPTS_DIR}/_utils.sh";
5 | source "${SCRIPTS_DIR}/_init-env-vars.sh";
6 | init_runtime_env_variables;
7 |
8 | link_dkim_keys () {
9 | local _PUB;
10 | cd "${DKIM_KEYS_DIR}";
11 | rm "${ZONEMTA_INSTALL_DIR}/keys"/*;
12 | for _PUB in $(pwd)/*; do
13 | ln -s "${_PUB}" "${ZONEMTA_INSTALL_DIR}/keys";
14 | done
15 |
16 | }
17 |
18 | link_dkim_keys;
19 |
--------------------------------------------------------------------------------
/scripts/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | source "${SCRIPTS_DIR}/00-define_variables.sh";
4 | source "${SCRIPTS_DIR}/_utils.sh";
5 | source "${SCRIPTS_DIR}/_init-env-vars.sh";
6 | source "${SCRIPTS_DIR}/_wildduck.sh";
7 | source "${SCRIPTS_DIR}/_haraka.sh";
8 | source "${SCRIPTS_DIR}/_zonemta.sh";
9 | source "${SCRIPTS_DIR}/_antispam.sh";
10 | source "${SCRIPTS_DIR}/_dkim.sh";
11 |
12 | main () {
13 | # === Configure ===
14 | init_runtime_env_variables;
15 | configure_wildduck;
16 | configure_haraka;
17 | configure_zonemta;
18 | configure_antispam;
19 | link_dkim_keys;
20 |
21 | # === Start ===
22 | start_antispam;
23 |
24 | start_wildduck &
25 | local WILDDUCK_PID=$!;
26 |
27 | start_haraka &
28 | local HARAKA_PID=$!;
29 |
30 | start_zonemta &
31 | local ZONEMTA_PID=$!;
32 |
33 | add_dkim_for_mail_domain;
34 |
35 | wait $WILDDUCK_PID;
36 | wait $HARAKA_PID;
37 | wait $ZONEMTA_PID;
38 | }
39 |
40 | main "$@";
41 |
--------------------------------------------------------------------------------