Вышел yesh v0.2.0.

В v0.2.0 можно проверить резервное копирование и восстановление с помощью rsync и rsnapshot, подготовить и опубликовать конфигурацию, проверить службу и журнал, посмотреть действия через --plan и убедиться, что после ошибки старые данные не исчезают. Команды Linux остаются видимыми; yesh сам по себе не делает ваши действия в системе безопасными.

Типичный административный скрипт на Bash начинается довольно прозаично. В нём обычно есть четыре шага:

  1. проверить, что действие допустимо;
  2. выполнить его;
  3. проверить результат;
  4. при ошибке остановиться так, чтобы было понятно, где и что уже изменилось.

На практике Bash усложняет проверку корректности именно этих четырёх шагов. Кавычки, неявное разделение слов, особенности set -e, неочевидное поведение конвейеров, различия между mv и cp на разных файловых системах, отсутствие явной границы «подготовили → опубликовали» — всё это приходится держать в голове одновременно. Поэтому даже в скрипте на 15–20 строк приходится мысленно компилировать последовательность действий, чтобы понять, что он действительно сделает.

Это не означает, что Bash плох. Bash — полноценный императивный язык с функциями, циклами, условиями, trap и обработкой ошибок. Он прекрасен для интерактивной работы, коротких команд и соединения существующих Linux-утилит. Проблема в другом: Bash не знает, какое изменение системы вы задумали, и не проверяет, соответствуют ли ему команды.

Синтаксически правильная команда всё ещё может обратиться не к тому пути, опубликовать неполный результат или оставить администратора без понятного доказательства успеха.

Где появляется yesh

yesh убирает часть этого шума вокруг команд. Он не делает Linux безопасным и не заменяет rsync, systemctl, ssh или tar. Идея проще:

команды остаются видимыми, аргументы — точными, ошибки — явными, а порядок действий — читаемым сверху вниз.

Например, обычное обновление конфигурации выглядит так:

set -euo pipefail if grep -q '^mode=production$' config.next; then install -m 0640 -- config.next /etc/myapp.conf.next mv -T /etc/myapp.conf.next /etc/myapp.conf systemctl restart myapp.service systemctl is-active --quiet myapp.service else echo "configuration validation failed" >&2 exit 2 fi 

В yesh тот же порядок записан как последовательность процедуры:

if grep -q '^mode=production$' config.next: install -m 0640 -- config.next /etc/myapp.conf.next publish /etc/myapp.conf.next to /etc/myapp.conf service restart myapp.service until active within 10s else: echo "configuration validation failed" exit 2 

Сначала проверка, затем подготовка, публикация, перезапуск и проверка состояния. Код не обязан быть намного короче; важно, чтобы его можно было понять до запуска.

publish выполняет переименование в пределах одной файловой системы и не переходит незаметно к копированию. Назначение должно быть новым именем или заменяемой символической ссылкой: yesh не сливает каталоги и не подменяет обычный существующий файл скрытой операцией.

Значения и команды

Строка остаётся одним значением, список раскрывается в отдельные аргументы, а переменная раскрывается явно через $name:

message = "backup finished" hosts = [web-1, web-2] echo $message for host in $hosts: echo $host 

yesh не превращает строку команды в текст для sh -c. Например:

install -m 0640 -- config.next /etc/myapp.conf.next 

получает такие аргументы:

["install", "-m", "0640", "--", "config.next", "/etc/myapp.conf.next"] 

rsync остаётся rsync, systemctl остаётся systemctl, а yesh отвечает за окружающие проверки, порядок действий и остановку после ошибки. Python по-прежнему подходит для сложной логики и библиотек, Ansible — для инвентаря и желаемого состояния множества узлов. yesh занимает более узкую промежуточную нишу.

Что показывает --plan

--plan показывает раскрытые действия без запуска процессов и открытия файлов, указанных в перенаправлениях. Вот нормализованный фрагмент настоящего прогона примера examples/rsync-incremental.yesh:

PLAN lock "/srv/example-snapshots/.rsync.lock" PLAN ["rsync" "-a" "--fsync" "--delay-updates" "--" "<environment value>" "<environment value>"] PLAN publish "/srv/example-snapshots/snapshot.1.tmp" to "/srv/example-snapshots/snapshot.1" 

До передачи видны блокировка, реальные флаги rsync и граница, после которой завершённое дерево станет доступно под постоянным именем. Значения из окружения заменяются на <environment value>, чтобы просмотр плана не печатал их содержимое.

Но план не является транзакцией и не фотографирует будущее состояние машины. Источник может исчезнуть после проверки, служба может изменить состояние до перезапуска, а внешняя команда всё равно может завершиться ошибкой. --plan отвечает на вопрос «что этот сценарий попытается сделать с такими значениями?», а не на вопрос «сработает ли это через минуту?».

Ошибка должна быть понятной

Обычная ошибка команды останавливает сценарий. Ненулевой результат команды, использованной как условие, может выбрать явную ветвь else. Отсутствующий исполняемый файл, ошибка запуска, сигнал, отмена или превышение времени остаются ошибками выполнения, а не превращаются молча в обычное условие.

Если падает любая стадия конвейера, падает весь конвейер. На POSIX отмена завершает группу принадлежащих сценарию процессов и дожидается дочерних процессов, а не останавливает только первый видимый PID.

Это не делает внешние программы добрыми. rsync всё ещё может удалить данные, если ему явно передали разрушительные аргументы. systemctl всё ещё может перезапустить службу. Прямой запуск с отдельными аргументами убирает повторный разбор оболочкой, но не заменяет проверку намерения администратора.

Что происходит при отказе

Количество тестов само по себе мало что доказывает. Гораздо полезнее один понятный пример того, что происходит при ошибке.

На временном Linux-стенде настоящий rsync запускается через обёртку, которая затем возвращает код 23, моделируя результат частичной передачи. Проверяется, что новый снимок не публикуется, предыдущий снимок не изменяется, временный каталог остаётся для диагностики, а повторный запуск завершается успешно.

Здесь проверяется простое обещание: частичная передача не должна стать резервной копией, которую увидит восстановление, а ошибка повторного запуска не должна уничтожить последнюю хорошую копию. Это не ответ на все возможные проблемы файловой системы и сети. Это один конкретный случай, проверенный на Linux-стенде.

В v0.2.0 есть и ещё одна практическая вещь: yesh может некоторое время наблюдать за одной службой systemd на Linux с cgroup v2. Он сохраняет историю в формате JSONL и строит сводку по памяти, процессору и дисковому вводу-выводу. Это удобно во время одного инцидента или обслуживания, но это не постоянный сборщик метрик и не управление cgroup.

Когда это имеет смысл

Рассмотрим резервное копирование, которое не должно публиковать неполный результат:

source = environment YESH_BACKUP_SOURCE staging = "/srv/backups/.next" published = "/srv/backups/current" rsync -a -- $source $staging publish $staging to $published 

Полный сценарий добавляет предварительные проверки, блокировку и новый пустой каталог назначения для каждого запуска. Если rsync возвращает код, означающий частичную передачу или превышение времени, publish не достигается, а прежнее опубликованное дерево остаётся доступным для восстановления.

У yesh есть цена: ещё один исполняемый файл, ещё один язык и ещё одна среда выполнения, которую нужно сопровождать. Для однострочной команды это не оправдано. Польза появляется там, где Bash-сценарий уже труднее проверить, чем сами Linux-команды: при публикации файлов, отмене процессов, ограниченных проверках служб и проверке восстановления.

Практический способ оценить такой инструмент простой: прочитать учебник по синтаксису, запустить --plan, посмотреть раскрытые аргументы, вызвать один отказ и проверить, что осталось неизменным. Если процедура после этого не стала проще для объяснения следующему администратору, Bash остаётся более подходящим выбором.

Русская страница проекта · Выпуск v0.2.0