Первая версия получилась курсом по 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

пишешь .sh

поведение скрипта на фикстуре

Configuration

правишь конфиг

свойства конфига, идемпотентность

Investigation

работаешь в CLI, собираешь отчёт

воспроизводимость находки

Code

пишешь .py или .c

поведение программы

Сезон, целиком собранный из 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

Комментарии (1)


  1. khomenkoev
    29.09.2026 10:34

    Текст какой-то мне показался самбурный, как будто вырвали из середины,мы ведь не кино смотрим. Хотелось бы с самого начала без погружения в дебри, желательно перед выпуском выводить bash -x .sh ))