├── .github ├── FUNDING.yml └── workflows │ ├── integration_test_run.yml │ ├── integration_test_result.yml │ └── lint.yml ├── requirements_lint.txt ├── .yamllint ├── molecule └── default │ ├── prepare.yml │ ├── Usage.md │ ├── converge.yml │ ├── molecule.yml │ └── verify.yml ├── playbook.yml ├── .ansible-lint.yml ├── tasks ├── debian │ ├── database.yml │ └── main.yml └── main.yml ├── requirements.yml ├── defaults └── main │ ├── 1_main.yml │ └── 0_hardcoded.yml ├── meta └── main.yml ├── LICENSE.txt ├── filter_plugins └── utils.py ├── Prerequisites.md ├── README.md └── .pylintrc /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /requirements_lint.txt: -------------------------------------------------------------------------------- 1 | # pip requirements 2 | yamllint 3 | pylint 4 | ansible-core 5 | ansible-lint 6 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | 5 | rules: 6 | truthy: 7 | allowed-values: ['true', 'false', 'yes', 'no'] 8 | line-length: 9 | max: 190 10 | -------------------------------------------------------------------------------- /molecule/default/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Prepare 4 | hosts: all 5 | gather_facts: false 6 | tasks: 7 | - name: APT Update 8 | ansible.builtin.apt: 9 | update_cache: true 10 | -------------------------------------------------------------------------------- /playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ansible-playbook -K -D -i inventory/hosts.yml playbook.yml 4 | 5 | - name: Proxmox Mail Gateway 6 | hosts: all # should be limited 7 | become: true 8 | gather_facts: yes 9 | roles: 10 | - oxlorg.proxmox_mail_gw 11 | -------------------------------------------------------------------------------- /.ansible-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | skip_list: 4 | - 'var-naming' 5 | - 'no-handler' 6 | - '503' # no-handler code 7 | - 'role-name' 8 | - '106' 9 | - 'ignore-errors' 10 | - 'yaml' 11 | - '204' # line length => checked by yamllint 12 | - 'name[template]' 13 | -------------------------------------------------------------------------------- /molecule/default/Usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | Check out the [Molecule Tutorial](https://github.com/ansibleguy/ansible_tutorial/blob/main/99/Molecule.md) on how to get started! 4 | 5 | # Running 6 | 7 | ```bash 8 | cd roles/oxlorg.proxmox_mail_gw 9 | molecule test 10 | ``` 11 | -------------------------------------------------------------------------------- /tasks/debian/database.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Proxmox Mail GW | Debian | Database | Installing 4 | ansible.builtin.apt: 5 | name: "{{ PMG_HC.packages.db }}" 6 | state: present 7 | 8 | - name: Proxmox Mail GW | Debian | Database | Starting/Enabling service 9 | ansible.builtin.systemd: 10 | name: "{{ PMG_HC.svc.db }}" 11 | enabled: yes 12 | state: started 13 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # external roles and collections to download 3 | # install: ansible-galaxy install -r requirements.yml 4 | 5 | collections: 6 | - 'community.crypto' 7 | 8 | roles: 9 | - name: 'oxlorg.nginx' 10 | src: 'git+https://github.com/O-X-L/ansible-role-nginx' 11 | 12 | - name: 'oxlorg.certs' 13 | src: 'git+https://github.com/O-X-L/ansible-role-certs' 14 | -------------------------------------------------------------------------------- /defaults/main/1_main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | no_prompts: false 4 | debug: false 5 | 6 | # default config => is overwritten by provided config 7 | defaults_pmg: 8 | subscription: false 9 | manage: 10 | webserver: true 11 | database: true 12 | 13 | nginx: 14 | proxy: 15 | port: 8006 16 | proto: 'https' 17 | 18 | PMG_CONFIG: "{{ defaults_pmg | combine(pmg, recursive=true) }}" 19 | 20 | pmg_conditionals_nginx: 21 | domain: "{{ PMG_CONFIG.fqdn }}" 22 | -------------------------------------------------------------------------------- /defaults/main/0_hardcoded.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | PMG_HC: 4 | repo: 5 | no_sub: "deb http://download.proxmox.com/debian/pmg {{ ansible_distribution_release | lower }} pmg-no-subscription" 6 | sub: "deb https://enterprise.proxmox.com/debian/pmg {{ ansible_distribution_release | lower }} pmg-enterprise" 7 | key: "https://enterprise.proxmox.com/debian/proxmox-release-{{ ansible_distribution_release | lower }}.gpg" 8 | packages: 9 | base: ['gpg', 'systemd', 'postfix', 'iproute2'] 10 | main: ['proxmox-mailgateway'] 11 | db: ['postgresql'] # pmg-api dependency 12 | svc: 13 | db: 'postgresql.service' 14 | pf: 'postfix@-.service' 15 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Converge - default 4 | hosts: test-ag-pmg-1 5 | vars: 6 | no_prompts: true 7 | pmg: 8 | fqdn: 'pmg1.test.oxl.at' 9 | 10 | nginx: 11 | aliases: ['alias.lan', '_'] 12 | ip: '192.168.7.2' 13 | ssl: 14 | mode: 'snakeoil' 15 | 16 | roles: 17 | - oxlorg.proxmox_mail_gw 18 | 19 | - name: Converge - no nginx 20 | hosts: test-ag-pmg-2 21 | vars: 22 | no_prompts: true 23 | subscription: true 24 | pmg: 25 | fqdn: 'pmg2.test.oxl.at' 26 | 27 | manage: 28 | webserver: false 29 | 30 | roles: 31 | - oxlorg.proxmox_mail_gw 32 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | galaxy_info: 4 | author: 'Rath Pascal ' 5 | namespace: 'oxlorg' 6 | license: 'MIT' 7 | issue_tracker_url: 'https://github.com/O-X-L/ansible-role-proxmox-mail-gw/issues' 8 | min_ansible_version: '2.14' 9 | description: 'Ansible role to deploy Proxmox Mail Gateway on a linux server' 10 | platforms: 11 | - name: Debian 12 | versions: 13 | - bullseye 14 | - bookworm 15 | - trixies 16 | galaxy_tags: 17 | - 'proxmox' 18 | - 'mail' 19 | - 'mailing' 20 | - 'security' 21 | - 'filtering' 22 | - 'filter' 23 | - 'antispam' 24 | - 'spam' 25 | - 'phishing' 26 | - 'email' 27 | - 'smtp' 28 | 29 | collections: [] 30 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Proxmox Mail GW | Showing debug info - user provided config 4 | ansible.builtin.debug: 5 | var: pmg 6 | when: 7 | - debug is defined 8 | - debug 9 | 10 | - name: Proxmox Mail GW | Showing debug info - running config 11 | ansible.builtin.debug: 12 | var: PMG_CONFIG 13 | when: 14 | - debug is defined 15 | - debug 16 | 17 | - name: Proxmox Mail GW | Checking config 18 | ansible.builtin.assert: 19 | that: 20 | - pmg is defined 21 | - pmg.fqdn | valid_hostname 22 | 23 | - name: Proxmox Mail GW | Checking environment 24 | ansible.builtin.assert: 25 | that: 26 | - "ansible_virtualization_role is undefined or ansible_virtualization_role != 'guest' or 'docker' not in ansible_virtualization_tech_guest" 27 | tags: molecule-notest 28 | 29 | - name: Proxmox Mail GW | Processing debian config 30 | ansible.builtin.import_tasks: debian/main.yml 31 | when: "ansible_distribution|lower in ['debian', 'ubuntu']" 32 | -------------------------------------------------------------------------------- /.github/workflows/integration_test_run.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Integration-Tests Execution 4 | 5 | on: workflow_dispatch 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | timeout-minutes: 1 11 | env: 12 | CI_JOB: 'ansible-test-molecule-${{ github.event.repository.name }}' 13 | CI_DOMAIN: 'ci.oss.oxl.app' 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | with: 19 | ref: ${{ github.ref }} 20 | 21 | - name: Install dependencies 22 | run: sudo apt install curl 23 | shell: bash 24 | 25 | - name: Starting Tests 26 | run: curl --fail-with-body -XPOST https://${{ env.CI_DOMAIN }}/api/job/${{ env.CI_JOB }}?token=${{ secrets.CI_TOKEN_RW }} 27 | shell: bash 28 | 29 | - name: You can pull the current logs at this URL 30 | run: > 31 | echo "You can pull the current logs at this URL:" 32 | echo " > https://${{ env.CI_DOMAIN }}/api/job/${{ env.CI_JOB }}/tail?token=${CI_TOKEN_RO}" 33 | env: 34 | CI_TOKEN_RO: "2b7bba30-9a37-4b57-be8a-99e23016ce70" 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2024 Rath Pascal 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | 10 | E-Mail: contact@oxl.at 11 | Web: https://github.com/O-X-L 12 | -------------------------------------------------------------------------------- /filter_plugins/utils.py: -------------------------------------------------------------------------------- 1 | from re import match as regex_match 2 | from re import compile as regex_compile 3 | 4 | 5 | class FilterModule(object): 6 | 7 | def filters(self): 8 | return { 9 | "valid_hostname": self.valid_hostname, 10 | "get_subdomain": self.get_subdomain, 11 | } 12 | 13 | @staticmethod 14 | def valid_hostname(name: str) -> bool: 15 | # see: https://validators.readthedocs.io/en/latest/_modules/validators/domain.html 16 | domain = regex_compile( 17 | r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|' 18 | r'([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|' 19 | r'([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.' 20 | r'([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$' 21 | ) 22 | valid_domain = domain.match(name) is not None 23 | # see: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names 24 | expr_hostname = r'^[a-zA-Z0-9-\.]{1,253}$' 25 | valid_hostname = regex_match(expr_hostname, name) is not None 26 | return all([valid_domain, valid_hostname]) 27 | 28 | @staticmethod 29 | def get_subdomain(domain: str) -> str: 30 | return domain.split('.', 1)[0] 31 | -------------------------------------------------------------------------------- /.github/workflows/integration_test_result.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Integration-Tests 4 | 5 | on: 6 | # schedule: 7 | # - cron: "53 6 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 1 14 | env: 15 | CI_JOB: 'ansible-test-molecule-${{ github.event.repository.name }}' 16 | CI_DOMAIN: 'ci.oss.oxl.app' 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | ref: ${{ github.ref }} 23 | 24 | - name: Install dependencies 25 | run: sudo apt install curl jq 26 | shell: bash 27 | 28 | - name: Pulling logs 29 | run: curl --fail-with-body https://${{ env.CI_DOMAIN }}/api/job/${{ env.CI_JOB }}/logs?token=${{ secrets.CI_TOKEN_RW }} | jq > /tmp/test.log 30 | shell: bash 31 | 32 | - uses: actions/upload-artifact@v4 33 | with: 34 | name: test-logs 35 | path: /tmp/test.log 36 | retention-days: 14 37 | 38 | - name: Checking job-state 39 | run: > 40 | curl --fail-with-body https://${{ env.CI_DOMAIN }}/api/job/${{ env.CI_JOB }}/state?token=${{ secrets.CI_TOKEN_RW }} | jq -r '.state' | grep -q 'failed' && exit 1 || exit 0 41 | shell: bash 42 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # todo: full testing using VMs => docker isn't working 4 | 5 | dependency: 6 | name: galaxy 7 | driver: 8 | name: docker 9 | platforms: 10 | - name: test-ag-pmg-1 11 | docker_networks: 12 | - name: 'test-ag-pmg' 13 | ipam_config: 14 | - subnet: '192.168.7.0/24' 15 | gateway: '192.168.7.254' 16 | networks: 17 | - name: 'test-ag-pmg' 18 | ipv4_address: '192.168.7.2' 19 | groups: [grp_targets] 20 | docker_host: 'tcp://molecule-docker.local:2375' 21 | # docker_host: 'unix://var/run/docker.sock' # localhost 22 | image: 'ansible0guy/molecule:debian-latest' 23 | tmpfs: ['/tmp', '/run', '/run/lock'] 24 | privileged: true 25 | command: '/sbin/init' 26 | 27 | # - name: test-ag-pmg-tester 28 | # networks: 29 | # - name: 'test-ag-pmg' 30 | # ipv4_address: '192.168.7.1' 31 | # etc_hosts: { 32 | # pmg1.test.oxl.at: '192.168.7.2', 33 | # pmg2.test.oxl.at: '192.168.7.3', 34 | # } 35 | # groups: [grp_tester] 36 | # <<: *docker_all 37 | 38 | - name: test-ag-pmg-2 39 | networks: 40 | - name: 'test-ag-pmg' 41 | ipv4_address: '192.168.7.3' 42 | groups: [grp_targets] 43 | docker_host: 'tcp://molecule-docker.local:2375' 44 | # docker_host: 'unix://var/run/docker.sock' # localhost 45 | image: 'ansible0guy/molecule:debian-latest' 46 | tmpfs: ['/tmp', '/run', '/run/lock'] 47 | privileged: true 48 | command: '/sbin/init' 49 | 50 | provisioner: 51 | name: ansible 52 | verifier: 53 | name: ansible 54 | scenario: 55 | name: default 56 | test_sequence: 57 | - destroy 58 | - syntax 59 | - create 60 | - converge 61 | - verify # MUST NOT make changes 62 | - idempotence 63 | - check 64 | - destroy 65 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Lint 4 | 5 | on: 6 | push: 7 | branches: [main, stable, latest] 8 | paths: 9 | - '**.py' 10 | - '**.yml' 11 | - '.github/workflows/lint.yml' 12 | - 'requirements_lint.txt' 13 | - '.yamllint' 14 | - '.pylintrc' 15 | - '.pylintrc_j2' 16 | - '.ansible-lint.yml' 17 | pull_request: 18 | branches: [main, stable, latest] 19 | paths: 20 | - '**.py' 21 | - '**.yml' 22 | - '.github/workflows/lint.yml' 23 | - 'requirements_lint.txt' 24 | - '.yamllint' 25 | - '.pylintrc' 26 | - '.pylintrc_j2' 27 | - '.ansible-lint.yml' 28 | 29 | jobs: 30 | lint: 31 | runs-on: ubuntu-latest 32 | timeout-minutes: 2 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v3 36 | with: 37 | ref: ${{ github.ref }} 38 | 39 | - name: Install python 40 | uses: actions/setup-python@v4 41 | with: 42 | python-version: '3.11' 43 | 44 | - name: Install dependencies 45 | run: | 46 | pip install -r requirements_lint.txt 47 | shell: bash 48 | 49 | - name: Running PyLint 50 | run: pylint --recursive=y . 51 | shell: bash 52 | 53 | - name: Running PyLint (*.py.j2) 54 | run: | 55 | if [ -f ".pylintrc_j2" ]; then j2_cnf=".pylintrc_j2"; else j2_cnf='.pylintrc'; fi 56 | find -type f -name *.py.j2 -print0 | xargs --no-run-if-empty -0 -n1 pylint --rcfile "$(pwd)/${j2_cnf}" 57 | shell: bash 58 | 59 | - name: Running YamlLint 60 | run: yamllint . 61 | shell: bash 62 | 63 | - name: Preparing for AnsibleLint 64 | run: | 65 | mkdir -p '/tmp/ansible_lint/roles/' 66 | ln -s "${{ github.workspace }}" "/tmp/ansible_lint/roles/oxlorg.proxmox_mail_gw" 67 | ansible-galaxy role install -r requirements.yml -p /tmp/ansible_lint/roles 68 | shell: bash 69 | 70 | - name: Running AnsibleLint 71 | run: ANSIBLE_ROLES_PATH=/tmp/ansible_lint/roles/ ansible-lint -c .ansible-lint.yml 72 | shell: bash 73 | -------------------------------------------------------------------------------- /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # not yet working => need to switch to testing with VMs 4 | # - name: Verify 5 | # hosts: grp_tester 6 | # gather_facts: false 7 | # tasks: 8 | # - name: Checking if web-service is reachable 9 | # ansible.builtin.uri: 10 | # url: 11 | # return_content: yes 12 | # validate_certs: false 13 | # register: page 14 | # failed_when: "'Proxmox Mail Gateway' not in page.content" 15 | # loop: 16 | # - 'https://pmg1.test.oxl.at' 17 | # - 'https://pmg2.test.oxl.at:8006' 18 | 19 | - name: Verify - default 20 | hosts: test-ag-pmg-1 21 | gather_facts: false 22 | tasks: 23 | - name: Checking that services are running and enabled 24 | ansible.builtin.systemd: 25 | name: "{{ item }}" 26 | enabled: true 27 | state: started 28 | check_mode: true 29 | register: svc_test 30 | loop: 31 | - 'nginx.service' 32 | # - 'pmgproxy.service' 33 | - 'postfix@-.service' 34 | - 'postgresql.service' 35 | failed_when: svc_test.changed 36 | 37 | - name: Checking that services survive restart 38 | ansible.builtin.systemd: 39 | name: "{{ item }}" 40 | state: restarted 41 | loop: 42 | - 'nginx.service' 43 | # - 'pmgproxy.service' 44 | - 'postfix@-.service' 45 | - 'postgresql.service' 46 | 47 | - name: Checking ports 48 | ansible.builtin.wait_for: 49 | port: "{{ item }}" 50 | timeout: 1 51 | msg: "Checking port {{ item }}" 52 | loop: 53 | - 80 # web 54 | - 443 55 | - 25 # mailing 56 | # - 465 57 | # - 587 58 | # - 8006 # web-mgmt 59 | 60 | - name: Verify - no nginx 61 | hosts: test-ag-pmg-2 62 | gather_facts: false 63 | tasks: 64 | - name: Checking that services are running and enabled 65 | ansible.builtin.systemd: 66 | name: "{{ item }}" 67 | enabled: true 68 | state: started 69 | check_mode: true 70 | register: svc_test 71 | loop: 72 | # - 'pmgproxy.service' 73 | - 'postfix@-.service' 74 | - 'postgresql.service' 75 | failed_when: svc_test.changed 76 | 77 | - name: Checking that services survive restart 78 | ansible.builtin.systemd: 79 | name: "{{ item }}" 80 | state: restarted 81 | loop: 82 | # - 'pmgproxy.service' 83 | - 'postfix@-.service' 84 | - 'postgresql.service' 85 | 86 | - name: Checking ports 87 | ansible.builtin.wait_for: 88 | port: "{{ item }}" 89 | timeout: 1 90 | msg: "Checking port {{ item }}" 91 | loop: 92 | # - 465 93 | # - 587 94 | # - 8006 # web-mgmt 95 | - 25 # mailing 96 | -------------------------------------------------------------------------------- /tasks/debian/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Proxmox Mail GW | Debian | Setting hostname 4 | ansible.builtin.hostname: 5 | name: "{{ PMG_CONFIG.fqdn }}" 6 | register: pmg_hostname 7 | tags: molecule-notest # docker limitation 8 | 9 | - name: Proxmox Mail GW | Debian | Set localhost name resolution 10 | ansible.builtin.lineinfile: 11 | dest: '/etc/hosts' 12 | regexp: '^127.0.1.1' 13 | line: "127.0.1.1 {{ PMG_CONFIG.fqdn }} 14 | {% if PMG_CONFIG.fqdn | get_subdomain != inventory_hostname %}{{ PMG_CONFIG.fqdn | get_subdomain }} {% endif %}\ 15 | {{ inventory_hostname }} localhost" 16 | state: present 17 | register: pmg_hosts 18 | tags: molecule-notest # docker limitation 19 | 20 | - name: Proxmox Mail GW | Debian | Ask user 21 | ansible.builtin.pause: 22 | prompt: "You need to reboot the system to fully update the hostname - can we do this NOW? [yes/NO]" 23 | register: pmg_prompt_reboot1 24 | when: 25 | - not no_prompts 26 | - pmg_hostname.changed or pmg_hosts.changed 27 | tags: molecule-notest 28 | 29 | - name: Proxmox Mail GW | Debian | Rebooting the system 30 | ansible.builtin.reboot: 31 | reboot_timeout: 60 32 | when: 33 | - pmg_hostname.changed or pmg_hosts.changed 34 | - pmg_prompt_reboot1.user_input in ['y', 'Y', 'YES', 'yes'] or no_prompts 35 | changed_when: false 36 | tags: molecule-notest 37 | 38 | - name: Proxmox Mail GW | Debian | Installing dependencies 39 | ansible.builtin.apt: 40 | name: "{{ PMG_HC.packages.base }}" 41 | update_cache: yes 42 | state: present 43 | 44 | - name: Proxmox Mail GW | Debian | Starting/Enabling Postfix service 45 | ansible.builtin.systemd: 46 | name: "{{ PMG_HC.svc.pf }}" 47 | enabled: yes 48 | state: started 49 | 50 | - name: Proxmox Mail GW | Debian | Managing database 51 | ansible.builtin.import_tasks: database.yml 52 | when: PMG_CONFIG.manage.database 53 | 54 | - name: Proxmox Mail GW | Debian | Configuring Nginx webserver 55 | ansible.builtin.include_role: 56 | name: oxlorg.nginx 57 | vars: 58 | nginx: 59 | sites: 60 | proxmox_mail_gateway: "{{ pmg_conditionals_nginx | combine(PMG_CONFIG.nginx, recursive=true) }}" 61 | when: PMG_CONFIG.manage.webserver 62 | tags: webserver 63 | args: 64 | apply: 65 | tags: webserver 66 | 67 | - name: Proxmox Mail GW | Debian | Adding repository-key 68 | ansible.builtin.apt_key: 69 | url: "{{ PMG_HC.repo.key }}" 70 | 71 | - name: Proxmox Mail GW | Debian | Adding package repository 72 | ansible.builtin.apt_repository: 73 | repo: "{{ PMG_HC.repo.no_sub }}" 74 | state: present 75 | update_cache: yes 76 | filename: 'proxmox_mail_gateway' 77 | 78 | - name: Proxmox Mail GW | Debian | Installing Proxmox Mail Gateway 79 | ansible.builtin.apt: 80 | name: "{{ PMG_HC.packages.main }}" 81 | state: present 82 | register: pmg_install 83 | when: > 84 | ansible_virtualization_role is undefined or 85 | ansible_virtualization_role != 'guest' or 86 | 'docker' not in ansible_virtualization_tech_guest 87 | # docker-check for testing-purposes 88 | 89 | - name: Proxmox Mail GW | Debian | Removing enterprise repo 90 | ansible.builtin.file: 91 | state: absent 92 | path: '/etc/apt/sources.list.d/pmg-enterprise.list' 93 | when: not PMG_CONFIG.subscription 94 | 95 | - name: Proxmox Mail GW | Debian | Removing community repo 96 | ansible.builtin.file: 97 | state: absent 98 | path: '/etc/apt/sources.list.d/proxmox_mail_gateway.list' 99 | when: PMG_CONFIG.subscription 100 | 101 | - name: Proxmox Mail GW | Debian | Ask user 102 | ansible.builtin.pause: 103 | prompt: "The system needs to be rebooted after finishing the installation - can we do this NOW? [yes/NO]" 104 | register: pmg_prompt_reboot2 105 | when: pmg_install.changed and not no_prompts 106 | 107 | - name: Proxmox Mail GW | Debian | Rebooting the system 108 | ansible.builtin.reboot: 109 | reboot_timeout: 60 110 | when: 111 | - pmg_install.changed 112 | - "pmg_prompt_reboot2.user_input in ['y', 'Y', 'YES', 'yes'] or no_prompts" 113 | changed_when: false 114 | -------------------------------------------------------------------------------- /Prerequisites.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | 4 | ## System requirements 5 | 6 | Make sure your target system meets the [system requirements](https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#system_requirements)! 7 | 8 | - The target system needs to have a public IP. 9 | - You need to have a public Domain. 10 | - ... 11 | 12 | ## Firewalling 13 | 14 | See also: [Documentation](https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#firewall_settings) 15 | 16 | ### Inbound 17 | 18 | For the server to work, you must allow the following ports using your firewall: 19 | 20 | - Mailing basic: 25/tcp from ANY 21 | - Mailing basic: 26/tcp from Mailserver 22 | - Web management: 8006/tcp (_from restricted sources only_) 23 | 24 | 25 | ### Outbound 26 | 27 | **If you want** to filter outbound connections using your firewall - you will need to allow: 28 | 29 | - Installation: while running the initial installation => please allow 80/tcp 443/tcp+udp to ANY 30 | - Mail forwarding to Mailserver: 25/tcp 31 | - Basics: 32 | - DNS: 53/tcp+udp to 1.1.1.1 and 8.8.8.8 (_or whatever dns servers you are using_) 33 | - NTP: 123/udp to 0.debian.pool.ntp.org and 1.debian.pool.ntp.org 34 | - APT: 443/tcp+udp to deb.debian.org, debian.map.fastlydns.net and security.debian.org (_or whatever main repository you are using_) 35 | - Proxmox: 443/tcp+udp to download.proxmox.com and enterprise.proxmox.com (_tbc.._) 36 | - Mailing: 25/tcp to Internet 37 | - ClamAV: 443/tcp+udp to database.clamav.net 38 | - SpamAssassin: 80/tcp, 443/tcp+udp to spamassassin.apache.org and domains listed in the [mirror list](https://spamassassin.apache.org/updates/MIRRORED.BY) 39 | - to be continued.. 40 | 41 | Optional: 42 | - LetsEncrypt: 80/tcp, 443/tcp+udp to acme-v02.api.letsencrypt.org, staging-v02.api.letsencrypt.org (_debug mode_) and r3.o.lencr.org 43 | - CloudMark Razor: 2703/tcp to discovery.razor.cloudmark.com and CURRENTLY 208.83.137.0/24 44 | 45 | 46 | ## Public DNS 47 | 48 | To filter incoming and outgoing mails, the Proxmox Mail Gateway needs to be configured as public mailserver! 49 | 50 | If you are interested => here's a nice overview of SPF/DKIM/DMARC: [LINK](https://seanthegeek.net/459/demystifying-dmarc/) 51 | 52 | **Needed**: 53 | 54 | | TYPE | KEY | VALUE | COMMENT | 55 | |:----:|:--------------------------------|:--------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 56 | | A | SRV.DOMAIN.TLD | SRV-PUBLIC-IP | - | 57 | | MX | DOMAIN.TLD | 10 SRV.DOMAIN.TLD | - | 58 | | TXT | DOMAIN.TLD | v=spf1 mx -all | - | 59 | | TXT | _dmarc.DOMAIN.TLD | v=DMARC1; p=quarantine; sp=quarantine; aspf=s; adkim=s; | You can also add a dedicated mail user to receive DMARC reports. See the 'overview' above for details. It would then look like this: 'v=DMARC1; p=quarantine; sp=quarantine; rua=mailto:ADDRESS@DOMAIN.TLD; ruf=mailto:ADDRESS@DOMAIN.TLD; aspf=s; adkim=s;' | 60 | | TXT | mail._domainkey.DOMAIN.TLD | v=DKIM1; p=MIIBIjANBgkqhkiG... | Replace the value by YOUR DKIM record! See: [Documentation](https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#pmgconfig_mailproxy_dkim) | 61 | | TXT | *.DOMAIN.TLD | v=spf1 -all | Any domain/subdomain that is not used to send mails, should IMPLICITLY DENY any senders! | 62 | | PTR | YOUR-SRV-IP | SRV.DOMAIN.TLD | You cannot set a PTR record in your DNS-Panel/management! Your internet provider/hoster has to do that. Bigger hosters will give you an option for this in their managment interface. | 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Proxmox Logo](https://www.proxmox.com/images/proxmox/Proxmox_logo_standard_hex_400px.png)](https://www.proxmox.com/en/proxmox-mail-gateway) 2 | 3 | # Ansible Role - Proxmox Mail Gateway 4 | 5 | Role to deploy [Proxmox Mail Gateway](https://www.proxmox.com/en/proxmox-mail-gateway) on a linux server. 6 | 7 | [![Proxmox Incoming Processing](https://dl.oss.oxl.app/sw_proxmox_mail_gw/flow.png)](https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html) 8 | 9 | 10 | [![Lint](https://github.com/O-X-L/ansible-role-proxmox-mail-gw/actions/workflows/lint.yml/badge.svg)](https://github.com/O-X-L/ansible-role-proxmox-mail-gw/actions/workflows/lint.yml) 11 | [![Ansible Galaxy](https://badges.oss.oxl.app/galaxy.badge.svg)](https://galaxy.ansible.com/ui/standalone/roles/oxlorg/proxmox_mail_gw) 12 | 13 | **Molecule Integration-Tests**: 14 | 15 | * Status: [![Molecule Test Status](https://badges.oss.oxl.app/sw_proxmox_mail_gw.molecule.svg)](https://github.com/O-X-L/ansible-role-oxl-cicd/blob/latest/templates/usr/local/bin/cicd/molecule.sh.j2) | 16 | [![Functional-Tests](https://github.com/O-X-L/ansible-role-proxmox-mail-gw/actions/workflows/integration_test_result.yml/badge.svg)](https://github.com/O-X-L/ansible-role-proxmox-mail-gw/actions/workflows/integration_test_result.yml) 17 | * Logs: [API](https://ci.oss.oxl.app/api/job/ansible-test-molecule-sw_proxmox_mail_gw/logs?token=2b7bba30-9a37-4b57-be8a-99e23016ce70&lines=1000) | [Short](https://badges.oss.oxl.app/log/molecule_sw_proxmox_mail_gw_test_short.log) | [Full](https://badges.oss.oxl.app/log/molecule_sw_proxmox_mail_gw_test.log) 18 | 19 | Internal CI: [Tester Role](https://github.com/O-X-L/ansible-role-oxl-cicd) | [Jobs API](https://github.com/O-X-L/github-self-hosted-jobs-systemd) 20 | 21 | **Tested:** 22 | * Debian 11 23 | * Debian 12 24 | 25 | ---- 26 | 27 | ## Install 28 | 29 | ```bash 30 | # latest 31 | ansible-galaxy role install git+https://github.com/O-X-L/ansible-role-proxmox-mail-gw 32 | 33 | # from galaxy 34 | ansible-galaxy install oxlorg.proxmox_mail_gw 35 | 36 | # or to custom role-path 37 | ansible-galaxy install oxlorg.proxmox_mail_gw --roles-path ./roles 38 | 39 | # install dependencies 40 | ansible-galaxy install -r requirements.yml 41 | ``` 42 | 43 | ---- 44 | 45 | ## Prerequisites 46 | 47 | See: [Prerequisites](https://github.com/O-X-L/ansible-role-proxmox-mail-gw/blob/stable/Prerequisites.md) 48 | 49 | ---- 50 | 51 | ## Advertisement 52 | 53 | * Need **professional support** using Ansible or Proxmox Mail-Gateway? Contact us: 54 | 55 | E-Mail: [contact@oxl.at](mailto:contact@oxl.at) 56 | 57 | Tel: [+43 3115 40 900 0](tel:+433115409000) 58 | 59 | Web: [EN](https://www.o-x-l.com) | [DE](https://www.oxl.at) 60 | 61 | Language: German or English 62 | 63 | * You want a simple **Ansible GUI**? 64 | 65 | Check-out this [Ansible WebUI](https://github.com/O-X-L/ansible-webui) 66 | 67 | 68 | ---- 69 | 70 | ## Usage 71 | 72 | ### Config 73 | 74 | Define the config as needed: 75 | ```yaml 76 | pmg: 77 | fqdn: 'pmg.template.oxl.at' # valid, public dns-hostname of your server 78 | 79 | manage: 80 | webserver: true # set to false to disable nginx-component 81 | 82 | nginx: # configure the webserver settings => see: https://github.com/O-X-L/ansible-role-nginx 83 | aliases: ['mail-gw.oxl.at'] # additional domains to add to the certificate 84 | ssl: 85 | mode: 'letsencrypt' # or selfsigned/ca 86 | # if you use 'selfsigned' or 'ca': 87 | # cert: 88 | # cn: 'Proxmox Mail Gateway' 89 | # org: 'AnsibleGuy' 90 | # email: 'pmg@template.oxl.at' 91 | letsencrypt: 92 | email: 'pmg@template.oxl.at' 93 | ``` 94 | 95 | Bare minimum example: 96 | ```yaml 97 | pmg: 98 | fqdn: 'pmg.template.oxl.at' 99 | ``` 100 | 101 | Example to use PMG's built-in ACME: 102 | ```yaml 103 | pmg: 104 | fqdn: 'pmg.template.oxl.at' 105 | 106 | nginx: 107 | aliases: ['mail-gw.oxl.at'] 108 | plain_site: false # nginx will not bind to port 80 109 | letsencrypt: 110 | email: 'pmg@template.oxl.at' 111 | ``` 112 | 113 | Example - if you want to setup postgreSQL manually: 114 | ```yaml 115 | pmg: 116 | fqdn: 'pmg.template.oxl.at' 117 | 118 | manage: 119 | database: false 120 | ``` 121 | 122 | You might want to use 'ansible-vault' to encrypt your passwords: 123 | ```bash 124 | ansible-vault encrypt_string 125 | ``` 126 | 127 | ### Execution 128 | 129 | Run the playbook: 130 | ```bash 131 | ansible-playbook -K -D -i inventory/hosts.yml playbook.yml 132 | ``` 133 | 134 | To debug errors - you can set the 'debug' variable at runtime: 135 | ```bash 136 | ansible-playbook -K -D -i inventory/hosts.yml playbook.yml -e debug=yes 137 | ``` 138 | 139 | ---- 140 | 141 | ## Functionality 142 | 143 | 144 | * **Package installation** 145 | * Ansible dependencies (_minimal_) 146 | * Systemd 147 | * Proxmox Mail Gateway 148 | * PMG dependencies 149 | * postgreSQL 150 | * Postfix 151 | 152 | 153 | * **Configuration** 154 | * default postgreSQL installation 155 | 156 | * **Default opt-ins**: 157 | * Nginx => using [THIS Role](https://github.com/O-X-L/ansible-role-nginx) 158 | 159 | * **Default opt-outs**: 160 | * Enterprise apt-repository (_[subscription needed](https://www.proxmox.com/en/proxmox-mail-gateway/pricing)_) 161 | 162 | ---- 163 | 164 | ## Info 165 | 166 | * **Warning:** **IF YOU ARE USING A DEDICATED VM FOR THIS SETUP**: 167 | 168 | You should probably use the [ISO installation process](https://www.proxmox.com/en/downloads/category/proxmox-mail-gateway). 169 | 170 | It might be better supported! 171 | 172 | 173 | * **Note:** this role currently only supports debian-based systems 174 | 175 | 176 | * **Note:** Most of the role's functionality can be opted in or out. 177 | 178 | For all available options - see the default-config located in [the main defaults-file](https://github.com/O-X-L/ansible-role-proxmox-mail-gw/blob/latest/defaults/main/1_main.yml)! 179 | 180 | 181 | * **Warning:** Not every setting/variable you provide will be checked for validity. Bad config might break the role! 182 | 183 | 184 | * **Warning:** If you choose to install the nginx web server (_default_) and want to use the [built-in ACME certificate management](https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#sysadmin_certificate_management) - you will have to configure 'nginx.plain_site' to 'false'. 185 | 186 | As this 'ACME standalone integration' needs the port 80 to be not in use! 187 | 188 | 189 | * **Note:** Check out the [nice documentation](https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#_features) provided by Proxmox! 190 | 191 | 192 | * **Warning:** Docker containers ARE NOT SUPPORTED. 193 | 194 | 195 | * **Info:** PMG's web interface default login is done via PAM/System users. 196 | 197 | Normally, at first, via 'root'. 198 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MAIN] 2 | 3 | # Analyse import fallback blocks. This can be used to support both Python 2 and 4 | # 3 compatible code, which means that the block might have code that exists 5 | # only in one or another interpreter, leading to false positives when analysed. 6 | analyse-fallback-blocks=no 7 | 8 | # Load and enable all available extensions. Use --list-extensions to see a list 9 | # all available extensions. 10 | #enable-all-extensions= 11 | 12 | # In error mode, messages with a category besides ERROR or FATAL are 13 | # suppressed, and no reports are done by default. Error mode is compatible with 14 | # disabling specific errors. 15 | #errors-only= 16 | 17 | # Always return a 0 (non-error) status code, even if lint errors are found. 18 | # This is primarily useful in continuous integration scripts. 19 | #exit-zero= 20 | 21 | # A comma-separated list of package or module names from where C extensions may 22 | # be loaded. Extensions are loading into the active Python interpreter and may 23 | # run arbitrary code. 24 | extension-pkg-allow-list= 25 | 26 | # A comma-separated list of package or module names from where C extensions may 27 | # be loaded. Extensions are loading into the active Python interpreter and may 28 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list 29 | # for backward compatibility.) 30 | extension-pkg-whitelist= 31 | 32 | # Return non-zero exit code if any of these messages/categories are detected, 33 | # even if score is above --fail-under value. Syntax same as enable. Messages 34 | # specified are enabled, while categories only check already-enabled messages. 35 | fail-on= 36 | 37 | # Specify a score threshold under which the program will exit with error. 38 | fail-under=10 39 | 40 | # Interpret the stdin as a python script, whose filename needs to be passed as 41 | # the module_or_package argument. 42 | #from-stdin= 43 | 44 | # Files or directories to be skipped. They should be base names, not paths. 45 | ignore=CVS 46 | 47 | # Add files or directories matching the regular expressions patterns to the 48 | # ignore-list. The regex matches against paths and can be in Posix or Windows 49 | # format. Because '\' represents the directory delimiter on Windows systems, it 50 | # can't be used as an escape character. 51 | ignore-paths= 52 | 53 | # Files or directories matching the regular expression patterns are skipped. 54 | # The regex matches against base names, not paths. The default value ignores 55 | # Emacs file locks 56 | ignore-patterns=^\.# 57 | 58 | # List of module names for which member attributes should not be checked 59 | # (useful for modules/projects where namespaces are manipulated during runtime 60 | # and thus existing member attributes cannot be deduced by static analysis). It 61 | # supports qualified module names, as well as Unix pattern matching. 62 | ignored-modules= 63 | 64 | # Python code to execute, usually for sys.path manipulation such as 65 | # pygtk.require(). 66 | #init-hook= 67 | 68 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 69 | # number of processors available to use, and will cap the count on Windows to 70 | # avoid hangs. 71 | jobs=1 72 | 73 | # Control the amount of potential inferred values when inferring a single 74 | # object. This can help the performance when dealing with large functions or 75 | # complex, nested conditions. 76 | limit-inference-results=100 77 | 78 | # List of plugins (as comma separated values of python module names) to load, 79 | # usually to register additional checkers. 80 | load-plugins= 81 | 82 | # Pickle collected data for later comparisons. 83 | persistent=yes 84 | 85 | # Minimum Python version to use for version dependent checks. Will default to 86 | # the version used to run pylint. 87 | py-version=3.10 88 | 89 | # Discover python modules and packages in the file system subtree. 90 | recursive=no 91 | 92 | # When enabled, pylint would attempt to guess common misconfiguration and emit 93 | # user-friendly hints instead of false-positive error messages. 94 | suggestion-mode=yes 95 | 96 | # Allow loading of arbitrary C extensions. Extensions are imported into the 97 | # active Python interpreter and may run arbitrary code. 98 | unsafe-load-any-extension=no 99 | 100 | # In verbose mode, extra non-checker-related info will be displayed. 101 | #verbose= 102 | 103 | 104 | [BASIC] 105 | 106 | # Naming style matching correct argument names. 107 | argument-naming-style=snake_case 108 | 109 | # Regular expression matching correct argument names. Overrides argument- 110 | # naming-style. If left empty, argument names will be checked with the set 111 | # naming style. 112 | #argument-rgx= 113 | 114 | # Naming style matching correct attribute names. 115 | attr-naming-style=snake_case 116 | 117 | # Regular expression matching correct attribute names. Overrides attr-naming- 118 | # style. If left empty, attribute names will be checked with the set naming 119 | # style. 120 | #attr-rgx= 121 | 122 | # Bad variable names which should always be refused, separated by a comma. 123 | bad-names=foo, 124 | bar, 125 | baz, 126 | toto, 127 | tutu, 128 | tata 129 | 130 | # Bad variable names regexes, separated by a comma. If names match any regex, 131 | # they will always be refused 132 | bad-names-rgxs= 133 | 134 | # Naming style matching correct class attribute names. 135 | class-attribute-naming-style=any 136 | 137 | # Regular expression matching correct class attribute names. Overrides class- 138 | # attribute-naming-style. If left empty, class attribute names will be checked 139 | # with the set naming style. 140 | #class-attribute-rgx= 141 | 142 | # Naming style matching correct class constant names. 143 | class-const-naming-style=UPPER_CASE 144 | 145 | # Regular expression matching correct class constant names. Overrides class- 146 | # const-naming-style. If left empty, class constant names will be checked with 147 | # the set naming style. 148 | #class-const-rgx= 149 | 150 | # Naming style matching correct class names. 151 | class-naming-style=PascalCase 152 | 153 | # Regular expression matching correct class names. Overrides class-naming- 154 | # style. If left empty, class names will be checked with the set naming style. 155 | #class-rgx= 156 | 157 | # Naming style matching correct constant names. 158 | const-naming-style=UPPER_CASE 159 | 160 | # Regular expression matching correct constant names. Overrides const-naming- 161 | # style. If left empty, constant names will be checked with the set naming 162 | # style. 163 | #const-rgx= 164 | 165 | # Minimum line length for functions/classes that require docstrings, shorter 166 | # ones are exempt. 167 | docstring-min-length=-1 168 | 169 | # Naming style matching correct function names. 170 | function-naming-style=snake_case 171 | 172 | # Regular expression matching correct function names. Overrides function- 173 | # naming-style. If left empty, function names will be checked with the set 174 | # naming style. 175 | #function-rgx= 176 | 177 | # Good variable names which should always be accepted, separated by a comma. 178 | good-names=i, 179 | j, 180 | k, 181 | ex, 182 | Run, 183 | _ 184 | 185 | # Good variable names regexes, separated by a comma. If names match any regex, 186 | # they will always be accepted 187 | good-names-rgxs= 188 | 189 | # Include a hint for the correct naming format with invalid-name. 190 | include-naming-hint=no 191 | 192 | # Naming style matching correct inline iteration names. 193 | inlinevar-naming-style=any 194 | 195 | # Regular expression matching correct inline iteration names. Overrides 196 | # inlinevar-naming-style. If left empty, inline iteration names will be checked 197 | # with the set naming style. 198 | #inlinevar-rgx= 199 | 200 | # Naming style matching correct method names. 201 | method-naming-style=snake_case 202 | 203 | # Regular expression matching correct method names. Overrides method-naming- 204 | # style. If left empty, method names will be checked with the set naming style. 205 | #method-rgx= 206 | 207 | # Naming style matching correct module names. 208 | module-naming-style=snake_case 209 | 210 | # Regular expression matching correct module names. Overrides module-naming- 211 | # style. If left empty, module names will be checked with the set naming style. 212 | #module-rgx= 213 | 214 | # Colon-delimited sets of names that determine each other's naming style when 215 | # the name regexes allow several styles. 216 | name-group= 217 | 218 | # Regular expression which should only match function or class names that do 219 | # not require a docstring. 220 | no-docstring-rgx=^_ 221 | 222 | # List of decorators that produce properties, such as abc.abstractproperty. Add 223 | # to this list to register other decorators that produce valid properties. 224 | # These decorators are taken in consideration only for invalid-name. 225 | property-classes=abc.abstractproperty 226 | 227 | # Regular expression matching correct type variable names. If left empty, type 228 | # variable names will be checked with the set naming style. 229 | #typevar-rgx= 230 | 231 | # Naming style matching correct variable names. 232 | variable-naming-style=snake_case 233 | 234 | # Regular expression matching correct variable names. Overrides variable- 235 | # naming-style. If left empty, variable names will be checked with the set 236 | # naming style. 237 | #variable-rgx= 238 | 239 | 240 | [CLASSES] 241 | 242 | # Warn about protected attribute access inside special methods 243 | check-protected-access-in-special-methods=no 244 | 245 | # List of method names used to declare (i.e. assign) instance attributes. 246 | defining-attr-methods=__init__, 247 | __new__, 248 | setUp, 249 | __post_init__ 250 | 251 | # List of member names, which should be excluded from the protected access 252 | # warning. 253 | exclude-protected=_asdict, 254 | _fields, 255 | _replace, 256 | _source, 257 | _make 258 | 259 | # List of valid names for the first argument in a class method. 260 | valid-classmethod-first-arg=cls 261 | 262 | # List of valid names for the first argument in a metaclass class method. 263 | valid-metaclass-classmethod-first-arg=cls 264 | 265 | 266 | [DESIGN] 267 | 268 | # List of regular expressions of class ancestor names to ignore when counting 269 | # public methods (see R0903) 270 | exclude-too-few-public-methods= 271 | 272 | # List of qualified class names to ignore when counting class parents (see 273 | # R0901) 274 | ignored-parents= 275 | 276 | # Maximum number of arguments for function / method. 277 | max-args=5 278 | 279 | # Maximum number of positional arguments for function / method. 280 | max-positional-arguments=5 281 | 282 | # Maximum number of attributes for a class (see R0902). 283 | max-attributes=7 284 | 285 | # Maximum number of boolean expressions in an if statement (see R0916). 286 | max-bool-expr=5 287 | 288 | # Maximum number of branch for function / method body. 289 | max-branches=12 290 | 291 | # Maximum number of locals for function / method body. 292 | max-locals=15 293 | 294 | # Maximum number of parents for a class (see R0901). 295 | max-parents=7 296 | 297 | # Maximum number of public methods for a class (see R0904). 298 | max-public-methods=20 299 | 300 | # Maximum number of return / yield for function / method body. 301 | max-returns=6 302 | 303 | # Maximum number of statements in function / method body. 304 | max-statements=50 305 | 306 | # Minimum number of public methods for a class (see R0903). 307 | min-public-methods=2 308 | 309 | 310 | [EXCEPTIONS] 311 | 312 | # Exceptions that will emit a warning when caught. 313 | overgeneral-exceptions=BaseException, 314 | Exception 315 | 316 | 317 | [FORMAT] 318 | 319 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 320 | expected-line-ending-format= 321 | 322 | # Regexp for a line that is allowed to be longer than the limit. 323 | ignore-long-lines=^\s*(# )??$ 324 | 325 | # Number of spaces of indent required inside a hanging or continued line. 326 | indent-after-paren=4 327 | 328 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 329 | # tab). 330 | indent-string=' ' 331 | 332 | # Maximum number of characters on a single line. 333 | max-line-length=120 334 | 335 | # Maximum number of lines in a module. 336 | max-module-lines=1000 337 | 338 | # Allow the body of a class to be on the same line as the declaration if body 339 | # contains single statement. 340 | single-line-class-stmt=no 341 | 342 | # Allow the body of an if to be on the same line as the test if there is no 343 | # else. 344 | single-line-if-stmt=no 345 | 346 | 347 | [IMPORTS] 348 | 349 | # List of modules that can be imported at any level, not just the top level 350 | # one. 351 | allow-any-import-level= 352 | 353 | # Allow wildcard imports from modules that define __all__. 354 | allow-wildcard-with-all=no 355 | 356 | # Deprecated modules which should not be used, separated by a comma. 357 | deprecated-modules= 358 | 359 | # Output a graph (.gv or any supported image format) of external dependencies 360 | # to the given file (report RP0402 must not be disabled). 361 | ext-import-graph= 362 | 363 | # Output a graph (.gv or any supported image format) of all (i.e. internal and 364 | # external) dependencies to the given file (report RP0402 must not be 365 | # disabled). 366 | import-graph= 367 | 368 | # Output a graph (.gv or any supported image format) of internal dependencies 369 | # to the given file (report RP0402 must not be disabled). 370 | int-import-graph= 371 | 372 | # Force import order to recognize a module as part of the standard 373 | # compatibility libraries. 374 | known-standard-library= 375 | 376 | # Force import order to recognize a module as part of a third party library. 377 | known-third-party=enchant 378 | 379 | # Couples of modules and preferred modules, separated by a comma. 380 | preferred-modules= 381 | 382 | 383 | [LOGGING] 384 | 385 | # The type of string formatting that logging methods do. `old` means using % 386 | # formatting, `new` is for `{}` formatting. 387 | logging-format-style=old 388 | 389 | # Logging modules to check that the string format arguments are in logging 390 | # function parameter format. 391 | logging-modules=logging 392 | 393 | 394 | [MESSAGES CONTROL] 395 | 396 | # Only show warnings with the listed confidence levels. Leave empty to show 397 | # all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, 398 | # UNDEFINED. 399 | confidence=HIGH, 400 | CONTROL_FLOW, 401 | INFERENCE, 402 | INFERENCE_FAILURE, 403 | UNDEFINED 404 | 405 | # Disable the message, report, category or checker with the given id(s). You 406 | # can either give multiple identifiers separated by comma (,) or put this 407 | # option multiple times (only on the command line, not in the configuration 408 | # file where it should appear only once). You can also use "--disable=all" to 409 | # disable everything first and then re-enable specific checks. For example, if 410 | # you want to run only the similarities checker, you can use "--disable=all 411 | # --enable=similarities". If you want to run only the classes checker, but have 412 | # no Warning level messages displayed, use "--disable=all --enable=classes 413 | # --disable=W". 414 | disable=raw-checker-failed, 415 | bad-inline-option, 416 | locally-disabled, 417 | file-ignored, 418 | suppressed-message, 419 | useless-suppression, 420 | deprecated-pragma, 421 | use-symbolic-message-instead, 422 | C0114, C0115, C0116, # docstrings 423 | C0103, # var naming 424 | R0205, # filter_plugins 'FilterModule' 425 | 426 | # Enable the message, report, category or checker with the given id(s). You can 427 | # either give multiple identifier separated by comma (,) or put this option 428 | # multiple time (only on the command line, not in the configuration file where 429 | # it should appear only once). See also the "--disable" option for examples. 430 | enable=c-extension-no-member 431 | 432 | 433 | [METHOD_ARGS] 434 | 435 | # List of qualified names (i.e., library.method) which require a timeout 436 | # parameter e.g. 'requests.api.get,requests.api.post' 437 | timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request 438 | 439 | 440 | [MISCELLANEOUS] 441 | 442 | # List of note tags to take in consideration, separated by a comma. 443 | notes=FIXME, 444 | XXX, 445 | TODO 446 | 447 | # Regular expression of note tags to take in consideration. 448 | notes-rgx= 449 | 450 | 451 | [REFACTORING] 452 | 453 | # Maximum number of nested blocks for function / method body 454 | max-nested-blocks=5 455 | 456 | # Complete name of functions that never returns. When checking for 457 | # inconsistent-return-statements if a never returning function is called then 458 | # it will be considered as an explicit return statement and no message will be 459 | # printed. 460 | never-returning-functions=sys.exit,argparse.parse_error 461 | 462 | 463 | [REPORTS] 464 | 465 | # Python expression which should return a score less than or equal to 10. You 466 | # have access to the variables 'fatal', 'error', 'warning', 'refactor', 467 | # 'convention', and 'info' which contain the number of messages in each 468 | # category, as well as 'statement' which is the total number of statements 469 | # analyzed. This score is used by the global evaluation report (RP0004). 470 | evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) 471 | 472 | # Template used to display messages. This is a python new-style format string 473 | # used to format the message information. See doc for all details. 474 | msg-template= 475 | 476 | # Set the output format. Available formats are text, parseable, colorized, json 477 | # and msvs (visual studio). You can also give a reporter class, e.g. 478 | # mypackage.mymodule.MyReporterClass. 479 | #output-format= 480 | 481 | # Tells whether to display a full report or only the messages. 482 | reports=no 483 | 484 | # Activate the evaluation score. 485 | score=yes 486 | 487 | 488 | [SIMILARITIES] 489 | 490 | # Comments are removed from the similarity computation 491 | ignore-comments=yes 492 | 493 | # Docstrings are removed from the similarity computation 494 | ignore-docstrings=yes 495 | 496 | # Imports are removed from the similarity computation 497 | ignore-imports=yes 498 | 499 | # Signatures are removed from the similarity computation 500 | ignore-signatures=yes 501 | 502 | # Minimum lines number of a similarity. 503 | min-similarity-lines=4 504 | 505 | 506 | [SPELLING] 507 | 508 | # Limits count of emitted suggestions for spelling mistakes. 509 | max-spelling-suggestions=4 510 | 511 | # Spelling dictionary name. Available dictionaries: none. To make it work, 512 | # install the 'python-enchant' package. 513 | spelling-dict= 514 | 515 | # List of comma separated words that should be considered directives if they 516 | # appear at the beginning of a comment and should not be checked. 517 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: 518 | 519 | # List of comma separated words that should not be checked. 520 | spelling-ignore-words= 521 | 522 | # A path to a file that contains the private dictionary; one word per line. 523 | spelling-private-dict-file= 524 | 525 | # Tells whether to store unknown words to the private dictionary (see the 526 | # --spelling-private-dict-file option) instead of raising a message. 527 | spelling-store-unknown-words=no 528 | 529 | 530 | [STRING] 531 | 532 | # This flag controls whether inconsistent-quotes generates a warning when the 533 | # character used as a quote delimiter is used inconsistently within a module. 534 | check-quote-consistency=no 535 | 536 | # This flag controls whether the implicit-str-concat should generate a warning 537 | # on implicit string concatenation in sequences defined over several lines. 538 | check-str-concat-over-line-jumps=no 539 | 540 | 541 | [TYPECHECK] 542 | 543 | # List of decorators that produce context managers, such as 544 | # contextlib.contextmanager. Add to this list to register other decorators that 545 | # produce valid context managers. 546 | contextmanager-decorators=contextlib.contextmanager 547 | 548 | # List of members which are set dynamically and missed by pylint inference 549 | # system, and so shouldn't trigger E1101 when accessed. Python regular 550 | # expressions are accepted. 551 | generated-members= 552 | 553 | # Tells whether to warn about missing members when the owner of the attribute 554 | # is inferred to be None. 555 | ignore-none=yes 556 | 557 | # This flag controls whether pylint should warn about no-member and similar 558 | # checks whenever an opaque object is returned when inferring. The inference 559 | # can return multiple potential results while evaluating a Python object, but 560 | # some branches might not be evaluated, which results in partial inference. In 561 | # that case, it might be useful to still emit no-member and other checks for 562 | # the rest of the inferred objects. 563 | ignore-on-opaque-inference=yes 564 | 565 | # List of symbolic message names to ignore for Mixin members. 566 | ignored-checks-for-mixins=no-member, 567 | not-async-context-manager, 568 | not-context-manager, 569 | attribute-defined-outside-init 570 | 571 | # List of class names for which member attributes should not be checked (useful 572 | # for classes with dynamically set attributes). This supports the use of 573 | # qualified names. 574 | ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace 575 | 576 | # Show a hint with possible names when a member name was not found. The aspect 577 | # of finding the hint is based on edit distance. 578 | missing-member-hint=yes 579 | 580 | # The minimum edit distance a name should have in order to be considered a 581 | # similar match for a missing member name. 582 | missing-member-hint-distance=1 583 | 584 | # The total number of similar names that should be taken in consideration when 585 | # showing a hint for a missing member. 586 | missing-member-max-choices=1 587 | 588 | # Regex pattern to define which classes are considered mixins. 589 | mixin-class-rgx=.*[Mm]ixin 590 | 591 | # List of decorators that change the signature of a decorated function. 592 | signature-mutators= 593 | 594 | 595 | [VARIABLES] 596 | 597 | # List of additional names supposed to be defined in builtins. Remember that 598 | # you should avoid defining new builtins when possible. 599 | additional-builtins= 600 | 601 | # Tells whether unused global variables should be treated as a violation. 602 | allow-global-unused-variables=yes 603 | 604 | # List of names allowed to shadow builtins 605 | allowed-redefined-builtins= 606 | 607 | # List of strings which can identify a callback function by name. A callback 608 | # name must start or end with one of those strings. 609 | callbacks=cb_, 610 | _cb 611 | 612 | # A regular expression matching the name of dummy variables (i.e. expected to 613 | # not be used). 614 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 615 | 616 | # Argument names that match this expression will be ignored. 617 | ignored-argument-names=_.*|^ignored_|^unused_ 618 | 619 | # Tells whether we should check for unused import in __init__ files. 620 | init-import=no 621 | 622 | # List of qualified module names which can have objects that can redefine 623 | # builtins. 624 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 625 | --------------------------------------------------------------------------------