220 lines
9.1 KiB
Bash
220 lines
9.1 KiB
Bash
#!/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" <<EOF
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Northwind Status</title>
|
|
<link rel="stylesheet" href="/styles.css">
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
</head>
|
|
<body><main><p class="tag">SERVICE NODE ${SITE_ID}</p><h1>Systems operational</h1><p>This endpoint provides encrypted application transport and status information.</p><a href="/about">Service information</a></main></body>
|
|
</html>
|
|
EOF
|
|
cat >"${SITE_DIR}/about.html" <<EOF
|
|
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Service information</title><link rel="stylesheet" href="/styles.css"></head><body><main><p class="tag">NODE ${SITE_ID}</p><h1>Service information</h1><p>This privately operated endpoint is monitored for availability.</p><a href="/">Return to status</a></main></body></html>
|
|
EOF
|
|
cat >"${SITE_DIR}/404.html" <<EOF
|
|
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Not found</title><link rel="stylesheet" href="/styles.css"></head><body><main><p class="tag">404</p><h1>Page not found</h1><a href="/">Return to status</a></main></body></html>
|
|
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
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="16" fill="#0b1827"/><path d="M17 34l10 10 21-25" fill="none" stroke="#61d3a5" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/></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}" <<EOF
|
|
Telegram WEB proxy
|
|
Created: $(date --iso-8601=seconds)
|
|
Hostname: ${HOSTNAME_ARG}
|
|
Secret: ${SECRET}
|
|
Desktop link: ${WEB_LINK}
|
|
HTTPS-form link: ${HTTPS_LINK}
|
|
Public cover site: https://${HOSTNAME_ARG}/
|
|
Pinned tproxy-server commit: ${TPROXY_COMMIT}
|
|
EOF
|
|
chmod 0600 "${RESULT_FILE}"
|
|
|
|
cat <<EOF
|
|
|
|
============================================================
|
|
Telegram WEB proxy is ready.
|
|
|
|
Hostname : ${HOSTNAME_ARG}
|
|
Secret : ${SECRET}
|
|
Open in Telegram Desktop:
|
|
${WEB_LINK}
|
|
|
|
Alternative link form:
|
|
${HTTPS_LINK}
|
|
|
|
Saved root-only at: ${RESULT_FILE}
|
|
Public site: https://${HOSTNAME_ARG}/
|
|
============================================================
|
|
|
|
Important: WEB proxy support is experimental. Use a compatible
|
|
Telegram Desktop build; normal mobile releases may not support it.
|
|
EOF
|