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.
Images:
ghcr.io/jitsi/*(jitsi_image_registry), pinnedstable-11248. Host ports80/443/10000are unchanged; the container listens on8000/8443because>=11146runs as uid 1000 and cannot bind privileged ports — it still terminates TLS itself. That uid also needs three persistent volumes it hard-exits without:storage/web(/storage, acme.sh + certs),storage/prosody(/var/lib/prosody, the account store) andtmp/web-crontabs(/var/spool/cron/crontabs). See runbook 48 §Adopting.
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¶
JWT (since 2026-08-15): any IPA user can start a meeting; this box only verifies.
AUTH_TYPE=jwt — prosody's mod_auth_token checks HS256 tokens minted by the meet_issuer
role on sso (meet-start.${domain}, Tier-2 behind Authelia forward-auth). The two halves
share exactly one thing, vault_jitsi_jwt_secret; the signed, room-scoped, short-lived token
crosses the boundary in the user's own browser redirect. TOKEN_AUTH_URL points the UI's
"I am the host" at the issuer (the client substitutes {room}). ENABLE_GUESTS=1 still lets
anonymous users into the lobby; a valid token joins authenticated and jicofo grants it
moderator. See runbook 48 §Auth.
The no-route-to-the-identity-core rule still holds: the box asks nobody who anyone is —
identity is proven lab-side where Authelia already fronts every Tier-2 app. The nordeck
OIDC→JWT adapter stays retired (jitsi_oidc_adapter_enabled: false — that was an OIDC
relying party on this box, Keycloak-only; the flag is kept because it gates the cleanup tasks
that tear the adapter off any host still running it). Superseded 2026-08-15: the per-person
Prosody-internal roster (jitsi_moderators + one KeePass password per human) — the role now
reports leftover internal accounts with their deluser command instead of managing them; they
are the rollback credential through the soak window.
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(shared withmeet_issueron sso — issuer signs, prosody verifies),vault_jitsi_jicofo_component_secret,vault_jitsi_jicofo_auth_password,vault_jitsi_jvb_auth_password. (vault_jitsi_oauth_client_secret, which fed the retired OIDC-adapter design, was pruned from the vault 2026-08-15 — these four are the complete set.) - 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
meet_issuer role + the
meet-start.${domain} forward-auth domain + apps-proxy vhost), not here — converge sso and
apps-proxy before flipping this box to jwt, or "I am the host" points at a name that is
not there yet. Publish the public meet.<domain> A record (nsupdate — runbook 48 §DNS).
Outward-facing — operator-gated.