#!/usr/bin/env bash set -Eeuo pipefail umask 077 # One-command installer for Telegram's experimental WEB proxy server. # Intended for a clean, dedicated Ubuntu 22.04+/Debian 12+ x86_64 VPS. readonly TPROXY_COMMIT="52a5feb7fac38f68da5afef9cedd9b3bfc8473ca" readonly TPROXY_ARCHIVE_URL="https://github.com/telegramdesktop/tproxy-server/archive/${TPROXY_COMMIT}.tar.gz" readonly RESULT_FILE="/root/telegram-web-proxy-access.txt" HOSTNAME_ARG="" EMAIL="" WORK_DIR="" say() { printf '\n==> %s\n' "$*"; } die() { printf '\nERROR: %s\n' "$*" >&2; exit 1; } cleanup() { if [[ -n "${WORK_DIR}" && -d "${WORK_DIR}" ]]; then rm -rf -- "${WORK_DIR}" fi } trap cleanup EXIT trap 'printf "\nInstallation stopped at line %s.\n" "$LINENO" >&2' ERR usage() { cat <<'EOF' Usage: sudo bash install.sh [--hostname proxy.example.com] [--email admin@example.com] Options: --hostname HOST Dedicated lowercase DNS name pointed to this VPS. --email EMAIL ACME/Let's Encrypt contact email. -h, --help Show this help. If hostname or email is omitted, the installer asks interactively. The WEB proxy secret is generated locally and never placed in shell history. EOF } while [[ $# -gt 0 ]]; do case "$1" in --hostname) HOSTNAME_ARG="${2:-}"; shift 2 ;; --email) EMAIL="${2:-}"; shift 2 ;; -h|--help) usage; exit 0 ;; *) die "Unknown argument: $1" ;; esac done [[ "${EUID}" -eq 0 ]] || die "Run this script as root (sudo bash install.sh)." [[ "$(uname -m)" == "x86_64" ]] || die "The official MTProxy build currently requires x86_64." [[ -r /etc/os-release ]] || die "Cannot identify the operating system." # shellcheck disable=SC1091 source /etc/os-release OS_MAJOR="${VERSION_ID%%.*}" [[ "${OS_MAJOR}" =~ ^[0-9]+$ ]] || die "Cannot parse OS version: ${VERSION_ID:-unknown}." if [[ "${ID:-}" == "ubuntu" ]]; then (( OS_MAJOR >= 22 )) || die "Ubuntu 22.04 or newer is required." elif [[ "${ID:-}" == "debian" ]]; then (( OS_MAJOR >= 12 )) || die "Debian 12 or newer is required." else die "Supported systems: Ubuntu 22.04+ or Debian 12+ (detected ${PRETTY_NAME:-unknown})." fi if [[ -z "${HOSTNAME_ARG}" ]]; then read -r -p "Proxy hostname (for example proxy.example.com): " HOSTNAME_ARG fi HOSTNAME_ARG="${HOSTNAME_ARG,,}" HOSTNAME_ARG="${HOSTNAME_ARG%.}" if [[ ! "${HOSTNAME_ARG}" =~ ^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$ || "${HOSTNAME_ARG}" != *.* ]]; then die "Hostname must be a lowercase ASCII DNS name, for example proxy.example.com." fi [[ "${HOSTNAME_ARG}" != *..* ]] || die "Hostname contains an empty DNS label." IFS='.' read -r -a DNS_LABELS <<<"${HOSTNAME_ARG}" for label in "${DNS_LABELS[@]}"; do [[ ${#label} -le 63 && "${label}" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]] || die "Invalid DNS label in hostname: ${label}" done if [[ -z "${EMAIL}" ]]; then read -r -p "Email for HTTPS certificate notices: " EMAIL fi [[ "${EMAIL}" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] || die "Invalid email address." command -v curl >/dev/null 2>&1 || { apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends curl ca-certificates } command -v openssl >/dev/null 2>&1 || { apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssl } say "Checking DNS and the VPS" PUBLIC_IPV4="$(curl --fail --silent --show-error --max-time 10 -4 https://api.ipify.org || true)" [[ "${PUBLIC_IPV4}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || die "Could not determine this VPS public IPv4." DNS_IPV4S="$(getent ahostsv4 "${HOSTNAME_ARG}" 2>/dev/null | awk '{print $1}' | sort -u || true)" [[ -n "${DNS_IPV4S}" ]] || die "${HOSTNAME_ARG} has no visible A record yet." if ! grep -Fxq "${PUBLIC_IPV4}" <<<"${DNS_IPV4S}"; then printf 'Public VPS IPv4: %s\nDNS currently returns:\n%s\n' "${PUBLIC_IPV4}" "${DNS_IPV4S}" >&2 die "Point the hostname A record to this VPS and wait for DNS propagation." fi if command -v ss >/dev/null 2>&1; then OCCUPIED="$(ss -H -lnt '( sport = :80 or sport = :443 )' 2>/dev/null || true)" if [[ -n "${OCCUPIED}" ]]; then printf '%s\n' "${OCCUPIED}" >&2 die "Ports 80/443 are already in use. Use a clean VPS or integrate manually; the official installer replaces Caddy configuration." fi fi if [[ -s /etc/caddy/Caddyfile ]]; then die "An existing Caddy configuration was found. This safe installer will not overwrite it." fi say "Downloading the pinned official tproxy-server source" WORK_DIR="$(mktemp -d /tmp/telegram-web-proxy.XXXXXXXX)" ARCHIVE="${WORK_DIR}/tproxy-server.tar.gz" curl --fail --silent --show-error --location \ --proto '=https' --proto-redir '=https' --tlsv1.2 \ --output "${ARCHIVE}" "${TPROXY_ARCHIVE_URL}" tar -tzf "${ARCHIVE}" >/dev/null tar -xzf "${ARCHIVE}" -C "${WORK_DIR}" SOURCE_DIR="${WORK_DIR}/tproxy-server-${TPROXY_COMMIT}" [[ -x "${SOURCE_DIR}/deploy/install.sh" ]] || die "Official installer is missing from the downloaded archive." say "Creating a unique public cover site" SITE_DIR="${WORK_DIR}/site" mkdir -p "${SITE_DIR}" SITE_ID="$(openssl rand -hex 6)" cat >"${SITE_DIR}/index.html" < Northwind Status

SERVICE NODE ${SITE_ID}

Systems operational

This endpoint provides encrypted application transport and status information.

Service information
EOF cat >"${SITE_DIR}/about.html" <Service information

NODE ${SITE_ID}

Service information

This privately operated endpoint is monitored for availability.

Return to status
EOF cat >"${SITE_DIR}/404.html" <Not found

404

Page not found

Return to status
EOF cat >"${SITE_DIR}/styles.css" <<'EOF' :root{color-scheme:dark;font-family:ui-sans-serif,system-ui,sans-serif;background:#07111f;color:#e8f0f8}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;background:radial-gradient(circle at 20% 20%,#173b5c 0,transparent 35%),#07111f}main{width:min(680px,calc(100% - 40px));padding:48px;border:1px solid #294158;border-radius:20px;background:#0b1827cc;box-shadow:0 28px 80px #0008}h1{font-size:clamp(2rem,7vw,4rem);letter-spacing:-.04em;margin:.2em 0}p{line-height:1.7;color:#a9bbcb}.tag{font-size:.75rem;letter-spacing:.18em;color:#61d3a5}a{color:#7ed7ff} EOF cat >"${SITE_DIR}/favicon.svg" < EOF SECRET="$(openssl rand -hex 16)" say "Installing Caddy, the relay and official MTProxy" printf '%s\n' "${SECRET}" | "${SOURCE_DIR}/deploy/install.sh" \ --hostname "${HOSTNAME_ARG}" \ --email "${EMAIL}" \ --site-dir "${SITE_DIR}" say "Verifying services and HTTPS" for unit in caddy tproxy-firewall mtproxy tproxy-server; do systemctl is-active --quiet "${unit}" || { systemctl --no-pager --full status "${unit}" || true die "Service ${unit} is not running." } done curl --fail --silent --show-error --max-time 10 http://127.0.0.1:8081/healthz >/dev/null curl --fail --silent --show-error --max-time 10 http://127.0.0.1:8081/readyz >/dev/null HTTPS_OK=0 for _ in 1 2 3 4 5 6; do if curl --fail --silent --show-error --location --max-time 15 "https://${HOSTNAME_ARG}/" >/dev/null; then HTTPS_OK=1 break fi sleep 5 done [[ "${HTTPS_OK}" -eq 1 ]] || die "Services are running, but public HTTPS verification failed. Check DNS, provider firewall and Caddy logs." WEB_LINK="tg://webproxy?server=${HOSTNAME_ARG}&secret=${SECRET}" HTTPS_LINK="https://t.me/webproxy?server=${HOSTNAME_ARG}&secret=${SECRET}" cat >"${RESULT_FILE}" <