├── .gitignore ├── requirements.yml ├── templates ├── prosody_focus_auth.j2 ├── prosody_config.j2 ├── videobridge_config.j2 ├── jicofo_config.j2 ├── jitsi_meet_nginx.conf.j2 └── jitsi_meet_config.js.j2 ├── examples ├── test.yml └── use-letsencrypt-certs.yml ├── meta └── main.yml ├── handlers └── main.yml ├── tasks ├── ufw.yml ├── jicofo.yml ├── nginx.yml ├── main.yml ├── browser_extensions.yml ├── jitsi_meet.yml ├── clean_up_default_configs.yml ├── prosody.yml ├── packages.yml └── dynamic_vars.yml ├── spec ├── spec_helper.rb ├── ufw_spec.rb ├── ssl_keys_spec.rb ├── videobridge_spec.rb ├── jitsi_meet_spec.rb ├── jicofo_spec.rb ├── nginx_spec.rb ├── apt_repo_spec.rb └── prosody_spec.rb ├── molecule.yml ├── defaults └── main.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .molecule/ 2 | .vagrant/ 3 | .kitchen/ 4 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - src: https://github.com/thefinn93/ansible-letsencrypt 3 | name: thefinn93.letsencrypt 4 | -------------------------------------------------------------------------------- /templates/prosody_focus_auth.j2: -------------------------------------------------------------------------------- 1 | return { 2 | ["password"] = "{{ jitsi_meet_jicofo_password }}"; 3 | }; 4 | -------------------------------------------------------------------------------- /examples/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test jitsi-meet role (stable repo) 3 | hosts: jitsi-meet-stable 4 | roles: 5 | - role: ../ansible-role-jitsi-meet 6 | jitsi_meet_use_nightly_apt_repo: false 7 | 8 | - name: Test jitsi-meet role (unstable repo) 9 | hosts: jitsi-meet-unstable 10 | roles: 11 | - role: ../ansible-role-jitsi-meet 12 | jitsi_meet_use_nightly_apt_repo: true 13 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: 4 | - Kevin Gallagher (@ageis) 5 | - Conor Schaefer (@conorsch) 6 | description: Installs Jitsi Meet videoconferencing software 7 | company: Freedom of the Press Foundation (@freedomofpress) 8 | license: MIT 9 | min_ansible_version: 1.9.4 10 | platforms: 11 | - name: Debian 12 | versions: 13 | - jessie 14 | categories: 15 | - system 16 | dependencies: [] 17 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for jitsi-meet 3 | - name: restart nginx 4 | service: 5 | name: nginx 6 | state: restarted 7 | 8 | - name: restart prosody 9 | service: 10 | name: prosody 11 | state: restarted 12 | 13 | - name: restart jicofo 14 | service: 15 | name: jicofo 16 | state: restarted 17 | 18 | - name: restart jitsi-videobridge 19 | service: 20 | name: jitsi-videobridge 21 | state: restarted 22 | -------------------------------------------------------------------------------- /tasks/ufw.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install ufw package. 3 | apt: 4 | name: ufw 5 | state: present 6 | 7 | - name: Specify allowed ports in firewall config. 8 | ufw: 9 | rule: allow 10 | port: "{{ item }}" 11 | proto: tcp 12 | with_items: 13 | - 22 14 | - 80 15 | - 443 16 | 17 | - name: Open UDP port 10000 for jitsi-videobridge 18 | ufw: 19 | rule: allow 20 | port: 10000 21 | proto: udp 22 | 23 | - name: Ensure UFW is running. 24 | ufw: 25 | name: ufw 26 | state: enabled 27 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | require 'net/ssh' 3 | 4 | host = ENV['TARGET_HOST'] 5 | ssh_config_files = ['./.vagrant/ssh-config'] + Net::SSH::Config.default_files 6 | options = Net::SSH::Config.for(host, ssh_config_files) 7 | options[:user] ||= 'vagrant' 8 | options[:keys].push("#{Dir.home}/.vagrant.d/insecure_private_key") 9 | 10 | set :backend, :ssh 11 | set :host, host 12 | set :ssh_options, options 13 | 14 | RSpec.configure do |config| 15 | config.color = true 16 | config.tty = true 17 | config.formatter = :documentation 18 | end 19 | -------------------------------------------------------------------------------- /spec/ufw_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | ufw_expected_rules = [ 4 | %r{ 22/tcp + ALLOW IN +Anywhere}, 5 | %r{ 80/tcp + ALLOW IN +Anywhere}, 6 | %r{ 443/tcp + ALLOW IN +Anywhere}, 7 | %r{ 10000/udp + ALLOW IN +Anywhere} 8 | ] 9 | 10 | describe command('ufw status numbered') do 11 | its(:stdout) { should match(/^Status: active/) } 12 | ufw_expected_rules.each do |ufw_expected_rule| 13 | its(:stdout) { should match(ufw_expected_rule) } 14 | end 15 | end 16 | 17 | describe service('ufw') do 18 | it { should be_enabled } 19 | # Debian 8 uses systemctl, but not for ufw, oddly. 20 | it { should be_running } unless os[:family] == 'debian' && os[:release] >= '8' 21 | end 22 | -------------------------------------------------------------------------------- /spec/ssl_keys_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | ssl_keypairs = [ 4 | { 'key' => '/var/lib/prosody/localhost.key', 5 | 'cert' => '/var/lib/prosody/localhost.crt' } 6 | ] 7 | ssl_keypairs.each do |ssl_keypair| 8 | describe x509_certificate(ssl_keypair['cert']) do 9 | it { should be_certificate } 10 | it { should be_valid } 11 | its(:keylength) { should be >= 2048 } 12 | its(:validity_in_days) { should be >= 30 } 13 | its(:validity_in_days) { should_not be < 30 } 14 | end 15 | 16 | describe x509_private_key(ssl_keypair['key']) do 17 | it { should_not be_encrypted } 18 | it { should be_valid } 19 | it { should have_matching_certificate ssl_keypair['cert'] } 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /tasks/jicofo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Copy Jicofo config file. 3 | template: 4 | dest: /etc/jitsi/jicofo/config 5 | src: jicofo_config.j2 6 | owner: root 7 | group: root 8 | mode: "0644" 9 | notify: restart jicofo 10 | 11 | - name: Copy Jitsi Videobridge config file. 12 | template: 13 | dest: /etc/jitsi/videobridge/config 14 | src: videobridge_config.j2 15 | owner: root 16 | group: root 17 | mode: "0644" 18 | notify: restart jitsi-videobridge 19 | 20 | - name: Set Jicofo log level. 21 | lineinfile: 22 | dest: /etc/jitsi/jicofo/logging.properties 23 | regexp: '^\.level' 24 | line: ".level={{ jitsi_meet_jicofo_loglevel }}" 25 | state: present 26 | notify: restart jicofo 27 | -------------------------------------------------------------------------------- /examples/use-letsencrypt-certs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure jitsi-meet server. 3 | hosts: jitsi 4 | vars: 5 | # Change this to match the DNS entry for your host IP. 6 | jitsi_meet_server_name: meet.example.com 7 | roles: 8 | - role: thefinn93.letsencrypt 9 | become: yes 10 | letsencrypt_email: "webmaster@{{ jitsi_meet_server_name }}" 11 | letsencrypt_cert_domains: 12 | - "{{ jitsi_meet_server_name }}" 13 | tags: letsencrypt 14 | 15 | - role: ansible-role-jitsi-meet 16 | jitsi_meet_ssl_cert_path: "/etc/letsencrypt/live/{{ jitsi_meet_server_name }}/fullchain.pem" 17 | jitsi_meet_ssl_key_path: "/etc/letsencrypt/live/{{ jitsi_meet_server_name }}/privkey.pem" 18 | become: yes 19 | tags: jitsi 20 | -------------------------------------------------------------------------------- /molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ansible: 3 | playbook: examples/test.yml 4 | verbose: vv 5 | 6 | molecule: 7 | serverspec_dir: spec/ 8 | 9 | verifier: 10 | name: serverspec 11 | 12 | vagrant: 13 | platforms: 14 | - name: jessie64 15 | box: debian/jessie64 16 | 17 | - name: trusty64 18 | box: ubuntu/trusty64 19 | 20 | providers: 21 | - name: virtualbox 22 | type: virtualbox 23 | 24 | instances: 25 | - name: jitsi-meet-stable 26 | raw_config_args: 27 | - "vm.network 'forwarded_port', guest: 443, host: 4443" 28 | - "vm.synced_folder './', '/vagrant', disabled: true" 29 | 30 | - name: jitsi-meet-unstable 31 | raw_config_args: 32 | - "vm.network 'forwarded_port', guest: 443, host: 4444" 33 | - "vm.synced_folder './', '/vagrant', disabled: true" 34 | -------------------------------------------------------------------------------- /tasks/nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Copy Nginx config for Jitsi Meet domain. 3 | template: 4 | dest: "/etc/nginx/sites-available/{{ jitsi_meet_server_name }}.conf" 5 | src: jitsi_meet_nginx.conf.j2 6 | owner: root 7 | group: root 8 | mode: "0644" 9 | notify: restart nginx 10 | # Skip unless role was called with custom SSL certs 11 | when: jitsi_meet_ssl_cert_path != '' and 12 | jitsi_meet_ssl_key_path != '' 13 | 14 | - name: Deactivate default Nginx site. 15 | file: 16 | path: /etc/nginx/sites-enabled/default 17 | state: absent 18 | notify: restart nginx 19 | 20 | - name: Activate custom Jitsi Meet Nginx site. 21 | file: 22 | path: "/etc/nginx/sites-enabled/{{ jitsi_meet_server_name }}.conf" 23 | src: "/etc/nginx/sites-available/{{ jitsi_meet_server_name }}.conf" 24 | state: link 25 | notify: restart nginx 26 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: packages.yml 3 | 4 | # Read Jitsi Meet config files and extract secrets for use in template configs. 5 | # Import to run these tasks after package installation, but before config tasks. 6 | - include: dynamic_vars.yml 7 | 8 | - include: prosody.yml 9 | 10 | - include: jitsi_meet.yml 11 | 12 | - include: jicofo.yml 13 | 14 | - include: nginx.yml 15 | when: jitsi_meet_configure_nginx 16 | 17 | - include: clean_up_default_configs.yml 18 | when: (jitsi_meet_ssl_cert_path != '' and 19 | jitsi_meet_ssl_key_path != '') or 20 | not jitsi_meet_configure_nginx 21 | 22 | 23 | - include: ufw.yml 24 | when: jitsi_meet_configure_firewall == true 25 | 26 | # Placing the browser extensions last so the associated debugging tasks 27 | # that display URLs are visible near the end of the play. 28 | - include: browser_extensions.yml 29 | -------------------------------------------------------------------------------- /templates/prosody_config.j2: -------------------------------------------------------------------------------- 1 | VirtualHost "{{ jitsi_meet_server_name }}" 2 | authentication = "{{ jitsi_meet_authentication }}" 3 | ssl = { 4 | key = "/var/lib/prosody/{{ jitsi_meet_server_name }}.key"; 5 | certificate = "/var/lib/prosody/{{ jitsi_meet_server_name }}.crt"; 6 | } 7 | 8 | modules_enabled = { 9 | "bosh"; 10 | "pubsub"; 11 | "ping"; 12 | } 13 | 14 | VirtualHost "auth.{{ jitsi_meet_server_name }}" 15 | authentication = "internal_plain" 16 | 17 | Component "conference.{{ jitsi_meet_server_name }}" "muc" 18 | 19 | admins = { "focus@auth.{{ jitsi_meet_server_name }}" } 20 | 21 | Component "jitsi-videobridge.{{ jitsi_meet_server_name }}" 22 | component_secret = "{{ jitsi_meet_videobridge_secret }}" 23 | 24 | Component "focus.{{ jitsi_meet_server_name }}" 25 | component_secret = "{{ jitsi_meet_jicofo_secret }}" 26 | -------------------------------------------------------------------------------- /tasks/browser_extensions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Copy Chrome web browser extension. 3 | copy: 4 | src: "{{ jitsi_meet_desktop_sharing_chrome_extension_filename }}" 5 | dest: "/usr/share/jitsi-meet/{{ jitsi_meet_desktop_sharing_chrome_extension_filename | basename }}" 6 | notify: restart nginx 7 | when: jitsi_meet_desktop_sharing_chrome_extension_filename is defined and 8 | jitsi_meet_desktop_sharing_chrome_extension_filename != '' 9 | 10 | - name: Display URL for downloading Chrome web browser extension. 11 | debug: 12 | msg: >- 13 | Installed Chrome web extension. Participants who wish to share 14 | their screens in Jitsi Meet must download the extension from 15 | 'https://{{ jitsi_meet_server_name }}/{{ jitsi_meet_desktop_sharing_chrome_extension_filename | basename }}', 16 | then drag-and-drop onto the chrome://extensions page. 17 | when: jitsi_meet_desktop_sharing_chrome_extension_filename is defined and 18 | jitsi_meet_desktop_sharing_chrome_extension_filename != '' 19 | -------------------------------------------------------------------------------- /templates/videobridge_config.j2: -------------------------------------------------------------------------------- 1 | # Jitsi Videobridge settings 2 | 3 | # sets the XMPP domain (default: none) 4 | JVB_HOSTNAME={{ jitsi_meet_server_name }} 5 | 6 | # sets the hostname of the XMPP server (default: domain if set, localhost otherwise) 7 | JVB_HOST= 8 | 9 | # sets the port of the XMPP server (default: 5275) 10 | JVB_PORT={{ jitsi_meet_videobridge_port }} 11 | 12 | # sets the shared secret used to authenticate to the XMPP server 13 | JVB_SECRET={{ jitsi_meet_videobridge_secret }} 14 | 15 | # extra options to pass to the JVB daemon 16 | JVB_OPTS="" 17 | 18 | # extra jvm params 19 | #JVB_EXTRA_JVM_PARAMS="-javaagent:/usr/share/newrelic/newrelic.jar -Dnewrelic.config.file=/etc/jitsi/videobridge/newrelic.yml" 20 | 21 | # adds java system props that are passed to jvb (default are for home and logging config file) 22 | JAVA_SYS_PROPS="$JVB_EXTRA_JVM_PARAMS -Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/etc/jitsi -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=videobridge -Dnet.java.sip.communicator.SC_LOG_DIR_LOCATION=/var/log/jitsi -Djava.util.logging.config.file=/etc/jitsi/videobridge/logging.properties" 23 | -------------------------------------------------------------------------------- /templates/jicofo_config.j2: -------------------------------------------------------------------------------- 1 | # Jitsi Conference Focus settings 2 | # sets the host name of the XMPP server 3 | JICOFO_HOST=localhost 4 | 5 | # sets the XMPP domain (default: none) 6 | JICOFO_HOSTNAME={{ jitsi_meet_server_name }} 7 | 8 | # sets the secret used to authenticate as an XMPP component 9 | JICOFO_SECRET={{ jitsi_meet_jicofo_secret }} 10 | 11 | # sets the port to use for the XMPP component connection 12 | JICOFO_PORT={{ jitsi_meet_jicofo_port }} 13 | 14 | # sets the XMPP domain name to use for XMPP user logins 15 | JICOFO_AUTH_DOMAIN=auth.{{ jitsi_meet_server_name }} 16 | 17 | # sets the username to use for XMPP user logins 18 | JICOFO_AUTH_USER={{ jitsi_meet_jicofo_user }} 19 | 20 | # sets the password to use for XMPP user logins 21 | JICOFO_AUTH_PASSWORD={{ jitsi_meet_jicofo_password }} 22 | 23 | # extra options to pass to the jicofo daemon 24 | JICOFO_OPTS="" 25 | 26 | # adds java system props that are passed to jicofo (default are for home and logging config file) 27 | JAVA_SYS_PROPS="-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/etc/jitsi -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=jicofo -Dnet.java.sip.communicator.SC_LOG_DIR_LOCATION=/var/log/jitsi -Djava.util.logging.config.file=/etc/jitsi/jicofo/logging.properties" 28 | -------------------------------------------------------------------------------- /tasks/jitsi_meet.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Copy Jitsi Meet JS config file. 3 | template: 4 | dest: "/etc/jitsi/meet/{{ jitsi_meet_server_name }}-config.js" 5 | src: jitsi_meet_config.js.j2 6 | owner: root 7 | group: root 8 | mode: "0644" 9 | notify: restart jitsi-videobridge 10 | 11 | - name: Set jitsi-videobridge log level. 12 | lineinfile: 13 | dest: /etc/jitsi/videobridge/logging.properties 14 | regexp: '^\.level' 15 | line: ".level={{ jitsi_meet_videobridge_loglevel }}" 16 | state: present 17 | notify: restart jitsi-videobridge 18 | 19 | - name: Create directory for SIP communicator settings. 20 | file: 21 | state: directory 22 | # Hard-coding homedir path and username because they're configured via 23 | # the jitsi-meet postinst scripts, so these values must match. 24 | path: /usr/share/jitsi-videobridge/.sip-communicator 25 | owner: jvb 26 | notify: restart jitsi-videobridge 27 | 28 | - name: Write SIP communicator settings. 29 | copy: 30 | dest: /usr/share/jitsi-videobridge/.sip-communicator/sip-communicator.properties 31 | content: "org.jitsi.impl.neomedia.transform.srtp.SRTPCryptoContext.checkReplay=false" 32 | owner: jvb 33 | notify: restart jitsi-videobridge 34 | -------------------------------------------------------------------------------- /spec/videobridge_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # This is the port which jvb will use to connect to prosody 4 | jvb_service_port = 5347 5 | 6 | describe file('/etc/jitsi/videobridge/config') do 7 | it { should be_file } 8 | it { should be_owned_by 'root' } 9 | it { should be_grouped_into 'root' } 10 | its('mode') { should eq '644' } 11 | its('content') { should match(/^JVB_HOSTNAME=localhost$/) } 12 | its('content') { should match(/^JVB_HOST=$/) } 13 | its('content') { should match(/^JVB_PORT=#{jvb_service_port}$/) } 14 | # It may be necessary to expand the regex for matching secrets. 15 | # See the jicofo tests for comparison. 16 | its('content') { should match(/^JVB_SECRET=\w{8,}$/) } 17 | end 18 | 19 | describe file('/etc/jitsi/videobridge/logging.properties') do 20 | it { should be_file } 21 | it { should be_owned_by 'root' } 22 | it { should be_grouped_into 'root' } 23 | its('mode') { should eq '644' } 24 | its('content') { should match(/^\.level=INFO$/) } 25 | end 26 | 27 | describe service('jitsi-videobridge') do 28 | it { should be_enabled } 29 | it { should be_running } 30 | end 31 | 32 | # Check that jitsi-videobridge process is running as jvb user 33 | describe command('pgrep -u jvb | wc -l') do 34 | its('stdout') { should eq "1\n" } 35 | end 36 | -------------------------------------------------------------------------------- /spec/jitsi_meet_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe package('jitsi-meet') do 4 | it { should be_installed } 5 | end 6 | 7 | describe package('default-jre-headless') do 8 | it { should be_installed } 9 | end 10 | 11 | describe command('echo "get jitsi-meet/jvb-hostname" | \ 12 | debconf-communicate') do 13 | its('stdout') { should eq "0 localhost\n" } 14 | end 15 | 16 | describe command('echo "get jitsi-meet/jvb-serve" | \ 17 | debconf-communicate') do 18 | its('stdout') { should eq "0 false\n" } 19 | end 20 | 21 | describe command('echo "get jitsi-meet-prosody/jvb-hostname" | \ 22 | debconf-communicate') do 23 | its('stdout') { should eq "0 localhost\n" } 24 | end 25 | 26 | describe file('/etc/jitsi/meet/localhost-config.js') do 27 | it { should be_file } 28 | it { should be_owned_by 'root' } 29 | it { should be_grouped_into 'root' } 30 | its('mode') { should eq '644' } 31 | its('content') { should match(/^\s+domain: 'localhost',/) } 32 | its('content') { should match(/^\s+muc: 'conference\.localhost',/) } 33 | its('content') { should match(/^\s+bridge: 'jitsi-videobridge\.localhost',/) } 34 | its('content') { should match(%r{^\s+bosh: '//localhost/http-bind',}) } 35 | its('content') { should match(/^\s+disableThirdPartyRequests: true,/) } 36 | end 37 | -------------------------------------------------------------------------------- /spec/jicofo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe file('/etc/jitsi/jicofo/config') do 4 | it { should be_file } 5 | it { should be_owned_by 'root' } 6 | it { should be_grouped_into 'root' } 7 | its('mode') { should eq '644' } 8 | its('content') { should match(/^JICOFO_HOST=localhost$/) } 9 | its('content') { should match(/^JICOFO_PORT=5347$/) } 10 | # The regex for the "secret" may be off. Tests have failed before 11 | # when matching only '\w', due to a '@', so adding that. 12 | # Also have seen '#', so adding that. Would love a definitive 13 | # take on which characters are allowed here. 14 | its('content') { should match(/^JICOFO_SECRET=[\w@#]{8,}$/) } 15 | its('content') { should match(/^JICOFO_AUTH_PASSWORD=[\w@#]{8,}$/) } 16 | its('content') { should match(/^JICOFO_AUTH_USER=focus$/) } 17 | end 18 | 19 | describe file('/etc/jitsi/jicofo/logging.properties') do 20 | it { should be_file } 21 | it { should be_owned_by 'root' } 22 | it { should be_grouped_into 'root' } 23 | its('mode') { should eq '644' } 24 | its('content') { should match(/^\.level=INFO$/) } 25 | end 26 | 27 | describe service('jicofo') do 28 | it { should be_enabled } 29 | it { should be_running } 30 | end 31 | 32 | # Check that jicofo process is running as jicofo user 33 | describe command('pgrep -u jicofo | wc -l') do 34 | its('stdout') { should eq "1\n" } 35 | end 36 | -------------------------------------------------------------------------------- /templates/jitsi_meet_nginx.conf.j2: -------------------------------------------------------------------------------- 1 | server_names_hash_bucket_size 64; 2 | 3 | server { 4 | listen 80; 5 | server_name {{ jitsi_meet_server_name }}; 6 | return 301 https://$host$request_uri; 7 | } 8 | server { 9 | listen 443 ssl; 10 | server_name {{ jitsi_meet_server_name }}; 11 | 12 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 13 | ssl_prefer_server_ciphers on; 14 | ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED"; 15 | 16 | add_header Strict-Transport-Security "max-age=31536000"; 17 | 18 | ssl_certificate {{ jitsi_meet_ssl_cert_path }}; 19 | ssl_certificate_key {{ jitsi_meet_ssl_key_path }}; 20 | 21 | root /usr/share/jitsi-meet; 22 | index index.html index.htm; 23 | 24 | location /config.js { 25 | alias /etc/jitsi/meet/{{ jitsi_meet_server_name }}-config.js; 26 | } 27 | 28 | location ~ ^/([a-zA-Z0-9=\?]+)$ { 29 | rewrite ^/(.*)$ / break; 30 | } 31 | 32 | location / { 33 | ssi on; 34 | } 35 | 36 | # BOSH 37 | location /http-bind { 38 | proxy_pass http://localhost:5280/http-bind; 39 | proxy_set_header X-Forwarded-For $remote_addr; 40 | proxy_set_header Host $http_host; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tasks/clean_up_default_configs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove default localhost Jitsi Meet config file. 3 | file: 4 | path: /etc/jitsi/meet/localhost-config.js 5 | state: absent 6 | notify: restart jitsi-videobridge 7 | 8 | - name: Remove Jicofo user config for localhost site. 9 | file: 10 | path: /var/lib/prosody/auth%2elocalhost 11 | state: absent 12 | notify: 13 | - restart jicofo 14 | - restart prosody 15 | 16 | - name: Remove default localhost Prosody config file. 17 | file: 18 | path: /etc/prosody/conf.d/localhost.cfg.lua 19 | state: absent 20 | notify: restart prosody 21 | 22 | # The "server_names_hash_bucket_size" directive should only occur once in the 23 | # Nginx config. If it's duplicated, even only in "available" sites rather than 24 | # "enabled", nginx will fail to start. Since this role writes it in a template 25 | # when using custom SSL keys, we'll clean up the default localhost file to avoid 26 | # duplicating the config line. 27 | - name: Remove default localhost Nginx config file. 28 | file: 29 | path: "{{ item }}" 30 | state: absent 31 | notify: restart nginx 32 | with_items: 33 | - /etc/nginx/sites-available/localhost.conf 34 | - /etc/nginx/sites-enabled/localhost.conf 35 | # Only remove the "localhost" file if we're using custom SSL keys, i.e. in prod. 36 | when: jitsi_meet_ssl_cert_path != '' and 37 | jitsi_meet_ssl_key_path != '' 38 | -------------------------------------------------------------------------------- /tasks/prosody.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Copy Prosody config template. 3 | template: 4 | src: prosody_config.j2 5 | dest: "/etc/prosody/conf.avail/{{ jitsi_meet_server_name }}.cfg.lua" 6 | owner: root 7 | group: root 8 | mode: "0644" 9 | # Simply syntax check for validation, doesn't catch config errors. 10 | validate: "luac -p %s" 11 | notify: restart prosody 12 | 13 | - name: Symlink Prosody config by domain name. 14 | file: 15 | path: "/etc/prosody/conf.d/{{ jitsi_meet_server_name }}.cfg.lua" 16 | state: link 17 | src: "/etc/prosody/conf.avail/{{ jitsi_meet_server_name }}.cfg.lua" 18 | notify: restart prosody 19 | 20 | - name: Register jicofo agent with Prosody service. 21 | command: > 22 | prosodyctl register focus auth.{{ jitsi_meet_server_name }} {{ jitsi_meet_jicofo_password }} 23 | args: 24 | # Yes, prosody actually URL-escapes the directory name for some reason. 25 | # Must hardcode the escaping in the prefix, since the replace filter only 26 | # applies to the server name var, not the concatenated string. 27 | creates: /var/lib/prosody/{{ 'auth%2e'+jitsi_meet_server_name | replace('.', '%2e') }}/accounts/focus.dat 28 | notify: 29 | - restart jicofo 30 | - restart prosody 31 | 32 | - name: Generate SSL keypair for Prosody service. 33 | shell: > 34 | yes '' | prosodyctl cert generate {{ jitsi_meet_server_name }} 35 | args: 36 | creates: /var/lib/prosody/{{ jitsi_meet_server_name }}.crt 37 | notify: 38 | - restart jicofo 39 | - restart prosody 40 | -------------------------------------------------------------------------------- /spec/nginx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe package('nginx') do 4 | it { should be_installed } 5 | its('version') { should >= '1.6.2-5' } 6 | end 7 | 8 | describe file('/etc/nginx/sites-available/localhost.conf') do 9 | it { should be_file } 10 | its('owner') { should eq 'root' } 11 | its('group') { should eq 'root' } 12 | its('mode') { should eq '644' } 13 | 14 | ssl_regexes = [ 15 | %r{^\s+ssl_certificate /var/lib/prosody/localhost.crt;$}, 16 | %r{^\s+ssl_certificate_key /var/lib/prosody/localhost.key;$} 17 | ] 18 | ssl_regexes.each do |ssl_regex| 19 | its('content') { should match(ssl_regex) } 20 | end 21 | end 22 | 23 | describe service('nginx') do 24 | it { should be_enabled } 25 | it { should be_running } 26 | end 27 | 28 | nginx_ports = %w(80 443) 29 | nginx_ports.each do |nginx_port| 30 | describe port(nginx_port) do 31 | it { should be_listening } 32 | it { should be_listening.on('0.0.0.0').with('tcp') } 33 | it { should_not be_listening.on('127.0.0.1') } 34 | end 35 | end 36 | 37 | # Check that nginx process is running as nginx user 38 | describe command('pgrep -u www-data | wc -l') do 39 | # The default jitsi-meet config sets 4 workers 40 | its('stdout') { should eq "4\n" } 41 | end 42 | 43 | describe command('sudo netstat -nlt') do 44 | its('stdout') { should_not match(/127\.0\.0\.1:80/) } 45 | its('stdout') { should_not match(/127\.0\.0\.1:443/) } 46 | its('stdout') { should match(/0\.0\.0\.0:80/) } 47 | its('stdout') { should match(/0\.0\.0\.0:443/) } 48 | end 49 | -------------------------------------------------------------------------------- /tasks/packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install base apt packages. 3 | apt: 4 | name: "{{ item }}" 5 | state: present 6 | update_cache: yes 7 | cache_valid_time: 3600 8 | with_items: "{{ jitsi_meet_base_packages }}" 9 | 10 | # Prior versions of the role were writing to this file, let's 11 | # clean it up and use a generalized filename for configuring the apt repo, 12 | # regardless of whether stable or unstable is used. 13 | - name: Remove deprecated repo filename. 14 | file: 15 | path: /etc/apt/sources.list.d/download_jitsi_org_nightly_deb.list 16 | state: absent 17 | 18 | - name: Determine repo strategy. 19 | set_fact: 20 | jitsi_strategy: "{{ 'unstable' if jitsi_meet_use_nightly_apt_repo else 'stable' }}" 21 | 22 | # Both stable and unstable repos use the same signing key. 23 | - name: Configure signing key for Jitsi repository. 24 | apt_key: 25 | id: 66A9CD0595D6AFA247290D3BEF8B479E2DC1389C 26 | url: "https://download.jitsi.org/jitsi-key.gpg.key" 27 | state: present 28 | 29 | - name: Install Jitsi apt repo. 30 | apt_repository: 31 | repo: "{{ item.value.repo_url }}" 32 | state: "{{ 'present' if jitsi_strategy == item.key else 'absent' }}" 33 | # Ansible will automatically add the ".list" suffix. 34 | filename: /etc/apt/sources.list.d/jitsi_meet 35 | with_dict: "{{ jitsi_meet_apt_repos }}" 36 | 37 | - name: Install Jitsi Meet 38 | apt: 39 | name: jitsi-meet 40 | state: latest 41 | update_cache: yes 42 | cache_valid_time: 3600 43 | 44 | - name: Set debconf options for jitsi-meet. 45 | debconf: 46 | name: "{{ item.name }}" 47 | question: "{{ item.question }}" 48 | value: "{{ item.value }}" 49 | vtype: "{{ item.vtype }}" 50 | with_items: "{{ jitsi_meet_debconf_settings }}" 51 | notify: 52 | - restart jitsi-videobridge 53 | - restart jicofo 54 | - restart prosody 55 | -------------------------------------------------------------------------------- /spec/apt_repo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe package('apt-transport-https') do 4 | it { should be_installed } 5 | end 6 | 7 | describe command('apt-cache policy') do 8 | jitsi_apt_repo_stable = < 39 | sub 4096R/88D3172B 2016-06-23 40 | APT_KEY 41 | 42 | # TODO: Perhaps this key still refers to unstable repo? 43 | jitsi_apt_key_incorrect = < 47 | sub 2048g/F6EFCE13 2008-06-20 48 | APT_KEY_UNWANTED 49 | its('stdout') { should include(jitsi_apt_key) } 50 | its('stdout') { should_not include(jitsi_apt_key_incorrect) } 51 | end 52 | -------------------------------------------------------------------------------- /tasks/dynamic_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Read Jitsi Videobridge secret var. 3 | command: > 4 | perl -lane '/^JVB_SECRET=(.*)$/ && print $1' 5 | /etc/jitsi/videobridge/config 6 | register: jitsi_meet_videobridge_secret_result 7 | failed_when: jitsi_meet_videobridge_secret_result.rc != 0 or 8 | jitsi_meet_videobridge_secret_result.stdout == '' 9 | always_run: true 10 | changed_when: false 11 | 12 | - name: Set fact for Jitsi Videobridge secret var. 13 | set_fact: 14 | jitsi_meet_videobridge_secret: "{{ jitsi_meet_videobridge_secret_result.stdout }}" 15 | # Permit custom secrets in the template; only set the var if no custom secret is declared. 16 | when: jitsi_meet_videobridge_secret is not defined or 17 | jitsi_meet_videobridge_secret == '' 18 | 19 | - name: Read Jicofo secret var. 20 | command: > 21 | perl -lane '/^JICOFO_SECRET=(.*)$/ && print $1' 22 | /etc/jitsi/jicofo/config 23 | register: jitsi_meet_jicofo_secret_result 24 | changed_when: false 25 | always_run: true 26 | failed_when: jitsi_meet_jicofo_secret_result.rc != 0 or 27 | jitsi_meet_jicofo_secret_result.stdout == '' 28 | 29 | - name: Set fact for Jitsi Videobridge secret var. 30 | set_fact: 31 | jitsi_meet_jicofo_secret: "{{ jitsi_meet_jicofo_secret_result.stdout }}" 32 | # Permit custom secrets in the template; only set the var if no custom secret is declared. 33 | when: jitsi_meet_jicofo_secret is not defined or 34 | jitsi_meet_jicofo_secret == '' 35 | 36 | - name: Read Jicofo user auth password var. 37 | command: > 38 | perl -lane '/^JICOFO_AUTH_PASSWORD=(.*)$/ && print $1' 39 | /etc/jitsi/jicofo/config 40 | register: jitsi_meet_jicofo_password_result 41 | changed_when: false 42 | always_run: true 43 | failed_when: jitsi_meet_jicofo_password_result.rc != 0 or 44 | jitsi_meet_jicofo_password_result.stdout == '' 45 | 46 | - name: Set fact for Jitsi user auth password var. 47 | set_fact: 48 | jitsi_meet_jicofo_password: "{{ jitsi_meet_jicofo_password_result.stdout }}" 49 | # Permit custom secrets in the template; only set the var if no custom secret is declared. 50 | when: jitsi_meet_jicofo_password is not defined or 51 | jitsi_meet_jicofo_password == '' 52 | -------------------------------------------------------------------------------- /spec/prosody_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe file('/etc/prosody/conf.avail/localhost.cfg.lua') do 4 | it { should be_file } 5 | it { should be_owned_by 'root' } 6 | it { should be_grouped_into 'root' } 7 | its('mode') { should eq '644' } 8 | 9 | its('content') do 10 | should contain('VirtualHost "localhost"') 11 | .before('authentication = "anonymous"') 12 | end 13 | 14 | wanted_enabled_modules = %w(bosh pubsub ping) 15 | wanted_enabled_modules.each do |enabled_module| 16 | its('content') do 17 | should contain(enabled_module.to_s) 18 | .from(/^\s+modules_enabled = \{/) 19 | .to(/^\s+\}$/) 20 | end 21 | end 22 | its('content') do 23 | should contain('VirtualHost "auth.localhost"') 24 | .before('authentication = "internal_plain"') 25 | end 26 | 27 | wanted_config_line_pairs = { 28 | 'VirtualHost "auth.localhost"' => 'authentication = "internal_plain"', 29 | 'Component "conference.localhost" "muc"' => 30 | 'admins = { "focus@auth.localhost" }', 31 | 'Component "jitsi-videobridge.localhost"' => 'component_secret = ', 32 | 'Component "focus.localhost"' => 'component_secret = ' 33 | } 34 | wanted_config_line_pairs.each do |line1, line2| 35 | regexp1 = /#{Regexp.quote(line1)}/ 36 | regexp2 = /#{Regexp.quote(line2)}/ 37 | regexp = /^#{regexp1}\n\s+#{regexp2}/m 38 | describe command('cat /etc/prosody/conf.avail/localhost.cfg.lua') do 39 | its('stdout') { should match(regexp) } 40 | end 41 | end 42 | 43 | describe command('luac -p /etc/prosody/conf.avail/localhost.cfg.lua') do 44 | its('exit_status') { should eq 0 } 45 | end 46 | 47 | describe file('/var/lib/prosody/auth%2elocalhost') do 48 | it { should be_directory } 49 | it { should be_owned_by 'prosody' } 50 | it { should be_grouped_into 'prosody' } 51 | its('mode') { should eq '750' } 52 | end 53 | 54 | describe file('/var/lib/prosody/auth%2elocalhost/accounts/focus.dat') do 55 | it { should be_file } 56 | it { should be_owned_by 'prosody' } 57 | it { should be_grouped_into 'prosody' } 58 | its('mode') { should eq '640' } 59 | regexp = /^\s+\["password"\] = "[\w@#]+";$/ 60 | its('content') { should match(regexp) } 61 | end 62 | end 63 | 64 | # 5347 is the XMPP component port. 65 | # Prosody listens on it, jicofo and jitsi-videobridge connect. 66 | describe port(5347) do 67 | it { should be_listening } 68 | it { should be_listening.on('127.0.0.1') } 69 | it { should_not be_listening.on('0.0.0.0') } 70 | end 71 | 72 | describe command('sudo netstat -nlt') do 73 | its('stdout') { should match(/127\.0\.0\.1:5347/) } 74 | its('stdout') { should_not match(/0\.0\.0\.0:5347/) } 75 | end 76 | -------------------------------------------------------------------------------- /templates/jitsi_meet_config.js.j2: -------------------------------------------------------------------------------- 1 | /* jshint -W101 */ 2 | var config = { 3 | // configLocation: './config.json', // see ./modules/HttpConfigFetch.js 4 | hosts: { 5 | domain: '{{ jitsi_meet_server_name }}', 6 | //anonymousdomain: 'guest.example.com', 7 | //authdomain: '{{ jitsi_meet_server_name }}', // defaults to 8 | muc: 'conference.{{ jitsi_meet_server_name }}', // FIXME: use XEP-0030 9 | bridge: 'jitsi-videobridge.{{ jitsi_meet_server_name }}', // FIXME: use XEP-0030 10 | //jirecon: 'jirecon.{{ jitsi_meet_server_name }}', 11 | //call_control: 'callcontrol.{{ jitsi_meet_server_name }}', 12 | //focus: 'focus.{{ jitsi_meet_server_name }}', // defaults to 'focus.{{ jitsi_meet_server_name }}' 13 | }, 14 | // getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; }, 15 | // useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server 16 | // useIPv6: true, // ipv6 support. use at your own risk 17 | useNicks: false, 18 | bosh: '//{{ jitsi_meet_server_name }}/http-bind', // FIXME: use xep-0156 for that 19 | clientNode: 'http://jitsi.org/jitsimeet', // The name of client node advertised in XEP-0115 'c' stanza 20 | //focusUserJid: '{{ jitsi_meet_jicofo_user }}@auth.{{ jitsi_meet_server_name }}', // The real JID of focus participant - can be overridden here 21 | //defaultSipNumber: '', // Default SIP number 22 | 23 | // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable. 24 | desktopSharingChromeMethod: '{{ jitsi_meet_desktop_sharing_chrome_method }}', 25 | // The ID of the jidesha extension for Chrome. 26 | desktopSharingChromeExtId: '{{ jitsi_meet_desktop_sharing_chrome_ext_id }}', 27 | // The media sources to use when using screen sharing with the Chrome 28 | // extension. 29 | desktopSharingChromeSources: ['screen', 'window'], 30 | // Required version of Chrome extension 31 | desktopSharingChromeMinExtVersion: '0.1', 32 | 33 | // The ID of the jidesha extension for Firefox. If null, we assume that no 34 | // extension is required. 35 | desktopSharingFirefoxExtId: {{ jitsi_meet_desktop_sharing_firefox_ext_id }}, 36 | // Whether desktop sharing should be disabled on Firefox. 37 | desktopSharingFirefoxDisabled: {{ 'true' if jitsi_meet_desktop_sharing_firefox_disabled else 'false' }}, 38 | // The maximum version of Firefox which requires a jidesha extension. 39 | // Example: if set to 41, we will require the extension for Firefox versions 40 | // up to and including 41. On Firefox 42 and higher, we will run without the 41 | // extension. 42 | // If set to -1, an extension will be required for all versions of Firefox. 43 | desktopSharingFirefoxMaxVersionExtRequired: {{ jitsi_meet_desktop_sharing_firefox_max_version_ext_required }}, 44 | // The URL to the Firefox extension for desktop sharing. 45 | desktopSharingFirefoxExtensionURL: null, 46 | 47 | // Disables ICE/UDP by filtering out local and remote UDP candidates in signalling. 48 | webrtcIceUdpDisable: false, 49 | // Disables ICE/TCP by filtering out local and remote TCP candidates in signalling. 50 | webrtcIceTcpDisable: false, 51 | 52 | openSctp: true, // Toggle to enable/disable SCTP channels 53 | disableStats: false, 54 | disableAudioLevels: false, 55 | channelLastN: -1, // The default value of the channel attribute last-n. 56 | adaptiveLastN: false, 57 | adaptiveSimulcast: false, 58 | enableRecording: false, 59 | enableWelcomePage: true, 60 | enableSimulcast: false, // blocks FF support 61 | logStats: false, // Enable logging of PeerConnection stats via the focus 62 | // requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room. 63 | // startAudioMuted: 10, // every participant after the Nth will start audio muted 64 | // startVideoMuted: 10, // every participant after the Nth will start video muted 65 | // defaultLanguage: "en", 66 | // To enable sending statistics to callstats.io you should provide Applicaiton ID and Secret. 67 | // callStatsID: "", // Application ID for callstats.io API 68 | // callStatsSecret: "", // Secret for callstats.io API 69 | /*noticeMessage: 'Service update is scheduled for 16th March 2015. ' + 70 | 'During that time service will not be available. ' + 71 | 'Apologise for inconvenience.',*/ 72 | disableThirdPartyRequests: {{ 'true' if jitsi_meet_disable_third_party_requests else 'false' }}, 73 | }; 74 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # The default cert files are /var/lib/prosody/localhost.{crt,key} 3 | # NOT setting them here, because empty strings for custom certs will 4 | # cause the custom Nginx config tasks to be skipped. 5 | jitsi_meet_ssl_cert_path: '' 6 | jitsi_meet_ssl_key_path: '' 7 | 8 | jitsi_meet_base_packages: 9 | - apt-transport-https 10 | - default-jre-headless 11 | 12 | # Without SSL, "localhost" is the correct default. If SSL info is provided, 13 | # then we'll need a real domain name. Using Ansible's inferred FQDN, but you 14 | # can set the variable value explicitly if you use a shorter hostname 15 | # If automatic Nginx configuration is disabled, also use FQDN, since presumably 16 | # another role will manage the vhost config. 17 | jitsi_meet_server_name: "{{ ansible_fqdn if (jitsi_meet_ssl_cert_path or not jitsi_meet_configure_nginx) else 'localhost' }}" 18 | 19 | # Only "anonymous" auth is supported, which lets anyone use the videoconference server. 20 | jitsi_meet_authentication: anonymous 21 | 22 | # Whether to use nightly builds of the Jitsi Meet components. 23 | jitsi_meet_use_nightly_apt_repo: false 24 | 25 | jitsi_meet_apt_repos: 26 | stable: 27 | repo_url: 'deb https://download.jitsi.org/ stable/' 28 | unstable: 29 | repo_url: 'deb https://download.jitsi.org unstable/' 30 | 31 | # The Debian package installation of jitsi-meet will generate secrets for the components. 32 | # The role will read the config file and preserve the secrets even while templating. 33 | # If you wish to generate your own secrets and use those, override these vars, but make 34 | # sure to store the secrets securely, e.g. with ansible-vault or credstash. 35 | jitsi_meet_videobridge_secret: '' 36 | jitsi_meet_jicofo_secret: '' 37 | jitsi_meet_jicofo_password: '' 38 | 39 | # Default auth information, used in multiple service templates. 40 | jitsi_meet_jicofo_user: focus 41 | jitsi_meet_jicofo_port: 5347 42 | 43 | # The Jitsi components use the standard Java log levels, see: 44 | # https://docs.oracle.com/javase/7/docs/api/java/util/logging/Level.html 45 | # When using log aggregation for jitsi-meet components, set to "WARNING". 46 | jitsi_meet_jicofo_loglevel: INFO 47 | # The default config file at /etc/jitsi/videobridge/config claims the default port 48 | # for JVB is "5275", but the manual install guide references "5347". 49 | # https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md 50 | jitsi_meet_videobridge_port: 5347 51 | 52 | jitsi_meet_videobridge_loglevel: INFO 53 | # A recent privacy-friendly addition, see here for details: 54 | # https://github.com/jitsi/jitsi-meet/issues/422 55 | # https://github.com/jitsi/jitsi-meet/pull/427 56 | jitsi_meet_disable_third_party_requests: true 57 | 58 | # Screensharing config for Chrome. You'll need to build and package a browser 59 | # extension specifically for your domain; see https://github.com/jitsi/jidesha 60 | jitsi_meet_desktop_sharing_chrome_method: 'ext' 61 | jitsi_meet_desktop_sharing_chrome_ext_id: 'diibjkoicjeejcmhdnailmkgecihlobk' 62 | 63 | # Path to local extension on disk, for copying to target host. The remote filename 64 | # will be the basename of the path provided here. 65 | jitsi_meet_desktop_sharing_chrome_extension_filename: '' 66 | 67 | # Screensharing config for Firefox. Set max_version to '42' and disabled to 'false' 68 | # if you want to use screensharing under Firefox. 69 | jitsi_meet_desktop_sharing_firefox_ext_id: 'null' 70 | jitsi_meet_desktop_sharing_firefox_disabled: true 71 | jitsi_meet_desktop_sharing_firefox_max_version_ext_required: '-1' 72 | 73 | # These debconf settings represent answers to interactive prompts during installation 74 | # of the jitsi-meet deb package. If you use custom SSL certs, you may have to set more options. 75 | jitsi_meet_debconf_settings: 76 | - name: jitsi-meet 77 | question: jitsi-meet/jvb-hostname 78 | value: "{{ jitsi_meet_server_name }}" 79 | vtype: string 80 | - name: jitsi-meet 81 | question: jitsi-meet/jvb-serve 82 | value: "false" 83 | vtype: boolean 84 | - name: jitsi-meet-prosody 85 | question: jitsi-meet-prosody/jvb-hostname 86 | value: "{{ jitsi_meet_server_name }}" 87 | vtype: string 88 | 89 | # Role will automatically install configure ufw with jitsi-meet port holes. 90 | # If you're managing a firewall elsewise, set this to false, and ufw will be skipped. 91 | jitsi_meet_configure_firewall: true 92 | 93 | # Role will automatically install nginx and configure a vhost for use with jitsi-meet. 94 | # If you prefer to manage web vhosts via a separate role, set this to false. 95 | jitsi_meet_configure_nginx: true 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jitsi-meet 2 | ========= 3 | 4 | Installs and configures the [Jitsi Meet] videoconferencing software. 5 | 6 | 7 | Requirements 8 | ------------ 9 | 10 | You should have DNS pointed at the server already, and SSL keys. If you don't have SSL 11 | keys for the domain yet, consider using the excellent [thefinn93.letsencrypt] Ansible role 12 | to obtain (free!) SSL certs from [LetsEncrypt]. 13 | 14 | You will also need to expose ports 443 TCP and 10000 UDP for the Jitsi Meet 15 | components to work. By default the role will use `ufw` to allow these ports. If you 16 | use another host-based firewall solution such as iptables, set 17 | `jitsi_meet_configure_firewall: false`. If you use AWS or similar, you'll need to 18 | expose those ports in the associated Security Group. 19 | 20 | Role Variables 21 | -------------- 22 | 23 | ```yaml 24 | # The default cert files are /var/lib/prosody/localhost.{crt,key} 25 | # NOT setting them here, because empty strings for custom certs will 26 | # cause the custom Nginx config tasks to be skipped. 27 | jitsi_meet_ssl_cert_path: '' 28 | jitsi_meet_ssl_key_path: '' 29 | 30 | # Without SSL, "localhost" is the correct default. If SSL info is provided, 31 | # then we'll need a real domain name. Using Ansible's inferred FQDN, but you 32 | # can set the variable value explicitly if you use a shorter hostname 33 | # If automatic Nginx configuration is disabled, also use FQDN, since presumably 34 | # another role will manage the vhost config. 35 | jitsi_meet_server_name: "{{ ansible_fqdn if (jitsi_meet_ssl_cert_path or not jitsi_meet_configure_nginx) else 'localhost' }}" 36 | 37 | # Only "anonymous" auth is supported, which lets anyone use the videoconference server. 38 | jitsi_meet_authentication: anonymous 39 | 40 | # The Debian package installation of jitsi-meet will generate secrets for the components. 41 | # The role will read the config file and preserve the secrets even while templating. 42 | # If you wish to generate your own secrets and use those, override these vars, but make 43 | # sure to store the secrets securely, e.g. with ansible-vault or credstash. 44 | jitsi_meet_videobridge_secret: '' 45 | jitsi_meet_jicofo_secret: '' 46 | jitsi_meet_jicofo_password: '' 47 | 48 | # Default auth information, used in multiple service templates. 49 | jitsi_meet_jicofo_user: focus 50 | jitsi_meet_jicofo_port: 5347 51 | 52 | # The default config file at /etc/jitsi/videobridge/config claims the default port 53 | # for JVB is "5275", but the manual install guide references "5347". 54 | # https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md 55 | jitsi_meet_videobridge_port: 5347 56 | 57 | # A recent privacy-friendly addition, see here for details: 58 | # https://github.com/jitsi/jitsi-meet/issues/422 59 | # https://github.com/jitsi/jitsi-meet/pull/427 60 | jitsi_meet_disable_third_party_requests: true 61 | 62 | # Screensharing config for Chrome. You'll need to build and package a browser 63 | # extension specifically for your domain; see https://github.com/jitsi/jidesha 64 | jitsi_meet_desktop_sharing_chrome_method: 'ext' 65 | jitsi_meet_desktop_sharing_chrome_ext_id: 'diibjkoicjeejcmhdnailmkgecihlobk' 66 | 67 | # Path to local extension on disk, for copying to target host. The remote filename 68 | # will be the basename of the path provided here. 69 | jitsi_meet_desktop_sharing_chrome_extension_filename: '' 70 | 71 | # Screensharing config for Firefox. Set max_version to '42' and disabled to 'false' 72 | # if you want to use screensharing under Firefox. 73 | jitsi_meet_desktop_sharing_firefox_ext_id: 'null' 74 | jitsi_meet_desktop_sharing_firefox_disabled: true 75 | jitsi_meet_desktop_sharing_firefox_max_version_ext_required: '-1' 76 | 77 | # These debconf settings represent answers to interactive prompts during installation 78 | # of the jitsi-meet deb package. If you use custom SSL certs, you may have to set more options. 79 | jitsi_meet_debconf_settings: 80 | - name: jitsi-meet 81 | question: jitsi-meet/jvb-hostname 82 | value: "{{ jitsi_meet_server_name }}" 83 | vtype: string 84 | - name: jitsi-meet 85 | question: jitsi-meet/jvb-serve 86 | value: "false" 87 | vtype: boolean 88 | - name: jitsi-meet-prosody 89 | question: jitsi-meet-prosody/jvb-hostname 90 | value: "{{ jitsi_meet_server_name }}" 91 | vtype: string 92 | 93 | # Role will automatically install configure ufw with jitsi-meet port holes. 94 | # If you're managing a firewall elsewise, set this to false, and ufw will be skipped. 95 | jitsi_meet_configure_firewall: true 96 | 97 | # Role will automatically install nginx and configure a vhost for use with jitsi-meet. 98 | # If you prefer to manage web vhosts via a separate role, set this to false. 99 | jitsi_meet_configure_nginx: true 100 | ``` 101 | 102 | Screen sharing 103 | -------------- 104 | Jitsi Meet supports screen sharing functionality via browser extensions. 105 | Only the party sharing the screen needs the extension installed—other participants 106 | in the meeting will be able to view the shared screen without installing anything. 107 | You'll need to build your own browser extension for Chrome and/or Firefox. 108 | See the [Jidesha] documentation for detailed build instructions. This role 109 | has only been tested with custom Chrome extensions. 110 | 111 | Chrome forbids installation of extensions from unapproved websites, so you must 112 | download the `.crx` file directly, then navigate to `chrome://extensions` and 113 | drag-and-drop the extension to install it. If you want to grant another 114 | participant screen-sharing support, share the URL for the extension with them 115 | via the Jitsi Meet text chat pane. 116 | 117 | Dependencies 118 | ------------ 119 | 120 | It's technically not a dependency, but you should check out [thefinn93.letsencrypt] 121 | for astoundingly easy SSL certs. 122 | 123 | Example Playbook 124 | ---------------- 125 | 126 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 127 | 128 | ```yaml 129 | - name: Configure jitsi-meet server. 130 | hosts: jitsi 131 | vars: 132 | # Change this to match the DNS entry for your host IP. 133 | jitsi_meet_server_name: meet.example.com 134 | roles: 135 | - role: thefinn93.letsencrypt 136 | become: yes 137 | letsencrypt_email: "webmaster@{{ jitsi_meet_server_name }}" 138 | letsencrypt_cert_domains: 139 | - "{{ jitsi_meet_server_name }}" 140 | tags: letsencrypt 141 | 142 | - role: ansible-role-jitsi-meet 143 | jitsi_meet_ssl_cert_path: "/etc/letsencrypt/live/{{ jitsi_meet_server_name }}/fullchain.pem" 144 | jitsi_meet_ssl_key_path: "/etc/letsencrypt/live/{{ jitsi_meet_server_name }}/privkey.pem" 145 | become: yes 146 | tags: jitsi 147 | ``` 148 | 149 | Running the tests 150 | ----------------- 151 | 152 | This role uses [Molecule] and [ServerSpec] for testing. To use it: 153 | 154 | ``` 155 | pip install molecule 156 | gem install serverspec 157 | molecule test 158 | ``` 159 | 160 | You can also run selective commands: 161 | 162 | ``` 163 | molecule idempotence 164 | molecule verify 165 | ``` 166 | 167 | See the [Molecule] docs for more info. 168 | 169 | License 170 | ------- 171 | 172 | MIT 173 | 174 | Author Information 175 | ------------------ 176 | 177 | [Freedom of the Press Foundation] 178 | 179 | [Jitsi Meet]: https://github.com/jitsi/jitsi-meet 180 | [thefinn93.letsencrypt]: https://github.com/thefinn93/ansible-letsencrypt 181 | [LetsEncrypt]: https://letsencrypt.org/ 182 | [Freedom of the Press Foundation]: https://freedom.press/ 183 | [Molecule]: http://molecule.readthedocs.org/en/master/ 184 | [ServerSpec]: http://serverspec.org/ 185 | [Jidesha]: https://github.com/jitsi/jidesha 186 | --------------------------------------------------------------------------------