Manage Jitsi through Ansible using custom role #172
11 changed files with 462 additions and 0 deletions
Add a role for Jitsi in Docker Compose
We need this role to follow the recommended deployment approach from https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker/ This role enables us to keep our config and customizations separate from releases of the Compose setup the Jitsi project publishs.
commit
dbac5ede16
|
|
@ -110,6 +110,13 @@
|
|||
tags:
|
||||
- docker_compose
|
||||
|
||||
- name: Ensure Jitsi deployment on jitsi_hosts
|
||||
hosts: jitsi_hosts
|
||||
roles:
|
||||
- jitsi
|
||||
tags:
|
||||
- jitsi
|
||||
|
||||
- name: Ensure NGINX deployment on nginx_hosts
|
||||
hosts: nginx_hosts:!public_reverse_proxy_hosts
|
||||
roles:
|
||||
|
|
|
|||
57
roles/jitsi/README.md
Normal file
57
roles/jitsi/README.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Role `jitsi`
|
||||
|
||||
A role for deploying [Jitsi Meet](https://jitsi.org/) using the official [docker-jitsi-meet](https://github.com/jitsi/docker-jitsi-meet) release.
|
||||
|
||||
The given release is downloaded and unpacked unmodified to `/opt/jitsi/releases/` and `/opt/jitsi/release` is pointed to it.
|
||||
The `.env` file at `/opt/jitsi/.env` is fully managed by this role; `env.example` and `gen-passwords.sh` of the release are not used.
|
||||
The `.env` also sets `COMPOSE_PROJECT_NAME` and `COMPOSE_FILE`, so `docker compose` can be run by hand in `/opt/jitsi`.
|
||||
The Jitsi configuration directory (`CONFIG`) is `/opt/jitsi/config`, with persistent state in `/opt/jitsi/config/storage/`.
|
||||
The image version (`JITSI_IMAGE_VERSION`) always equals the release version, so upgrading means bumping `jitsi__version`.
|
||||
Releases other than the configured one are removed.
|
||||
|
||||
TLS certificates are obtained by the web container itself using Let's Encrypt, so ports 80/tcp and 443/tcp as well as 10000/udp (media) must be reachable.
|
||||
|
||||
Optionally, a [coturn](https://github.com/coturn/coturn) TURN server and the [Prometheus Jitsi Meet exporter](https://github.com/systemli/prometheus-jitsi-meet-exporter) are deployed as additional services of the same Compose project, defined in `/opt/jitsi/compose.override.yaml`.
|
||||
coturn uses the certificate obtained by the web container and is restarted weekly to pick up renewals.
|
||||
It needs 3478/udp+tcp, 5349/tcp and the relay port range 49152-65535/udp to be reachable.
|
||||
The exporter scrapes the JVB statistics and listens on port 9888.
|
||||
|
||||
## Supported Distributions
|
||||
|
||||
Should work on Debian-based distributions.
|
||||
|
||||
## Required Arguments
|
||||
|
||||
- `jitsi__version`: The docker-jitsi-meet release to deploy, e.g. `stable-11248`.
|
||||
- `jitsi__domain`: The domain Jitsi is served on. Used for `PUBLIC_URL` and the Let's Encrypt certificate.
|
||||
- `jitsi__letsencrypt_email`: The e-mail address for the Let's Encrypt account.
|
||||
- `jitsi__jicofo_auth_password`: XMPP password for Jicofo. Generate using `openssl rand -hex 16`.
|
||||
- `jitsi__jvb_auth_password`: XMPP password for the JVB. Generate using `openssl rand -hex 16`.
|
||||
|
||||
## Optional Arguments
|
||||
|
||||
- `jitsi__timezone`: The time zone for the containers.
|
||||
Defaults to `Europe/Berlin`.
|
||||
- `jitsi__jvb_advertise_ips`: List of IP addresses the JVB should advertise for media (`JVB_ADVERTISE_IPS`).
|
||||
Defaults to `[ ]`.
|
||||
- `jitsi__whiteboard`: Whether to deploy the Excalidraw whiteboard backend (`whiteboard.yml`).
|
||||
Defaults to `false`.
|
||||
- `jitsi__turn`: Whether to deploy coturn and configure Jitsi to use it.
|
||||
Defaults to `false`.
|
||||
- `jitsi__turn_secret`: The shared secret between Prosody and coturn. Required if `jitsi__turn` is `true`.
|
||||
Generate using `openssl rand -hex 32`.
|
||||
- `jitsi__prometheus_exporter`: Whether to deploy the Prometheus Jitsi Meet exporter on port 9888.
|
||||
Defaults to `false`.
|
||||
- `jitsi__env`: Dictionary of additional variables to put into the `.env` file.
|
||||
See `env.example` of the release and the handbook for available variables.
|
||||
Defaults to `{ }`.
|
||||
- `jitsi__custom_config_js`: Content of `custom-config.js`, which gets appended to the generated `config.js`.
|
||||
Defaults to `""` (no file).
|
||||
- `jitsi__custom_interface_config_js`: Content of `custom-interface_config.js`, which gets appended to the generated `interface_config.js`.
|
||||
Defaults to `""` (no file).
|
||||
|
||||
## Links & Resources
|
||||
|
||||
- <https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker/>
|
||||
- <https://github.com/jitsi/docker-jitsi-meet>
|
||||
- <https://github.com/jitsi/docker-jitsi-meet/releases>
|
||||
8
roles/jitsi/defaults/main.yaml
Normal file
8
roles/jitsi/defaults/main.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
jitsi__timezone: Europe/Berlin
|
||||
jitsi__jvb_advertise_ips: [ ]
|
||||
jitsi__whiteboard: false
|
||||
jitsi__turn: false
|
||||
jitsi__prometheus_exporter: false
|
||||
jitsi__env: { }
|
||||
jitsi__custom_config_js: ""
|
||||
jitsi__custom_interface_config_js: ""
|
||||
19
roles/jitsi/handlers/main.yaml
Normal file
19
roles/jitsi/handlers/main.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
- name: restart jitsi web
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ jitsi__base_dir }}"
|
||||
project_name: jitsi
|
||||
files: "{{ jitsi__compose_files }}"
|
||||
services:
|
||||
- web
|
||||
state: restarted
|
||||
become: true
|
||||
|
||||
- name: restart jitsi coturn
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ jitsi__base_dir }}"
|
||||
project_name: jitsi
|
||||
files: "{{ jitsi__compose_files }}"
|
||||
services:
|
||||
- coturn
|
||||
state: restarted
|
||||
become: true
|
||||
57
roles/jitsi/meta/argument_specs.yaml
Normal file
57
roles/jitsi/meta/argument_specs.yaml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
argument_specs:
|
||||
main:
|
||||
options:
|
||||
jitsi__version:
|
||||
type: str
|
||||
required: true
|
||||
jitsi__domain:
|
||||
type: str
|
||||
required: true
|
||||
jitsi__letsencrypt_email:
|
||||
type: str
|
||||
required: true
|
||||
jitsi__jicofo_auth_password:
|
||||
type: str
|
||||
required: true
|
||||
no_log: true
|
||||
jitsi__jvb_auth_password:
|
||||
type: str
|
||||
required: true
|
||||
no_log: true
|
||||
jitsi__timezone:
|
||||
type: str
|
||||
required: false
|
||||
default: Europe/Berlin
|
||||
jitsi__jvb_advertise_ips:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
default: [ ]
|
||||
jitsi__whiteboard:
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
jitsi__turn:
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
jitsi__turn_secret:
|
||||
type: str
|
||||
required: false
|
||||
no_log: true
|
||||
jitsi__prometheus_exporter:
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
jitsi__env:
|
||||
type: dict
|
||||
required: false
|
||||
default: { }
|
||||
jitsi__custom_config_js:
|
||||
type: str
|
||||
required: false
|
||||
default: ""
|
||||
jitsi__custom_interface_config_js:
|
||||
type: str
|
||||
required: false
|
||||
default: ""
|
||||
4
roles/jitsi/meta/main.yaml
Normal file
4
roles/jitsi/meta/main.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
allow_duplicates: false
|
||||
dependencies:
|
||||
- role: docker
|
||||
186
roles/jitsi/tasks/main.yaml
Normal file
186
roles/jitsi/tasks/main.yaml
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
- name: make sure a TURN secret is given, if TURN is enabled
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- jitsi__turn_secret is defined
|
||||
- jitsi__turn_secret | length > 0
|
||||
quiet: true
|
||||
when: jitsi__turn
|
||||
|
||||
- name: make sure the Jitsi base directories exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
loop:
|
||||
- "{{ jitsi__base_dir }}"
|
||||
- "{{ jitsi__base_dir }}/releases"
|
||||
|
||||
- name: make sure the docker-jitsi-meet release is present
|
||||
ansible.builtin.unarchive:
|
||||
src: "https://github.com/jitsi/docker-jitsi-meet/archive/refs/tags/{{ jitsi__version }}.tar.gz"
|
||||
remote_src: true
|
||||
dest: "{{ jitsi__base_dir }}/releases"
|
||||
creates: "{{ jitsi__release_dir }}/docker-compose.yml"
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
|
||||
- name: point the `release` symlink to the configured release
|
||||
ansible.builtin.file:
|
||||
src: "{{ jitsi__release_dir }}"
|
||||
dest: "{{ jitsi__base_dir }}/release"
|
||||
state: link
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
|
||||
# Configuration read by the containers at startup; managed by Ansible.
|
||||
- name: make sure the configuration input directories exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ jitsi__config_dir }}/{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
loop:
|
||||
- ""
|
||||
- web
|
||||
- prosody
|
||||
- prosody/config
|
||||
- prosody/prosody-plugins-custom
|
||||
- jicofo
|
||||
- jvb
|
||||
- storage
|
||||
- tmp
|
||||
|
||||
# State written by the containers, which run as uid/gid 1000.
|
||||
# The certificate directory is created here, as Docker would otherwise create it owned by
|
||||
# root for the coturn bind mount, before the web container gets to write the certificate.
|
||||
- name: make sure the storage and tmp directories exist and are writable by the containers
|
||||
ansible.builtin.file:
|
||||
path: "{{ jitsi__config_dir }}/{{ item }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
become: true
|
||||
loop:
|
||||
- storage/prosody
|
||||
- storage/web
|
||||
- storage/web/acme-certs
|
||||
- "storage/web/acme-certs/{{ jitsi__domain }}"
|
||||
- storage/transcripts
|
||||
- tmp/web-crontabs
|
||||
- tmp/web-load-test
|
||||
|
||||
- name: deploy custom web configuration
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.content }}"
|
||||
dest: "{{ jitsi__config_dir }}/web/{{ item.name }}"
|
||||
mode: "0644"
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
when: item.content != ""
|
||||
loop:
|
||||
- name: custom-config.js
|
||||
content: "{{ jitsi__custom_config_js }}"
|
||||
- name: custom-interface_config.js
|
||||
content: "{{ jitsi__custom_interface_config_js }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
notify: restart jitsi web
|
||||
|
||||
- name: remove unconfigured custom web configuration
|
||||
ansible.builtin.file:
|
||||
path: "{{ jitsi__config_dir }}/web/{{ item.name }}"
|
||||
state: absent
|
||||
become: true
|
||||
when: item.content == ""
|
||||
loop:
|
||||
- name: custom-config.js
|
||||
content: "{{ jitsi__custom_config_js }}"
|
||||
- name: custom-interface_config.js
|
||||
content: "{{ jitsi__custom_interface_config_js }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
notify: restart jitsi web
|
||||
|
||||
- name: make sure the coturn directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ jitsi__base_dir }}/coturn"
|
||||
state: "{{ 'directory' if jitsi__turn else 'absent' }}"
|
||||
mode: "0755"
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
|
||||
# Readable by uid 1000, which coturn runs as.
|
||||
- name: deploy the coturn configuration
|
||||
ansible.builtin.template:
|
||||
src: turnserver.conf.j2
|
||||
dest: "{{ jitsi__base_dir }}/coturn/turnserver.conf"
|
||||
mode: "0640"
|
||||
owner: root
|
||||
group: "1000"
|
||||
become: true
|
||||
when: jitsi__turn
|
||||
notify: restart jitsi coturn
|
||||
|
||||
# coturn doesn't notice the certificate renewed by the web container on its own.
|
||||
- name: ensure the weekly coturn restart cron job is present
|
||||
ansible.builtin.cron:
|
||||
name: "jitsi coturn restart for certificate renewal"
|
||||
special_time: weekly
|
||||
job: "cd {{ jitsi__base_dir }} && docker compose restart coturn >/dev/null"
|
||||
state: "{{ 'present' if jitsi__turn else 'absent' }}"
|
||||
become: true
|
||||
|
||||
# Changes to these make `docker compose up` below recreate the affected containers.
|
||||
- name: deploy the compose override file
|
||||
ansible.builtin.template:
|
||||
src: compose.override.yaml.j2
|
||||
dest: "{{ jitsi__base_dir }}/compose.override.yaml"
|
||||
mode: "0644"
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
|
||||
- name: deploy the .env file
|
||||
ansible.builtin.template:
|
||||
src: jitsi.env.j2
|
||||
dest: "{{ jitsi__base_dir }}/.env"
|
||||
mode: "0600"
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
|
||||
- name: docker compose up
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ jitsi__base_dir }}"
|
||||
project_name: jitsi
|
||||
files: "{{ jitsi__compose_files }}"
|
||||
state: present
|
||||
pull: missing
|
||||
remove_orphans: true
|
||||
become: true
|
||||
|
||||
- name: find old releases
|
||||
ansible.builtin.find:
|
||||
paths: "{{ jitsi__base_dir }}/releases"
|
||||
file_type: directory
|
||||
excludes: "docker-jitsi-meet-{{ jitsi__version }}"
|
||||
register: jitsi__old_releases
|
||||
|
||||
- name: remove old releases
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
become: true
|
||||
loop: "{{ jitsi__old_releases.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
40
roles/jitsi/templates/compose.override.yaml.j2
Normal file
40
roles/jitsi/templates/compose.override.yaml.j2
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# {{ ansible_managed }}
|
||||
# Services in addition to the ones of the docker-jitsi-meet release.
|
||||
|
||||
# Docker Compose explicitly disables IPv6 for networks not enabling it, overriding the
|
||||
# daemon's default-network-opts.
|
||||
networks:
|
||||
meet.jitsi:
|
||||
enable_ipv6: true
|
||||
|
||||
services:
|
||||
{% if jitsi__turn %}
|
||||
coturn:
|
||||
image: docker.io/coturn/coturn:4.18.0
|
||||
restart: unless-stopped
|
||||
# Recommended by coturn, as Docker performs badly with large port ranges.
|
||||
network_mode: host
|
||||
# Needs to be able to read the certificate obtained by the web container.
|
||||
user: "1000:1000"
|
||||
command: ["-c", "/etc/coturn/turnserver.conf"]
|
||||
volumes:
|
||||
- {{ jitsi__base_dir }}/coturn/turnserver.conf:/etc/coturn/turnserver.conf:ro
|
||||
- {{ jitsi__config_dir }}/storage/web/acme-certs/{{ jitsi__domain }}:/certs:ro
|
||||
tmpfs:
|
||||
- /var/lib/coturn
|
||||
{% endif %}
|
||||
{% if jitsi__prometheus_exporter %}
|
||||
prometheus-jitsi-meet-exporter:
|
||||
image: docker.io/systemli/prometheus-jitsi-meet-exporter:1.4.2
|
||||
restart: unless-stopped
|
||||
command: ["-videobridge-url", "http://jvb:8080/colibri/stats"]
|
||||
ports:
|
||||
- "9888:9888"
|
||||
depends_on:
|
||||
- jvb
|
||||
networks:
|
||||
meet.jitsi:
|
||||
{% endif %}
|
||||
{% if not (jitsi__turn or jitsi__prometheus_exporter) %}
|
||||
{}
|
||||
{% endif %}
|
||||
46
roles/jitsi/templates/jitsi.env.j2
Normal file
46
roles/jitsi/templates/jitsi.env.j2
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# {{ ansible_managed }}
|
||||
# Only the settings differing from the defaults are set here.
|
||||
# Reference: {{ jitsi__base_dir }}/release/env.example and
|
||||
# https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker
|
||||
|
||||
# Allow running `docker compose` by hand in {{ jitsi__base_dir }}.
|
||||
COMPOSE_PROJECT_NAME=jitsi
|
||||
COMPOSE_FILE={{ jitsi__compose_files | join(':') }}
|
||||
|
||||
CONFIG={{ jitsi__config_dir }}
|
||||
JITSI_IMAGE_VERSION={{ jitsi__version }}
|
||||
TZ={{ jitsi__timezone }}
|
||||
PUBLIC_URL=https://{{ jitsi__domain }}
|
||||
|
||||
HTTP_PORT=80
|
||||
HTTPS_PORT=443
|
||||
ENABLE_HTTP_REDIRECT=1
|
||||
ENABLE_LETSENCRYPT=1
|
||||
LETSENCRYPT_DOMAIN={{ jitsi__domain }}
|
||||
LETSENCRYPT_EMAIL={{ jitsi__letsencrypt_email }}
|
||||
LETSENCRYPT_ACME_SERVER=letsencrypt
|
||||
{% if jitsi__jvb_advertise_ips | length > 0 %}
|
||||
JVB_ADVERTISE_IPS={{ jitsi__jvb_advertise_ips | join(',') }}
|
||||
{% endif %}
|
||||
{% if jitsi__whiteboard %}
|
||||
|
||||
WHITEBOARD_COLLAB_SERVER_URL_BASE=http://whiteboard.meet.jitsi
|
||||
{% endif %}
|
||||
{% if jitsi__turn %}
|
||||
|
||||
TURN_CREDENTIALS={{ jitsi__turn_secret }}
|
||||
TURN_HOST={{ jitsi__domain }}
|
||||
TURN_PORT=3478
|
||||
TURN_TRANSPORT=udp,tcp
|
||||
TURNS_HOST={{ jitsi__domain }}
|
||||
TURNS_PORT=5349
|
||||
{% endif %}
|
||||
{% if jitsi__env | length > 0 %}
|
||||
|
||||
{% for key, value in jitsi__env.items() %}
|
||||
{{ key }}={{ value }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
JICOFO_AUTH_PASSWORD={{ jitsi__jicofo_auth_password }}
|
||||
JVB_AUTH_PASSWORD={{ jitsi__jvb_auth_password }}
|
||||
34
roles/jitsi/templates/turnserver.conf.j2
Normal file
34
roles/jitsi/templates/turnserver.conf.j2
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# {{ ansible_managed }}
|
||||
listening-port=3478
|
||||
tls-listening-port=5349
|
||||
realm={{ jitsi__domain }}
|
||||
use-auth-secret
|
||||
static-auth-secret={{ jitsi__turn_secret }}
|
||||
cert=/certs/fullchain.pem
|
||||
pkey=/certs/key.pem
|
||||
no-tlsv1
|
||||
no-tlsv1_1
|
||||
no-cli
|
||||
no-multicast-peers
|
||||
no-loopback-peers
|
||||
log-file=stdout
|
||||
# Don't relay to private and otherwise special addresses.
|
||||
denied-peer-ip=0.0.0.0-0.255.255.255
|
||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||
denied-peer-ip=100.64.0.0-100.127.255.255
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=169.254.0.0-169.254.255.255
|
||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||
denied-peer-ip=192.0.0.0-192.0.0.255
|
||||
denied-peer-ip=192.0.2.0-192.0.2.255
|
||||
denied-peer-ip=192.88.99.0-192.88.99.255
|
||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||
denied-peer-ip=198.18.0.0-198.19.255.255
|
||||
denied-peer-ip=198.51.100.0-198.51.100.255
|
||||
denied-peer-ip=203.0.113.0-203.0.113.255
|
||||
denied-peer-ip=240.0.0.0-255.255.255.255
|
||||
denied-peer-ip=::1
|
||||
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
|
||||
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
|
||||
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
4
roles/jitsi/vars/main.yaml
Normal file
4
roles/jitsi/vars/main.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
jitsi__base_dir: /opt/jitsi
|
||||
jitsi__config_dir: "{{ jitsi__base_dir }}/config"
|
||||
jitsi__release_dir: "{{ jitsi__base_dir }}/releases/docker-jitsi-meet-{{ jitsi__version }}"
|
||||
jitsi__compose_files: "{{ ['release/docker-compose.yml'] + (['release/whiteboard.yml'] if jitsi__whiteboard else []) + ['compose.override.yaml'] }}"
|
||||
Loading…
Reference in a new issue