629 lines
No EOL
19 KiB
Bash
629 lines
No EOL
19 KiB
Bash
#!/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 |