#!/usr/bin/env bash # # Упорядочивание папок игр в common/ (HDD 4 TB): GameName/GameName/ (файлы) + appmanifest в GameName/, # затем перенос всех папок GameName из common/ на уровень выше (HDD 4 TB). Итог: HDD 4 TB/GameName/GameName/, HDD 4 TB/GameName/appmanifest_*.acf, common/ пустая. # AppId берётся по имени папки из steam_library_with_sizes.json (резолвер resolve_steam_appid.py). # Ручные сопоставления (CM3, Darksiders 3, DOOMEternal и др.) — в resolve_steam_appid.py MANUAL_MAP. # # Запуск в CT 101. Корневая директория: /mnt/nextcloud-extra/common/ (PARENT = /mnt/nextcloud-extra = HDD 4 TB). # # Подготовка: скопировать в контейнер скрипты и JSON (в одну папку, например /opt/scripts/): # pct push 101 homelab/scripts/reorganize-games-common.sh /opt/scripts/ # pct push 101 homelab/scripts/resolve_steam_appid.py /opt/scripts/ # pct push 101 homelab/scripts/steam_library_with_sizes.json /opt/scripts/ # # Dry-run (ничего не меняет, только вывод; в конце — папки без совпадений): # ssh root@192.168.1.150 'pct exec 101 -- bash /opt/scripts/reorganize-games-common.sh /mnt/nextcloud-extra/common 1 /opt/scripts/resolve_steam_appid.py /opt/scripts/steam_library_with_sizes.json' # # Реальное выполнение (второй аргумент 0): # ssh root@192.168.1.150 'pct exec 101 -- bash /opt/scripts/reorganize-games-common.sh /mnt/nextcloud-extra/common 0 /opt/scripts/resolve_steam_appid.py /opt/scripts/steam_library_with_sizes.json' # set -euo pipefail ROOT="${1:?Usage: $0 [DRY_RUN=1] [RESOLVER_SCRIPT] [LIBRARY_JSON]}" DRY_RUN="${2:-1}" RESOLVER_SCRIPT="${3:-}" LIBRARY_JSON="${4:-}" [[ -z "$RESOLVER_SCRIPT" ]] && [[ -f ./resolve_steam_appid.py ]] && RESOLVER_SCRIPT="./resolve_steam_appid.py" [[ -n "$RESOLVER_SCRIPT" ]] && [[ -z "$LIBRARY_JSON" ]] && LIBRARY_JSON="$(dirname "$RESOLVER_SCRIPT")/steam_library_with_sizes.json" [[ -z "$RESOLVER_SCRIPT" ]] && { echo "ERROR: resolve_steam_appid.py not found. Pass path as 3rd arg or run from script dir." exit 1 } [[ ! -f "$RESOLVER_SCRIPT" ]] && { echo "ERROR: resolver script not found: $RESOLVER_SCRIPT" exit 1 } [[ -z "$LIBRARY_JSON" ]] || [[ ! -f "$LIBRARY_JSON" ]] && { echo "ERROR: steam_library_with_sizes.json not found. Pass path as 4th arg or put next to resolver." exit 1 } echo "=== ROOT: $ROOT | DRY_RUN: $DRY_RUN | Resolver: $RESOLVER_SCRIPT | Library: $LIBRARY_JSON ===" echo "" if [[ ! -d "$ROOT" ]]; then echo "ERROR: not a directory: $ROOT" exit 1 fi PARENT_DIR="$(dirname "$ROOT")" echo "Папки после обработки будут перенесены: $ROOT -> $PARENT_DIR (уровень выше)" echo "" MISSING_APPID=() MISSING_MANIFEST=() RESOLVED=() PROCESSED_DIRS=() while IFS= read -r -d '' dir; do base=$(basename "$dir") [[ "$base" == "lost+found" ]] && continue appid="" appid=$(python3 "$RESOLVER_SCRIPT" "$LIBRARY_JSON" "$base" 2>/dev/null || true) if [[ -z "$appid" ]]; then MISSING_APPID+=("$base") echo "[$base] appId не найден по имени — пропуск" echo "" continue fi manifest="$ROOT/appmanifest_${appid}.acf" # Манифест может лежать в корне common/ или на уровень выше (steamapps/) if [[ ! -f "$manifest" ]]; then manifest_parent="$(dirname "$ROOT")/appmanifest_${appid}.acf" if [[ -f "$manifest_parent" ]]; then manifest="$manifest_parent" else MISSING_MANIFEST+=("$base -> appmanifest_${appid}.acf") fi fi inner="$dir/$base" if [[ -d "$inner" ]]; then echo "[$base] (уже есть вложенная $base/) appId=$appid" for item in "$dir"/*; do [[ -e "$item" ]] || continue iname=$(basename "$item") [[ "$iname" == "$base" ]] && 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 dest_manifest="$dir/appmanifest_${appid}.acf" if [[ "$manifest" != "$dest_manifest" ]]; then if [[ $DRY_RUN -eq 1 ]]; then echo " [DRY-RUN] cp/mv '$manifest' -> '$dest_manifest'" else cp "$manifest" "$dest_manifest" 2>/dev/null || mv "$manifest" "$dest_manifest" echo " appmanifest_${appid}.acf -> $dir/" fi fi fi else echo "[$base] (создать $base/$base/ и перенести файлы) appId=$appid" 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" == 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 dest_manifest="$dir/appmanifest_${appid}.acf" if [[ "$manifest" != "$dest_manifest" ]]; then if [[ $DRY_RUN -eq 1 ]]; then echo " [DRY-RUN] cp/mv '$manifest' -> '$dest_manifest'" else cp "$manifest" "$dest_manifest" 2>/dev/null || mv "$manifest" "$dest_manifest" echo " appmanifest_${appid}.acf -> $dir/" fi fi fi fi RESOLVED+=("$base -> $appid") PROCESSED_DIRS+=("$base") echo "" done < <(find "$ROOT" -maxdepth 1 -type d ! -path "$ROOT" -print0 | sort -z) echo "=== Перенос папок из common/ на уровень выше (HDD 4 TB) ===" for base in "${PROCESSED_DIRS[@]}"; do src="$ROOT/$base" dst="$PARENT_DIR/$base" if [[ $DRY_RUN -eq 1 ]]; then echo " [DRY-RUN] mv '$src' -> '$PARENT_DIR/'" else if mv "$src" "$PARENT_DIR/" 2>/dev/null; then echo " mv $base -> $PARENT_DIR/" else # Цель уже существует (например, дубликат игры) — сливаем содержимое и удаляем источник if [[ -d "$dst" ]]; then inner="$src/$base" if [[ -d "$inner" ]]; then mkdir -p "$dst/$base" for item in "$inner"/*; do [[ -e "$item" ]] && mv "$item" "$dst/$base/"; done rmdir "$inner" 2>/dev/null || true fi for f in "$src"/appmanifest_*.acf; do [[ -f "$f" ]] && cp -n "$f" "$dst/" 2>/dev/null || true; done rm -rf "$src" echo " merge $base -> $PARENT_DIR/ (target existed)" else echo " ERROR: could not move $base" fi fi fi done echo "" echo "=== Папки, для которых не найден appId ===" for x in "${MISSING_APPID[@]}"; do echo " $x"; done echo "" echo "=== Итог: игры в $PARENT_DIR/GameName/GameName/, манифесты в $PARENT_DIR/GameName/, common/ пустая ===" echo "" echo "=== Нет файла appmanifest (игра найдена по имени) ===" for x in "${MISSING_MANIFEST[@]}"; do echo " $x"; done echo "" echo "=== Сопоставления имя -> appId ===" for x in "${RESOLVED[@]}"; do echo " $x"; done echo "" echo "=== Конец (DRY_RUN=$DRY_RUN) ==="