Гайд: Создание mount серверов в панели Pterodactyl для экономии места

Mr_panica

XenForo one 💖
Сообщения
942
Реакции
458
Способ проверен только частично, потому что уже не помню как точно делал, кто будет делать потом напишите если работает корректно.
Делайте на свой страх и риск, но на чистую VPS можно спокойно ставить.
Этот гайд скорее для того что бы понять сам принцип, после его написания я бы и сам бы не захотел ему следовать 😆

Этот способ позволит экономить огромное количество места на вашем VDS.
По стандарту в Pterodactyl при создании нового сервера он качается полностью. Все 20-25 ГБ файлов и сделать итоговый вес около 5-10 МБ (чистый сервер)
1765858823823.png
В основном это звуки и текстуры.
Но ведь они одинаковы у всех серверов и меняются только при обновлении серверов.
В панели Pterodactyl есть способ создавать ссылки на файлы, то есть сервер будет видеть ссылку на файл, пойдёт по пути и будет использовать файл по ссылке.

Называется способ Mount (Using Mounts | Pterodactyl)

Я буду показывать на примере сервера для игры Team Fortress 2.
Имея скромную VDS на 100ГБ с этим способом она без проблем вмещает 8 игровых сервреров + Fast-DL + сайт с форумом + остаётся ещё 25-30ГБ свободного места для обновления сервера.
В этом способе обновляется только главный сервер.

По идеи вам никто не мешает сделать маунты SourceMod или карт, звуков, моделей, если они у вас одинаковые везде.
Возможно можно сделать FastDL (но без сжатия bz2)

Плюсы:
-Освобождение места на VDS.
-Быстрое обновление (обновляется только один сервер, а не все)
-Потенциальная автоматизация.
-Меньше нагрузки на VDS.

Минусы:
-Потенциальные проблемы (с TF2 вроде за +-полгода не было, но всё же)
-При обновлении сервера могут крашатсся (обновление занимает 5-10 мин)
-Несовершенство метода (делал сам так что на свой страх и риск)

*Возможно можно сделать чтобы mount был из контейнера в контейнер, но я не нашёл такого способа, хотя в интернете была информация что так можно сделать.
В данной реализации mount идёт из системы Linux VDS, а не контейнера от Pterodactyl.

Тут будет смешанное руководство через консоль SSH и графического SFTP клиента.
Использовал:

SFTP клиент: WinSCP
SSH клиент: Termius
OS: Ubuntu 24

1. Создание папки для целого сервера.

В директории /var/lib/pterodactyl создайте папку mounts (если её нет)
Тут будет находиться папка с сервером.
Создайте папку с любым названием, там будет полный сервер, у меня называется tf2_full, итоговый путь: /var/lib/pterodactyl/mounts/tf2_full

1765851730859.png


2. Установка SteamCMD (Если нет)
Гайд от ИИ (т.к уже у меня установлена)

Рекомендуемый способ: Ручная установка (работает стабильно)​

  1. Создайте отдельного пользователя для безопасности(не запускайте сервер от root):

    Bash:
    sudo adduser --gecos "" steam
    sudo passwd steam  # Установите пароль
    sudo usermod -aG sudo steam  # Опционально, для удобства


  2. Перейдите в домашнюю директорию пользователя steam:

    su - steam


  3. Создайте директорию для SteamCMD и перейдите в неё:

    Bash:
    mkdir ~/steamcmd
    cd ~/steamcmd


  4. Скачайте и распакуйте SteamCMD:

    Bash:
    wget https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz
    tar -xvzf steamcmd_linux.tar.gz
Запустите SteamCMD для первоначальной инициализации(он сам обновится):

./steamcmd.sh

Второй способ: Через пакетный менеджер:
*
Тут SteamCMD должен установиться по пути /usr/games/steamcmd
Bash:
sudo add-apt-repository multiverse
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install steamcmd

Установка и обновление сервера TF2​


AppID для dedicated-сервера Team Fortress 2 — 232250.

Пример команд в SteamCMD:
Заходим в SteamCMD:

./steamcmd.sh

Внутри SteamCMD:

Код:
login anonymous  # Анонимный логин для публичных серверов
force_install_dir /var/lib/pterodactyl/mounts/tf2_full # Укажите путь к директории сервера
app_update 232250 validate  # Установка/обновление с проверкой файлов
quit

В панели Pterodactyl нужно создать API Ключи для управления серверами:
Настройка находится по пути domen.ru/account/api или в панели в вашем профиле вкладка API Credentials.
Создаём новый ключ и записываем его.

1765855085938.png

Создание файла автообновления сервера:
Будем создавать скрипт по пути: /usr/local/bin

Например название: ptero_tf2_update.sh
Содержание:
C-подобный:
#!/bin/bash
set -euo pipefail

# === Конфигурация ===
STEAMCMD="/usr/games/steamcmd" #Путь до SteamCMD
APP_ID=232250 #AppID сервера в Steam
MOUNT_PATH="/var/lib/pterodactyl/mounts/tf2_full" #Путь до полного сервера игры
LOCKFILE="/tmp/tf2full_update.lock" #Файл лока чтобы не дёргать часто
PANEL_API_KEY="ptlc_B1NddTOiQPxsaXYUr17x6AzdsPeDiT48lGCcZ" #API ключ клиента панели для взаимодействия с серверами (перезагрузки)
PANEL_HOST="https://sub.domain.ru" #Домен где установлена панель

# === Вебхуки ===
DISCORD_WEBHOOK_URL="https://discordapp.com/api/webhooks/1398526936280996935/M2HqbU231aon99Vp0-3M90sd2XmtQ5kh1G0xds40pWiwhLhc0JsdfJzDKwnKkGPblWd_e5dC" #WebHook для системных сообщений в дискорд канале об обновлениях (для вас)
SECOND_WEBHOOK_URL="https://discord.com/api/webhooks/14264049923404414854/2jWssdxehTmHUPrtoe4sdfNXhjDGIOiaUVdsfsdAdsf7P27CootWLCL5sdfDiENKlJyltY60" #WebHook для системных сообщений в дискорд канале об обновлениях (для всех)

# === Утилиты ===
send_embed() {
  local title="$1" desc="$2" color="${3:-16776960}"
  local payload=$(cat <<EOF
{"embeds":[{"title":"$title","description":"$desc","color":$color,"timestamp":"$(date -Iseconds)"}]}
EOF
)
  curl -s -H "Content-Type: application/json" -X POST -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null
}

send_plain_to_second() {
  local content="$1"
  local payload=$(cat <<EOF
{"content":"$content"}
EOF
)
  curl -s -H "Content-Type: application/json" -X POST -d "$payload" "$SECOND_WEBHOOK_URL" >/dev/null
}

get_remote_buildid() {
  "$STEAMCMD" +login anonymous +app_info_update 1 \
    +app_info_print "$APP_ID" +quit 2>/dev/null \
    | awk '/"buildid"/ { gsub(/"/,""); print $2; exit }'
}

get_local_buildid() {
  local acf="$MOUNT_PATH/steamapps/appmanifest_${APP_ID}.acf"
  [[ -f "$acf" ]] && awk -F'"' '/"buildid"/ { print $4; exit }' "$acf" || echo "0"
}

api_curl() {
  curl -sk -H "Authorization: Bearer $PANEL_API_KEY" \
       -H "Content-Type: application/json" \
       -H "Accept: application/vnd.pterodactyl.v1+json" "$@"
}

# === Логика ===
exec 200>"$LOCKFILE"
flock -n 200 || { echo "🔒 Уже работает"; exit 1; }

local=$(get_local_buildid)
remote=$local;
remote=$(get_remote_buildid)

# ✅ ПРОВЕРКА: удалённый buildid должен быть непустым числом
if [[ -z "$remote" || ! "$remote" =~ ^[0-9]+$ ]]; then
  echo "❌ Не удалось получить корректный удалённый buildid"
  send_embed "❌ Ошибка обновления TF2" "Не удалось получить корректный удалённый buildid из SteamCMD." 16711680
  exit 1
fi

if [[ "$remote" == "$local" ]]; then
  echo "✔ Уже актуально ($local)"
  exit 0
fi

# === Уведомление о наличии обновления ===
send_embed "⚙️ Обнаружено обновление TF2" "🔹 Текущая версия: **$local**\n🔹 Новая версия: **$remote**\nНачинаю обновление..." 16776960
send_plain_to_second "⚙️ Обнаружено обновление TF2\nТекущая: **$local** → Новая: **$remote**\nToday at $(date +"%H:%M")"

# === Обновление с проверкой и ретраями (до 3 попыток) ===
tmpf=$(mktemp)
cat >"$tmpf" <<EOF
@ShutdownOnFailedCommand 1
@NoPromptForPassword 1
force_install_dir "$MOUNT_PATH"
login anonymous
app_update $APP_ID
quit
EOF

attempt=1
max_attempts=3
updated_ok=0

while (( attempt <= max_attempts )); do
  echo "⏬ Запуск app_update (попытка $attempt/$max_attempts)…"
  "$STEAMCMD" +runscript "$tmpf" || true

  new_local=$(get_local_buildid)
  echo "🔎 Проверка: локальный buildid=$new_local, ожидаем $remote"

  if [[ -n "$new_local" && "$new_local" != "0" && "$new_local" == "$remote" ]]; then
    echo "✅ Обновление успешно на попытке $attempt"
    updated_ok=1
    break
  fi

  if (( attempt < max_attempts )); then
    send_embed "🔁 Повтор обновления TF2" "После попытки $attempt buildid=$new_local, ожидали $remote. Пробую ещё раз." 16776960
    sleep 5
  fi

  ((attempt++))
done

rm -f "$tmpf"

if (( updated_ok == 0 )); then
  echo "❌ Обновление не удалось после $max_attempts попыток (buildid=$new_local, ожидали $remote)."
  send_embed "❌ Обновление не удалось" "После $max_attempts попыток: buildid=$new_local, ожидали $remote.\nРестарты отменены." 16711680
  exit 1
fi

# === Очистка карт ===
find "$MOUNT_PATH/tf/maps" -type f -name "*.bsp" -delete || true

# === Перезапуск всех серверов ===
avail=$(api_curl "$PANEL_HOST/api/client")
mapfile -t uuids < <(jq -r '.data[].attributes.uuid' <<<"$avail")

inf_src="$MOUNT_PATH/tf/steam.inf"

for uuid in "${uuids[@]}"; do
  vol="/var/lib/pterodactyl/volumes/$uuid"
  inf_dest="$vol/tf/steam.inf"
  [[ -f "$inf_src" ]] && cp "$inf_src" "$inf_dest" || continue

  api_curl -X POST "$PANEL_HOST/api/client/servers/$uuid/power" \
    -d '{"signal":"restart"}' >/dev/null || {
      send_embed "⚠️ Ошибка" "Не удалось перезапустить сервер **$uuid**" 16711680
      continue
    }

  send_embed "🔄 Сервер перезапущен" "$uuid обновлён до **$remote**" 3066993
done

# === Финальное уведомление ===
send_embed "✅ Обновление завершено" "🎉 Все сервера успешно перезапущены." 5814783
send_plain_to_second "✅ Обновление завершено\n🎉 Все сервера перезапущены.\nToday at $(date +"%H:%M")"

Скрипт будет обновлять сервер если для него будет обновления, при начале и окончании обновления будет присылать сообщения в ДС.
*Обязательно редактируйте данные в скрипте на свои*

1765855330446.png
1765855375083.png


Теперь нужно сделать так чтобы скрипт периодически запускался и смотрел есть ли обновления, для этого сделаем крон:
Пишем в консоле VDS:
crontab -e

У вас откроется файлик, на последнюю пустую строчку пропишите: */3 * * * * /usr/local/bin/ptero_tf2_update.sh
Каждые 3 минуты будет запускаться скрипт для проверки есть ли обновления.
Потом нужно закрыть режим редактирования crontab, он предложит сохранить.

У меня это комбинации:
Ctrl + x - закрытие режима редактирования.
Затем он предложит сохранить - нажимаете y (именно английскую)
Затем нажимаете enter.

Должен написать:
crontab: installing new crontab

Можете повторно зайти в режим редактирования и убедиться что там есть новая запись.

И так, мы наконец-то добрались до основной темы гайда...
Создаём необходимые маунты тут: /admin/mounts
Эмпирическим путём (тыканием пальца) выяснил что как минимум эти маунты рабочие:
1765856066859.png


Name: base_hl2
Source: /var/lib/pterodactyl/mounts/tf2_full/hl2
Target: /home/container/hl2

Name: base_platform
Source: /var/lib/pterodactyl/mounts/tf2_full/platform
Target: /home/container/platform

Name: base_bin
Source: /var/lib/pterodactyl/mounts/tf2_full/bin
Target: /home/container/bin

Name: tf_base_tf_files
Source: /var/lib/pterodactyl/mounts/tf2_full/tf
Target: /home/container/tf/base_tf_files

Name: tf_bin
Source: /var/lib/pterodactyl/mounts/tf2_full/tf/bin
Target: /home/container/tf/bin

Name: tf_resource
Source: /var/lib/pterodactyl/mounts/tf2_full/tf/resource
Target: /home/container/tf/resource

Name: tf_scripts
Source: /var/lib/pterodactyl/mounts/tf2_full/tf/scripts
Target: /home/container/tf/scripts

Name: base_steam
Source: /var/lib/pterodactyl/mounts/tf2_full/.steam
Target: /home/container/.steam

Name: base_steamcmd
Source: /var/lib/pterodactyl/mounts/tf2_full/steamcmd
Target: /home/container/steamcmd

Name: base_steamapps
Source: /var/lib/pterodactyl/mounts/tf2_full/steamapps
Target: /home/container/steamapps
Везде Read Only: true, кроме base_steam, там false (но можете попробовать и true поставить)

Вики Pterodactyl говорит что нельзя в target писать часть пути home/container, но хз, без этого не работало.

При создании Mount указываете свою ноду и яйцо сервера.
Затем нужно разрешить директорию маунтов в кфг Pterodacty, файл: /etc/pterodactyl, внизу файла, должно получится как-то так:
YAML:
allowed_mounts:
- /var/lib/pterodactyl/mounts
allowed_origins: []

Затем в установленном сервере в панели Pterodactyl маунтите все Mount (нажимаете на зелёные плюсики)
1765856566083.png


Изменения вступят в силу после перезагрузки.
Особенность Mount, файлы которые замаунчены видит только сам игровой сервер (контейнер), если мотреть через консоль VDS, то будет показывать что просто пустая папка.

Теперь нужно создать скрипт для автоудаления необходимых оригинальных файлов на игровом сервере:
Например у меня это скрипт /usr/local/bin/optimize_container.sh


Код:
#!/bin/bash
set -euo pipefail

PANEL_API_KEY="ptlc_B1NddTOiQPxsaXYUr17x6AzdsPeDiT48lGCcZ" #API ключ клиента панели для взаимодействия с серверами (перезагрузки)
PANEL_HOST="https://sub.domain.ru" #Домен где установлена панель
VOLUMES_PATH="/var/lib/pterodactyl/volumes"

if [ $# -ne 1 ]; then
  echo "Использование: $0 <SERVER_ID>"
  exit 1
fi

SERVER_ID="$1"
echo "[*] Получаю данные сервера ID $SERVER_ID..."
SERVER_DATA=$(curl -s -H "Authorization: Bearer $PANEL_API_KEY" \
  -H "Accept: application/vnd.pterodactyl.v1+json" \
  "$PANEL_HOST/api/application/servers/$SERVER_ID")

UUID=$(echo "$SERVER_DATA" | jq -r '.attributes.uuid')
CONTAINER_PATH="$VOLUMES_PATH/$UUID"

if [[ -z "$UUID" || "$UUID" == "null" || ! -d "$CONTAINER_PATH" ]]; then
  echo "❌ Сервер или контейнер не найден."
  exit 1
fi

echo "[+] UUID: $UUID"
echo "[+] Путь к контейнеру: $CONTAINER_PATH"

echo "[*] Удаление старых файлов и директорий..."
rm -rf \
  "$CONTAINER_PATH/.steam" \
  "$CONTAINER_PATH/bin" \
  "$CONTAINER_PATH/hl2" \
  "$CONTAINER_PATH/platform" \
  "$CONTAINER_PATH/steamapps" \
  "$CONTAINER_PATH/steamcmd" \
  "$CONTAINER_PATH/tf/bin" \
  "$CONTAINER_PATH/tf/platform" \
  "$CONTAINER_PATH/tf/resource" \
  "$CONTAINER_PATH/tf/scripts" \
  "$CONTAINER_PATH/tf/tf2_misc_"*.vpk \
  "$CONTAINER_PATH/tf/tf2_misc_dir.vpk" \
  "$CONTAINER_PATH/tf/tf2_sound_misc.vpk.sound.cache" \
  "$CONTAINER_PATH/tf/tf2_sound_misc_dir.vpk" \
  "$CONTAINER_PATH/tf/tf2_sound_vo_english.vpk.sound.cache" \
  "$CONTAINER_PATH/tf/tf2_sound_vo_english_dir.vpk" \
  "$CONTAINER_PATH/tf/tf2_textures_"*.vpk \
  "$CONTAINER_PATH/tf/tf2_textures_dir.vpk"

echo "[+] Удаление завершено."

Потом запускаем скрипт через SSH VDS:
/usr/local/bin/optimize_container.sh <ID сервера> (Именно ID, а не UID контейнера)
Смотреть ID тут:
1765857151497.png

Для данного примера: /usr/local/bin/optimize_container.sh 20

Далее самые спорные места, 100% можно сделать лучше:

*Сервер должен быть включён
Заходим в оболочку контейнера игрового сервера:
docker exec -it <UUID сервера> /bin/sh, пример: docker exec -it d10cb34f-3f5c-4e99-ad8a-cf68b4088deb /bin/sh
Если всё ок:
1765857826401.png

Пропишите в консоль:
Bash:
link_base_tf_files() {
    LINK_SRC="tf/base_tf_files"
    LINK_DST="tf"

    echo "📎 [LINKER] Начинаю связывание файлов..."
    echo "📂 Исходник: $LINK_SRC"
    echo "📂 Цель: $LINK_DST"

    if [ ! -d "$LINK_SRC" ]; then
        echo "❌ Каталог не найден: $LINK_SRC"
        return
    fi

    count=0
    for file in "$LINK_SRC"/*.vpk "$LINK_SRC"/*.cache "$LINK_SRC"/*.manifest; do
        [ -e "$file" ] || continue
        basefile=$(basename "$file")
        target="$LINK_DST/$basefile"
        [ -L "$target" ] && [ "$(readlink "$target")" = "../$file" ] && continue
        [ -e "$target" ] && echo "⚠️  Удаляю: $basefile" && rm -f "$target"
        ln -s "../$file" "$target"
        echo "🔗 Связано: $basefile"
        count=$((count+1))
    done

    echo "✅ [LINKER] Всего создано ссылок: $count"
}

link_base_tf_files
1765857964192.png
1765857978335.png


Необходимые ссылки создались.

В параметры запуска сервера можно прописать +sv_auto_update_enabled 0 для отключения автообновления, хотя в интернете разнится информация как отключать автообновление у серверов.

Перезагружаете сервер и надеетесь что всё работает)

Конечно лучше на пустом сервере проверять сначала или сделать бэкап...
Еб*нулся ли я? Наверное...
 

Вложения

  • 1765855455275.png
    1765855455275.png
    24.7 КБ · Просмотры: 4
  • 1765857958957.png
    1765857958957.png
    9.2 КБ · Просмотры: 4
Последнее редактирование:
Сверху Снизу