Nauka terminala przez wpisywanie prawdziwych komend w prawdziwym shellu.
To nie jest symulator. Każdy znak trafia do realnego bash w kontenerze Dockera, tworzonym
na czas sesji i kasowanym po zamknięciu karty. Potoki |, przekierowania >, &&, pętle,
dowolne komendy z obrazu — wszystko działa, bo to jest bash, a nie jego imitacja w JavaScripcie.
Learn the Linux terminal by typing real commands into a real
bash— sandboxed in a per-session Docker container, no account, no data collected. 17 courses included; course content and UI are in Polish.
Symulator uczy tego, co jego autor przewidział. Wpiszesz komendę w formie, o której nie
pomyślał — dostajesz „błąd", choć zrobiłeś dobrze. TermLearn sprawdza efekt w kontenerze,
więc każda droga do celu zalicza zadanie: mkdir backup i mkdir -p ./backup są tak samo
poprawne, bo katalog istnieje.
Drugi powód jest ważniejszy: w prawdziwym shellu można się pomylić naprawdę. rm usuwa,
fork bomba zawiesza, > nadpisuje plik. Uczysz się na tym samym narzędziu, którego użyjesz
w pracy — tylko że w środowisku, w którym pomyłka nic nie kosztuje.
Backend uruchamia weryfikację w tym samym kontenerze, w którym pracujesz. Trzy tryby:
| Typ | Co sprawdza | Do czego |
|---|---|---|
script |
skrypt shellowy w kontenerze, exit 0 = zaliczone |
zadania zmieniające dysk (mkdir, cp, echo >) |
cwd |
realny katalog roboczy interaktywnego basha | nawigacja (cd) |
lastcmd |
ostatnia wpisana komenda (regex) | komendy wyświetlające (pwd, ls, grep) — nie zmieniają stanu, więc nie ma czego mierzyć na dysku |
Zadania lastcmd zaliczają się same po Enterze, reszta po kliknięciu „Sprawdź zadanie".
Rozwiązania nigdy nie trafiają do przeglądarki — check czytany jest z pliku kursu po stronie
serwera, więc nie da się ich podejrzeć w DevTools.
Uczeń wpisuje dowolne komendy, więc kontener traktowany jest jak środowisko wrogie:
| Ustawienie | Po co |
|---|---|
--network none |
brak dostępu do sieci — z kontenera nie wyjdzie ani skan, ani pobranie |
--cap-drop ALL + --security-opt no-new-privileges |
brak uprawnień systemowych, brak drogi do eskalacji |
--read-only + tmpfs na /home/learner i /tmp |
zapis tylko w pamięci, kasowany razem z sesją |
--memory 256m, --cpus 0.5, --pids-limit 128 |
ochrona przed fork bombą i wyczerpaniem zasobów hosta |
USER learner |
uczeń nigdy nie pracuje jako root |
kontener per sesja, docker rm -f przy rozłączeniu |
zero śladów na hoście |
sprawdzanie nagłówka Origin |
terminal otwiera tylko strona serwowana przez ten backend |
| limit 8 równoległych sesji | górna granica tego, ile kontenerów da się zamówić naraz |
Dlaczego sprawdzamy Origin: połączenia WebSocket nie podlegają regule tego samego
źródła — przeglądarka pozwoli dowolnej otwartej stronie połączyć się z localhost, więc to
serwer musi odsiać obcych. Bez tej kontroli każda witryna w Twojej przeglądarce mogła po cichu
zamawiać kontenery i wykonywać w nich komendy, dopóki TermLearn działał. Skutek byłby
ograniczony do piaskownicy (bez sieci, bez dostępu do dysku hosta), ale wystarczał do zajechania
maszyny liczbą kontenerów.
Serwer nasłuchuje wyłącznie na 127.0.0.1 i tak ma zostać. To narzędzie do pracy lokalnej:
nie ma logowania ani kont. Wystawienie go na publiczny adres oznacza oddanie obcym powłoki
w kontenerze — chcesz udostępnić to grupie, dołóż uwierzytelnienie, a limit sesji podnieś
świadomie (MAX_SESJI w backend/main.py).
Uczciwie o granicach izolacji: kontener to nie maszyna wirtualna. Powyższe ustawienia zamykają drogę wyjścia przez sieć, uprawnienia i system plików, ale ucieczka przez błąd w jądrze albo w samym Dockerze pozostaje możliwa. Do nauki komend to w zupełności wystarcza; do uruchamiania kodu, któremu nie ufasz, użyj maszyny wirtualnej.
Aplikacja nie ma kont, nie zbiera danych i nie woła żadnego zewnętrznego API. Postęp, XP
i streak żyją w localStorage przeglądarki (przycisk „Resetuj postęp" czyści je).
Strona nie wysyła ani jednego żądania poza localhost — xterm.js leży w repo
(backend/vendor/, licencja MIT w vendor/xterm-LICENSE.txt), a nie na CDN. Po zbudowaniu
obrazu całość działa bez internetu i nikt z zewnątrz nie widzi, że się uczysz.
Aktualizacja biblioteki = podmiana plików w backend/vendor/ (wersje: @xterm/xterm@5.5.0,
@xterm/addon-fit@0.10.0).
- Linux z działającym Dockerem (użytkownik w grupie
docker, bezsudo) - Python 3 z modułem
venv—start.shsam tworzy środowisko i instaluje FastAPI + uvicorn - internet tylko przy pierwszym uruchomieniu — do pobrania obrazu Debiana; potem całość działa offline
git clone https://github.com/Tyr9102/TermLearn.git
cd TermLearn/backend
./start.shSkrypt buduje obraz sesji (debian:bookworm-slim + bash, coreutils, grep, findutils, nano,
less, procps, man, zip/unzip, git — ok. 270 MB, jednorazowo) i startuje serwer.
Potem otwórz http://localhost:8002. Inny port: PORT=8080 ./start.sh.
17 kursów, od orientacji w systemie po skrypty bashowe:
Podstawowe komendy Linuksa · Przekierowania i strumienie · Potoki · Ekspansje i cudzysłowy ·
Uprawnienia: rwx, chmod, umask · Procesy, zadania i sygnały · Środowisko shella ·
Dokumentacja, wildcards i find · Archiwa i kompresja · Dowiązania i dysk ·
Wyrażenia regularne · Przetwarzanie tekstu: grep, awk, sed · Sortowanie i duplikaty ·
Narzędzia tekstowe II · xargs i find -exec · Skrypty bash · Git od podstaw
Każde zadanie ma panel wyjaśnień (co robi komenda, składnia, ważne flagi, przykłady w stylu
tldr) i wielopoziomową pomoc: wskazówka → wyjaśnienie → gotowe rozwiązanie. Gotowca dostajesz
dopiero, gdy sam o niego poprosisz trzeci raz.
- Skopiuj
lessons/_TEMPLATE.jsonnalessons/<temat>.jsoni wypełnij (pola_commentusuń). - Dopisz wpis do
lessons/index.json(file,id,title,icon,description). - Odśwież stronę — bez buildu i bez restartu serwera; kurs czytany jest z dysku przy każdym sprawdzeniu.
Ćwiczebne pliki, na których pracuje uczeń (readme.txt, users.csv, access.log…), leżą
w backend/seed/ i są kopiowane do świeżego kontenera przy starcie sesji.
Szkielet zadania:
Regexy w lastcmd warto pisać tolerancyjnie na warianty flag —
"ls\\s+(-[a-zA-Z]*a[a-zA-Z]*|--all)" łapie ls -a, ls -la i ls --all.
- Jedno środowisko dla wszystkich kursów. Obraz jest wspólny, więc kurs wymagający
python,dockerczynmapnie zadziała, dopóki nie powstanie „env per kurs". - Brak kont i synchronizacji — postęp zostaje w przeglądarce, na innym urządzeniu zaczynasz od zera.
- Brak uwierzytelnienia — projekt zakłada jednego użytkownika na
localhost; szczegóły w sekcji o bezpieczeństwie.
przeglądarka (xterm.js) ──WebSocket──► FastAPI ──PTY──► docker exec -it bash
│
└── /api/check ──► docker exec (weryfikacja zadania)
Backend to jeden plik (backend/main.py, ~340 linii), frontend to jeden plik
(backend/app.html). Bez frameworków frontendowych i bez kroku budowania.
MIT — patrz LICENSE.

{ "id": "unikalne-id", "prompt": "Utwórz nowy katalog o nazwie 'backup'.", "check": { "type": "script", "test": "[ -d backup ]" }, "command": { // panel wyjaśnień (opcjonalny, ale zalecany) "name": "mkdir", "fullName": "make directory", "what": "Tworzy katalog...", "syntax": "mkdir [opcje] nazwa...", "flags": [{ "flag": "-p", "desc": "twórz też katalogi nadrzędne" }], "examples": [{ "cmd": "mkdir backup", "desc": "utwórz katalog backup" }] }, "hints": ["Wskazówka...", "Wyjaśnienie...", "Wpisz: mkdir backup"] }