Instalando davical con ansible, por supuesto a lo bestia

Un objeto entra en contacto con la atmosfera de un planeta

Davical es, según su creador, un servidor para compartir calendarios, una implementación del protocolo caldav para almacenar recursos de calendario en un servidor compartido.

Se trata de una pieza de software muy bien hecha y muy madura, que nos permite compartir nuestros calendario y agendas con nosotros mismo (crear un evento en dispositivo android y que se replique en el Thunderbir de nuestro escritorio o compartir los contactos entre todos los dispositivos) o crear recursos compartidos con terceros, por ejemplo que un asistente pueda crear una cita en el calendario de su jefe, pudiendo leer los que ha creado pero sin poder acceder a los que ha creado su jefe, o solo poder acceder a un freebusy(ver las horas libres y reservadas pero no de que), el sistema de recursos de Davical permite la creación de gran cantidad de recursos con permisos muy bien controlados.

Todo esto hace que junto con la instalación de Dbmail que hicimos en este artículo, tengamos ya los mimbres sobre los que montaremos nuestra herramienta colaborativa. Al igual que con Dbmail, vamos a hacer la instalación a lo bestia, es decir en un playbook, ya más adelante, cuando hayamos tocado el tema de roles y vaults convertiremos el playbook en un rol que habrá que empezar a tratar bien con un control de versiones, con pruebas unitarias y con código documentado, poco a poco.

Definiendo nuestra instalación:

Ya hemos tratado el tema de las plantillas y las variables, esta vez, antes de comenzar con el playbook, vamos a hacer un listado con los pasos que tenemos que dar:

  • Por sanidad, actualizar el estado del servidor.
  • Instalar los paquetes necesarios, en nuestro caso, los paquetes de la base de datos, de davical, apache y php5, además de sus dependencias.
  • Enviar las configuraciones para apache, davical y postgresql
  • Crear, la base de datos, los usuarios e importar la estructura de tablas.

Vamos al lío, primero los paquetes:

Aquí ya empezamos a ver la reusabilidad de los fragmentos de código YAML de nuestros anteriores playbooks, por lo que es recomendable realizar este tipo de acciones en roles para poder utilizar ficheros tal cual con tareas que son comunes a distintos roles.

davical.yml(1)

---
- hosts: davical
  user: ansrunner
  sudo: yes
  vars_files:
    - vars/vars_davical.yml
  tasks:
  - name: Actualiza cache de apt
    apt: update_cache=yes cache_valid_time=3600
  - name: Actualiza paquetes
    apt: upgrade=yes
  - name: instalar paquetes
    apt: name={{ item }} state=installed allow_unauthenticated=yes
    with_items:
    - "apache2"
    - "postgresql"
    - "php5"
    - "php5-pgsql"
    - "php5-imap"
    - "php5-curl"
    - "php5-cgi"
    - "libapache2-mod-php5"
    - "libyaml-perl"
    - "libdbd-pg-perl"
    - "libdbi-perl"
    - "libawl-php"
    - "davical"
    - "python-psycopg2"

Como vemos, salvo que el listado de paquetes es distinto, la estructura es la misma que en el caso de la instalación de dbmail.

Creando la base de datos y los usuarios:

Davical utiliza dos usuarios distintos, un usuario dba para la administración de la base de datos y un usuario app para el acceso mediante la aplicación, Creamos los dos usuarios y le damos permisos al usuario dba, los scripts de davical ya establecen los permisos para el usuario app.

davical.yml(2)

  - name: verifica que la bdd esta creada
    become: true
    become_user: postgres
    postgresql_db: state=present db={{ davical_database }} login_user=postgres
        
  - name: verifica que el usuario dba esta creado
    become: true
    become_user: postgres
    postgresql_user: name={{ item.username }} password={{ item.password }} state=present priv=CONNECT db={{ davical_database }} login_user=postgres
    with_items:
    - {username: "{{ davical_dba_username }}", password: "{{ davical_dba_password }}" }
    - {username: "{{ davical_app_username }}", password: "{{ davical_app_password }}" }
    
  - name: verifica que el usuario tiene los privilegios correctos
    become: true
    become_user: postgres
    postgresql_user: name={{ davical_dba_username }} role_attr_flags=LOGIN login_user=postgres
        
  - name: verifica que el usuario dba puede modificar la bdd
    become: true
    become_user: postgres
    postgresql_privs: privs=ALL type=schema objs=public role={{ davical_dba_username }} db={{ davical_database }} login_user=postgres
   

Aplicando la configuración con plantillas:

Al igual que en casos anteriores, utilizamos el motor de plantillas para desplegar los ficheros de configuración:

davical.yml(3)

  - name: prepara configuraciones
    template: src={{ item.source }} dest={{ item.dest }}
    with_items:
      - {source: 'templates/davical_config_php.j2', dest: '/etc/davical/config.php'}
      - {source: 'templates/apache2_sites_available_davical.j2', dest: '/etc/apache2/sites-available/davical'}
      - {source: 'templates/apache2_sites_available_davical_ssl.j2', dest: '/etc/apache2/sites-available/davical-ssl'}

En este caso, vamos a detenernos a echar un vistazo a la configuración de davical:

davical_config_php.j2

<?php
#
# {{ ansible_managed }}
# Please do not edit manually
# Last modified: {{ ansible_date_time.date }}
#
$c->pg_connect[] = "dbname={{ davical_database }} user={{ davical_app_username }} host=localhost password={{ davical_app_password }}";
$c->system_name = "CalDAV Praderas";
$c->get_includes_subcollections = true;
$c->readonly_webdav_collections = true;
$c->skip_bad_event_on_import = true;
$c->admin_email ='{{ davical_admin_email }}';
$c->restrict_setup_to_admin = true;
$c->restrict_admin_domain = 'admin.{{ davical_server_name }}';
$c->enable_row_linking = true;
$c->default_privileges = array('read-free-busy', 'schedule-query-freebusy','schedule-send-reply','schedule-deliver-reply');
$c->hide_older_than = 90;
$c->default_locale = "es_ES";
$c->enable_auto_schedule = true;
$c->authenticate_hook['call'] = 'IMAP_PAM_check';
$c->authenticate_hook['config'] = array('imap_url' => '{{ '{' }}{{ davical_server_name }}{{ ':' }}993/imap/ssl/{{ '}' }}INBOX','email_base' => '{{ davical_dominio_correo }}');
include('drivers_imap_pam.php');
?>

En la configuración, que se puede ver con más detalle en la página de Davical, vemos que utilizamos un hook de autenticación para permitir que los usuarios se autentiquen mediante IMAP, esto, unido a que la configuración por defecto permite crear automáticamente la agenda y la libreta de direcciones de los usuarios autenticados, nos permitirá no tener que administrar la creación de usuarios y buzones por defecto en davical, sino que el mismo usuario del correo servirá para acceder a Caldav.

Vemos que en la sintaxis tenemos que utilizar el formato {{ '{' }} tanto para las llaves como para los dos puntos, de lo contrario el intérprete nos daría error, ya que ambos son caracteres especiales para Jinja2.

Las variables que hemos definido para este playbook (obviamente sin el contenido) y sus plantillas son:

vars_davical.j2

davical_dba_username: "davical_dba"
davical_database: "davical"
davical_dba_password: ""
davical_app_username: "davical_app"
davical_app_password: ""
admin_password: ""
davical_admin_email: ""
davical_server_name: ""
davical_dominio_correo: ""
admin_email: ""
tls_cert_file: ""
tls_key_file: ""
tls_ca_file: ""

Configurando Apache:

Una vez copiadas las plantillas, vinculamos los archivos de site-available a site-enabled para habilitarlos y habilitamos los módulos ssl y rewrite de apache:

davical.yml(4)

  - name: añade el sitio a apache2
    file:
      src: '/etc/apache2/sites-available/davical'
      dest: '/etc/apache2/sites-enabled/010-davical.conf'
      owner: root
      group: root
      state: link
      
  - name: añade el sitio ssl a apache2
    file:
      src: '/etc/apache2/sites-available/davical-ssl'
      dest: '/etc/apache2/sites-enabled/010-davical-ssl.conf'
      owner: root
      group: root
      state: link    

  - name: Habilita módulos de apache
    apache2_module: state: present name: {{ item }}
    with_items:
      - "ssl"
      - "rewrite"
    notify: reinicia apache   

El fichero de plantilla de sitio de apache usa el modulo rewrite para convertir las llamadas del estilo nombredelsitio/usuario/calendario a llamadas al caldav.php de esta manera las URLs serán limpias y más fáciles de poner en el cliente de correo y en los dispositivos móviles, además, hacemos uso de los facts de ansible para completar campos:

apache2_sites_available_davical_ssl.j2

#
# {{ ansible_managed }}
# Please do not edit manually
# Last modified: {{ ansible_date_time.date }}
#

<VirtualHost {{ ansible_eth0.ipv4.address }}:443 >
  DocumentRoot /usr/share/davical/htdocs
  DirectoryIndex index.php
  ServerName {{ davical_server_name }}
  SSLEngine on
  SSLCertificateFile    {{ tls_cert_file }}
  SSLCertificateKeyFile {{ tls_key_file }}

  <Directory /usr/share/davical/htdocs/>
     AllowOverride None
     Order allow,deny
     Allow from all
  </Directory>
    RewriteEngine On
    RewriteCond %{REQUEST_URI} !^/$
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ /caldav.php/$1  [NC,L]
</VirtualHost>

Vemos como en la última tarea, hay un comando notify, esto quiere decir que, en caso de que se modifique el equipo con esta tarea, se notifica al handler indicado, en este caso, nuestro handler reinicia apache. Los handlers se sitúan en el fichero del playbook una vez terminadas las tareas.

Preparando la base de datos:

Con los paquetes instalados, los ficheros de configuración listos y apache con los sitios creados, sólo nos queda definir la base de datos, igual que en el caso de dbmail, para asegurar la idempotencia comprobaremos la existencia de las tablas en la bdd antes de comenzar y utilizamos el fichero .pgpass para almacenar las contraseñas de la base de datos, como ya lo habíamos creado con anterioridad, primero comprobamos si existe, para no seobrescribirlo.

davical.yml(5)

  - name: Verificamos si existe ~/.pgpass
    stat: path=~/.pgpass
    register: dotpgpass
    
  - name: Revisamos .pgpass si existe
    lineinfile:
      dest: ~/.pgpass
      state: present
      regexp: '^localhost:5432:{{ davical_database }}:{{ davical_dba_username }}:{{ davical_dba_password }}'
      line: 'localhost:5432:{{ davical_database }}:{{ davical_dba_username }}:{{ davical_dba_password }}'
    when: dotpgpass.stat.exists is defined and dotpgpass.stat.exists == True

  - name: creamos fichero .pgpass si no existe
    template: src={{ item.source }} dest={{ item.dest }} mode={{ item.mode }}
    with_items:    
      - {source: 'templates/davical_dotpgpass.j2', dest: '~/.pgpass', mode: '0600'}
    when: dotpgpass.stat.exists is defined and dotpgpass.stat.exists == False

  - name: comprueba que las tablas están creadas.
    command: psql -U {{ davical_dba_username }} -d {{ davical_database }} -h localhost -w -q -c "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'usr');"
    register: respsql
    
  - name: Ejecuta scripts en la base de datos
    command: psql -U {{ davical_dba_username }} -d {{ davical_database }} -h localhost -w -q -f {{ item }}
    with_items:
      - '/usr/share/awl/dba/awl-tables.sql '
      - '/usr/share/awl/dba/schema-management.sql'
      - '/usr/share/davical/dba/davical.sql'
    when: respsql.stdout_lines[2] is defined and respsql.stdout_lines[2] | search('f')       

  - name: Ejecuta script de configuración de permisos
    command:  /usr/share/davical/dba/update-davical-database --dbname {{ davical_database }} --dbuser davical_dba --dbhost localhost --dbpass {{ davical_dba_password}} --appuser davical_app --nopatch --owner davical_dba
    when: respsql.stdout_lines[2] is defined and respsql.stdout_lines[2] | search('f')   
    
  - name: Realiza la carga inicial de datos
    command: psql -U {{ davical_dba_username }} -d {{ davical_database }} -h localhost -w -q -f /usr/share/davical/dba/base-data.sql
    when: respsql.stdout_lines[2] is defined and respsql.stdout_lines[2] | search('f')

  - name: Modifica password de administrador
    command: psql -U {{ davical_dba_username }} -d {{ davical_database }} -h localhost -w -q -c "UPDATE usr SET password = '**{{ admin_password }}' WHERE user_no = 1;"
    when: respsql.stdout_lines[2] is defined and respsql.stdout_lines[2] | search('f')    
    
  handlers:

  - name: reinicia apache
    service: name=apache2 state=restarted

Con esto, ya tenemos el servidor de davical funcionando, como en el caso anterior, me diréis que el trabajo es el mismo o más, y otra vez os doy la razón, salvo que quieras instalar un segundo servidor, en ese caso el tiempo de instalación es:

PLAY RECAP *********************************************************************
servidornuevo        : ok=18   changed=12   unreachable=0    failed=0

real    1m27.068s
user    0m11.468s
sys     0m27.220s

Los ficheros:

davical.yml

templates_davical.zip