#!/bin/bash
# grommunio-meet-setup [FQDN]
# Configure prosody, jicofo and jvb for grommunio Meet under /meet.
# Safe to re-run; FQDN defaults to `hostname -f`.
set -uo pipefail

BASE="${1:-$(hostname -f)}"
JITSI_ROOT=/srv/jitsi-meet
PLUGINS=/usr/share/jitsi/meet/prosody-plugins
CERTS=/etc/prosody/certs
ANCHORS=/etc/pki/trust/anchors
LE_DIR="$(ls -d /etc/letsencrypt/live/$BASE 2>/dev/null | head -1)"
SECRETS=/etc/jitsi/.meet-secrets

echo "grommunio-meet-setup: configuring meet for $BASE"
install -d -m 0750 /var/log/jitsi

# keep secrets stable across runs
if [ -f "$SECRETS" ]; then . "$SECRETS"; fi
FOCUS_PASS="${FOCUS_PASS:-$(openssl rand -hex 16)}"
JVB_PASS="${JVB_PASS:-$(openssl rand -hex 16)}"
JVB_NICK="${JVB_NICK:-$(cat /proc/sys/kernel/random/uuid)}"

TURN_HOST="${TURN_HOST:-turn.grommun.io}"
TURN_SECRET="${TURN_SECRET:-c43258756tq0w7894rt6njas98udfznweo87t}"
umask 077; printf 'FOCUS_PASS=%s\nJVB_PASS=%s\nJVB_NICK=%s\n' "$FOCUS_PASS" "$JVB_PASS" "$JVB_NICK" >"$SECRETS"; umask 022

# prosody virtualhost
install -d -m 0755 /etc/prosody/conf.d
cat >/etc/prosody/conf.d/"$BASE".cfg.lua <<EOF
-- grommunio meet
component_admins_as_room_owners = true
plugin_paths = { "$PLUGINS" }
muc_mapper_domain_base = "$BASE";

external_service_secret = "$TURN_SECRET";
external_services = {
     { type = "stun", host = "$TURN_HOST", port = 3478 },
     { type = "turn", host = "$TURN_HOST", port = 3478, transport = "udp", secret = true, ttl = 86400, algorithm = "turn" },
     { type = "turns", host = "$TURN_HOST", port = 443, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" }
};

cross_domain_bosh = false;
consider_bosh_secure = true;
consider_websocket_secure = true;
unlimited_jids = { "focus@auth.$BASE", "jvb@auth.$BASE" }
smacks_max_unacked_stanzas = 5;
smacks_hibernation_time = 60;
smacks_max_old_sessions = 1;

VirtualHost "$BASE"
    authentication = "anonymous"
    ssl = { key = "$CERTS/$BASE.key"; certificate = "$CERTS/$BASE.crt"; }
    modules_enabled = { "bosh"; "websocket"; "external_services"; "smacks"; "ping"; "conference_duration"; "muc_lobby_rooms"; "muc_breakout_rooms"; }
    c2s_require_encryption = false
    lobby_muc = "lobby.$BASE"
    breakout_rooms_muc = "breakout.$BASE"
    main_muc = "conference.$BASE"

Component "conference.$BASE" "muc"
    restrict_room_creation = true
    storage = "memory"
    modules_enabled = { "muc_hide_all"; "muc_meeting_id"; "muc_domain_mapper"; "muc_rate_limit"; "muc_password_whitelist"; }
    admins = { "focus@auth.$BASE" }
    muc_password_whitelist = { "focus@auth.$BASE" }
    muc_room_locking = false
    muc_room_default_public_jids = true

Component "breakout.$BASE" "muc"
    restrict_room_creation = true
    storage = "memory"
    modules_enabled = { "muc_hide_all"; "muc_meeting_id"; "muc_domain_mapper"; "muc_rate_limit"; }
    admins = { "focus@auth.$BASE" }
    muc_room_locking = false
    muc_room_default_public_jids = true

Component "internal.auth.$BASE" "muc"
    storage = "memory"
    modules_enabled = { "muc_hide_all"; "ping"; }
    admins = { "focus@auth.$BASE", "jvb@auth.$BASE" }
    muc_room_locking = false
    muc_room_default_public_jids = true

VirtualHost "auth.$BASE"
    ssl = { key = "$CERTS/auth.$BASE.key"; certificate = "$CERTS/auth.$BASE.crt"; }
    modules_enabled = { "limits_exception"; "smacks"; }
    authentication = "internal_hashed"

VirtualHost "recorder.$BASE"
    modules_enabled = { "smacks"; }
    authentication = "internal_hashed"

Component "focus.$BASE" "client_proxy"
    target_address = "focus@auth.$BASE"
Component "speakerstats.$BASE" "speakerstats_component"
    muc_component = "conference.$BASE"
Component "endconference.$BASE" "end_conference"
    muc_component = "conference.$BASE"
Component "avmoderation.$BASE" "av_moderation_component"
    muc_component = "conference.$BASE"
Component "lobby.$BASE" "muc"
    storage = "memory"
    restrict_room_creation = true
    muc_room_locking = false
    muc_room_default_public_jids = true
    modules_enabled = { "muc_hide_all"; "muc_rate_limit"; }
Component "metadata.$BASE" "room_metadata_component"
    muc_component = "conference.$BASE"
    breakout_rooms_component = "breakout.$BASE"
EOF
grep -q 'conf.d/\*.cfg.lua' /etc/prosody/prosody.cfg.lua || \
    echo 'Include "/etc/prosody/conf.d/*.cfg.lua"' >>/etc/prosody/prosody.cfg.lua

# cjson shim (no lua51-cjson on Leap; back it with prosody's util.json)
LP=/usr/share/lua/5.1
install -d "$LP/cjson"
printf 'local json=require"util.json"\nreturn {encode=json.encode,decode=json.decode}\n' >"$LP/cjson.lua"
printf 'local json=require"util.json"\nlocal M={}\nfunction M.encode(...) local ok,r=pcall(json.encode,...) if ok then return r end return nil,r end\nfunction M.decode(s) local ok,r=pcall(json.decode,s) if ok then return r end return nil,r end\nreturn M\n' >"$LP/cjson/safe.lua"

# certificates
install -d -m 0750 -o root -g prosody "$CERTS"
gencert(){ local h="$1"; [ -f "$CERTS/$h.crt" ] && return 0
  openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -keyout "$CERTS/$h.key" -out "$CERTS/$h.crt" -subj "/CN=$h" >/dev/null 2>&1; }
if [ -n "$LE_DIR" ] && [ -f "$LE_DIR/privkey.pem" ]; then
  cp -fL "$LE_DIR/privkey.pem" "$CERTS/$BASE.key"; cp -fL "$LE_DIR/fullchain.pem" "$CERTS/$BASE.crt"
else gencert "$BASE"; fi
for h in "auth.$BASE" "recorder.$BASE"; do gencert "$h"; done
chgrp -R prosody "$CERTS" 2>/dev/null || true; chmod -R g+rX "$CERTS" 2>/dev/null || true
# jvb/jicofo validate the XMPP cert, so trust the self-signed ones
if [ -d "$ANCHORS" ]; then
  for h in "auth.$BASE" "$BASE" "recorder.$BASE"; do
    [ -f "$CERTS/$h.crt" ] && cp -f "$CERTS/$h.crt" "$ANCHORS/prosody-$h.crt"
  done
  update-ca-certificates >/dev/null 2>&1 || true
fi

# prosody users
systemctl start prosody 2>/dev/null || true
prosodyctl register focus "auth.$BASE" "$FOCUS_PASS" 2>/dev/null || \
  { prosodyctl deluser "focus@auth.$BASE" 2>/dev/null; prosodyctl register focus "auth.$BASE" "$FOCUS_PASS"; }
prosodyctl register jvb "auth.$BASE" "$JVB_PASS" 2>/dev/null || \
  { prosodyctl deluser "jvb@auth.$BASE" 2>/dev/null; prosodyctl register jvb "auth.$BASE" "$JVB_PASS"; }

# client_proxy needs focus's presence: subscribe focus.$BASE in focus's roster
ENC=$(echo "auth.$BASE" | sed 's/\./%2e/g')
RDIR="/var/lib/prosody/$ENC/roster"
install -d -o prosody -g prosody -m 0750 "$RDIR"
cat >"$RDIR/focus.dat" <<ROSTER
return {
	[false] = { ["pending"] = {}; ["version"] = 2; };
	["focus.$BASE"] = { ["groups"] = {}; ["subscription"] = "from"; };
};
ROSTER
chown prosody:prosody "$RDIR/focus.dat"; chmod 0640 "$RDIR/focus.dat"

# jicofo
cat >/etc/jitsi/jicofo/jicofo.conf <<EOF
jicofo {
  xmpp {
    client {
      client-proxy = "focus.$BASE"
      xmpp-domain = "$BASE"
      domain = "auth.$BASE"
      username = "focus"
      password = "$FOCUS_PASS"
      disable-certificate-verification = true
    }
  }
  bridge { brewery-jid = "JvbBrewery@internal.auth.$BASE" }
}
EOF
cat >/etc/jitsi/jicofo/config <<EOF
JAVA_SYS_PROPS="-Dnet.java.sip.communicator.SC_LOG_DIR_LOCATION=/var/log/jitsi -Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/etc/jitsi -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=jicofo"
EOF

# jvb secret (the rest of jvb.conf ships with the package)
sed -i "s|\$PASSWORD|$JVB_PASS|g; s|unique-instance-id|$JVB_NICK|g" /etc/jitsi/videobridge/jvb.conf

# advertise the public IP for media; JVB_PUBLIC_ADDRESS overrides STUN discovery
LOCAL_ADDR=$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[0-9.]+' | head -1)
PUBLIC_ADDR="${JVB_PUBLIC_ADDRESS:-$(python3 - "$TURN_HOST" 3478 <<'PY' 2>/dev/null
import socket,struct,os,sys
try:
    h,p=sys.argv[1],int(sys.argv[2])
    s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.settimeout(3)
    s.sendto(struct.pack(">HHI12s",1,0,0x2112A442,os.urandom(12)),(h,p))
    d,_=s.recvfrom(1024); i=20
    while i+4<=len(d):
        t,l=struct.unpack(">HH",d[i:i+4]); v=d[i+4:i+4+l]
        if t==0x0020:
            print(".".join(map(str,bytes(b^c for b,c in zip(v[4:8],bytes.fromhex("2112A442")))))); break
        i+=4+l+((4-l%4)%4)
except Exception: pass
PY
)}"
if [ -n "$PUBLIC_ADDR" ] && [ -n "$LOCAL_ADDR" ] && [ "$PUBLIC_ADDR" != "$LOCAL_ADDR" ]; then
  printf 'org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=%s\norg.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=%s\n' \
    "$LOCAL_ADDR" "$PUBLIC_ADDR" >/etc/jitsi/videobridge/sip-communicator.properties
  chown jvb:jitsi /etc/jitsi/videobridge/sip-communicator.properties 2>/dev/null || true
fi

# config.js domain (the /meet subdir is set by the package %post)
sed -i "s/jitsi-meet.example.com/$BASE/g; s/\${FQDN}/$BASE/g" "$JITSI_ROOT/config.js"

# open the media port
if command -v firewall-cmd >/dev/null && systemctl is-active --quiet firewalld; then
  firewall-cmd --permanent --add-port=10000/udp >/dev/null 2>&1
  firewall-cmd --reload >/dev/null 2>&1
fi

echo "grommunio-meet-setup: restarting services"
systemctl daemon-reload
for s in prosody jitsi-videobridge jitsi-jicofo; do systemctl enable --now "$s" >/dev/null 2>&1; systemctl restart "$s"; done
nginx -t >/dev/null 2>&1 && systemctl reload nginx
echo "grommunio-meet-setup: done. Meet is at https://$BASE/meet"
cat <<EOF

grommunio Meet network access (see grommunio-meet-network.md):
  inbound to ${PUBLIC_ADDR:-<public-ip>}:
    tcp/443     web + signalling (already used by grommunio-web)
    udp/10000   videobridge media, all participants - must reach this host
                (forward ${PUBLIC_ADDR:-<public-ip>}:10000/udp -> ${LOCAL_ADDR:-<host>}:10000/udp)
  outbound: udp/3478 + tcp/443 to ${TURN_HOST}
EOF
