A complete teaching platform — video, screen share, recording owned by each participant, whiteboard, persistent notes, breakout rooms, role-based access — running on one workstation in a 44 m² apartment near Iași. Two surfaces, one rig. The conversation stays where the conversation happened.
Most teaching today happens on platforms whose business model is to harvest the conversation. Recordings, transcripts, attention metrics, and increasingly the conversational content itself become training data. For private tutoring, family learning, sensitive consultations, or simply for people who would prefer their lessons not become someone else's dataset, that is a problem.
The Classroom is the answer from the operator side: a teaching stack that runs end-to-end on a single workstation under one person's control, indistinguishable from commercial services from the participant side, and architected so that no telemetry, no recording, and no transcript ever leaves the building unless the operator explicitly chooses for it to.
It serves two audiences in parallel — and this is the part most people don't realise is possible:
lsn-classroom.shuffle-on.com — open registration, admin-approval workflow, role-based room access, JWT-bound joins.lsn-classroom.home.lsn — only reachable from the LAN or over WireGuard, no public DNS, full LAN bandwidth, zero attack surface.Same codebase. Two ingress paths. The operator chooses which to use per session.
Two K3S namespaces — lsn-classroom (Flask + PostgreSQL) and lsn-jitsi (full Jitsi Meet stack + CoTURN) — wired through Traefik with TLS termination and route-level Authelia gating on the admin path.
For courses, public seminars, paid tutoring, registered cohorts, conference talks. The world can reach the surface; only credentialed users reach the room.
For family teaching, sensitive consultations, internal team meetings, anything that should not even appear on public DNS or TLS Certificate Transparency logs.
Not a stripped-down homelab demo. The full Jitsi Meet feature set, working today, in both the open-source mobile app and any modern browser. No tier gating. No paywall. Nothing held back for the upsell.
X-API-Key. Automatable, scriptable.The non-default choices — every one of them deliberate, every one of them load-bearing for the security or capability story.
| Tweak | What it does | Why it matters |
|---|---|---|
| Forced TURN relay | All client media goes through the JVB / TURN relay; no direct peer-to-peer. | Hides participant IPs from each other. Predictable bandwidth. Safer for sensitive sessions. |
| TURNS over TCP/443 | TURN-over-TLS on port 443 via Traefik SNI routing. | Works through any firewall that allows HTTPS. No reliance on UDP. Captive-portal friendly. |
| ENABLE_AUTO_OWNER=false | First joiner does not become owner. | Prevents rogue takeover of an empty room. Ownership comes only via JWT. |
| token_affiliation v6 | Custom Prosody Lua module reads JWT-bound affiliation, intercepts unauthorised owner promotions in MUC#admin IQ stanzas. | Closes a known loophole where an authenticated XMPP client can self-promote via raw stanzas. The classroom is the only source of truth for who is moderator. |
| P2P disabled | Two-person calls also go through JVB. | No fallback to direct connection that would expose IPs and bypass server-side moderation. |
| Etherpad IP logging off | Notes don't carry contributor IPs. | Privacy by default. |
| Excalidraw transport patch | Adds polling to the socket.io transport list at deploy time. | Whiteboard reliable across all networks; works around a chart compatibility bug. |
| CoTURN peer-IP allowlist | Relay only permitted to LAN + pod network. | TURN cannot be abused as an open relay for arbitrary internet traffic. |
| JWT 2-hour expiry | Tokens valid for 2 hours after issue. | Long enough for any session, short enough that a leaked link is not a permanent access grant. |
| Authelia on /admin/* only | Admin board behind 2FA + ForwardAuth; the public classroom UI is reachable without it. | Operator pays the auth tax; participants don't. Operational ergonomics + security where it counts. |
| Local domain on private TLS | *.home.lsn uses a private wildcard cert; public domains use Let's Encrypt. | Local LAN/WG sessions never appear in Certificate Transparency logs. |
Between any external attacker and any participant's microphone, there are eight independent gates. Each one on its own would already be more than commercial services expose; together they are surgical.
admin_required, teacher_or_admin, login_required decorators on every classroom endpoint.Both are convenient. Both are familiar. Neither lets you keep what was said.
| Property | LSN Classroom + Meet | Google Meet | Zoom |
|---|---|---|---|
| Operator-side data exposure | Zero — content never leaves your network | Full meeting metadata + AI features train on content | Significant; legal trail of past breaches |
| Recording | Client-side, owned by each participant — file lands on their device, never crosses operator storage | Cloud-recorded into Google's storage by default | Cloud-recorded; AI Companion analyses for "insights" |
| Screen sharing | Native (browser + mobile app), no licence tier | Native, free tier | Native, premium gating in some tiers |
| Whiteboarding | Excalidraw, built-in, persists | Jamboard (deprecated 2024) → Google Drawings | Native but feature-light |
| Persistent shared notes | Etherpad, built-in, URL-accessible after | Google Docs side-link | Separate or third-party |
| Time limits | None | 60 min on free tier | 40 min on free tier |
| Per-feature paywalls | None — every feature on every tier | Recording, breakouts, polls all gated | Most premium features tier-locked |
| Audit trail | Fully under operator control, queryable, no third party | Google-side only, partial export | Zoom-side only |
| Data residency / sovereignty | EU-onshore, GDPR-clean, NIS2-aligned | US (or chosen Google region); Schrems II open | US (with regional options); historical compliance issues |
| Custom integration | Native — classroom roles map straight to Jitsi affiliations via JWT | API exists; auth is a separate flow | API exists; auth is a separate flow |
| Air-gapped operation | Yes — works fully on the LAN with no internet uplink | No (cloud-required) | No (cloud-required) |
| Cost | €0/month + electricity | €6–18/user/month + add-ons | €15–22/user/month + add-ons |
| Source code | Inspectable, modifiable, forkable | Closed | Closed |
| Operator can change UI / wire custom flows | Yes — it's your code | No | No |
Everything above is the spec sheet. Here is what the spec sheet does not show.
A teacher running ten hours of classes per week on Zoom Pro is paying ≈ €200/year for video, ≈ €300/year for cloud storage of recordings, ≈ €100/year for any whiteboard tool worth the name, ≈ €60/year for shared notes with revision history, and an unbounded amount in attention they will never recover clicking through upsells. Across ten years of teaching that is ~€6,600 plus inflation — and all of it is gone the moment your account is suspended, the company changes its terms, or its servers go down on the morning of your exam.
The hardware that runs this stack — a Dell Precision Tower 7910 — was bought used for less than the cost of one year of Zoom Business across a small department. It will run for ten more years. The marginal cost of one more student, one more class, one more whiteboard, one more recording is zero. There is no upsell path because there is no upsell entity.
The most-requested feature in any teaching platform — being able to record a class — is also the most-abused. On Zoom and Google Meet, that recording goes into a third-party cloud bucket where it is retained according to a TOS you scrolled past, accessed by support engineers under conditions you cannot audit, and increasingly fed into AI training pipelines under terms you cannot opt out of without giving up the platform.
Here, recording is a function of the participant's browser or mobile app. The file lands on the device of whoever pressed the button. It does not pass through a server. It does not exist on the operator's storage. It is not backed up to a cloud you cannot inspect. It cannot be subpoenaed from a third party because there is no third party. Whoever recorded it owns it. Nobody else has a copy. That is not a workaround. That is the design.
The same Jitsi pod, the same classroom database, the same hardware serves two completely separate networks: the public internet and a private invitation-only network reachable only via LAN or WireGuard. Most people do not know this is possible. They assume "self-hosted" means one server, one address, one audience.
It does not. The same engine that hosts a public seminar at 18:00 can host a private family-music lesson at 19:00 — different DNS, different TLS chain, different security posture, different audit profile, same person at the keyboard. The private surface does not exist on Certificate Transparency logs. It does not appear in DNS. It is invisible to anyone not holding a WireGuard key. From a hotel room in Bucharest you can step into a room that, for all the rest of the internet knows, is not there.
In every commercial offering, the meeting tool and the user database are two separate systems glued together with shaky SSO. Roles defined in your CRM do not propagate cleanly to who-is-moderator-of-the-Tuesday-9am. Auth tokens leak. Permissions get out of sync. Rooms get hijacked.
Here, the classroom is the auth source. A teacher in the classroom database becomes, automatically, an owner in any room they create — via JWT, via the token_affiliation v6 Lua module, enforced at the XMPP layer on every join and on every privilege-escalation attempt. There is no second source of truth. Add a teacher; the rooms know. Suspend an account; they cannot rejoin even with an old link.
The stack is not a black box. It is a mesh of components. Etherpad notes are accessible by URL after the meeting. Excalidraw whiteboards persist. The classroom database is queryable from any other service in the cluster. The Telegram bot fires on every join request. The audit log is a SQL table. The CrowdSec stream feeds a Grafana dashboard. Every component speaks to every other component.
Want a workflow where a student's confusion in office hours auto-creates a follow-up task in your todo system? Possible. Want a chatbot that summarises Etherpad notes after each session and emails attendees? Possible. Want every class recording to be diff'd against last week's so the AI can flag what's new? Possible. None of this is possible on Zoom. None of it is possible on Google Meet. Both of those are sealed boxes. This is a Lego set.
A computer-science teacher running this stack is teaching their students what a real distributed system looks like every time they hold a class. The students are not just receiving content; they are interacting with K3S, Traefik, Authelia, Prosody, Etherpad, Excalidraw, JWT, TURN, Prometheus, Grafana — every single one of them present, working, and (if the teacher chooses) inspectable. The class itself is the lab.
A teacher of any subject running this stack is also teaching, by demonstration, that you do not have to take what is given to you. That sovereign infrastructure is not a corporate slide-deck term but a thing one person in a 44 m² apartment can build and maintain. That privacy is not a luxury reserved for governments and Fortune 500 boards but a default that every teacher and every student can have, today, with hardware that fits on a desk.
Commercial platforms cannot teach this. Their entire business model depends on it remaining unteachable.
The harder version of the comparison. The commercial platforms are not better than this stack on any property that actually matters to teaching. They are better at convenience (the calendar one-click), at familiarity (the UI participants already know), and at reliability (the multi-region failover). Those are real. They are also dwarfed by what is being given up in exchange — the ownership of the conversation, the freedom from unilateral terms changes, the right to inspect and modify the tool, the certainty that today's class will not become tomorrow's training data, the ability to teach in private without the surface even existing on the public internet.
For a small operator — one teacher, one tutor, one workshop, one family — the trade is so lopsided in favour of self-hosting that the only honest question is why does this not yet exist for everyone? The answer is partly that the engineering knowledge is not distributed widely enough, partly that the platform incumbents have made themselves invisible (you do not see a thing you take for granted), and partly that nobody bothered to write a working example down to be copied.
This is the working example. It is in production. It works. The code is open. The rig fits in an apartment.
Authelia Manager v2 — federating classroom identity into cluster-wide identity, surgical role propagation across every service.
Today, classroom user management and Authelia user management are two separate stores. Adding a teacher in classroom creates a row in PostgreSQL; granting that teacher access to the admin board still requires editing Authelia's user list separately.
The forthcoming Authelia Manager v2 — already running in this cluster, now being wired in — closes that gap. Once federated, classroom roles will become first-class identity facts visible in Authelia's visual access matrix. Adding a user becomes one operation, propagating to:
When that wiring is complete, "create a teacher with private-mode access to the Linear Algebra room and the admin board, with WireGuard access to the home LAN" becomes a single button, with full audit trail and GitOps versioning. That is the masterpiece this page is the prelude to.
Everything on this page is something running, in production, in a 44 m² apartment, today. The mobile app you are about to download to join your next Zoom call could just as easily join a meeting on this stack — and when it did, the recording would land on your phone, not on a server in Oregon. The whiteboard would be yours to keep. The notes would survive the meeting. The audit trail would be one operator's, not one corporation's.
The conversation would stay where the conversation happened.