Иногда приходится настраивать различные *unix системы: персональные компьютеры, VPS, Raspberry Pi и так далее. Но когда их становится много, настраивать их становится всё сложнее и сложнее. Поэтому хочется автоматизировать этот процесс. В данной статье я расскажу, как я решил подобную проблему, расскажу про некоторые существующие решения, а также покажу магию bash!

Проблема

Приведём несколько часто встречающихся случаев, когда надо настроить linux под себя, то есть установить пакеты различными способами, настроить их (с помощью команд или изменяя конфиги).

  • VPS. Когда ты покупаешь облачный сервер, то он обычно пуст. Там почти никогда нет того, что тебе нужно: docker, docker-compose, настроенного VPN или почтового сервера. Не для всего нужен k8s с возможностью резервирования дополнительных мощностей, да и настройка подобной инфраструктуры занимает время.

  • Персональный компьютер, виртуальные машины. Если вы предпочитаете операционные системы на основе Linux, то вам наверняка не раз приходилось пробовать новые дистрибутивы и сборки. И со временем вы обрастаете полезным инструментарием для работы на них. Но если вы вдруг купите новое железо или друзья попросят поделиться наработками? А может, у вас навернулся SSD или вы потеряли рабочий ноутбук?

  • Установка и настройка одного и того же на различные дистрибутивы. Не все программы можно одинаково легко и безболезненно установить и настроить одновременно на Arch Linux и Debian. И если вы захотите перейти с Ubuntu на Arch Linux, то могут возникнуть сложности, и большие

  • Сборка и установка пакетов из исходного кода. Нередко, чтобы получить актуальную или конкретную версию программы, нет иного способа, кроме как клонировать репозиторий, установить зависимости, собрать программу, установить её, а после настроить под себя. А иногда зависимости тоже необходимо собирать вручную. Это становится проблемой, когда весь процесс надо повторить на других машинах.

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

Пример такого скрипта на bash:

# Скачиваем скрипт-установщик
curl -fsSL https://get.docker.com -o get-docker.sh

# Устанавливаем докер
sudo sh get-docker.sh

# Удаляем скрипт-установщик
rm get-docker.sh

# Запускаем службу Docker
sudo systemctl enable --now docker

# Выдаём права текущему пользователю
sudo groupadd docker
sudo usermod -aG docker $USER

# Применяем права
newgrp docker

Скрипт простой и рабочий, хотя и не лишён недостатков, например, в плане безопасности.

Хотя для большинства пакетов и не требуется ввода несколько команд, но иногда приходится часами разбираться с проблемами в зависимостях тех или иных пакетов. Настраивать их под себя и так далее.

Решения

Решают эту проблему по-разному. Приведу несколько интересных способов, что мне запомнились:

  1. Свой дистрибутив/ядро/сборка. Собственные сборки на все случаи жизни.

    Мнение: Трудоёмко и сложно в настройке и обновлении, но максимально стабильно и самый лучший способ кастомизации.

  2. Свой универсальный пакет. В пакеты можно встраивать и скрипты для конфигурации. Что мешает создать пакет mydesktop, который при установке будет устанавливать и настраивать все ваши любимые программы.

    Мнение: Менее трудоёмко, чем свой дистрибутив, но требует для настройки CI/CD для выпуска пакетов. Удобен для обновления и миграций конфигурации множества машин.

  3. Скрипты на Python. С помощью таких библиотек как: Fabric, Invoke, Plumbum, Ansible и subprocess.

    Мнение: Более гибкий способ, чем скрипты и собственно команды. Можно создать, например, универсальный пакетный менеджер, фасад для работы во всеми существующими. Но требует предварительной установки Python на машину (если ею не управляют через SSH).

  4. Написать скрипты на Bash.

    Мнение: самый простой способ - скопировать команды из терминала в файл, а после доставить через wget, SCP, curl куда угодно. Но не такой гибкий и с обновлениями есть проблемы.

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

Идея

Допустим у нас есть 3 скрипта: install-docker.sh, install-wireshark.sh, install-tmux.sh.

Хотим создать из них 2 итоговых скрипта: desktop.sh и server.sh. В первый включить все существующие скрипты, а во второй только install-docker.sh и install-tmux.sh

Почему сразу их не включить в эти скрипты? Принцип единственной ответственности: одна программа - один скрипт.

Реализовать на bash это достаточно просто:

#!/usr/bin/env bash

cat install-wireshark.sh > desktop.sh
cat install-tmux.sh >> desktop.sh
cat install-docker.sh >> desktop.sh

cat install-tmux.sh > server.sh
cat install-docker.sh >> server.sh

Теперь мы можем запустить этот скрипт и получить на выходе 2 скрипта для конфигурации.

Создадим папку scripts и все скрипты будем хранить там:

mkdir scripts

mv install-docker.sh scripts/docker.sh
mv install-tmux.sh scripts/tmux.sh
mv install-wireshark.sh scripts/wireshark.sh

Теперь составим список всех скриптов в папке, чтобы всегда можно было добавить что-нибудь ещё и добавим список для выбора (whiptail) для наглядности:

# Выясняем абсолютный путь до текущего скрипта
SCRIPTPATH="$(
	cd "$(dirname "$0")" >/dev/null 2>&1
	pwd -P
)"

# Указываем путь до папки со скриптами
SCRIPTS_DIRECTORY=$SCRIPTPATH/scripts

# Инициализиуем переменную со списком для checklist
_checklist=

# Цикл по всем скриптам в папке
for i in $SCRIPTS_DIRECTORY/*.sh; do
    [ -e "$i" ] || continue
    script_name=$(basename "$i")

    _checklist+="$script_name $script_name off "
done

# Checklist
OUTPUT=$(whiptail --checklist "Please pick one" 10 60 4 $_checklist 3>&2 2>&1 1>&3)
Результат
Результат

Абсолютный путь до исполняемого скрипта SCRIPTPATH необходим, если скрипт будет запускаться из произвольного места.

Осталось только выделенные скрипты собрать в один:

output_script=$SCRIPTPATH/output.sh

for select_script_name in $OUTPUT; do
    # Убираем кавычки:
		select_script_name="${select_script_name%\"}"
		select_script_name="${select_script_name#\"}"
    # Собираем скрипты
    cat $SCRIPTS_DIRECTORY/$select_script_name >> $output_script
done

Таким образом, мы получили простой скрипт для сборки скриптов для установки и настройки.

Итоговый код
#!/usr/bin/env bash

# Выясняем абсолютный путь до текущего скрипта
SCRIPTPATH="$(
	cd "$(dirname "$0")" >/dev/null 2>&1
	pwd -P
)"

# Указываем путь до папки со скриптами
SCRIPTS_DIRECTORY=$SCRIPTPATH/scripts

# Инициализиуем переменную со списком для checklist
_checklist=

# Цикл по всем скриптам в папке
for i in $SCRIPTS_DIRECTORY/*.sh; do
    [ -e "$i" ] || continue
    script_name=$(basename "$i")

    _checklist+="$script_name $script_name off "
done

# Checklist
OUTPUT=$(whiptail --checklist "Please pick one" 10 60 4 $_checklist 3>&2 2>&1 1>&3)


output_script=$SCRIPTPATH/output.sh

for select_script_name in $OUTPUT; do
    # Убираем ковычки:
		select_script_name="${select_script_name%\"}"
		select_script_name="${select_script_name#\"}"
    # Собираем скрипты
    cat $SCRIPTS_DIRECTORY/$select_script_name >> $output_script
done

Заключение

Но простого скрипта для слияния нескольких файлов недостаточно, чтобы это было полезно для повседневной жизни. Вот что ещё предстоит реализовать для этого:

  1. Зависимости из других скриптов. Например, чтобы использовать скрипт install-docker-compose.sh для начала надо использовать install-docker.sh.

  2. Пользовательский интерфейс: CLI и/или TUI.

  3. Логирование и работа с исключениями

  4. Скрипты могут работать по-разному для различных систем. Поэтому скрипты, как минимум, должны знать:

    • Какие в системе установлены пакетные менеджеры

    • Какой дистрибутив используется

    • Какие установлены WM и DE

    • Версия ядра

  5. Возможность в итоговый скрипт запаковывать дополнительные файлы (например, конфигурационные файлы)

  6. Способы публикации созданных конфигурационных скриптов и доставки на устройства

  7. Тестирование проекта

Если вам интересно, как это всё можно реализовать на чистом bash, то можете написать комментарии, что конкретно вам больше всего интересно. А так же можете посмотреть что в итоге у меня вышло: Oh my Linux!

P.S.

Под конец хочу ещё раз напомнить, что в данной статье не преследуется цель создать аналог новый Fabric или Ansible, но на чистом Bash. Тут я делюсь, своими заметками о том, что Bash тоже интересный и вполне рабочий инструмент, на котором можно создавать проекты.

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


  1. lair
    22.11.2021 20:25
    +2

    Что-то это выглядит так, как если бы вы писали свой собственный провижнинг. Казалось бы, почему не взять готовый?


    Ну не знаю там, скажем, когда передо мной встала задача повторяемо разворачивать что-то на малинке, я взял cloud-init.


    1. dodo101000101 Автор
      23.11.2021 18:54

      в данной статье не преследуется цель создать аналог новый Fabric или Ansible, но на чистом Bash. Тут я делюсь, своими заметками о том, что Bash тоже интересный и вполне рабочий инструмент, на котором можно создавать проекты.


      1. lair
        23.11.2021 18:54

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

        Но зачем?


        1. dodo101000101 Автор
          23.11.2021 19:31

          Вопрос на который каждый находит ответ сам.

          Например, для меня было важно, чтобы можно было передать команду, что конфигурирует машину к которой никто не имеет или не будет иметь доступа, кроме физического (air-gapped environment).


          1. lair
            23.11.2021 19:32

            … и откуда вы в этом случае докер поставите?


            1. dodo101000101 Автор
              23.11.2021 19:38

              Рассказываю:

              Ставим докер, потом отрубаем интернет. На докер приходят готовые образы из местного registry. Звучит криво,поэтому обычно в закрытых системах не используется Docker. Скрипт его установки я использую для себя.


              1. lair
                23.11.2021 19:39
                +1

                Ставим докер, потом отрубаем интернет.

                Этот подход с равным успехом сработает и без баша. Засетапили машину любым удобным велосипедом (cloud-init, кстати, тоже интернет не нужен), потом отрубили сеть.


  1. Tuxozaur
    22.11.2021 20:46
    +28

    Зачем изобретать велосипед, если лучше потратить время на изучение Ansible?


    1. dodo101000101 Автор
      23.11.2021 18:58

      Ansible - хороший инструмент, который я уже начал изучать, и который собираюсь использовать в ближайшее время.

      Но

      в данной статье не преследуется цель создать аналог новый Fabric или Ansible, но на чистом Bash. Тут я делюсь, своими заметками о том, что Bash тоже интересный и вполне рабочий инструмент, на котором можно создавать проекты.


    1. telpos
      25.11.2021 14:16

      Ansible не удобен в отладке. Во всём остальном хорош


      1. nad_oby
        27.11.2021 03:56

        Познай molecule и придет к тебе свет.

        Но все сценарии не получилось нормально молекулой тестировать, только те где нужно ставить и конфигурировпть один два продукта.

        Но это не молекула плохая а архитектура была дерьмовая


  1. Naves
    22.11.2021 20:46
    +9

    Следующим этапом начните изучать ansible, и делайте вот это все через него.

    Странно, что вы пропустили его как решение для вашей задачи.


    1. dodo101000101 Автор
      23.11.2021 18:59

      Благодарю, за то что указали на это. Поправил статью.


  1. Loggus66
    22.11.2021 20:50
    +5

    Но требует предварительной установки Python на машину (если ею не управляют через SSH)

    Принципиально можно понять это возражение, если речь идёт об air-gapped environment без SSH. С другой стороны, если можно притащить через прокси сборщик Bash-скриптов, можно и контейнер с Ansible и Python, да хоть с чем, главное, чтобы с него SSH-соединение было.

    Велосипеды - это интересно, но в итоге они всё же проигрывают известным системам при наращивании масштаба. Если же упороться и сделать прохождение этапа только в случае необходимости и с проверкой результата (существует ли файл? Есть ли .lock файл, сигнализирующий об успешном прохождении этапа?), то выучить Ansible просто быстрее.


    1. dodo101000101 Автор
      23.11.2021 19:05

      Поэтому я и не рассматривал ситуации с использованием SSH, так как эта ниша давно занята известными решениями.

      Про air-gapped environment вы заметили верно. Иногда мне приходится работать с оборудованием, что после настройки не будет иметь доступа в интернет вообще и SSH на них если и настраивается, но работает только в локальной сети для экстренных случаев.


  1. irbis_al
    22.11.2021 23:01
    +1

    С готовой Linux системы где есть всё всё всё,-Подготовьте образ утилитой dd if=/dev/sda of=/mnt/linux_whereFullmycomponents.img

    Потом на чистый комп ...грузимся с диска и dd if=/mnt/linux_whereFullmycomponents.img of=/dev/sda

    Это(набрать команду) сделает даже эникейщик...Пьете кофе ...возвращаетесь ..система полностью готова к использованию.(возможно потребуется набрать dracut -fv (если совсем железо чуждое))


    1. sherbinko
      22.11.2021 23:39
      +2

      Да вот ещё ..., а кто будет загрузчик настраивать? Сетевые устройства тоже будут другие.

      Скажите честно, что вы ни разу так не делали ...


      1. irbis_al
        23.11.2021 09:58

        Не понимаю про какой загрузчик вы говорите...в случае того метода..загрузочная запись MBR GPT UEFI всё попадает..сетевые устройства настроить всё же прозе чем...посмотрите ,что у меня в образе(200gb файл)

        Centos7 x64 Субд Oracle 11.2.01(ещё тот квест её поднять) Всё для Pos систем.Драйвера сканеров весов фискальнх регистраторов.Прога для работы с егаисом програ для работы с меркурием сервисы для алкогольных деклараций..Linux окружение для работы с Рутокеном .Окружение для работы с Терминалами сбора данных Программный фискальный регистратор(Это для Украины),Сама ИС.Докер с образом IBM DB2 Express. edition.Desctop окружение Desktop Viber telegram Chrome Mozila.Virtual box c образом винды для росийского егаиса. Всякие утилиты для диагностики...ВЫ это полмесяца вручную ставить будите и не представляю скрипты чтоб это все установить махом.Всё это не используется еа рабочем месте в один момент..но образ единый и подстаивается уже под рабочее место.А так пока всё это час ставится...я занимаюсь своими делами.Linux если знает оборудование в ядре (естественно оно ставится на разное железо) ,то поднимается...можно запустить в режиме rescue и доставить драйвера


        1. sherbinko
          23.11.2021 10:08

          Grub загрузчик. Там часто идёт привязка к UUID дисков + могут понадобиться дополнительные драйвера для загрузки ядра.

          А скриптами нельзя чтоли всё установить? Я свой арч со всем окружением одним скриптом в виртуалке ставлю


          1. irbis_al
            23.11.2021 11:17

            Нет проблем с grub . Когда Вы дублируете dd байт в байт диски UUID(Это ж не аппаратная состовляющая) также дублируется и становится такой же на всех разделах соотвтственно первоначальной системе.Вы получаете полностью копию первоначального диска(При этом размер второго может быть больше). в ядре втором могли быть проблемы если два диска копии поставить на систему(У них одинаковый UUID)...но уже с 3 kernel оно разруливалось(Приоритет монтирования был диска с которго грузится ОС).

            Я делаю это с 2007 года и уже наверное более 1000 копий было сделано(мной и командой)...Этот способ реально удобен...а вот когда уже этот "монстр" поднялся...уже скриптами настроечными можно его кастомизировать.(мы так и делам)...А для виртуалки тоже есть соотвтествующий образ...


    1. Almighty_Goose
      24.11.2021 12:25

      Тоже лет десять назад делал так бэкапы разделов.

      Только я еще предварительно все пустое место файловой системы заполнял нулями, а потом гзипил файлы разделов. Восстановление, соответственно через "zcat img.gz > /dev/sda". Тогда простота метода без необходимости ставить коммерческие программы а-ля "акронис труъ имэйдж" поражала мощью linux.

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


  1. psydvl
    22.11.2021 23:57
    +3

    Рекомендую попробовать Nix или Guix. Можно даже десктопом. Всё что нужно настраивается в файлике конфигурации, развертывается одной командой и обеспечивает воспроизводимость сборки.

    Вот пример конфига для NixOS https://nixos.wiki/wiki/NixOS#Declarative_Configuration

    Вот пример конфига для GuixOS https://guix.gnu.org/en/manual/en/html_node/Using-the-Configuration-System.html#Using-the-Configuration-System


    1. darkee
      23.11.2021 19:08

      nixos идеален для такой работы


  1. saboteur_kiev
    23.11.2021 02:26
    +1

    Все, что вы делаете - лучше сделать через ansible, вдобавок будет гораздо более совместимо с разными дистрибутивами и разными пакетными менеджерами.

    Кстати, по вашему велосипеду. Непонятно, зачем вы делаете server.sh и desktop.sh именно контатенацией других скриптов. Гораздо красивее было бы запаковать все ваши инсталл скрипты в поддиректорию, а в server.sh и desktop.sh просто прописать вызов внешних скриптов через source или обычные запуски внешних процессов
    файл server.sh

    library/install-wireshark.sh
    library/install-tmux.sh
    library/install-docker.sh

    Или даже создать из них список и обрабатывать в цикле, с проверкой статуса каждого и суммарным итогом.

    В результате, просто заглянув в server.sh будет видно список действий


    1. dodo101000101 Автор
      23.11.2021 19:24

       Непонятно, зачем вы делаете server.sh и desktop.sh именно контатенацией других скриптов.

      Целью было генерировать отдельный единый файл скрипта. И распространять его примерно так же как делает это https://www.anaconda.com в виде Аnaconda3.sh

      Никаких SSH и оркестрации, только bash и хардкор.

      Гораздо красивее было бы запаковать все ваши инсталл скрипты в поддиректорию, а в server.sh и desktop.sh просто прописать вызов внешних скриптов через source или обычные запуски внешних процессов
      файл server.sh

      Во-первых, мы для красоты создаём поддиректорию scripts:

      Создадим папку scripts и все скрипты будем хранить там

      Если посмотреть мой итоговый проект, то там предусмотрена команда run для запуска скриптов или готовых сборок через source

      Или даже создать из них список и обрабатывать в цикле, с проверкой статуса каждого и суммарным итогом.

      Это мы тут и сделали:

      # Цикл по всем скриптам в папке
      for i in $SCRIPTS_DIRECTORY/*.sh; do
          [ -e "$i" ] || continue
          script_name=$(basename "$i")
      
          _checklist+="$script_name $script_name off "
      done

      В самом же проекте добавлено легирование, проверка статуса выполнения скрипта, обработка ошибок и многое другое.


  1. shimarulin
    23.11.2021 11:07

    Согласен с предыдущими комментаторами по поводу Ansible. Есть уже готовые проекты для этих целей, которые можно взять за основу, например https://github.com/pigmonkey/spark и https://github.com/raphiz/my-arch-setup. У меня есть https://github.com/shimarulin/workstation для арча и https://github.com/shimarulin/workstation-config для убунты (последний давно не обновлялся, но там можно посмотреть настройку Gnome 3, на арче я гном не использовал)


  1. WoozyMasta
    25.11.2021 13:26
    +1

    а также покажу магию bash

    Магии я не увидел, только небезопасный сценарий.

    Рекомендую, вам, еще годик попрактиковаться, желательно с серверами за которые вы несете большую ответственность, где за любой простой вам прилетит нагоняй. А потом попробуйте еще раз пересмотреть эту статью и написать новую, проведя работу над ошибками.