Manage Jitsi through Ansible using custom role #172
21 changed files with 691 additions and 2 deletions
|
|
@ -1,4 +1,7 @@
|
||||||
ansible_pull__age_private_key: ENC[AES256_GCM,data:8PCkZx76R5aWPITn5MCZCMckXvaLfVvnDgO70XmF/eeH5xLVOVx+EC3DZjmHxEoW7Kn4LAJTR4mWl/xP+p++elRvTMcHltV+vBQ=,iv:jKq07NLM84MSGU4CCyUS9ODmwiUMhlDONkKJ7P9P2DM=,tag:95yBZlwYdSNeX2O4t/h9kw==,type:str]
|
ansible_pull__age_private_key: ENC[AES256_GCM,data:8PCkZx76R5aWPITn5MCZCMckXvaLfVvnDgO70XmF/eeH5xLVOVx+EC3DZjmHxEoW7Kn4LAJTR4mWl/xP+p++elRvTMcHltV+vBQ=,iv:jKq07NLM84MSGU4CCyUS9ODmwiUMhlDONkKJ7P9P2DM=,tag:95yBZlwYdSNeX2O4t/h9kw==,type:str]
|
||||||
|
jitsi__jicofo_auth_password: ENC[AES256_GCM,data:a7y36RJUR4OBtVNzgmqlGEsg1hNRGgsuwG2YFJrDlMI=,iv:mnGlKOXtNltNe3wY34BnVCJ9CtLQ7aksr4xXbrTOnJg=,tag:j1N7MUTJG8K0JW/a6YESFg==,type:str]
|
||||||
|
jitsi__jvb_auth_password: ENC[AES256_GCM,data:xUFiIXAzY4pEpwgL/5LPBlhkx73RX6VFT3QJlG5QbFo=,iv:/XqENztrrpj1h3lBhEtJJo78lCrxrMWxRszNjSwx+KQ=,tag:+LRYmv9Tvt0muR47QTd6HA==,type:str]
|
||||||
|
jitsi__turn_secret: ENC[AES256_GCM,data:UPev9ND9Mf2gGK28huQuuxRyUZvPPCmFL9rU1f0zndSPPBMspSsnlgtMy0+VY/CsGEIEcWsy3eYDPcAW/EKFtA==,iv:ew0MKP5zdQvqfla2y7xkcXLm3qSMlf5sucn8Qu3G3Zs=,tag:dqe239LD+wPVqLQFF+nz2A==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
|
|
@ -30,8 +33,8 @@ sops:
|
||||||
QzBBdJ6TiDwJ1rwwM211smfIbcYhWX6C/EfitI5XoIW8aS4ln1g1Ww==
|
QzBBdJ6TiDwJ1rwwM211smfIbcYhWX6C/EfitI5XoIW8aS4ln1g1Ww==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age187zdk64205lx7r83a4m26pfa8hq6khc8sf83k266jasngeq08v3sfqswfg
|
recipient: age187zdk64205lx7r83a4m26pfa8hq6khc8sf83k266jasngeq08v3sfqswfg
|
||||||
lastmodified: "2026-07-29T20:26:44Z"
|
lastmodified: "2026-10-08T07:56:43Z"
|
||||||
mac: ENC[AES256_GCM,data:dHiB768SBppBrjNoSpgDvriCgrSNzIkpSYzm9REx4otbnvNjHYPhP9OdsZ6zTcoFoX+41/zE13r4EoAz4F++jdJidv1+7DMFvVUKMuFNzp2VjDfB1ZNzzyM+px7l83L5eNhgNPp26mweOVMEz92NgB916KMY+5e3uUeSR+iIdho=,iv:7k8rugBXULjPSOZukQiufNn9Vs5uWVhcvFKIhD1o7so=,tag:IE+2uhzGS2xJJgFHuc94CA==,type:str]
|
mac: ENC[AES256_GCM,data:CgD19xHN5nEg9TMQq/c4Xpv1LH0LfcXrkfBMUcbJiIn4FsSzQetLYxueeDXUVOujOb9ZSEin1nR+OZwa/nuwN9mTg37c0XZ6HV0UvNNEIrN/o4doUCQa2jLmbX8etxeNhF7oWJ84OgQIhPLvLwp3Z0A/+kPKbmtz3znQNgKoHQY=,iv:6Dt7z55az2SJovJ7ozG35Q99a08kOrKl/uQCUVcbq8c=,tag:M6p0ZJzoFnWgspnGknq7SQ==,type:str]
|
||||||
pgp:
|
pgp:
|
||||||
- created_at: "2026-09-24T19:54:13Z"
|
- created_at: "2026-09-24T19:54:13Z"
|
||||||
enc: |-
|
enc: |-
|
||||||
|
|
|
||||||
33
inventories/chaosknoten/host_vars/jitsi.yaml
Normal file
33
inventories/chaosknoten/host_vars/jitsi.yaml
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
nftables__config: "{{ lookup('ansible.builtin.file', 'resources/chaosknoten/jitsi/nftables/nftables.conf') }}"
|
||||||
|
|
||||||
|
# renovate: datasource=github-releases depName=jitsi/docker-jitsi-meet versioning=regex:^stable-(?<major>\d+)$
|
||||||
|
jitsi__version: stable-11248
|
||||||
|
jitsi__domain: jitsi.hamburg.ccc.de
|
||||||
|
jitsi__letsencrypt_email: le-admin@hamburg.ccc.de
|
||||||
|
jitsi__jvb_advertise_ips:
|
||||||
|
- 212.12.51.139
|
||||||
|
- 2a00:14b0:f000:23:51:139:0:1
|
||||||
|
jitsi__whiteboard: true
|
||||||
|
jitsi__turn: true
|
||||||
|
jitsi__prometheus_exporter: true
|
||||||
|
# jitsi__jicofo_auth_password, jitsi__jvb_auth_password and jitsi__turn_secret are defined in jitsi.sops.yaml.
|
||||||
|
|
||||||
|
# Branding
|
||||||
|
jitsi__env:
|
||||||
|
DYNAMIC_BRANDING_URL: https://jitsi.hamburg.ccc.de/static/ccchh-branding.json
|
||||||
|
jitsi__custom_interface_config_js: |
|
||||||
|
// Replace the footer with app store links with our own in welcomePageAdditionalContent.html.
|
||||||
|
interfaceConfig.DISPLAY_WELCOME_FOOTER = false;
|
||||||
|
interfaceConfig.DISPLAY_WELCOME_PAGE_CONTENT = true;
|
||||||
|
interfaceConfig.JITSI_WATERMARK_LINK = 'https://hamburg.ccc.de/';
|
||||||
|
jitsi__web_files:
|
||||||
|
- path: images/watermark.svg
|
||||||
|
content: "{{ lookup('ansible.builtin.file', 'resources/chaosknoten/jitsi/web/ccchh-logo.svg') }}"
|
||||||
|
- path: images/favicon.svg
|
||||||
|
content: "{{ lookup('ansible.builtin.file', 'resources/chaosknoten/jitsi/web/round-logo.svg') }}"
|
||||||
|
- path: images/ccchh-background.svg
|
||||||
|
content: "{{ lookup('ansible.builtin.file', 'resources/chaosknoten/jitsi/web/background.svg') }}"
|
||||||
|
- path: static/ccchh-branding.json
|
||||||
|
content: "{{ lookup('ansible.builtin.file', 'resources/chaosknoten/jitsi/web/branding.json') }}"
|
||||||
|
- path: static/welcomePageAdditionalContent.html
|
||||||
|
content: "{{ lookup('ansible.builtin.file', 'resources/chaosknoten/jitsi/web/welcomePageAdditionalContent.html') }}"
|
||||||
|
|
@ -146,6 +146,7 @@ systemd_networkd_hosts:
|
||||||
router:
|
router:
|
||||||
nftables_hosts:
|
nftables_hosts:
|
||||||
hosts:
|
hosts:
|
||||||
|
jitsi:
|
||||||
router:
|
router:
|
||||||
docker_compose_hosts:
|
docker_compose_hosts:
|
||||||
hosts:
|
hosts:
|
||||||
|
|
@ -263,6 +264,9 @@ wiki_hosts:
|
||||||
netbox_hosts:
|
netbox_hosts:
|
||||||
hosts:
|
hosts:
|
||||||
netbox:
|
netbox:
|
||||||
|
jitsi_hosts:
|
||||||
|
hosts:
|
||||||
|
jitsi:
|
||||||
proxmox_vm_template_hosts:
|
proxmox_vm_template_hosts:
|
||||||
hosts:
|
hosts:
|
||||||
chaosknoten:
|
chaosknoten:
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,13 @@
|
||||||
tags:
|
tags:
|
||||||
- docker_compose
|
- docker_compose
|
||||||
|
|
||||||
|
- name: Ensure Jitsi deployment on jitsi_hosts
|
||||||
|
hosts: jitsi_hosts
|
||||||
|
roles:
|
||||||
|
- jitsi
|
||||||
|
tags:
|
||||||
|
- jitsi
|
||||||
|
|
||||||
- name: Ensure NGINX deployment on nginx_hosts
|
- name: Ensure NGINX deployment on nginx_hosts
|
||||||
hosts: nginx_hosts:!public_reverse_proxy_hosts
|
hosts: nginx_hosts:!public_reverse_proxy_hosts
|
||||||
roles:
|
roles:
|
||||||
|
|
|
||||||
65
resources/chaosknoten/jitsi/nftables/nftables.conf
Normal file
65
resources/chaosknoten/jitsi/nftables/nftables.conf
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/usr/sbin/nft -f
|
||||||
|
|
||||||
|
# Docker manages its own tables (via iptables-nft), so don't use "flush ruleset" here,
|
||||||
|
# but only replace our own table. This also means that a reload is fine, but stopping
|
||||||
|
# or restarting nftables.service (which flushes the whole ruleset) requires restarting
|
||||||
|
# Docker afterwards.
|
||||||
|
# Traffic to ports published by Docker containers (web, JVB, exporter) gets DNATed in
|
||||||
|
# prerouting and therefore passes the forward hook instead of the input hook.
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
define if_wan = "ens18"
|
||||||
|
|
||||||
|
# Prometheus on grafana (IPv4 via the NAT of router)
|
||||||
|
define prometheus_v4 = { 212.12.48.123 }
|
||||||
|
define prometheus_v6 = { 2a00:14b0:42:102::e }
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
table inet host_filter
|
||||||
|
delete table inet host_filter
|
||||||
|
|
||||||
|
table inet host_filter {
|
||||||
|
# Runs before Docker's DNAT, so this also applies to ports published by containers.
|
||||||
|
chain prerouting {
|
||||||
|
type filter hook prerouting priority dstnat - 10; policy accept;
|
||||||
|
|
||||||
|
iifname $if_wan tcp dport 9888 ip saddr $prometheus_v4 accept comment "jitsi exporter (container)"
|
||||||
|
iifname $if_wan tcp dport 9888 ip6 saddr $prometheus_v6 accept comment "jitsi exporter (container)"
|
||||||
|
iifname $if_wan tcp dport 9888 drop
|
||||||
|
}
|
||||||
|
|
||||||
|
chain input {
|
||||||
|
type filter hook input priority filter; policy drop;
|
||||||
|
|
||||||
|
iifname "lo" accept
|
||||||
|
ct state invalid drop
|
||||||
|
ct state established,related accept
|
||||||
|
|
||||||
|
# ICMPv6 is needed for neighbour discovery etc.
|
||||||
|
meta l4proto ipv6-icmp accept
|
||||||
|
meta l4proto icmp accept
|
||||||
|
|
||||||
|
# Docker networks
|
||||||
|
iifname { "docker0", "br-*" } accept
|
||||||
|
|
||||||
|
tcp dport 22 accept comment "ssh"
|
||||||
|
|
||||||
|
# Ports published by containers normally get DNATed and never reach the input
|
||||||
|
# hook, but they do when handled by docker-proxy (e.g. for IPv6 without IPv6
|
||||||
|
# enabled on the Docker network).
|
||||||
|
tcp dport { 80, 443 } accept comment "jitsi web (container)"
|
||||||
|
udp dport 10000 accept comment "jitsi jvb (container)"
|
||||||
|
|
||||||
|
# coturn (runs in the host network namespace)
|
||||||
|
udp dport 3478 accept comment "turn"
|
||||||
|
tcp dport 3478 accept comment "turn"
|
||||||
|
tcp dport 5349 accept comment "turns"
|
||||||
|
udp dport 5349 accept comment "turns (dtls)"
|
||||||
|
udp dport 49152-65535 accept comment "turn relay ports"
|
||||||
|
|
||||||
|
tcp dport { 9100, 9888 } ip saddr $prometheus_v4 accept comment "node exporter, jitsi exporter (container)"
|
||||||
|
tcp dport { 9100, 9888 } ip6 saddr $prometheus_v6 accept comment "node exporter, jitsi exporter (container)"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
resources/chaosknoten/jitsi/web/background.svg
Normal file
18
resources/chaosknoten/jitsi/web/background.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.8 KiB |
5
resources/chaosknoten/jitsi/web/branding.json
Normal file
5
resources/chaosknoten/jitsi/web/branding.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"logoImageUrl": "https://jitsi.hamburg.ccc.de/images/watermark.svg",
|
||||||
|
"logoClickUrl": "https://hamburg.ccc.de/",
|
||||||
|
"backgroundImageUrl": "https://jitsi.hamburg.ccc.de/images/ccchh-background.svg"
|
||||||
|
}
|
||||||
5
resources/chaosknoten/jitsi/web/ccchh-logo.svg
Normal file
5
resources/chaosknoten/jitsi/web/ccchh-logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 84 KiB |
7
resources/chaosknoten/jitsi/web/round-logo.svg
Normal file
7
resources/chaosknoten/jitsi/web/round-logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.3 KiB |
|
|
@ -0,0 +1,13 @@
|
||||||
|
<template id = "welcome-page-additional-content-template">
|
||||||
|
<footer class = "welcome-footer">
|
||||||
|
<div class = "welcome-footer-centered">
|
||||||
|
<div class = "welcome-footer-padded">
|
||||||
|
<div class = "welcome-footer-row-block welcome-footer--row-1" style = "flex-wrap: wrap; gap: 16px;">
|
||||||
|
<a href = "https://wiki.hamburg.ccc.de/infrastructure:services:jitsi" style = "color: #fff;">Technical Information for CCCHH Jitsi</a>
|
||||||
|
<a href = "https://hamburg.ccc.de/imprint/" style = "color: #fff;">Imprint and Contact</a>
|
||||||
|
<a href = "https://hamburg.ccc.de/privacy-notice/" style = "color: #fff;">Privacy Policy</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
64
roles/jitsi/README.md
Normal file
64
roles/jitsi/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
Files can be added to or replaced in the Jitsi Meet web root (`/usr/share/jitsi-meet` in the web container) using `jitsi__web_files`, e.g. for branding.
|
||||||
|
They are deployed to `/opt/jitsi/web-files/` and bind-mounted into the web container.
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
- `jitsi__web_files`: List of files to mount into the Jitsi Meet web root.
|
||||||
|
Defaults to `[ ]`.
|
||||||
|
- `jitsi__web_files.*.path`: Path relative to the web root, e.g. `images/watermark.svg` or `static/welcomePageAdditionalContent.html`.
|
||||||
|
- `jitsi__web_files.*.content`: The content of the 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>
|
||||||
9
roles/jitsi/defaults/main.yaml
Normal file
9
roles/jitsi/defaults/main.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
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: ""
|
||||||
|
jitsi__web_files: [ ]
|
||||||
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
|
||||||
69
roles/jitsi/meta/argument_specs.yaml
Normal file
69
roles/jitsi/meta/argument_specs.yaml
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
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: ""
|
||||||
|
jitsi__web_files:
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
required: false
|
||||||
|
default: [ ]
|
||||||
|
options:
|
||||||
|
path:
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
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
|
||||||
226
roles/jitsi/tasks/main.yaml
Normal file
226
roles/jitsi/tasks/main.yaml
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
- 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
|
||||||
|
|
||||||
|
# Files mounted into the web container, replacing or adding to the files of Jitsi Meet.
|
||||||
|
- name: make sure the directories for the web files exist
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ jitsi__base_dir }}/web-files/{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
become: true
|
||||||
|
loop: "{{ [''] + (jitsi__web_files | map(attribute='path') | map('dirname') | unique | reject('equalto', '') | list) }}"
|
||||||
|
|
||||||
|
- name: deploy the web files
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.content }}"
|
||||||
|
dest: "{{ jitsi__base_dir }}/web-files/{{ item.path }}"
|
||||||
|
mode: "0644"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
become: true
|
||||||
|
loop: "{{ jitsi__web_files }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
# Single file bind mounts keep pointing to the replaced file until the container is restarted.
|
||||||
|
notify: restart jitsi web
|
||||||
|
|
||||||
|
- name: find web files to remove
|
||||||
|
ansible.builtin.find:
|
||||||
|
paths: "{{ jitsi__base_dir }}/web-files"
|
||||||
|
recurse: true
|
||||||
|
register: jitsi__web_files_found
|
||||||
|
|
||||||
|
- name: remove web files, which should be removed
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
become: true
|
||||||
|
loop: "{{ jitsi__web_files_found.files | map(attribute='path') | reject('in', jitsi__web_files_paths) | list }}"
|
||||||
|
vars:
|
||||||
|
jitsi__web_files_paths: "{{ jitsi__web_files | map(attribute='path') | map('regex_replace', '^', jitsi__base_dir ~ '/web-files/') | list }}"
|
||||||
|
|
||||||
|
# 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 }}"
|
||||||
47
roles/jitsi/templates/compose.override.yaml.j2
Normal file
47
roles/jitsi/templates/compose.override.yaml.j2
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# {{ 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__web_files | length > 0 %}
|
||||||
|
web:
|
||||||
|
volumes:
|
||||||
|
{% for file in jitsi__web_files %}
|
||||||
|
- {{ jitsi__base_dir }}/web-files/{{ file.path }}:/usr/share/jitsi-meet/{{ file.path }}:ro
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% 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__web_files or 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'] }}"
|
||||||
|
|
@ -13,3 +13,10 @@
|
||||||
group: root
|
group: root
|
||||||
become: true
|
become: true
|
||||||
notify: Reload nftables service
|
notify: Reload nftables service
|
||||||
|
|
||||||
|
- name: ensure nftables service is enabled and started
|
||||||
|
ansible.builtin.systemd_service:
|
||||||
|
name: nftables
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
become: true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue