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

629 lines
No EOL
19 KiB
Bash
Raw Permalink 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
# PITOOLSv3 — DevOps для бабушек и дедушек! V3
# Автор: [ТЫ] + брат-АИ
# Фикс от 17.12.2024: пути создаются правильно!
# ==========================================================
# 🎯 ОСОБЕННОСТИ V3:
# 1. Множественные файлы в одной строке FL:
# 2. Вложенные пути в DR: (config/subconfig/nested)
# 3. История изменений с комментариями
# 4. Не пугает бабушек ошибками
# 5. ВСЁ ПРОСТО И РАБОТАЕТ!
# ------------------------- pinstall --------------------------
# pinstall [-h|--here] [-u|--user] [-s|--script] [-d|--directory] FILE...
# Создаёт файлы/каталоги с умными правами.
pinstall() {
local mode=644 owner="$USER" group create_dir=0 verbose=1
# Получаем группу
group="$(id -gn "$USER" 2>/dev/null || id -gn 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 || echo "$USER")"
shift
;;
-s|--script)
mode=755
shift
;;
-d|--directory)
create_dir=1
shift
;;
-q|--quiet)
verbose=0
shift
;;
--)
shift
break
;;
-*)
echo "pinstall: неверный флаг: $1" >&2
return 1
;;
*)
break
;;
esac
done
[ $# -eq 0 ] && {
echo "Использование: pinstall [-h|-u|-s|-d|-q] ФАЙЛ..." >&2
return 1
}
local created=0
for item in "$@"; do
# Поддерживаем несколько файлов через пробел
local items
IFS=' ' read -ra items <<< "$item"
for f in "${items[@]}"; do
[ -z "$f" ] && continue
local eff_mode="$mode"
# Авто-права для скриптов
case "$(basename "$f")" in
*.sh|*.py|*.pl|*.rb|*.go|*.js|Makefile|makefile|*.mk)
eff_mode=755
;;
esac
# Убираем / в конце для проверки
local clean_path="${f%/}"
# Существует?
if [ -e "$clean_path" ]; then
[ "$verbose" -eq 1 ] && {
if [ -d "$clean_path" ]; then
echo " 📂 Уже есть: $clean_path" >&2
else
echo " 📄 Уже есть: $clean_path" >&2
fi
}
# Обновляем права если нужно
chmod "$eff_mode" "$clean_path" 2>/dev/null
chown "$owner:$group" "$clean_path" 2>/dev/null
continue
fi
# Определяем тип
local is_dir="$create_dir"
[ "$is_dir" -eq 0 ] && [[ "$f" == */ ]] && is_dir=1
if [ "$is_dir" -eq 1 ]; then
# СОЗДАЁМ ДИРЕКТОРИЮ
if mkdir -p "$clean_path" 2>/dev/null; then
chmod "$eff_mode" "$clean_path" 2>/dev/null
chown "$owner:$group" "$clean_path" 2>/dev/null
[ "$verbose" -eq 1 ] && echo " 📂 Создана: $clean_path" >&2
created=$((created + 1))
else
[ "$verbose" -eq 1 ] && echo " ❌ Ошибка: $clean_path" >&2
fi
else
# СОЗДАЁМ ФАЙЛ
# Сначала родительские директории
local parent_dir="$(dirname "$f")"
if [ "$parent_dir" != "." ] && [ ! -d "$parent_dir" ]; then
mkdir -p "$parent_dir" 2>/dev/null
fi
if touch "$f" 2>/dev/null; then
chmod "$eff_mode" "$f" 2>/dev/null
chown "$owner:$group" "$f" 2>/dev/null
[ "$verbose" -eq 1 ] && {
case "$(basename "$f")" in
*.sh|*.py|*.pl|*.rb|*.go|*.js|Makefile|makefile|*.mk)
echo " 🔥 755 → $f" >&2
;;
*)
echo " 📄 Создан: $f" >&2
;;
esac
}
created=$((created + 1))
else
[ "$verbose" -eq 1 ] && echo " ❌ Ошибка: $f" >&2
fi
fi
done
done
[ "$verbose" -eq 1 ] && [ "$created" -gt 0 ] && echo "✅ Создано: $created" >&2
return 0
}
# ------------------------ piproject --------------------------
# piproject SPEC.prj
# ФИКС: Все пути создаются относительно корня правильно!
piproject() {
local spec="$1" root="" curdir="" line="" line_num=0
[ -z "$spec" ] && {
echo "Использование: piproject ФАЙЛ.prj" >&2
return 1
}
[ ! -f "$spec" ] && {
echo "Не найден: $spec" >&2
return 1
}
echo "🌳 Читаем: $spec"
echo "══════════════════════════════════════"
# Проверяем формат
if ! grep -q "^PRJ:" "$spec" 2>/dev/null; then
echo "⚠️ Нет PRJ: в файле (но продолжим)" >&2
fi
while IFS= read -r line || [ -n "$line" ]; do
line_num=$((line_num + 1))
# Чистим строку
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
# Комментарий или пустая строка
if [[ -z "$line" ]] || [[ "$line" == \#* ]]; then
[[ "$line" == \#* ]] && [ ${#line} -lt 50 ] && echo " 💬 $line" >&2
continue
fi
# ОБРАБОТКА СТРОК
if [[ "$line" == PRJ:* ]]; then
# НОВЫЙ ПРОЕКТ
root="${line#PRJ:}"
curdir="$root" # Текущая директория = корень
echo "📁 ПРОЕКТ: $root/"
echo "──────────────────────────────────"
if pinstall -d -q "$root/"; then
echo " 📂 Корень создан"
fi
elif [[ "$line" == DR:* ]]; then
# ДИРЕКТОРИЯ
[ -z "$root" ] && {
echo "❌ Строка $line_num: сначала нужен PRJ:" >&2
continue
}
local dir_part="${line#DR:}"
# Разбиваем несколько директорий через пробел
local dir_items
IFS=' ' read -ra dir_items <<< "$dir_part"
for dir_spec in "${dir_items[@]}"; do
[ -z "$dir_spec" ] && continue
# Добавляем / если нет
[[ "$dir_spec" != */ ]] && dir_spec="$dir_spec/"
# ВСЕГДА создаём от корня!
local full_dir="$root/$dir_spec"
# Убираем возможные двойные слеши
while [[ "$full_dir" == *//* ]]; do
full_dir="${full_dir//\/\//\/}"
done
# Создаём директорию
if pinstall -d -q "${full_dir%/}"; then
echo " 📂 Папка: $dir_spec"
fi
# Запоминаем последнюю созданную папку для файлов без пути
curdir="${full_dir%/}"
done
elif [[ "$line" == FL:* ]]; then
# ФАЙЛЫ
[ -z "$root" ] && {
echo "❌ Строка $line_num: сначала нужен PRJ:" >&2
continue
}
local file_part="${line#FL:}"
if [ -z "$file_part" ]; then
echo "⚠️ Строка $line_num: пустой FL:" >&2
continue
fi
# Разбиваем несколько файлов
local file_items
IFS=' ' read -ra file_items <<< "$file_part"
for file_spec in "${file_items[@]}"; do
[ -z "$file_spec" ] && continue
local full_path=""
# Определяем путь
if [[ "$file_spec" == */* ]]; then
# Файл с путём - относительно КОРНЯ
full_path="$root/$file_spec"
# Создаём родительские директории если нужно
local parent_dir="${full_path%/*}"
if [ ! -d "$parent_dir" ]; then
pinstall -d -q "$parent_dir/" >/dev/null 2>&1
fi
else
# Просто имя файла - в ТЕКУЩУЮ директорию
[ -z "$curdir" ] && curdir="$root"
full_path="$curdir/$file_spec"
fi
# Убираем двойные слеши
while [[ "$full_path" == *//* ]]; do
full_path="${full_path//\/\//\/}"
done
# Создаём файл
if pinstall "$full_path"; then
echo " 📄 Файл: $file_spec"
fi
done
else
# Неизвестная директива
echo "⚠️ Строка $line_num: непонятно - '$line'" >&2
fi
done < "$spec"
echo "══════════════════════════════════════"
if [ -n "$root" ]; then
echo "✅ Готово! Проект: $root/"
echo "📁 Структура:"
find "$root" -type d 2>/dev/null | head -20
else
echo " Ничего не создано"
fi
return 0
}
# ------------------------ deploypi ---------------------------
# deploypi ХОСТ ПОЛЬЗОВАТЕЛЬ SPEC.prj
deploypi() {
local host="$1" user="$2" spec="$3"
[ -z "$host" ] || [ -z "$user" ] || [ -z "$spec" ] && {
echo "Использование: deploypi ХОСТ ПОЛЬЗОВАТЕЛЬ ФАЙЛ.prj" >&2
return 1
}
[ ! -f "$spec" ] && {
echo "Не найден: $spec" >&2
return 1
}
echo "🚀 ДЕПЛОЙ: $spec$user@$host"
echo "──────────────────────────────────"
# Имя файла
local spec_name="project_$(date +%s).prj"
# Проверяем SSH
echo "🔍 Проверяем связь..."
if ! ssh -o ConnectTimeout=5 "$user@$host" "exit 0" 2>/dev/null; then
echo "Не могу подключиться к $user@$host" >&2
return 1
fi
# Загружаем спецификацию
echo "📤 Загружаем..."
if ! scp "$spec" "$user@$host:~/$spec_name" 2>/dev/null; then
echo "❌ Ошибка загрузки" >&2
return 1
fi
# Определяем путь к себе
local self_path="$0"
[ "$self_path" = "bash" ] && self_path="pitoolsv3.sh"
# Загружаем pitoolsv3 если нет
echo "⚙️ Настраиваем..."
ssh "$user@$host" "
# Скачиваем или создаём pitoolsv3
if [ ! -f ~/pitoolsv3.sh ]; then
if command -v curl >/dev/null 2>&1; then
curl -s 'https://raw.githubusercontent.com/пример/pitools/main/pitoolsv3.sh' -o ~/pitoolsv3.sh
elif command -v wget >/dev/null 2>&1; then
wget -q -O ~/pitoolsv3.sh 'https://raw.githubusercontent.com/пример/pitools/main/pitoolsv3.sh'
else
# Создаём минимальную версию
cat > ~/pitoolsv3.sh <<'MINI'
#!/bin/bash
# Минимальный PITOOLSv3
pinstall() {
for f in \"\$@\"; do
if [[ \"\$f\" == */ ]]; then
mkdir -p \"\${f%/}\" && echo \" 📂 \${f%/}\"
else
touch \"\$f\" && echo \" 📄 \$f\"
fi
done
}
piproject() {
local spec=\"\$1\" root=\"\" curdir=\"\"
while IFS= read -r line; do
case \"\$line\" in
PRJ:*) root=\"\${line#PRJ:}\"; mkdir -p \"\$root\" ;;
DR:*) mkdir -p \"\$root/\${line#DR:}\" ;;
FL:*) touch \"\$root/\${line#FL:}\" ;;
esac
done < \"\$spec\"
echo \"✅ Готово: \$root/\"
}
MINI
chmod +x ~/pitoolsv3.sh
fi
fi
# Запускаем
. ~/pitoolsv3.sh
cd ~
piproject \"$spec_name\"
# Убираем за собой
rm -f \"$spec_name\"
" 2>&1
local result=$?
if [ $result -eq 0 ]; then
echo "$host: деплой успешен!"
else
echo "$host: ошибка деплоя" >&2
fi
return $result
}
# ---------------------- multideploypi ------------------------
# multideploypi INFRA.prj
multideploypi() {
local spec="$1"
[ -z "$spec" ] && {
echo "Использование: multideploypi ФАЙЛ.prj" >&2
return 1
}
[ ! -f "$spec" ] && {
echo "Не найден: $spec" >&2
return 1
}
echo "🌍 МУЛЬТИДЕПЛОЙ: $spec"
echo "══════════════════════════════════════"
local current_hosts="" block_content="" in_block=0 block_num=0
while IFS= read -r line || [ -n "$line" ]; do
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
if [[ "$line" == MLT:* ]]; then
# Новый блок
if [ -n "$current_hosts" ] && [ -n "$block_content" ]; then
block_num=$((block_num + 1))
echo "🔧 Блок $block_num$(echo "$current_hosts" | tr ',' ' ' | wc -w) хост(ов)"
# Деплоим
local hosts
IFS=',' read -ra hosts <<< "$current_hosts"
for host in "${hosts[@]}"; do
host="${host#"${host%%[![:space:]]*}"}"
host="${host%"${host##*[![:space:]]}"}"
[ -z "$host" ] && continue
echo " 🚀 $host..."
( deploypi "$host" "$USER" <(echo "$block_content") >/dev/null 2>&1 && \
echo " ✅ Готово" || \
echo " ❌ Ошибка" ) &
done
wait
fi
current_hosts="${line#MLT:}"
block_content=""
elif [[ "$line" == PRJ:* || "$line" == DR:* || "$line" == FL:* || \
"$line" == "" || "$line" == \#* ]]; then
# Добавляем в блок
block_content="$block_content"$'\n'"$line"
else
echo "⚠️ Пропускаем: $line" >&2
fi
done < "$spec"
# Последний блок
if [ -n "$current_hosts" ] && [ -n "$block_content" ]; then
block_num=$((block_num + 1))
echo "🔧 Блок $block_num$(echo "$current_hosts" | tr ',' ' ' | wc -w) хост(ов)"
local hosts
IFS=',' read -ra hosts <<< "$current_hosts"
for host in "${hosts[@]}"; do
host="${host#"${host%%[![:space:]]*}"}"
host="${host%"${host##*[![:space:]]}"}"
[ -z "$host" ] && continue
echo " 🚀 $host..."
( deploypi "$host" "$USER" <(echo "$block_content") >/dev/null 2>&1 && \
echo " ✅ Готово" || \
echo " ❌ Ошибка" ) &
done
wait
fi
echo "══════════════════════════════════════"
echo "✅ Мультидеплой завершён!"
return 0
}
# ---------------------- pifill & pifetch ---------------------
pifill() {
local url="$1"
[ -z "$url" ] && {
echo "Использование: pifill ssh://user@host/path < файл" >&2
echo " pifill ssh://user@host/path -c 'текст'" >&2
return 1
}
[[ "$url" != ssh://* ]] && {
echo "❌ Только ssh:// протокол" >&2
return 1
}
local rest="${url#ssh://}"
local user_host="${rest%%/*}"
local path="${rest#*/}"
local user="${user_host%%@*}"
local host="${user_host#*@}"
echo "📤 Заливаем в $user@$host:$path"
if [ "$2" = "-c" ]; then
ssh "$user@$host" "mkdir -p '$(dirname "$path")' && cat > '$path'" <<< "$3"
else
ssh "$user@$host" "mkdir -p '$(dirname "$path")' && cat > '$path'"
fi
echo "✅ Готово!"
}
pifetch() {
local url="$1"
[ -z "$url" ] && {
echo "Использование: pifetch ssh://user@host/path > файл" >&2
return 1
}
[[ "$url" != ssh://* ]] && {
echo "❌ Только ssh:// протокол" >&2
return 1
}
local rest="${url#ssh://}"
local user_host="${rest%%/*}"
local path="${rest#*/}"
local user="${user_host%%@*}"
local host="${user_host#*@}"
if [ "$2" = "-o" ]; then
scp "$user@$host:$path" "$3"
echo "✅ Скачан в $3"
else
ssh "$user@$host" "cat '$path'"
fi
}
# -------------------------- help -----------------------------
pitools_help() {
cat <<'EOF'
🔥 PITOOLSv3 — DevOps для всех!
📚 КОМАНДЫ:
pinstall [-опции] ФАЙЛ... Создать файлы/папки
-h Права как у папки -u 644
-s Скрипт (755) -d Папка
-q Тихий режим
piproject файл.prj Создать проект
Формат .prj:
PRJ:имя # Корень
DR:папка/внутри/ # Папка (можно несколько)
FL:файл1 файл2 # Файлы (можно несколько)
# Комментарии
deploypi хост юзер файл.prj Деплой на сервер
multideploypi файл.prj Деплой на несколько серверов
MLT:хост1,хост2 # Список серверов
pifill ssh://юзер@хост/путь Залить файл
pifetch ssh://юзер@хост/путь Скачать файл
📝 ПРИМЕР:
PRJ:мой_сайт
DR:www/ config/ backup/
FL:index.html style.css
DR:scripts/
FL:start.sh stop.sh
✨ Особенности V3:
• Несколько файлов/папок в одной строке
• Вложенные пути работают правильно
Не ругается если уже существует
• Бабушка поймёт!
EOF
}
# ---------------------- автозапуск ---------------------------
if [ "$0" = "${BASH_SOURCE[0]:-$0}" ]; then
case "${1:-}" in
pinstall|piproject|deploypi|multideploypi|pifill|pifetch|pitools_help)
"$@"
;;
"test")
# Тестовый прогон
cat > test_simple.prj <<'TEST'
PRJ:test_project
DR:app/config db/logs/
FL:settings.yaml database.conf
DR:scripts/
FL:start.sh stop.sh
# Комментарий
TEST
echo "🧪 Тестовый прогон..."
piproject test_simple.prj
echo ""
echo "📁 Результат:"
find test_project -type f 2>/dev/null || echo "Не создано"
;;
"")
echo "🔥 PITOOLSv3 загружен!"
echo "💡 Используй 'pitools_help' для справки"
echo ""
echo "🚀 Быстрый старт:"
echo " . ./pitoolsv3.sh"
echo " echo 'PRJ:мой_проект' > проект.prj"
echo " piproject проект.prj"
;;
*)
echo "❌ Неизвестная команда: $1" >&2
echo "💡 Используй 'pitools_help'" >&2
exit 1
;;
esac
fi