├── .ansible-lint ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ ├── release.yml │ └── stale.yml ├── .gitignore ├── .yamllint ├── LICENSE ├── README.md ├── defaults └── main.yml ├── meta └── main.yml ├── molecule └── default │ ├── converge.yml │ ├── molecule.yml │ ├── requirements.yml │ └── test-message ├── tasks └── main.yml └── templates ├── mailhog.init.j2 └── mailhog.unit.j2 /.ansible-lint: -------------------------------------------------------------------------------- 1 | skip_list: 2 | - 'yaml' 3 | - 'risky-shell-pipe' 4 | - 'role-name' 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | --- 3 | github: geerlingguy 4 | patreon: geerlingguy 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 'on': 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: "0 3 * * 3" 10 | 11 | defaults: 12 | run: 13 | working-directory: 'geerlingguy.mailhog' 14 | 15 | jobs: 16 | 17 | lint: 18 | name: Lint 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Check out the codebase. 22 | uses: actions/checkout@v4 23 | with: 24 | path: 'geerlingguy.mailhog' 25 | 26 | - name: Set up Python 3. 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.x' 30 | 31 | - name: Install test dependencies. 32 | run: pip3 install yamllint 33 | 34 | - name: Lint code. 35 | run: | 36 | yamllint . 37 | 38 | molecule: 39 | name: Molecule 40 | runs-on: ubuntu-latest 41 | strategy: 42 | matrix: 43 | distro: 44 | # See: https://github.com/geerlingguy/docker-rockylinux9-ansible/issues/6 45 | # - rockylinux9 46 | - ubuntu2004 47 | 48 | steps: 49 | - name: Check out the codebase. 50 | uses: actions/checkout@v4 51 | with: 52 | path: 'geerlingguy.mailhog' 53 | 54 | - name: Set up Python 3. 55 | uses: actions/setup-python@v5 56 | with: 57 | python-version: '3.x' 58 | 59 | - name: Install test dependencies. 60 | run: pip3 install ansible molecule molecule-plugins[docker] docker 61 | 62 | - name: Run Molecule tests. 63 | run: molecule test 64 | env: 65 | PY_COLORS: '1' 66 | ANSIBLE_FORCE_COLOR: '1' 67 | MOLECULE_DISTRO: ${{ matrix.distro }} 68 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This workflow requires a GALAXY_API_KEY secret present in the GitHub 3 | # repository or organization. 4 | # 5 | # See: https://github.com/marketplace/actions/publish-ansible-role-to-galaxy 6 | # See: https://github.com/ansible/galaxy/issues/46 7 | 8 | name: Release 9 | 'on': 10 | push: 11 | tags: 12 | - '*' 13 | 14 | defaults: 15 | run: 16 | working-directory: 'geerlingguy.mailhog' 17 | 18 | jobs: 19 | 20 | release: 21 | name: Release 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Check out the codebase. 25 | uses: actions/checkout@v4 26 | with: 27 | path: 'geerlingguy.mailhog' 28 | 29 | - name: Set up Python 3. 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: '3.x' 33 | 34 | - name: Install Ansible. 35 | run: pip3 install ansible-core 36 | 37 | - name: Trigger a new import on Galaxy. 38 | run: >- 39 | ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} 40 | $(echo ${{ github.repository }} | cut -d/ -f1) $(echo ${{ github.repository }} | cut -d/ -f2) 41 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Close inactive issues 3 | 'on': 4 | schedule: 5 | - cron: "55 1 * * 0" # semi-random time 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v8 15 | with: 16 | days-before-stale: 120 17 | days-before-close: 60 18 | exempt-issue-labels: bug,pinned,security,planned 19 | exempt-pr-labels: bug,pinned,security,planned 20 | stale-issue-label: "stale" 21 | stale-pr-label: "stale" 22 | stale-issue-message: | 23 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 24 | 25 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 28 | stale-pr-message: | 29 | This pr has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 30 | 31 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 32 | close-pr-message: | 33 | This pr has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.retry 2 | */__pycache__ 3 | *.pyc 4 | .cache 5 | 6 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 160 7 | level: warning 8 | 9 | ignore: | 10 | .github/workflows/stale.yml 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jeff Geerling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: MailHog 2 | 3 | [![CI](https://github.com/geerlingguy/ansible-role-mailhog/actions/workflows/ci.yml/badge.svg)](https://github.com/geerlingguy/ansible-role-mailhog/actions/workflows/ci.yml) 4 | 5 | Installs [MailHog](https://github.com/mailhog/MailHog), a Go-based SMTP server and web UI/API for displaying captured emails, on RedHat or Debian-based linux systems. 6 | 7 | Also installs [mhsendmail](https://github.com/mailhog/mhsendmail) so you can redirect system mail to MailHog's built-in SMTP server. 8 | 9 | If you're using PHP and would like to route all PHP email into MailHog, you will need to update the `sendmail_path` configuration option in php.ini, like so: 10 | 11 | sendmail_path = "{{ mailhog_install_dir }}/mhsendmail" 12 | 13 | (Replace `{{ mailhog_install_dir }}` with the actual MailHog installation directory, which is `/opt/mailhog` by default—e.g. `/opt/mailhog/mhsendmail`). 14 | 15 | ## Requirements 16 | 17 | None. 18 | 19 | ## Role Variables 20 | 21 | Available variables are listed below, along with default values (see `defaults/main.yml`): 22 | 23 | mailhog_install_dir: /opt/mailhog 24 | 25 | The directory into which the MailHog binary will be installed. 26 | 27 | mailhog_version: 1.0.0 28 | 29 | The version of MailHog that will be installed. You can find the latest version by visiting the [MailHog project releases page](https://github.com/mailhog/MailHog/releases). 30 | 31 | mailhog_binary_url: "https://github.com/mailhog/MailHog/releases/download/v{{ mailhog_version }}/MailHog_linux_amd64" 32 | 33 | The MailHog binary that will be installed. You can find the latest version or a 32-bit version by visiting the [MailHog project releases page](https://github.com/mailhog/MailHog/releases). 34 | 35 | mailhog_daemonize_bin_path: /usr/sbin/daemonize 36 | 37 | The path to `daemonize`, which is used to launch MailHog via init script. 38 | 39 | mhsendmail_version: 0.2.0 40 | 41 | The version of the mhsendmail binary that will be installed. You can find the latest version by visiting the [mhsendmail project releases page](https://github.com/mailhog/mhsendmail/releases). 42 | 43 | mhsendmail_binary_url: "https://github.com/mailhog/mhsendmail/releases/download/v{{ mhsendmail_version }}/mhsendmail_linux_amd64" 44 | 45 | The mhsendmail binary that will be installed. You can find the latest version or a 32-bit version by visiting the [mhsendmail project releases page](https://github.com/mailhog/mhsendmail/releases). 46 | 47 | ## Dependencies 48 | 49 | - geerlingguy.daemonize 50 | 51 | ## Example Playbook 52 | 53 | - hosts: servers 54 | roles: 55 | - { role: geerlingguy.mailhog } 56 | 57 | ## License 58 | 59 | MIT / BSD 60 | 61 | ## Author Information 62 | 63 | This role was created in 2014 by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/). 64 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mailhog_arch: "{{ 'arm' if ansible_architecture == 'aarch64' else 'amd64' }}" 3 | mailhog_install_dir: /opt/mailhog 4 | mailhog_version: 1.0.0 5 | mailhog_binary_url: "https://github.com/mailhog/MailHog/releases/download/v{{ mailhog_version }}/MailHog_linux_{{ mailhog_arch }}" 6 | mhsendmail_version: 0.2.0 7 | mhsendmail_binary_url: "https://github.com/mailhog/mhsendmail/releases/download/v{{ mhsendmail_version }}/mhsendmail_linux_{{ mailhog_arch }}" 8 | 9 | # Path to daemonize, which is used to launch MailHog via init script. 10 | mailhog_daemonize_bin_path: /usr/sbin/daemonize 11 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - geerlingguy.daemonize 4 | 5 | galaxy_info: 6 | role_name: mailhog 7 | author: geerlingguy 8 | description: "MailHog for Linux" 9 | company: "Midwestern Mac, LLC" 10 | license: "license (BSD, MIT)" 11 | min_ansible_version: 2.10 12 | platforms: 13 | - name: Ubuntu 14 | versions: 15 | - all 16 | - name: Debian 17 | versions: 18 | - all 19 | galaxy_tags: 20 | - development 21 | - web 22 | - system 23 | - mail 24 | - mailhog 25 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | #become: true 5 | 6 | pre_tasks: 7 | - name: Update apt cache. 8 | apt: update_cache=true cache_valid_time=600 9 | when: ansible_os_family == 'Debian' 10 | 11 | - name: Ensure build dependencies are installed (RedHat). 12 | dnf: 13 | name: 14 | - "@Development tools" 15 | - tar 16 | - unzip 17 | - net-tools 18 | - curl 19 | state: present 20 | allowerasing: true 21 | when: ansible_os_family == 'RedHat' 22 | 23 | - name: Ensure build dependencies are installed (Debian). 24 | apt: 25 | name: 26 | - build-essential 27 | - tar 28 | - unzip 29 | - net-tools 30 | - curl 31 | state: present 32 | when: ansible_os_family == 'Debian' 33 | 34 | roles: 35 | - geerlingguy.daemonize 36 | - geerlingguy.mailhog 37 | 38 | post_tasks: 39 | - name: Copy test message into place. 40 | copy: 41 | src: test-message 42 | dest: /tmp/test-message 43 | mode: 0644 44 | 45 | - name: Send an email via mhsendmail. 46 | shell: cat /tmp/test-message | /opt/mailhog/mhsendmail johndoe@example.com 47 | changed_when: false 48 | 49 | - name: Test retrieiving messages from the MailHog API. 50 | uri: 51 | url: http://localhost:8025/api/v2/messages 52 | register: result 53 | until: result.status == 200 54 | retries: 60 55 | delay: 1 56 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | role_name_check: 1 3 | dependency: 4 | name: galaxy 5 | options: 6 | ignore-errors: true 7 | driver: 8 | name: docker 9 | platforms: 10 | - name: instance 11 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux9}-ansible:latest" 12 | command: ${MOLECULE_DOCKER_COMMAND:-""} 13 | volumes: 14 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 15 | cgroupns_mode: host 16 | privileged: true 17 | pre_build_image: true 18 | provisioner: 19 | name: ansible 20 | playbooks: 21 | converge: ${MOLECULE_PLAYBOOK:-converge.yml} 22 | -------------------------------------------------------------------------------- /molecule/default/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - src: geerlingguy.daemonize 3 | -------------------------------------------------------------------------------- /molecule/default/test-message: -------------------------------------------------------------------------------- 1 | From: johndoe@example.com 2 | To: janedoe@example.com 3 | Subject: Test email 4 | 5 | Hello world! -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install and configure MailHog. 3 | - name: Ensure mailhog install directory exists. 4 | file: 5 | path: "{{ mailhog_install_dir }}" 6 | owner: root 7 | group: root 8 | state: directory 9 | mode: 0755 10 | 11 | - name: Download MailHog and mhsendmail binaries. 12 | get_url: 13 | url: "{{ item.url }}" 14 | dest: "{{ item.dest }}" 15 | owner: root 16 | group: root 17 | mode: 0755 18 | with_items: 19 | - url: "{{ mailhog_binary_url }}" 20 | dest: "{{ mailhog_install_dir }}/mailhog" 21 | - url: "{{ mhsendmail_binary_url }}" 22 | dest: "{{ mailhog_install_dir }}/mhsendmail" 23 | 24 | - name: Copy mailhog init script into place. 25 | template: 26 | src: mailhog.init.j2 27 | dest: /etc/init.d/mailhog 28 | owner: root 29 | group: root 30 | mode: 0755 31 | when: "ansible_service_mgr != 'systemd'" 32 | 33 | - name: Copy mailhog systemd unit file into place (for systemd systems). 34 | template: 35 | src: mailhog.unit.j2 36 | dest: /etc/systemd/system/mailhog.service 37 | owner: root 38 | group: root 39 | mode: 0755 40 | when: "ansible_service_mgr == 'systemd'" 41 | 42 | - name: Ensure mailhog is enabled and will start on boot. 43 | service: name=mailhog state=started enabled=yes 44 | -------------------------------------------------------------------------------- /templates/mailhog.init.j2: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # /etc/init.d/mailhog 3 | # 4 | # MailHog init script. 5 | # 6 | # @author Jeff Geerling 7 | 8 | ### BEGIN INIT INFO 9 | # Provides: mailhog 10 | # Required-Start: $remote_fs $syslog 11 | # Required-Stop: $remote_fs $syslog 12 | # Default-Start: 2 3 4 5 13 | # Default-Stop: 0 1 6 14 | # Short-Description: Start MailHog at boot time. 15 | # Description: Enable MailHog. 16 | ### END INIT INFO 17 | 18 | PID=/var/run/mailhog.pid 19 | LOCK=/var/lock/mailhog.lock 20 | USER=nobody 21 | BIN={{ mailhog_install_dir }}/mailhog 22 | DAEMONIZE_BIN={{ mailhog_daemonize_bin_path }} 23 | 24 | # Carry out specific functions when asked to by the system 25 | case "$1" in 26 | start) 27 | echo "Starting mailhog." 28 | $DAEMONIZE_BIN -p $PID -l $LOCK -u $USER $BIN 29 | ;; 30 | stop) 31 | if [ -f $PID ]; then 32 | echo "Stopping mailhog."; 33 | kill -TERM $(cat $PID); 34 | rm -f $PID; 35 | else 36 | echo "MailHog is not running."; 37 | fi 38 | ;; 39 | restart) 40 | echo "Restarting mailhog." 41 | if [ -f $PID ]; then 42 | kill -TERM $(cat $PID); 43 | rm -f $PID; 44 | fi 45 | $DAEMONIZE_BIN -p $PID -l $LOCK -u $USER $BIN 46 | ;; 47 | status) 48 | if [ -f $PID ]; then 49 | echo "MailHog is running."; 50 | else 51 | echo "MailHog is not running."; 52 | exit 3 53 | fi 54 | ;; 55 | *) 56 | echo "Usage: /etc/init.d/mailhog {start|stop|status|restart}" 57 | exit 1 58 | ;; 59 | esac 60 | 61 | exit 0 -------------------------------------------------------------------------------- /templates/mailhog.unit.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MailHog Email Catcher 3 | After=syslog.target network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart={{ mailhog_install_dir }}/mailhog 8 | StandardOutput=journal 9 | Restart=on-failure 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | --------------------------------------------------------------------------------