From c47495d6713f83d937a98c0272f2c0bbfc4ce176 Mon Sep 17 00:00:00 2001 From: Dimasssss Date: Thu, 19 Mar 2026 00:36:02 +0300 Subject: [PATCH] Update install.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Вернул старый функционал + добавил новый: - Вернул автоматическое создание конфига с секретом - Вернул автоматическое создание службы - Добавил удаление службы и telemt через `install.sh uninstall` - Полное удаление вместе с конфигом через `install.sh --purge` - Добавил установку нужной версии `install.sh 3.3.15` --- install.sh | 568 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 489 insertions(+), 79 deletions(-) diff --git a/install.sh b/install.sh index 2dd207b..330bc3e 100644 --- a/install.sh +++ b/install.sh @@ -1,115 +1,525 @@ #!/bin/sh set -eu +# --- Global Configurations --- REPO="${REPO:-telemt/telemt}" BIN_NAME="${BIN_NAME:-telemt}" -VERSION="${1:-${VERSION:-latest}}" -INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" +INSTALL_DIR="${INSTALL_DIR:-/bin}" +CONFIG_DIR="${CONFIG_DIR:-/etc/telemt}" +CONFIG_FILE="${CONFIG_FILE:-${CONFIG_DIR}/telemt.toml}" +WORK_DIR="${WORK_DIR:-/opt/telemt}" +SERVICE_NAME="telemt" +TEMP_DIR="" +SUDO="" -say() { - printf '%s\n' "$*" -} +# --- Argument Parsing --- +ACTION="install" +TARGET_VERSION="${VERSION:-latest}" -die() { - printf 'Error: %s\n' "$*" >&2 - exit 1 -} - -need_cmd() { - command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" -} - -detect_os() { - os="$(uname -s)" - case "$os" in - Linux) printf 'linux\n' ;; - OpenBSD) printf 'openbsd\n' ;; - *) printf '%s\n' "$os" ;; +while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + ACTION="help" + shift + ;; + uninstall|--uninstall) + [ "$ACTION" != "purge" ] && ACTION="uninstall" + shift + ;; + --purge) + ACTION="purge" + shift + ;; + install|--install) + ACTION="install" + shift + ;; + -*) + printf '[ERROR] Unknown option: %s\n' "$1" >&2 + exit 1 + ;; + *) + if [ "$ACTION" = "install" ]; then + TARGET_VERSION="$1" + fi + shift + ;; esac +done + +# --- Core Functions --- +say() { printf '[INFO] %s\n' "$*"; } +die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; } + +cleanup() { + if [ -n "${TEMP_DIR:-}" ] && [ -d "$TEMP_DIR" ]; then + rm -rf -- "$TEMP_DIR" + fi +} + +trap cleanup EXIT INT TERM + +show_help() { + say "Usage: $0 [version | install | uninstall | --purge | --help]" + say " version Install specific version (e.g. 1.0.0, default: latest)" + say " uninstall Remove the binary and service (keeps config)" + say " --purge Remove everything including configuration" + exit 0 +} + +user_exists() { + if command -v getent >/dev/null 2>&1; then + getent passwd "$1" >/dev/null 2>&1 + else + grep -q "^${1}:" /etc/passwd 2>/dev/null + fi +} + +group_exists() { + if command -v getent >/dev/null 2>&1; then + getent group "$1" >/dev/null 2>&1 + else + grep -q "^${1}:" /etc/group 2>/dev/null + fi +} + +verify_common() { + [ -z "$BIN_NAME" ] && die "BIN_NAME cannot be empty." + [ -z "$INSTALL_DIR" ] && die "INSTALL_DIR cannot be empty." + [ -z "$CONFIG_DIR" ] && die "CONFIG_DIR cannot be empty." + + if [ "$(id -u)" -eq 0 ]; then + SUDO="" + else + if ! command -v sudo >/dev/null 2>&1; then + die "This script requires root or sudo. Neither found." + fi + SUDO="sudo" + say "sudo is available. Caching credentials..." + if ! sudo -v; then + die "Failed to cache sudo credentials" + fi + fi + + case "${INSTALL_DIR}${CONFIG_DIR}${WORK_DIR}" in + *[!a-zA-Z0-9_./-]*) + die "Invalid characters in path variables. Only alphanumeric, _, ., -, and / are allowed." + ;; + esac + + case "$BIN_NAME" in + *[!a-zA-Z0-9_-]*) die "Invalid characters in BIN_NAME: $BIN_NAME" ;; + esac + + for path in "$CONFIG_DIR" "$WORK_DIR"; do + check_path="$path" + + while [ "$check_path" != "/" ] && [ "${check_path%"/"}" != "$check_path" ]; do + check_path="${check_path%"/"}" + done + [ -z "$check_path" ] && check_path="/" + + case "$check_path" in + /|/bin|/sbin|/usr|/usr/bin|/usr/local|/etc|/opt|/var|/home|/root|/tmp) + die "Safety check failed: '$path' is a critical system directory." + ;; + esac + done + + for cmd in uname grep find rm chown chmod mv head mktemp; do + command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd" + done +} + +verify_install_deps() { + if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then + die "Neither curl nor wget is installed." + fi + command -v tar >/dev/null 2>&1 || die "Required command not found: tar" + command -v gzip >/dev/null 2>&1 || die "Required command not found: gzip" + command -v cp >/dev/null 2>&1 || command -v install >/dev/null 2>&1 || die "Need cp or install" + + if ! command -v setcap >/dev/null 2>&1; then + say "setcap is missing. Installing required capability tools..." + if command -v apk >/dev/null 2>&1; then + $SUDO apk add --no-cache libcap || die "Failed to install libcap" + elif command -v apt-get >/dev/null 2>&1; then + $SUDO apt-get update -qq && $SUDO apt-get install -y -qq libcap2-bin || die "Failed to install libcap2-bin" + elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then + $SUDO ${YUM_CMD:-yum} install -y -q libcap || die "Failed to install libcap" + else + die "Cannot install 'setcap'. Package manager not found. Please install libcap manually." + fi + fi } detect_arch() { - arch="$(uname -m)" - case "$arch" in - x86_64|amd64) printf 'x86_64\n' ;; - aarch64|arm64) printf 'aarch64\n' ;; - *) die "unsupported architecture: $arch" ;; + sys_arch="$(uname -m)" + case "$sys_arch" in + x86_64|amd64) echo "x86_64" ;; + aarch64|arm64) echo "aarch64" ;; + *) die "Unsupported architecture: $sys_arch" ;; esac } detect_libc() { - case "$(ldd --version 2>&1 || true)" in - *musl*) printf 'musl\n' ;; - *) printf 'gnu\n' ;; - esac + if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then + echo "musl"; return 0 + fi + + if grep -q '^ID=alpine' /etc/os-release 2>/dev/null || grep -q '^ID="alpine"' /etc/os-release 2>/dev/null; then + echo "musl"; return 0 + fi + for f in /lib/ld-musl-*.so.* /lib64/ld-musl-*.so.*; do + if [ -e "$f" ]; then + echo "musl"; return 0 + fi + done + echo "gnu" } -fetch_to_stdout() { - url="$1" +fetch_file() { + fetch_url="$1" + fetch_out="$2" + if command -v curl >/dev/null 2>&1; then - curl -fsSL "$url" + curl -fsSL "$fetch_url" -o "$fetch_out" || return 1 elif command -v wget >/dev/null 2>&1; then - wget -qO- "$url" + wget -qO "$fetch_out" "$fetch_url" || return 1 else - die "neither curl nor wget is installed" + die "curl or wget required" + fi +} + +ensure_user_group() { + nologin_bin="/bin/false" + + cmd_nologin="$(command -v nologin 2>/dev/null || true)" + if [ -n "$cmd_nologin" ] && [ -x "$cmd_nologin" ]; then + nologin_bin="$cmd_nologin" + else + for bin in /sbin/nologin /usr/sbin/nologin; do + if [ -x "$bin" ]; then + nologin_bin="$bin" + break + fi + done + fi + + if ! group_exists telemt; then + if command -v groupadd >/dev/null 2>&1; then + $SUDO groupadd -r telemt || die "Failed to create group via groupadd" + elif command -v addgroup >/dev/null 2>&1; then + $SUDO addgroup -S telemt || die "Failed to create group via addgroup" + else + die "Cannot create group: neither groupadd nor addgroup found" + fi + fi + + if ! user_exists telemt; then + if command -v useradd >/dev/null 2>&1; then + $SUDO useradd -r -g telemt -d "$WORK_DIR" -s "$nologin_bin" -c "Telemt Proxy" telemt || die "Failed to create user via useradd" + elif command -v adduser >/dev/null 2>&1; then + $SUDO adduser -S -D -H -h "$WORK_DIR" -s "$nologin_bin" -G telemt telemt || die "Failed to create user via adduser" + else + die "Cannot create user: neither useradd nor adduser found" + fi + fi +} + +setup_dirs() { + say "Setting up directories..." + $SUDO mkdir -p "$WORK_DIR" "$CONFIG_DIR" || die "Failed to create directories" + $SUDO chown telemt:telemt "$WORK_DIR" || die "Failed to set owner on WORK_DIR" + $SUDO chmod 750 "$WORK_DIR" || die "Failed to set permissions on WORK_DIR" +} + +stop_service() { + say "Stopping service if running..." + if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then + $SUDO systemctl stop "$SERVICE_NAME" 2>/dev/null || true + elif command -v rc-service >/dev/null 2>&1; then + $SUDO rc-service "$SERVICE_NAME" stop 2>/dev/null || true fi } install_binary() { - src="$1" - dst="$2" + bin_src="$1" + bin_dst="$2" - if [ -w "$INSTALL_DIR" ] || { [ ! -e "$INSTALL_DIR" ] && [ -w "$(dirname "$INSTALL_DIR")" ]; }; then - mkdir -p "$INSTALL_DIR" - install -m 0755 "$src" "$dst" - elif command -v sudo >/dev/null 2>&1; then - sudo mkdir -p "$INSTALL_DIR" - sudo install -m 0755 "$src" "$dst" + $SUDO mkdir -p "$INSTALL_DIR" || die "Failed to create install directory" + if command -v install >/dev/null 2>&1; then + $SUDO install -m 0755 "$bin_src" "$bin_dst" || die "Failed to install binary" else - die "cannot write to $INSTALL_DIR and sudo is not available" + $SUDO rm -f "$bin_dst" + $SUDO cp "$bin_src" "$bin_dst" || die "Failed to copy binary" + $SUDO chmod 0755 "$bin_dst" || die "Failed to set permissions" + fi + + if [ ! -x "$bin_dst" ]; then + die "Failed to install binary or it is not executable: $bin_dst" + fi + + say "Granting network bind capabilities to bind port 443..." + if ! $SUDO setcap cap_net_bind_service=+ep "$bin_dst" 2>/dev/null; then + say "[WARNING] Failed to apply setcap. The service will NOT be able to open port 443!" + say "[WARNING] This usually happens inside unprivileged Docker/LXC containers." fi } -need_cmd uname -need_cmd tar -need_cmd mktemp -need_cmd grep -need_cmd install +generate_secret() { + if command -v openssl >/dev/null 2>&1; then + secret="$(openssl rand -hex 16 2>/dev/null)" && [ -n "$secret" ] && { echo "$secret"; return 0; } + fi + if command -v xxd >/dev/null 2>&1; then + secret="$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | xxd -p | tr -d '\n')" && [ -n "$secret" ] && { echo "$secret"; return 0; } + fi + secret="$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n')" && [ -n "$secret" ] && { echo "$secret"; return 0; } + return 1 +} -ARCH="$(detect_arch)" -OS="$(detect_os)" +generate_config_content() { + cat </dev/null && config_exists=1 || true + else + [ -f "$CONFIG_FILE" ] && config_exists=1 || true + fi + + if [ "$config_exists" -eq 1 ]; then + say "Config already exists, skipping generation." + return 0 + fi + + toml_secret="$(generate_secret)" || die "Failed to generate secret" + say "Creating config at $CONFIG_FILE..." + + tmp_conf="$(mktemp "${TEMP_DIR:-/tmp}/telemt_conf.XXXXXX")" || die "Failed to create temp config" + generate_config_content "$toml_secret" > "$tmp_conf" || die "Failed to write temp config" + + $SUDO mv "$tmp_conf" "$CONFIG_FILE" || die "Failed to install config file" + $SUDO chown root:telemt "$CONFIG_FILE" || die "Failed to set owner" + $SUDO chmod 640 "$CONFIG_FILE" || die "Failed to set config permissions" + + say "Secret for user 'hello': $toml_secret" +} + +generate_systemd_content() { + cat </dev/null 2>&1 && [ -d /run/systemd/system ]; then + say "Installing systemd service..." + tmp_svc="$(mktemp "${TEMP_DIR:-/tmp}/${SERVICE_NAME}.service.XXXXXX")" || die "Failed to create temp service" + generate_systemd_content > "$tmp_svc" || die "Failed to generate service content" + + $SUDO mv "$tmp_svc" "/etc/systemd/system/${SERVICE_NAME}.service" || die "Failed to move service file" + $SUDO chown root:root "/etc/systemd/system/${SERVICE_NAME}.service" + $SUDO chmod 644 "/etc/systemd/system/${SERVICE_NAME}.service" + + $SUDO systemctl daemon-reload || die "Failed to reload systemd" + $SUDO systemctl enable "$SERVICE_NAME" || die "Failed to enable service" + $SUDO systemctl start "$SERVICE_NAME" || die "Failed to start service" + + elif command -v rc-update >/dev/null 2>&1; then + say "Installing OpenRC service..." + tmp_svc="$(mktemp "${TEMP_DIR:-/tmp}/${SERVICE_NAME}.init.XXXXXX")" || die "Failed to create temp file" + generate_openrc_content > "$tmp_svc" || die "Failed to generate init content" + + $SUDO mv "$tmp_svc" "/etc/init.d/${SERVICE_NAME}" || die "Failed to move service file" + $SUDO chown root:root "/etc/init.d/${SERVICE_NAME}" + $SUDO chmod 0755 "/etc/init.d/${SERVICE_NAME}" + + $SUDO rc-update add "$SERVICE_NAME" default 2>/dev/null || die "Failed to register service" + $SUDO rc-service "$SERVICE_NAME" start 2>/dev/null || die "Failed to start OpenRC service" + else + say "No service manager found. You can start it manually with:" + if [ -n "$SUDO" ]; then + say " sudo -u telemt ${INSTALL_DIR}/${BIN_NAME} ${CONFIG_FILE}" + else + say " su -s /bin/sh telemt -c '${INSTALL_DIR}/${BIN_NAME} ${CONFIG_FILE}'" + fi + fi +} + +kill_user_procs() { + say "Ensuring $BIN_NAME processes are killed..." + + if pkill_cmd="$(command -v pkill 2>/dev/null)"; then + $SUDO "$pkill_cmd" -u telemt "$BIN_NAME" 2>/dev/null || true + sleep 1 + $SUDO "$pkill_cmd" -9 -u telemt "$BIN_NAME" 2>/dev/null || true + elif killall_cmd="$(command -v killall 2>/dev/null)"; then + $SUDO "$killall_cmd" "$BIN_NAME" 2>/dev/null || true + sleep 1 + $SUDO "$killall_cmd" -9 "$BIN_NAME" 2>/dev/null || true + fi +} + +uninstall() { + purge_data=0 + [ "$ACTION" = "purge" ] && purge_data=1 + + say "Uninstalling $BIN_NAME..." + stop_service + + if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then + $SUDO systemctl disable "$SERVICE_NAME" 2>/dev/null || true + $SUDO rm -f "/etc/systemd/system/${SERVICE_NAME}.service" + $SUDO systemctl daemon-reload || true + elif command -v rc-update >/dev/null 2>&1; then + $SUDO rc-update del "$SERVICE_NAME" 2>/dev/null || true + $SUDO rm -f "/etc/init.d/${SERVICE_NAME}" + fi + + kill_user_procs + + $SUDO rm -f "${INSTALL_DIR}/${BIN_NAME}" + + $SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true + $SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true + + if [ "$purge_data" -eq 1 ]; then + say "Purging configuration and data..." + $SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR" + else + say "Note: Configuration in $CONFIG_DIR was kept. Run with '--purge' to remove it." + fi + + say "Uninstallation complete." + exit 0 +} + +# ============================================================================ +# Main Entry Point +# ============================================================================ + +case "$ACTION" in + help) + show_help ;; - *) - URL="https://github.com/$REPO/releases/download/${VERSION}/${BIN_NAME}-${ARCH}-linux-${LIBC}.tar.gz" + uninstall|purge) + verify_common + uninstall + ;; + install) + say "Starting installation..." + verify_common + verify_install_deps + + ARCH="$(detect_arch)" + LIBC="$(detect_libc)" + say "Detected system: $ARCH-linux-$LIBC" + + FILE_NAME="${BIN_NAME}-${ARCH}-linux-${LIBC}.tar.gz" + FILE_NAME="$(printf '%s' "$FILE_NAME" | tr -d ' \t\n\r')" + + if [ "$TARGET_VERSION" = "latest" ]; then + DL_URL="https://github.com/${REPO}/releases/latest/download/${FILE_NAME}" + else + DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}" + fi + + TEMP_DIR="$(mktemp -d)" || die "Failed to create temp directory" + if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR" ]; then + die "Temp directory creation failed" + fi + + say "Downloading from $DL_URL..." + fetch_file "$DL_URL" "${TEMP_DIR}/archive.tar.gz" || die "Download failed (check version or network)" + + gzip -dc "${TEMP_DIR}/archive.tar.gz" | tar -xf - -C "$TEMP_DIR" || die "Extraction failed" + + EXTRACTED_BIN="$(find "$TEMP_DIR" -type f -name "$BIN_NAME" -print 2>/dev/null | head -n 1)" + [ -z "$EXTRACTED_BIN" ] && die "Binary '$BIN_NAME' not found in archive" + + ensure_user_group + setup_dirs + stop_service + + say "Installing binary..." + install_binary "$EXTRACTED_BIN" "${INSTALL_DIR}/${BIN_NAME}" + + install_config + install_service + + say "" + say "=============================================" + say "Installation complete!" + say "=============================================" + if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then + say "To check the logs, run:" + say " journalctl -u $SERVICE_NAME -f" + say "" + fi + say "To get user connection links, run:" + if command -v jq >/dev/null 2>&1; then + say " curl -s http://127.0.0.1:9091/v1/users | jq -r '.data[] | \"User: \\(.username)\\n\\(.links.tls[0] // empty)\"'" + else + say " curl -s http://127.0.0.1:9091/v1/users" + say " (Note: Install 'jq' package to see the links nicely formatted)" + fi ;; esac - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT INT TERM - -say "Installing $BIN_NAME ($VERSION) for $ARCH-linux-$LIBC..." -fetch_to_stdout "$URL" | tar -xzf - -C "$TMPDIR" - -[ -f "$TMPDIR/$BIN_NAME" ] || die "archive did not contain $BIN_NAME" - -install_binary "$TMPDIR/$BIN_NAME" "$INSTALL_DIR/$BIN_NAME" - -say "Installed: $INSTALL_DIR/$BIN_NAME" -"$INSTALL_DIR/$BIN_NAME" --version 2>/dev/null || true