role: jitsi¶
Self-hosted Jitsi Meet — a public video plane, off the origin/edge planes (see
runbooks/48-jitsi-meet.md and CLAUDE.md). INTERIM: runs as a pve VM holding its own
public IP on its own NIC (wan in the vms.conf VLAN column — untagged on the WAN bridge,
exactly like the edge VMs), standing in for a future off-prem vendor edge3.
jitsi_public_ip is the box's own address, so it is ansible_host too.
It was first deployed behind an OPNsense WAN VIP with NAT to a DMZ address; that was undone, because inbound NAT applies only on WAN, so internal clients hit the firewall's DNS-rebind guard instead of Jitsi. Don't publish a service on a firewall VIP.
What it is¶
docker-jitsi-meet mapped onto podman quadlets: web, prosody, jicofo, jvb, on a
private meet network. It runs on its own public IP with its own UDP/10000 media port,
and terminates its own TLS. Its only link to the home lab is a WireGuard tunnel used solely
to pull blessed packages — AllowedIPs is two /32s (the origin's tunnel address and the
RPM mirror). No origin Caddy, no RFC2136 to the home Bind primary, no media over the tunnel.
Why it breaks two edge invariants (on purpose)¶
- Terminates its own TLS (Let's Encrypt HTTP-01) — the passthrough edges hold no certs, but Jitsi is an app host, not a dumb relay.
- Serves a public UDP media port — the SFU needs it; the TCP-SNI edges can't carry it.
Both are acceptable because E2EE is on by default (jitsi_e2ee_default): media frames
are end-to-end encrypted (insertable streams), so even this box's JVB — and therefore the
vendor — cannot read them. Terminating transport TLS only exposes signaling/metadata.
Auth¶
A named Prosody-internal moderator gates who can start a meeting. AUTH_TYPE=internal
with one account (jitsi_moderator_user, password in KeePass — a human credential, not in
the automation vault). ENABLE_GUESTS=1 lets anonymous users into the lobby, but only the
moderator can create/moderate a room. The role creates the account only when absent and never
rewrites an existing password, so a hand rotation is not silently undone by the next converge.
Not SSO, deliberately: this box has no route to the identity core — the same rule as
tvheadend and ntfy. The nordeck OIDC→JWT adapter was retired (jitsi_oidc_adapter_enabled:
false); it is Keycloak-only and could not front Authelia without the origin Caddy impersonating
Keycloak. The flag is kept rather than deleted because it also gates the cleanup tasks that tear
the adapter off any host still running it.
Inputs¶
jitsi_public_ip— the VPS's real public IP (set ingroup_vars/all/zz-local.yml). FeedsJVB_ADVERTISE_IPS, the inventoryansible_host, and themeetA-record.- Vault secrets (per-play in
playbooks/jitsi.yml):vault_jitsi_jwt_secret,vault_jitsi_oauth_client_secret,vault_jitsi_jicofo_component_secret,vault_jitsi_jicofo_auth_password,vault_jitsi_jvb_auth_password. - Firewall (
group_vars/jitsi):80/tcp,443/tcp,10000/udp(+ optional4443/tcp). jitsi_jvb_max_memory/jitsi_jicofo_max_memory— JVM heap caps, derived from the guest'sansible_memtotal_mb(40% / 20%). Not optional tuning:jvb.shandjicofo.shboth default to-Xmx3072mwhen unset, i.e. 6 GB of heap requested on a 4 GB no-swap guest. See runbook 48 "Provision".
Not IPA-enrolled¶
Off-prem-shaped public box (lab_ipa_enroll: false), like the edges — gets the common
baseline (SSH-CA login, fail2ban, host firewall) + this role's auditd. Its own firewall is
the boundary: nothing else filters this box, so 22/tcp is source-scoped via
host_firewall_source_ports and lockout recovery is the Proxmox console.
It IS on central obs. Metrics and logs ship over the same WireGuard tunnel as the package
pulls, with per-host attribution via Alloy's host label. That works because the obs /32 is in
jitsi_peer_allowed_ips alongside the origin's tunnel address and the mirror — the edges are wired
the same way (edge_peer_allowed_ips + origin_tunnel_masq_dests).
⚠️ Remove that obs
/32and meet goes silently unmonitored — it does NOT fail loudly. Alloy pushes, so with no route to obsprometheus.remote_writeretries forever withcontext deadline exceededwhile the unit staysactive. A running-but-mute agent is indistinguishable from a healthy one, so "Systemd unit failed" cannot see it. That was the real state until 2026-08-01: meet had never shipped a sample, and the moment it did, obs immediately surfaced acloud-final.servicefailure that had been invisible since first boot.fleet-agent-not-shipping(runbook 27) now catches a host that stops shipping — but a host that never shipped has no series to age, so when adding a tunnel-attached box, confirm a metric actually ARRIVES;is-activeis not evidence.
Deploy¶
ansible-playbook -i inventory.ini playbooks/jitsi.yml
playbooks/site.yml -l sso). Publish the
public meet.<domain> A record (nsupdate — runbook 48 §DNS). Outward-facing — operator-gated.