Initial overhaul of documentation

This commit is contained in:
Unrud 2020-04-11 16:14:41 +02:00
parent d6d5e512dc
commit 27ac0ed025

View File

@ -21,7 +21,7 @@ Radicale is really easy to install and works out-of-the-box.
```bash ```bash
$ python3 -m pip install --upgrade radicale $ python3 -m pip install --upgrade radicale
$ python3 -m radicale --config "" --storage-filesystem-folder=~/.var/lib/radicale/collections $ python3 -m radicale --storage-filesystem-folder=~/.var/lib/radicale/collections
``` ```
When your server is launched, you can check that everything's OK by going When your server is launched, you can check that everything's OK by going
@ -60,7 +60,6 @@ fit your needs.
- [What can I configure?](#documentation/configuration) - [What can I configure?](#documentation/configuration)
- [Authentication & Rights.](#documentation/authentication-and-rights) - [Authentication & Rights.](#documentation/authentication-and-rights)
- [Storage.](#documentation/storage) - [Storage.](#documentation/storage)
- [Logging.](#documentation/logging)
### Hack ### Hack
@ -97,7 +96,7 @@ Then open a console and type:
# Run the following command as root or # Run the following command as root or
# add the --user argument to only install for the current user # add the --user argument to only install for the current user
$ python3 -m pip install --upgrade radicale $ python3 -m pip install --upgrade radicale
$ python3 -m radicale --config "" --storage-filesystem-folder=~/.var/lib/radicale/collections $ python3 -m radicale --storage-filesystem-folder=~/.var/lib/radicale/collections
``` ```
Victory! Open [http://localhost:5232/](http://localhost:5232/) in your browser! Victory! Open [http://localhost:5232/](http://localhost:5232/) in your browser!
@ -115,11 +114,9 @@ Launch a command prompt and type:
```powershell ```powershell
C:\Users\User> python -m pip install --upgrade radicale C:\Users\User> python -m pip install --upgrade radicale
C:\Users\User> python -m radicale --config "" --storage-filesystem-folder=~/radicale/collections C:\Users\User> python -m radicale --storage-filesystem-folder=~/radicale/collections
``` ```
If you are using PowerShell replace ``--config ""`` with ``--config '""'``.
Victory! Open [http://localhost:5232/](http://localhost:5232/) in your browser! Victory! Open [http://localhost:5232/](http://localhost:5232/) in your browser!
You can login with any username and password. You can login with any username and password.
@ -134,10 +131,12 @@ Installation instructions can be found on the
### Configuration ### Configuration
Radicale tries to load configuration files from `/etc/radicale/config`, Radicale tries to load configuration files from `/etc/radicale/config` and
`~/.config/radicale/config` and the `RADICALE_CONFIG` environment variable. `~/.config/radicale/config`.
A custom path can be specified with the `--config /path/to/config` command Custom paths can be specified with the `--config /path/to/config` command
line argument. line argument or the `RADICALE_CONFIG` environment variable.
Multiple configuration files can be separated by `:` (resp. `;` on Windows).
Paths that start with `?` are optional.
You should create a new configuration file at the desired location. You should create a new configuration file at the desired location.
(If the use of a configuration file is inconvenient, all options can be (If the use of a configuration file is inconvenient, all options can be
@ -160,19 +159,14 @@ The `users` file can be created and managed with
[htpasswd](https://httpd.apache.org/docs/current/programs/htpasswd.html): [htpasswd](https://httpd.apache.org/docs/current/programs/htpasswd.html):
```bash ```bash
# Create a new htpasswd file with the user "user1" # Create a new htpasswd file with the user "user1"
$ htpasswd -B -c /path/to/users user1 $ htpasswd -c /path/to/users user1
New password: New password:
Re-type new password: Re-type new password:
# Add another user # Add another user
$ htpasswd -B /path/to/users user2 $ htpasswd /path/to/users user2
New password: New password:
Re-type new password: Re-type new password:
``` ```
**bcrypt** is used to secure the passwords. Radicale requires additional
dependencies for this encryption method:
```bash
$ python3 -m pip install --upgrade radicale[bcrypt]
```
Authentication can be enabled with the following configuration: Authentication can be enabled with the following configuration:
```ini ```ini
@ -180,7 +174,7 @@ Authentication can be enabled with the following configuration:
type = htpasswd type = htpasswd
htpasswd_filename = /path/to/users htpasswd_filename = /path/to/users
# encryption method used in the htpasswd file # encryption method used in the htpasswd file
htpasswd_encryption = bcrypt htpasswd_encryption = md5
``` ```
#### The simple but insecure way #### The simple but insecure way
@ -206,15 +200,13 @@ htpasswd_encryption = plain
The default configuration binds the server to localhost. It can't be reached The default configuration binds the server to localhost. It can't be reached
from other computers. This can be changed with the following configuration from other computers. This can be changed with the following configuration
options: options (IPv4 and IPv6):
```ini ```ini
[server] [server]
hosts = 0.0.0.0:5232 hosts = 0.0.0.0:5232, [::]:5232
``` ```
More addresses can be added (separated by commas).
### Storage ### Storage
Data is stored in the folder `/var/lib/radicale/collections`. The path can Data is stored in the folder `/var/lib/radicale/collections`. The path can
@ -342,21 +334,6 @@ $ journalctl --unit radicale.service
*To be written.* *To be written.*
### Classic daemonization
Set the configuration option `daemon` in the section `server` to `True`.
You may want to set the option `pid` to the path of a PID file.
After daemonization the server will not log anything. You have to configure
[Logging](#documentation/logging).
If you start Radicale now, it will initialize and fork into the background.
The main process exits, after the PID file is written.
**Security:** You can set the **umask** with `umask 0027` before you start the
daemon, to protect your calendar data and log files from other users.
Don't forget to set permissions of files that are already created!
### Windows with "NSSM - the Non-Sucking Service Manager" ### Windows with "NSSM - the Non-Sucking Service Manager"
First install [NSSM](https://nssm.cc/) and start `nssm install` in a command First install [NSSM](https://nssm.cc/) and start `nssm install` in a command
@ -569,7 +546,7 @@ Radicale has been tested with:
Many clients do not support the creation of new calendars and address books. Many clients do not support the creation of new calendars and address books.
You can use Radicale's web interface You can use Radicale's web interface
(e.g. [http://localhost:5232](http://localhost:5232)) to create and manage (e.g. [http://localhost:5232](http://localhost:5232)) to create and manage
collections. address books and calendars.
In some clients you can just enter the URL of the Radicale server In some clients you can just enter the URL of the Radicale server
(e.g. `http://localhost:5232`) and your user name. In others, you have to (e.g. `http://localhost:5232`) and your user name. In others, you have to
@ -716,60 +693,49 @@ An example configuration file looks like:
```ini ```ini
[server] [server]
# Bind all addresses # Bind all addresses
hosts = 0.0.0.0:5232 hosts = 0.0.0.0:5232, [::]:5232
[auth] [auth]
type = htpasswd type = htpasswd
htpasswd_filename = /path/to/users htpasswd_filename = /path/to/users
htpasswd_encryption = bcrypt htpasswd_encryption = md5
[storage] [storage]
filesystem_folder = ~/.var/lib/radicale/collections filesystem_folder = ~/.var/lib/radicale/collections
``` ```
Radicale tries to load configuration files from `/etc/radicale/config`, Radicale tries to load configuration files from `/etc/radicale/config` and
`~/.config/radicale/config` and the `RADICALE_CONFIG` environment variable. `~/.config/radicale/config`.
This behaviour can be overwritten by specifying a path with the Custom paths can be specified with the `--config /path/to/config` command
`--config /path/to/config` command line argument. line argument or the `RADICALE_CONFIG` environment variable.
Multiple configuration files can be separated by `:` (resp. `;` on Windows).
Paths that start with `?` are optional.
The same example configuration via command line arguments looks like: The same example configuration via command line arguments looks like:
```bash ```bash
python3 -m radicale --config "" --server-hosts 0.0.0.0:5232 --auth-type htpasswd --htpasswd-filename /path/to/htpasswd --htpasswd-encryption bcrypt python3 -m radicale --server-hosts 0.0.0.0:5232,[::]:5232 --auth-type htpasswd --htpasswd-filename /path/to/htpasswd --htpasswd-encryption md5
``` ```
The `--config ""` argument is required to stop Radicale from trying Add the argument `--config ""` to stop Radicale from loading the default
to load configuration files. Run `python3 -m radicale --help` for more information. configuration files. Run `python3 -m radicale --help` for more information.
In the following, all configuration categories and options are described. In the following, all configuration categories and options are described.
### server ### server
Most configuration options in this category are only relevant in standalone The configuration options in this category are only relevant in standalone
mode. All options beside `max_content_length` and `realm` are ignored, mode. All options are ignored, when Radicale runs via WSGI.
when Radicale runs via WSGI.
#### hosts #### hosts
A comma separated list of addresses that the server will bind to. A comma separated list of addresses that the server will bind to.
Default: `127.0.0.1:5232` Default: `localhost:5232`
#### daemon
Daemonize the Radicale process. It does not reset the umask.
Default: `False`
#### pid
If daemon mode is enabled, Radicale will write its PID to this file.
Default:
#### max_connections #### max_connections
The maximum number of parallel connections. Set to `0` to disable the limit. The maximum number of parallel connections. Set to `0` to disable the limit.
Default: `20` Default: `8`
#### max_content_length #### max_content_length
@ -810,30 +776,6 @@ authentication plugin that extracts the user name from the certifcate.
Default: Default:
#### protocol
SSL protocol used. See python's ssl module for available values.
Default: `PROTOCOL_TLSv1_2`
#### ciphers
Available ciphers for SSL. See python's ssl module for available ciphers.
Default:
#### dns_lookup
Reverse DNS to resolve client address in logs.
Default: `True`
#### realm
Message displayed in the client when a password is needed.
Default: `Radicale - Password Required`
### encoding ### encoding
#### request #### request
@ -897,25 +839,12 @@ Available methods:
`bcrypt` `bcrypt`
: This uses a modified version of the Blowfish stream cipher. It's very secure. : This uses a modified version of the Blowfish stream cipher. It's very secure.
The **passlib** python module is required for this. Additionally you may need The installation of **radicale[bcrypt]** is required for this.
one of the following python modules: **bcrypt**, **py-bcrypt** or **bcryptor**.
`md5` `md5`
: This uses an iterated md5 digest of the password with a salt. : This uses an iterated md5 digest of the password with a salt.
The **passlib** python module is required for this.
`sha1` Default: `md5`
: Passwords are stored as SHA1 hashes. It's insecure!
`ssha`
: Passwords are stored as salted SHA1 hashes. It's insecure!
`crypt`
: This uses UNIX
[crypt(3)](https://manpages.debian.org/unstable/manpages-dev/crypt.3.en.html).
It's insecure!
Default: `bcrypt`
#### delay #### delay
@ -923,6 +852,12 @@ Average delay after failed login attempts in seconds.
Default: `1` Default: `1`
#### realm
Message displayed in the client when a password is needed.
Default: `Radicale - Password Required`
### rights ### rights
#### type #### type
@ -978,28 +913,12 @@ Folder for storing local collections, created if not present.
Default: `/var/lib/radicale/collections` Default: `/var/lib/radicale/collections`
#### filesystem_locking
Lock the storage. This must be disabled if locking is not supported by the
underlying file system. Never start multiple instances of Radicale or edit the
storage externally while Radicale is running if disabled.
Default: `True`
#### max_sync_token_age #### max_sync_token_age
Delete sync-token that are older than the specified time. (seconds) Delete sync-token that are older than the specified time. (seconds)
Default: `2592000` Default: `2592000`
#### filesystem_fsync
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!
Default: `True`
#### hook #### hook
Command that is run after changes to storage. Take a look at the Command that is run after changes to storage. Take a look at the
@ -1023,17 +942,13 @@ Available backends:
Default: `internal` Default: `internal`
### logging ### logging
#### debug #### level
Set the default logging level to debug. Set the logging level.
Default: `False` Available levels: **debug**, **info**, **warning**, **error**, **critical**
#### full_environment Default: `warning`
Log all environment variables (including those set in the shell).
Default: `False`
#### mask_passwords #### mask_passwords
@ -1041,12 +956,6 @@ Don't include passwords in logs.
Default: `True` Default: `True`
#### config
Logging configuration file. See the [Logging](#documentation/logging) page.
Default:
### headers ### headers
In this section additional HTTP headers that are sent to clients can be In this section additional HTTP headers that are sent to clients can be
@ -1110,7 +1019,7 @@ The path of the collection is separated by `/` and has no leading or trailing
`%(login)s` gets replaced by the user name and `%(path)s` by the path of `%(login)s` gets replaced by the user name and `%(path)s` by the path of
the collection. You can also get groups from the `user` regex in the the collection. You can also get groups from the `user` regex in the
`collection` regex with `{0}`, `{1}`, etc. `collection` regex with `{1}`, `{2}`, etc.
## Storage ## Storage
@ -1196,85 +1105,9 @@ and `nNumberOfBytesToLockHigh` to `0` works.
## Logging ## Logging
Radicale logs to `stderr`. The verbosity of the log output can be controlled Radicale logs to `stderr`. The verbosity of the log output can be controlled
with `--debug` command line argument or the `debug` configuration option in with `--debug` command line argument or the `level` configuration option in
the `logging` section. the `logging` section.
This is the recommended configuration for use with modern init systems
(like **systemd**) or if you just test Radicale in a terminal.
You can configure Radicale to write its logging output to files (and even
rotate them).
This is useful if the process daemonizes or if your chosen method of running
Radicale doesn't handle logging output.
A logging configuration file can be specified in the `config` configuration
option in the `logging` section. The file format is explained in the
[Python Logging Module](https://docs.python.org/3/library/logging.config.html#configuration-file-format).
### Logging to a file
An example configuration to write the log output to the file `/var/log/radicale/log`:
```ini
[loggers]
keys = root
[handlers]
keys = file
[formatters]
keys = full
[logger_root]
# Change this to DEBUG or INFO for higher verbosity.
level = WARNING
handlers = file
[handler_file]
class = FileHandler
# Specify the output file here.
args = ('/var/log/radicale/log',)
formatter = full
[formatter_full]
format = %(asctime)s - [%(thread)x] %(levelname)s: %(message)s
```
You can specify multiple **logger**, **handler** and **formatter** if you want
to have multiple simultaneous log outputs.
The parent folder of the log files must exist and must be writable by Radicale.
**Security:** The log files should not be readable by unauthorized users. Set
permissions accordingly.
#### Timed rotation of disk log files
An example **handler** configuration to write the log output to the file `/var/log/radicale/log` and rotate it.
Replace the section `handler_file` from the file logging example:
```ini
[handler_file]
class = handlers.TimedRotatingFileHandler
# Specify the output file and parameter for rotation here.
# See https://docs.python.org/3/library/logging.handlers.html#logging.handlers.TimedRotatingFileHandler
# Example: rollover at midnight and keep 7 files (means one week)
args = ('/var/log/radicale/log', 'midnight', 1, 7)
formatter = full
```
#### Rotation of disk log files based on size
An example **handler** configuration to write the log output to the file `/var/log/radicale/log` and rotate it .
Replace the section `handle_file` from the file logging example:
```ini
[handler_file]
class = handlers.RotatingFileHandler
# Specify the output file and parameter for rotation here.
# See https://docs.python.org/3/library/logging.handlers.html#logging.handlers.RotatingFileHandler
# Example: rollover at 100000 kB and keep 10 files (means 1 MB)
args = ('/var/log/radicale/log', 'a', 100000, 10)
formatter = full
```
## Architecture ## Architecture
Radicale is a really small piece of software, but understanding it is not as Radicale is a really small piece of software, but understanding it is not as
@ -1343,61 +1176,63 @@ icons and buttons, a terminal or another web application.
### Code Architecture ### Code Architecture
The ``radicale`` package offers 9 modules. The ``radicale`` package offers the following modules.
`__main__`
: The main module provides a simple function called run. Its main work is to
read the configuration from the configuration file and from the options given
in the command line; then it creates a server, according to the configuration.
`__init__` `__init__`
: This is the core part of the module, with the code for the CalDAV/CardDAV : Contains the entry point for WSGI.
server. The server inherits from a WSGIServer server class, which relies on
the default HTTP server class given by Python. The code managing the
different HTTP requests according to the CalDAV/CardDAV normalization is
written here.
`config` `__main__`
: This part gives a dict-like access to the server configuration, read from the : Provides the entry point for the ``radicale`` executable and
configuration file. The configuration can be altered when launching the includes the command line parser. It loads configuration files from
executable with some command line options. the default (or specified) paths and starts the internal server.
`xmlutils` `app`
: The functions defined in this module are mainly called by the CalDAV/CardDAV : This is the core part of Radicale, with the code for the CalDAV/CardDAV
server class to read the XML part of the request, read or alter the server. The code managing the different HTTP requests according to the
calendars, and create the XML part of the response. The main part of this CalDAV/CardDAV specification can be found here.
code relies on ElementTree.
`log`
: The start function provided by this module starts a logging mechanism based
on the default Python logging module. Logging options can be stored in a
logging configuration file.
`auth` `auth`
: This module provides a default authentication manager equivalent to Apache's : Used for authenticating users based on username and password, mapping
htpasswd. Login + password couples are stored in a file and used to usernames to internal users and optionally retrieving credentials from
authenticate users. Passwords can be encrypted using various methods. Other the environment.
authentication methods can inherit from the base class in this file and be
provided as plugins. `config`
: Contains the code for managing configuration and loading settings from files.
`ìtem`
: Internal represenation of address book and calendar entries. Based on
[VObject](https://eventable.github.io/vobject/).
`log`
: The logger for Radicale based on the default Python logging module.
`rights` `rights`
: This module is a set of Access Control Lists, a set of methods used by : This module is used by Radicale to manage access rights to collections,
Radicale to manage rights to access the calendars. When the CalDAV/CardDAV address books and calendars.
server is launched, an Access Control List is chosen in the set, according to
the configuration. The HTTP requests are then filtered to restrict the access `server`
depending on who is authenticated. Other configurations can be written using : The integrated HTTP server for standalone use.
regex-based rules. Other rights managers can also inherit from the base class
in this file and be provided as plugins.
`storage` `storage`
: In this module are written the classes representing collections and items in : This module contains the classes representing collections in Radicale and
Radicale, and the class storing these collections and items in your the code for storing and loading them in the filesystem.
filesystem. Other storage classes can inherit from the base class in this
file and be provided as plugins.
`web` `web`
: This module contains the web interface. : This module contains the web interface.
`utils`
: Contains general helper functions.
`httputils`
: Contains helper functions for working with HTTP.
`pathutils`
: Helper functions for working with paths and the filesystem.
`xmlutils`
: Helper functions for working with the XML part of CalDAV/CardDAV requests
and responses. It's based on the ElementTree XML API.
## Plugins ## Plugins
Radicale can be extended by plugins for authentication, rights management and Radicale can be extended by plugins for authentication, rights management and
@ -1406,7 +1241,7 @@ storage. Plugins are **python** modules.
### Getting started ### Getting started
To get started we walk through the creation of a simple authentication To get started we walk through the creation of a simple authentication
plugin, that accepts login attempts if the username and password are equal. plugin, that accepts login attempts with a static password.
The easiest way to develop and install **python** modules is The easiest way to develop and install **python** modules is
[Distutils](https://docs.python.org/3/distutils/setupscript.html). [Distutils](https://docs.python.org/3/distutils/setupscript.html).
@ -1418,31 +1253,36 @@ in an empty folder:
from distutils.core import setup from distutils.core import setup
setup(name="radicale_silly_auth", packages=["radicale_silly_auth"]) setup(name="radicale_static_password_auth", packages=["radicale_static_password_auth"])
``` ```
In the same folder create the sub-folder `radicale_silly_auth`. The folder In the same folder create the sub-folder `radicale_static_password_auth`.
must have the same name as specified in `packages` above. The folder must have the same name as specified in `packages` above.
Create the file `__init__.py` in the `radicale_silly_auth` folder with the Create the file `__init__.py` in the `radicale_static_password_auth` folder
following content: with the following content:
```python ```python
from radicale.auth import BaseAuth from radicale.auth import BaseAuth
from radicale.log import logger
PLUGIN_CONFIG_SCHEMA = {"auth": {
"password": {"value": "", "type": str}}}
class Auth(BaseAuth): class Auth(BaseAuth):
def is_authenticated(self, user, password): def __init__(self, configuration):
# Example custom configuration option super().__init__(configuration.copy(PLUGIN_CONFIG_SCHEMA))
foo = ""
if self.configuration.has_option("auth", "foo"):
foo = self.configuration.get("auth", "foo")
self.logger.info("Configuration option %r is %r", "foo", foo)
def login(self, user, password):
# Get password from configuration option
static_password = self.configuration.get("auth", "password")
# Check authentication # Check authentication
self.logger.info("Login attempt by %r with password %r", logger.info("Login attempt by %r with password %r",
user, password) user, password)
return user == password if password == static_password:
return user
return ""
``` ```
Install the python module by running the following command in the same folder Install the python module by running the following command in the same folder
@ -1452,46 +1292,46 @@ python3 -m pip install --upgrade .
``` ```
To make use this great creation in Radicale, set the configuration option To make use this great creation in Radicale, set the configuration option
`type` in the `auth` section to `radicale_silly_auth`: `type` in the `auth` section to `radicale_static_password_auth`:
```ini ```ini
[auth] [auth]
type = radicale_silly_auth type = radicale_static_password_auth
foo = bar password = secret
``` ```
You can uninstall the module with: You can uninstall the module with:
```bash ```bash
python3 -m pip uninstall radicale_silly_auth python3 -m pip uninstall radicale_static_password_auth
``` ```
### Authentication plugins ### Authentication plugins
This plugin type is used to check login credentials. This plugin type is used to check login credentials.
The module must contain a class `Auth` that extends The module must contain a class `Auth` that extends
`radicale.auth.BaseAuth`. Take a look at the file `radicale/auth.py` in `radicale.auth.BaseAuth`. Take a look at the file `radicale/auth/__init__.py`
Radicale's source code for more information. in Radicale's source code for more information.
### Rights management plugins ### Rights management plugins
This plugin type is used to check if a user has access to a path. This plugin type is used to check if a user has access to a path.
The module must contain a class `Rights` that extends The module must contain a class `Rights` that extends
`radicale.rights.BaseRights`. Take a look at the file `radicale/rights.py` in `radicale.rights.BaseRights`. Take a look at the file
Radicale's source code for more information. `radicale/rights/__init__.py` in Radicale's source code for more information.
### Web plugins ### Web plugins
This plugin type is used to provide the web interface for Radicale. This plugin type is used to provide the web interface for Radicale.
The module must contain a class `Web` that extends The module must contain a class `Web` that extends
`radicale.web.BaseWeb`. Take a look at the file `radicale/web.py` in `radicale.web.BaseWeb`. Take a look at the file `radicale/web/__init__.py` in
Radicale's source code for more information. Radicale's source code for more information.
### Storage plugins ### Storage plugins
This plugin is used to store collections and items. This plugin is used to store collections and items.
The module must contain a class `Collection` that extends The module must contain a class `Storage` that extends
`radicale.storage.BaseCollection`. Take a look at the file `radicale/storage.py` `radicale.storage.BaseStorage`. Take a look at the file
in Radicale's source code for more information. `radicale/storage/__init__.py` in Radicale's source code for more information.
# Contribute # Contribute
@ -1507,7 +1347,7 @@ Found a bug? Want a new feature? Report a new issue on the
### Hack ### Hack
Interested in hacking? Feel free to clone the Interested in hacking? Feel free to clone the
[git repository on Github](https://github.com/Kozea/Radicale) if you want to [git repository on GitHub](https://github.com/Kozea/Radicale) if you want to
add new features, fix bugs or update the documentation. add new features, fix bugs or update the documentation.
### Documentation ### Documentation
@ -1537,34 +1377,8 @@ You can also download the content of the repository as an
### Source Packages ### Source Packages
You can download the Radicale package for each release: You can find the source packages of all releases on
[GitHub](https://github.com/Kozea/Radicale/releases).
- [**2.1.11 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.11)
- [**2.1.10 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.10)
- [**2.1.9 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.9)
- [**2.1.8 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.8)
- [**2.1.7 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.7)
- [**2.1.6 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.6)
- [**2.1.5 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.5)
- [**2.1.4 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.4)
- [**2.1.3 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.3)
- [**2.1.2 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.2)
- [**1.1.6 - Sixth Law of Nature**](https://api.github.com/repos/Kozea/Radicale/tarball/1.1.6)
- [**2.1.1 - Wild Radish Again**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.1)
- [**2.1.0 - Wild Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.0)
- [**1.1.4 - Fifth Law of Nature**](https://api.github.com/repos/Kozea/Radicale/tarball/1.1.4)
- [2.1.0rc3](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.0rc3)
- [2.1.0rc2](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.0rc2)
- [2.1.0rc1](https://api.github.com/repos/Kozea/Radicale/tarball/2.1.0rc1)
- [**2.0.0 - Little Big Radish**](https://api.github.com/repos/Kozea/Radicale/tarball/2.0.0)
- [**1.1.3 - Fourth Law of Nature**](https://api.github.com/repos/Kozea/Radicale/tarball/1.1.3)
- [2.0.0rc2](https://api.github.com/repos/Kozea/Radicale/tarball/2.0.0rc2)
- [**1.1.2 - Third Law of Nature**](https://api.github.com/repos/Kozea/Radicale/tarball/1.1.2)
- [2.0.0rc1](https://api.github.com/repos/Kozea/Radicale/tarball/2.0.0rc1)
- [**1.1.1 - Second Law of Nature**](https://api.github.com/repos/Kozea/Radicale/tarball/1.1.1)
- [**1.1 - Law of Nature**](https://api.github.com/repos/Kozea/Radicale/tarball/1.1)
- [**1.0.1 - Sunflower Again**](https://api.github.com/repos/Kozea/Radicale/tarball/1.0.1)
- [**1.0 - Sunflower**](https://api.github.com/repos/Kozea/Radicale/tarball/1.0)
### Linux Distribution Packages ### Linux Distribution Packages