RUNNING IN PRODUCTION · TWO SURFACES · ZERO CLOUD

The Classroom

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.

0
€/month
8
Security layers
2
Surfaces, 1 rig
4
Roles enforced
5
Room types
Feature paywalls

Why this exists

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:

  • Public teaching at lsn-classroom.shuffle-on.com — open registration, admin-approval workflow, role-based room access, JWT-bound joins.
  • Private teaching at 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.

The architecture, at a glance

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.

// REQUEST FLOW

🌐Internetor LAN / WireGuard
🛡️MikroTik prerouting+ CrowdSec subnets
🔐Traefik (TLS)cert-resolver / wildcard-lsn-tls
🪪Authelia/admin/* gate
🎓ClassroomFlask + PG, JWT issuer
📹Jitsi MeetJWT-enforced via Prosody
📡TURNS :443CoTURN behind SNI

// PUBLIC SURFACE

*.shuffle-on.com

For courses, public seminars, paid tutoring, registered cohorts, conference talks. The world can reach the surface; only credentialed users reach the room.

  • Self-registration with admin approval
  • Email + Telegram notifications on every join request
  • TURNS-on-443 traverses corporate firewalls
  • Per-room access control: students cannot wander between rooms
  • Lobby on by default
  • Authelia-protected admin board

// PRIVATE SURFACE

*.home.lsn (LAN / WireGuard only)

For family teaching, sensitive consultations, internal team meetings, anything that should not even appear on public DNS or TLS Certificate Transparency logs.

  • Only resolves on LAN or to authorised WireGuard peers
  • Wildcard cert never appears in public CT logs
  • Full LAN bandwidth (gigabit) — supports far higher participant counts
  • From a hotel room in Bucharest, you step into a room that for the rest of the internet does not exist
  • Same Jitsi UI, same JWT flow — only the surface differs

Everything you would expect — and more

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.

⏺️
Local recording
Each participant records on their own device — file lands locally, never traverses the operator's storage. You own your recording.
🖥️
Screen sharing
Full screen, single window, or single tab. Desktop browsers + Jitsi mobile app.
🎨
Whiteboard
Excalidraw embedded in the room. Persists, exportable as PNG / SVG.
📝
Persistent notes
Etherpad in-room. URL-accessible after the meeting ends. Revision history included.
🚪
Lobby
Late joiners require host approval. Controlled entry by default.
🧩
Breakout rooms
Split groups for workshops or office hours.
📊
Polls
Live audience polling, no third-party Slido integration needed.
😊
Reactions
Hand raise, emoji reactions, speaker stats, mute-all, kick / promote / demote.
🌫️
Virtual backgrounds
Built into the web client.
🎤
Noise suppression
Krisp-equivalent noise cancellation native in the web client.
📱
Mobile deep linking
Prejoin page offers one-tap install of the Jitsi mobile app. Future links open the app directly.
🔤
Closed captions
Browser-native via the Web Speech API where supported.
🔑
JWT-bound joins
Per-room JWT minted by the classroom — moderator status baked into the token.
🛂
4 user roles
Admin · Teacher · Student · Guest. Each with distinct visibility and management rights.
🏛️
5 room types
Class · Meeting · Office Hours · Seminar · Workshop. First-class objects with members + per-room roles.
📨
Dual notifications
Telegram + Email on every guest request, approval, kick, promotion. Redundant by design.
📋
Full audit log
Every admin action recorded with actor + IP + timestamp. Queryable, exportable.
🔌
REST API
Every UI action available via JSON over X-API-Key. Automatable, scriptable.

Tweaks that matter

The non-default choices — every one of them deliberate, every one of them load-bearing for the security or capability story.

TweakWhat it doesWhy it matters
Forced TURN relayAll 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/443TURN-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=falseFirst joiner does not become owner.Prevents rogue takeover of an empty room. Ownership comes only via JWT.
token_affiliation v6Custom 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 disabledTwo-person calls also go through JVB.No fallback to direct connection that would expose IPs and bypass server-side moderation.
Etherpad IP logging offNotes don't carry contributor IPs.Privacy by default.
Excalidraw transport patchAdds polling to the socket.io transport list at deploy time.Whiteboard reliable across all networks; works around a chart compatibility bug.
CoTURN peer-IP allowlistRelay only permitted to LAN + pod network.TURN cannot be abused as an open relay for arbitrary internet traffic.
JWT 2-hour expiryTokens 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/* onlyAdmin 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.

Eight independent security layers

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.

01
🛡️
Edge filtering — MikroTik prerouting
CrowdSec-fed subnet bans. ~120 banned /24 + /16 networks, refreshed continuously from the global federation (300K+ contributing nodes).
02
🔐
TLS termination — Traefik
Auto-renewing Let's Encrypt for public domains; private wildcard cert for *.home.lsn (never on CT logs).
03
🪪
Authelia ForwardAuth — admin path
TOTP / WebAuthn second factor on every admin session. Public surface unaffected.
04
🚦
Application-level role enforcement
admin_required, teacher_or_admin, login_required decorators on every classroom endpoint.
05
🔑
PostgreSQL salted password hashing
Per-user salt; passwords never stored or logged in plaintext.
06
🎟️
JWT-bound room joins
Moderator status determined at token-issue time, verified server-side by Prosody on every connection.
07
🧬
Custom Prosody token_affiliation v6
Even authenticated XMPP clients cannot self-elevate via raw IQ stanzas.
08
🛰️
Forced TURN relay + P2P disabled
No participant ever sees another participant's IP; no media stream ever leaves the controlled relay path.

vs. Google Meet + Zoom — honest comparison

Both are convenient. Both are familiar. Neither lets you keep what was said.

Property LSN Classroom + Meet Google Meet Zoom
Operator-side data exposureZero — content never leaves your networkFull meeting metadata + AI features train on contentSignificant; legal trail of past breaches
RecordingClient-side, owned by each participant — file lands on their device, never crosses operator storageCloud-recorded into Google's storage by defaultCloud-recorded; AI Companion analyses for "insights"
Screen sharingNative (browser + mobile app), no licence tierNative, free tierNative, premium gating in some tiers
WhiteboardingExcalidraw, built-in, persistsJamboard (deprecated 2024) → Google DrawingsNative but feature-light
Persistent shared notesEtherpad, built-in, URL-accessible afterGoogle Docs side-linkSeparate or third-party
Time limitsNone60 min on free tier40 min on free tier
Per-feature paywallsNone — every feature on every tierRecording, breakouts, polls all gatedMost premium features tier-locked
Audit trailFully under operator control, queryable, no third partyGoogle-side only, partial exportZoom-side only
Data residency / sovereigntyEU-onshore, GDPR-clean, NIS2-alignedUS (or chosen Google region); Schrems II openUS (with regional options); historical compliance issues
Custom integrationNative — classroom roles map straight to Jitsi affiliations via JWTAPI exists; auth is a separate flowAPI exists; auth is a separate flow
Air-gapped operationYes — works fully on the LAN with no internet uplinkNo (cloud-required)No (cloud-required)
Cost€0/month + electricity€6–18/user/month + add-ons€15–22/user/month + add-ons
Source codeInspectable, modifiable, forkableClosedClosed
Operator can change UI / wire custom flowsYes — it's your codeNoNo

// Honest cons (every one real, none load-bearing)

  • Bandwidth ceiling on the public surface. Residential upload (~50–100 Mbps) caps concurrent HD video to ~25–30 participants. The LAN/WireGuard surface is gigabit-bound and effectively unlimited.
  • No SLA. Power, internet, or Dell Precision down means platform down. 14 kWh battery backup mitigates power; internet outages remain real.
  • No auto-transcription beyond browser Web Speech API. A local Whisper deployment is straightforward to add — deliberately not yet shipped.
  • Manual calendar integration. Participants get a URL to bookmark or paste. The URL is stable per room.
  • UI unfamiliar to first-timers who expect Zoom's exact layout. Jitsi is close but not identical.
  • No native phone-dial-in. Jigasi could provide it; not currently configured.
  • Operator burden. K3S maintenance, helm upgrades, certificate rotation — all on one person.

// What you cannot get from any commercial service

  • A private surface that does not exist on the public internet.
  • Recordings owned by participants, never centralised.
  • One auth source for users + roles + WireGuard ACL + room moderators.
  • Custom UI, custom flows, custom buttons — it's your code.
  • EU-onshore, GDPR/NIS2-aligned without paying a sovereign-cloud premium.
  • Composability with the rest of your infrastructure.
  • The certainty that today's class will not become tomorrow's training data.

Behind the curtain — what most reports don't say

Everything above is the spec sheet. Here is what the spec sheet does not show.

You already paid for this. Many times.

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 recording is yours, not someone else's

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.

Two surfaces, one rig — a fact that breaks people's mental model

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.

The classroom is the auth source, the meeting follows

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.

Composability — the real alpha

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.

Pedagogy you literally cannot offer with commercial tools

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.

The masterpiece, in progress

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:

  • The classroom database (role, status, room memberships)
  • Authelia's identity store (allowed services, MFA enforcement, group membership)
  • OIDC clients automatically discovered across the cluster
  • The MikroTik ACL — for WireGuard peer access if applicable

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.

The rest is just deciding

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.

🎓 Open the classroom 📹 Join a meeting 🔧 The Workshop 📜 The Human Document