Ctrl K
ComunidadGuías…

install

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh
# torrcast: curl -fsSL https://torrcast.anysda.space | sh   (или .../install - тот же файл)
#
# Бутстрап, а не установка: узнаёт у GitHub последнюю версию, тащит тарбол этой
# версии, сверяет sha256, распаковывает в mktemp -d и зовёт install.sh оттуда.
# Всё тело - внутри функций: оборванная закачка (curl режет на середине) ничего
# не выполнит, потому что не доедет до вызова main в конце файла.
#
# 🔴 TC-887. Загрузчик тут ОДИН, а входа в него два, и это не совпадение, а решение:
# `cast --upgrade` зовёт этот же файл (install.sh кладёт его в $PREFIX/install), а
# не второй такой же на питоне. Отличается только преддверие - названная версия, от
# которой идём, - а узнавание тега, закачка, сверка sha256, распаковка и передача
# install.sh общие обоим входам. Заведи обновление своей закачкой, и два тела разъедутся
# ровно там, где чинить будут только одно из них.
set -eu

BOOTSTRAP_URL="${TORRCAST_BOOTSTRAP_URL:-https://torrcast.anysda.space}"
GITHUB_WEB="${TORRCAST_GITHUB_WEB:-https://github.com}"
PROJECT_PATH="${TORRCAST_PROJECT_PATH:-anysda/torrcast}"
LANGUAGE="${TORRCAST_LANGUAGE:-en}"
#: Установленная версия, от которой идёт обновление. Непустое значение - это и есть
#: второй вход: работа начинается со сверки версий, а не со скачивания.
UPGRADE_FROM="${TORRCAST_UPGRADE_FROM:-}"

case "$LANGUAGE" in
    en|ru) ;;
    # 🔴 TC-887. Код 1, а НЕ 2. Двойка занята штатным исходом установщика - урезанным
    # каталогом индексеров (EXIT_CATALOG_CUT в install.sh), - и `cast --upgrade` читает
    # её как «поставилось, но беднее полного». Отдай отсюда двойку, и отказ разбора
    # доехал бы до человека словом «обновлено», хотя не тронуто ничего.
    *) printf 'error: TORRCAST_LANGUAGE must be en or ru\n' >&2; exit 1 ;;
esac

say() { if [ "$LANGUAGE" = ru ]; then printf '%s\n' "$2" >&2; else printf '%s\n' "$1" >&2; fi; }
fail() { say "error: $1" "ошибка: $2"; exit 1; }

need() { command -v "$1" >/dev/null 2>&1 || fail "$1 is required but is not in PATH" "нужен $1, его нет в PATH"; }

# Не root - не падаем молча: под sudo перезапускаемся тем же одностроком, без него
# просим человека сделать это самому. TORRCAST_NO_ROOT - лазейка для песочницы, как
# в install.sh.
# `sudo -E` тут не годится: на части машин sudo собран без сохранения окружения, и
# `-E` тихо игнорируется (sudo молча предупреждает в stderr и правит по-своему), из-за
# чего TORRCAST_GITHUB_WEB/TORRCAST_PROJECT_PATH/TORRCAST_BOOTSTRAP_URL пропадали бы у
# перезапущенного процесса без единого слова. Поэтому переопределения тащим не через
# окружение, а явными аргументами позиционника, и экспортируем их уже внутри новой
# оболочки за трубой - так их видит и curl, и запущенный им sh.
become_root() {
    [ -n "${TORRCAST_NO_ROOT:-}" ] && return 0
    [ "$(id -u)" -eq 0 ] && return 0
    # 🔴 Обновление под sudo НЕ перезапускается одностроком. Односторок - это установка
    # с нуля: он потерял бы и версию, от которой идём, и заставку обновления, и человек
    # получил бы вместо `cast --upgrade` полную переустановку, о которой не просил.
    # Поэтому тут именно строка «повтори вот так», а не тихая подмена работы.
    have_sudo=""
    command -v sudo >/dev/null 2>&1 && have_sudo=1
    if [ -n "$UPGRADE_FROM" ]; then
        [ -n "$have_sudo" ] \
            && fail "root is required: sudo cast --upgrade" "нужен root: sudo cast --upgrade"
        fail "root is required: run cast --upgrade as root" \
             "нужен root: запусти cast --upgrade от root"
    fi
    if [ -n "$have_sudo" ]; then
        say "not root - restarting through sudo" "не root - перезапуск через sudo"
        # Язык через sudo сносим, только если он НАЗВАН в окружении однострока (TC-955):
        # экспорт дефолта отсюда сбрасывал бы язык из живого конфига при каждом
        # обновлении не-root'ом. Молчание обязано доехать до install.sh молчанием.
        exec sudo sh -c '
            export TORRCAST_BOOTSTRAP_URL="$1" TORRCAST_GITHUB_WEB="$2" TORRCAST_PROJECT_PATH="$3"
            [ -z "$4" ] || export TORRCAST_LANGUAGE="$4"
            curl -fsSL "$1" | sh
        ' _ "$BOOTSTRAP_URL" "$GITHUB_WEB" "$PROJECT_PATH" "${TORRCAST_LANGUAGE:-}"
    fi
    # Сюда доходят только там, где sudo нет вовсе: советовать его тут значило бы
    # послать человека за командой, которой у него на машине не существует.
    fail "root is required: run it as root: sh -c \"curl -fsSL $BOOTSTRAP_URL | sh\"" \
        "нужен root: запусти от root: sh -c \"curl -fsSL $BOOTSTRAP_URL | sh\""
}

# Печатает тег последней версии в stdout. Спрашивается не API, а сам `/releases/latest`:
# он отвечает 302, и в Location стоит `/releases/tag/vX.Y.Z`. Так узнаётся версия без
# api.github.com, у которого анониму отведено 60 запросов в час на адрес - потолок,
# делящийся на всех за одним NAT, и упереться в него значило бы получить отказ установки
# на ровном месте. До первого релиза тот же адрес уводит на `/releases` без `/tag/`:
# релизов ещё нет - сказать об этом словами, а не сырым перенаправлением.
latest_version() {
    loc="$(curl -fsS --connect-timeout 10 -o /dev/null -w '%{redirect_url}' \
                "$GITHUB_WEB/$PROJECT_PATH/releases/latest" 2>/dev/null)" \
        || fail "could not reach $GITHUB_WEB/$PROJECT_PATH/releases/latest" \
                "не достучаться до $GITHUB_WEB/$PROJECT_PATH/releases/latest"
    case "$loc" in
        */releases/tag/*) ;;
        "") fail "no redirect from $GITHUB_WEB/$PROJECT_PATH/releases/latest" \
                 "$GITHUB_WEB/$PROJECT_PATH/releases/latest не перенаправил никуда" ;;
        *) fail "no releases yet (releases/latest leads to the release list) - come back after the first release" \
                "релизов ещё нет (releases/latest ведёт на список релизов) - вернись, когда выйдет первый релиз" ;;
    esac
    ver="${loc##*/releases/tag/}"
    [ -n "$ver" ] || fail "the redirect names no tag" "в перенаправлении нет тега"
    printf '%s\n' "$ver"
}

main() {
    need curl
    need tar
    need sha256sum
    need bash
    become_root

    # Тег из GitHub - с ведущей v (v1.0.0): так он стоит в Location у releases/latest,
    # и так же он стоит в пути к ассету. Имена самих файлов - БЕЗ v (release.sh срезает
    # её через ${tag#v}), а имя внутри .sha256 обязано побайтово совпасть с локальным
    # именем файла.
    tag="$(latest_version)"
    ver="${tag#v}"
    # Имя продукта не переводится и не транслитерируется: torrcast на обоих языках.
    if [ -n "$UPGRADE_FROM" ]; then
        # 🔴 Совпало - работы нет ВОВСЕ: ни временного каталога, ни закачки, ни единого
        # тронутого файла. Ровно этим `cast --upgrade` отличается от «переустановить и
        # посмотреть, что выйдет»: у переустановки тот же исход стоит полного прогона
        # установщика, перезаписи юнитов и перезапуска служб под идущим показом.
        # Выход отсюда стоит ДО mktemp нарочно: так «не тронул ничего» держится кодом,
        # а не обещанием прибрать за собой.
        if [ "$ver" = "$UPGRADE_FROM" ]; then
            say "torrcast $ver is already the latest version" \
                "torrcast $ver - уже последняя версия"
            return 0
        fi
        say "torrcast $UPGRADE_FROM → $ver" "torrcast $UPGRADE_FROM → $ver"
        # Дальше по цепочке это читает install.sh из тарбола: он и рисует заставку
        # обновления вместо заставки установки.
        export TORRCAST_UPGRADE_FROM="$UPGRADE_FROM"
    else
        say "torrcast $ver" "torrcast $ver"
    fi

    tmp="$(mktemp -d)"
    trap 'rm -rf "$tmp"' EXIT
    pkg="$GITHUB_WEB/$PROJECT_PATH/releases/download/$tag"

    curl -fsSL -o "$tmp/torrcast-$ver.tar.gz" "$pkg/torrcast-$ver.tar.gz" \
        || fail "could not download torrcast-$ver.tar.gz" "не скачался тарбол torrcast-$ver.tar.gz"
    curl -fsSL -o "$tmp/torrcast-$ver.tar.gz.sha256" "$pkg/torrcast-$ver.tar.gz.sha256" \
        || fail "could not download the sha256 checksum" "не скачалась сверка sha256"
    (cd "$tmp" && sha256sum -c "torrcast-$ver.tar.gz.sha256") >/dev/null \
        || fail "sha256 mismatch - the archive is damaged or was replaced" "sha256 не сошёлся - тарбол повреждён или подменён"

    mkdir "$tmp/src"
    tar -xzf "$tmp/torrcast-$ver.tar.gz" -C "$tmp/src"
    [ -f "$tmp/src/install.sh" ] || fail "the archive has no install.sh" "в тарболе нет install.sh"

    # install.sh - bash-скрипт со своим шебангом (#!/usr/bin/env bash), не sh: на
    # Debian /bin/sh - dash, и на башизмах install.sh он падает сразу. </dev/null
    # отцепляет stdin от недочитанного хвоста трубы curl | sh.
    (cd "$tmp/src" && ./install.sh </dev/null)
    exit $?
}

main "$@"