#!/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