SunDrSunDr
Назад к блогу
engineering

Деплой приложений на Samsung Smart TV: bash-скрипт, который экономит часы

После 9+ лет деплоя OTT-приложений на Tizen я написал скрипт, который превращает 15-шаговый ручной процесс в одну команду. Открытый код с интерактивным меню.

Опубликовано 28 марта 2026 г.10 мин чтения
TizenSamsung TVSmart TVOTTBashDevToolsDeployment
Деплой приложений на Samsung Smart TV: bash-скрипт, который экономит часы

Если вы хотя бы раз деплоили веб-приложение на Samsung Smart TV, вы знаете эту боль. Подключиться через sdb, определить имя устройства, запаковать .wgt с нужным сертификатом, удалить старую версию, установить новую, запустить, помолиться, чтобы не упало. Каждый. Раз. После 9+ лет такого на десятках OTT-проектов — включая платформы на 80M+ зрителей — я не выдержал и написал скрипт, который автоматизирует весь процесс.

Делюсь им здесь, потому что хотел бы, чтобы кто-то поделился чем-то подобным со мной много лет назад. Если хотите понять, почему разработка под Smart TV — это вообще больно — древние браузеры, лимиты памяти, кошмар пространственной навигации — я писал об этом в статье Разработка приложений для Smart TV: о чём не предупреждают.

Что нужно подготовить заранее

Прежде чем скрипт сможет творить магию, нужно настроить три вещи на вашем компьютере и телевизоре.

1. Установите Tizen Studio. Скачайте с официальной страницы Tizen Studio. Нужны CLI-инструменты — конкретно sdb и утилита tizen. Скрипт ожидает их в ~/tizen-studio/tools/. После базовой установки откройте Package Manager, перейдите на вкладку Extension SDK и установите TV Extensions и Samsung Certificate Extension. Подробный процесс описан в гайде по установке Samsung TV SDK.

2. Создайте сертификат Samsung. Это место, где большинство спотыкается. Нужен подписной сертификат, чтобы упаковывать и устанавливать приложения на физический телевизор. Следуйте гайду Samsung по сертификатам: откройте Certificate Manager в Tizen Studio, создайте профиль именно Samsung-сертификата (не Tizen), авторизуйтесь через аккаунт Samsung Developer и зарегистрируйте DUID вашего телевизора как целевое устройство. Храните сертификат в надёжном месте — для обновлений приложения нужен тот же author-сертификат, иначе ТВ воспримет апдейт как совершенно новое приложение.

3. Включите Developer Mode на телевизоре. На самом ТВ зайдите в панель Apps, откройте App Settings и введите код 12345. Появится окно Developer Mode. Включите его, введите IP-адрес вашего компьютера и перезагрузите телевизор. После перезапуска увидите "Develop Mode" вверху панели приложений. Полный гайд — в документации Samsung по настройке устройства.

Также нужна Java 8. CLI-инструменты Tizen до сих пор зависят от Java 8, не от новых версий. На macOS установите через brew install --cask temurin8. Скрипт автоматически находит и переключается на Java 8 только для текущей сессии — системная Java остаётся нетронутой.

Что умеет скрипт

Скрипт даёт интерактивное меню на базе gum (тулкит для терминального UI, который автоматически установится, если его нет). Вам доступны 8 опций:

  1. Запустить все шаги — подключение, определение устройства, упаковка, удаление старой версии, установка и запуск. Один выбор — и готово.
  2. Подключиться к ТВ — устанавливает sdb-соединение с IP телевизора.
  3. Определить имя устройства — считывает идентификатор устройства из sdb, нужный для всех команд Tizen CLI.
  4. Упаковать приложение — подписывает директорию сборки как .wgt-файл вашим сертификатом. Автоматически чистит имя файла от пробелов.
  5. Удалить приложение — убирает текущую версию с ТВ (тихо игнорирует, если не установлено).
  6. Установить приложение — загружает .wgt на телевизор.
  7. Отладка приложения — запускает debug-сессию и пробрасывает порт для подключения Chrome DevTools.
  8. Запустить приложение — запускает в обычном режиме без отладки.

Можно также передать готовый .wgt-файл напрямую, и скрипт пропустит этап упаковки.

Конфигурация сохраняется: настроил раз — деплоишь всегда

При первом запуске скрипт спросит четыре вещи: IP телевизора, имя сертификата, package ID и путь к директории сборки (или .wgt-файлу). Package ID берётся из config.xml вашего проекта — это атрибут package из тега tizen:application, в формате AbCdEf1234.MyApp. Всё сохраняется в ~/.tizen_deploy_config, и каждый следующий запуск просто работает — без повторного ввода.

Переключились на другой проект или другой телевизор? Запустите скрипт с --clear-config, чтобы сбросить всё:

./tizen_deploy.sh --clear-config

Это удалит сохранённый конфиг и спросит всё заново.

Полный код скрипта

Вот полный исходный код. Сохраните как tizen_deploy.sh, сделайте исполняемым через chmod +x tizen_deploy.sh и запускайте.

#!/bin/bash

# PLATFORM DETECTION
OS_TYPE="$(uname -s)"
IS_MAC=false
IS_LINUX=false
IS_WIN=false

case "$OS_TYPE" in
  Darwin*) IS_MAC=true ;;
  Linux*) IS_LINUX=true ;;
  MINGW*|MSYS*|CYGWIN*) IS_WIN=true ;;
esac

# CONFIG PATH
CONFIG_PATH="$HOME/.tizen_deploy_config"
$IS_WIN && CONFIG_PATH="$HOME/_tizen_deploy_config"

# --clear-config ARG HANDLING
if [[ "$1" == "--clear-config" ]]; then
  echo "Clearing config at $CONFIG_PATH"
  rm -f "$CONFIG_PATH"
fi

# INSTALL GUM IF NOT FOUND
install_gum() {
  if command -v gum &>/dev/null; then return; fi
  echo "'gum' is not installed. Installing..."

  if $IS_MAC; then
    if ! command -v brew &>/dev/null; then
      echo "Homebrew is required on macOS." >&2
      exit 1
    fi
    brew install charmbracelet/tap/gum
  elif $IS_LINUX; then
    if command -v apt &>/dev/null; then
      sudo apt update && sudo apt install -y gum
    elif command -v dnf &>/dev/null; then
      sudo dnf install -y gum
    elif command -v pacman &>/dev/null; then
      sudo pacman -Sy gum
    else
      echo "Unsupported package manager." >&2
      exit 1
    fi
  elif $IS_WIN; then
    echo "On Windows, install gum manually:"
    echo "  scoop install gum"
    read -p "Press Enter when installed..."
  fi
}

if ! command -v tizen &>/dev/null; then
  echo "Tizen CLI not found. Install Tizen Studio first."
  exit 1
fi

install_gum

# FORCE JAVA 8 FOR TIZEN
if command -v /usr/libexec/java_home &>/dev/null; then
  JAVA_8_HOME=$(/usr/libexec/java_home -v 1.8 2>/dev/null)
  if [[ -z "$JAVA_8_HOME" ]]; then
    echo "Java 8 is required. Install: brew install --cask temurin8"
    exit 1
  fi
  export JAVA_HOME="$JAVA_8_HOME"
  export PATH="$JAVA_HOME/bin:$PATH"
fi

# LOAD / PROMPT CONFIGURATION
prompt_or_load() {
  local var_name=$1
  local prompt_msg=$2
  local current_value=$(grep "^$var_name=" "$CONFIG_PATH" 2>/dev/null | cut -d'=' -f2-)
  if [ -n "$current_value" ]; then
    eval "$var_name="$current_value""
  else
    read -p "$prompt_msg: " user_input
    echo "$var_name="$user_input"" >> "$CONFIG_PATH"
    eval "$var_name="$user_input""
  fi
}

prompt_or_load TV_IP "Enter TV IP address"
prompt_or_load CERTIFICATE_NAME "Enter Certificate/Profile name"
prompt_or_load PACKAGE_ID "Enter PACKAGE_ID (e.g., abc123.MyApp)"
prompt_or_load INPUT_PATH "Enter build dir or .wgt file path"

# Derive behavior based on INPUT_PATH
if [[ "$INPUT_PATH" == *.wgt ]]; then
  BUILD_DIR="$(dirname "$INPUT_PATH")"
  APP_NAME="$(basename "$INPUT_PATH")"
  SKIP_PACKAGE=true
else
  BUILD_DIR="$INPUT_PATH"
  APP_NAME="$(echo "$PACKAGE_ID" | cut -d. -f2).wgt"
  SKIP_PACKAGE=false
fi

SDB="$HOME/tizen-studio/tools/sdb"
TIZEN_CLI="$HOME/tizen-studio/tools/ide/bin/tizen"

TV_PORT="26101"
DEVICE_NAME=""
DEBUG_PORT=""

# STEP FUNCTIONS
connect() {
  echo "Connecting to $TV_IP:$TV_PORT..."
  CONNECT_OUTPUT=$($SDB connect "$TV_IP:$TV_PORT" 2>&1)
  echo "$CONNECT_OUTPUT" | grep -Eq "connected to|is already connected"     && echo "Connected." || { echo "Failed."; echo "$CONNECT_OUTPUT"; exit 1; }
}

resolve_device() {
  DEVICE_NAME=$($SDB devices | grep "$TV_IP:$TV_PORT" | awk '{print $3}')
  [[ -z "$DEVICE_NAME" ]] && { echo "Could not resolve device."; exit 1; }
  echo "Device: $DEVICE_NAME"
}

package_app() {
  $SKIP_PACKAGE && { echo "Skipping packaging (.wgt provided)"; return; }
  PACKAGE_OUTPUT=$($TIZEN_CLI package -s "$CERTIFICATE_NAME" -t wgt -- "$BUILD_DIR" 2>&1)
  if echo "$PACKAGE_OUTPUT" | grep -q "Package File Location"; then
    PKG_FILE=$(echo "$PACKAGE_OUTPUT" | grep "Package File Location" | sed -E 's/.*:s*//' | xargs)
    NEW_BASENAME=$(basename "$PKG_FILE" | tr -d ' ')
    mv "$PKG_FILE" "$(dirname "$PKG_FILE")/$NEW_BASENAME" 2>/dev/null
    APP_NAME="$NEW_BASENAME"
    echo "Packaged: $APP_NAME"
  else
    echo "Packaging failed."; echo "$PACKAGE_OUTPUT"; exit 1
  fi
}

uninstall_app() {
  $TIZEN_CLI uninstall -t "$DEVICE_NAME" -p "$PACKAGE_ID" 2>&1 |     grep -q "uninstall completed" && echo "Uninstalled." || echo "Not installed, skipping."
}

install_app() {
  INSTALL_OUTPUT=$($TIZEN_CLI install --name "$APP_NAME" -t "$DEVICE_NAME" -- "$BUILD_DIR" 2>&1)
  echo "$INSTALL_OUTPUT" | grep -q "successfully installed"     && echo "Installed." || { echo "Install failed."; echo "$INSTALL_OUTPUT"; exit 1; }
}

debug_app() {
  DEBUG_OUTPUT=$($SDB shell 0 debug "$PACKAGE_ID" 2>&1)
  DEBUG_PORT=$(echo "$DEBUG_OUTPUT" | grep -oE "port: [0-9]+" | awk '{print $2}')
  [[ -z "$DEBUG_PORT" ]] && { echo "Debug failed."; echo "$DEBUG_OUTPUT"; exit 1; }
  $SDB forward "tcp:$DEBUG_PORT" "tcp:$DEBUG_PORT" 2>&1
  echo "Debug on port $DEBUG_PORT"
}

run_app() {
  RUN_OUTPUT=$($TIZEN_CLI run -t "$DEVICE_NAME" -p "$PACKAGE_ID" 2>&1)
  echo "$RUN_OUTPUT" | grep -q "successfully launched"     && echo "Launched." || { echo "Launch failed."; echo "$RUN_OUTPUT"; exit 1; }
}

run_all() { connect; resolve_device; package_app; uninstall_app; install_app; run_app; }

# INTERACTIVE MENU
choice=$(gum choose   "Run All Steps (1-5, 7)"   "1. Connect to TV"   "2. Resolve device name"   "3. Package app"   "4. Uninstall app"   "5. Install app"   "6. Debug app + forward port"   "7. Run app (non-debug)"   "8. Disconnect from TV")

case "$choice" in
  "Run All Steps (1-5, 7)") run_all ;;
  "1. Connect to TV") connect ;;
  "2. Resolve device name") resolve_device ;;
  "3. Package app") package_app ;;
  "4. Uninstall app") resolve_device && uninstall_app ;;
  "5. Install app") resolve_device && install_app ;;
  "6. Debug app + forward port") resolve_device && debug_app ;;
  "7. Run app (non-debug)") resolve_device && run_app ;;
  "8. Disconnect from TV") disconnect ;;
  *) echo "Invalid selection." && exit 1 ;;
esac

echo "Done."

Как дебажить приложение через Chrome DevTools

Одна из самых полезных возможностей скрипта — удалённая отладка. Вот как подключить Chrome DevTools к приложению, работающему на телевизоре.

Шаг 1: Закройте приложение на ТВ. Если приложение уже запущено — сначала закройте его. Debug-сессия должна сама запустить приложение, она не подключится к уже работающему.

Шаг 2: Запустите debug-команду. Запустите скрипт и выберите "Debug app + forward port" из меню. Скрипт выведет номер порта — скопируйте его.

Шаг 3: Откройте Chrome DevTools для устройств. В Chrome перейдите на chrome://inspect/#devices. Убедитесь, что "Discover network targets" отмечен, затем нажмите Configure...

Страница Chrome DevTools Devices с выделенной кнопкой Configure

Шаг 4: Добавьте debug-порт. В диалоге Target discovery settings добавьте localhost:{port}, где {port} — номер, который вернул скрипт. Нажмите Done.

Настройки Target discovery с добавленным debug-портом

Шаг 5: Inspect. Ваше ТВ-приложение появится под Remote Target. Нажмите inspect на первом элементе в списке — откроется полноценное окно Chrome DevTools, подключённое к приложению на телевизоре. Можно инспектить DOM, дебажить JavaScript, профилировать производительность и смотреть сетевые запросы — точно как на обычной веб-странице.

Remote Target с приложением на ТВ и выделенной ссылкой inspect

Важные замечания и ограничения

Несколько вещей, которые стоит иметь в виду:

  • Тестировано на macOS. В скрипте есть определение платформы для Linux и Windows (Git Bash), но я полноценно тестировал только на macOS. Linux должен работать с минимальными корректировками. Windows через Git Bash/MSYS — экспериментально, результат не гарантирован.
  • Java 8 обязательна. Tizen CLI отказывается работать с Java 11+. Скрипт решает это переключением JAVA_HOME только для текущей сессии — системная Java остаётся как есть.
  • Проблемы с сертификатами — причина номер один всех ошибок. Если установка падает с ошибкой подписи, проверьте три вещи: имя сертификатного профиля должно совпадать точно, DUID телевизора должен быть зарегистрирован в distributor-сертификате, и тип сертификата имеет значение — нужен именно Samsung-сертификат (не Tizen), а уровень привилегий должен быть правильным (Partner для приложений с привилегированными API, Public для базовых приложений).
  • gum устанавливается автоматически. Интерактивное меню использует gum от Charm. Если не установлен — скрипт поставит через Homebrew (macOS), apt/dnf/pacman (Linux) или попросит установить вручную (Windows).

Разрабатываете приложение для Samsung TV?

Этот скрипт — лишь малая часть того, что нужно для выпуска продакшен-приложений на Samsung Smart TV. Настоящие вызовы — пространственная навигация, управление памятью, древние браузерные движки, интеграция DRM — вот где 9+ лет опыта разработки OTT делают разницу. Я строил стриминговые платформы на 80M+ зрителей для Tizen, WebOS, Android TV и других. Если вы делаете OTT-приложение или любой продукт для Smart TV и хотите человека, который уже решил все сложные задачи — запишитесь на бесплатный 30-минутный звонок или попробуйте калькулятор проекта для быстрой оценки.

Есть проект?

Запишитесь на бесплатный 30-минутный звонок или попробуйте калькулятор для быстрой оценки.

Aleksandr Sakov

Александр Саков

Основатель SunDr. 9+ лет разработки OTT-стриминговых платформ, мобильных приложений и веб-продуктов. Платформы, которые я построил, обслуживают 80M+ зрителей на 15+ типах устройств.