Compare commits

..
Author SHA1 Message Date
0ec52411e4 Update all stable non-major dependencies
Some checks failed
/ build (pull_request) Successful in 53s
/ Ansible Lint (pull_request) Failing after 2m58s
/ Ansible Lint (push) Failing after 3m12s
2026-10-20 14:16:58 +00:00
ce96105463 Make Jitsi JVB work correctly with IPv6
All checks were successful
/ build (push) Successful in 25s
/ Ansible Lint (push) Successful in 3m43s
Since the Jitsi Docker Compose stack does not properly take IPv6 into consideration, patch the JVB config to add mappings from the private IPv6 address of the Docker network to the public IPv6 address.

This should be the source of the reported problems with conferences with 3+ people.
2026-10-20 15:34:05 +02:00
442231323c disable ccchoir.de for now
All checks were successful
/ build (push) Successful in 26s
/ Ansible Lint (push) Successful in 3m30s
2026-10-20 15:05:55 +02:00
stb
c02b7e6f35 Merge branch 'main' into jitsi-docker-compose-role
All checks were successful
/ cleanup-staging (pull_request) Successful in 3s
/ build (pull_request) Successful in 49s
/ build (push) Successful in 29s
/ Ansible Lint (push) Successful in 4m16s
/ Ansible Lint (pull_request) Successful in 4m19s
2026-10-20 15:04:15 +02:00
stb
c898a0ec0e Merge branch 'main' into jitsi-docker-compose-role
All checks were successful
/ build (pull_request) Successful in 58s
/ Ansible Lint (push) Successful in 3m14s
/ Ansible Lint (pull_request) Successful in 3m14s
2026-10-14 12:57:38 +02:00
stb
816c7497fa Merge branch 'main' into jitsi-docker-compose-role
All checks were successful
/ build (pull_request) Successful in 30s
/ Ansible Lint (push) Successful in 5m9s
/ Ansible Lint (pull_request) Successful in 5m46s
2026-10-08 11:12:38 +02:00
8bf5690275 Customize with our logos
All checks were successful
/ build (pull_request) Successful in 30s
/ Ansible Lint (push) Successful in 2m58s
/ Ansible Lint (pull_request) Successful in 3m15s
2026-10-08 10:50:48 +02:00
610a1faca8 Allow UI customization 2026-10-08 10:50:23 +02:00
86e793c624 Use jitsi role to configure jitsi
All checks were successful
/ Ansible Lint (push) Successful in 3m47s
/ build (pull_request) Successful in 44s
/ Ansible Lint (pull_request) Successful in 3m45s
The host has been manually cleaned up (removed old packages and config) and this config has been deployed already.
2026-10-08 10:24:38 +02:00
dbac5ede16 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.
2026-10-08 10:23:17 +02:00
23 changed files with 781 additions and 20 deletions

View file

@ -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]
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:
age:
- enc: |
@ -30,8 +33,8 @@ sops:
QzBBdJ6TiDwJ1rwwM211smfIbcYhWX6C/EfitI5XoIW8aS4ln1g1Ww==
-----END AGE ENCRYPTED FILE-----
recipient: age187zdk64205lx7r83a4m26pfa8hq6khc8sf83k266jasngeq08v3sfqswfg
lastmodified: "2026-07-29T20:26:44Z"
mac: ENC[AES256_GCM,data:dHiB768SBppBrjNoSpgDvriCgrSNzIkpSYzm9REx4otbnvNjHYPhP9OdsZ6zTcoFoX+41/zE13r4EoAz4F++jdJidv1+7DMFvVUKMuFNzp2VjDfB1ZNzzyM+px7l83L5eNhgNPp26mweOVMEz92NgB916KMY+5e3uUeSR+iIdho=,iv:7k8rugBXULjPSOZukQiufNn9Vs5uWVhcvFKIhD1o7so=,tag:IE+2uhzGS2xJJgFHuc94CA==,type:str]
lastmodified: "2026-10-08T07:56:43Z"
mac: ENC[AES256_GCM,data:CgD19xHN5nEg9TMQq/c4Xpv1LH0LfcXrkfBMUcbJiIn4FsSzQetLYxueeDXUVOujOb9ZSEin1nR+OZwa/nuwN9mTg37c0XZ6HV0UvNNEIrN/o4doUCQa2jLmbX8etxeNhF7oWJ84OgQIhPLvLwp3Z0A/+kPKbmtz3znQNgKoHQY=,iv:6Dt7z55az2SJovJ7ozG35Q99a08kOrKl/uQCUVcbq8c=,tag:M6p0ZJzoFnWgspnGknq7SQ==,type:str]
pgp:
- created_at: "2026-09-24T19:54:13Z"
enc: |-

View 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') }}"

View file

@ -146,6 +146,7 @@ systemd_networkd_hosts:
router:
nftables_hosts:
hosts:
jitsi:
router:
docker_compose_hosts:
hosts:
@ -263,6 +264,9 @@ wiki_hosts:
netbox_hosts:
hosts:
netbox:
jitsi_hosts:
hosts:
jitsi:
proxmox_vm_template_hosts:
hosts:
chaosknoten:

View file

@ -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:

View file

@ -16,24 +16,24 @@ services:
backend:
restart: unless-stopped
app:
image: docker.io/library/wordpress:7-php8.2
environment:
- "WORDPRESS_DB_HOST=database"
- "WORDPRESS_DB_NAME=wordpress"
- "WORDPRESS_DB_USER=wordpress"
- "WORDPRESS_TABLE_PREFIX=wp_"
- "WORDPRESS_DB_PASSWORD={{ secret__wordpress_db_password }}"
volumes:
- wordpress:/var/www/html/wp-content
ports:
- "127.0.0.1:3000:80"
networks:
backend:
frontend:
restart: unless-stopped
depends_on:
- database
# app:
# image: docker.io/library/wordpress:7-php8.2
# environment:
# - "WORDPRESS_DB_HOST=database"
# - "WORDPRESS_DB_NAME=wordpress"
# - "WORDPRESS_DB_USER=wordpress"
# - "WORDPRESS_TABLE_PREFIX=wp_"
# - "WORDPRESS_DB_PASSWORD={{ secret__wordpress_db_password }}"
# volumes:
# - wordpress:/var/www/html/wp-content
# ports:
# - "127.0.0.1:3000:80"
# networks:
# backend:
# frontend:
# restart: unless-stopped
# depends_on:
# - database
volumes:
database: {}

View 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)"
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

View 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"
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 84 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -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>

70
roles/jitsi/README.md Normal file
View file

@ -0,0 +1,70 @@
# 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.
IPv4 addresses are put into `JVB_ADVERTISE_IPS`, which docker-jitsi-meet maps to the IPv4 address of the JVB container.
IPv6 addresses are mapped to the IPv6 address of the JVB container in `custom-jvb.conf` instead, for which the `meet.jitsi` network gets a fixed IPv6 subnet and the JVB a fixed IPv6 address.
Defaults to `[ ]`.
- `jitsi__ipv6_subnet`: The IPv6 subnet of the `meet.jitsi` network, if IPv6 addresses are advertised.
Defaults to `fd4a:7c1e:93b5:1::/64`.
- `jitsi__jvb_ipv6_address`: The IPv6 address of the JVB container within `jitsi__ipv6_subnet`.
Defaults to `fd4a:7c1e:93b5:1::10`.
- `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>

View file

@ -0,0 +1,11 @@
jitsi__timezone: Europe/Berlin
jitsi__jvb_advertise_ips: [ ]
jitsi__ipv6_subnet: fd4a:7c1e:93b5:1::/64
jitsi__jvb_ipv6_address: fd4a:7c1e:93b5:1::10
jitsi__whiteboard: false
jitsi__turn: false
jitsi__prometheus_exporter: false
jitsi__env: { }
jitsi__custom_config_js: ""
jitsi__custom_interface_config_js: ""
jitsi__web_files: [ ]

View file

@ -0,0 +1,29 @@
- 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 jvb
community.docker.docker_compose_v2:
project_src: "{{ jitsi__base_dir }}"
project_name: jitsi
files: "{{ jitsi__compose_files }}"
services:
- jvb
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

View file

@ -0,0 +1,77 @@
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__ipv6_subnet:
type: str
required: false
default: fd4a:7c1e:93b5:1::/64
jitsi__jvb_ipv6_address:
type: str
required: false
default: fd4a:7c1e:93b5:1::10
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

View file

@ -0,0 +1,4 @@
---
allow_duplicates: false
dependencies:
- role: docker

245
roles/jitsi/tasks/main.yaml Normal file
View file

@ -0,0 +1,245 @@
- 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: deploy custom JVB configuration
ansible.builtin.template:
src: custom-jvb.conf.j2
dest: "{{ jitsi__config_dir }}/jvb/custom-jvb.conf"
mode: "0644"
owner: root
group: root
become: true
when: jitsi__jvb_advertise_ipv6s | length > 0
notify: restart jitsi jvb
- name: remove unconfigured custom JVB configuration
ansible.builtin.file:
path: "{{ jitsi__config_dir }}/jvb/custom-jvb.conf"
state: absent
become: true
when: jitsi__jvb_advertise_ipv6s | length == 0
notify: restart jitsi jvb
- 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 }}"

View file

@ -0,0 +1,59 @@
# {{ 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
{% if jitsi__jvb_advertise_ipv6s | length > 0 %}
# A fixed subnet, so the JVB can have a fixed IPv6 address to map the public ones to.
ipam:
config:
- subnet: {{ jitsi__ipv6_subnet }}
{% endif %}
services:
{% if jitsi__jvb_advertise_ipv6s | length > 0 %}
jvb:
networks:
meet.jitsi:
ipv6_address: {{ jitsi__jvb_ipv6_address }}
{% endif %}
{% 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__jvb_advertise_ipv6s or jitsi__web_files or jitsi__turn or jitsi__prometheus_exporter) %}
{}
{% endif %}

View file

@ -0,0 +1,11 @@
# {{ ansible_managed }}
# Included at the end of the generated jvb.conf.
# Map the advertised IPv6 addresses to the pinned IPv6 address of the JVB, in addition to
# the mappings of the IPv4 addresses generated from JVB_ADVERTISE_IPS.
{% for ip in jitsi__jvb_advertise_ipv6s %}
ice4j.harvest.mapping.static-mappings += {
local-address = "{{ jitsi__jvb_ipv6_address }}"
public-address = "{{ ip }}"
name = "ipv6-{{ loop.index0 }}"
}
{% endfor %}

View 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_ipv4s | length > 0 %}
JVB_ADVERTISE_IPS={{ jitsi__jvb_advertise_ipv4s | 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 }}

View 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

View file

@ -0,0 +1,8 @@
jitsi__base_dir: /opt/jitsi
jitsi__config_dir: "{{ jitsi__base_dir }}/config"
jitsi__release_dir: "{{ jitsi__base_dir }}/releases/docker-jitsi-meet-{{ jitsi__version }}"
# docker-jitsi-meet maps all JVB_ADVERTISE_IPS to the container's IPv4 address, so IPv6
# addresses are mapped to the pinned IPv6 address of the JVB in custom-jvb.conf instead.
jitsi__jvb_advertise_ipv4s: "{{ jitsi__jvb_advertise_ips | reject('search', ':') | list }}"
jitsi__jvb_advertise_ipv6s: "{{ jitsi__jvb_advertise_ips | select('search', ':') | list }}"
jitsi__compose_files: "{{ ['release/docker-compose.yml'] + (['release/whiteboard.yml'] if jitsi__whiteboard else []) + ['compose.override.yaml'] }}"

View file

@ -13,3 +13,10 @@
group: root
become: true
notify: Reload nftables service
- name: ensure nftables service is enabled and started
ansible.builtin.systemd_service:
name: nftables
enabled: true
state: started
become: true