Improved backups

This commit is contained in:
Spitap
2025-11-01 17:18:22 +01:00
parent 30b9393877
commit 3e5b9ab310
9 changed files with 287 additions and 459 deletions

View File

@@ -9,8 +9,7 @@ from .. import utils
def load_app_script(appname):
"""Load module corresponding to the given appname."""
try:
script = importlib.import_module(
"modoboa_installer.scripts.{}".format(appname))
script = importlib.import_module("modoboa_installer.scripts.{}".format(appname))
except ImportError:
print("Unknown application {}".format(appname))
sys.exit(1)
@@ -19,8 +18,9 @@ def load_app_script(appname):
def install(appname: str, config, upgrade: bool, archive_path: str):
"""Install an application."""
if (config.has_option(appname, "enabled") and
not config.getboolean(appname, "enabled")):
if config.has_option(appname, "enabled") and not config.getboolean(
appname, "enabled"
):
return
utils.printcolor("Installing {}".format(appname), utils.MAGENTA)
@@ -34,8 +34,9 @@ def install(appname: str, config, upgrade: bool, archive_path: str):
def backup(appname, config, path):
"""Backup an application."""
if (config.has_option(appname, "enabled") and
not config.getboolean(appname, "enabled")):
if config.has_option(appname, "enabled") and not config.getboolean(
appname, "enabled"
):
return
utils.printcolor("Backing up {}".format(appname), utils.MAGENTA)
@@ -49,6 +50,5 @@ def backup(appname, config, path):
def restore_prep(restore):
"""Restore instance"""
script = importlib.import_module(
"modoboa_installer.scripts.restore")
script = importlib.import_module("modoboa_installer.scripts.restore")
getattr(script, "Restore")(restore)

View File

@@ -10,18 +10,29 @@ from . import backup, install
class Amavis(base.Installer):
"""Amavis installer."""
appname = "amavis"
packages = {
"deb": [
"libdbi-perl", "amavisd-new", "arc", "arj", "cabextract",
"liblz4-tool", "lrzip", "lzop", "p7zip-full", "rpm2cpio",
"libdbi-perl",
"amavisd-new",
"arc",
"arj",
"cabextract",
"liblz4-tool",
"lrzip",
"lzop",
"p7zip-full",
"rpm2cpio",
"unrar-free",
],
"rpm": [
"amavisd-new", "arj", "lz4", "lzop", "p7zip",
"amavisd-new",
"arj",
"lz4",
"lzop",
"p7zip",
],
}
with_db = True
@@ -43,8 +54,10 @@ class Amavis(base.Installer):
"""Return appropriate config files."""
if package.backend.FORMAT == "deb":
return [
"conf.d/05-node_id", "conf.d/15-content_filter_mode",
"conf.d/50-user"]
"conf.d/05-node_id",
"conf.d/15-content_filter_mode",
"conf.d/50-user",
]
return ["amavisd.conf"]
def get_packages(self):
@@ -62,7 +75,7 @@ class Amavis(base.Installer):
if major_version >= 13:
packages = [p if p != "liblz4-tool" else "lz4" for p in packages]
return packages
if self.db_driver == "pgsql":
db_driver = "Pg"
elif self.db_driver == "mysql":
@@ -71,9 +84,9 @@ class Amavis(base.Installer):
raise NotImplementedError("DB driver not supported")
packages += ["perl-DBD-{}".format(db_driver)]
name, version = utils.dist_info()
if version.startswith('7'):
if version.startswith("7"):
packages += ["cabextract", "lrzip", "unar", "unzoo"]
elif version.startswith('8'):
elif version.startswith("8"):
packages += ["perl-IO-stringy"]
return packages
@@ -85,12 +98,10 @@ class Amavis(base.Installer):
version = package.backend.get_installed_version("amavis")
if version is None:
raise utils.FatalError("Amavis is not installed")
path = self.get_file_path(
"amavis_{}_{}.sql".format(self.dbengine, version))
path = self.get_file_path("amavis_{}_{}.sql".format(self.dbengine, version))
if not os.path.exists(path):
version = ".".join(version.split(".")[:-1]) + ".X"
path = self.get_file_path(
"amavis_{}_{}.sql".format(self.dbengine, version))
path = self.get_file_path("amavis_{}_{}.sql".format(self.dbengine, version))
if not os.path.exists(path):
raise utils.FatalError("Failed to find amavis database schema")
return path
@@ -107,20 +118,21 @@ class Amavis(base.Installer):
def custom_backup(self, path):
"""Backup custom configuration if any."""
if package.backend.FORMAT == "deb":
amavis_custom = f"{self.config_dir}/conf.d/99-custom"
if os.path.isfile(amavis_custom):
utils.copy_file(amavis_custom, path)
utils.success("Amavis custom configuration saved!")
backup("spamassassin", self.config, os.path.dirname(path))
amavis_custom = f"{self.config_dir}/conf.d/99-custom"
if os.path.isfile(amavis_custom):
utils.copy_file(amavis_custom, path)
utils.success("Amavis custom configuration saved!")
backup("spamassassin", self.config, self.base_backup_path)
def restore(self):
"""Restore custom config files."""
if package.backend.FORMAT != "deb":
return
amavis_custom_configuration = os.path.join(
self.archive_path, "custom/99-custom")
self.archive_path, "custom/amavis/99-custom"
)
if os.path.isfile(amavis_custom_configuration):
utils.copy_file(amavis_custom_configuration, os.path.join(
self.config_dir, "conf.d"))
utils.copy_file(
amavis_custom_configuration, os.path.join(self.config_dir, "conf.d")
)
utils.success("Custom amavis configuration restored.")

View File

@@ -1,226 +0,0 @@
"""Backup script for pre-installed instance."""
import os
import pwd
import shutil
import stat
import sys
import datetime
from .. import database
from .. import utils
from ..constants import DEFAULT_BACKUP_DIRECTORY
class Backup:
"""
Backup structure ( {optional} ):
{{backup_directory}}
||
||--> installer.cfg
||--> custom
|--> { (copy of) /etc/amavis/conf.d/99-custom }
|--> { (copy of) /etc/postfix/custom_whitelist.cidr }
|--> { (copy of) dkim directory }
|--> {dkim.pem}...
|--> { (copy of) radicale home_dir }
||--> databases
|--> modoboa.sql
|--> { amavis.sql }
|--> { spamassassin.sql }
||--> mails
|--> vmails
"""
def __init__(self, config, silent_backup, backup_path, nomail):
self.config = config
self.backup_path = backup_path
self.nomail = nomail
self.silent_backup = silent_backup
def validate_path(self, path):
"""Check basic condition for backup directory."""
path_exists = os.path.exists(path)
if path_exists and os.path.isfile(path):
utils.error("Error, you provided a file instead of a directory!")
return False
if not path_exists:
if not self.silent_backup:
create_dir = input(
f"\"{path}\" doesn't exist, would you like to create it? [Y/n]\n").lower()
if self.silent_backup or (not self.silent_backup and create_dir.startswith("y")):
pw = pwd.getpwnam("root")
utils.mkdir_safe(path, stat.S_IRWXU |
stat.S_IRWXG, pw[2], pw[3])
else:
utils.error("Error, backup directory not present.")
return False
if len(os.listdir(path)) != 0:
if not self.silent_backup:
delete_dir = input(
"Warning: backup directory is not empty, it will be purged if you continue... [Y/n]\n").lower()
if self.silent_backup or (not self.silent_backup and delete_dir.startswith("y")):
try:
os.remove(os.path.join(path, "installer.cfg"))
except FileNotFoundError:
pass
shutil.rmtree(os.path.join(path, "custom"),
ignore_errors=False)
shutil.rmtree(os.path.join(path, "mails"), ignore_errors=False)
shutil.rmtree(os.path.join(path, "databases"),
ignore_errors=False)
else:
utils.error("Error: backup directory not clean.")
return False
self.backup_path = path
pw = pwd.getpwnam("root")
for dir in ["custom/", "databases/"]:
utils.mkdir_safe(os.path.join(self.backup_path, dir),
stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3])
return True
def set_path(self):
"""Setup backup directory."""
if self.silent_backup:
if self.backup_path is None:
if self.config.has_option("backup", "default_path"):
path = self.config.get("backup", "default_path")
else:
path = DEFAULT_BACKUP_DIRECTORY
date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M")
path = os.path.join(path, f"backup_{date}")
self.validate_path(path)
else:
if not self.validate_path(self.backup_path):
utils.printcolor(
f"Path provided: {self.backup_path}", utils.BLUE)
sys.exit(1)
else:
user_value = None
while user_value == "" or user_value is None or not self.validate_path(user_value):
utils.printcolor(
"Enter backup path (it must be an empty directory)", utils.MAGENTA)
utils.printcolor("CTRL+C to cancel", utils.MAGENTA)
user_value = utils.user_input("-> ")
def config_file_backup(self):
utils.copy_file("installer.cfg", self.backup_path)
def mail_backup(self):
if self.nomail:
utils.printcolor(
"Skipping mail backup, no-mail argument provided", utils.MAGENTA)
return
utils.printcolor("Backing up mails", utils.MAGENTA)
home_path = self.config.get("dovecot", "home_dir")
if not os.path.exists(home_path) or os.path.isfile(home_path):
utils.error("Error backing up Email, provided path "
f" ({home_path}) seems not right...")
else:
dst = os.path.join(self.backup_path, "mails/")
if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(home_path, dst)
utils.printcolor("Mail backup complete!", utils.GREEN)
def custom_config_backup(self):
"""
Custom config :
- DKIM keys: {{keys_storage_dir}}
- Radicale collection (calendars, contacts): {{home_dir}}
- Amavis : /etc/amavis/conf.d/99-custom
- Postwhite : /etc/postwhite.conf
Feel free to suggest to add others!
"""
utils.printcolor(
"Backing up some custom configuration...", utils.MAGENTA)
custom_path = os.path.join(
self.backup_path, "custom")
# DKIM Key
if (self.config.has_option("opendkim", "enabled") and
self.config.getboolean("opendkim", "enabled")):
dkim_keys = self.config.get(
"opendkim", "keys_storage_dir", fallback="/var/lib/dkim")
if os.path.isdir(dkim_keys):
shutil.copytree(dkim_keys, os.path.join(custom_path, "dkim"))
utils.printcolor(
"DKIM keys saved!", utils.GREEN)
# Radicale Collections
if (self.config.has_option("radicale", "enabled") and
self.config.getboolean("radicale", "enabled")):
radicale_backup = os.path.join(self.config.get(
"radicale", "home_dir", fallback="/srv/radicale"), "collections")
if os.path.isdir(radicale_backup):
shutil.copytree(radicale_backup, os.path.join(
custom_path, "radicale"))
utils.printcolor("Radicale files saved", utils.GREEN)
# AMAVIS
if (self.config.has_option("amavis", "enabled") and
self.config.getboolean("amavis", "enabled")):
amavis_custom = "/etc/amavis/conf.d/99-custom"
if os.path.isfile(amavis_custom):
utils.copy_file(amavis_custom, custom_path)
utils.printcolor(
"Amavis custom configuration saved!", utils.GREEN)
# POSTWHITE
if (self.config.has_option("postwhite", "enabled") and
self.config.getboolean("postwhite", "enabled")):
postswhite_custom = "/etc/postwhite.conf"
if os.path.isfile(postswhite_custom):
utils.copy_file(postswhite_custom, custom_path)
utils.printcolor(
"Postwhite configuration saved!", utils.GREEN)
def database_backup(self):
"""Backing up databases"""
utils.printcolor("Backing up databases...", utils.MAGENTA)
self.database_dump("modoboa")
self.database_dump("amavis")
self.database_dump("spamassassin")
def database_dump(self, app_name):
dump_path = os.path.join(self.backup_path, "databases")
backend = database.get_backend(self.config)
if app_name == "modoboa" or (self.config.has_option(app_name, "enabled") and
self.config.getboolean(app_name, "enabled")):
dbname = self.config.get(app_name, "dbname")
dbuser = self.config.get(app_name, "dbuser")
dbpasswd = self.config.get(app_name, "dbpassword")
backend.dump_database(dbname, dbuser, dbpasswd,
os.path.join(dump_path, f"{app_name}.sql"))
def backup_completed(self):
utils.printcolor("Backup process done, your backup is available here:"
f"--> {self.backup_path}", utils.GREEN)
def run(self):
self.set_path()
self.config_file_backup()
self.mail_backup()
self.custom_config_backup()
self.database_backup()
self.backup_completed()

View File

@@ -31,12 +31,12 @@ class Installer:
self.app_config = dict(self.config.items(self.appname))
self.dbengine = self.config.get("database", "engine")
# Used to install system packages
self.db_driver = (
"pgsql" if self.dbengine == "postgres" else self.dbengine)
self.db_driver = "pgsql" if self.dbengine == "postgres" else self.dbengine
self.backend = database.get_backend(self.config)
self.dbhost = self.config.get("database", "host")
self.dbport = self.config.get(
"database", "port", fallback=self.backend.default_port)
"database", "port", fallback=self.backend.default_port
)
self._config_dir = None
if not self.with_db:
return
@@ -50,19 +50,19 @@ class Installer:
modoboa_version = python.get_package_version(
"modoboa",
self.config.get("modoboa", "venv_path"),
sudo_user=self.config.get("modoboa", "user")
)
sudo_user=self.config.get("modoboa", "user"),
)
condition = (
(int(modoboa_version[0]) == 2 and int(modoboa_version[1]) >= 2) or
int(modoboa_version[0]) > 2
)
int(modoboa_version[0]) == 2 and int(modoboa_version[1]) >= 2
) or int(modoboa_version[0]) > 2
return condition
@property
def config_dir(self):
"""Return main configuration directory."""
if self._config_dir is None and self.config.has_option(
self.appname, "config_dir"):
self.appname, "config_dir"
):
self._config_dir = self.config.get(self.appname, "config_dir")
return self._config_dir
@@ -73,11 +73,11 @@ class Installer:
def get_sql_schema_from_backup(self):
"""Retrieve a dump path from a previous backup."""
utils.printcolor(
f"Trying to restore {self.appname} database from backup.",
utils.MAGENTA
f"Trying to restore {self.appname} database from backup.", utils.MAGENTA
)
database_backup_path = os.path.join(
self.archive_path, f"databases/{self.appname}.sql")
self.archive_path, f"databases/{self.appname}.sql"
)
if os.path.isfile(database_backup_path):
utils.success(f"SQL dump found in backup for {self.appname}!")
return database_backup_path
@@ -86,8 +86,7 @@ class Installer:
def get_file_path(self, fname):
"""Return the absolute path of this file."""
return os.path.abspath(
os.path.join(
os.path.dirname(__file__), "files", self.appname, fname)
os.path.join(os.path.dirname(__file__), "files", self.appname, fname)
)
def setup_database(self):
@@ -102,8 +101,7 @@ class Installer:
if not schema:
schema = self.get_sql_schema_path()
if schema:
self.backend.load_sql_file(
self.dbname, self.dbuser, self.dbpasswd, schema)
self.backend.load_sql_file(self.dbname, self.dbuser, self.dbpasswd, schema)
def setup_user(self):
"""Setup a system user."""
@@ -119,8 +117,7 @@ class Installer:
def get_template_context(self):
"""Return context used for template rendering."""
context = {
"dbengine": (
"Pg" if self.dbengine == "postgres" else self.dbengine),
"dbengine": ("Pg" if self.dbengine == "postgres" else self.dbengine),
"dbhost": self.dbhost,
"dbport": self.dbport,
}
@@ -172,9 +169,10 @@ class Installer:
utils.copy_from_template(src, dst, context)
def backup(self, path):
self.base_backup_path = path
if self.with_db:
self._dump_database(path)
custom_backup_path = os.path.join(path, "custom")
custom_backup_path = os.path.join(path, "custom", self.appname)
self.custom_backup(custom_backup_path)
def custom_backup(self, path):
@@ -213,8 +211,7 @@ class Installer:
"""Create a new database dump for this app."""
target_dir = os.path.join(backup_path, "databases")
target_file = os.path.join(target_dir, f"{self.appname}.sql")
self.backend.dump_database(
self.dbname, self.dbuser, self.dbpasswd, target_file)
self.backend.dump_database(self.dbname, self.dbuser, self.dbpasswd, target_file)
def pre_run(self):
"""Tasks to execute before the installer starts."""

View File

@@ -16,10 +16,7 @@ class Opendkim(base.Installer):
"""OpenDKIM installer."""
appname = "opendkim"
packages = {
"deb": ["opendkim"],
"rpm": ["opendkim"]
}
packages = {"deb": ["opendkim"], "rpm": ["opendkim"]}
config_files = ["opendkim.conf", "opendkim.hosts"]
def get_packages(self):
@@ -36,30 +33,34 @@ class Opendkim(base.Installer):
"""Make sure config directory exists."""
user = self.config.get("opendkim", "user")
pw = pwd.getpwnam(user)
targets = [
[self.app_config["keys_storage_dir"], pw[2], pw[3]]
]
targets = [[self.app_config["keys_storage_dir"], pw[2], pw[3]]]
for target in targets:
if not os.path.exists(target[0]):
utils.mkdir(
target[0],
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH,
target[1], target[2]
stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH,
target[1],
target[2],
)
super().install_config_files()
def get_template_context(self):
"""Additional variables."""
context = super(Opendkim, self).get_template_context()
context.update({
"db_driver": self.db_driver,
"db_name": self.config.get("modoboa", "dbname"),
"db_user": self.app_config["dbuser"],
"db_password": self.app_config["dbpassword"],
"port": self.app_config["port"],
"user": self.app_config["user"]
})
context.update(
{
"db_driver": self.db_driver,
"db_name": self.config.get("modoboa", "dbname"),
"db_user": self.app_config["dbuser"],
"db_password": self.app_config["dbpassword"],
"port": self.app_config["port"],
"user": self.app_config["user"],
}
)
return context
def setup_database(self):
@@ -72,11 +73,14 @@ class Opendkim(base.Installer):
dbuser = self.config.get("modoboa", "dbuser")
dbpassword = self.config.get("modoboa", "dbpassword")
self.backend.load_sql_file(
dbname, dbuser, dbpassword,
self.get_file_path("dkim_view_{}.sql".format(self.dbengine))
dbname,
dbuser,
dbpassword,
self.get_file_path("dkim_view_{}.sql".format(self.dbengine)),
)
self.backend.grant_right_on_table(
dbname, "dkim", self.app_config["dbuser"], "SELECT")
dbname, "dkim", self.app_config["dbuser"], "SELECT"
)
def post_run(self):
"""Additional tasks.
@@ -90,31 +94,33 @@ class Opendkim(base.Installer):
else:
params_file = "/etc/opendkim.conf"
pattern = r"s/^(SOCKET=.*)/#\1/"
utils.exec_cmd(
"perl -pi -e '{}' {}".format(pattern, params_file))
utils.exec_cmd("perl -pi -e '{}' {}".format(pattern, params_file))
with open(params_file, "a") as f:
f.write('\n'.join([
"",
'SOCKET="inet:12345@localhost"',
]))
f.write(
"\n".join(
[
"",
'SOCKET="inet:12345@localhost"',
]
)
)
# Make sure opendkim is started after postgresql and mysql,
# respectively.
if (self.dbengine != "postgres" and package.backend.FORMAT == "deb"):
if self.dbengine != "postgres" and package.backend.FORMAT == "deb":
dbservice = "mysql.service"
elif (self.dbengine != "postgres" and package.backend.FORMAT != "deb"):
elif self.dbengine != "postgres" and package.backend.FORMAT != "deb":
dbservice = "mysqld.service"
else:
dbservice = "postgresql.service"
pattern = (
"s/^After=(.*)$/After=$1 {}/".format(dbservice))
pattern = "s/^After=(.*)$/After=$1 {}/".format(dbservice)
utils.exec_cmd(
"perl -pi -e '{}' /lib/systemd/system/opendkim.service".format(pattern))
"perl -pi -e '{}' /lib/systemd/system/opendkim.service".format(pattern)
)
def restore(self):
"""Restore keys."""
dkim_keys_backup = os.path.join(
self.archive_path, "custom/dkim")
dkim_keys_backup = os.path.join(self.archive_path, "custom/opendkim")
keys_storage_dir = self.app_config["keys_storage_dir"]
if os.path.isdir(dkim_keys_backup):
for file in os.listdir(dkim_keys_backup):
@@ -129,6 +135,5 @@ class Opendkim(base.Installer):
def custom_backup(self, path):
"""Backup DKIM keys."""
if os.path.isdir(self.app_config["keys_storage_dir"]):
shutil.copytree(self.app_config["keys_storage_dir"], os.path.join(path, "dkim"))
utils.printcolor(
"DKIM keys saved!", utils.GREEN)
shutil.copytree(self.app_config["keys_storage_dir"], path)
utils.printcolor("DKIM keys saved!", utils.GREEN)

View File

@@ -19,10 +19,7 @@ class Postwhite(base.Installer):
"crontab=/etc/cron.d/postwhite",
]
no_daemon = True
packages = {
"deb": ["bind9-host", "unzip"],
"rpm": ["bind-utils", "unzip"]
}
packages = {"deb": ["bind9-host", "unzip"], "rpm": ["bind-utils", "unzip"]}
def install_from_archive(self, repository, target_dir):
"""Install from an archive."""
@@ -36,8 +33,7 @@ class Postwhite(base.Installer):
if os.path.exists(archive_dir):
shutil.rmtree(archive_dir)
utils.exec_cmd("unzip master.zip", cwd=target_dir)
utils.exec_cmd(
"mv {name}-master {name}".format(name=app_name), cwd=target_dir)
utils.exec_cmd("mv {name}-master {name}".format(name=app_name), cwd=target_dir)
os.unlink(target)
return archive_dir
@@ -45,10 +41,8 @@ class Postwhite(base.Installer):
"""Additionnal tasks."""
install_dir = "/usr/local/bin"
self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir)
self.postw_dir = self.install_from_archive(
POSTWHITE_REPOSITORY, install_dir)
utils.copy_file(
os.path.join(self.postw_dir, "postwhite.conf"), self.config_dir)
self.postw_dir = self.install_from_archive(POSTWHITE_REPOSITORY, install_dir)
utils.copy_file(os.path.join(self.postw_dir, "postwhite.conf"), self.config_dir)
self.postw_bin = os.path.join(self.postw_dir, "postwhite")
utils.exec_cmd("{} /etc/postwhite.conf".format(self.postw_bin))
@@ -57,13 +51,13 @@ class Postwhite(base.Installer):
postswhite_custom = "/etc/postwhite.conf"
if os.path.isfile(postswhite_custom):
utils.copy_file(postswhite_custom, path)
utils.printcolor(
"Postwhite configuration saved!", utils.GREEN)
utils.printcolor("Postwhite configuration saved!", utils.GREEN)
def restore(self):
"""Restore config files."""
postwhite_backup_configuration = os.path.join(
self.archive_path, "custom/postwhite.conf")
self.archive_path, "custom/postwhite/postwhite.conf"
)
if os.path.isfile(postwhite_backup_configuration):
utils.copy_file(postwhite_backup_configuration, self.config_dir)
utils.success("postwhite.conf restored from backup")

View File

@@ -18,10 +18,7 @@ class Radicale(base.Installer):
appname = "radicale"
config_files = ["config"]
no_daemon = True
packages = {
"deb": ["supervisor"],
"rpm": ["supervisor"]
}
packages = {"deb": ["supervisor"], "rpm": ["supervisor"]}
with_user = True
def __init__(self, *args, **kwargs):
@@ -32,24 +29,25 @@ class Radicale(base.Installer):
def _setup_venv(self):
"""Prepare a dedicated virtualenv."""
python.setup_virtualenv(self.venv_path, sudo_user=self.user)
packages = [
"Radicale", "pytz", "radicale-modoboa-auth-oauth2"
]
packages = ["Radicale", "pytz", "radicale-modoboa-auth-oauth2"]
python.install_packages(packages, self.venv_path, sudo_user=self.user)
def get_template_context(self):
"""Additional variables."""
context = super().get_template_context()
oauth2_client_id, oauth2_client_secret = utils.create_oauth2_app(
"Radicale", "radicale", self.config)
"Radicale", "radicale", self.config
)
hostname = self.config.get("general", "hostname")
oauth2_introspection_url = (
f"https://{oauth2_client_id}:{oauth2_client_secret}"
f"@{hostname}/api/o/introspect/"
)
context.update({
"oauth2_introspection_url": oauth2_introspection_url,
})
context.update(
{
"oauth2_introspection_url": oauth2_introspection_url,
}
)
return context
def get_config_files(self):
@@ -67,16 +65,19 @@ class Radicale(base.Installer):
if not os.path.exists(self.config_dir):
utils.mkdir(
self.config_dir,
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH,
0, 0
stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH,
0,
0,
)
super().install_config_files()
def restore(self):
"""Restore collections."""
radicale_backup = os.path.join(
self.archive_path, "custom/radicale")
radicale_backup = os.path.join(self.archive_path, "custom/radicale")
if os.path.isdir(radicale_backup):
restore_target = os.path.join(self.home_dir, "collections")
if os.path.isdir(restore_target):
@@ -87,18 +88,17 @@ class Radicale(base.Installer):
def post_run(self):
"""Additional tasks."""
self._setup_venv()
daemon_name = (
"supervisor" if package.backend.FORMAT == "deb" else "supervisord"
)
daemon_name = "supervisor" if package.backend.FORMAT == "deb" else "supervisord"
system.enable_service(daemon_name)
utils.exec_cmd("service {} stop".format(daemon_name))
utils.exec_cmd("service {} start".format(daemon_name))
def custom_backup(self, path):
"""Backup collections."""
radicale_backup = os.path.join(self.config.get(
"radicale", "home_dir", fallback="/srv/radicale"), "collections")
radicale_backup = os.path.join(
self.config.get("radicale", "home_dir", fallback="/srv/radicale"),
"collections",
)
if os.path.isdir(radicale_backup):
shutil.copytree(radicale_backup, os.path.join(
path, "radicale"))
shutil.copytree(radicale_backup, path)
utils.printcolor("Radicale files saved", utils.GREEN)

View File

@@ -16,11 +16,7 @@ class Rspamd(base.Installer):
"""Rspamd installer."""
appname = "rspamd"
packages = {
"deb": [
"rspamd", "redis"
]
}
packages = {"deb": ["rspamd", "redis"]}
config_files = [
"local.d/arc.conf",
"local.d/dkim_signing.conf",
@@ -39,10 +35,9 @@ class Rspamd(base.Installer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.generate_password_condition = (
not self.upgrade or utils.user_input(
"Do you want to (re)generate rspamd password ? (y/N)").lower().startswith("y")
)
self.generate_password_condition = not self.upgrade or utils.user_input(
"Do you want to (re)generate rspamd password ? (y/N)"
).lower().startswith("y")
@property
def config_dir(self):
@@ -54,16 +49,20 @@ class Rspamd(base.Installer):
if debian_based_dist:
utils.mkdir_safe(
"/etc/apt/keyrings",
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH,
0, 0
stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH,
0,
0,
)
package.backend.add_custom_repository(
"rspamd",
"http://rspamd.com/apt-stable/",
"https://rspamd.com/apt-stable/gpg.key",
codename
codename,
)
package.backend.update()
@@ -73,16 +72,18 @@ class Rspamd(base.Installer):
"""Make sure config directory exists."""
user = self.config.get(self.appname, "user")
pw = pwd.getpwnam(user)
targets = [
[self.app_config["dkim_keys_storage_dir"], pw[2], pw[3]]
]
targets = [[self.app_config["dkim_keys_storage_dir"], pw[2], pw[3]]]
for target in targets:
if not os.path.exists(target[0]):
utils.mkdir(
target[0],
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH,
target[1], target[2]
stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH,
target[1],
target[2],
)
super().install_config_files()
@@ -101,16 +102,23 @@ class Rspamd(base.Installer):
def get_template_context(self):
_context = super().get_template_context()
_context["greylisting_disabled"] = "" if not self.app_config["greylisting"].lower() == "true" else "#"
_context["whitelist_auth_enabled"] = "" if self.app_config["whitelist_auth"].lower() == "true" else "#"
_context["greylisting_disabled"] = (
"" if not self.app_config["greylisting"].lower() == "true" else "#"
)
_context["whitelist_auth_enabled"] = (
"" if self.app_config["whitelist_auth"].lower() == "true" else "#"
)
if self.generate_password_condition:
code, controller_password = utils.exec_cmd(
r"rspamadm pw -p {}".format(self.app_config["password"]))
r"rspamadm pw -p {}".format(self.app_config["password"])
)
if code != 0:
utils.error("Error setting rspamd password. "
"Please make sure it is not 'q1' or 'q2'."
"Storing the password in plain. See"
"https://rspamd.com/doc/quickstart.html#setting-the-controller-password")
utils.error(
"Error setting rspamd password. "
"Please make sure it is not 'q1' or 'q2'."
"Storing the password in plain. See"
"https://rspamd.com/doc/quickstart.html#setting-the-controller-password"
)
_context["controller_password"] = self.app_config["password"]
else:
controller_password = controller_password.decode().replace("\n", "")
@@ -120,33 +128,31 @@ class Rspamd(base.Installer):
def post_run(self):
"""Additional tasks."""
user = self.config.get(self.appname, "user")
system.add_user_to_group(
self.config.get("modoboa", "user"),
user
)
system.add_user_to_group(self.config.get("modoboa", "user"), user)
if self.config.getboolean("clamav", "enabled"):
install("clamav", self.config, self.upgrade, self.archive_path)
def custom_backup(self, path):
"""Backup custom configuration if any."""
custom_config_dir = os.path.join(self.config_dir,
"/local.d/")
custom_backup_dir = os.path.join(path, "/rspamd/")
local_files = [f for f in os.listdir(custom_config_dir)
if os.path.isfile(custom_config_dir, f)
]
custom_config_dir = os.path.join(self.config_dir, "local.d/")
local_files = [
os.path.join(custom_config_dir, f)
for f in os.listdir(custom_config_dir)
if os.path.isfile(os.path.join(custom_config_dir, f))
]
for file in local_files:
utils.copy_file(file, custom_backup_dir)
print(file)
utils.copy_file(file, path)
if len(local_files) != 0:
utils.success("Rspamd custom configuration saved!")
def restore(self):
"""Restore custom config files."""
custom_config_dir = os.path.join(self.config_dir,
"/local.d/")
custom_config_dir = os.path.join(self.config_dir, "/local.d/")
custom_backup_dir = os.path.join(self.archive_path, "/rspamd/")
backed_up_files = [
f for f in os.listdir(custom_backup_dir)
f
for f in os.listdir(custom_backup_dir)
if os.path.isfile(custom_backup_dir, f)
]
for f in backed_up_files: