Added Radicale setup. (#194)

* Added Radicale setup.

see #193

* Fixed setup on CentOS.
This commit is contained in:
Antoine Nguyen
2018-03-31 14:12:53 +02:00
committed by GitHub
parent b68de9e139
commit d813039270
10 changed files with 334 additions and 7 deletions

View File

@@ -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",
}
]
},
]

View File

@@ -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
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):
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)

View File

@@ -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

View File

@@ -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
}

View 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 = *

View 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

View File

@@ -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"]:

View File

@@ -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)

View 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))

1
run.py
View File

@@ -93,6 +93,7 @@ def main(input_args):
scripts.install("amavis", config)
scripts.install("modoboa", config)
scripts.install("automx", config)
scripts.install("radicale", config)
scripts.install("uwsgi", config)
scripts.install("nginx", config)
scripts.install("postfix", config)