kind: ConfigMap
apiVersion: v1
metadata:
  namespace: jitsi
  name: prosody
data:
  mod_prometheus.lua:  |
    -- Log statistics to Prometheus
    --
    -- Copyright (C) 2014 Daurnimator
    -- Copyright (C) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
    --
    -- This module is MIT/X11 licensed.
    module:set_global();
    local tostring = tostring;
    local t_insert = table.insert;
    local t_concat = table.concat;
    local socket = require "socket";
    local get_stats = require "core.statsmanager".get_stats;
    local function escape(text)
      return text:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n");
    end
    local function escape_name(name)
      return name:gsub("[^A-Za-z0-9_]", "_"):gsub("^[^A-Za-z_]", "_%1");
    end
    local function get_timestamp()
      -- Using LuaSocket for that because os.time() only has second precision.
      return math.floor(socket.gettime() * 1000);
    end
    local function repr_help(metric, docstring)
      docstring = docstring:gsub("\\", "\\\\"):gsub("\n", "\\n");
      return "# HELP "..escape_name(metric).." "..docstring.."\n";
    end
    -- local allowed_types = { counter = true, gauge = true, histogram = true, summary = true, untyped = true };
    -- local allowed_types = { "counter", "gauge", "histogram", "summary", "untyped" };
    local function repr_type(metric, type_)
      -- if not allowed_types:contains(type_) then
      --   return;
      -- end
      return "# TYPE "..escape_name(metric).." "..type_.."\n";
    end
    local function repr_label(key, value)
      return key.."=\""..escape(value).."\"";
    end
    local function repr_labels(labels)
      local values = {}
      for key, value in pairs(labels) do
        t_insert(values, repr_label(escape_name(key), escape(value)));
      end
      if #values == 0 then
        return "";
      end
      return "{"..t_concat(values, ", ").."}";
    end
    local function repr_sample(metric, labels, value, timestamp)
      return escape_name(metric)..repr_labels(labels).." "..value.." "..timestamp.."\n";
    end
    local allowed_extras = { min = true, max = true, average = true };
    local function insert_extras(data, key, name, timestamp, extra)
      if not extra then
        return false;
      end
      local has_extra = false;
      for extra_name in pairs(allowed_extras) do
        if extra[extra_name] then
          local field = {

            value = extra[extra_name],
            labels = {

              ["type"] = name,
              field = extra_name,
            },
            typ = "gauge";
            timestamp = timestamp,
          };
          t_insert(data[key], field);
          has_extra = true;
        end
      end
      return has_extra;
    end
    local function parse_stats()
      local timestamp = tostring(get_timestamp());
      local data = {};
      local stats, changed_only, extras = get_stats();
      for stat, value in pairs(stats) do
        -- module:log("debug", "changed_stats[%q] = %s", stat, tostring(value));
        local extra = extras[stat];
        local host, sect, name, typ = stat:match("^/([^/]+)/([^/]+)/(.+):(%a+)$");
        if host == nil then
          sect, name, typ = stat:match("^([^.]+)%.(.+):(%a+)$");
        elseif host == "*" then
          host = nil;
        end
        if sect:find("^mod_measure_.") then
          sect = sect:sub(13);
        elseif sect:find("^mod_statistics_.") then
          sect = sect:sub(16);
        end
        local key = escape_name("prosody_"..sect);
        local field = {

          value = value,
          labels = { ["type"] = name},
          -- TODO: Use the other types where it makes sense.
          typ = (typ == "rate" and "counter" or "gauge"),
          timestamp = timestamp,
        };
        if host then
          field.labels.host = host;
        end
        if data[key] == nil then
          data[key] = {};
        end
        if not insert_extras(data, key, name, timestamp, extra) then
          t_insert(data[key], field);
        end
      end
      return data;
    end
    local function get_metrics(event)
      local response = event.response;
      response.headers.content_type = "text/plain; version=0.0.4";
      local answer = {};
      for key, fields in pairs(parse_stats()) do
        t_insert(answer, repr_help(key, "TODO: add a description here."));
        t_insert(answer, repr_type(key, fields[1].typ));
        for _, field in pairs(fields) do
          t_insert(answer, repr_sample(key, field.labels, field.value, field.timestamp));
        end
      end
      return t_concat(answer, "");
    end
    function module.add_host(module)
      module:depends "http";
      module:provides("http", {

        default_path = "metrics";
        route = {

          GET = get_metrics;
        };
      });
    end

  mod_measure_stanza_counts.lua:  |
    module:set_global()

    local filters = require"util.filters";

    local stanza_kinds = { message = true, presence = true, iq = true };

    local function rate(measures, dir)
        return function (stanza, session)
            measures[dir]();
            measures[dir .. "_" .. session.type]();
            if stanza.attr and not stanza.attr.xmlns and stanza_kinds[stanza.name] then
                measures[dir .. "_" .. session.type .. "_" .. stanza.name]();
            end
            return stanza;
        end
    end

    local measures = setmetatable({}, {
        __index = function (t, name)
            local m = module:measure(name, "rate");
            t[name] = m;
            return m;
        end
    });

    local function measure_stanza_counts(session)
        filters.add_filter(session, "stanzas/in",  rate(measures, "incoming"));
        filters.add_filter(session, "stanzas/out", rate(measures, "outgoing"));
    end

    filters.add_filter_hook(measure_stanza_counts);

  mod_measure_client_presence.lua:  |
    module:set_global();

    local measure = require"core.statsmanager".measure;

    local valid_shows = {
        available = true,
        chat = true,
        away = true,
        dnd = true,
        xa = true,
        unavailable = true,
    }

    local counters = {
        available = measure("amount", "client_presence.available"),
        chat = measure("amount", "client_presence.chat"),
        away = measure("amount", "client_presence.away"),
        dnd = measure("amount", "client_presence.dnd"),
        xa = measure("amount", "client_presence.xa"),
        unavailable = measure("amount", "client_presence.unavailable"),
        invalid = measure("amount", "client_presence.invalid");
    };

    module:hook("stats-update", function ()
        local buckets = {
            available = 0,
            chat = 0,
            away = 0,
            dnd = 0,
            xa = 0,
            unavailable = 0,
            invalid = 0,
        };
        for _, session in pairs(full_sessions) do
            local status = "unavailable";
            if session.presence then
                status = session.presence:get_child_text("show") or "available";
            end
            if valid_shows[status] ~= nil then
                buckets[status] = buckets[status] + 1;
            else
                buckets.invalid = buckets.invalid + 1;
            end
        end
        for bucket, count in pairs(buckets) do
            counters[bucket](count)
        end
    end)

  jitsi-meet.cfg.lua: |-
    {{ $ENABLE_AUTH := .Env.ENABLE_AUTH | default "0" | toBool }}
    {{ $ENABLE_GUEST_DOMAIN := and $ENABLE_AUTH (.Env.ENABLE_GUESTS | default "0" | toBool)}}
    {{ $AUTH_TYPE := .Env.AUTH_TYPE | default "internal" }}
    {{ $JWT_ASAP_KEYSERVER := .Env.JWT_ASAP_KEYSERVER | default "" }}
    {{ $JWT_ALLOW_EMPTY := .Env.JWT_ALLOW_EMPTY | default "0" | toBool }}
    {{ $JWT_AUTH_TYPE := .Env.JWT_AUTH_TYPE | default "token" }}
    {{ $JWT_TOKEN_AUTH_MODULE := .Env.JWT_TOKEN_AUTH_MODULE | default "token_verification" }}
    {{ $ENABLE_LOBBY := .Env.ENABLE_LOBBY | default "true" | toBool }}
    {{ $ENABLE_AV_MODERATION := .Env.ENABLE_AV_MODERATION | default "true" | toBool }}
    {{ $ENABLE_XMPP_WEBSOCKET := .Env.ENABLE_XMPP_WEBSOCKET | default "1" | toBool }}
    {{ $PUBLIC_URL := .Env.PUBLIC_URL | default "https://localhost:8443" -}}
    {{ $TURN_PORT := .Env.TURN_PORT | default "443" }}
    {{ $TURNS_PORT := .Env.TURNS_PORT | default "443" }}
    {{ $XMPP_MUC_DOMAIN_PREFIX := (split "." .Env.XMPP_MUC_DOMAIN)._0 }}
    {{ $DISABLE_POLLS := .Env.DISABLE_POLLS | default "false" | toBool -}}

    admins = {
        "{{ .Env.JICOFO_AUTH_USER }}@{{ .Env.XMPP_AUTH_DOMAIN }}",
        "{{ .Env.JVB_AUTH_USER }}@{{ .Env.XMPP_AUTH_DOMAIN }}"
    }

    unlimited_jids = {
        "{{ .Env.JICOFO_AUTH_USER }}@{{ .Env.XMPP_AUTH_DOMAIN }}",
        "{{ .Env.JVB_AUTH_USER }}@{{ .Env.XMPP_AUTH_DOMAIN }}"
    }

    plugin_paths = { "/prosody-plugins/", "/prosody-plugins-custom" }

    muc_mapper_domain_base = "{{ .Env.XMPP_DOMAIN }}";
    muc_mapper_domain_prefix = "{{ $XMPP_MUC_DOMAIN_PREFIX }}";

    http_default_host = "{{ .Env.XMPP_DOMAIN }}"

    {{ if .Env.TURN_CREDENTIALS }}
    external_service_secret = "{{.Env.TURN_CREDENTIALS}}";
    {{ end }}

    {{ if or .Env.TURN_HOST .Env.TURNS_HOST }}
    external_services = {
      {{ if .Env.TURN_HOST }}
        { type = "turn", host = "{{ .Env.TURN_HOST }}", port = {{ $TURN_PORT }}, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" }
      {{ end }}
      {{ if and .Env.TURN_HOST .Env.TURNS_HOST }}
      ,
      {{ end }}
      {{ if .Env.TURNS_HOST }}
        { type = "turns", host = "{{ .Env.TURNS_HOST }}", port = {{ $TURNS_PORT }}, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" }
      {{ end }}
    };
    {{ end }}

    {{ if and $ENABLE_AUTH (eq $AUTH_TYPE "jwt") .Env.JWT_ACCEPTED_ISSUERS }}
    asap_accepted_issuers = { "{{ join "\",\"" (splitList "," .Env.JWT_ACCEPTED_ISSUERS) }}" }
    {{ end }}
    {{ if and $ENABLE_AUTH (eq $AUTH_TYPE "jwt") .Env.JWT_ACCEPTED_AUDIENCES }}
    asap_accepted_audiences = { "{{ join "\",\"" (splitList "," .Env.JWT_ACCEPTED_AUDIENCES) }}" }
    {{ end }}
    consider_bosh_secure = true;
    -- Deprecated in 0.12
    -- https://github.com/bjc/prosody/commit/26542811eafd9c708a130272d7b7de77b92712de
    {{ $XMPP_CROSS_DOMAINS := $PUBLIC_URL }}
    {{ $XMPP_CROSS_DOMAIN := .Env.XMPP_CROSS_DOMAIN | default "" }}
    {{ if eq $XMPP_CROSS_DOMAIN "true"}}
    cross_domain_websocket = true
    cross_domain_bosh = true
    {{ else }}
    {{ if not (eq $XMPP_CROSS_DOMAIN "false") }}
      {{ $XMPP_CROSS_DOMAINS = list $PUBLIC_URL (print "https://" .Env.XMPP_DOMAIN) .Env.XMPP_CROSS_DOMAIN | join "," }}
    {{ end }}
    cross_domain_websocket = { "{{ join "\",\"" (splitList "," $XMPP_CROSS_DOMAINS) }}" }
    cross_domain_bosh = { "{{ join "\",\"" (splitList "," $XMPP_CROSS_DOMAINS) }}" }
    {{ end }}
    VirtualHost "{{ .Env.XMPP_DOMAIN }}"
    {{ if $ENABLE_AUTH }}
      {{ if eq $AUTH_TYPE "jwt" }}
        authentication = "{{ $JWT_AUTH_TYPE }}"
        app_id = "{{ .Env.JWT_APP_ID }}"
        app_secret = "{{ .Env.JWT_APP_SECRET }}"
        allow_empty_token = {{ $JWT_ALLOW_EMPTY }}
        {{ if $JWT_ASAP_KEYSERVER }}
        asap_key_server = "{{ .Env.JWT_ASAP_KEYSERVER }}"
        {{ end }}
        {{ else if eq $AUTH_TYPE "ldap" }}
        authentication = "cyrus"
        cyrus_application_name = "xmpp"
        allow_unencrypted_plain_auth = true
      {{ else if eq $AUTH_TYPE "internal" }}
        authentication = "internal_hashed"
      {{ end }}
    {{ else }}
        authentication = "jitsi-anonymous"
    {{ end }}
        ssl = {
            key = "/config/certs/{{ .Env.XMPP_DOMAIN }}.key";
            certificate = "/config/certs/{{ .Env.XMPP_DOMAIN }}.crt";
        }
        modules_enabled = {
            "bosh";
            {{ if $ENABLE_XMPP_WEBSOCKET }}
            "websocket";
            "smacks"; -- XEP-0198: Stream Management
            {{ end }}
            "pubsub";
            "ping";
            "speakerstats";
            "conference_duration";
            {{ if or .Env.TURN_HOST .Env.TURNS_HOST }}
            "external_services";
            {{ end }}
            {{ if $ENABLE_LOBBY }}
            "muc_lobby_rooms";
            {{ end }}
            {{ if $ENABLE_AV_MODERATION }}
            "av_moderation";
            {{ end }}
            {{ if .Env.XMPP_MODULES }}
            "{{ join "\";\n\"" (splitList "," .Env.XMPP_MODULES) }}";
            {{ end }}
            {{ if and $ENABLE_AUTH (eq $AUTH_TYPE "ldap") }}
            "auth_cyrus";
            {{end}}
        }
        {{ if $ENABLE_LOBBY }}
        main_muc = "{{ .Env.XMPP_MUC_DOMAIN }}"
        lobby_muc = "lobby.{{ .Env.XMPP_DOMAIN }}"
        {{ if .Env.XMPP_RECORDER_DOMAIN }}
        muc_lobby_whitelist = { "{{ .Env.XMPP_RECORDER_DOMAIN }}" }
        {{ end }}
        {{ end }}
        speakerstats_component = "speakerstats.{{ .Env.XMPP_DOMAIN }}"
        conference_duration_component = "conferenceduration.{{ .Env.XMPP_DOMAIN }}"
        {{ if $ENABLE_AV_MODERATION }}
        av_moderation_component = "avmoderation.{{ .Env.XMPP_DOMAIN }}"
        {{ end }}
        c2s_require_encryption = false
    {{ if $ENABLE_GUEST_DOMAIN }}
    VirtualHost "{{ .Env.XMPP_GUEST_DOMAIN }}"
        authentication = "jitsi-anonymous"
        c2s_require_encryption = false
    {{ end }}
    VirtualHost "{{ .Env.XMPP_AUTH_DOMAIN }}"
        ssl = {
            key = "/config/certs/{{ .Env.XMPP_AUTH_DOMAIN }}.key";
            certificate = "/config/certs/{{ .Env.XMPP_AUTH_DOMAIN }}.crt";
        }
        modules_enabled = {
            "limits_exception";
        }
        authentication = "internal_hashed"
    {{ if .Env.XMPP_RECORDER_DOMAIN }}
    VirtualHost "{{ .Env.XMPP_RECORDER_DOMAIN }}"
        modules_enabled = {
          "ping";
        }
        authentication = "internal_hashed"
    {{ end }}
    Component "{{ .Env.XMPP_INTERNAL_MUC_DOMAIN }}" "muc"
        storage = "memory"
        modules_enabled = {
            "ping";
            {{ if .Env.XMPP_INTERNAL_MUC_MODULES -}}
            "{{ join "\";\n\"" (splitList "," .Env.XMPP_INTERNAL_MUC_MODULES) }}";
            {{ end -}}
        }
        restrict_room_creation = true
        muc_room_locking = false
        muc_room_default_public_jids = true
    Component "{{ .Env.XMPP_MUC_DOMAIN }}" "muc"
        storage = "memory"
        modules_enabled = {
            "muc_meeting_id";
            {{ if .Env.XMPP_MUC_MODULES -}}
            "{{ join "\";\n\"" (splitList "," .Env.XMPP_MUC_MODULES) }}";
            {{ end -}}
            {{ if and $ENABLE_AUTH (eq $AUTH_TYPE "jwt") -}}
            "{{ $JWT_TOKEN_AUTH_MODULE }}";
            {{ end -}}
            {{ if not $DISABLE_POLLS -}}
            "polls";
            {{ end -}}
        }
        muc_room_cache_size = 1000
        muc_room_locking = false
        muc_room_default_public_jids = true
    Component "focus.{{ .Env.XMPP_DOMAIN }}" "client_proxy"
        target_address = "{{ .Env.JICOFO_AUTH_USER }}@{{ .Env.XMPP_AUTH_DOMAIN }}"
    Component "speakerstats.{{ .Env.XMPP_DOMAIN }}" "speakerstats_component"
        muc_component = "{{ .Env.XMPP_MUC_DOMAIN }}"
    Component "conferenceduration.{{ .Env.XMPP_DOMAIN }}" "conference_duration_component"
        muc_component = "{{ .Env.XMPP_MUC_DOMAIN }}"
    {{ if $ENABLE_AV_MODERATION }}
    Component "avmoderation.{{ .Env.XMPP_DOMAIN }}" "av_moderation_component"
        muc_component = "{{ .Env.XMPP_MUC_DOMAIN }}"
    {{ end }}
    {{ if $ENABLE_LOBBY }}
    Component "lobby.{{ .Env.XMPP_DOMAIN }}" "muc"
        storage = "memory"
        restrict_room_creation = true
        muc_room_locking = false
        muc_room_default_public_jids = true
    {{ end }}