pitools/pitoolsv2.sh
2025-12-23 02:45:20 +03:00

663 lines
24 KiB
Bash
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# PITOOLSV2 — простой SSH-оркестратор:
# pinstall — создать файл/директорию с правами
# piproject — собрать проект по .prj
# deploypi — деплой одного .prj на один хост
# multideploypi — деплой нескольких блоков MLT+PRJ/DR/FL из одного .prj
# ------------------------- pinstall --------------------------
# pinstall [-h|--here] [-u|--user] [-s|--script] FILE...
# Создаёт файлы/каталоги с умными правами и владельцем.
pinstall() {
local mode=644 owner="$USER" group create_dir=0
# Получаем группу текущего пользователя
group="$(id -gn "$USER" 2>/dev/null || id -gn 2>/dev/null || whoami 2>/dev/null || echo "$USER")"
# Парсим флаги
while [ $# -gt 0 ] && [ "${1#-}" != "$1" ]; do
case "$1" in
-h|--here)
# Наследуем права и владельца от текущей директории
if [ -d . ]; then
mode="$(stat -c '%a' . 2>/dev/null || echo 644)"
owner="$(stat -c '%U' . 2>/dev/null || echo "$USER")"
group="$(stat -c '%G' . 2>/dev/null || echo "$group")"
fi
shift
;;
-u|--user)
# Стандартные права для пользовательских файлов
mode=644
owner="$USER"
group="$(id -gn "$USER" 2>/dev/null || id -gn 2>/dev/null || whoami 2>/dev/null || echo "$USER")"
shift
;;
-s|--script)
# Принудительно исполняемые права
mode=755
shift
;;
-d|--directory)
# Создавать директорию
create_dir=1
shift
;;
--)
# Конец флагов
shift
break
;;
-*)
echo "Использование: pinstall [-h|--here] [-u|--user] [-s|--script] [-d|--directory] ФАЙЛ..." >&2
return 1
;;
*)
# Не флаг - выходим из цикла
break
;;
esac
done
# Проверка: хотя бы один файл должен быть указан
[ $# -eq 0 ] && {
echo "Использование: pinstall [-h|--here] [-u|--user] [-s|--script] [-d|--directory] ФАЙЛ..." >&2
return 1
}
# Счётчик созданных объектов
local created=0
# Обрабатываем все переданные файлы
for f in "$@"; do
local eff_mode="$mode"
# Автоматически делаем скрипты исполняемыми
case "$(basename "$f")" in
*.sh|*.py|*.pl|*.rb|*.go|*.js|Makefile|makefile|*.mk)
eff_mode=755
;;
esac
# Убираем возможный / в конце для проверки существования
local check_path="$f"
[[ "$check_path" == */ ]] && check_path="${check_path%/}"
# Проверяем, существует ли уже
if [ -e "$check_path" ]; then
# Существует — обновляем права если нужно
if [ -d "$check_path" ]; then
# Это директория
if [ "$eff_mode" != "644" ] && [ "$eff_mode" != "755" ]; then
chmod "$eff_mode" "$check_path" 2>/dev/null && \
echo " 🔧 Обновлены права директории: $f" >&2
fi
else
# Это файл
if [ "$eff_mode" != "644" ]; then
chmod "$eff_mode" "$check_path" 2>/dev/null && \
echo " 🔧 Обновлены права файла: $f" >&2
fi
fi
continue # Уже существует, пропускаем создание
fi
# Определяем, создавать директорию или файл
local is_dir="$create_dir"
[ "$is_dir" -eq 0 ] && [[ "$f" == */ ]] && is_dir=1
if [ "$is_dir" -eq 1 ]; then
# Создаём директорию
if install -d -m "$eff_mode" -o "$owner" -g "$group" "$f" 2>/dev/null; then
echo " 📂 Создана директория: ${f%/}" >&2
created=$((created + 1))
else
echo " ⚠️ Не удалось создать директорию: $f" >&2
fi
else
# Создаём файл
if install -D -m "$eff_mode" -o "$owner" -g "$group" \
--backup=numbered /dev/null "$f" 2>/dev/null; then
# Проверяем, был ли авто-режим 755
case "$(basename "$f")" in
*.sh|*.py|*.pl|*.rb|*.go|*.js|Makefile|makefile|*.mk)
echo " 🔥 AUTO 755 → $f" >&2
;;
*)
echo " 📄 Создан файл: $f" >&2
;;
esac
created=$((created + 1))
else
echo " ⚠️ Не удалось создать файл: $f" >&2
fi
fi
done
[ "$created" -gt 0 ] && echo "✅ Создано объектов: $created" >&2
return 0
}
# ------------------------ piproject --------------------------
# piproject SPEC.prj
# Читает PRJ/DR/FL и создаёт структуру проекта локально.
piproject() {
local spec="$1" root="" curdir="" line="" created_projects=0
# Проверка аргументов
[ -z "$spec" ] && {
echo "Использование: piproject ФАЙЛ.prj" >&2
echo "Пример: piproject мойпроект.prj" >&2
return 1
}
[ ! -f "$spec" ] && {
echo "❌ Файл спецификации не найден: $spec" >&2
return 1
}
echo "🌳 Читаем спецификацию: $spec"
# Проверяем, есть ли хоть одна PRJ: строка
if ! grep -q "^PRJ:" "$spec" 2>/dev/null; then
echo "❌ В файле нет PRJ: определения" >&2
return 1
fi
# Читаем файл построчно
while IFS= read -r line || [ -n "$line" ]; do
# Убираем пробелы в начале и конце
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
# Пропускаем пустые строки и комментарии
case "$line" in
""|"#"*)
continue
;;
PRJ:*)
# Начало нового проекта - определяем корневую директорию
root="${line#PRJ:}"
root="${root#"${root%%[![:space:]]*}"}"
root="${root%"${root##*[![:space:]]}"}"
# Проверяем безопасность пути
if [[ "$root" == *".."* ]] || [[ "$root" == /* ]]; then
echo "⚠️ Предупреждение: путь '$root' может быть небезопасен" >&2
fi
# Создаём корневую директорию
echo "📁 Создаём проект: $root/"
if pinstall -d "$root/"; then
curdir="$root"
created_projects=$((created_projects + 1))
else
echo "Не удалось создать корень проекта: $root" >&2
return 1
fi
;;
DR:*)
# Создание поддиректории
[ -z "$root" ] && {
echo "❌ Сначала определите PRJ: в файле $spec" >&2
return 1
}
local dir_path="${line#DR:}"
dir_path="${dir_path#"${dir_path%%[![:space:]]*}"}"
dir_path="${dir_path%"${dir_path##*[![:space:]]}"}"
# Защита от небезопасных путей
if [[ "$dir_path" == *".."* ]] || [[ "$dir_path" == /* ]]; then
echo "⚠️ Предупреждение: путь '$dir_path' может быть небезопасен" >&2
fi
# Создаём поддиректорию
if [ -n "$dir_path" ]; then
curdir="$root/$dir_path"
if pinstall -d "$curdir/"; then
echo " 📂 Директория: $dir_path"
else
echo " ⚠️ Не удалось создать директорию: $dir_path" >&2
fi
fi
;;
FL:*)
# Создание файла
[ -z "$root" ] && {
echo "❌ Сначала определите PRJ: в файле $spec" >&2
return 1
}
local file_path="${line#FL:}"
file_path="${file_path#"${file_path%%[![:space:]]*}"}"
file_path="${file_path%"${file_path##*[![:space:]]}"}"
if [ -z "$file_path" ]; then
echo "⚠️ Пустое имя файла в FL:, пропускаем" >&2
continue
fi
# Определяем полный путь к файлу
local full_path=""
if [[ "$file_path" == */* ]]; then
# В пути есть слеш - создаём относительно корня
full_path="$root/$file_path"
# Создаём родительские директории если нужно
local parent_dir="${full_path%/*}"
if [ -n "$parent_dir" ] && [ ! -d "$parent_dir" ]; then
pinstall -d "$parent_dir/" >/dev/null 2>&1
fi
else
# Простое имя файла - создаём в текущей директории
[ -z "$curdir" ] && curdir="$root"
full_path="$curdir/$file_path"
fi
# Создаём файл
if pinstall "$full_path"; then
echo " 📄 Файл: $file_path"
else
echo " ⚠️ Не удалось создать файл: $file_path" >&2
fi
;;
*)
# Неизвестная строка
echo "⚠️ Неизвестная строка в $spec (пропускаем): $line" >&2
;;
esac
done < "$spec"
if [ "$created_projects" -gt 0 ]; then
echo "✅ Готово! Создано проектов: $created_projects"
else
echo " Ничего не создано (всё уже существует?)"
fi
return 0
}
# ------------------------ deploypi ---------------------------
# deploypi ХОСТ ПОЛЬЗОВАТЕЛЬ SPEC.prj
# Копирует pitoolsv2 + spec на хост и запускает piproject.
deploypi() {
local host="$1" user="$2" spec="$3"
# Проверка аргументов
[ -z "$host" ] || [ -z "$user" ] || [ -z "$spec" ] && {
echo "Использование: deploypi ХОСТ ПОЛЬЗОВАТЕЛЬ ФАЙЛ.prj" >&2
echo "Пример: deploypi server.ru vasya мойпроект.prj" >&2
return 1
}
[ ! -f "$spec" ] && {
echo "❌ Файл спецификации не найден: $spec" >&2
return 1
}
echo "🚀 DEPLOYpi: $spec$user@$host"
# Определяем путь к текущему скрипту
local self=""
if [ -f "$0" ]; then
self="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
elif [ -n "${BASH_SOURCE[0]}" ]; then
self="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
else
self="./pitoolsv2.sh"
fi
# Безопасное получение имени спецификации
local spec_basename="$(basename "$spec")"
spec_basename="${spec_basename//[!a-zA-Zа-яА-ЯёЁ0-9._-]/_}"
echo "🔍 Проверяем соединение с $host..."
# Проверяем доступность SSH
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
"$user@$host" "exit 0" 2>/dev/null; then
echo "Не могу подключиться к $user@$host через SSH" >&2
echo " Проверьте: ssh $user@$host" >&2
return 1
fi
echo "📤 Загружаем инструменты и спецификацию..."
# 1. Копируем сам скрипт на удалённый хост
if ! scp -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \
"$self" "$user@$host:~/.pitoolsv2.sh" 2>/dev/null; then
echo "Не удалось загрузить pitoolsv2 на $host" >&2
return 1
fi
# 2. Копируем спецификацию проекта
if ! scp -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \
"$spec" "$user@$host:~/$spec_basename" 2>/dev/null; then
echo "Не удалось загрузить спецификацию на $host" >&2
return 1
fi
echo "⚡ Выполняем удалённо..."
# 3. Запускаем создание проекта на удалённом хосте
ssh -o ConnectTimeout=30 -o StrictHostKeyChecking=accept-new "$user@$host" "
echo '🔍 Проверяем загруженные файлы...'
if [ ! -f ~/.pitoolsv2.sh ]; then
echo '❌ Ошибка: pitoolsv2 не найден' >&2
exit 1
fi
if [ ! -f ~/'$spec_basename' ]; then
echo '❌ Ошибка: спецификация не найдена' >&2
exit 1
fi
echo '⚙️ Настраиваем окружение...'
# Добавляем загрузку pitools в .bashrc если ещё нет
if ! grep -q 'pitoolsv2' ~/.bashrc 2>/dev/null; then
echo '[ -f ~/.pitoolsv2.sh ] && . ~/.pitoolsv2.sh # PITOOLSv2' >> ~/.bashrc
echo ' ✅ Добавили в .bashrc'
fi
# Загружаем скрипт и выполняем
. ~/.pitoolsv2.sh 2>/dev/null || {
echo '❌ Не удалось загрузить pitoolsv2' >&2
exit 1
}
cd ~ || { echo '❌ Не могу перейти в домашнюю директорию' >&2; exit 1; }
echo '🛠️ Запускаем создание проекта...'
if piproject '$spec_basename'; then
echo '✅ Удалённый проект успешно создан!'
echo ''
echo 'Созданные файлы:'
find . -type f -name '*$spec_basename*' -o -path './*' | head -10
exit 0
else
echo '❌ Ошибка при создании проекта' >&2
exit 1
fi
" 2>&1
local result=$?
if [ $result -eq 0 ]; then
echo "$host: проект '$spec_basename' успешно развёрнут!"
return 0
else
echo "❌ Ошибка выполнения на $host" >&2
return $result
fi
}
# ---------------------- multideploypi ------------------------
# multideploypi INFRA.prj
# Читает файл, разбивает на блоки:
# MLT:host1,host2
# PRJ/DR/FL...
# Каждый блок деплоит на свои хосты через deploypi.
multideploypi() {
local spec="$1"
# Проверка аргументов
[ -z "$spec" ] && {
echo "Использование: multideploypi ФАЙЛ_МУЛЬТИ.prj" >&2
echo "Пример: multideploypi инфраструктура.prj" >&2
return 1
}
[ ! -f "$spec" ] && {
echo "❌ Файл спецификации не найден: $spec" >&2
return 1
}
echo "🌍 MULTIDEPLOYpi: обрабатываем $spec"
local line current_hosts="" tmpdir block_spec prj_seen=0
local block_counter=0
local pids=""
local temp_files=""
# Создаём временную директорию
tmpdir="$(mktemp -d -t pitools-XXXXXX 2>/dev/null)" || {
echo "❌ Не удалось создать временную директорию" >&2
return 1
}
# Функция для очистки временных файлов
cleanup() {
for pid in $pids; do
kill "$pid" 2>/dev/null || true
done
rm -rf "$tmpdir" 2>/dev/null || true
}
# Устанавливаем обработчик прерывания
trap cleanup EXIT INT TERM
# Завершение текущего блока
finish_block() {
local hosts="$1" specfile="$2"
[ -z "$hosts" ] && return 0
[ ! -s "$specfile" ] && return 0
[ "$prj_seen" -eq 0 ] && {
echo "⚠️ Блок без PRJ: директивы, пропускаем" >&2
return 0
}
# Разбиваем хосты по запятым
local hosts_arr
IFS=',' read -r -a hosts_arr <<< "$hosts"
local valid_hosts=0
for h in "${hosts_arr[@]}"; do
h="${h#"${h%%[![:space:]]*}"}"
h="${h%"${h##*[![:space:]]}"}"
[ -z "$h" ] && continue
valid_hosts=$((valid_hosts + 1))
done
if [ "$valid_hosts" -eq 0 ]; then
echo "⚠️ В блоке нет валидных хостов, пропускаем" >&2
return 0
fi
echo "🔧 Блок $block_counter$valid_hosts хост(ов): ${hosts_arr[*]}"
# Запускаем деплой на каждый хост
for h in "${hosts_arr[@]}"; do
h="${h#"${h%%[![:space:]]*}"}"
h="${h%"${h##*[![:space:]]}"}"
[ -z "$h" ] && continue
# Создаём копию спецификации для каждого хоста
local host_spec=""
host_spec="$(mktemp "$tmpdir/spec_${h}_XXXXXX.prj" 2>/dev/null)" || continue
temp_files="$temp_files $host_spec"
cp "$specfile" "$host_spec"
# Запускаем деплой в фоне
(
echo " 🚀 Начинаем деплой на $h..."
if deploypi "$h" "$USER" "$host_spec" > "$tmpdir/deploy_${h}.log" 2>&1; then
echo "$h: деплой успешен"
else
echo "$h: деплой не удался (лог: $tmpdir/deploy_${h}.log)" >&2
fi
) &
pids="$pids $!"
done
}
# Инициализируем первый блок
block_counter=1
block_spec="$(mktemp "$tmpdir/block_${block_counter}_XXXXXX.prj" 2>/dev/null)" || {
echo "❌ Не удалось создать временный файл" >&2
return 1
}
temp_files="$temp_files $block_spec"
echo "📖 Парсим спецификацию..."
# Читаем файл построчно
while IFS= read -r line || [ -n "$line" ]; do
# Убираем лишние пробелы
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
case "$line" in
""|"#"*)
# Комментарии и пустые строки сохраняем для читаемости
[ -n "$block_spec" ] && echo "$line" >> "$block_spec"
;;
MLT:*)
# Новый блок хостов
echo "📦 Нашли MLT блок: ${line#MLT:}"
# Завершаем предыдущий блок
finish_block "$current_hosts" "$block_spec"
# Начинаем новый блок
current_hosts="${line#MLT:}"
block_counter=$((block_counter + 1))
block_spec="$(mktemp "$tmpdir/block_${block_counter}_XXXXXX.prj" 2>/dev/null)" || {
echo "Не удалось создать временный файл для блока $block_counter" >&2
return 1
}
temp_files="$temp_files $block_spec"
prj_seen=0
# Сохраняем MLT строку в новый блок
echo "$line" >> "$block_spec"
;;
PRJ:*)
# Начало описания проекта
echo "$line" >> "$block_spec"
prj_seen=1
;;
DR:*|FL:*)
# Описание директорий и файлов
[ -z "$block_spec" ] && {
echo "❌ DR: или FL: без MLT: блока" >&2
return 1
}
echo "$line" >> "$block_spec"
;;
*)
# Неизвестные строки пропускаем с предупреждением
echo "⚠️ Неизвестная директива, пропускаем: $line" >&2
;;
esac
done < "$spec"
# Завершаем последний блок
finish_block "$current_hosts" "$block_spec"
# Ждём завершения всех фоновых процессов
if [ -n "$pids" ]; then
echo "⏳ Ждём завершения всех деплоев..."
local failed=0
for pid in $pids; do
if wait "$pid"; then
echo "✓ Процесс $pid завершился успешно"
else
echo "✗ Процесс $pid завершился с ошибкой" >&2
failed=1
fi
done
if [ "$failed" -eq 1 ]; then
echo "⚠️ Некоторые деплои не удались. Логи в: $tmpdir"
echo " Посмотреть: ls -la $tmpdir/*.log"
# Не удаляем временную директорию при ошибках
trap - EXIT INT TERM
return 1
else
echo "✅ Все деплои успешно завершены!"
return 0
fi
else
echo " Нет хостов для деплоя"
return 0
fi
}
# -------------------------- help -----------------------------
# Короткая подсказка по доступным командам.
pitools_help() {
cat <<EOF
PITOOLSv2 — Простой SSH-оркестратор для всех
===========================================
Доступные команды:
pinstall [-h|-u|-s|-d] ФАЙЛ... Создать файлы/директории
-h, --here Унаследовать права от текущей папки
-u, --user Стандартные права пользователя (644)
-s, --script Исполняемые права (755)
-d, --directory Создать директорию (не файл)
.sh, .py, .js файлы автоматически получают 755!
piproject ФАЙЛ.prj Создать структуру проекта
Формат:
PRJ:имя_проекта # Корень проекта
DR:подпапка # Создать папку
FL:файл # Создать файл
FL:путь/к/файлу # Файл с путём
# Комментарии разрешены
deploypi ХОСТ ПОЛЬЗОВАТЕЛЬ ФАЙЛ.prj Деплой на один хост
Пример: deploypi server.ru vasya проект.prj
multideploypi ФАЙЛ_МУЛЬТИ.prj Деплой на несколько хостов
Формат:
MLT:хост1,хост2,хост3 # Хосты для этого блока
PRJ:проект # Описание проекта
DR:... # Папки
FL:... # Файлы
pitools_help Показать эту справку
Примеры:
pinstall script.sh # Создаёт script.sh с правами 755
piproject мойпроект.prj # Создать локальный проект
deploypi server1 vasya app.prj # Деплой на один сервер
multideploypi infra.prj # Деплой на несколько серверов
Советы:
- Если файл/папка уже существует, права будут обновлены
- Ошибки не останавливают выполнение (только предупреждения)
- Все пути безопасно обрабатываются
EOF
}
# Автоматическое выполнение команды если скрипт вызван напрямую
if [ "$0" = "${BASH_SOURCE[0]:-$0}" ]; then
case "${1:-}" in
pinstall|piproject|deploypi|multideploypi|pitools_help)
"$@"
;;
"")
echo "PITOOLSv2 загружен успешно!"
echo "Используйте 'pitools_help' для справки."
echo ""
echo "Быстрый старт:"
echo " . ./pitoolsv2.sh # Загрузить в текущую сессию"
echo " piproject пример.prj # Создать проект"
;;
*)
echo "❌ Неизвестная команда: $1" >&2
echo "Используйте: pinstall, piproject, deploypi, multideploypi, pitools_help" >&2
exit 1
;;
esac
fi