Compare commits
5 Commits
fix/sorbs
...
fix/modobo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e2e9b6ab9 | ||
|
|
7ccc871da7 | ||
|
|
5f4817736f | ||
|
|
804c20a18d | ||
|
|
dd32b21ce9 |
22
.github/workflows/installer.yml
vendored
22
.github/workflows/installer.yml
vendored
@@ -11,29 +11,29 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.8, 3.9, '3.10', '3.11']
|
python-version: [3.7, 3.8, 3.9]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install -r test-requirements.txt
|
pip install -r test-requirements.txt
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: ${{ matrix.python-version != '3.11' }}
|
if: ${{ matrix.python-version != '3.9' }}
|
||||||
run: |
|
run: |
|
||||||
python tests.py
|
python tests.py
|
||||||
- name: Run tests and coverage
|
- name: Run tests and coverage
|
||||||
if: ${{ matrix.python-version == '3.11' }}
|
if: ${{ matrix.python-version == '3.9' }}
|
||||||
run: |
|
run: |
|
||||||
coverage run tests.py
|
coverage run tests.py
|
||||||
- name: Upload coverage result
|
- name: Upload coverage result
|
||||||
if: ${{ matrix.python-version == '3.11' }}
|
if: ${{ matrix.python-version == '3.9' }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: coverage-results
|
name: coverage-results
|
||||||
path: .coverage
|
path: .coverage
|
||||||
@@ -42,16 +42,16 @@ jobs:
|
|||||||
needs: test
|
needs: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.9'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install codecov
|
pip install codecov
|
||||||
- name: Download coverage results
|
- name: Download coverage results
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: coverage-results
|
name: coverage-results
|
||||||
- name: Report coverage
|
- name: Report coverage
|
||||||
|
|||||||
32
.github/workflows/versioning.yml
vendored
32
.github/workflows/versioning.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Update version file
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
branches: [ master ]
|
|
||||||
workflows: [Modoboa installer]
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-version:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
|
|
||||||
ref: ${{ github.head_ref }}
|
|
||||||
- name: Overwrite file
|
|
||||||
uses: "DamianReeves/write-file-action@master"
|
|
||||||
with:
|
|
||||||
path: version.txt
|
|
||||||
write-mode: overwrite
|
|
||||||
contents: ${{ github.sha }}
|
|
||||||
|
|
||||||
- name: Commit & Push
|
|
||||||
uses: Andro999b/push@v1.3
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
branch: ${{ github.ref_name }}
|
|
||||||
force: true
|
|
||||||
message: '[GitHub Action] Updated version file'
|
|
||||||
74
README.rst
74
README.rst
@@ -1,5 +1,5 @@
|
|||||||
**modoboa-installer**
|
modoboa-installer
|
||||||
=====================
|
=================
|
||||||
|
|
||||||
|workflow| |codecov|
|
|workflow| |codecov|
|
||||||
|
|
||||||
@@ -9,8 +9,9 @@ An installer which deploy a complete mail server based on Modoboa.
|
|||||||
|
|
||||||
This tool is still in beta stage, it has been tested on:
|
This tool is still in beta stage, it has been tested on:
|
||||||
|
|
||||||
* Debian 10 and upper
|
* Debian Buster (10) / Bullseye (11)
|
||||||
* Ubuntu Bionic Beaver (18.04) and upper
|
* Ubuntu Bionic Beaver (18.04) and upper
|
||||||
|
* CentOS 7
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ If you want more information about the installation process, add the
|
|||||||
``--debug`` option to your command line.
|
``--debug`` option to your command line.
|
||||||
|
|
||||||
Upgrade mode
|
Upgrade mode
|
||||||
============
|
------------
|
||||||
|
|
||||||
An experimental upgrade mode is available.
|
An experimental upgrade mode is available.
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ You can activate it as follows::
|
|||||||
It will automatically install latest versions of modoboa and its plugins.
|
It will automatically install latest versions of modoboa and its plugins.
|
||||||
|
|
||||||
Backup mode
|
Backup mode
|
||||||
===========
|
------------
|
||||||
|
|
||||||
An experimental backup mode is available.
|
An experimental backup mode is available.
|
||||||
|
|
||||||
@@ -107,19 +108,7 @@ You can start the process as follows::
|
|||||||
|
|
||||||
Then follow the step on the console.
|
Then follow the step on the console.
|
||||||
|
|
||||||
There is also a non-interactive mode::
|
There is also a non-interactive mode:
|
||||||
|
|
||||||
$ sudo ./run.py --silent-backup <your domain>
|
|
||||||
|
|
||||||
You can also add a path, else it will be saved in ./modoboa_backup/Backup_M_Y_d_H_M::
|
|
||||||
|
|
||||||
$ sudo ./run.py --silent-backup --backup-path "/My_Backup_Path" <your domain>
|
|
||||||
|
|
||||||
if you want to disable mail backup::
|
|
||||||
|
|
||||||
$ sudo ./run.py --backup --no-mail <your domain>
|
|
||||||
|
|
||||||
This can be useful for larger instance
|
|
||||||
|
|
||||||
1. Silent mode
|
1. Silent mode
|
||||||
|
|
||||||
@@ -141,7 +130,7 @@ configuration file (set enabled to False).
|
|||||||
This can be useful for larger instance.
|
This can be useful for larger instance.
|
||||||
|
|
||||||
Restore mode
|
Restore mode
|
||||||
============
|
------------
|
||||||
|
|
||||||
An experimental restore mode is available.
|
An experimental restore mode is available.
|
||||||
|
|
||||||
@@ -152,7 +141,7 @@ You can start the process as follows::
|
|||||||
Then wait for the process to finish.
|
Then wait for the process to finish.
|
||||||
|
|
||||||
Change the generated hostname
|
Change the generated hostname
|
||||||
=============================
|
-----------------------------
|
||||||
|
|
||||||
By default, the installer will setup your email server using the
|
By default, the installer will setup your email server using the
|
||||||
following hostname: ``mail.<your domain>``. If you want a different
|
following hostname: ``mail.<your domain>``. If you want a different
|
||||||
@@ -171,26 +160,14 @@ modifications.
|
|||||||
Finally, run the installer without the
|
Finally, run the installer without the
|
||||||
``--stop-after-configfile-check`` option.
|
``--stop-after-configfile-check`` option.
|
||||||
|
|
||||||
Certificate
|
Let's Encrypt certificate
|
||||||
===========
|
-------------------------
|
||||||
|
|
||||||
Self-signed
|
|
||||||
-----------
|
|
||||||
|
|
||||||
It is the default type of certificate the installer will generate, it
|
|
||||||
is however not recommended for production use.
|
|
||||||
|
|
||||||
Letsencrypt
|
|
||||||
-----------
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Please note that by using this option, you agree to the `ToS
|
Please note this option requires the hostname you're using to be
|
||||||
<https://community.letsencrypt.org/tos>`_ of
|
valid (ie. it can be resolved with a DNS query) and to match the
|
||||||
letsencrypt and that your IP will be logged (see ToS).
|
server you're installing Modoboa on.
|
||||||
Please also note this option requires the hostname you're using to be
|
|
||||||
valid (ie. it can be resolved with a DNS query) and to match the
|
|
||||||
server you're installing Modoboa on.
|
|
||||||
|
|
||||||
If you want to generate a valid certificate using `Let's Encrypt
|
If you want to generate a valid certificate using `Let's Encrypt
|
||||||
<https://letsencrypt.org/>`_, edit the ``installer.cfg`` file and
|
<https://letsencrypt.org/>`_, edit the ``installer.cfg`` file and
|
||||||
@@ -199,8 +176,6 @@ modify the following settings::
|
|||||||
[certificate]
|
[certificate]
|
||||||
generate = true
|
generate = true
|
||||||
type = letsencrypt
|
type = letsencrypt
|
||||||
tls_cert_file_path =
|
|
||||||
tls_key_file_path =
|
|
||||||
|
|
||||||
[letsencrypt]
|
[letsencrypt]
|
||||||
email = admin@example.com
|
email = admin@example.com
|
||||||
@@ -208,27 +183,6 @@ modify the following settings::
|
|||||||
Change the ``email`` setting to a valid value since it will be used
|
Change the ``email`` setting to a valid value since it will be used
|
||||||
for account recovery.
|
for account recovery.
|
||||||
|
|
||||||
Manual
|
|
||||||
------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
It is not possible to configure manual certs interactively, so
|
|
||||||
you'll have to do it in 2 steps. Please run ``run.py`` with
|
|
||||||
`--stop-after-configfile-check` first, configure your file as
|
|
||||||
desired and apply the configuration as written bellow. Then run
|
|
||||||
``run.py`` again but without `--stop-after-configfile-check` or
|
|
||||||
`--interactive`.
|
|
||||||
|
|
||||||
If you want to use already generated certs, simply edit the
|
|
||||||
``installer.cfg`` file and modify the following settings::
|
|
||||||
|
|
||||||
[certificate]
|
|
||||||
generate = true
|
|
||||||
type = manual
|
|
||||||
tls_cert_file_path = *path to tls fullchain file*
|
|
||||||
tls_key_file_path = *path to tls key file*
|
|
||||||
|
|
||||||
.. |workflow| image:: https://github.com/modoboa/modoboa-installer/workflows/Modoboa%20installer/badge.svg
|
.. |workflow| image:: https://github.com/modoboa/modoboa-installer/workflows/Modoboa%20installer/badge.svg
|
||||||
.. |codecov| image:: http://codecov.io/github/modoboa/modoboa-installer/coverage.svg?branch=master
|
.. |codecov| image:: http://codecov.io/github/modoboa/modoboa-installer/coverage.svg?branch=master
|
||||||
:target: http://codecov.io/github/modoboa/modoboa-installer?branch=master
|
:target: http://codecov.io/github/modoboa/modoboa-installer?branch=master
|
||||||
|
|||||||
37
checks.py
37
checks.py
@@ -1,37 +0,0 @@
|
|||||||
"""Checks to be performed before any install or upgrade"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from urllib.request import urlopen
|
|
||||||
|
|
||||||
from modoboa_installer import utils
|
|
||||||
|
|
||||||
|
|
||||||
def check_version():
|
|
||||||
local_version = ""
|
|
||||||
with open("version.txt", "r") as version:
|
|
||||||
local_version = version.readline()
|
|
||||||
remote_version = ""
|
|
||||||
with urlopen("https://raw.githubusercontent.com/modoboa/modoboa-installer/master/version.txt") as r_version:
|
|
||||||
remote_version = r_version.read().decode()
|
|
||||||
if local_version == "" or remote_version == "":
|
|
||||||
utils.printcolor(
|
|
||||||
"Could not check that your installer is up-to-date: "
|
|
||||||
f"local version: {local_version}, "
|
|
||||||
f"remote version: {remote_version}",
|
|
||||||
utils.YELLOW
|
|
||||||
)
|
|
||||||
if remote_version != local_version:
|
|
||||||
utils.error(
|
|
||||||
"Your installer seems outdated.\n"
|
|
||||||
"Check README file for instructions about how to update.\n"
|
|
||||||
"No support will be provided without an up-to-date installer!"
|
|
||||||
)
|
|
||||||
answer = utils.user_input("Continue anyway? (Y/n) ")
|
|
||||||
if not answer.lower().startswith("y"):
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
utils.success("Installer seems up to date!")
|
|
||||||
|
|
||||||
|
|
||||||
def handle():
|
|
||||||
check_version()
|
|
||||||
@@ -21,14 +21,13 @@ COMPATIBILITY_MATRIX = {
|
|||||||
"modoboa-sievefilters": ">=1.1.1",
|
"modoboa-sievefilters": ">=1.1.1",
|
||||||
"modoboa-webmail": ">=1.2.0",
|
"modoboa-webmail": ">=1.2.0",
|
||||||
},
|
},
|
||||||
|
"2.1.0": {
|
||||||
|
"modoboa-pdfcredentials": None,
|
||||||
|
"modoboa-dmarc": None,
|
||||||
|
"modoboa-imap-migration": None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
EXTENSIONS_AVAILABILITY = {
|
EXTENSIONS_AVAILABILITY = {
|
||||||
"modoboa-contacts": "1.7.4",
|
"modoboa-contacts": "1.7.4",
|
||||||
}
|
}
|
||||||
|
|
||||||
REMOVED_EXTENSIONS = {
|
|
||||||
"modoboa-pdfcredentials": "2.1.0",
|
|
||||||
"modoboa-dmarc": "2.1.0",
|
|
||||||
"modoboa-imap-migration": "2.1.0"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -30,21 +30,16 @@ ConfigDictTemplate = [
|
|||||||
{
|
{
|
||||||
"name": "certificate",
|
"name": "certificate",
|
||||||
"values": [
|
"values": [
|
||||||
|
{
|
||||||
|
"option": "generate",
|
||||||
|
"default": "true",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"option": "type",
|
"option": "type",
|
||||||
"default": "self-signed",
|
"default": "self-signed",
|
||||||
"customizable": True,
|
"customizable": True,
|
||||||
"question": "Please choose your certificate type",
|
"question": "Please choose your certificate type",
|
||||||
"values": ["self-signed", "letsencrypt", "manual"],
|
"values": ["self-signed", "letsencrypt"],
|
||||||
"non_interactive_values": ["manual"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"option": "tls_cert_file_path",
|
|
||||||
"default": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"option": "tls_key_file_path",
|
|
||||||
"default": ""
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ class DEBPackage(Package):
|
|||||||
def install(self, name):
|
def install(self, name):
|
||||||
"""Install a package."""
|
"""Install a package."""
|
||||||
self.update()
|
self.update()
|
||||||
utils.exec_cmd("apt-get -o Dpkg::Progress-Fancy=0 install --quiet --assume-yes -o DPkg::options::=--force-confold {}".format(name))
|
utils.exec_cmd("apt-get -o Dpkg::Progress-Fancy=0 install --quiet --assume-yes {}".format(name))
|
||||||
|
|
||||||
def install_many(self, names):
|
def install_many(self, names):
|
||||||
"""Install many packages."""
|
"""Install many packages."""
|
||||||
self.update()
|
self.update()
|
||||||
return utils.exec_cmd("apt-get -o Dpkg::Progress-Fancy=0 install --quiet --assume-yes -o DPkg::options::=--force-confold {}".format(
|
return utils.exec_cmd("apt-get -o Dpkg::Progress-Fancy=0 install --quiet --assume-yes {}".format(
|
||||||
" ".join(names)))
|
" ".join(names)))
|
||||||
|
|
||||||
def get_installed_version(self, name):
|
def get_installed_version(self, name):
|
||||||
@@ -108,7 +108,7 @@ def get_backend():
|
|||||||
"""Return the appropriate package backend."""
|
"""Return the appropriate package backend."""
|
||||||
distname = utils.dist_name()
|
distname = utils.dist_name()
|
||||||
backend = None
|
backend = None
|
||||||
if distname in ["debian", "debian gnu/linux", "ubuntu", "linuxmint"]:
|
if distname in ["debian", "debian gnu/linux", "ubuntu"]:
|
||||||
backend = DEBPackage
|
backend = DEBPackage
|
||||||
elif "centos" in distname:
|
elif "centos" in distname:
|
||||||
backend = RPMPackage
|
backend = RPMPackage
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Python related tools."""
|
"""Python related tools."""
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -48,35 +49,29 @@ def install_packages(names, venv=None, upgrade=False, **kwargs):
|
|||||||
|
|
||||||
def get_package_version(name, venv=None, **kwargs):
|
def get_package_version(name, venv=None, **kwargs):
|
||||||
"""Returns the version of an installed package."""
|
"""Returns the version of an installed package."""
|
||||||
cmd = "{} show {}".format(
|
cmd = f"{get_pip_path(venv)} list --format json"
|
||||||
get_pip_path(venv),
|
|
||||||
name
|
|
||||||
)
|
|
||||||
exit_code, output = utils.exec_cmd(cmd, **kwargs)
|
exit_code, output = utils.exec_cmd(cmd, **kwargs)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
utils.error(f"Failed to get version of {name}. "
|
utils.error(f"Failed to get version of {name}. "
|
||||||
f"Output is: {output}")
|
f"Output is: {output}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"name: {name}, venv: {venv}, cmd: {cmd}, exit_code: {exit_code}, output: {output.decode()}")
|
||||||
|
list_dict = json.loads(output.decode())
|
||||||
|
version_list = []
|
||||||
|
for element in list_dict:
|
||||||
|
if element["name"] == name:
|
||||||
|
version_list = element["version"].split(".")
|
||||||
|
break
|
||||||
version_list_clean = []
|
version_list_clean = []
|
||||||
for line in output.decode().split("\n"):
|
for element in version_list:
|
||||||
if not line.startswith("Version:"):
|
try:
|
||||||
continue
|
version_list_clean.append(int(element))
|
||||||
version_item_list = line.split(":")
|
except ValueError:
|
||||||
version_list = version_item_list[1].split(".")
|
utils.printcolor(
|
||||||
for element in version_list:
|
f"Failed to decode some part of the version of {name}",
|
||||||
try:
|
utils.YELLOW)
|
||||||
version_list_clean.append(int(element))
|
version_list_clean.append(element)
|
||||||
except ValueError:
|
|
||||||
utils.printcolor(
|
|
||||||
f"Failed to decode some part of the version of {name}",
|
|
||||||
utils.YELLOW)
|
|
||||||
version_list_clean.append(element)
|
|
||||||
if len(version_list_clean) == 0:
|
|
||||||
utils.printcolor(
|
|
||||||
f"Failed to find the version of {name}",
|
|
||||||
utils.RED)
|
|
||||||
sys.exit(1)
|
|
||||||
return version_list_clean
|
return version_list_clean
|
||||||
|
|
||||||
|
|
||||||
@@ -99,17 +94,26 @@ def install_package_from_remote_requirements(url, venv=None, **kwargs):
|
|||||||
utils.exec_cmd(cmd, **kwargs)
|
utils.exec_cmd(cmd, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def setup_virtualenv(path, sudo_user=None):
|
def setup_virtualenv(path, sudo_user=None, python_version=2):
|
||||||
"""Install a virtualenv if needed."""
|
"""Install a virtualenv if needed."""
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
return
|
return
|
||||||
if utils.dist_name().startswith("centos"):
|
if python_version == 2:
|
||||||
python_binary = "python3"
|
python_binary = "python"
|
||||||
packages = ["python3"]
|
packages = ["python-virtualenv"]
|
||||||
|
if utils.dist_name() == "debian":
|
||||||
|
packages.append("virtualenv")
|
||||||
else:
|
else:
|
||||||
python_binary = "python3"
|
if utils.dist_name().startswith("centos"):
|
||||||
packages = ["python3-venv"]
|
python_binary = "python3"
|
||||||
|
packages = ["python3"]
|
||||||
|
else:
|
||||||
|
python_binary = "python3"
|
||||||
|
packages = ["python3-venv"]
|
||||||
package.backend.install_many(packages)
|
package.backend.install_many(packages)
|
||||||
with utils.settings(sudo_user=sudo_user):
|
with utils.settings(sudo_user=sudo_user):
|
||||||
utils.exec_cmd("{} -m venv {}".format(python_binary, path))
|
if python_version == 2:
|
||||||
install_packages(["pip", "setuptools"], venv=path, upgrade=True)
|
utils.exec_cmd("virtualenv {}".format(path))
|
||||||
|
else:
|
||||||
|
utils.exec_cmd("{} -m venv {}".format(python_binary, path))
|
||||||
|
install_packages(["pip", "setuptools\<58.0.0"], venv=path, upgrade=True)
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ class Automx(base.Installer):
|
|||||||
|
|
||||||
def _setup_venv(self):
|
def _setup_venv(self):
|
||||||
"""Prepare a python virtualenv."""
|
"""Prepare a python virtualenv."""
|
||||||
python.setup_virtualenv(self.venv_path, sudo_user=self.user)
|
python.setup_virtualenv(
|
||||||
|
self.venv_path, sudo_user=self.user, python_version=3)
|
||||||
packages = [
|
packages = [
|
||||||
"future", "lxml", "ipaddress", "sqlalchemy < 2.0", "python-memcached",
|
"future", "lxml", "ipaddress", "sqlalchemy < 2.0", "python-memcached",
|
||||||
"python-dateutil", "configparser"
|
"python-dateutil", "configparser"
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ postscreen_dnsbl_sites =
|
|||||||
zen.spamhaus.org=127.0.0.[2..11]*3
|
zen.spamhaus.org=127.0.0.[2..11]*3
|
||||||
bl.spameatingmonkey.net=127.0.0.2*2
|
bl.spameatingmonkey.net=127.0.0.2*2
|
||||||
bl.spamcop.net=127.0.0.2
|
bl.spamcop.net=127.0.0.2
|
||||||
|
dnsbl.sorbs.net=127.0.0.[2..15]
|
||||||
postscreen_dnsbl_threshold = 3
|
postscreen_dnsbl_threshold = 3
|
||||||
postscreen_dnsbl_action = enforce
|
postscreen_dnsbl_action = enforce
|
||||||
|
|
||||||
|
|||||||
@@ -149,4 +149,4 @@ autoreply unix - n n - - pipe
|
|||||||
%{amavis_enabled} -o smtpd_client_connection_count_limit=0
|
%{amavis_enabled} -o smtpd_client_connection_count_limit=0
|
||||||
%{amavis_enabled} -o smtpd_client_connection_rate_limit=0
|
%{amavis_enabled} -o smtpd_client_connection_rate_limit=0
|
||||||
%{amavis_enabled} -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
|
%{amavis_enabled} -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
|
||||||
%{amavis_enabled} -o local_header_rewrite_clients=permit_mynetworks,permit_sasl_authenticated
|
%{amavis_enabled} -o local_header_rewrite_clients=
|
||||||
|
|||||||
@@ -65,31 +65,21 @@ class Modoboa(base.Installer):
|
|||||||
|
|
||||||
def is_extension_ok_for_version(self, extension, version):
|
def is_extension_ok_for_version(self, extension, version):
|
||||||
"""Check if extension can be installed with this modo version."""
|
"""Check if extension can be installed with this modo version."""
|
||||||
|
if extension not in compatibility_matrix.EXTENSIONS_AVAILABILITY:
|
||||||
|
return True
|
||||||
version = utils.convert_version_to_int(version)
|
version = utils.convert_version_to_int(version)
|
||||||
if extension in compatibility_matrix.EXTENSIONS_AVAILABILITY:
|
min_version = compatibility_matrix.EXTENSIONS_AVAILABILITY[extension]
|
||||||
min_version = compatibility_matrix.EXTENSIONS_AVAILABILITY[extension]
|
min_version = utils.convert_version_to_int(min_version)
|
||||||
min_version = utils.convert_version_to_int(min_version)
|
return version >= min_version
|
||||||
return version >= min_version
|
|
||||||
if extension in compatibility_matrix.REMOVED_EXTENSIONS:
|
|
||||||
max_version = compatibility_matrix.REMOVED_EXTENSIONS[extension]
|
|
||||||
max_version = utils.convert_version_to_int(max_version)
|
|
||||||
return version < max_version
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _setup_venv(self):
|
def _setup_venv(self):
|
||||||
"""Prepare a dedicated virtualenv."""
|
"""Prepare a dedicated virtualenv."""
|
||||||
python.setup_virtualenv(self.venv_path, sudo_user=self.user)
|
python.setup_virtualenv(
|
||||||
|
self.venv_path, sudo_user=self.user, python_version=3)
|
||||||
packages = ["rrdtool"]
|
packages = ["rrdtool"]
|
||||||
version = self.config.get("modoboa", "version")
|
version = self.config.get("modoboa", "version")
|
||||||
if version == "latest":
|
if version == "latest":
|
||||||
packages += ["modoboa"] + self.extensions
|
packages += ["modoboa"] + self.extensions
|
||||||
for extension in list(self.extensions):
|
|
||||||
if extension in compatibility_matrix.REMOVED_EXTENSIONS.keys():
|
|
||||||
self.extensions.remove(extension)
|
|
||||||
self.extensions = [
|
|
||||||
extension for extension in self.extensions
|
|
||||||
if extension not in compatibility_matrix.REMOVED_EXTENSIONS
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
matrix = compatibility_matrix.COMPATIBILITY_MATRIX[version]
|
matrix = compatibility_matrix.COMPATIBILITY_MATRIX[version]
|
||||||
packages.append("modoboa=={}".format(version))
|
packages.append("modoboa=={}".format(version))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Nginx(base.Installer):
|
|||||||
|
|
||||||
def get_template_context(self, app):
|
def get_template_context(self, app):
|
||||||
"""Additionnal variables."""
|
"""Additionnal variables."""
|
||||||
context = super().get_template_context()
|
context = super(Nginx, self).get_template_context()
|
||||||
context.update({
|
context.update({
|
||||||
"app_instance_path": (
|
"app_instance_path": (
|
||||||
self.config.get(app, "instance_path")),
|
self.config.get(app, "instance_path")),
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ class Radicale(base.Installer):
|
|||||||
|
|
||||||
def _setup_venv(self):
|
def _setup_venv(self):
|
||||||
"""Prepare a dedicated virtualenv."""
|
"""Prepare a dedicated virtualenv."""
|
||||||
python.setup_virtualenv(self.venv_path, sudo_user=self.user)
|
python.setup_virtualenv(
|
||||||
|
self.venv_path, sudo_user=self.user, python_version=3)
|
||||||
packages = [
|
packages = [
|
||||||
"Radicale", "radicale-dovecot-auth", "pytz"
|
"Radicale", "radicale-dovecot-auth", "pytz"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from . import package
|
|||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
class CertificateBackend:
|
class CertificateBackend(object):
|
||||||
"""Base class."""
|
"""Base class."""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
@@ -24,44 +24,13 @@ class CertificateBackend:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def generate_cert(self):
|
|
||||||
"""Create a certificate."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ManualCertificate(CertificateBackend):
|
|
||||||
"""Use certificate provided."""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
path_correct = True
|
|
||||||
self.tls_cert_file_path = self.config.get("certificate",
|
|
||||||
"tls_cert_file_path")
|
|
||||||
self.tls_key_file_path = self.config.get("certificate",
|
|
||||||
"tls_key_file_path")
|
|
||||||
|
|
||||||
if not os.path.exists(self.tls_key_file_path):
|
|
||||||
utils.error("'tls_key_file_path' path is not accessible")
|
|
||||||
path_correct = False
|
|
||||||
if not os.path.exists(self.tls_cert_file_path):
|
|
||||||
utils.error("'tls_cert_file_path' path is not accessible")
|
|
||||||
path_correct = False
|
|
||||||
|
|
||||||
if not path_correct:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
self.config.set("general", "tls_key_file",
|
|
||||||
self.tls_key_file_path)
|
|
||||||
self.config.set("general", "tls_cert_file",
|
|
||||||
self.tls_cert_file_path)
|
|
||||||
|
|
||||||
|
|
||||||
class SelfSignedCertificate(CertificateBackend):
|
class SelfSignedCertificate(CertificateBackend):
|
||||||
"""Create a self signed certificate."""
|
"""Create a self signed certificate."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Sanity checks."""
|
"""Sanity checks."""
|
||||||
super().__init__(*args, **kwargs)
|
super(SelfSignedCertificate, self).__init__(*args, **kwargs)
|
||||||
if self.config.has_option("general", "tls_key_file"):
|
if self.config.has_option("general", "tls_key_file"):
|
||||||
# Compatibility
|
# Compatibility
|
||||||
return
|
return
|
||||||
@@ -96,7 +65,7 @@ class LetsEncryptCertificate(CertificateBackend):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Update config."""
|
"""Update config."""
|
||||||
super().__init__(*args, **kwargs)
|
super(LetsEncryptCertificate, self).__init__(*args, **kwargs)
|
||||||
self.hostname = self.config.get("general", "hostname")
|
self.hostname = self.config.get("general", "hostname")
|
||||||
self.config.set("general", "tls_cert_file", (
|
self.config.set("general", "tls_cert_file", (
|
||||||
"/etc/letsencrypt/live/{}/fullchain.pem".format(self.hostname)))
|
"/etc/letsencrypt/live/{}/fullchain.pem".format(self.hostname)))
|
||||||
@@ -146,24 +115,12 @@ class LetsEncryptCertificate(CertificateBackend):
|
|||||||
cfg_file = "/etc/letsencrypt/renewal/{}.conf".format(self.hostname)
|
cfg_file = "/etc/letsencrypt/renewal/{}.conf".format(self.hostname)
|
||||||
pattern = "s/authenticator = standalone/authenticator = nginx/"
|
pattern = "s/authenticator = standalone/authenticator = nginx/"
|
||||||
utils.exec_cmd("perl -pi -e '{}' {}".format(pattern, cfg_file))
|
utils.exec_cmd("perl -pi -e '{}' {}".format(pattern, cfg_file))
|
||||||
with open("/etc/letsencrypt/renewal-hooks/deploy/reload-services.sh", "w") as fp:
|
|
||||||
fp.write(f"""#!/bin/bash
|
|
||||||
|
|
||||||
HOSTNAME=$(basename $RENEWED_LINEAGE)
|
|
||||||
|
|
||||||
if [ "$HOSTNAME" = "{self.hostname}" ]
|
|
||||||
then
|
|
||||||
systemctl reload dovecot
|
|
||||||
systemctl reload postfix
|
|
||||||
fi
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
def get_backend(config):
|
def get_backend(config):
|
||||||
"""Return the appropriate backend."""
|
"""Return the appropriate backend."""
|
||||||
cert_type = config.get("certificate", "type")
|
if not config.getboolean("certificate", "generate"):
|
||||||
if cert_type == "letsencrypt":
|
return None
|
||||||
|
if config.get("certificate", "type") == "letsencrypt":
|
||||||
return LetsEncryptCertificate(config)
|
return LetsEncryptCertificate(config)
|
||||||
if cert_type == "manual":
|
|
||||||
return ManualCertificate(config)
|
|
||||||
return SelfSignedCertificate(config)
|
return SelfSignedCertificate(config)
|
||||||
|
|||||||
@@ -316,17 +316,6 @@ def get_entry_value(entry, interactive):
|
|||||||
|
|
||||||
if entry.get("values") and user_value != "":
|
if entry.get("values") and user_value != "":
|
||||||
user_value = values[int(user_value)]
|
user_value = values[int(user_value)]
|
||||||
|
|
||||||
non_interactive_values = entry.get("non_interactive_values", [])
|
|
||||||
if user_value in non_interactive_values:
|
|
||||||
error(
|
|
||||||
f"{user_value} cannot be set interactively. "
|
|
||||||
"Please configure installer.cfg manually by running "
|
|
||||||
"'python3 run.py --stop-after-configfile-check domain'. "
|
|
||||||
"Check modoboa-installer README for more information."
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
return user_value if user_value else default_value
|
return user_value if user_value else default_value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
45
run.py
45
run.py
@@ -11,7 +11,6 @@ except ImportError:
|
|||||||
import ConfigParser as configparser
|
import ConfigParser as configparser
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import checks
|
|
||||||
from modoboa_installer import compatibility_matrix
|
from modoboa_installer import compatibility_matrix
|
||||||
from modoboa_installer import constants
|
from modoboa_installer import constants
|
||||||
from modoboa_installer import package
|
from modoboa_installer import package
|
||||||
@@ -38,12 +37,6 @@ PRIMARY_APPS = [
|
|||||||
def installation_disclaimer(args, config):
|
def installation_disclaimer(args, config):
|
||||||
"""Display installation disclaimer."""
|
"""Display installation disclaimer."""
|
||||||
hostname = config.get("general", "hostname")
|
hostname = config.get("general", "hostname")
|
||||||
utils.printcolor(
|
|
||||||
"Notice:\n"
|
|
||||||
"It is recommanded to run this installer on a FRESHLY installed server.\n"
|
|
||||||
"(ie. with nothing special already installed on it)\n",
|
|
||||||
utils.CYAN
|
|
||||||
)
|
|
||||||
utils.printcolor(
|
utils.printcolor(
|
||||||
"Warning:\n"
|
"Warning:\n"
|
||||||
"Before you start the installation, please make sure the following "
|
"Before you start the installation, please make sure the following "
|
||||||
@@ -54,7 +47,7 @@ def installation_disclaimer(args, config):
|
|||||||
hostname.replace(".{}".format(args.domain), ""),
|
hostname.replace(".{}".format(args.domain), ""),
|
||||||
hostname
|
hostname
|
||||||
),
|
),
|
||||||
utils.YELLOW
|
utils.CYAN
|
||||||
)
|
)
|
||||||
utils.printcolor(
|
utils.printcolor(
|
||||||
"Your mail server will be installed with the following components:",
|
"Your mail server will be installed with the following components:",
|
||||||
@@ -120,9 +113,6 @@ def backup_system(config, args):
|
|||||||
utils.copy_file(args.configfile, backup_path)
|
utils.copy_file(args.configfile, backup_path)
|
||||||
# Backup applications
|
# Backup applications
|
||||||
for app in PRIMARY_APPS:
|
for app in PRIMARY_APPS:
|
||||||
if app == "dovecot" and args.no_mail:
|
|
||||||
utils.printcolor("Skipping mail backup", utils.BLUE)
|
|
||||||
continue
|
|
||||||
scripts.backup(app, config, backup_path)
|
scripts.backup(app, config, backup_path)
|
||||||
|
|
||||||
|
|
||||||
@@ -174,17 +164,11 @@ def main(input_args):
|
|||||||
help="For script usage, do not require user interaction "
|
help="For script usage, do not require user interaction "
|
||||||
"backup will be saved at ./modoboa_backup/Backup_M_Y_d_H_M "
|
"backup will be saved at ./modoboa_backup/Backup_M_Y_d_H_M "
|
||||||
"if --backup-path is not provided")
|
"if --backup-path is not provided")
|
||||||
parser.add_argument(
|
|
||||||
"--no-mail", action="store_true", default=False,
|
|
||||||
help="Disable mail backup (save space)")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--restore", type=str, metavar="path",
|
"--restore", type=str, metavar="path",
|
||||||
help="Restore a previously backup up modoboa instance on a NEW machine. "
|
help="Restore a previously backup up modoboa instance on a NEW machine. "
|
||||||
"You MUST provide backup directory"
|
"You MUST provide backup directory"
|
||||||
),
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--skip-checks", action="store_true", default=False,
|
|
||||||
help="Skip the checks the installer performs initially")
|
|
||||||
parser.add_argument("domain", type=str,
|
parser.add_argument("domain", type=str,
|
||||||
help="The main domain of your future mail server")
|
help="The main domain of your future mail server")
|
||||||
args = parser.parse_args(input_args)
|
args = parser.parse_args(input_args)
|
||||||
@@ -205,12 +189,6 @@ def main(input_args):
|
|||||||
|
|
||||||
utils.success("Welcome to Modoboa installer!\n")
|
utils.success("Welcome to Modoboa installer!\n")
|
||||||
|
|
||||||
# Checks
|
|
||||||
if not args.skip_checks:
|
|
||||||
utils.printcolor("Checking the installer...", utils.BLUE)
|
|
||||||
checks.handle()
|
|
||||||
utils.success("Checks complete\n")
|
|
||||||
|
|
||||||
is_config_file_available, outdate_config = utils.check_config_file(
|
is_config_file_available, outdate_config = utils.check_config_file(
|
||||||
args.configfile, args.interactive, args.upgrade, args.backup, is_restoring)
|
args.configfile, args.interactive, args.upgrade, args.backup, is_restoring)
|
||||||
|
|
||||||
@@ -223,11 +201,11 @@ def main(input_args):
|
|||||||
if is_config_file_available and outdate_config:
|
if is_config_file_available and outdate_config:
|
||||||
answer = utils.user_input("It seems that your config file is outdated. "
|
answer = utils.user_input("It seems that your config file is outdated. "
|
||||||
"Would you like to update it? (Y/n) ")
|
"Would you like to update it? (Y/n) ")
|
||||||
if not answer or answer.lower().startswith("y"):
|
if answer.lower().startswith("y"):
|
||||||
config_file_update_complete(utils.update_config(args.configfile))
|
config_file_update_complete(utils.update_config(args.configfile))
|
||||||
if not args.stop_after_configfile_check:
|
if not args.stop_after_configfile_check:
|
||||||
answer = utils.user_input("Would you like to stop to review the updated config? (Y/n)")
|
answer = utils.user_input("Would you like to stop to review the updated config? (Y/n)")
|
||||||
if not answer or answer.lower().startswith("y"):
|
if answer.lower().startswith("y"):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
utils.error("You might encounter unexpected errors ! "
|
utils.error("You might encounter unexpected errors ! "
|
||||||
@@ -263,7 +241,7 @@ def main(input_args):
|
|||||||
components = []
|
components = []
|
||||||
for section in config.sections():
|
for section in config.sections():
|
||||||
if section in ["general", "database", "mysql", "postgres",
|
if section in ["general", "database", "mysql", "postgres",
|
||||||
"certificate", "letsencrypt", "backup"]:
|
"certificate", "letsencrypt"]:
|
||||||
continue
|
continue
|
||||||
if (config.has_option(section, "enabled") and
|
if (config.has_option(section, "enabled") and
|
||||||
not config.getboolean(section, "enabled")):
|
not config.getboolean(section, "enabled")):
|
||||||
@@ -298,19 +276,6 @@ def main(input_args):
|
|||||||
"Restore complete! You can enjoy Modoboa at https://{} (same credentials as before)"
|
"Restore complete! You can enjoy Modoboa at https://{} (same credentials as before)"
|
||||||
.format(config.get("general", "hostname"))
|
.format(config.get("general", "hostname"))
|
||||||
)
|
)
|
||||||
utils.success(
|
|
||||||
"\n"
|
|
||||||
"Modoboa is a free software maintained by volunteers.\n"
|
|
||||||
"You like the project and want it to be sustainable?\n"
|
|
||||||
"Then don't wait anymore and go sponsor it here:\n"
|
|
||||||
)
|
|
||||||
utils.printcolor(
|
|
||||||
"https://github.com/sponsors/modoboa\n",
|
|
||||||
utils.YELLOW
|
|
||||||
)
|
|
||||||
utils.success(
|
|
||||||
"Thank you for your help :-)\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
c1abbe97925917d4ec62ff11a70b375d40be5147
|
|
||||||
Reference in New Issue
Block a user