Серия «ИТ»

33

Разворачиваем сеть на RHEL8-based хостах (копия с Хабра)

Разворачиваем сеть на RHEL8-based хостах (копия с Хабра) Linux, IT, Программирование, Perl, Командная оболочка bash, Длиннопост

1. Предисловие

Развертывание ИТ-инфраструктуры с нуля — задача интересная и трудозатратная. Особенно, когда речь не о постепенном развитии (как это часто случается при поступательно-линейном росте бизнеса и, соответственно, его потребностей), а о куда более сжатых сроках, например, при открытии филиала или обособленного подразделения (другой вариант — необходимость в короткие сроки развернуть инфраструктуру для тестирования), где важную роль играет организация сети.


Конечно, первоначальная установка и настройка — это всегда полевая работа: монтаж СКС, сетевого оборудования и серверов; конфигурирование DHCP и организация удалённого доступа; иногда — заведение VLAN-ов.


2. Описание задачи

Итак, представим, что первичная настройка сети проведена: монтаж СКС осуществлён, DHCP выдал всем устройствам IP-адреса, удалённый доступ до сети филиала (или обособленного подразделения) в наличии, на одни сервера заведены 2 (или более) физических соединения для организации BOND-ов, на другие поданы транковые соединения с несколькими VLAN-ами и т.д.

Список IP-адресов серверов сведён в таблицу и доступ по SSH в наличии. Если размер списка невелик (например, до 3-5 единиц), то ручная настройка покажется неплохим вариантом, хоть и отнимет какое-то время.


А если список содержит, например, 10 (или более) хостов, на части из которых необходима уникальная сетевая конфигурация (разделение trunk-соединения на vlan-ы, виртуальные bridge-ы, etc)? Тут уж временные затраты существенно возрастают.


Но нет необходимости тратить время на рутину, если готовое решение есть и ожидает вас ниже (со ссылкой на guthub-репозиторий в конце публикации).


3. Стек технологий

1) Ansible. Весьма популярен (хотя кому-то ближе Puppet, Chef или Salt). Применяется для доставки контента/команд на целевые хост-системы.

2) Командная оболочка Bash. Присутствует во всех linux-системах. В рамках решения используется как для создания оболочки над Ansible, так и для отложенного выполнения операций (если конкретнее — отката сетевых настроек по таймеру) на целевых хостах.

3) Perl 5. Отличный инструмент в умелых руках. С помощью этого ЯП написан функционал генерации ifcfg-файлов на основе текстового конфига (т.е. Perl задействован только на ansible-хосте).

4) Network-scripts. Хоть и не включён в минимальный вариант установки ОС семейства RHEL8, но является проверенным средством настройки сетевой подсистемы.


4. Концепция приложения

Приложение «conf_int_ipv4_via_network_scripts» (ansible-приложение) представляет собой набор perl/bash-скриптов и файлов конфигурации (inventory-file, etc). Хотя и является составной частью репозитория «ansible_helpers», но имеет автономный характер.


5. Описание приложения

5.1. Структура директорий1. Корневой раздел (условно обозначим «../»). Содержит все основные скрипты поддиректории.

2. Директория с дополнительными конфигами («../additional_configs»). Содержит дополнительные файлы конфигурации, с помощью которых возможно задать содержание resolv.conf (указать NS-сервера), таймаут отката конфигурации и опцию удаления неиспользуемых ifcfg-файлов на стороне целевых хостов.

3. Директория с playbook-ами («../playbooks), которая в свою очередь имеет свои подразделы:

3.1) dyn_ifcfg_playbooks. Тут динамически генерируеются уникальные для каждого хоста плейбуки, ifcfg-файлы и настройки dns (resolv.conf).

3.2) ifcfg_backup_from_remote. Содержит резервные копии сетевых настроек с привязкой к дате («history»), текущую конфигурацию до изменения («now») и полную информацию о сетевых интерфейсах («network_data»).

3.3) ifcfg_tmplt. Как следует из названия, хранит шаблоны ifcfg-конфигураций, на основе которых формируются уникальные для каждого inventory-хоста настройки сети.

3.4) scripts_for_local. Хранит скрипты для исполнения на ansible-хосте. В данном случае подразумевается скрипт преобразования сырых данных в файлы c информацией о сетевых интерфейсах (размещаются в директории «../ifcfg_backup_from_remote/network_data»).

3.5) scripts_for_remote. Скрипты для исполнения на удалённых хостах. В данном случае он один — rollback_ifcfg_changes.sh (скрипт отката к предыдущим настройкам сетевой подсистемы).

6) tasks. Используемые в плейбуках файлы задач.

4. «../run_history». Логи запуска/исполнения сценариев приложения.


5.2. Файлы конфигурации

1. «../conf_network_scripts_hosts» — inventory-файл приложения.

2. «../config» – основной файл конфигурации приложения. Также имеется файл «config_examples» с примерами настроек. Для получения имён сетевых интерфейсов и соответствующих им MAC-адресов, требуемых при заполнении файла «config», необходимо запустить скрипт «just_run_ifcfg_backup.sh» и ознакомиться с файлом «../playbooks/ifcfg_backup_from_remote/network_data/inv_hosts_interfaces_info.txt».

3. «../additional_configs/config_del_not_configured_ifcfg» - задаёт действие относительно тех ifcfg-файлов, которые не сконфигурированы в «../config». Если в «config_del_not_configured_ifcfg» вписать адрес хоста, то ansible-приложение удалит на remote-хосте все ifcfg-файлы, выключит соответствующие интерфейсы («ifdown») и удалит линки (через «ip link delete»), но только те, которые относятся к bond/bridge и vlan-интерфейсам (например, eth0.100). Настройка будет полезна в случая, когда, например, требуется переименовать какое-либо bond/bridge-соединение.

4. «../additional_configs/config_temporary_apply_ifcfg» - тут возможно задать таймаут (как общий, так и для различных inventory-хостов индивидуально) отката к предыдущим настройкам.

5. «../additional_configs/dns_settings». Предоставляет возможность выставить настройки dns индивидуально для каждого inventory-хоста.


5.3. Скрипты и их назначение

1. «install_network_scripts_and_configure_network.sh» - производит бэкап ifcfg-файлов, устанавливает «network-scripts» (если функционал не установлен ранее), проверяет статус сервиса «network.service» (если не запущен, то стартует), исполняет скрипт «generate_dynamic_ifcfg.pl» и применяет изменения сетевых настроек (заданных в файле «config») на удалённых хостах (если, конечно, настройки корректны).

2. «just_install_network_scripts.sh» устанавливает «network-scripts» (если функционал не установлен ранее).

3. «just_run_ifcfg_backup.sh» - производит бэкап ifcfg-файлов.

4. «check_network_scripts_serv_is_started.sh» - проверяет статус сервиса «network.service» (если не запущен, то стартует).

5. «check_ifcfg_without_apply.sh» - проверяет корректность сетевых настроек (файл «config»).

6. «apply_temporary_ifcfg.sh» - временно применяет сетевые настройки. Таймаут отката задается в конфиге «../additional_configs/config_temporary_apply_ifcfg».

7. «apply_immediately_ifcfg.sh» - немедленно применяет новые сетевые настройки, если, конечно, они изменились с момента предыдущего запуска (в т.ч. если были внесены какие-либо изменения вручную на удалённых хостах).


5.4. Описание основного файла конфигурации (UPD 2022-12-19)

Разворачиваем сеть на RHEL8-based хостах (копия с Хабра) Linux, IT, Программирование, Perl, Командная оболочка bash, Длиннопост

Каждая строка в файле представляет собой набор параметров, разделённых символами "пробел/табуляция":
1) INV_HOST. Должен соответствовать одному из хостов inentory-файла "conf_network_scripts_hosts".
2) CONF_ID. Уникальный идентификатор сетевого интерфейса (или набора сетевых интерфейсов) в рамках каждого inventory-хоста.
3) CONF_TYPE. Определяет тип конфигурации для конкретных сетевых интерфейсов. Возможные значения:
3.1) just_interface (обычный интерфейс);
3.2) interface-vlan (интерфейс, поднимаемый в рамках транкового соединения, из которого каждый vlan возможно выделить по vlan-идентификатору);
3.3) virt_bridge (виртуальный мост);
3.4) just_bridge (обычный мост);
3.5) bridge-vlan (мост поверх vlan-интерфейса);
3.6) just_bond (конфигурация агрегированного канала, состоящего из 2-х и более физических соединений);
3.7) bond-vlan (vlan-итерфейс поверх bond-соединения);
3.8) bond-bridge (соединение типа "мост" поверх bond-соединения);
3.9) bond-bridge-vlan (соединение типа "bridge-vlan" поверх bond-соединения).
Каждому из этих значений соответствует свой набор шаблонов ifcfg-файлов (директория "ifcfg_tmplt").
4) INT_LIST. Список сетевых интерфейсов (разделённых запятой), используемых в рамках конкретного типа конфигурации (CONF_TYPE).
5) HWADDR_LIST. Список MAC-адресов, соответствующих сетевым интерфейсам из INT_LIST. Для CONF_TYPE=virt_bridge параметру необходимо присвоить значение "no".
6) VLAN_ID. Идентификатор VLAN. В случае, если речь не про vlan, то должен иметь значение "no".
7) BOND_NAME. Используется только для CONF_TYPE, равных "just_bond, bond-vlan, bond-bridge, bond-bridge-vlan". В остальных случаях необходимо выставлять значение "no".
8) BRIDGE_NAME. Используется только для CONF_TYPE, равных "virt_bridge, just_bridge, bridge-vlan, bond-bridge-vlan". В остальных случаях необходимо выставлять значение "no".
9) IPv4_ADDR_OPTS. Опции IPv4. Возможные значения: строка вида "ipv4,gateway,netmask" (для использования статической адресации) или "dhcp" (для получения ip-адреса и прочих параметров через DHCP).
10) BOND_OPTS. Настройки агрегирования для bond-соединения. Возможные значения: "def" ("mode=4,xmit_hash_policy=2,lacp_rate=1,miimon=100") или иное сочетание параметров, разделённых запятой.
11) DEFROUTE. Флаг "маршрут по умолчанию". Для конкретного inventory-хоста должен быть только один.


Чтение и обработка файла "config" происходит посредством Perl-скрипта "generate_dynamic_ifcfg.pl", который осуществляет ряд проверок конфигурации (в т.ч. и используя информацию, полученную посредством «just_run_ifcfg_backup.sh»), инициирует приостановку исполнения сценариев на удалённых хостах, если данные в "config" некорректны, формирует необходимые наборы ifcfg-файлов для каждого inventory-хоста, а также создаёт персональные для каждого хоста ansible-playbook-и (в директории "dyn_ifcfg_playbooks"), если сгенерированные файлы интерфейсов отличаются от текущих.


6. Постскриптум

Ссылка на репозиторий


В README-файле перечислены как уже готовые приложения, так и те, статус которых варьируется от «в процессе» до «есть в планах».

Спокойного кодинга всем причастным!

=====================
Статья на Хабр

Показать полностью 1
29

Ansible. Network-scripts. RHEL8. Сбор информации о MAC-адресах

Ansible. Network-scripts. RHEL8. Сбор информации о MAC-адресах Linux, Разработка, Программирование, IT, Perl, Командная оболочка bash

Нихао!


Очередная доработка хелпера "conf_int_ipv4_via_network_scripts" подъехала ( предыдущая публикация - Ansible. Network-scripts. RHEL8. Временное применение сетевой конфигурации ).

Как и обещал, реализовал сбор MAC-адресов (а также сведений о соседствах/neighbours) с удалённых хостов, на основе чего добавлена следующая функциональность:
1) дополнительные проверки на соответствие interface_name+hwaddr из "config" реальным интерфейсам удалённых хостов. Например, если на хосте А используется MAC-адрес "A1:A2:A3:A4:A5:A6", то попытка прописать такой же MAC-адрес на хосте B приведёт к ошибке и остановке сценария. Или, например, если в файле конфигурации для хоста А прописано, что за интерфейсом eth0 закреплён адрес "B1:B2:B3:B4:B5:B6", но в реальности eth0 = "C1:C2:C3:C4:C5:C6", то скрипт предложит исправить "config";
2) функционал проверки конфигурации без её применения ('check_ifcfg_without_apply.sh');
3) анализ сети (в пределах inventory-файла "conf_network_scripts_hosts") на предмет дублирования MAC-адресов. Если после запуска сценариев (бэкап ifcfg, проверка конфигурации, применение конфигурации) в директории ".../playbooks/ifcfg_backup_from_remote/network_data" был создан файл "WARNINGS.txt", то стоит обратить внимание на его содержание.


Также в директории "network_data" присутствует информация (файл "inv_hosts_interfaces_info.txt") об используемых на том или ином хосте MAC-адресах, что полезно при первоначальном заполнении файла "config" (дабы не собирать вручную информацию). В общем, если config на этапе заполнения, то просто запустите, например, скрипт бэкапа "just_run_ifcfg_backup.sh" и загляните в ".../playbooks/ifcfg_backup_from_remote/network_data".


Ссылка на репозиторий: https://github.com/vladimir-chursin000/ansible_helpers

Показать полностью
13

Ansible. Network-scripts. RHEL8. Временное применение сетевой конфигурации

Ansible. Network-scripts. RHEL8. Временное применение сетевой конфигурации Linux, Разработка, IT, Программирование, Perl, Командная оболочка bash, Open Source

Бонжур!


Доработал недавно (как и обещал в предыдущей публикации - Ansible. Network-scripts. RHEL8) функционал ansible-хелпера "conf_int_ipv4_via_network_scripts". Теперь изменения сетевых настроек возможно применить временно (например, на период тестирования). Для этого достаточно:

1) сконфигурировать целевые интерфейсы посредством правки файла "config";

2) задать время отката настроек на предыдущие через конфиг "additional_configs/config_temporary_apply_ifcfg" (по умолчанию = 10 минут);

3) запустить скрипт "apply_temporary_ifcfg.sh". Выполняет действия, аналогичные "apply_immediately_ifcfg.sh" (т.е. реконфигурирует сеть в соответствии с файлом "config"), но перед рестартом сервиса "network" запускает на удалённом хосте bash-скрипт "rollback_ifcfg_changes.sh", который возвращает сетевые настройки к виду до модификации через временной промежуток, определённый в файле "config_temporary_apply_ifcfg";

4) протестировать сетевые связанности целевых хостов (вероятно, когда-нибудь реализую утилиту на основе стека "ansible + bash + perl");

5) если всё в порядке, то запустить скрипт "apply_immediately_ifcfg.sh", который остановит исполнение сценария "rollback_ifcfg_changes.sh".


Итого, два варианта на выбор пользователя - либо применить новые настройки незамедлительно (just run "apply_immediately_ifcfg.sh"), либо применить их временно (run "apply_temporary_ifcfg.sh") до осуществления тестирования и отмены возврата к предыдущей конфигурации сети хоста (run "apply_immediately_ifcfg.sh").


Конечно, неплохо бы добавить централизованный сбор наименований сетевых интерфейсов и соответствующих им MAC-адресов, дабы в автоматическом режиме (при попытке применить конфигурацию) сверять с тем, что пользователь напишет в конфиге (и прекращать выполнение софта, если, например, для интерфейса задан некорректный MAC), но эту фичу, думаю, реализую позже.


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

===

Ссылка на репозиторий: https://github.com/vladimir-chursin000/ansible_helpers

Показать полностью
26

Ansible. Network-scripts. RHEL8

Ansible. Network-scripts. RHEL8 Разработка, Программирование, IT, Perl, Linux, Shell, Командная оболочка bash

Приветствую!


Практически завершил работу над ansible-хелпером "conf_int_ipv4_via_network_scripts" (репозиторий "ansible_helpers"), но "практически" означает, что совокупность скриптов и сценариев уже можно использовать в работе.


Краткая инструкция.

1. Клонируем репозиторий - git clone "https://github.com/vladimir-chursin000/ansible_helpers".

2. Переходим в директорию ".../ansible_helpers/conf_int_ipv4_via_network_scripts/rhel8_based".

3. Заполняем inventory-файл "conf_network_scripts_hosts" (не забываем про ssh-ключи на удалённых хостах).

4. Заполняем основной файл конфигурации "config" (такой вот незамысловатый нейминг). Каждая линия - настройка конкретного сетевого интерфейса на конкретном хосте. Присутствует файл со всеми возможными примерами конфигурации - "config_examples".

5. Правим дополнительные файлы конфигурации, расположенные в директории "additional_configs":

а) dns_settings (настройки DNS). По умолчанию содержит только доменные сервера Google (8.8.8.8, 8.8.4.4) в качестве общих NS (nameservers/сервера имён) для всех хостов из inventory-файла. Также присутствует возможность для отдельных хостов выставить уникальные сервера имён;

б) config_del_not_configured_ifcfg. Файл конфигурации, определяющий действия в отношении сетевых интерфейсов, отсутствующих в основном файле конфигурации (который "config"). Для inventory-хостов, вписанных в этот конфиг, действует правило - отключать сетевые интерфейсы (и удалять соответствующие ifcfg-файлы), не сконфигурированные в файле "config".

6. Запускаем скрипт "install_network_scripts_and_configure_network.sh" (если "network-scripts" не установлен) или "apply_immediately_ifcfg.sh".


Что произойдёт после запуска (если опустить часть с установкой "network-scripts"), если кратко:

1. Бэкап ifcfg-файлов с сохранением в директорию (и поддиректории) ".../playbooks/ifcfg_backup_from_remote": "history" - для хранения, "now" - для дальнейшего сравнения с ifcfg-файлами, генерация которых (на основе config-а) произойдёт на следующем этапе.

2. Запуск perl-скрипта "generate_dynamic_ifcfg.pl", которые создаёт:

а) ifcfg-файлы для каждого inventory-хоста (на основе основного конфига). Размещаются в ".../playbooks/dyn_ifcfg_playbooks/dyn_ifcfg";

б) файлы resolv-conf (на основе "dns_settings"). Директория ".../playbooks/dyn_ifcfg_playbooks/dyn_resolv_conf";

в) task-файл для каждого inventory-хоста, содержащий ansible-инструкции для реконфигурации сети (директория ".../playbooks/dyn_ifcfg_playbooks"). Важный момент - если сгенерированные ifcfg-файлы не отличаются от текущих (скопированных на первом этапе исполнения), то task-файл будет содержать только ansible-код для взаимодействия с "resolv.conf".

3. Исполнение сформированных task-файлов.


P.S. №1. Осталась самая малость - реализовать механизм временного применения сетевых настроек (о чём писал ранее).


P.S. №2. Надеюсь, кому-то результат моих изысканий поможет сэкономить время.

Показать полностью

Скрипт генерации ifcfg-файлов

Скрипт генерации ifcfg-файлов Linux, Программирование, IT, Разработка, Perl, Open Source

Приветствую!

В рамках создания ansible-helper-а "conf_int_ipv4_via_network_scripts" дописал скрипт ("generate_dynamic_ifcfg.pl") генерации сетевых интерфейсов для network-scripts по заранее заданным конфигурациям, который вполне допустимо использовать отдельно от хелпера (файл конфигурации с примерами = config_examples).

Чтобы использовать perl-программу вне контекста репозитория, потребуется:

1. Залогиниться на хост/ВМ с ОС Linux и установленным Perl5.

2. Скопировать скрипт "generate_dynamic_ifcfg.pl", например, в директорию "/opt/generate_dynamic_ifcfg".

3. Скопировать файл конфигурации (config), файл с примерами конфигурации (config_examples) и папку "ifcfg_tmplt" (содержит шаблоны конфигураций интерфейсов) в директорию со скриптом.

4. Откорректировать параметры скрипта из блока "STATIC VARS", т.е. задать директорию с шаблонами ifcfg-файлов и директорию для размещения сконфигурированных сетевых интерфейсов (ifcfg-файлов с конкретными значениями: имя интерфейса, MAC-адрес и проч.).

5. Отредактировать файл config, задав свои настройки сетевых интерфейсов (обычный интерфейс, bond, bridge, etc).

6. Запустить скрипт.

7. PROFIT!


P.S. №1. Увы, сам хелпер пока далёк от завершения. Предстоит ещё многое сделать, в т.ч. и, например, реализовать механизм временного применения сетевых настроек, например, на 3-4 минуты с последующим откатом к предыдущей конфигурации (наверняка для кого-то сия опция окажется весьма полезной).

===

P.S. №2. Ох и объёмная же статья (на основе репозитория ansible_helpers) для Хабра получится.

===
Ссылка на helper: https://github.com/vladimir-chursin000/ansible_helpers/tree/...
Показать полностью

Ответ на пост «Autodesk в РФ - всё... ломаем PowerMill»1

Готовый список из комментаторов для тов. майора?
===
+ Вражий гугл тут подсказал вот чего: https://csprut.ru/common_news/importozam/
===
Повторю вопрос @Xianren . Если не создать, то может стоит поискать хотя бы аналоги, а не заниматься пиратством?

Все эти санкции когда-нибудь закончатся, а вот нарушение так и останется нарушением. Более того, если речь про продукты Autodesk (в зависимости от кол-ва крякнутых копий), то можно легко нарваться на уголовную статью: https://www.elcode.ru/service/news/daydjest-novostey-zakonod...

Также стоит упомянуть про сроки давности уголовного преступления.
"Лицо освобождается от уголовной ответственности, если со дня совершения преступления небольшой тяжести истекло 2 года, средней тяжести - 6 лет, тяжкого преступления - 10 лет, особо тяжкого - 15 лет. По каждому преступлению сроки исчисляются самостоятельно."

Ответ на пост «Autodesk в РФ - всё... ломаем PowerMill» Windows, Приложение, Autodesk, Длиннопост, Ответ на пост

Ansible без излишеств. NFS-клиент

Ansible без излишеств. NFS-клиент Разработка, Программирование, IT, Системное администрирование, Open Source

Доброго вечера (утра/дня/ночи)!

В рамках репозитория "ansible_helpers" добавлено новое приложение "install_nfs_client" ( https://github.com/vladimir-chursin000/ansible_helpers ), позволяющее централизованно управлять nfs-клиентами на множестве ОС вида "rhel8_based" (также произведены косметические изменения на ранее созданных "приложениях").

Ключевая особенность - единый файл конфигурации, на основе которого динамически формируются yml-файлы с заданиями (tasks) на монтирование nfs-ресурсов. Т.е. достаточно  заполнить inventory-файл "nfs_client_hosts" (вписав ip-адреса целевых хостов), внести изменения в файл "dyn_mount_config" и запустить скрипт "install_nfs_client.sh" (или "just_apply_new_mounts.sh", если необходимый софт для доступа к сетевым файловым системам уже установлен), чтобы получить результат.

===

P.S. На очереди "приложение" для развёртывания набора сетевых сервисов в комплексе (samba + vsftpd + webdav + etc).

Показать полностью
19

Ansible без излишеств. NFS

Ansible без излишеств. NFS Программирование, IT, Разработка, Linux

В рамках репозитория "ansible_helpers" ( https://github.com/vladimir-chursin000/ansible_helpers ) добавил новое приложение-помощник (helper), реализующее процесс установки и конфигурирования nfs-сервера на ОС вида "rhel8_based".

Репозиторий содержит краткие справочные материалы и пояснения. Всё, что необходимо, - это:

1) вписать ip-адреса целевых хостов в файл "nfs_server_hosts";

2) убедиться, что на эти хостах в файл "/root/.ssh/authorized_keys" (знаю, что лучшее решение - выдать права посредством SUDOERS определённому пользователю на запуск команд от root-а через sudo, но пока так) добавлены публичные ключи пользователя, из-под которого планируется запуск ansible-сценария;

3) сконфигурировать экспортируемые вовне директории, отредактировав файл "nfs_exports_config";

4) если есть необходимость, то отредактировать файлы конфигурации "nfs_server.conf" и "nfsmount_server.conf";

5) запустить исполнение сценария посредством скрипта "install_nfs_server.sh";

6) разрешить целевые сервисы на фаерволе (когда-нибудь для этих целей создам отдельный helper).

Если впоследствии потребуется либо изменить конфигурацию сервиса, либо поменять свойства экспортируемых директорий (добавить, удалить и проч.), то для этого предусмотрены скрипты - "check_nfs_serv_conf_is_changed.sh" (при изменении файлов конфигурации) и "check_nfs_serv_exports_is_changed.sh" (при изменении файла "/etc/exports").

Также, если возникнет необходимость проверить работоспособность nfs-сервиса (т.е. убедиться, что все необходимые сервисы запущены), в наличии скрипт "check_all_nfs_serv_is_started.sh".

P.S.

1. В планах, когда накопится достаточное кол-во приложений-помощников, написание полноценной статьи для Хабра (и не только) на тему "Минимальная ИТ-инфраструктура для малого бизнеса" (for example).

2. Про Ansible Galaxy в курсе, но хотелось иметь свой набор "велосипедов", который было бы удобно использовать лично мне (и, вероятно, кому-либо ещё).

Показать полностью
Отличная работа, все прочитано!