diff --git a/install.sh b/install.sh index 36c0ad1..09b73be 100644 --- a/install.sh +++ b/install.sh @@ -21,47 +21,212 @@ PORT_PROVIDED=0 SECRET_PROVIDED=0 AD_TAG_PROVIDED=0 DOMAIN_PROVIDED=0 +LANG_PROVIDED=0 ACTION="install" TARGET_VERSION="${VERSION:-latest}" +LANG_CHOICE="en" + +set_language() { + case "$1" in + ru) + L_ERR_DOMAIN_REQ="требует аргумент (домен)." + L_ERR_PORT_REQ="требует аргумент (порт)." + L_ERR_PORT_NUM="Порт должен быть числом." + L_ERR_PORT_RANGE="Порт должен быть от 1 до 65535." + L_ERR_SECRET_REQ="требует аргумент (секрет)." + L_ERR_SECRET_HEX="Секрет должен содержать только HEX символы." + L_ERR_SECRET_LEN="Секрет должен состоять ровно из 32 символов." + L_ERR_ADTAG_REQ="требует аргумент (ad_tag)." + L_ERR_UNKNOWN_OPT="Неизвестная опция:" + L_WARN_EXTRA_ARG="Игнорируется лишний аргумент:" + L_ERR_REQ_ARG="требует аргумент (1, 2, en или ru)." + L_ERR_EMPTY_VAR="не может быть пустым." + L_ERR_INV_VER="Недопустимые символы в версии." + L_ERR_INV_BIN="Недопустимые символы в BIN_NAME." + L_ERR_ROOT="Для работы скрипта требуются права root или sudo." + L_ERR_SUDO_TTY="sudo требует пароль, но терминал (TTY) не обнаружен." + L_ERR_DIR_CHECK="Ошибка: конфиг является директорией." + L_ERR_CMD_NOT_FOUND="Необходимая команда не найдена:" + L_ERR_NO_DL_TOOL="Не установлен curl или wget." + L_ERR_NO_CP_TOOL="Необходима утилита cp или install." + L_WARN_NO_NET_TOOL="Утилиты сети не найдены. Проверка порта пропущена." + L_INFO_PORT_IGNORE="Порт занят текущим процессом телеметрии. Игнорируем." + L_ERR_PORT_IN_USE="Порт уже занят другим процессом:" + L_ERR_PORT_FREE="Освободите порт или укажите другой и попробуйте снова." + L_ERR_UNSUP_ARCH="Неподдерживаемая архитектура:" + L_ERR_CREATE_GRP="Не удалось создать группу" + L_ERR_CREATE_USR="Не удалось создать пользователя" + L_ERR_MKDIR="Не удалось создать директории" + L_ERR_INSTALL_DIR="не является директорией." + L_ERR_BIN_INSTALL="Не удалось установить бинарный файл" + L_ERR_BIN_COPY="Не удалось скопировать бинарный файл" + L_ERR_BIN_EXEC="Бинарный файл не исполняемый." + L_ERR_GEN_SEC="Не удалось сгенерировать секрет." + L_INFO_CONF_EXISTS="Конфиг уже существует. Обновление параметров..." + L_INFO_UPD_PORT="Обновлен порт:" + L_INFO_UPD_SEC="Обновлен секрет для пользователя 'hello'" + L_INFO_UPD_DOM="Обновлен tls_domain:" + L_INFO_UPD_TAG="Обновлен ad_tag" + L_ERR_CONF_INST="Не удалось установить конфиг" + L_INFO_CONF_OK="Конфиг успешно создан." + L_INFO_CONF_SEC="Настроен секрет для пользователя 'hello':" + L_WARN_SVC_FAIL="Не удалось запустить службу" + L_INFO_MANUAL_START="Менеджер служб не найден. Запустите вручную:" + L_INFO_UNINST_START="Начинается удаление" + L_U_STAGE_1=">>> Этап 1: Остановка служб" + L_U_STAGE_2=">>> Этап 2: Удаление конфигурации службы" + L_U_STAGE_3=">>> Этап 3: Завершение процессов пользователя" + L_U_STAGE_4=">>> Этап 4: Удаление бинарного файла" + L_U_STAGE_5=">>> Этап 5: Полная очистка (конфиг, данные, пользователь)" + L_INFO_KEEP_CONF="Примечание: Конфигурация сохранена. Используйте 'purge' для очистки." + L_INFO_I_START="Начинается установка" + L_I_STAGE_1=">>> Этап 1: Проверка окружения и зависимостей" + L_I_STAGE_1_5=">>> Этап 1.5: Интерактивная настройка" + L_I_PROMPT_DOM="\nПожалуйста, укажите домен TLS\nНажмите Enter, чтобы оставить по умолчанию [%s]: " + L_WARN_NO_TTY="Интерактивный режим недоступен (нет TTY). Используется:" + L_I_STAGE_2=">>> Этап 2: Загрузка архива" + L_ERR_TMP_DIR="Не удалось создать временную директорию" + L_ERR_TMP_INV="Временная директория недействительна" + L_INFO_FALLBACK="Сборка x86_64-v3 не найдена, откат к стандартной x86_64..." + L_ERR_DL_FAIL="Ошибка загрузки архива" + L_I_STAGE_3=">>> Этап 3: Распаковка архива" + L_ERR_EXTRACT="Ошибка распаковки архива." + L_ERR_BIN_NOT_FOUND="Бинарный файл не найден в архиве" + L_I_STAGE_4=">>> Этап 4: Настройка окружения (Юзер, Группа, Папки)" + L_I_STAGE_5=">>> Этап 5: Установка бинарного файла" + L_I_STAGE_6=">>> Этап 6: Генерация/Обновление конфигурации" + L_I_STAGE_7=">>> Этап 7: Установка и запуск службы" + L_OUT_WARN_H="УСТАНОВКА ЗАВЕРШЕНА С ПРЕДУПРЕЖДЕНИЯМИ" + L_OUT_WARN_D="Служба установлена, но не запустилась.\nПожалуйста, проверьте логи.\n" + L_OUT_SUCC_H="УСТАНОВКА УСПЕШНО ЗАВЕРШЕНА" + L_OUT_UNINST_H="УДАЛЕНИЕ ЗАВЕРШЕНО" + L_OUT_LINK="Ваша ссылка для подключения к Telegram Proxy:\n" + ;; + *) + L_ERR_DOMAIN_REQ="requires a domain argument." + L_ERR_PORT_REQ="requires a port argument." + L_ERR_PORT_NUM="Port must be a valid number." + L_ERR_PORT_RANGE="Port must be between 1 and 65535." + L_ERR_SECRET_REQ="requires a secret argument." + L_ERR_SECRET_HEX="Secret must contain only hex characters." + L_ERR_SECRET_LEN="Secret must be exactly 32 chars." + L_ERR_ADTAG_REQ="requires an ad_tag argument." + L_ERR_UNKNOWN_OPT="Unknown option:" + L_WARN_EXTRA_ARG="Ignoring extra argument:" + L_ERR_REQ_ARG="requires an argument (1, 2, en, ru)." + L_ERR_EMPTY_VAR="cannot be empty." + L_ERR_INV_VER="Invalid characters in version." + L_ERR_INV_BIN="Invalid characters in BIN_NAME." + L_ERR_ROOT="This script requires root or sudo." + L_ERR_SUDO_TTY="sudo requires a password, but no TTY detected." + L_ERR_DIR_CHECK="Safety check failed: Config is a directory." + L_ERR_CMD_NOT_FOUND="Required command not found:" + L_ERR_NO_DL_TOOL="Neither curl nor wget is installed." + L_ERR_NO_CP_TOOL="Need cp or install." + L_WARN_NO_NET_TOOL="Network tools not found. Skipping port check." + L_INFO_PORT_IGNORE="Port is in use by telemt. Ignoring as it will be restarted." + L_ERR_PORT_IN_USE="Port is already in use by another process:" + L_ERR_PORT_FREE="Please free the port or change it and try again." + L_ERR_UNSUP_ARCH="Unsupported architecture:" + L_ERR_CREATE_GRP="Cannot create group" + L_ERR_CREATE_USR="Cannot create user" + L_ERR_MKDIR="Failed to create directories" + L_ERR_INSTALL_DIR="is not a directory." + L_ERR_BIN_INSTALL="Failed to install binary" + L_ERR_BIN_COPY="Failed to copy binary" + L_ERR_BIN_EXEC="Binary not executable." + L_ERR_GEN_SEC="Failed to generate secret." + L_INFO_CONF_EXISTS="Config already exists. Updating parameters..." + L_INFO_UPD_PORT="Updated port:" + L_INFO_UPD_SEC="Updated secret for user 'hello'" + L_INFO_UPD_DOM="Updated tls_domain:" + L_INFO_UPD_TAG="Updated ad_tag" + L_ERR_CONF_INST="Failed to install config" + L_INFO_CONF_OK="Config created successfully." + L_INFO_CONF_SEC="Configured secret for user 'hello':" + L_WARN_SVC_FAIL="Failed to start service" + L_INFO_MANUAL_START="Service manager not found. Start manually:" + L_INFO_UNINST_START="Starting uninstallation of" + L_U_STAGE_1=">>> Stage 1: Stopping services" + L_U_STAGE_2=">>> Stage 2: Removing service configuration" + L_U_STAGE_3=">>> Stage 3: Terminating user processes" + L_U_STAGE_4=">>> Stage 4: Removing binary" + L_U_STAGE_5=">>> Stage 5: Purging configuration, data, and user" + L_INFO_KEEP_CONF="Note: Configuration kept. Run with 'purge' to remove completely." + L_INFO_I_START="Starting installation of" + L_I_STAGE_1=">>> Stage 1: Verifying environment and dependencies" + L_I_STAGE_1_5=">>> Stage 1.5: Interactive Setup" + L_I_PROMPT_DOM="\nPlease specify the TLS Domain\nPress Enter to keep default [%s]: " + L_WARN_NO_TTY="Interactive mode unavailable (no TTY). Using:" + L_I_STAGE_2=">>> Stage 2: Downloading archive" + L_ERR_TMP_DIR="Temp directory creation failed" + L_ERR_TMP_INV="Temp directory is invalid or was not created" + L_INFO_FALLBACK="x86_64-v3 build not found, falling back to standard x86_64..." + L_ERR_DL_FAIL="Download failed" + L_I_STAGE_3=">>> Stage 3: Extracting archive" + L_ERR_EXTRACT="Extraction failed." + L_ERR_BIN_NOT_FOUND="Binary not found in archive" + L_I_STAGE_4=">>> Stage 4: Setting up environment (User, Group, Directories)" + L_I_STAGE_5=">>> Stage 5: Installing binary" + L_I_STAGE_6=">>> Stage 6: Generating/Updating configuration" + L_I_STAGE_7=">>> Stage 7: Installing and starting service" + L_OUT_WARN_H="INSTALLATION COMPLETED WITH WARNINGS" + L_OUT_WARN_D="The service was installed but failed to start.\nPlease check the logs to determine the issue.\n" + L_OUT_SUCC_H="INSTALLATION SUCCESS" + L_OUT_UNINST_H="UNINSTALLATION COMPLETE" + L_OUT_LINK="Your Telegram Proxy connection link:\n" + ;; + esac +} + +set_language "$LANG_CHOICE" while [ $# -gt 0 ]; do case "$1" in -h|--help) ACTION="help"; shift ;; + -l|--lang) + if [ "$#" -lt 2 ] || [ -z "$2" ]; then + printf '[ERROR] %s %s\n' "$1" "$L_ERR_REQ_ARG" >&2; exit 1 + fi + case "$2" in + ru|2) LANG_CHOICE="ru"; set_language "$LANG_CHOICE"; LANG_PROVIDED=1 ;; + en|1) LANG_CHOICE="en"; set_language "$LANG_CHOICE"; LANG_PROVIDED=1 ;; + *) printf '[ERROR] %s %s\n' "$1" "$L_ERR_REQ_ARG" >&2; exit 1 ;; + esac + shift 2 ;; -d|--domain) if [ "$#" -lt 2 ] || [ -z "$2" ]; then - printf '[ERROR] %s requires a domain argument.\n' "$1" >&2 - exit 1 + printf '[ERROR] %s %s\n' "$1" "$L_ERR_DOMAIN_REQ" >&2; exit 1 fi TLS_DOMAIN="$2"; DOMAIN_PROVIDED=1; shift 2 ;; -p|--port) if [ "$#" -lt 2 ] || [ -z "$2" ]; then - printf '[ERROR] %s requires a port argument.\n' "$1" >&2; exit 1 + printf '[ERROR] %s %s\n' "$1" "$L_ERR_PORT_REQ" >&2; exit 1 fi case "$2" in - *[!0-9]*) printf '[ERROR] Port must be a valid number.\n' >&2; exit 1 ;; + *[!0-9]*) printf '[ERROR] %s\n' "$L_ERR_PORT_NUM" >&2; exit 1 ;; esac port_num="$(printf '%s\n' "$2" | sed 's/^0*//')" [ -z "$port_num" ] && port_num="0" if [ "${#port_num}" -gt 5 ] || [ "$port_num" -lt 1 ] || [ "$port_num" -gt 65535 ]; then - printf '[ERROR] Port must be between 1 and 65535.\n' >&2; exit 1 + printf '[ERROR] %s\n' "$L_ERR_PORT_RANGE" >&2; exit 1 fi SERVER_PORT="$port_num"; PORT_PROVIDED=1; shift 2 ;; -s|--secret) if [ "$#" -lt 2 ] || [ -z "$2" ]; then - printf '[ERROR] %s requires a secret argument.\n' "$1" >&2; exit 1 + printf '[ERROR] %s %s\n' "$1" "$L_ERR_SECRET_REQ" >&2; exit 1 fi case "$2" in - *[!0-9a-fA-F]*) - printf '[ERROR] Secret must contain only hex characters.\n' >&2; exit 1 ;; + *[!0-9a-fA-F]*) printf '[ERROR] %s\n' "$L_ERR_SECRET_HEX" >&2; exit 1 ;; esac if [ "${#2}" -ne 32 ]; then - printf '[ERROR] Secret must be exactly 32 chars.\n' >&2; exit 1 + printf '[ERROR] %s\n' "$L_ERR_SECRET_LEN" >&2; exit 1 fi USER_SECRET="$2"; SECRET_PROVIDED=1; shift 2 ;; -a|--ad-tag|--ad_tag) if [ "$#" -lt 2 ] || [ -z "$2" ]; then - printf '[ERROR] %s requires an ad_tag argument.\n' "$1" >&2; exit 1 + printf '[ERROR] %s %s\n' "$1" "$L_ERR_ADTAG_REQ" >&2; exit 1 fi AD_TAG="$2"; AD_TAG_PROVIDED=1; shift 2 ;; uninstall|--uninstall) @@ -69,14 +234,31 @@ while [ $# -gt 0 ]; do shift ;; purge|--purge) ACTION="purge"; shift ;; install|--install) ACTION="install"; shift ;; - -*) printf '[ERROR] Unknown option: %s\n' "$1" >&2; exit 1 ;; + -*) printf '[ERROR] %s %s\n' "$L_ERR_UNKNOWN_OPT" "$1" >&2; exit 1 ;; *) if [ "$ACTION" = "install" ]; then TARGET_VERSION="$1" - else printf '[WARNING] Ignoring extra argument: %s\n' "$1" >&2; fi + else printf '[WARNING] %s %s\n' "$L_WARN_EXTRA_ARG" "$1" >&2; fi shift ;; esac done +if [ "$ACTION" != "help" ] && [ "$LANG_PROVIDED" -eq 0 ]; then + if [ -t 0 ] || [ -c /dev/tty ]; then + printf "\nSelect language / Выберите язык:\n" + printf " 1) English (default)\n" + printf " 2) Русский\n" + printf "Your choice / Ваш выбор [1/2]: " + read -r input_lang | install | uninstall | purge ] [ options ]" - say " Install specific version (e.g. 3.3.15, default: latest)" - say " install Install the latest version" - say " uninstall Remove the binary and service" - say " purge Remove everything including configuration, data, and user" - say "" - say "Options:" - say " -d, --domain Set TLS domain (default: petrovich.ru)" - say " -p, --port Set server port (default: 443)" - say " -s, --secret Set specific user secret (32 hex characters)" - say " -a, --ad-tag Set ad_tag" + if [ "$LANG_CHOICE" = "ru" ]; then + say "Использование: $0 [ <версия> | install | uninstall | purge ] [ опции ]" + say " <версия> Установить конкретную версию (например, 3.3.15, по умолчанию: latest)" + say " install Установить последнюю версию" + say " uninstall Удалить бинарный файл и службу" + say " purge Полностью удалить вместе с конфигурацией, данными и пользователем" + say "" + say "Опции:" + say " -d, --domain Указать домен TLS (по умолчанию: petrovich.ru)" + say " -p, --port Указать порт сервера (по умолчанию: 443)" + say " -s, --secret Указать секрет пользователя (32 hex символа)" + say " -a, --ad-tag Указать ad_tag" + say " -l, --lang Выбрать язык вывода (1/en или 2/ru)" + else + say "Usage: $0 [ | install | uninstall | purge ] [ options ]" + say " Install specific version (e.g. 3.3.15, default: latest)" + say " install Install the latest version" + say " uninstall Remove the binary and service" + say " purge Remove everything including configuration, data, and user" + say "" + say "Options:" + say " -d, --domain Set TLS domain (default: petrovich.ru)" + say " -p, --port Set server port (default: 443)" + say " -s, --secret Set specific user secret (32 hex characters)" + say " -a, --ad-tag Set ad_tag" + say " -l, --lang Set output language (1/en or 2/ru)" + fi exit 0 } @@ -171,17 +369,13 @@ is_config_exists() { } verify_common() { - [ -n "$BIN_NAME" ] || die "BIN_NAME cannot be empty." - [ -n "$INSTALL_DIR" ] || die "INSTALL_DIR cannot be empty." - [ -n "$CONFIG_DIR" ] || die "CONFIG_DIR cannot be empty." - [ -n "$CONFIG_FILE" ] || die "CONFIG_FILE cannot be empty." + [ -n "$BIN_NAME" ] || die "BIN_NAME $L_ERR_EMPTY_VAR" + [ -n "$INSTALL_DIR" ] || die "INSTALL_DIR $L_ERR_EMPTY_VAR" + [ -n "$CONFIG_DIR" ] || die "CONFIG_DIR $L_ERR_EMPTY_VAR" + [ -n "$CONFIG_FILE" ] || die "CONFIG_FILE $L_ERR_EMPTY_VAR" - case "${INSTALL_DIR}${CONFIG_DIR}${WORK_DIR}${CONFIG_FILE}" in - *[!a-zA-Z0-9_./-]*) die "Invalid characters in paths." ;; - esac - - case "$TARGET_VERSION" in *[!a-zA-Z0-9_.-]*) die "Invalid characters in version." ;; esac - case "$BIN_NAME" in *[!a-zA-Z0-9_-]*) die "Invalid characters in BIN_NAME." ;; esac + case "$TARGET_VERSION" in *[!a-zA-Z0-9_.-]*) die "$L_ERR_INV_VER" ;; esac + case "$BIN_NAME" in *[!a-zA-Z0-9_-]*) die "$L_ERR_INV_BIN" ;; esac INSTALL_DIR="$(get_realpath "$INSTALL_DIR")" CONFIG_DIR="$(get_realpath "$CONFIG_DIR")" @@ -195,42 +389,42 @@ verify_common() { if [ "$(id -u)" -eq 0 ]; then SUDO="" else - command -v sudo >/dev/null 2>&1 || die "This script requires root or sudo." + command -v sudo >/dev/null 2>&1 || die "$L_ERR_ROOT" SUDO="sudo" if ! sudo -n true 2>/dev/null; then if ! [ -t 0 ]; then - die "sudo requires a password, but no TTY detected." + die "$L_ERR_SUDO_TTY" fi fi fi if [ -n "$SUDO" ]; then if $SUDO sh -c '[ -d "$1" ]' _ "$CONFIG_FILE"; then - die "Safety check failed: CONFIG_FILE '$CONFIG_FILE' is a directory." + die "$L_ERR_DIR_CHECK" fi elif [ -d "$CONFIG_FILE" ]; then - die "Safety check failed: CONFIG_FILE '$CONFIG_FILE' is a directory." + die "$L_ERR_DIR_CHECK" fi for cmd in id uname awk grep find rm chown chmod mv mktemp mkdir tr dd sed ps head sleep cat tar gzip; do - command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd" + command -v "$cmd" >/dev/null 2>&1 || die "$L_ERR_CMD_NOT_FOUND $cmd" done } verify_install_deps() { - command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 || die "Neither curl nor wget is installed." - command -v cp >/dev/null 2>&1 || command -v install >/dev/null 2>&1 || die "Need cp or install" + command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 || die "$L_ERR_NO_DL_TOOL" + command -v cp >/dev/null 2>&1 || command -v install >/dev/null 2>&1 || die "$L_ERR_NO_CP_TOOL" - if ! command -v setcap >/dev/null 2>&1 || ! command -v conntrack >/dev/null 2>&1; then + if ! command -v setcap >/dev/null 2>&1; then if command -v apk >/dev/null 2>&1; then - $SUDO apk add --no-cache libcap-utils libcap conntrack-tools >/dev/null 2>&1 || true + $SUDO apk add --no-cache libcap-utils libcap >/dev/null 2>&1 || true elif command -v apt-get >/dev/null 2>&1; then - $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin conntrack >/dev/null 2>&1 || { + $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin >/dev/null 2>&1 || { $SUDO env DEBIAN_FRONTEND=noninteractive apt-get update -q >/dev/null 2>&1 || true - $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin conntrack >/dev/null 2>&1 || true + $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin >/dev/null 2>&1 || true } - elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y -q libcap conntrack-tools >/dev/null 2>&1 || true - elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y -q libcap conntrack-tools >/dev/null 2>&1 || true + elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y -q libcap >/dev/null 2>&1 || true + elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y -q libcap >/dev/null 2>&1 || true fi fi } @@ -245,17 +439,17 @@ check_port_availability() { elif command -v lsof >/dev/null 2>&1; then port_info=$($SUDO lsof -i :${SERVER_PORT} 2>/dev/null | grep LISTEN || true) else - say "[WARNING] Network diagnostic tools (ss, netstat, lsof) not found. Skipping port check." + say "[WARNING] $L_WARN_NO_NET_TOOL" return 0 fi if [ -n "$port_info" ]; then if printf '%s\n' "$port_info" | grep -q "${BIN_NAME}"; then - say " -> Port ${SERVER_PORT} is in use by ${BIN_NAME}. Ignoring as it will be restarted." + say " -> $L_INFO_PORT_IGNORE" else - say "[ERROR] Port ${SERVER_PORT} is already in use by another process:" + say "[ERROR] $L_ERR_PORT_IN_USE $SERVER_PORT:" printf ' %s\n' "$port_info" - die "Please free the port ${SERVER_PORT} or change it and try again." + die "$L_ERR_PORT_FREE" fi fi } @@ -271,7 +465,7 @@ detect_arch() { fi ;; aarch64|arm64) echo "aarch64" ;; - *) die "Unsupported architecture: $sys_arch" ;; + *) die "$L_ERR_UNSUP_ARCH $sys_arch" ;; esac } @@ -295,7 +489,7 @@ ensure_user_group() { if ! check_os_entity group telemt; then if command -v groupadd >/dev/null 2>&1; then $SUDO groupadd -r telemt elif command -v addgroup >/dev/null 2>&1; then $SUDO addgroup -S telemt - else die "Cannot create group"; fi + else die "$L_ERR_CREATE_GRP" ; fi fi if ! check_os_entity passwd telemt; then @@ -307,12 +501,12 @@ ensure_user_group() { else $SUDO adduser --system --home "$WORK_DIR" --shell "$nologin_bin" --no-create-home --ingroup telemt --disabled-password telemt fi - else die "Cannot create user"; fi + else die "$L_ERR_CREATE_USR"; fi fi } setup_dirs() { - $SUDO mkdir -p "$WORK_DIR" "$CONFIG_DIR" "$CONFIG_PARENT_DIR" || die "Failed to create directories" + $SUDO mkdir -p "$WORK_DIR" "$CONFIG_DIR" "$CONFIG_PARENT_DIR" || die "$L_ERR_MKDIR" $SUDO chown telemt:telemt "$WORK_DIR" && $SUDO chmod 750 "$WORK_DIR" $SUDO chown telemt:telemt "$CONFIG_DIR" && $SUDO chmod 750 "$CONFIG_DIR" @@ -334,20 +528,20 @@ stop_service() { install_binary() { bin_src="$1"; bin_dst="$2" if [ -e "$INSTALL_DIR" ] && [ ! -d "$INSTALL_DIR" ]; then - die "'$INSTALL_DIR' is not a directory." + die "'$INSTALL_DIR' $L_ERR_INSTALL_DIR" fi - $SUDO mkdir -p "$INSTALL_DIR" || die "Failed to create install directory" + $SUDO mkdir -p "$INSTALL_DIR" || die "$L_ERR_MKDIR" $SUDO rm -f "$bin_dst" 2>/dev/null || true if command -v install >/dev/null 2>&1; then - $SUDO install -m 0755 "$bin_src" "$bin_dst" || die "Failed to install binary" + $SUDO install -m 0755 "$bin_src" "$bin_dst" || die "$L_ERR_BIN_INSTALL" else - $SUDO cp "$bin_src" "$bin_dst" && $SUDO chmod 0755 "$bin_dst" || die "Failed to copy binary" + $SUDO cp "$bin_src" "$bin_dst" && $SUDO chmod 0755 "$bin_dst" || die "$L_ERR_BIN_COPY" fi - $SUDO sh -c '[ -x "$1" ]' _ "$bin_dst" || die "Binary not executable: $bin_dst" + $SUDO sh -c '[ -x "$1" ]' _ "$bin_dst" || die "$L_ERR_BIN_EXEC $bin_dst" if command -v setcap >/dev/null 2>&1; then $SUDO setcap cap_net_bind_service,cap_net_admin=+ep "$bin_dst" 2>/dev/null || true @@ -404,40 +598,32 @@ EOF install_config() { if is_config_exists; then - say " -> Config already exists at $CONFIG_FILE. Updating parameters..." + say " -> $L_INFO_CONF_EXISTS" tmp_conf="${TEMP_DIR}/config.tmp" $SUDO cat "$CONFIG_FILE" > "$tmp_conf" escaped_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')" - export AWK_PORT="$SERVER_PORT" - export AWK_SECRET="$USER_SECRET" - export AWK_DOMAIN="$escaped_domain" - export AWK_AD_TAG="$AD_TAG" - export AWK_FLAG_P="$PORT_PROVIDED" - export AWK_FLAG_S="$SECRET_PROVIDED" - export AWK_FLAG_D="$DOMAIN_PROVIDED" - export AWK_FLAG_A="$AD_TAG_PROVIDED" - - awk ' + awk -v port="$SERVER_PORT" -v secret="$USER_SECRET" -v domain="$escaped_domain" -v ad_tag="$AD_TAG" \ + -v flag_p="$PORT_PROVIDED" -v flag_s="$SECRET_PROVIDED" -v flag_d="$DOMAIN_PROVIDED" -v flag_a="$AD_TAG_PROVIDED" ' BEGIN { ad_tag_handled = 0 } - ENVIRON["AWK_FLAG_P"] == "1" && /^[ \t]*port[ \t]*=/ { print "port = " ENVIRON["AWK_PORT"]; next } - ENVIRON["AWK_FLAG_S"] == "1" && /^[ \t]*hello[ \t]*=/ { print "hello = \"" ENVIRON["AWK_SECRET"] "\""; next } - ENVIRON["AWK_FLAG_D"] == "1" && /^[ \t]*tls_domain[ \t]*=/ { print "tls_domain = \"" ENVIRON["AWK_DOMAIN"] "\""; next } + flag_p == "1" && /^[ \t]*port[ \t]*=/ { print "port = " port; next } + flag_s == "1" && /^[ \t]*hello[ \t]*=/ { print "hello = \"" secret "\""; next } + flag_d == "1" && /^[ \t]*tls_domain[ \t]*=/ { print "tls_domain = \"" domain "\""; next } - ENVIRON["AWK_FLAG_A"] == "1" && /^[ \t]*ad_tag[ \t]*=/ { + flag_a == "1" && /^[ \t]*ad_tag[ \t]*=/ { if (!ad_tag_handled) { - print "ad_tag = \"" ENVIRON["AWK_AD_TAG"] "\""; + print "ad_tag = \"" ad_tag "\""; ad_tag_handled = 1; } next } - ENVIRON["AWK_FLAG_A"] == "1" && /^\[general\]/ { + flag_a == "1" && /^\[general\]/ { print; if (!ad_tag_handled) { - print "ad_tag = \"" ENVIRON["AWK_AD_TAG"] "\""; + print "ad_tag = \"" ad_tag "\""; ad_tag_handled = 1; } next @@ -446,10 +632,10 @@ install_config() { { print } ' "$tmp_conf" > "${tmp_conf}.new" && mv "${tmp_conf}.new" "$tmp_conf" - [ "$PORT_PROVIDED" -eq 1 ] && say " -> Updated port: $SERVER_PORT" - [ "$SECRET_PROVIDED" -eq 1 ] && say " -> Updated secret for user 'hello'" - [ "$DOMAIN_PROVIDED" -eq 1 ] && say " -> Updated tls_domain: $TLS_DOMAIN" - [ "$AD_TAG_PROVIDED" -eq 1 ] && say " -> Updated ad_tag" + [ "$PORT_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_PORT $SERVER_PORT" + [ "$SECRET_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_SEC" + [ "$DOMAIN_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_DOM $TLS_DOMAIN" + [ "$AD_TAG_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_TAG" write_root "$CONFIG_FILE" < "$tmp_conf" rm -f "$tmp_conf" @@ -457,14 +643,14 @@ install_config() { fi if [ -z "$USER_SECRET" ]; then - USER_SECRET="$(generate_secret)" || die "Failed to generate secret." + USER_SECRET="$(generate_secret)" || die "$L_ERR_GEN_SEC" fi - generate_config_content "$USER_SECRET" "$AD_TAG" | write_root "$CONFIG_FILE" || die "Failed to install config" + generate_config_content "$USER_SECRET" "$AD_TAG" | write_root "$CONFIG_FILE" || die "$L_ERR_CONF_INST" $SUDO chown root:telemt "$CONFIG_FILE" && $SUDO chmod 640 "$CONFIG_FILE" - say " -> Config created successfully." - say " -> Configured secret for user 'hello': $USER_SECRET" + say " -> $L_INFO_CONF_OK" + say " -> $L_INFO_CONF_SEC $USER_SECRET" } generate_systemd_content() { @@ -517,7 +703,7 @@ install_service() { $SUDO systemctl enable "$SERVICE_NAME" || true if ! $SUDO systemctl start "$SERVICE_NAME"; then - say "[WARNING] Failed to start service" + say "[WARNING] $L_WARN_SVC_FAIL" SERVICE_START_FAILED=1 fi elif [ "$svc" = "openrc" ]; then @@ -527,15 +713,15 @@ install_service() { $SUDO rc-update add "$SERVICE_NAME" default 2>/dev/null || true if ! $SUDO rc-service "$SERVICE_NAME" start 2>/dev/null; then - say "[WARNING] Failed to start service" + say "[WARNING] $L_WARN_SVC_FAIL" SERVICE_START_FAILED=1 fi else cmd="\"${INSTALL_DIR}/${BIN_NAME}\" \"${CONFIG_FILE}\"" if [ -n "$SUDO" ]; then - say " -> Service manager not found. Start manually: sudo -u telemt $cmd" + say " -> $L_INFO_MANUAL_START sudo -u telemt $cmd" else - say " -> Service manager not found. Start manually: su -s /bin/sh telemt -c '$cmd'" + say " -> $L_INFO_MANUAL_START su -s /bin/sh telemt -c '$cmd'" fi fi } @@ -566,12 +752,12 @@ kill_user_procs() { } uninstall() { - say "Starting uninstallation of $BIN_NAME..." + say "$L_INFO_UNINST_START $BIN_NAME..." - say ">>> Stage 1: Stopping services" + say "$L_U_STAGE_1" stop_service - say ">>> Stage 2: Removing service configuration" + say "$L_U_STAGE_2" svc="$(get_svc_mgr)" if [ "$svc" = "systemd" ]; then $SUDO systemctl disable "$SERVICE_NAME" 2>/dev/null || true @@ -582,28 +768,30 @@ uninstall() { $SUDO rm -f "/etc/init.d/${SERVICE_NAME}" fi - say ">>> Stage 3: Terminating user processes" + say "$L_U_STAGE_3" kill_user_procs - say ">>> Stage 4: Removing binary" + say "$L_U_STAGE_4" $SUDO rm -f "${INSTALL_DIR}/${BIN_NAME}" if [ "$ACTION" = "purge" ]; then - say ">>> Stage 5: Purging configuration, data, and user" + say "$L_U_STAGE_5" $SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR" $SUDO rm -f "$CONFIG_FILE" - sleep 1 - $SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true + + if check_os_entity passwd telemt; then + $SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true + fi if check_os_entity group telemt; then $SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true fi else - say "Note: Configuration and user kept. Run with 'purge' to remove completely." + say "$L_INFO_KEEP_CONF" fi printf '\n====================================================================\n' - printf ' UNINSTALLATION COMPLETE\n' + printf ' %s\n' "$L_OUT_UNINST_H" printf '====================================================================\n\n' exit 0 } @@ -612,21 +800,45 @@ case "$ACTION" in help) show_help ;; uninstall|purge) verify_common; uninstall ;; install) - say "Starting installation of $BIN_NAME (Version: $TARGET_VERSION)" + say "$L_INFO_I_START $BIN_NAME (Version: $TARGET_VERSION)" - say ">>> Stage 1: Verifying environment and dependencies" + say "$L_I_STAGE_1" verify_common verify_install_deps - if is_config_exists && [ "$PORT_PROVIDED" -eq 0 ]; then + if is_config_exists; then ext_port="$($SUDO awk -F'=' '/^[ \t]*port[ \t]*=/ {gsub(/[^0-9]/, "", $2); print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)" - if [ -n "$ext_port" ]; then + if [ -n "$ext_port" ] && [ "$PORT_PROVIDED" -eq 0 ]; then SERVER_PORT="$ext_port" fi + + ext_secret="$($SUDO awk -F'"' '/^[ \t]*hello[ \t]*=/ {print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)" + if [ -n "$ext_secret" ] && [ "$SECRET_PROVIDED" -eq 0 ]; then + USER_SECRET="$ext_secret" + fi + + ext_domain="$($SUDO awk -F'"' '/^[ \t]*tls_domain[ \t]*=/ {print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)" + if [ -n "$ext_domain" ] && [ "$DOMAIN_PROVIDED" -eq 0 ]; then + TLS_DOMAIN="$ext_domain" + fi fi check_port_availability + if [ "$DOMAIN_PROVIDED" -eq 0 ]; then + say "$L_I_STAGE_1_5" + if [ -t 0 ] || [ -c /dev/tty ]; then + printf "$L_I_PROMPT_DOM" "$TLS_DOMAIN" + read -r input_domain >> Stage 2: Downloading archive" - TEMP_DIR="$(mktemp -d)" || die "Temp directory creation failed" + say "$L_I_STAGE_2" + TEMP_DIR="$(mktemp -d)" || die "$L_ERR_TMP_DIR" if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR" ]; then - die "Temp directory is invalid or was not created" + die "$L_ERR_TMP_INV" fi if ! fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}"; then if [ "$ARCH" = "x86_64-v3" ]; then - say " -> x86_64-v3 build not found, falling back to standard x86_64..." + say " -> $L_INFO_FALLBACK" ARCH="x86_64" FILE_NAME="${BIN_NAME}-${ARCH}-linux-${LIBC}.tar.gz" if [ "$TARGET_VERSION" = "latest" ]; then @@ -656,64 +868,58 @@ case "$ACTION" in else DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}" fi - fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}" || die "Download failed" + fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}" || die "$L_ERR_DL_FAIL" else - die "Download failed" + die "$L_ERR_DL_FAIL" fi fi - say ">>> Stage 3: Extracting archive" + say "$L_I_STAGE_3" if ! gzip -dc "${TEMP_DIR}/${FILE_NAME}" | tar -xf - -C "$TEMP_DIR" 2>/dev/null; then - die "Extraction failed (downloaded archive might be invalid or 404)." + die "$L_ERR_EXTRACT" fi EXTRACTED_BIN="$(find "$TEMP_DIR" -type f -name "$BIN_NAME" -print 2>/dev/null | head -n 1 || true)" - [ -n "$EXTRACTED_BIN" ] || die "Binary '$BIN_NAME' not found in archive" + [ -n "$EXTRACTED_BIN" ] || die "$L_ERR_BIN_NOT_FOUND" - say ">>> Stage 4: Setting up environment (User, Group, Directories)" + say "$L_I_STAGE_4" ensure_user_group; setup_dirs; stop_service - say ">>> Stage 5: Installing binary" + say "$L_I_STAGE_5" install_binary "$EXTRACTED_BIN" "${INSTALL_DIR}/${BIN_NAME}" - say ">>> Stage 6: Generating/Updating configuration" + say "$L_I_STAGE_6" install_config - say ">>> Stage 7: Installing and starting service" + say "$L_I_STAGE_7" install_service if [ "${SERVICE_START_FAILED:-0}" -eq 1 ]; then printf '\n====================================================================\n' - printf ' INSTALLATION COMPLETED WITH WARNINGS\n' + printf ' %s\n' "$L_OUT_WARN_H" printf '====================================================================\n\n' - printf 'The service was installed but failed to start automatically.\n' - printf 'Please check the logs to determine the issue.\n\n' + printf '%b' "$L_OUT_WARN_D" else printf '\n====================================================================\n' - printf ' INSTALLATION SUCCESS\n' + printf ' %s\n' "$L_OUT_SUCC_H" printf '====================================================================\n\n' fi - svc="$(get_svc_mgr)" - if [ "$svc" = "systemd" ]; then - printf 'To check the status of your proxy service, run:\n' - printf ' systemctl status %s\n\n' "$SERVICE_NAME" - elif [ "$svc" = "openrc" ]; then - printf 'To check the status of your proxy service, run:\n' - printf ' rc-service %s status\n\n' "$SERVICE_NAME" - fi + SERVER_IP="" + if command -v curl >/dev/null 2>&1; then SERVER_IP="$(curl -s4 -m 3 ifconfig.me 2>/dev/null || curl -s4 -m 3 api.ipify.org 2>/dev/null || true)" + elif command -v wget >/dev/null 2>&1; then SERVER_IP="$(wget -qO- -T 3 ifconfig.me 2>/dev/null || wget -qO- -T 3 api.ipify.org 2>/dev/null || true)"; fi + [ -z "$SERVER_IP" ] && SERVER_IP="" + + if command -v xxd >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | xxd -p | tr -d '\n')" + elif command -v hexdump >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | hexdump -v -e '/1 "%02x"')" + elif command -v od >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | od -A n -t x1 | tr -d ' \n')" + else HEX_DOMAIN=""; fi - API_LISTEN="$($SUDO awk -F'"' '/^[ \t]*listen[ \t]*=/ {print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)" - API_LISTEN="${API_LISTEN:-127.0.0.1:9091}" + CLIENT_SECRET="ee${USER_SECRET}${HEX_DOMAIN}" - printf 'To get your user connection links (for Telegram), run:\n' - if command -v jq >/dev/null 2>&1; then - printf ' curl -s http://%s/v1/users | jq -r '\''.data[]? | "User: \\(.username)\\n\\(.links.tls[0] // empty)\\n"'\''\n' "$API_LISTEN" - else - printf ' curl -s http://%s/v1/users\n' "$API_LISTEN" - printf ' (Tip: Install '\''jq'\'' for a much cleaner output)\n' - fi + printf '%b\n' "$L_OUT_LINK" + printf ' tg://proxy?server=%s&port=%s&secret=%s\n\n' "$SERVER_IP" "$SERVER_PORT" "$CLIENT_SECRET" - printf '\n====================================================================\n' + printf '====================================================================\n' ;; esac