init radicale role
This commit is contained in:
commit
8e985a84c0
33
README.md
Normal file
33
README.md
Normal 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
65
defaults/main.yaml
Normal 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
29
files/rights.conf
Normal 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
9
handlers/main.yaml
Normal 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
22
meta/main.yaml
Normal 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
114
tasks/main.yaml
Normal 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
|
141
templates/etc/radicale/config.j2
Normal file
141
templates/etc/radicale/config.j2
Normal 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
6
tests/test.yaml
Normal 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
|
Loading…
Reference in New Issue
Block a user