Я собрал себе курс по Linux (но сначала получился курс по .sh)

Первая версия получилась курсом по shell-скриптингу. А я не этого хотел: мне нужно было разобраться с Linux на практике, а не в теории по книге — права, службы, журналы, монтирование, сеть. На выходе же лежала полсотня заданий вида «напиши .sh, который…». Хотя каждое по отдельности выглядело нормально.
Заметил я это, когда стал считать, что человек физически делает в каждой серии. Почти везде одно и то же: открывает редактор и пишет скрипт.
Причина скучная и техническая. Скрипт легко проверить: запустил, посмотрел вывод, сравнил. Всё остальное проверять неудобно, поэтому всё остальное вымывается само, без чьего-либо умысла. Модель, которой я всё это надиктовывал, пошла по пути наименьшего сопротивления, опираясь на курс по языку C, который я дал ей как образец.
А администратор большую часть времени не автоматизирует. Он читает конфиг, смотрит, что запущено, и выясняет, почему оно работает не так, как написано в документации. Скрипты тоже пишет — но это следствие, а не основное занятие.
Стоп, я начал с середины
Речь про KERNEL SHADOWS: 101 серия в восьми сезонах, лежит на гитхабе под GPL v3, бесплатно. Это не видеокурс и не платформа: это репозиторий с текстом. Папка на серию, внутри README с теорией и заданием, каркас файла, тесты и эталонное решение. Проходишь в редакторе с терминалом — я прохожу в Cursor. Регистрации никакой.
Выглядит это так:
git clone https://github.com/gfazzz/kernel-shadows.git
cd kernel-shadows/season-01-shell-foundations/s01e01-terminal-awakening
less README.md
cp starter/whereami.sh artifacts/
bash tests/test.sh
В starter/ лежит не решение, а дырки:
set -euo pipefail
# TODO 1: получи текущий абсолютный путь (подсказка: команда из 3 букв)
# и сохрани его в переменную here.
here="" # <-- замени на вызов нужной команды
# TODO 2: выведи текущую директорию и домашнюю директорию ($HOME).
echo "Текущая директория (pwd): ${here}"
# TODO 3: определи, находишься ли ты в корне "/", и напечатай понятный статус.
Тест на нём сразу красный, и это нормально:
PASS: whereami.sh найден
PASS: синтаксис bash корректен
PASS: есть shebang
FAIL: в выводе нет текущего пути /home/max/ops/deep/place (скрипт должен печатать pwd)
FAIL: в выводе нет $HOME (/home/max)
FAIL: похоже, путь захардкожен: вывод не меняется при смене директории
Итог: 3 passed, 3 failed
Третий FAIL — про то, ради чего вообще стоило писать тесты руками. Он не сверяет текст вывода, он запускает скрипт из двух разных каталогов и смотрит, меняется ли ответ. Захардкоженный путь так не пролезает.
Эталон лежит рядом в solution/, и открывать его до своей попытки — единственный способ испортить себе курс. make progress эталон не засчитывает: серия пройдена, только если зелёный тест прошёл на твоём файле.
Из софта нужны bash, git, make, python3. Ни один тест не просит root, сеть, докер или железо — про это дальше.
Правило, которое пришлось ввести насильно
Чтобы курс перестал скатываться в bash, я расписал Клоду типы серий и сделал баланс критерием приёмки сезона:
Тип | Что делаешь | Что смотрит тест |
|---|---|---|
Automation | пишешь | поведение скрипта на фикстуре |
Configuration | правишь конфиг | свойства конфига, идемпотентность |
Investigation | работаешь в CLI, собираешь отчёт | воспроизводимость находки |
Code | пишешь | поведение программы |
Сезон, целиком собранный из Automation, не принимается. Сейчас по факту: 25 серий на автоматизацию, 38 на конфигурацию, 30 на разбор, 8 на код. Скрипты — четверть курса.
Investigation звучит хорошо ровно до первого теста
Как проверить, что человек разобрался в инциденте? Не «написал ли он скрипт», а именно разобрался. Плюс я себе заранее запретил тесты, которым нужен root, сеть, докер, кластер или железо.
Запрет не из аскетизма. Курс, для которого надо поднять Kubernetes, не пройдёт никто, включая автора, — а делался он в первую очередь как раз для автора. А тест, дёргающий живую сеть, падает через раз, и человек решает, что сломан он. Первая версия серии про доступность узлов честно вызывала ping, и от этого пришлось уйти.
Сейчас тест кладёт свой ping в начало PATH:
cat > "${FAKEBIN}/ping" <<'MOCK'
#!/usr/bin/env bash
host="${!#}"
case "${host}" in
up-*|10.0.0.1) exit 0 ;;
*) exit 1 ;;
esac
MOCK
Проверяется не связность, а логика решения: сохранил ли код возврата, не захардкодил ли статус, отличает ли «не ответил» от «ответил, но без поля time=». То же самое сделано для ss, dig, ufw и dpkg.
Конфиг можно прочитать глазами той программы, которая его читает
Дальше был systemd, и вот тут ИИ довольно долго считал, что зашёл в тупик. Серия про юнит: человек пишет .service, а как убедиться, что он правильный, если systemd в тесте не запустить?
Оказалось, запускать и не надо. Юнит — это текст с известной семантикой, и семантику можно воспроизвести. Тест читает файл так, как читает его systemd: секции значимы, побеждает последнее присваивание, пустое значение сбрасывает настройку, комментарии не в счёт. Потом сверяет эффективные значения с требованиями к службе. Отдельно ловятся конвейер в ExecStart (оболочки-то нет), After без Wants и настройка, которую ниже по файлу сбросили пустым присваиванием.
Неожиданно это оказалось полезнее запуска. Запущенная служба отвечает на вопрос «стартовала ли она сейчас, в этой системе, с этим окружением». Разбор отвечает на «что здесь вообще написано», а это и есть навык, за которым человек пришёл. Тем же способом проверяются sshd_config, fstab, logrotate, compose.yaml, манифесты Kubernetes и правила auditd.
Побочный эффект приятный: тест ловит то, что в жизни всплывает недели спустя. Директиву, которая осталась внутри комментария. Директиву, заданную дважды. Host * в начале ~/.ssh/config, из-за которого все частные секции ниже перестают действовать.
Спорить с этим можно, и я заранее соглашусь: если тебе нужно убедиться, что служба реально поднимается на твоём дистрибутиве с твоим SELinux, разбор конфига этого не покажет. Для учебной задачи важнее первое, для продакшена — второе. Две серии, где без живого ядра совсем никак (сборка модуля, insmod, чтение /dev/shadow0), вынесены в отдельную цель и без Linux честно печатают SKIP.
А криминалистика и в жизни работает со снимком
Серии про разбор инцидентов гоняются на снятых состояниях: вывод ps, содержимое /proc, журналы, дамп /proc/net/tcp. Надо найти скрытый процесс, стёртый бинарник и адрес управляющего сервера, закодированный little-endian. Тут компромисса вообще нет: живой машине, на которой стоит руткит, нельзя задавать вопросы о ней самой.
Что ломалось
Тесты, зелёные на моей машине, — не тесты. Поэтому в Makefile завели три цели, каждая после конкретного позора.
make test-repeat гоняет всё дважды подряд. Ловит серии, где второй прогон видит мусор от первого.
make test-locale запускает под LC_ALL=C и под чужой таймзоной с немецкой локалью. Ловит проверки, которые сравнивают отсортированный вывод: в другой локали сортировка другая, и тест краснеет на верном решении.
make clean-clone разворачивает курс с нуля и прогоняет на свежем клоне. Самая обидная находка была именно тут: типовой .gitignore вычёркивает *.log, ключи и .env, а в курсе по Linux это ровно учебные материалы — фикстуры для разбора журналов, учебные ключи SSH и TLS, app.env контейнерных серий. Локально всё на месте, потому что оно у тебя уже есть, а в клоне задания остаются без входных данных. Сейчас игнорирование идёт по каталогу запуска, а не по расширению, и в .gitignore висит комментарий капслоком, чтобы мы не переписали это обратно.
Из чего это состоит
Восемь сезонов, восемь стран, шестьдесят дней операции. Ты — админ, которого наняли построить и защитить инфраструктуру на полсотни узлов, и по ходу выясняется, что защищать придётся всерьёз.
Сезон | Тема | Серий |
|---|---|---|
1 | Оболочка и основы | 16 |
2 | Сети | 12 |
3 | Администрирование | 13 |
4 | DevOps и автоматизация | 12 |
5 | Безопасность | 12 |
6 | Встраиваемый Linux | 12 |
7 | Эксплуатация | 12 |
8 | Финальная операция | 12 |
Названия сезонов ничего не говорят, поэтому вот третий по темам серий:
s03e01 Список жильцов: «Кто есть на этой машине»
s03e02 Три буквы: «Кто что может»
s03e03 Заряженный пистолет: «Каждому ровно то, что нужно»
s03e04 PID 6623: «Имя, которое пишет себе сам»
s03e05 Служба, которая вернётся: «Кто их запускает»
s03e06 Пять минут по расписанию: «Кто догонит пропущенное»
s03e07 Вечер 14 октября: «Всё это было записано»
s03e08 Место, которого нет: «df говорит одно, du — другое»
s03e09 Пятьсот гигабайт, которых не видно: «Диск есть, места нет»
s03e10 Машина, которая не поднялась: «Кто скажет ей, что монтировать»
s03e11 Копия, которой не было: «Когда вы восстанавливались в последний раз»
s03e12 Строка, которой не было: «Кто скажет службе, что журнал повернули»
s03e13 03:47: «Третий вопрос»
Серия — это один концепт, одно задание, один проверяемый артефакт, 45–90 минут. Сезон собирает работающую вещь, а не набор упражнений: shadow_toolkit, netshield, fleet_admin, shadow_iac, hardening_kit, shadow_mesh, aurora, shadow_core. Финал новой темы не вводит, он принимает построенное: двенадцать фаз, по одной на навык каждого сезона.
Подсказок по ходу становится меньше. Первые три сезона дают полный каркас и эталон, четвёртый с пятым — каркас и эталон после попытки, шестой с седьмым — только интерфейсы, восьмой — договор вызова и всё.
Это спин-офф моего же курса по языку C, про который я писал три недели назад. Вселенная и персонажи общие, разница в том, что там пишешь программы, а здесь управляешь системами, на которых они крутятся. Идея была записана через три дня после первой, и тогда я ещё не знал, приквел это будет или спин-офф.
Про ИИ, раз уж без него ничего бы не было
Оба курса я собрал себе сам, с ИИ, руками почти не писал. Вывод из этого получился не тот, которого обычно ждут.
Код вместо меня ИИ тут не писал, а сделал две другие вещи.
Первая: помог составить программу под меня. Не «сгенерируй курс по Linux» — так получается оглавление учебника, я пробовал. А разговор о том, чему я хочу научиться, в каком порядке, на каком материале и что должно получиться в конце. Раньше такое было доступно тем, у кого есть живой наставник.
Вторая: помогает, пока я по этому курсу иду. В сюжете есть наставник по имени LILITH, и за ним стоит обычный чат в Cursor, открытый рядом с кодом. Проходить надо в режиме Ask. В Agent ассистент правит файлы сам: серия окажется решённой, а ты не наберёшь ни одной команды. Первый раздел .cursorrules запрещает ему писать в artifacts/, выдавать решение серии и пересказывать solution/ — но режим переключаешь ты, а не файл, так что это скорее напоминание, чем защита.
Поэтому в конце каждой серии ИИ выписал вопросы, которые стоит ему же и задать, и один запрещённый. Запрещённый всегда выглядит совершенно разумным и вредит именно здесь. В серии про разбор пакета это «разбери заголовок за меня»: зашитая длина IP-заголовка работает на девяти пакетах из десяти, и десятый надо найти самому. В серии про профилирование — «оптимизируй мой код»: сначала профиль, потом оптимизация, наоборот это гадание.
Обычно разговор про ИИ сводится к тому, что админы и программисты скоро не понадобятся. По-моему, наоборот. Подешевела не профессия, а персональная программа обучения: раньше между «хочу разобраться в Linux» и «разбираюсь» стояли поиск нормального курса, чужой темп и чужой порядок тем, теперь стоит вечер разговора с ассистентом. Человек, который умеет посмотреть в sshd_config и увидеть, что директива не действует, нужен ровно так же, как был нужен.
Репозитории: KERNEL SHADOWS — Linux, GPL v3 OPERATION MOONLIGHT — C, MIT
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.