Files
homelab-docs/homelab/scripts/reorganize-games.sh
2026-02-23 16:47:17 +03:00

233 lines
9.0 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
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.
#!/usr/bin/env bash
#
# Упорядочивание папок игр: GameName/GameName/ (файлы) + appmanifest в GameName/
# Запуск в CT 101. Поддерживает две корневые директории: Игры и nextcloud-extra.
#
# Использование (с хоста Proxmox 192.168.1.150):
#
# Dry-run (только вывод действий, ничего не меняет):
# ssh root@192.168.1.150 'pct exec 101 -- bash -s "/mnt/nextcloud-data/html/data/kerrad/files/Игры" 1' < homelab/scripts/reorganize-games.sh
# ssh root@192.168.1.150 'pct exec 101 -- bash -s /mnt/nextcloud-extra 1' < homelab/scripts/reorganize-games.sh
#
# Реальное выполнение (второй аргумент 0):
# ssh root@192.168.1.150 'pct exec 101 -- bash -s "/mnt/nextcloud-data/html/data/kerrad/files/Игры" 0' < homelab/scripts/reorganize-games.sh
# ssh root@192.168.1.150 'pct exec 101 -- bash -s /mnt/nextcloud-extra 0' < homelab/scripts/reorganize-games.sh
#
# Либо скопировать скрипт в контейнер и запустить там:
# pct exec 101 -- bash /path/to/reorganize-games.sh "/mnt/.../Игры" 1
#
set -euo pipefail
ROOT="${1:?Usage: $0 <ROOT_DIR> [DRY_RUN=1]}"
DRY_RUN="${2:-1}"
README=""
[ -f "$ROOT/readme.md" ] && README="$ROOT/readme.md"
[ -f "$ROOT/Readme.md" ] && README="${README:-$ROOT/Readme.md}"
[ -z "$README" ] && { echo "ERROR: no readme.md or Readme.md in $ROOT"; exit 1; }
# Маппинг: "имя в readme" -> "имя папки на диске" (если отличается)
declare -A NAME_TO_FOLDER
# Игры (kerrad/files/Игры)
NAME_TO_FOLDER["Mortal Kombat X"]="MK10"
NAME_TO_FOLDER["Ведьмак 3"]="The Witcher 3"
NAME_TO_FOLDER["Crusader Kings 3"]="Crusader Kings III"
NAME_TO_FOLDER["Cities: Skylines"]="Cities_Skylines"
NAME_TO_FOLDER["Baldur's gate 3"]="Baldurs Gate 3"
NAME_TO_FOLDER["Anno 1800 (full dlc)"]="Anno 1800"
NAME_TO_FOLDER["Titan Quest (full dlc)"]="Titan Quest Anniversary Edition"
NAME_TO_FOLDER["KCD 2"]="KingdomComeDeliverance2"
NAME_TO_FOLDER["FC 24"]="EA Sports FC 24"
NAME_TO_FOLDER["Katana Zero"]="Katana ZERO"
NAME_TO_FOLDER["L4D2"]="Left 4 Dead 2"
NAME_TO_FOLDER["XCOM2"]="XCOM 2"
NAME_TO_FOLDER["L.A. Noire"]="L.A.Noire"
NAME_TO_FOLDER["ETS 2"]="Euro Truck Simulator 2"
NAME_TO_FOLDER["Phantome Doctrine"]="PhantomDoctrine"
NAME_TO_FOLDER["Черепашки ниндзя: В поисках Сплинтера"]="Teenage Mutant Ninja Turtles Splintered Fate"
NAME_TO_FOLDER["Казаки 3"]="Cossacks 3"
NAME_TO_FOLDER["A Way Out"]="AWayOut"
NAME_TO_FOLDER["Counter Strike 2"]="Counter-Strike Global Offensive"
NAME_TO_FOLDER["Sid Meier's Civilization IV"]="Sid Meier's Civilization VI"
# nextcloud-extra (readme: "Assasins" с одной s, на диске часто "Assassin's")
NAME_TO_FOLDER["Assasins Creed"]="Assassins Creed"
NAME_TO_FOLDER["Bully"]="Bully Scholarship Edition"
NAME_TO_FOLDER["Assasins Creed 2"]="Assassin's Creed 2"
NAME_TO_FOLDER["Assasins Creed III Remastered"]="Assassin's Creed III Remastered"
NAME_TO_FOLDER["Assasins Creed IV Black Flag"]="Assassin's Creed IV Black Flag"
NAME_TO_FOLDER["Assasins Creed Revelations"]="Assassin's Creed Revelations"
NAME_TO_FOLDER["Assasins Creed Syndicate"]="Assassin's Creed Syndicate"
NAME_TO_FOLDER["Assasins Creed Unity"]="Assassin's Creed Unity"
NAME_TO_FOLDER["Assasins Creed Valhalla"]="Assassin's Creed Valhalla"
NAME_TO_FOLDER["Assasins Creed Mirage"]="Assassin's Creed Mirage"
NAME_TO_FOLDER["Assassins Creed Brotherhood"]="Assassins Creed Brotherhood"
NAME_TO_FOLDER["Assassins Creed Odyssey"]="Assassins Creed Odyssey"
NAME_TO_FOLDER["Assassins Creed Origins"]="Assassins Creed Origins"
NAME_TO_FOLDER["Bioshock Remastered"]="BioShock Remastered"
NAME_TO_FOLDER["Bioshock Infinite"]="BioShock Infinite"
NAME_TO_FOLDER["Bioshock 2 Remastered"]="BioShock 2 Remastered"
normalize() {
echo "$1" | tr '[:upper:]' '[:lower:]' | tr -s ' \t' ' ' | sed "s/[[:punct:]]//g" | sed 's/^ *//;s/ *$//'
}
# Парсим readme: строки таблицы | ... | название | appid |
declare -A README_NAME_TO_APPID
while IFS= read -r line; do
[[ "$line" != "|"* ]] && continue
# Убираем первый и последний |, разбиваем по |
rest="${line#|}"
rest="${rest%|}"
rest="${rest# }"
rest="${rest% }"
# Колонки: жанр | название | appmanifest
name=""
appid=""
col=0
while [[ -n "$rest" ]]; do
if [[ "$rest" == *"|"* ]]; then
part="${rest%%|*}"
rest="${rest#*|}"
else
part="$rest"
rest=""
fi
part=$(echo "$part" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [[ $col -eq 1 ]]; then
name="$part"
elif [[ $col -eq 2 ]]; then
appid="${part// /}"
fi
((col++)) || true
done
[[ -z "$name" || -z "$appid" ]] && continue
[[ "$name" == "название игры" ]] && continue
[[ "$name" == "appmanifest" ]] && continue
README_NAME_TO_APPID["$name"]="$appid"
done < "$README"
# По имени из readme находим папку на диске
resolve_folder() {
local readme_name="$1"
if [[ -n "${NAME_TO_FOLDER[$readme_name]:-}" ]]; then
echo "${NAME_TO_FOLDER[$readme_name]}"
return
fi
local norm=$(normalize "$readme_name")
for dir in "$ROOT"/*/; do
[[ -d "$dir" ]] || continue
base=$(basename "$dir")
[[ "$base" == "моды" || "$base" == "Моды" || "$base" == "lost+found" ]] && continue
if [[ $(normalize "$base") == "$norm" ]]; then
echo "$base"
return
fi
done
echo ""
}
# По имени папки находим appid из readme (перебор по всем именам в readme)
resolve_appid() {
local folder_name="$1"
local norm_folder=$(normalize "$folder_name")
for readme_name in "${!README_NAME_TO_APPID[@]}"; do
local f=$(resolve_folder "$readme_name")
if [[ "$f" == "$folder_name" ]]; then
echo "${README_NAME_TO_APPID[$readme_name]}"
return
fi
done
# Прямое совпадение по нормализованному имени папки
for readme_name in "${!README_NAME_TO_APPID[@]}"; do
if [[ $(normalize "$readme_name") == "$norm_folder" ]]; then
echo "${README_NAME_TO_APPID[$readme_name]}"
return
fi
done
echo ""
}
echo "=== ROOT: $ROOT | DRY_RUN: $DRY_RUN ==="
echo ""
# Собираем папки игр (директории, не файлы)
MISSING_APPID=()
MISSING_MANIFEST=()
while IFS= read -r -d '' dir; do
base=$(basename "$dir")
[[ "$base" == "моды" || "$base" == "Моды" || "$base" == "lost+found" ]] && continue
appid=$(resolve_appid "$base")
if [[ -z "$appid" ]]; then
MISSING_APPID+=("$base (нет в readme)")
continue
fi
manifest="$ROOT/appmanifest_${appid}.acf"
if [[ ! -f "$manifest" ]]; then
MISSING_MANIFEST+=("$base -> appmanifest_${appid}.acf")
fi
inner="$dir/$base"
if [[ -d "$inner" ]]; then
# Уже есть вложенная папка с тем же именем
echo "[$base] (есть вложенная $base/)"
for item in "$dir"/*; do
[[ -e "$item" ]] || continue
iname=$(basename "$item")
[[ "$iname" == "$base" ]] && continue
[[ "$iname" == "readme.md" || "$iname" == "Readme.md" ]] && continue
[[ "$iname" == appmanifest_*.acf ]] && continue
if [[ $DRY_RUN -eq 1 ]]; then
echo " [DRY-RUN] mv '$item' -> '$inner/'"
else
echo " mv '$item' -> '$inner/'"
mv "$item" "$inner/"
fi
done
if [[ -f "$manifest" ]]; then
if [[ $DRY_RUN -eq 1 ]]; then
echo " [DRY-RUN] mv '$manifest' -> '$dir/'"
else
mv "$manifest" "$dir/"
echo " mv appmanifest_${appid}.acf -> $dir/"
fi
fi
else
# Нет вложенной папки — создаём GameName/GameName/ и переносим всё
echo "[$base] (создать $base/$base/ и перенести файлы)"
if [[ $DRY_RUN -eq 1 ]]; then
echo " [DRY-RUN] mkdir -p '$inner'"
else
mkdir -p "$inner"
fi
for item in "$dir"/*; do
[[ -e "$item" ]] || continue
iname=$(basename "$item")
[[ "$iname" == "$base" ]] && continue
[[ "$iname" == "readme.md" || "$iname" == "Readme.md" ]] && continue
[[ "$iname" == appmanifest_*.acf ]] && continue
if [[ $DRY_RUN -eq 1 ]]; then
echo " [DRY-RUN] mv '$item' -> '$inner/'"
else
mv "$item" "$inner/"
echo " mv $iname -> $base/"
fi
done
if [[ -f "$manifest" ]]; then
if [[ $DRY_RUN -eq 1 ]]; then
echo " [DRY-RUN] mv '$manifest' -> '$dir/'"
else
mv "$manifest" "$dir/"
echo " mv appmanifest_${appid}.acf -> $dir/"
fi
fi
fi
echo ""
done < <(find "$ROOT" -maxdepth 1 -type d ! -path "$ROOT" -print0 | sort -z)
echo "=== Папки без соответствия в readme ==="
for x in "${MISSING_APPID[@]}"; do echo " $x"; done
echo ""
echo "=== Не найдены appmanifest (игра есть в readme) ==="
for x in "${MISSING_MANIFEST[@]}"; do echo " $x"; done
echo ""
echo "=== Конец (DRY_RUN=$DRY_RUN) ==="