Интерактивная учебная платформа для отработки Linux-команд в формате игрового практикума в компьютерном классе.
- Краткое описание
- Руководство по установке
- Руководство по созданию кастомных игр
- Отличия от v2
- Архитектура и устройство проекта
- API и события реального времени
- CI/CD и релизный архив
- Ограничения и практические рекомендации
SchoolLinux помогает преподавателю проводить практические занятия по Linux терминалу в виде игр.
Как это работает:
- ученики регистрируются через веб-интерфейс.
- Преподаватель выбирает игру и запускает раунд.
- Backend по SSH разворачивает индивидуальные задания на машинах учеников.
- ученики ищут «клады» (или выполняют другие условия игры) в Linux-терминале.
- Прогресс и очки видны преподавателю в реальном времени.
Ключевые особенности текущей версии:
- Плагинная система игр (
backend/app/games/*.py). - FastAPI + Socket.IO (ASGI) для REST и realtime-обновлений.
- Типизированное состояние и JSON-персистентность в
SLData.json. - React + TypeScript фронтенд с отдельными экранами для преподавателя и ученика.
- Тесты backend (
pytest) и CI-пайплайны для тестирования и сборки.
Если нужен готовый архив без сборки из исходников:
- Откройте страницу GitHub Releases проекта.
- Скачайте самый свежий релизный архив
SchoolLinux-<tag>.tar.gz. - Распакуйте архив на целевой машине.
- Запустите установку:
./install.sh
./run.shЭтот вариант удобен для быстрого развёртывания на машине преподавателя, когда исходники и CI уже подготовили релизный пакет.
После запуска Frontend системы будет доступен на 8000 порту, соответствующие адреса выводяться в терминал.
Сервер (компьютер преподавателя):
- Linux
- Python 3.12+
- Node.js (рекомендуется актуальная LTS; в CI используется Node 24) // Только при ручной установке, релизная версия содержит собранный Frontend
- Доступ к ученикам по SSH (порт 22)
Клиенты (компьютеры учеников):
- Linux
- Запущенный SSH-сервер
- Единые учетные данные для SSH-доступа
- Доступ к серверу преподавателя по сети
git clone https://github.com/A2020GK/SchoolLinux.git
cd SchoolLinuxpython -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtСоздайте .env в корне проекта (можно на основе .env.example):
SSH_USER=game
SSH_PASSWORD=game
SSH_TIMEOUT=10
ALLOW_IP_OVERRIDE=false
IP_OVERRIDE_HEADER=X-Debug-IPПояснения:
SSH_USER/SSH_PASSWORD- учетные данные для подключения к машинам учеников.ALLOW_IP_OVERRIDE=trueиспользовать только для локальной отладки и тестов.
Установка зависимостей выполняется из корня проекта:
npm installФронтенд находится в frontend/, сборка идет через корневой Vite-конфиг.
Терминал 1 (backend):
source .venv/bin/activate
fastapi run --host 0.0.0.0Терминал 2 (frontend):
npx vite --host 0.0.0.0 --port 4000После запуска:
- Backend API доступен на
http://<teacher_ip>:8000 - Frontend (Vite dev server) обычно на
http://<teacher_ip>:4000
npm run buildАртефакты будут в frontend/dist.
При наличии собранного Frontend API-сервер будет пытаться отдать его файлы на 8000 порту.
source .venv/bin/activate
pytest backend/testsКастомные игры добавляются без изменения внутренних модулей backend: достаточно создать новый файл в backend/app/games/.
- Путь:
backend/app/games/<my_game>.py - Файлы, начинающиеся с
_, автообнаружением игнорируются.
Рекомендуемый импорт:
from backend.app.games import Game, GameSettingsItem, execute_command, upload_and_run_scriptМинимум нужно реализовать:
class MyGame(Game):
def install(self, client, game_data):
...
def check(self, client, game_data):
...
def uninstall(self, client, game_data):
...Если игра проверяет текстовый ответ, добавьте:
string_submission = True
def check_string_submission(self, submission, game_data):
...name- отображаемое название игры.description- описание задания для ученика.settings_form- настройки, редактируемые преподавателем.string_submission- режим проверки строкового ответа.anticheat_required- требуется ли античит-проверка (если используется в вашем сценарии). // На текущей момент не реализованно, в процессе.required_user_score- целевой баллdefault_game_data- шаблон данных игры на ученика на раунд.
Важно:
- В
game_dataхраните только прогресс конкретного ученика во время раунда. - Конфигурацию игры берите из
self.settings. - Всегда удаляйте созданные файлы/папки в
uninstall.
Поддерживаемые типы:
stringnumberbooleanoption(нужен словарьoptions)
Пример:
settings_form = {
"difficulty": GameSettingsItem(
name="Сложность",
type="option",
options={"easy": "Easy", "hard": "Hard"},
default="easy",
),
"files": GameSettingsItem(
name="Количество файлов",
type="number",
default=30,
),
}В backend.app.games вместе с Game и GameSettingsItem реэкспортируются SSH-хелперы:
-
execute_command(client, command)- Выполняет shell-команду на машине ученика.
- Возвращает кортеж
(stdout_text, stderr_text)как строки (str). - Удобно использовать в
checkиuninstallдля коротких команд.
-
upload_and_run_script(client, name, script)- Загружает скрипт в
/tmp/<name>.sh, делает его исполняемым и запускает черезbash. - Возвращает кортеж
(stdout_text, stderr_text)как строки (str). - Если скрипт завершился с ошибкой (ненулевой
exit code), выбрасываетRuntimeError. - Рекомендуемый вариант для
install, когда нужно выполнить несколько команд подряд.
- Загружает скрипт в
Пример проверки stderr у execute_command:
stdout, stderr = execute_command(client, 'cat "$HOME/Game/message.txt"')
if stderr.strip():
return 0
content = stdout.strip()from backend.app.games import Game, GameSettingsItem, execute_command, upload_and_run_script
class EchoGame(Game):
name = "Echo"
description = "Создайте $HOME/Game/message.txt с ожидаемым текстом"
string_submission = False
anticheat_required = False
required_user_score = 1
settings_form = {
"expected_text": GameSettingsItem(
name="Ожидаемый текст",
type="string",
default="hello",
)
}
default_game_data = {
"checked": False,
}
def install(self, client, game_data):
script = """#!/bin/bash
set -e
mkdir -p "$HOME/Game"
"""
upload_and_run_script(client, "echo_game_install", script)
game_data["checked"] = False # Проверяли ли ранее?
def check(self, client, game_data):
expected = self.settings["expected_text"]
stdout, stderr = execute_command(client, 'cat "$HOME/Game/message.txt" 2>/dev/null')
if stderr.strip():
return 0
content = stdout.strip()
if content == expected and not game_data["checked"]:
game_data["checked"] = True
return 1
return 0
def uninstall(self, client, game_data):
execute_command(client, 'rm -rf "$HOME/Game"')
def check_string_submission(self, submission, game_data):
return 0- При старте backend выполняется автообнаружение игр в
backend/app/games/. - Ключ игры соответствует имени файла (без
.py). - Настройки игры автоматически сохраняются в общем состоянии и восстанавливаются при перезапуске.
Ниже перечислены ключевые изменения относительно v2 (ветка v2).
-
Было: монолитная структура файлов в корне.
-
Стало: модульная структура
backend/app/*+ отдельный современныйfrontend/*. -
Было: упор на один сценарий игры.
-
Стало: плагинная модель игр с автообнаружением классов-наследников
Game.
-
Было: pickle-сериализация (
data.pkl) и менее прозрачный формат. -
Стало: JSON (
SLData.json) с валидацией, санитизацией и типизированной моделью состояния. -
Было: статус
reg | init | run. -
Стало: явная глобальная машина состояний
idle | init | running.
-
Было: маршруты вида
/students/*,/game/klad,/info. -
Стало: унифицированные маршруты
/user/*и/game/*с более строгой валидацией и ролями. -
Было: модель данных ученика с
kladsкак набором строк в рамках старой логики. -
Стало:
score+game_data, где подсчет и прогресс управляются конкретной игрой.
-
Было: старый frontend (сейчас сохранен как
frontend.old/). -
Стало: новый React + TypeScript frontend в
frontend/с контекстами, типами, API-слоем и разделением Teacher/Student экранов. -
Добавлено: mock preview режим через переменные окружения (
VITE_MOCK_PREVIEW,VITE_MOCK_SCREEN).
- Было: базовые realtime-обновления.
- Стало: формализованные Socket.IO события (
users_update,kicked,game_change,game_state_changed) и более предсказуемая синхронизация UI.
- Было: документация фокусировалась на ручном запуске.
- Стало: встроенные pytest-тесты backend и CI workflows:
.github/workflows/tests.yml- автотесты backend..github/workflows/frontend-build.yml- проверка сборки frontend..github/workflows/release-tarball.yml- сборка релизного архива по тегу.
- Добавлена автоматическая сборка tar.gz-архива
SchoolLinux-<tag>.tar.gzс backend, frontend dist, скриптами запуска и.envна базе.env.example.
- Язык: Python 3.12
- Фреймворк: FastAPI
- Realtime: python-socketio (ASGI)
- SSH: Paramiko
- Тесты: pytest
Основные директории:
backend/app/routers- HTTP-маршруты (/user,/game)backend/app/services- бизнес-логикаbackend/app/schemas- pydantic-схемыbackend/app/socket- socket-сервер, менеджер подключений, обработчикиbackend/app/games- встроенные и кастомные игрыbackend/app/state.py- загрузка/сохранение состояния приложения
- Язык: TypeScript
- UI: React 19
- Сборка: Vite
- Realtime-клиент: socket.io-client
Основные директории:
frontend/views- экраны Teacher/Studentfrontend/contexts- глобальные контексты состоянияfrontend/api- типизированные HTTP и socket-клиентыfrontend/types- контракты данных
GET /ping
Пользователи:
POST /user/registerGET /user/all(только преподаватель)GET /user/meDELETE /user/mePOST /user/kick/{ip}(только преподаватель)
Игра:
GET /game/GET /game/list(только преподаватель)POST /game/(выбор игры/настроек)POST /game/checkPOST /game/startPOST /game/stopGET /game/state
users_updatekickedgame_changegame_state_changed
Сокет-маршрут подключен как /socket.io.
-
.github/workflows/tests.yml- Запускает
pytest backend/testsна push и pull_request.
- Запускает
-
.github/workflows/frontend-build.yml- Проверяет сборку frontend (
npm run build) на push и pull_request.
- Проверяет сборку frontend (
-
.github/workflows/release-tarball.yml- Срабатывает при пуше тега.
- Строит frontend, гоняет backend-тесты, формирует
SchoolLinux-<tag>.tar.gz.
backend/frontend/dist/requirements.txtREADME.mdinstall.shrun.sh.env(из.env.example)
После распаковки архива:
./install.sh
./run.sh- Система рассчитана прежде всего на локальную школьную сеть.
- Ролевая модель по IP (loopback = преподаватель) удобна в классе, но не заменяет полноценную аутентификацию.
ALLOW_IP_OVERRIDEвключайте только в отладке/тестах.- Перед занятием обязательно проверяйте SSH-доступ до всех машин учеников.
- Для нестабильных сетей полезно провести «прогон» короткой тестовой игры до начала урока.
Если вы хотите добавить новую игру, начните с раздела «Руководство по созданию кастомных игр», создайте файл в backend/app/games/ и перезапустите backend: игра будет обнаружена автоматически.
Developed by Antony Karasev (A2020GK), School №192, Moscow, Russia