Added Radicale setup. (#194)
* Added Radicale setup. see #193 * Fixed setup on CentOS.
This commit is contained in:
@@ -158,7 +158,9 @@ ConfigDictTemplate = [
|
||||
"default": (
|
||||
"modoboa-amavis modoboa-pdfcredentials "
|
||||
"modoboa-postfix-autoreply modoboa-sievefilters "
|
||||
"modoboa-stats modoboa-webmail modoboa-contacts"),
|
||||
"modoboa-stats modoboa-webmail modoboa-contacts "
|
||||
"modoboa-radicale"
|
||||
),
|
||||
},
|
||||
{
|
||||
"option": "devmode",
|
||||
@@ -270,6 +272,10 @@ ConfigDictTemplate = [
|
||||
"option": "postmaster_address",
|
||||
"default": "postmaster@%(domain)s",
|
||||
},
|
||||
{
|
||||
"option": "radicale_auth_socket_path",
|
||||
"default": "/var/run/dovecot/auth-radicale"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -372,4 +378,29 @@ ConfigDictTemplate = [
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "radicale",
|
||||
"values": [
|
||||
{
|
||||
"option": "enabled",
|
||||
"default": "true",
|
||||
},
|
||||
{
|
||||
"option": "user",
|
||||
"default": "radicale",
|
||||
},
|
||||
{
|
||||
"option": "config_dir",
|
||||
"default": "/etc/radicale",
|
||||
},
|
||||
{
|
||||
"option": "home_dir",
|
||||
"default": "/srv/radicale",
|
||||
},
|
||||
{
|
||||
"option": "venv_path",
|
||||
"default": "%(home_dir)s/env",
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -36,14 +36,35 @@ def install_packages(names, venv=None, upgrade=False, **kwargs):
|
||||
utils.exec_cmd(cmd, **kwargs)
|
||||
|
||||
|
||||
def setup_virtualenv(path, sudo_user=None):
|
||||
def install_package_from_repository(name, url, vcs="git", venv=None, **kwargs):
|
||||
"""Install a Python package from its repository."""
|
||||
if vcs == "git":
|
||||
package.backend.install("git")
|
||||
cmd = "{} install -e {}+{}#egg={}".format(
|
||||
get_pip_path(venv), vcs, url, name)
|
||||
utils.exec_cmd(cmd, **kwargs)
|
||||
|
||||
|
||||
def setup_virtualenv(path, sudo_user=None, python_version=2):
|
||||
"""Install a virtualenv if needed."""
|
||||
if os.path.exists(path):
|
||||
return
|
||||
packages = ["python-virtualenv"]
|
||||
if utils.dist_name() == "debian":
|
||||
packages.append("virtualenv")
|
||||
if python_version == 2:
|
||||
python_binary = "python"
|
||||
packages = ["python-virtualenv"]
|
||||
if utils.dist_name() == "debian":
|
||||
packages.append("virtualenv")
|
||||
else:
|
||||
if utils.dist_name().startswith("centos"):
|
||||
python_binary = "python36"
|
||||
packages = ["python36"]
|
||||
else:
|
||||
python_binary = "python3"
|
||||
packages = ["python3-venv"]
|
||||
package.backend.install_many(packages)
|
||||
with utils.settings(sudo_user=sudo_user):
|
||||
utils.exec_cmd("virtualenv {}".format(path))
|
||||
if python_version == 2:
|
||||
utils.exec_cmd("virtualenv {}".format(path))
|
||||
else:
|
||||
utils.exec_cmd("{} -m venv {}".format(python_binary, path))
|
||||
install_package("pip", venv=path, upgrade=True)
|
||||
|
||||
@@ -77,7 +77,8 @@ class Dovecot(base.Installer):
|
||||
"modoboa_dbuser": self.config.get("modoboa", "dbuser"),
|
||||
"modoboa_dbpassword": self.config.get("modoboa", "dbpassword"),
|
||||
"protocols": protocols,
|
||||
"ssl_protocols": ssl_protocols
|
||||
"ssl_protocols": ssl_protocols,
|
||||
"radicale_user": self.config.get("radicale", "user")
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
@@ -116,6 +116,13 @@ service auth {
|
||||
group = postfix
|
||||
}
|
||||
|
||||
# Radicale auth
|
||||
%{radicale_enabled}unix_listener %{radicale_auth_socket_path} {
|
||||
%{radicale_enabled} mode = 0666
|
||||
%{radicale_enabled} user = %{radicale_user}
|
||||
%{radicale_enabled} group = %{radicale_user}
|
||||
%{radicale_enabled}}
|
||||
|
||||
# Auth process is run as this user.
|
||||
#user = $default_internal_user
|
||||
}
|
||||
|
||||
161
modoboa_installer/scripts/files/radicale/config.tpl
Normal file
161
modoboa_installer/scripts/files/radicale/config.tpl
Normal file
@@ -0,0 +1,161 @@
|
||||
# -*- mode: conf -*-
|
||||
# vim:ft=cfg
|
||||
|
||||
# Config file for Radicale - A simple calendar server
|
||||
#
|
||||
# Place it into /etc/radicale/config (global)
|
||||
# or ~/.config/radicale/config (user)
|
||||
#
|
||||
# The current values are the default ones
|
||||
|
||||
|
||||
[server]
|
||||
|
||||
# CalDAV server hostnames separated by a comma
|
||||
# IPv4 syntax: address:port
|
||||
# IPv6 syntax: [address]:port
|
||||
# For example: 0.0.0.0:9999, [::]:9999
|
||||
#hosts = 127.0.0.1:5232
|
||||
|
||||
# Daemon flag
|
||||
#daemon = False
|
||||
|
||||
# File storing the PID in daemon mode
|
||||
#pid =
|
||||
|
||||
# Max parallel connections
|
||||
#max_connections = 20
|
||||
|
||||
# Max size of request body (bytes)
|
||||
#max_content_length = 10000000
|
||||
|
||||
# Socket timeout (seconds)
|
||||
#timeout = 10
|
||||
|
||||
# SSL flag, enable HTTPS protocol
|
||||
#ssl = False
|
||||
|
||||
# SSL certificate path
|
||||
#certificate = /etc/ssl/radicale.cert.pem
|
||||
|
||||
# SSL private key
|
||||
#key = /etc/ssl/radicale.key.pem
|
||||
|
||||
# CA certificate for validating clients. This can be used to secure
|
||||
# TCP traffic between Radicale and a reverse proxy
|
||||
#certificate_authority =
|
||||
|
||||
# SSL Protocol used. See python's ssl module for available values
|
||||
#protocol = PROTOCOL_TLSv1_2
|
||||
|
||||
# Available ciphers. See python's ssl module for available ciphers
|
||||
#ciphers =
|
||||
|
||||
# Reverse DNS to resolve client address in logs
|
||||
#dns_lookup = True
|
||||
|
||||
# Message displayed in the client when a password is needed
|
||||
#realm = Radicale - Password Required
|
||||
|
||||
|
||||
[encoding]
|
||||
|
||||
# Encoding for responding requests
|
||||
#request = utf-8
|
||||
|
||||
# Encoding for storing local collections
|
||||
#stock = utf-8
|
||||
|
||||
|
||||
[auth]
|
||||
|
||||
# Authentication method
|
||||
# Value: none | htpasswd | remote_user | http_x_remote_user
|
||||
type = radicale_dovecot_auth
|
||||
|
||||
# Htpasswd filename
|
||||
# htpasswd_filename = users
|
||||
|
||||
# Htpasswd encryption method
|
||||
# Value: plain | sha1 | ssha | crypt | bcrypt | md5
|
||||
# Only bcrypt can be considered secure.
|
||||
# bcrypt and md5 require the passlib library to be installed.
|
||||
# htpasswd_encryption = plain
|
||||
|
||||
# Incorrect authentication delay (seconds)
|
||||
#delay = 1
|
||||
|
||||
auth_socket = %{radicale_auth_socket_path}
|
||||
|
||||
|
||||
[rights]
|
||||
|
||||
# Rights backend
|
||||
# Value: none | authenticated | owner_only | owner_write | from_file
|
||||
type = from_file
|
||||
|
||||
# File for rights management from_file
|
||||
file = %{config_dir}/rights
|
||||
|
||||
|
||||
[storage]
|
||||
|
||||
# Storage backend
|
||||
# Value: multifilesystem
|
||||
type = radicale_storage_by_index
|
||||
radicale_storage_by_index_fields = dtstart, dtend, uid, summary
|
||||
|
||||
# Folder for storing local collections, created if not present
|
||||
filesystem_folder = %{home_dir}/collections
|
||||
|
||||
# Lock the storage. Never start multiple instances of Radicale or edit the
|
||||
# storage externally while Radicale is running if disabled.
|
||||
#filesystem_locking = True
|
||||
|
||||
# Sync all changes to disk during requests. (This can impair performance.)
|
||||
# Disabling it increases the risk of data loss, when the system crashes or
|
||||
# power fails!
|
||||
#filesystem_fsync = True
|
||||
|
||||
# Delete sync token that are older (seconds)
|
||||
#max_sync_token_age = 2592000
|
||||
|
||||
# Close the lock file when no more clients are waiting.
|
||||
# This option is not very useful in general, but on Windows files that are
|
||||
# opened cannot be deleted.
|
||||
#filesystem_close_lock_file = False
|
||||
|
||||
# Command that is run after changes to storage
|
||||
# Example: ([ -d .git ] || git init) && git add -A && (git diff --cached --quiet || git commit -m "Changes by "%%(user)s)
|
||||
#hook =
|
||||
|
||||
|
||||
[web]
|
||||
|
||||
# Web interface backend
|
||||
# Value: none | internal
|
||||
type = none
|
||||
|
||||
|
||||
[logging]
|
||||
|
||||
# Logging configuration file
|
||||
# If no config is given, simple information is printed on the standard output
|
||||
# For more information about the syntax of the configuration file, see:
|
||||
# http://docs.python.org/library/logging.config.html
|
||||
#config = /etc/radicale/logging
|
||||
|
||||
# Set the default logging level to debug
|
||||
debug = False
|
||||
|
||||
# Store all environment variables (including those set in the shell)
|
||||
#full_environment = False
|
||||
|
||||
# Don't include passwords in logs
|
||||
#mask_passwords = True
|
||||
|
||||
|
||||
[headers]
|
||||
|
||||
# Additional HTTP headers
|
||||
#Access-Control-Allow-Origin = *
|
||||
8
modoboa_installer/scripts/files/radicale/supervisor.tpl
Normal file
8
modoboa_installer/scripts/files/radicale/supervisor.tpl
Normal file
@@ -0,0 +1,8 @@
|
||||
[program:radicale]
|
||||
autostart=true
|
||||
autorestart=true
|
||||
command=%{venv_path}/bin/radicale -C %{config_dir}/config
|
||||
directory=%{home_dir}
|
||||
redirect_stderr=true
|
||||
user=%{user}
|
||||
numprocs=1
|
||||
@@ -50,6 +50,9 @@ class Modoboa(base.Installer):
|
||||
self.amavis_enabled = True
|
||||
else:
|
||||
self.extensions.remove("modoboa-amavis")
|
||||
if "modoboa-radicale" in self.extensions:
|
||||
if not self.config.getboolean("radicale", "enabled"):
|
||||
self.extensions.remove("modoboa-radicale")
|
||||
|
||||
def is_extension_ok_for_version(self, extension, version):
|
||||
"""Check if extension can be installed with this modo version."""
|
||||
@@ -200,6 +203,12 @@ class Modoboa(base.Installer):
|
||||
},
|
||||
"modoboa_pdfcredentials": {
|
||||
"storage_dir": pdf_storage_dir
|
||||
},
|
||||
"modoboa_radicale": {
|
||||
"server_location": "https://{}/radicale/".format(
|
||||
self.config.get("general", "hostname")),
|
||||
"rights_file_path": "{}/rights".format(
|
||||
self.config.get("radicale", "config_dir"))
|
||||
}
|
||||
}
|
||||
for path in ["/var/log/maillog", "/var/log/mail.log"]:
|
||||
|
||||
@@ -71,6 +71,15 @@ class Nginx(base.Installer):
|
||||
include uwsgi_params;
|
||||
uwsgi_pass automx;
|
||||
}
|
||||
"""
|
||||
if self.config.get("radicale", "enabled"):
|
||||
extra_modoboa_config += """
|
||||
location /radicale/ {
|
||||
proxy_pass http://localhost:5232/; # The / is important!
|
||||
proxy_set_header X-Script-Name /radicale;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass_header Authorization;
|
||||
}
|
||||
"""
|
||||
self._setup_config(
|
||||
"modoboa", extra_config=extra_modoboa_config)
|
||||
|
||||
79
modoboa_installer/scripts/radicale.py
Normal file
79
modoboa_installer/scripts/radicale.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Radicale related tasks."""
|
||||
|
||||
import os
|
||||
import stat
|
||||
|
||||
from .. import package
|
||||
from .. import python
|
||||
from .. import utils
|
||||
|
||||
from . import base
|
||||
|
||||
|
||||
class Radicale(base.Installer):
|
||||
"""Radicale installation."""
|
||||
|
||||
appname = "radicale"
|
||||
config_files = ["config"]
|
||||
no_daemon = True
|
||||
packages = {
|
||||
"deb": ["supervisor"],
|
||||
"rpm": ["supervisor"]
|
||||
}
|
||||
with_user = True
|
||||
|
||||
def __init__(self, config):
|
||||
"""Get configuration."""
|
||||
super(Radicale, self).__init__(config)
|
||||
self.venv_path = config.get("radicale", "venv_path")
|
||||
|
||||
def _setup_venv(self):
|
||||
"""Prepare a dedicated virtualenv."""
|
||||
python.setup_virtualenv(
|
||||
self.venv_path, sudo_user=self.user, python_version=3)
|
||||
packages = ["Radicale", "radicale-dovecot-auth", "pytz"]
|
||||
python.install_packages(packages, self.venv_path, sudo_user=self.user)
|
||||
python.install_package_from_repository(
|
||||
"radicale-storage-by-index",
|
||||
"https://github.com/tonioo/RadicaleStorageByIndex",
|
||||
venv=self.venv_path, sudo_user=self.user)
|
||||
|
||||
def get_template_context(self):
|
||||
"""Additional variables."""
|
||||
context = super(Radicale, self).get_template_context()
|
||||
radicale_auth_socket_path = self.config.get(
|
||||
"dovecot", "radicale_auth_socket_path")
|
||||
context.update({
|
||||
"radicale_auth_socket_path": radicale_auth_socket_path
|
||||
})
|
||||
return context
|
||||
|
||||
def get_config_files(self):
|
||||
"""Return appropriate path."""
|
||||
config_files = super(Radicale, self).get_config_files()
|
||||
if package.backend.FORMAT == "deb":
|
||||
path = "supervisor=/etc/supervisor/conf.d/radicale.conf"
|
||||
else:
|
||||
path = "supervisor=/etc/supervisord.d/radicale.ini"
|
||||
config_files.append(path)
|
||||
return config_files
|
||||
|
||||
def install_config_files(self):
|
||||
"""Make sure config directory exists."""
|
||||
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
|
||||
)
|
||||
super(Radicale, self).install_config_files()
|
||||
|
||||
def post_run(self):
|
||||
"""Additional tasks."""
|
||||
self._setup_venv()
|
||||
daemon_name = (
|
||||
"supervisor" if package.backend.FORMAT == "deb" else "supervisord"
|
||||
)
|
||||
utils.exec_cmd("service {} stop".format(daemon_name))
|
||||
utils.exec_cmd("service {} start".format(daemon_name))
|
||||
Reference in New Issue
Block a user