init radicale role

This commit is contained in:
vincent 2019-10-08 20:18:14 +02:00
commit 8e985a84c0
8 changed files with 419 additions and 0 deletions

33
README.md Normal file
View File

@ -0,0 +1,33 @@
# Anarcho-Tech NYC: Radicale [![Build Status](https://travis-ci.org/AnarchoTechNYC/ansible-role-radicale.svg?branch=master)](https://travis-ci.org/AnarchoTechNYC/ansible-role-radicale)
An [Ansible role](https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html) for installing a [Radicale](http://radicale.org/) server. Notably, this role has been tested with [Raspbian](https://www.raspbian.org/) on [Raspberry Pi](https://www.raspberrypi.org/) hardware. This role's purpose is to make it simple to install a CalDAV and CardDAV server.
# Configuring Radicale
To configure your Radicale server instance, use the `radicale_config` dictionary. The keys in this dictionary map nearly one-to-one to the configuration directives described in [Radicale's Configuration documentation page](https://radicale.org/configuration/). Configuration directive groups are their own dictionaries, and directives that can accept more than one value are specified as a list.
Some examples may prove helpful:
1. Simple Radicale server with default for all values:
```yaml
radicale_config:
```
1. Simple Radicale server bound to the local host only and listening on the alternative HTTP port:
```yaml
radicale_config:
server:
hosts:
- addr: 127.0.0.1
port: 8080
```
See the comments in the [`defaults/main.yaml`](defaults/main.yaml) file for additional details.
# Adding or removing Radicale user accounts
The `radicale_users` variable is a list containing dictionaries for each user account. Each user account dictionary in the list can have the following keys:
* `name`: The name of the user account. This key is required.
* `password`: The password for this user account. It is recommended to encrypt this value with Ansible Vault. If this is omitted, the `bcrypt_hash` key is required.
* `bcrypt_hash`: Instead of supplying a password, you can supply a bcrypt hash of the password in `passlib` format. If this is omitted, the `password` key is required.
* `state`: Whether the user should exist (`present`) or not (`absent`). This key is optional.

65
defaults/main.yaml Normal file
View File

@ -0,0 +1,65 @@
---
radicale_server_username: radicale
radicale_server_home_dir: "/var/lib/{{ radicale_server_username }}"
radicale_service_state: started
# See https://radicale.org/configuration/
radicale_config:
server:
hosts:
- addr: 0.0.0.0
port: 5232
#daemon: true
#pid: /var/run/radicale/radicale.pid
#max_connections: 20
#max_connections: 100000000
#timeout: 30
dns_lookup: false
#realm: Radicale Realm
# Consider TLS directives carefully before activating them.
#ssl: true
#certificate: "/etc/ssl/radicale.cert.pem"
#key: "/etc/ssl/radicale.key.pem"
#certificate_authority:
#protocol: PROTOCOL_TLSv1_2
#ciphers:
#encoding:
#request: utf-8
#stock: utf-8
auth:
type: htpasswd
htpasswd_filename: "{{ radicale_server_home_dir }}/users.htpasswd"
htpasswd_encryption: bcrypt
delay: 1
rights:
type: from_file
file: "{{ radicale_server_home_dir }}/rights.conf"
storage:
type: multifilesystem
filesystem_folder: "{{ radicale_server_home_dir }}/collections"
filesystem_locking: true
filesystem_fsync: true
# For an example of the `hook` directive in use, see
# http://radicale.org/versioning/
#hook:
#web:
#type: internal
#headers:
#X-Extra-HTTP-Header: foo
#X-Another-Header: bar
#logging:
#debug: false
#mask_passwords: true
#full_environment: false
#config: "/etc/radicale/log.conf"
# List of Radicale user information as a dictionary.
radicale_users:
- name: admin # The username.
password: admin # Their password. This should probably be vault-encrypted.
# As an alternative to a password, you can specify a bcrypt hash.
# Create this hash using the standard `htpasswd` utility, then
# paste it here. This method allows a user to generate a password
# for their account themselves, and then send you the hash rather
# than the plaintext.
#bcrypt_hash: "$2y$05$t31SnKFWj9UcMr5Y96cl3uBFkdhelqkZn77TnquIeVb9sriEByUPK"

29
files/rights.conf Normal file
View File

@ -0,0 +1,29 @@
################################################
# Radicale user rights configuration file. #
# #
# See http://radicale.org/rights/ for details. #
################################################
## The user "admin" can read and write any collection.
#[admin]
#user = admin
#collection = .*
#permission = rw
# Authenticated users can list (discover) their own collections.
[owner-discover]
user = .+
collection = ^%(login)s$
permission = rw
# Authenticated users can read and write their own collections.
[owner-write]
user = .+
collection = ^%(login)s/.*
permission = rw
# Everyone can read the root collection
[read]
user = .*
collection =
permission = r

9
handlers/main.yaml Normal file
View File

@ -0,0 +1,9 @@
---
- name: Reload systemd.
systemd:
daemon_reload: true
- name: Restart Radicale.
service:
name: radicale
state: restarted

22
meta/main.yaml Normal file
View File

@ -0,0 +1,22 @@
---
dependencies: []
galaxy_info:
role_name: radicale
author: AnarchoTechNYC
description: Provision a Radicale CalDAV/CardDAV server in a number of small- to medium-sized deployments.
company: Eat The Rich, Inc.
license: AGPL-3.0-or-later # SPDX tag from https://spdx.org/licenses/
min_ansible_version: 2.7
platforms:
- name: Raspbian
versions:
- all
- name: Debian
versions:
- 9
galaxy_tags:
- CalDAV
- CardDAV
- calendar
- contacts
- addressbook

114
tasks/main.yaml Normal file
View File

@ -0,0 +1,114 @@
---
- name: Install Radicale package dependencies.
apt:
name: "{{ packages }}"
vars:
packages:
- python3
- python3-pip
- python3-setuptools
- apache2-utils
# These three are for Ansible itself to run on the managed host.
- python-setuptools
- python-passlib
- python-bcrypt
- name: Install Radicale Python dependencies.
pip:
executable: pip3 # Radicale requires Python 3.3 or greater.
name: "{{ item }}"
state: present
loop:
- passlib
- bcrypt
- name: Create Radicale system user.
user:
name: "{{ radicale_server_username }}"
system: true
home: "{{ radicale_server_home_dir }}"
shell: "/bin/false"
state: present
- name: Install Radicale.
pip:
executable: pip3 # Radicale requires Python 3.3 or greater.
name: radicale
state: present
- name: Create Radicale configuration directory.
file:
path: /etc/radicale
state: directory
- name: Write Radicale configuration file.
template:
src: etc/radicale/config.j2
dest: /etc/radicale/config
notify:
- Restart Radicale.
- name: Write Radicale user rights configuration.
copy:
src: rights.conf
dest: "{{ radicale_server_home_dir }}/rights.conf"
owner: "{{ radicale_server_username }}"
group: "{{ radicale_server_username }}"
mode: "400"
notify:
- Restart Radicale.
- name: Ensure Radicale user accounts are defined.
when:
- radicale_config.auth is defined
- radicale_config.auth.type is defined
- radicale_config.auth.type == "htpasswd"
block:
- name: Ensure Radicale htpasswd file exists.
file:
path: "{{ radicale_config.auth.htpasswd_filename | default('/var/lib/radicale/users.htpasswd') }}"
state: touch
access_time: preserve
modification_time: preserve
- name: Set Radicale user with password.
when: item.password is defined
no_log: true
htpasswd:
path: "{{ radicale_config.auth.htpasswd_filename | default('/var/lib/radicale/users.htpasswd') }}"
name: "{{ item.name }}"
password: "{{ item.password }}"
state: "{{ item.state | default('present') }}"
crypt_scheme: "bcrypt"
mode: "600"
owner: "{{ radicale_server_username }}"
group: "{{ radicale_server_username }}"
loop: "{{ radicale_users }}"
- name: Set Radicale user with password hash.
when: item.bcrypt_hash is defined
no_log: true
lineinfile:
path: "{{ radicale_config.auth.htpasswd_filename | default('/var/lib/radicale/users.htpasswd') }}"
line: "{{ item.name }}:{{ item.bcrypt_hash }}"
state: "{{ item.state | default('present') }}"
mode: "600"
owner: "{{ radicale_server_username }}"
group: "{{ radicale_server_username }}"
loop: "{{ radicale_users }}"
- name: Create systemd service unit.
template:
src: radicale.service.j2
dest: /etc/systemd/system/radicale.service
# TODO:
#validate: "systemd-analyze verify %s"
notify:
- Reload systemd.
- Restart Radicale.
- name: Start and enable Radicale service.
service:
name: radicale
state: "{{ radicale_service_state }}"
enabled: true

View File

@ -0,0 +1,141 @@
# Radicale configuration file.
#
# See http://radicale.org/configuration/ for more details and
# descriptions of additional available configuration directives.
{% if radicale_config.server is defined %}
[server]
{% if radicale_config.server.hosts is defined %}
hosts = {% for host in radicale_config.server.hosts %}
{{ host.addr | default('0.0.0.0') }}:{{ host.port | default('5232') }}{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %}
{% if radicale_config.server.daemon is defined %}
daemon = {{ radicale_config.server.daemon }}
{% endif %}
{% if radicale_config.server.pid is defined %}
pid = {{ radicale_config.server.pid }}
{% endif %}
{% if radicale_config.server.max_connections is defined %}
max_connections = {{ radicale_config.server.max_connections | default(20) | int }}
{% endif %}
{% if radicale_config.server.max_content_length is defined %}
max_content_length = {{ radicale_config.server.max_content_length | default(100000000) | int }}
{% endif %}
{% if radicale_config.server.timeout is defined %}
timeout = {{ radicale_config.server.timeout | default(30) | int }}
{% endif %}
{% if radicale_config.server.dns_lookup is defined %}
dns_lookup = {{ radicale_config.server.dns_lookup | default(true) }}
{% endif %}
{% if radicale_config.server.realm is defined %}
realm = {{ radicale_config.server.realm | default('Radicale - Password Required') }}
{% endif %}
{% if radicale_config.server.ssl is defined %}
ssl = {{ radicale_config.server.ssl | default('false') }}
{% endif %}
{% if radicale_config.server.certificate is defined %}
certificate = {{ radicale_config.server.certificate | default('/etc/ssl/radicale.cert.pem') }}
{% endif %}
{% if radicale_config.server.key is defined %}
key = {{ radicale_config.server.key | default('/etc/ssl/radicale.key.pem') }}
{% endif %}
{% if radicale_config.server.certificate_authority is defined %}
certificate_authority = {{ radicale_config.server.certificate_authority }}
{% endif %}
{% if radicale_config.server.protocol is defined %}
protocol = {{ radicale_config.server.protocol | default('PROTOCOL_TLSv1_2') }}
{% endif %}
{% if radicale_config.server.ciphers is defined %}
ciphers = {{ radicale_config.server.ciphers }}
{% endif %}
{% endif %}{# END if radicale_config.server is defined #}
{% if radicale_config.encoding is defined %}
[encoding]
{% if radicale_config.encoding.request is defined %}
request = {{ radicale_config.encoding.request | default('utf-8') }}
{% endif %}
{% if radicale_config.encoding.stock is defined %}
stock = {{ radicale_config.encoding.stock | default('utf-8') }}
{% endif %}
{% endif %}{# END if radicale_config.encoding is defined #}
{% if radicale_config.auth is defined %}
[auth]
{% if radicale_config.auth.type is defined %}
type = {{ radicale_config.auth.type }}
{% endif %}
{% if radicale_config.auth.htpasswd_filename is defined %}
htpasswd_filename = {{ radicale_config.auth.htpasswd_filename }}
{% endif %}
{% if radicale_config.auth.htpasswd_encryption is defined %}
htpasswd_encryption = {{ radicale_config.auth.htpasswd_encryption }}
{% endif %}
{% if radicale_config.auth.delay is defined %}
delay = {{ radicale_config.auth.delay | default('1') }}
{% endif %}
{% endif %}{# END if radicale_config.auth is defined #}
{% if radicale_config.rights is defined %}
[rights]
{% if radicale_config.rights.type is defined %}
type = {{ radicale_config.rights.type | default('owner_only') }}
{% endif %}
{% if radicale_config.rights is defined and radicale_config.rights.type == "from_file" %}
file = {{ radicale_config.rights.file }}
{% endif %}
{% endif %}{# END if radicale_config.rights is defined #}
{% if radicale_config.storage is defined %}
[storage]
{% if radicale_config.storage.type is defined %}
type = {{ radicale_config.storage.type | default('multifilesystem') }}
{% endif %}
{% if radicale_config.storage.filesystem_folder is defined %}
filesystem_folder = {{ radicale_config.storage.filesystem_folder | default('/var/lib/radicale/collections') }}
{% endif %}
{% if radicale_config.storage.filesystem_locking is defined %}
filesystem_locking = {{ radicale_config.storage.filesystem_locking | default(true) }}
{% endif %}
{% if radicale_config.storage.max_sync_token_age is defined %}
max_sync_token_age = {{ radicale_config.storage.max_sync_token_age | default(2592000) | int }}
{% endif %}
{% if radicale_config.storage.filesystem_fsync is defined %}
filesystem_fsync = {{ radicale_config.storage.filesystem_fsync | default(true) }}
{% endif %}
{% if radicale_config.storage.hook is defined %}
hook = {{ radicale_config.storage.hook }}
{% endif %}
{% endif %}{# END if radicale_config.storage is defined #}
{% if radicale_config.web is defined %}
[web]
{% if radicale_config.web.type is defined %}
type = {{ radicale_config.web.type | default('internal') }}
{% endif %}
{% endif %}{# END if radicale_config.web is defined #}
{% if radicale_config.headers is defined %}
[headers]
{% for k in radicale_config.headers %}
{{ k }} = {{ radicale_config.headers[k] }}
{% endfor %}
{% endif %}{# END if radicale_config.headers is defined #}
{% if radicale_config.logging is defined %}
[logging]
{% if radicale_config.logging.debug is defined %}
debug = {{ radicale_config.logging.debug | default(false) }}
{% endif %}
{% if radicale_config.logging.mask_passwords is defined %}
mask_passwords = {{ radicale_config.logging.mask_passwords | default(true) }}
{% endif %}
{% if radicale_config.logging.full_environment %}
full_environment = {{ radicale_config.logging.full_environment | default(false) }}
{% endif %}
{% if radicale_config.logging.config %}
config = {{ radicale_config.logging.config }}
{% endif %}
{% endif %}{# END if radicale_config.logging is defined #}

6
tests/test.yaml Normal file
View File

@ -0,0 +1,6 @@
# This Ansible playbook runs test plays to ensure the role works.
---
- name: Test role.
hosts: localhost
roles:
- ansible-role-radicale