#!/bin/bash # Verified Apple Silicon and Intel releases, selected on this computer. if [ "$(/usr/sbin/sysctl -n hw.optional.arm64 2>/dev/null || true)" = 1 ]; then #!/bin/bash # Rebound installer template. Generate the distributable with create-installer.mts. # The release version and ZIP checksum come from the owner's signed manifest. set -euo pipefail export PATH=/usr/bin:/bin:/usr/sbin:/sbin umask 077 VERSION='0.5.48' ARCH='arm64' MINIMUM_MAJOR='12' ZIP_SIZE='277251200' ZIP_SHA256='50662caaa2dd0b4402e9ee0739e6a323719df7c54ad37852cf9215988c338693' DOWNLOAD_URL='https://rebound.service-stavropol.ru/api/client-updates/releases/0.5.48/zip' TEAM_ID='' CHECK_ONLY=0 OPEN_APP=1 USER_INSTALL=0 WORK='' STAGE='' BACKUP='' TARGET='' COMMITTED=0 LOCAL_ZIP='' TARGET_OVERRIDE='' WAIT_PID='' READY_FILE='' ABORT_FILE='' say() { printf '%s\n' "$*"; } fail() { say "Ошибка: $*" >&2; exit 1; } usage() { say "Установка Rebound для Mac ($ARCH)." say 'Запуск: /bin/bash install.sh [--check] [--no-open] [--user]' say ' --check Только скачать и проверить сборку, без установки.' say ' --no-open Установить, но не запускать приложение.' say ' --user Установить в папку «Программы» текущего пользователя.' say 'Проверяется SHA-256 архива и подпись приложения. Для тестовой сборки' say 'снимается карантин только с Rebound. Настройки защиты macOS не меняются.' } cleanup() { local result=$? trap - EXIT if [ "$COMMITTED" -eq 0 ] && [ -n "$BACKUP" ] && [ -d "$BACKUP/Rebound.app" ]; then if [ ! -e "$TARGET" ] && [ ! -L "$TARGET" ]; then if /bin/mv "$BACKUP/Rebound.app" "$TARGET"; then say 'Предыдущая версия восстановлена.' >&2 else say "Предыдущая версия сохранена: $BACKUP/Rebound.app" >&2 BACKUP='' fi else say "Предыдущая версия сохранена: $BACKUP/Rebound.app" >&2 BACKUP='' fi fi [ -z "$STAGE" ] || /bin/rm -rf "$STAGE" [ -z "$BACKUP" ] || /bin/rm -rf "$BACKUP" [ -z "$WORK" ] || /bin/rm -rf "$WORK" if [ "$result" -ne 0 ]; then say 'Установка не завершена. Сохраните текст ошибки из Терминала.' >&2 fi exit "$result" } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM while [ "$#" -gt 0 ]; do case "$1" in --check) CHECK_ONLY=1 ;; --no-open) OPEN_APP=0 ;; --user) USER_INSTALL=1 ;; --local-zip|--target|--wait-pid|--ready-file|--abort-file) [ "$#" -ge 2 ] || fail "Нет значения для $1" case "$1" in --local-zip) LOCAL_ZIP="$2" ;; --target) TARGET_OVERRIDE="$2" ;; --wait-pid) WAIT_PID="$2" ;; --ready-file) READY_FILE="$2" ;; --abort-file) ABORT_FILE="$2" ;; esac shift ;; --help|-h) usage; exit 0 ;; *) fail "Неизвестный параметр: $1" ;; esac shift done if [ -n "$LOCAL_ZIP$TARGET_OVERRIDE$WAIT_PID$READY_FILE$ABORT_FILE" ]; then [ -f "$LOCAL_ZIP" ] && [ ! -L "$LOCAL_ZIP" ] || fail 'Локальный архив недоступен.' [ -n "$TARGET_OVERRIDE" ] && [ -n "$READY_FILE" ] && [ -n "$ABORT_FILE" ] || fail 'Неполные параметры локального обновления.' case "$WAIT_PID" in ''|*[!0-9]*) fail 'Неверный процесс обновляемого приложения.' ;; esac [ "$WAIT_PID" -gt 1 ] || fail 'Неверный процесс обновляемого приложения.' [ "$CHECK_ONLY" -eq 0 ] || fail 'Локальное обновление не поддерживает --check.' fi [ "$(/usr/bin/uname -s)" = Darwin ] || fail 'Этот установщик предназначен для macOS.' [ "$(/usr/bin/id -u)" -ne 0 ] || fail 'Запустите без sudo от своего пользователя.' if [ "$(/usr/sbin/sysctl -n hw.optional.arm64 2>/dev/null || true)" = 1 ]; then COMPUTER_ARCH=arm64 else [ "$(/usr/bin/uname -m)" = x86_64 ] || fail 'Этот процессор Mac не поддерживается.' COMPUTER_ARCH=x64 fi if [ "$COMPUTER_ARCH" != "$ARCH" ]; then # An already-installed Intel app can update its signed Intel build under # Rosetta. New installs still select the native build on the website. if [ "$COMPUTER_ARCH" = arm64 ] && [ "$ARCH" = x64 ] && [ -n "$LOCAL_ZIP" ]; then /usr/bin/arch -x86_64 /usr/bin/true || fail 'Для обновления Intel-сборки нужна Rosetta. Установите родную версию с сайта Rebound.' else fail 'Выбран выпуск для другого процессора. Используйте команду установки с сайта Rebound.' fi fi SYSTEM_VERSION=$(/usr/bin/sw_vers -productVersion) SYSTEM_MAJOR=${SYSTEM_VERSION%%.*} case "$SYSTEM_MAJOR" in ''|*[!0-9]*) fail 'Не удалось определить версию macOS.' ;; esac [ "$SYSTEM_MAJOR" -ge "$MINIMUM_MAJOR" ] || fail "Требуется macOS $MINIMUM_MAJOR или новее. Установлена $SYSTEM_VERSION." say "Rebound $VERSION · macOS $SYSTEM_VERSION · $ARCH" if [ -z "$TEAM_ID" ]; then say 'Тестовая сборка без сертификата Developer ID и нотарификации Apple.' say 'Установщик проверит архив и снимет карантин только с Rebound.' fi is_running() { /usr/bin/pgrep -x Rebound >/dev/null 2>&1; } if [ "$CHECK_ONLY" -eq 0 ]; then [ -n "$WAIT_PID" ] || ! is_running || fail 'Сначала закройте Rebound через Rebound → Завершить Rebound и повторите команду.' if [ -n "$TARGET_OVERRIDE" ]; then case "$TARGET_OVERRIDE" in /*/Rebound.app) ;; *) fail 'Неверный путь обновляемого приложения.' ;; esac INSTALL_DIR=$(/usr/bin/dirname "$TARGET_OVERRIDE") [ ! -L "$INSTALL_DIR" ] || fail 'Папка установки не должна быть ссылкой.' CANONICAL_DIR=$(cd -P "$INSTALL_DIR" && /bin/pwd) [ "$CANONICAL_DIR" = "$INSTALL_DIR" ] || fail 'Папка установки должна иметь прямой путь.' elif [ "$USER_INSTALL" -eq 1 ] || [ ! -w /Applications ]; then [ -n "${HOME:-}" ] && [ -d "$HOME" ] || fail 'Не найдена домашняя папка пользователя.' INSTALL_DIR="$HOME/Applications" [ ! -L "$INSTALL_DIR" ] || fail 'Папка установки не должна быть символической ссылкой.' /bin/mkdir -p "$INSTALL_DIR" else INSTALL_DIR='/Applications' fi [ -w "$INSTALL_DIR" ] && [ ! -L "$INSTALL_DIR" ] || fail 'Папка установки недоступна для записи. Попробуйте параметр --user.' TARGET="$INSTALL_DIR/Rebound.app" [ ! -L "$TARGET" ] || fail 'Rebound.app является ссылкой. Удалите ссылку вручную или используйте --user.' if [ -e "$TARGET" ]; then EXISTING_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$TARGET/Contents/Info.plist" 2>/dev/null || true) [ "$EXISTING_ID" = app.rebound.iphone ] || fail 'По пути Rebound.app находится другой объект. Он не будет заменён.' EXISTING_VERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$TARGET/Contents/Info.plist" 2>/dev/null || true) if /usr/bin/awk -v old="$EXISTING_VERSION" -v new="$VERSION" 'BEGIN {split(old,a,".");split(new,b,".");for(i=1;i<=3;i++){if(a[i]+0>b[i]+0)exit 0;if(a[i]+0 "$WORK/entries" || fail 'Не удалось прочитать список файлов обновления.' /usr/bin/awk ' /^\// || /\\/ || /[[:cntrl:]]/ {exit 1} !/^Rebound\.app(\/|$)/ && !/^__MACOSX\/Rebound\.app(\/|$)/ {exit 1} {n=split($0,parts,"/");for(i=1;i<=n;i++)if(parts[i]=="..")exit 1} ' "$WORK/entries" || fail 'Архив обновления содержит небезопасный путь.' /usr/bin/ditto -x -k "$WORK/Rebound.zip" "$WORK/unpacked" APP="$WORK/unpacked/Rebound.app" [ -d "$APP" ] && [ ! -L "$APP" ] || fail 'В архиве нет Rebound.app.' [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Contents/Info.plist")" = app.rebound.iphone ] || fail 'Неверный идентификатор приложения.' [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist")" = "$VERSION" ] || fail 'Версия приложения не совпала с выпуском.' [ -x "$APP/Contents/MacOS/Rebound" ] || fail 'Исполняемый файл отсутствует или не имеет разрешения на запуск.' /usr/bin/codesign --verify --deep --strict "$APP" || fail 'Подпись приложения повреждена.' for TOOL in "$APP/Contents/Resources/tools/"*; do [ ! -f "$TOOL" ] || /usr/bin/codesign --verify --strict "$TOOL" || fail 'Повреждён один из USB-компонентов.' done if [ -n "$TEAM_ID" ]; then /usr/bin/codesign --verify --strict -R "anchor apple generic and certificate leaf[subject.OU] = \"$TEAM_ID\"" "$APP" || fail 'Сертификат разработчика не совпал.' /usr/sbin/spctl --assess --type execute "$APP" || fail 'macOS не подтвердила нотарификацию этой сборки.' fi if [ "$CHECK_ONLY" -eq 1 ]; then say "Проверка Rebound $VERSION завершена. Приложение не устанавливалось и не запускалось." exit 0 fi STAGE=$(/usr/bin/mktemp -d "$INSTALL_DIR/.rebound-stage.XXXXXXXX") /usr/bin/ditto "$APP" "$STAGE/Rebound.app" if [ -z "$TEAM_ID" ]; then # Scoped to the exact verified app in a private staging directory. Never # disable Gatekeeper, remove all xattrs, or re-sign damaged downloaded code. /usr/bin/xattr -r -d com.apple.quarantine "$STAGE/Rebound.app" || fail 'Не удалось снять карантин с проверенной копии Rebound.' fi /usr/bin/codesign --verify --deep --strict "$STAGE/Rebound.app" || fail 'Копия приложения не прошла проверку.' if [ -n "$WAIT_PID" ]; then printf 'ready\n' > "$READY_FILE" WAITED=0 while /bin/kill -0 "$WAIT_PID" 2>/dev/null; do [ ! -e "$ABORT_FILE" ] || fail 'Обновление отложено.' [ "$WAITED" -lt 120 ] || fail 'Приложение не завершилось. Обновление отложено.' /bin/sleep 1 WAITED=$((WAITED+1)) done [ ! -e "$ABORT_FILE" ] || fail 'Обновление отложено.' fi is_running && fail 'Rebound был запущен во время установки. Закройте его и повторите команду.' if [ -e "$TARGET" ]; then BACKUP=$(/usr/bin/mktemp -d "$INSTALL_DIR/.rebound-backup.XXXXXXXX") /bin/mv "$TARGET" "$BACKUP/Rebound.app" || fail 'Не удалось заменить старую версию. Попробуйте параметр --user.' fi /bin/mv "$STAGE/Rebound.app" "$TARGET" || fail 'Не удалось поместить приложение в папку установки.' say "Установлен Rebound $VERSION: $TARGET" say 'Аккаунты, баланс и настройки сохранены.' if [ "$OPEN_APP" -eq 1 ]; then if /usr/bin/open "$TARGET"; then say 'Команда запуска отправлена macOS.' else say 'Приложение установлено, но macOS отклонила запуск. Передайте владельцу текст ошибки.' >&2 if [ -n "$BACKUP" ] && [ -d "$BACKUP/Rebound.app" ]; then # Leave TARGET absent so the EXIT trap atomically restores the old app. /bin/mv "$TARGET" "$STAGE/Rebound.app" || say "Предыдущая версия сохранена: $BACKUP/Rebound.app" >&2 fi exit 1 fi fi COMMITTED=1 say 'Если приложение сразу закрывается, сообщите текст ошибки и версию macOS.' else #!/bin/bash # Rebound installer template. Generate the distributable with create-installer.mts. # The release version and ZIP checksum come from the owner's signed manifest. set -euo pipefail export PATH=/usr/bin:/bin:/usr/sbin:/sbin umask 077 VERSION='0.5.48' ARCH='x64' MINIMUM_MAJOR='12' ZIP_SIZE='286654734' ZIP_SHA256='c5b21489b29f2e75c91e38f687be0b29d046e48e991338ac2ac1e89748c230a0' DOWNLOAD_URL='https://rebound.service-stavropol.ru/api/client-updates/macos-x64/releases/0.5.48/zip' TEAM_ID='' CHECK_ONLY=0 OPEN_APP=1 USER_INSTALL=0 WORK='' STAGE='' BACKUP='' TARGET='' COMMITTED=0 LOCAL_ZIP='' TARGET_OVERRIDE='' WAIT_PID='' READY_FILE='' ABORT_FILE='' say() { printf '%s\n' "$*"; } fail() { say "Ошибка: $*" >&2; exit 1; } usage() { say "Установка Rebound для Mac ($ARCH)." say 'Запуск: /bin/bash install.sh [--check] [--no-open] [--user]' say ' --check Только скачать и проверить сборку, без установки.' say ' --no-open Установить, но не запускать приложение.' say ' --user Установить в папку «Программы» текущего пользователя.' say 'Проверяется SHA-256 архива и подпись приложения. Для тестовой сборки' say 'снимается карантин только с Rebound. Настройки защиты macOS не меняются.' } cleanup() { local result=$? trap - EXIT if [ "$COMMITTED" -eq 0 ] && [ -n "$BACKUP" ] && [ -d "$BACKUP/Rebound.app" ]; then if [ ! -e "$TARGET" ] && [ ! -L "$TARGET" ]; then if /bin/mv "$BACKUP/Rebound.app" "$TARGET"; then say 'Предыдущая версия восстановлена.' >&2 else say "Предыдущая версия сохранена: $BACKUP/Rebound.app" >&2 BACKUP='' fi else say "Предыдущая версия сохранена: $BACKUP/Rebound.app" >&2 BACKUP='' fi fi [ -z "$STAGE" ] || /bin/rm -rf "$STAGE" [ -z "$BACKUP" ] || /bin/rm -rf "$BACKUP" [ -z "$WORK" ] || /bin/rm -rf "$WORK" if [ "$result" -ne 0 ]; then say 'Установка не завершена. Сохраните текст ошибки из Терминала.' >&2 fi exit "$result" } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM while [ "$#" -gt 0 ]; do case "$1" in --check) CHECK_ONLY=1 ;; --no-open) OPEN_APP=0 ;; --user) USER_INSTALL=1 ;; --local-zip|--target|--wait-pid|--ready-file|--abort-file) [ "$#" -ge 2 ] || fail "Нет значения для $1" case "$1" in --local-zip) LOCAL_ZIP="$2" ;; --target) TARGET_OVERRIDE="$2" ;; --wait-pid) WAIT_PID="$2" ;; --ready-file) READY_FILE="$2" ;; --abort-file) ABORT_FILE="$2" ;; esac shift ;; --help|-h) usage; exit 0 ;; *) fail "Неизвестный параметр: $1" ;; esac shift done if [ -n "$LOCAL_ZIP$TARGET_OVERRIDE$WAIT_PID$READY_FILE$ABORT_FILE" ]; then [ -f "$LOCAL_ZIP" ] && [ ! -L "$LOCAL_ZIP" ] || fail 'Локальный архив недоступен.' [ -n "$TARGET_OVERRIDE" ] && [ -n "$READY_FILE" ] && [ -n "$ABORT_FILE" ] || fail 'Неполные параметры локального обновления.' case "$WAIT_PID" in ''|*[!0-9]*) fail 'Неверный процесс обновляемого приложения.' ;; esac [ "$WAIT_PID" -gt 1 ] || fail 'Неверный процесс обновляемого приложения.' [ "$CHECK_ONLY" -eq 0 ] || fail 'Локальное обновление не поддерживает --check.' fi [ "$(/usr/bin/uname -s)" = Darwin ] || fail 'Этот установщик предназначен для macOS.' [ "$(/usr/bin/id -u)" -ne 0 ] || fail 'Запустите без sudo от своего пользователя.' if [ "$(/usr/sbin/sysctl -n hw.optional.arm64 2>/dev/null || true)" = 1 ]; then COMPUTER_ARCH=arm64 else [ "$(/usr/bin/uname -m)" = x86_64 ] || fail 'Этот процессор Mac не поддерживается.' COMPUTER_ARCH=x64 fi if [ "$COMPUTER_ARCH" != "$ARCH" ]; then # An already-installed Intel app can update its signed Intel build under # Rosetta. New installs still select the native build on the website. if [ "$COMPUTER_ARCH" = arm64 ] && [ "$ARCH" = x64 ] && [ -n "$LOCAL_ZIP" ]; then /usr/bin/arch -x86_64 /usr/bin/true || fail 'Для обновления Intel-сборки нужна Rosetta. Установите родную версию с сайта Rebound.' else fail 'Выбран выпуск для другого процессора. Используйте команду установки с сайта Rebound.' fi fi SYSTEM_VERSION=$(/usr/bin/sw_vers -productVersion) SYSTEM_MAJOR=${SYSTEM_VERSION%%.*} case "$SYSTEM_MAJOR" in ''|*[!0-9]*) fail 'Не удалось определить версию macOS.' ;; esac [ "$SYSTEM_MAJOR" -ge "$MINIMUM_MAJOR" ] || fail "Требуется macOS $MINIMUM_MAJOR или новее. Установлена $SYSTEM_VERSION." say "Rebound $VERSION · macOS $SYSTEM_VERSION · $ARCH" if [ -z "$TEAM_ID" ]; then say 'Тестовая сборка без сертификата Developer ID и нотарификации Apple.' say 'Установщик проверит архив и снимет карантин только с Rebound.' fi is_running() { /usr/bin/pgrep -x Rebound >/dev/null 2>&1; } if [ "$CHECK_ONLY" -eq 0 ]; then [ -n "$WAIT_PID" ] || ! is_running || fail 'Сначала закройте Rebound через Rebound → Завершить Rebound и повторите команду.' if [ -n "$TARGET_OVERRIDE" ]; then case "$TARGET_OVERRIDE" in /*/Rebound.app) ;; *) fail 'Неверный путь обновляемого приложения.' ;; esac INSTALL_DIR=$(/usr/bin/dirname "$TARGET_OVERRIDE") [ ! -L "$INSTALL_DIR" ] || fail 'Папка установки не должна быть ссылкой.' CANONICAL_DIR=$(cd -P "$INSTALL_DIR" && /bin/pwd) [ "$CANONICAL_DIR" = "$INSTALL_DIR" ] || fail 'Папка установки должна иметь прямой путь.' elif [ "$USER_INSTALL" -eq 1 ] || [ ! -w /Applications ]; then [ -n "${HOME:-}" ] && [ -d "$HOME" ] || fail 'Не найдена домашняя папка пользователя.' INSTALL_DIR="$HOME/Applications" [ ! -L "$INSTALL_DIR" ] || fail 'Папка установки не должна быть символической ссылкой.' /bin/mkdir -p "$INSTALL_DIR" else INSTALL_DIR='/Applications' fi [ -w "$INSTALL_DIR" ] && [ ! -L "$INSTALL_DIR" ] || fail 'Папка установки недоступна для записи. Попробуйте параметр --user.' TARGET="$INSTALL_DIR/Rebound.app" [ ! -L "$TARGET" ] || fail 'Rebound.app является ссылкой. Удалите ссылку вручную или используйте --user.' if [ -e "$TARGET" ]; then EXISTING_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$TARGET/Contents/Info.plist" 2>/dev/null || true) [ "$EXISTING_ID" = app.rebound.iphone ] || fail 'По пути Rebound.app находится другой объект. Он не будет заменён.' EXISTING_VERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$TARGET/Contents/Info.plist" 2>/dev/null || true) if /usr/bin/awk -v old="$EXISTING_VERSION" -v new="$VERSION" 'BEGIN {split(old,a,".");split(new,b,".");for(i=1;i<=3;i++){if(a[i]+0>b[i]+0)exit 0;if(a[i]+0 "$WORK/entries" || fail 'Не удалось прочитать список файлов обновления.' /usr/bin/awk ' /^\// || /\\/ || /[[:cntrl:]]/ {exit 1} !/^Rebound\.app(\/|$)/ && !/^__MACOSX\/Rebound\.app(\/|$)/ {exit 1} {n=split($0,parts,"/");for(i=1;i<=n;i++)if(parts[i]=="..")exit 1} ' "$WORK/entries" || fail 'Архив обновления содержит небезопасный путь.' /usr/bin/ditto -x -k "$WORK/Rebound.zip" "$WORK/unpacked" APP="$WORK/unpacked/Rebound.app" [ -d "$APP" ] && [ ! -L "$APP" ] || fail 'В архиве нет Rebound.app.' [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Contents/Info.plist")" = app.rebound.iphone ] || fail 'Неверный идентификатор приложения.' [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist")" = "$VERSION" ] || fail 'Версия приложения не совпала с выпуском.' [ -x "$APP/Contents/MacOS/Rebound" ] || fail 'Исполняемый файл отсутствует или не имеет разрешения на запуск.' /usr/bin/codesign --verify --deep --strict "$APP" || fail 'Подпись приложения повреждена.' for TOOL in "$APP/Contents/Resources/tools/"*; do [ ! -f "$TOOL" ] || /usr/bin/codesign --verify --strict "$TOOL" || fail 'Повреждён один из USB-компонентов.' done if [ -n "$TEAM_ID" ]; then /usr/bin/codesign --verify --strict -R "anchor apple generic and certificate leaf[subject.OU] = \"$TEAM_ID\"" "$APP" || fail 'Сертификат разработчика не совпал.' /usr/sbin/spctl --assess --type execute "$APP" || fail 'macOS не подтвердила нотарификацию этой сборки.' fi if [ "$CHECK_ONLY" -eq 1 ]; then say "Проверка Rebound $VERSION завершена. Приложение не устанавливалось и не запускалось." exit 0 fi STAGE=$(/usr/bin/mktemp -d "$INSTALL_DIR/.rebound-stage.XXXXXXXX") /usr/bin/ditto "$APP" "$STAGE/Rebound.app" if [ -z "$TEAM_ID" ]; then # Scoped to the exact verified app in a private staging directory. Never # disable Gatekeeper, remove all xattrs, or re-sign damaged downloaded code. /usr/bin/xattr -r -d com.apple.quarantine "$STAGE/Rebound.app" || fail 'Не удалось снять карантин с проверенной копии Rebound.' fi /usr/bin/codesign --verify --deep --strict "$STAGE/Rebound.app" || fail 'Копия приложения не прошла проверку.' if [ -n "$WAIT_PID" ]; then printf 'ready\n' > "$READY_FILE" WAITED=0 while /bin/kill -0 "$WAIT_PID" 2>/dev/null; do [ ! -e "$ABORT_FILE" ] || fail 'Обновление отложено.' [ "$WAITED" -lt 120 ] || fail 'Приложение не завершилось. Обновление отложено.' /bin/sleep 1 WAITED=$((WAITED+1)) done [ ! -e "$ABORT_FILE" ] || fail 'Обновление отложено.' fi is_running && fail 'Rebound был запущен во время установки. Закройте его и повторите команду.' if [ -e "$TARGET" ]; then BACKUP=$(/usr/bin/mktemp -d "$INSTALL_DIR/.rebound-backup.XXXXXXXX") /bin/mv "$TARGET" "$BACKUP/Rebound.app" || fail 'Не удалось заменить старую версию. Попробуйте параметр --user.' fi /bin/mv "$STAGE/Rebound.app" "$TARGET" || fail 'Не удалось поместить приложение в папку установки.' say "Установлен Rebound $VERSION: $TARGET" say 'Аккаунты, баланс и настройки сохранены.' if [ "$OPEN_APP" -eq 1 ]; then if /usr/bin/open "$TARGET"; then say 'Команда запуска отправлена macOS.' else say 'Приложение установлено, но macOS отклонила запуск. Передайте владельцу текст ошибки.' >&2 if [ -n "$BACKUP" ] && [ -d "$BACKUP/Rebound.app" ]; then # Leave TARGET absent so the EXIT trap atomically restores the old app. /bin/mv "$TARGET" "$STAGE/Rebound.app" || say "Предыдущая версия сохранена: $BACKUP/Rebound.app" >&2 fi exit 1 fi fi COMMITTED=1 say 'Если приложение сразу закрывается, сообщите текст ошибки и версию macOS.' fi