Add CI/CD

This commit is contained in:
peginelab 2025-12-23 02:45:20 +03:00
commit 8ec21b41fa
3 changed files with 1929 additions and 0 deletions

629
pitoolsv3.sh Normal file
View file

@ -0,0 +1,629 @@
#!/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