# tribly-setup.ps1 — установка Tribly на ваш сервер (Windows 10/11). # # ЗАПУСК. Откройте PowerShell и выполните одну строку: # # irm https://tribly.ru/tribly-setup.ps1 -OutFile $env:TEMP ribly-setup.ps1; powershell -ExecutionPolicy Bypass -File $env:TEMP ribly-setup.ps1 # # Если файл уже скачан, запускайте его в открытом окне PowerShell: # # powershell -ExecutionPolicy Bypass -File .\tribly-setup.ps1 # # Двойным щелчком и через «Выполнить с помощью PowerShell» скачанный файл # запускать не стоит: Windows помечает загруженные из интернета файлы и может # закрыть окно раньше, чем вы прочитаете сообщение. Снять метку можно так: # # Unblock-File .\tribly-setup.ps1 # # Скрипт спросит адрес и пользователя вашего сервера, подключится по SSH # (он встроен в Windows) и развернёт всё на сервере сам. $ErrorActionPreference = "Stop" # Окно, запущенное двойным щелчком, закрывается сразу после выхода — вместе с # текстом ошибки. Держим его открытым, пока человек не прочитает. $KeepOpen = $Host.Name -eq 'ConsoleHost' -and -not $env:WT_SESSION -and $MyInvocation.MyCommand.Path function Pause-IfNeeded { if ($KeepOpen) { Write-Host ""; Read-Host "Нажмите Enter, чтобы закрыть окно" | Out-Null } } trap { Write-Host "" Write-Host "ОШИБКА: $($_.Exception.Message)" -ForegroundColor Red Pause-IfNeeded Pause-IfNeeded exit 1 } # ИСТОЧНИК ДИСТРИБУТИВА — релизы на GitHub. Тот же, из которого сервер потом # берёт обновления: «чем поставили» и «чем обновляется» — одно место. $UpdateRepo = $env:UPDATE_REPO if (-not $UpdateRepo) { $UpdateRepo = "4eSyH/tribly-messenger" } Write-Host "=== Установка Tribly на ваш VPS ===" -ForegroundColor Cyan Write-Host "Нужен VPS с Ubuntu 24.04 или новее и белым IP" Write-Host "" $ip = Read-Host "IP адрес сервера" if (-not $ip) { Write-Host "IP обязателен." -ForegroundColor Red; Pause-IfNeeded; exit 1 } $user = Read-Host "SSH-пользователь [root]" if (-not $user) { $user = "root" } if (-not (Get-Command ssh -ErrorAction SilentlyContinue)) { Write-Host "ssh не найден. Включите OpenSSH Client: Параметры → Приложения → Дополнительные компоненты → OpenSSH Client." -ForegroundColor Red exit 1 } Write-Host "" Write-Host "Подключаюсь к $user@$ip. Если для этого сервера настроен ключ SSH — вход пройдёт без пароля, иначе он будет запрошен." -ForegroundColor Yellow Write-Host "Дальше отвечайте на вопросы установщика прямо здесь." Write-Host "" # Одинарные кавычки (@'…'@) — БЕЗ PowerShell-интерполяции: bash-переменные # на стороне Windows разворачиваться не должны. Значения подставляем заменой. $remote = @' set -e SUDO=""; [ "$(id -u)" -eq 0 ] || SUDO=sudo export DEBIAN_FRONTEND=noninteractive command -v curl >/dev/null 2>&1 || { $SUDO apt-get update -y && $SUDO apt-get install -y curl; } $SUDO mkdir -p /opt/tribly && cd /opt/tribly # Caddyfile — конфиг входа по TLS: без него docker создаёт на месте # ./caddy/Caddyfile пустой КАТАЛОГ, и контейнер входа уходит в рестарт-цикл. # updater.sh — служба обновления по кнопке из админки. SETUP_FILES="install.sh update.sh remote-update.sh updater.sh backup-db.sh docker-compose.yml Caddyfile" # Образец настроек — справочник, а не деталь стека: установка без него # проходит полностью. Качаем отдельно и молча пропускаем, если его нет. EXTRA_FILES="env.example" # КОМПЛЕКТ ПРИВОДИМ К ВЫПУСКУ ПО СОДЕРЖИМОМУ, А НЕ ПО НАЛИЧИЮ ФАЙЛОВ. # # Раньше скачивалось только недостающее. После прерванной установки на сервере # оставались все файлы прошлого выпуска, ничего не «не хватало» — и запускался # СТАРЫЙ установщик, хотя человек только что скачал свежий скрипт. Со стороны # это выглядело как «правки не приехали». # # Теперь сверяем каждый файл с контрольной суммой выпуска и обновляем то, что # разошлось. Набор всегда получается из ОДНОГО выпуска. echo "==> Узнаю последнюю версию…" VER_RAW=$(curl -fsSL --max-time 20 -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/__REPO__/releases/latest" \ | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | cut -d'"' -f4) # Тег выпуска несёт букву «v», и адрес файла строится именно из тега. TAG="$VER_RAW" VER="${VER_RAW#v}" if [ -z "$VER" ]; then # Версия не определяется и от часового предела запросов у GitHub, и от # сетевой икоты хостера. Если установщик уже лежит — продолжаем тем, что # есть: чего не хватает, скажет он сам. if [ -f /opt/tribly/install.sh ]; then echo "Версию узнать не удалось — продолжаю тем, что уже скачано." GET= else echo "Не удалось определить версию — проверьте доступ сервера в интернет." exit 1 fi else echo "==> Готовлю Tribly $VER…" GET="$SETUP_FILES" # Старый манифест сначала удаляем: оставшийся от прошлого выпуска, он # заставлял считать устаревшие файлы совпадающими и пропускать обновление. $SUDO rm -f /opt/tribly/.release-sums $SUDO curl -fsSL --max-time 60 -o /opt/tribly/.release-sums \ "https://github.com/__REPO__/releases/download/$TAG/SHA256SUMS" 2>/dev/null \ || echo " контрольные суммы выпуска не скачались — обновляю весь комплект" fi for f in $GET; do # Файл на месте и совпадает с выпуском — не трогаем. if [ -f "/opt/tribly/$f" ] && [ -s /opt/tribly/.release-sums ]; then want=$(grep -E "[ *]$f\$" /opt/tribly/.release-sums | awk '{print $1}' | head -1) have=$(sha256sum "/opt/tribly/$f" 2>/dev/null | awk '{print $1}') if [ -n "$want" ] && [ "$want" = "$have" ]; then continue fi echo " обновляю $f" fi # Качаем во временный файл и переносим только после успеха: при разрыве связи # curl оставляет на диске ОБРЕЗАННЫЙ файл, и такой огрызок дальше считался бы # готовым. $SUDO curl -fsSL -o "/opt/tribly/$f.part" \ "https://github.com/__REPO__/releases/download/$TAG/$f" \ || { $SUDO rm -f "/opt/tribly/$f.part"; echo "Не скачался $f"; exit 1; } # Копию настроек сохраняем ВСЕГДА перед заменой, а не только когда есть # манифест: без него ветка сверки не выполняется вовсе, и правки владельца # затирались молча. case "$f" in docker-compose.yml|Caddyfile) if [ -f "/opt/tribly/$f" ] && [ -f /opt/tribly/.env ] \ && ! cmp -s "/opt/tribly/$f" "/opt/tribly/$f.part"; then $SUDO cp -f "/opt/tribly/$f" "/opt/tribly/$f.bak" \ && echo " прежний $f сохранён рядом как $f.bak" fi ;; esac $SUDO mv -f "/opt/tribly/$f.part" "/opt/tribly/$f" done # Образец настроек не обязателен: отказ проходит молча. for f in $EXTRA_FILES; do $SUDO curl -fsSL -o "/opt/tribly/$f.part" \ "https://github.com/__REPO__/releases/download/$TAG/$f" 2>/dev/null \ && $SUDO mv -f "/opt/tribly/$f.part" "/opt/tribly/$f" \ || $SUDO rm -f "/opt/tribly/$f.part" done # Раскладка конфига входа: в релизе он лежит плоским файлом, а стек монтирует # ./caddy/Caddyfile. Свежий install.sh делает это сам, но на сервере со старым # install.sh докачанный Caddyfile так и остался бы лежать без дела — docker # создал бы на месте отсутствующего файла пустой КАТАЛОГ, и вход остался бы # без TLS. if [ ! -f /opt/tribly/caddy/Caddyfile ] && [ -f /opt/tribly/Caddyfile ]; then $SUDO mkdir -p /opt/tribly/caddy $SUDO cp /opt/tribly/Caddyfile /opt/tribly/caddy/Caddyfile fi # chmod по тем файлам, что есть: при недокачанном комплекте отсутствующий файл # уронил бы весь прогон на set -e, не дав дойти до install.sh. for f in install.sh update.sh remote-update.sh updater.sh; do if [ -f "/opt/tribly/$f" ]; then $SUDO chmod +x "/opt/tribly/$f"; fi done # Своё зеркало выпусков передаём установщику: без этого он взял бы адрес # по умолчанию, и обновления шли бы мимо зеркала. $SUDO env UPDATE_REPO="__REPO__" ./install.sh '@ -replace "__REPO__", $UpdateRepo -replace "`r`n", "`n" # Скрипт едет на сервер ОДНИМ токеном в base64, а не текстом аргумента. # Windows PowerShell 5.1 (он и стоит в системе по умолчанию) передаёт нативным # программам аргументы по старым правилам: строку с пробелами разбивает на # слова и срезает внутренние кавычки — bash получал `curl -H Accept:` с пустым # заголовком, незакрытый `grep -o '[^]*'` и `cut -d''`, и установка падала на # «Не удалось определить версию». В base64 нет ни пробелов, ни кавычек, ломать # нечего, и команда одинаково доезжает и из 5.1, и из pwsh 7. # # На сервере скрипт кладём файлом и запускаем `bash <файл>`, а НЕ `bash -s`: # при чтении с stdin установщик унаследовал бы уже прочитанный канал вместо # терминала и не смог бы задать свои вопросы (домен, почта). Ради этих вопросов # здесь и стоит ssh -t. $b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($remote)) ssh -t -o StrictHostKeyChecking=accept-new "$user@$ip" "echo $b64 | base64 -d > ~/tribly-setup.sh && bash ~/tribly-setup.sh" Pause-IfNeeded