mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-23 21:00:44 +00:00
Added predefined tunnel functionality, allowing users to define multiple tunnels in the configuration file and start them by name, including the following improvements: - Added --all flag to start all configured tunnels - Added parameterless start command to list available tunnels - Support configuration of multiple tunnel types (http, https, tcp) - Support advanced configurations such as subdomains, transport protocols, and IP allowlists refactor(deployments): Refactor Docker deployment configuration Removed old Dockerfile and Compose configurations, added new deployment files: - Removed .env.example and old Docker build files - Added Caddy reverse proxy configuration file - Added two deployment modes: standard and Caddy reverse proxy - Added detailed server configuration example files docs: Update documentation to include tunnel configuration and deployment guide Updated Chinese and English README documents: - Added usage instructions and configuration examples for predefined tunnels - Expanded server deployment section to include direct TLS and reverse proxy modes - Added server configuration reference table with detailed configuration item descriptions - Added specific configuration methods for Caddy and Nginx reverse proxies
1122 lines
35 KiB
Bash
Executable File
1122 lines
35 KiB
Bash
Executable File
#!/bin/bash
|
||
set -e
|
||
|
||
# ============================================================================
|
||
# Configuration
|
||
# ============================================================================
|
||
GITHUB_REPO="Gouryella/drip"
|
||
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
|
||
SERVICE_USER="${SERVICE_USER:-drip}"
|
||
CONFIG_DIR="/etc/drip"
|
||
WORK_DIR="/var/lib/drip"
|
||
VERSION="${VERSION:-}"
|
||
COMMAND_MADE_AVAILABLE=false
|
||
|
||
# Default values
|
||
DEFAULT_PORT=8443
|
||
DEFAULT_TCP_PORT_MIN=20000
|
||
DEFAULT_TCP_PORT_MAX=40000
|
||
|
||
# Colors
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
BOLD='\033[1m'
|
||
NC='\033[0m'
|
||
|
||
# Language (default: en)
|
||
LANG_CODE="${LANG_CODE:-en}"
|
||
|
||
# ============================================================================
|
||
# Internationalization
|
||
# ============================================================================
|
||
declare -A MSG_EN
|
||
declare -A MSG_ZH
|
||
|
||
# English messages
|
||
MSG_EN=(
|
||
["banner_title"]="Drip Server - One-Click Installer"
|
||
["select_lang"]="Select language / 选择语言"
|
||
["lang_en"]="English"
|
||
["lang_zh"]="中文"
|
||
["checking_root"]="Checking root privileges..."
|
||
["need_root"]="This script requires root privileges. Please run with sudo."
|
||
["checking_os"]="Checking operating system..."
|
||
["unsupported_os"]="Unsupported operating system. Only Linux is supported."
|
||
["checking_arch"]="Checking system architecture..."
|
||
["unsupported_arch"]="Unsupported architecture. Only amd64 and arm64 are supported."
|
||
["checking_deps"]="Checking dependencies..."
|
||
["installing_deps"]="Installing dependencies..."
|
||
["deps_ok"]="Dependencies check passed"
|
||
["downloading"]="Downloading Drip server"
|
||
["download_failed"]="Download failed"
|
||
["download_ok"]="Download completed"
|
||
["installing"]="Installing binary..."
|
||
["install_ok"]="Binary installed successfully"
|
||
["config_title"]="Server Configuration"
|
||
["enter_domain"]="Enter your domain (e.g., tunnel.example.com)"
|
||
["domain_required"]="Domain is required"
|
||
["enter_port"]="Enter server port"
|
||
["enter_token"]="Enter authentication token (leave empty to auto-generate)"
|
||
["token_generated"]="Authentication token generated"
|
||
["metrics_token_generated"]="Metrics token generated"
|
||
["enter_cert_path"]="Enter TLS certificate path (public key)"
|
||
["enter_key_path"]="Enter TLS private key path"
|
||
["cert_not_found"]="Certificate file not found"
|
||
["key_not_found"]="Private key file not found"
|
||
["cert_option_title"]="TLS Certificate Configuration"
|
||
["cert_option_certbot"]="Use Let's Encrypt (recommended)"
|
||
["cert_option_self"]="Generate self-signed certificate (10 years)"
|
||
["cert_option_provide"]="Provide your own certificate"
|
||
["enter_email"]="Enter email for Let's Encrypt (optional, press Enter to skip)"
|
||
["obtaining_cert"]="Obtaining Let's Encrypt certificate..."
|
||
["cert_obtained"]="Certificate obtained successfully"
|
||
["cert_failed"]="Failed to obtain certificate. Please check domain DNS settings."
|
||
["generating_cert"]="Generating self-signed ECDSA certificate..."
|
||
["cert_generated"]="Self-signed certificate generated"
|
||
["cert_warning"]="Note: Self-signed certificates require --insecure flag on client"
|
||
["cert_exists"]="Certificate already exists, skipping..."
|
||
["certbot_domain_note"]="Make sure your domain DNS points to this server before proceeding"
|
||
["creating_user"]="Creating service user..."
|
||
["user_exists"]="User already exists"
|
||
["user_created"]="Service user created"
|
||
["creating_service"]="Creating systemd service..."
|
||
["service_created"]="Systemd service created"
|
||
["configuring_firewall"]="Configuring firewall..."
|
||
["firewall_ok"]="Firewall configured"
|
||
["saving_config"]="Saving configuration..."
|
||
["config_saved"]="Configuration saved"
|
||
["start_now"]="Start service now?"
|
||
["starting_service"]="Starting service..."
|
||
["service_started"]="Service started successfully"
|
||
["service_failed"]="Service failed to start"
|
||
["install_complete"]="Installation completed!"
|
||
["client_info"]="Client connection info"
|
||
["server_addr"]="Server"
|
||
["token_label"]="Auth Token"
|
||
["metrics_token_label"]="Metrics Token"
|
||
["service_commands"]="Service management commands"
|
||
["cmd_start"]="Start service"
|
||
["cmd_stop"]="Stop service"
|
||
["cmd_restart"]="Restart service"
|
||
["cmd_status"]="Check status"
|
||
["cmd_logs"]="View logs"
|
||
["cmd_enable"]="Enable auto-start"
|
||
["yes"]="y"
|
||
["no"]="n"
|
||
["press_enter"]="Press Enter to continue..."
|
||
["tcp_port_range"]="TCP tunnel port range"
|
||
["enter_tcp_min"]="Enter minimum TCP port"
|
||
["enter_tcp_max"]="Enter maximum TCP port"
|
||
["invalid_port"]="Invalid port number"
|
||
["enter_public_port"]="Enter public port (for URL display, e.g., behind reverse proxy)"
|
||
["wildcard_cert_note"]="Note: For subdomain tunnels, you need a wildcard certificate (*.domain.com)"
|
||
["already_installed"]="Drip server is already installed"
|
||
["current_version"]="Current version"
|
||
["update_now"]="Update to the latest version?"
|
||
["updating"]="Updating..."
|
||
["update_ok"]="Update completed"
|
||
["skip_update"]="Skipping update"
|
||
["service_not_found"]="Systemd service not found. Proceeding with full configuration..."
|
||
["copying_cert"]="Copying certificate with proper permissions..."
|
||
["cert_copied"]="Certificate copied successfully"
|
||
["creating_renewal_hook"]="Creating certificate renewal hook..."
|
||
["renewal_hook_created"]="Renewal hook created"
|
||
)
|
||
|
||
# Chinese messages
|
||
MSG_ZH=(
|
||
["banner_title"]="Drip 服务器 - 一键安装脚本"
|
||
["select_lang"]="Select language / 选择语言"
|
||
["lang_en"]="English"
|
||
["lang_zh"]="中文"
|
||
["checking_root"]="检查 root 权限..."
|
||
["need_root"]="此脚本需要 root 权限,请使用 sudo 运行。"
|
||
["checking_os"]="检查操作系统..."
|
||
["unsupported_os"]="不支持的操作系统,仅支持 Linux。"
|
||
["checking_arch"]="检查系统架构..."
|
||
["unsupported_arch"]="不支持的架构,仅支持 amd64 和 arm64。"
|
||
["checking_deps"]="检查依赖..."
|
||
["installing_deps"]="安装依赖..."
|
||
["deps_ok"]="依赖检查通过"
|
||
["downloading"]="下载 Drip 服务器"
|
||
["download_failed"]="下载失败"
|
||
["download_ok"]="下载完成"
|
||
["installing"]="安装二进制文件..."
|
||
["install_ok"]="二进制文件安装成功"
|
||
["config_title"]="服务器配置"
|
||
["enter_domain"]="输入你的域名(例如:tunnel.example.com)"
|
||
["domain_required"]="域名是必填项"
|
||
["enter_port"]="输入服务器端口"
|
||
["enter_token"]="输入认证令牌(留空自动生成)"
|
||
["token_generated"]="认证令牌已生成"
|
||
["metrics_token_generated"]="监控令牌已生成"
|
||
["enter_cert_path"]="输入 TLS 证书路径(公钥)"
|
||
["enter_key_path"]="输入 TLS 私钥路径"
|
||
["cert_not_found"]="证书文件未找到"
|
||
["key_not_found"]="私钥文件未找到"
|
||
["cert_option_title"]="TLS 证书配置"
|
||
["cert_option_certbot"]="使用 Let's Encrypt 自动获取(推荐)"
|
||
["cert_option_self"]="生成自签名证书(10 年有效期)"
|
||
["cert_option_provide"]="使用自己的证书"
|
||
["enter_email"]="输入 Let's Encrypt 邮箱(可选,直接回车跳过)"
|
||
["obtaining_cert"]="正在获取 Let's Encrypt 证书..."
|
||
["cert_obtained"]="证书获取成功"
|
||
["cert_failed"]="证书获取失败,请检查域名 DNS 是否正确指向本服务器"
|
||
["generating_cert"]="正在生成自签名 ECDSA 证书..."
|
||
["cert_generated"]="自签名证书已生成"
|
||
["cert_warning"]="提示:自签名证书需要客户端使用 --insecure 参数"
|
||
["cert_exists"]="证书已存在,跳过..."
|
||
["certbot_domain_note"]="请确保域名 DNS 已指向本服务器再继续"
|
||
["creating_user"]="创建服务用户..."
|
||
["user_exists"]="用户已存在"
|
||
["user_created"]="服务用户创建完成"
|
||
["creating_service"]="创建 systemd 服务..."
|
||
["service_created"]="systemd 服务创建完成"
|
||
["configuring_firewall"]="配置防火墙..."
|
||
["firewall_ok"]="防火墙配置完成"
|
||
["saving_config"]="保存配置..."
|
||
["config_saved"]="配置已保存"
|
||
["start_now"]="是否立即启动服务?"
|
||
["starting_service"]="正在启动服务..."
|
||
["service_started"]="服务启动成功"
|
||
["service_failed"]="服务启动失败"
|
||
["install_complete"]="安装完成!"
|
||
["client_info"]="客户端连接信息"
|
||
["server_addr"]="服务器"
|
||
["token_label"]="认证令牌"
|
||
["metrics_token_label"]="监控令牌"
|
||
["service_commands"]="服务管理命令"
|
||
["cmd_start"]="启动服务"
|
||
["cmd_stop"]="停止服务"
|
||
["cmd_restart"]="重启服务"
|
||
["cmd_status"]="查看状态"
|
||
["cmd_logs"]="查看日志"
|
||
["cmd_enable"]="开机启动"
|
||
["yes"]="y"
|
||
["no"]="n"
|
||
["press_enter"]="按 Enter 继续..."
|
||
["tcp_port_range"]="TCP 隧道端口范围"
|
||
["enter_tcp_min"]="输入最小 TCP 端口"
|
||
["enter_tcp_max"]="输入最大 TCP 端口"
|
||
["invalid_port"]="无效的端口号"
|
||
["enter_public_port"]="输入公开端口(用于 URL 显示,如在反向代理后)"
|
||
["wildcard_cert_note"]="注意:要支持子域名隧道,需要通配符证书(*.domain.com)"
|
||
["already_installed"]="Drip 服务器已安装"
|
||
["current_version"]="当前版本"
|
||
["update_now"]="是否更新到最新版本?"
|
||
["updating"]="正在更新..."
|
||
["update_ok"]="更新完成"
|
||
["skip_update"]="跳过更新"
|
||
["service_not_found"]="Systemd 服务未找到,将进行完整配置..."
|
||
["copying_cert"]="正在复制证书并设置权限..."
|
||
["cert_copied"]="证书复制成功"
|
||
["creating_renewal_hook"]="创建证书续期钩子..."
|
||
["renewal_hook_created"]="续期钩子创建完成"
|
||
)
|
||
|
||
# Get message by key
|
||
msg() {
|
||
local key="$1"
|
||
if [[ "$LANG_CODE" == "zh" ]]; then
|
||
echo "${MSG_ZH[$key]:-$key}"
|
||
else
|
||
echo "${MSG_EN[$key]:-$key}"
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Output functions
|
||
# ============================================================================
|
||
print_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||
print_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||
print_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||
print_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||
print_step() { echo -e "${CYAN}[→]${NC} $1"; }
|
||
|
||
repeat_char() {
|
||
local char="$1"
|
||
local count="$2"
|
||
local out=""
|
||
for _ in $(seq 1 "$count"); do
|
||
out+="$char"
|
||
done
|
||
echo "$out"
|
||
}
|
||
|
||
print_panel() {
|
||
local title="$1"
|
||
shift
|
||
local width=58
|
||
local bar
|
||
bar=$(repeat_char "=" "$width")
|
||
echo ""
|
||
echo -e "${CYAN}${bar}${NC}"
|
||
echo -e "${CYAN}${title}${NC}"
|
||
echo -e "${CYAN}${bar}${NC}"
|
||
for line in "$@"; do
|
||
echo -e " $line"
|
||
done
|
||
echo -e "${CYAN}${bar}${NC}"
|
||
echo ""
|
||
}
|
||
|
||
print_subheader() {
|
||
local title="$1"
|
||
local width=58
|
||
local bar
|
||
bar=$(repeat_char "-" "$width")
|
||
echo ""
|
||
echo -e "${CYAN}${title}${NC}"
|
||
echo -e "${CYAN}${bar}${NC}"
|
||
}
|
||
|
||
ensure_command_access() {
|
||
hash -r 2>/dev/null || true
|
||
command -v rehash >/dev/null 2>&1 && rehash || true
|
||
|
||
if command -v drip >/dev/null 2>&1; then
|
||
COMMAND_MADE_AVAILABLE=true
|
||
return
|
||
fi
|
||
|
||
local target_path="${INSTALL_DIR}/drip"
|
||
local preferred="/usr/local/bin"
|
||
|
||
if [[ ":$PATH:" == *":$preferred:"* ]]; then
|
||
if [[ -w "$preferred" ]]; then
|
||
if ln -sf "$target_path" "$preferred/drip" 2>/dev/null; then
|
||
COMMAND_MADE_AVAILABLE=true
|
||
print_success "Made drip available at $preferred/drip"
|
||
fi
|
||
else
|
||
if sudo ln -sf "$target_path" "$preferred/drip" 2>/dev/null; then
|
||
COMMAND_MADE_AVAILABLE=true
|
||
print_success "Made drip available at $preferred/drip"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
hash -r 2>/dev/null || true
|
||
command -v rehash >/dev/null 2>&1 && rehash || true
|
||
}
|
||
|
||
# Print banner
|
||
print_banner() {
|
||
echo -e "${GREEN}"
|
||
cat << "EOF"
|
||
____ _
|
||
/ __ \_____(_)___
|
||
/ / / / ___/ / __ \
|
||
/ /_/ / / / / /_/ /
|
||
/_____/_/ /_/ .___/
|
||
/_/
|
||
EOF
|
||
echo -e "${BOLD}$(msg banner_title)${NC}"
|
||
echo ""
|
||
}
|
||
|
||
# Extract version from a drip binary, preferring the plain output when available
|
||
get_version_from_binary() {
|
||
local binary="$1"
|
||
local output=""
|
||
local version=""
|
||
|
||
output=$("$binary" version --short 2>/dev/null || true)
|
||
if [[ -n "$output" ]]; then
|
||
version=$(printf '%s\n' "$output" | awk -F': ' '/Version/ {print $2; exit}')
|
||
fi
|
||
|
||
if [[ -z "$version" ]]; then
|
||
output=$("$binary" version 2>/dev/null || true)
|
||
if [[ -n "$output" ]]; then
|
||
output=$(printf '%s\n' "$output" | sed -E $'s/\x1b\\[[0-9;]*[A-Za-z]//g')
|
||
version=$(printf '%s\n' "$output" | sed -nE 's/.*Version:[[:space:]]*([vV]?[0-9][^[:space:]]*).*/\1/p' | head -n1)
|
||
fi
|
||
fi
|
||
|
||
echo "${version:-unknown}"
|
||
}
|
||
|
||
# ============================================================================
|
||
# Language selection
|
||
# ============================================================================
|
||
select_language() {
|
||
print_panel "$(msg select_lang)" \
|
||
"${GREEN}1)${NC} English" \
|
||
"${GREEN}2)${NC} 中文"
|
||
|
||
read -p "Select [1]: " lang_choice < /dev/tty
|
||
case "$lang_choice" in
|
||
2)
|
||
LANG_CODE="zh"
|
||
;;
|
||
*)
|
||
LANG_CODE="en"
|
||
;;
|
||
esac
|
||
echo ""
|
||
}
|
||
|
||
# ============================================================================
|
||
# System checks
|
||
# ============================================================================
|
||
check_root() {
|
||
print_step "$(msg checking_root)"
|
||
if [[ $EUID -ne 0 ]]; then
|
||
print_error "$(msg need_root)"
|
||
exit 1
|
||
fi
|
||
print_success "root"
|
||
}
|
||
|
||
check_os() {
|
||
print_step "$(msg checking_os)"
|
||
if [[ "$(uname)" != "Linux" ]]; then
|
||
print_error "$(msg unsupported_os)"
|
||
exit 1
|
||
fi
|
||
|
||
# Detect distribution (use subshell to avoid variable pollution)
|
||
if [[ -f /etc/os-release ]]; then
|
||
OS_NAME=$(. /etc/os-release && echo "$ID")
|
||
OS_VERSION_ID=$(. /etc/os-release && echo "$VERSION_ID")
|
||
else
|
||
OS_NAME="unknown"
|
||
fi
|
||
|
||
print_success "Linux ($OS_NAME)"
|
||
}
|
||
|
||
check_arch() {
|
||
print_step "$(msg checking_arch)"
|
||
ARCH=$(uname -m)
|
||
case "$ARCH" in
|
||
x86_64)
|
||
ARCH="amd64"
|
||
;;
|
||
aarch64|arm64)
|
||
ARCH="arm64"
|
||
;;
|
||
*)
|
||
print_error "$(msg unsupported_arch): $ARCH"
|
||
exit 1
|
||
;;
|
||
esac
|
||
print_success "$ARCH"
|
||
}
|
||
|
||
check_dependencies() {
|
||
print_step "$(msg checking_deps)"
|
||
|
||
local deps_to_install=()
|
||
|
||
# Check curl or wget
|
||
if ! command -v curl &> /dev/null && ! command -v wget &> /dev/null; then
|
||
deps_to_install+=("curl")
|
||
fi
|
||
|
||
# Install missing dependencies
|
||
if [[ ${#deps_to_install[@]} -gt 0 ]]; then
|
||
print_info "$(msg installing_deps)"
|
||
|
||
if command -v apt-get &> /dev/null; then
|
||
apt-get update -qq
|
||
apt-get install -y -qq "${deps_to_install[@]}"
|
||
elif command -v yum &> /dev/null; then
|
||
yum install -y -q "${deps_to_install[@]}"
|
||
elif command -v dnf &> /dev/null; then
|
||
dnf install -y -q "${deps_to_install[@]}"
|
||
elif command -v pacman &> /dev/null; then
|
||
pacman -Sy --noconfirm "${deps_to_install[@]}"
|
||
fi
|
||
fi
|
||
|
||
print_success "$(msg deps_ok)"
|
||
}
|
||
|
||
get_latest_version() {
|
||
# Get latest version from GitHub API
|
||
local api_url="https://api.github.com/repos/${GITHUB_REPO}/releases/latest"
|
||
local version=""
|
||
|
||
if command -v curl &> /dev/null; then
|
||
version=$(curl -fsSL "$api_url" | grep '"tag_name":' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/' 2>/dev/null)
|
||
else
|
||
version=$(wget -qO- "$api_url" | grep '"tag_name":' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/' 2>/dev/null)
|
||
fi
|
||
|
||
if [[ -z "$version" ]]; then
|
||
print_error "Failed to get latest version from GitHub"
|
||
exit 1
|
||
fi
|
||
|
||
echo "$version"
|
||
}
|
||
|
||
|
||
check_existing_install() {
|
||
local server_path="$INSTALL_DIR/drip"
|
||
|
||
if [[ -f "$server_path" ]]; then
|
||
local current_version=$(get_version_from_binary "$server_path")
|
||
|
||
print_warning "$(msg already_installed): $server_path"
|
||
print_info "$(msg current_version): $current_version"
|
||
|
||
# Check remote version
|
||
print_step "Checking for updates..."
|
||
local latest_version=$(get_latest_version)
|
||
|
||
if [[ "$current_version" == "$latest_version" ]]; then
|
||
print_success "Already up to date ($current_version)"
|
||
exit 0
|
||
else
|
||
print_info "Latest version: $latest_version"
|
||
echo ""
|
||
read -p "$(msg update_now) [Y/n]: " update_choice < /dev/tty
|
||
fi
|
||
|
||
if [[ "$update_choice" =~ ^[Nn]$ ]]; then
|
||
print_info "$(msg skip_update)"
|
||
exit 0
|
||
fi
|
||
|
||
IS_UPDATE=true
|
||
|
||
# Stop service if running
|
||
if systemctl is-active --quiet drip-server 2>/dev/null; then
|
||
print_info "Stopping drip-server service..."
|
||
systemctl stop drip-server
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Download and install
|
||
# ============================================================================
|
||
download_binary() {
|
||
# Get latest version if not set
|
||
if [[ -z "$VERSION" ]]; then
|
||
VERSION=$(get_latest_version)
|
||
fi
|
||
|
||
if [[ "$IS_UPDATE" == true ]]; then
|
||
print_step "$(msg updating)"
|
||
else
|
||
print_step "$(msg downloading)..."
|
||
fi
|
||
|
||
# Strip 'v' prefix for archive filename (v0.7.0 -> 0.7.0)
|
||
local version_number="${VERSION#v}"
|
||
local archive_name="drip_${version_number}_linux_${ARCH}.tar.gz"
|
||
local download_url="https://github.com/${GITHUB_REPO}/releases/download/${VERSION}/${archive_name}"
|
||
|
||
local tmp_archive="/tmp/drip-archive.tar.gz"
|
||
local tmp_dir="/tmp/drip-extract"
|
||
|
||
# Clean up any previous extraction
|
||
rm -rf "$tmp_dir"
|
||
mkdir -p "$tmp_dir"
|
||
|
||
if command -v curl &> /dev/null; then
|
||
# Use -# for progress bar instead of -s (silent)
|
||
if ! curl -f#L "$download_url" -o "$tmp_archive"; then
|
||
print_error "$(msg download_failed): $download_url"
|
||
exit 1
|
||
fi
|
||
else
|
||
# Use --show-progress to display download progress
|
||
if ! wget --show-progress "$download_url" -O "$tmp_archive" 2>&1 | grep -v "^$"; then
|
||
print_error "$(msg download_failed): $download_url"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# Extract the archive
|
||
if ! tar -xzf "$tmp_archive" -C "$tmp_dir"; then
|
||
print_error "Failed to extract archive"
|
||
exit 1
|
||
fi
|
||
|
||
# Find the binary (it should be named 'drip')
|
||
local extracted_binary
|
||
extracted_binary=$(find "$tmp_dir" -name "drip" -type f 2>/dev/null | head -1)
|
||
|
||
if [[ -z "$extracted_binary" ]]; then
|
||
print_error "Binary not found in archive"
|
||
exit 1
|
||
fi
|
||
|
||
# Move to standard location
|
||
mv "$extracted_binary" /tmp/drip
|
||
chmod +x /tmp/drip
|
||
|
||
# Clean up
|
||
rm -rf "$tmp_archive" "$tmp_dir"
|
||
|
||
print_success "$(msg download_ok)"
|
||
}
|
||
|
||
install_binary() {
|
||
print_step "$(msg installing)"
|
||
|
||
mkdir -p "$INSTALL_DIR"
|
||
mv /tmp/drip "$INSTALL_DIR/drip"
|
||
chmod +x "$INSTALL_DIR/drip"
|
||
|
||
if [[ "$IS_UPDATE" == true ]]; then
|
||
print_success "$(msg update_ok): $INSTALL_DIR/drip"
|
||
else
|
||
print_success "$(msg install_ok): $INSTALL_DIR/drip"
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Configuration
|
||
# ============================================================================
|
||
generate_token() {
|
||
# Generate a random 32-character token (16 bytes = 32 hex chars)
|
||
if command -v openssl &> /dev/null; then
|
||
openssl rand -hex 16
|
||
else
|
||
head -c 16 /dev/urandom | xxd -p
|
||
fi
|
||
}
|
||
|
||
configure_server() {
|
||
print_subheader "$(msg config_title)"
|
||
|
||
# Domain
|
||
while true; do
|
||
read -p "$(msg enter_domain): " DOMAIN < /dev/tty
|
||
if [[ -n "$DOMAIN" ]]; then
|
||
break
|
||
fi
|
||
print_error "$(msg domain_required)"
|
||
done
|
||
|
||
# Port
|
||
read -p "$(msg enter_port) [$DEFAULT_PORT]: " PORT < /dev/tty
|
||
PORT="${PORT:-$DEFAULT_PORT}"
|
||
|
||
# Public port (for URL display, e.g., behind reverse proxy)
|
||
read -p "$(msg enter_public_port) [$PORT]: " PUBLIC_PORT < /dev/tty
|
||
PUBLIC_PORT="${PUBLIC_PORT:-$PORT}"
|
||
|
||
# TCP port range
|
||
echo ""
|
||
print_info "$(msg tcp_port_range)"
|
||
read -p "$(msg enter_tcp_min) [$DEFAULT_TCP_PORT_MIN]: " TCP_PORT_MIN < /dev/tty
|
||
TCP_PORT_MIN="${TCP_PORT_MIN:-$DEFAULT_TCP_PORT_MIN}"
|
||
|
||
read -p "$(msg enter_tcp_max) [$DEFAULT_TCP_PORT_MAX]: " TCP_PORT_MAX < /dev/tty
|
||
TCP_PORT_MAX="${TCP_PORT_MAX:-$DEFAULT_TCP_PORT_MAX}"
|
||
|
||
# Authentication token (user can provide or auto-generate)
|
||
echo ""
|
||
read -p "$(msg enter_token): " TOKEN < /dev/tty
|
||
if [[ -z "$TOKEN" ]]; then
|
||
TOKEN=$(generate_token)
|
||
print_success "$(msg token_generated): $TOKEN"
|
||
fi
|
||
|
||
# Metrics token (always auto-generated)
|
||
METRICS_TOKEN=$(generate_token)
|
||
print_success "$(msg metrics_token_generated): $METRICS_TOKEN"
|
||
|
||
# TLS certificate selection
|
||
print_panel "$(msg cert_option_title)" \
|
||
"${GREEN}1)${NC} $(msg cert_option_certbot)" \
|
||
"${GREEN}2)${NC} $(msg cert_option_self)" \
|
||
"${GREEN}3)${NC} $(msg cert_option_provide)"
|
||
|
||
read -p "Select [1]: " cert_choice < /dev/tty
|
||
|
||
case "$cert_choice" in
|
||
2)
|
||
# Self-signed certificate
|
||
CERT_MODE="self_signed"
|
||
CERT_PATH="${CONFIG_DIR}/server.crt"
|
||
KEY_PATH="${CONFIG_DIR}/server.key"
|
||
echo ""
|
||
print_warning "$(msg cert_warning)"
|
||
;;
|
||
3)
|
||
# User provided certificate
|
||
CERT_MODE="provided"
|
||
echo ""
|
||
while true; do
|
||
read -p "$(msg enter_cert_path): " CERT_PATH < /dev/tty
|
||
if [[ -f "$CERT_PATH" ]]; then
|
||
break
|
||
fi
|
||
print_error "$(msg cert_not_found): $CERT_PATH"
|
||
done
|
||
|
||
while true; do
|
||
read -p "$(msg enter_key_path): " KEY_PATH < /dev/tty
|
||
if [[ -f "$KEY_PATH" ]]; then
|
||
break
|
||
fi
|
||
print_error "$(msg key_not_found): $KEY_PATH"
|
||
done
|
||
print_success "Certificate files verified"
|
||
;;
|
||
*)
|
||
# Let's Encrypt (default)
|
||
CERT_MODE="certbot"
|
||
CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem"
|
||
KEY_PATH="/etc/letsencrypt/live/${DOMAIN}/privkey.pem"
|
||
echo ""
|
||
print_warning "$(msg certbot_domain_note)"
|
||
echo ""
|
||
read -p "$(msg enter_email): " CERTBOT_EMAIL < /dev/tty
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# ============================================================================
|
||
# Certificate
|
||
# ============================================================================
|
||
setup_certificate() {
|
||
case "$CERT_MODE" in
|
||
"certbot")
|
||
obtain_letsencrypt_cert
|
||
;;
|
||
"self_signed")
|
||
generate_self_signed_cert
|
||
;;
|
||
"provided")
|
||
# Certificate already verified in configure_server
|
||
print_success "Using provided certificate"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
obtain_letsencrypt_cert() {
|
||
# Let's Encrypt original cert paths
|
||
local le_cert_path="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem"
|
||
local le_key_path="/etc/letsencrypt/live/${DOMAIN}/privkey.pem"
|
||
|
||
# We'll copy certs to CONFIG_DIR for proper permissions
|
||
# Update global CERT_PATH and KEY_PATH
|
||
CERT_PATH="${CONFIG_DIR}/server.crt"
|
||
KEY_PATH="${CONFIG_DIR}/server.key"
|
||
|
||
# Check if certificate already exists (either in Let's Encrypt or our copy)
|
||
if [[ -f "$CERT_PATH" ]] && [[ -f "$KEY_PATH" ]]; then
|
||
print_warning "$(msg cert_exists)"
|
||
return
|
||
fi
|
||
|
||
# Check if Let's Encrypt cert exists but we just need to copy it
|
||
if [[ -f "$le_cert_path" ]]; then
|
||
print_info "Let's Encrypt certificate found, copying with proper permissions..."
|
||
copy_letsencrypt_cert "$le_cert_path" "$le_key_path"
|
||
return
|
||
fi
|
||
|
||
print_step "$(msg obtaining_cert)"
|
||
|
||
# Install certbot if not present
|
||
if ! command -v certbot &> /dev/null; then
|
||
print_info "Installing certbot..."
|
||
if command -v apt-get &> /dev/null; then
|
||
apt-get update -qq
|
||
apt-get install -y -qq certbot
|
||
elif command -v yum &> /dev/null; then
|
||
yum install -y -q certbot
|
||
elif command -v dnf &> /dev/null; then
|
||
dnf install -y -q certbot
|
||
elif command -v pacman &> /dev/null; then
|
||
pacman -Sy --noconfirm certbot
|
||
fi
|
||
fi
|
||
|
||
# Stop services that might use port 80
|
||
local nginx_was_running=false
|
||
local apache_was_running=false
|
||
|
||
if systemctl is-active --quiet nginx 2>/dev/null; then
|
||
systemctl stop nginx
|
||
nginx_was_running=true
|
||
fi
|
||
|
||
if systemctl is-active --quiet apache2 2>/dev/null; then
|
||
systemctl stop apache2
|
||
apache_was_running=true
|
||
fi
|
||
|
||
if systemctl is-active --quiet httpd 2>/dev/null; then
|
||
systemctl stop httpd
|
||
apache_was_running=true
|
||
fi
|
||
|
||
# Build certbot command
|
||
local certbot_args="certonly --standalone -d ${DOMAIN}"
|
||
if [[ -n "$CERTBOT_EMAIL" ]]; then
|
||
certbot_args="$certbot_args --email ${CERTBOT_EMAIL}"
|
||
else
|
||
certbot_args="$certbot_args --register-unsafely-without-email"
|
||
fi
|
||
certbot_args="$certbot_args --agree-tos --non-interactive"
|
||
|
||
# Obtain certificate
|
||
if certbot $certbot_args; then
|
||
print_success "$(msg cert_obtained)"
|
||
else
|
||
print_error "$(msg cert_failed)"
|
||
# Restart services before exit
|
||
[[ "$nginx_was_running" == true ]] && systemctl start nginx
|
||
[[ "$apache_was_running" == true ]] && (systemctl start apache2 2>/dev/null || systemctl start httpd 2>/dev/null)
|
||
exit 1
|
||
fi
|
||
|
||
# Restart services
|
||
[[ "$nginx_was_running" == true ]] && systemctl start nginx
|
||
[[ "$apache_was_running" == true ]] && (systemctl start apache2 2>/dev/null || systemctl start httpd 2>/dev/null)
|
||
|
||
# Copy certificates with proper permissions
|
||
copy_letsencrypt_cert "$le_cert_path" "$le_key_path"
|
||
|
||
# Create renewal hook for automatic certificate updates
|
||
create_certbot_renewal_hook
|
||
}
|
||
|
||
copy_letsencrypt_cert() {
|
||
local le_cert="$1"
|
||
local le_key="$2"
|
||
|
||
print_step "$(msg copying_cert)"
|
||
|
||
# Ensure config directory exists
|
||
mkdir -p "$CONFIG_DIR"
|
||
|
||
# Copy certificates
|
||
cp -L "$le_cert" "$CERT_PATH"
|
||
cp -L "$le_key" "$KEY_PATH"
|
||
|
||
# Set proper permissions (readable by drip user)
|
||
chmod 644 "$CERT_PATH"
|
||
chmod 640 "$KEY_PATH"
|
||
chown root:"$SERVICE_USER" "$KEY_PATH"
|
||
|
||
print_success "$(msg cert_copied)"
|
||
print_info "Certificate: $CERT_PATH"
|
||
print_info "Private Key: $KEY_PATH"
|
||
}
|
||
|
||
create_certbot_renewal_hook() {
|
||
print_step "$(msg creating_renewal_hook)"
|
||
|
||
local hook_dir="/etc/letsencrypt/renewal-hooks/deploy"
|
||
mkdir -p "$hook_dir"
|
||
|
||
cat > "${hook_dir}/drip-server.sh" << 'HOOK_EOF'
|
||
#!/bin/bash
|
||
# Drip Server certificate renewal hook
|
||
# This script copies renewed certificates and restarts the service
|
||
|
||
DOMAIN_DIR="/etc/letsencrypt/live"
|
||
CONFIG_DIR="/etc/drip"
|
||
SERVICE_USER="drip"
|
||
|
||
# Find the domain from the renewed certificate
|
||
for domain_path in "$DOMAIN_DIR"/*; do
|
||
if [[ -d "$domain_path" ]]; then
|
||
domain=$(basename "$domain_path")
|
||
le_cert="${domain_path}/fullchain.pem"
|
||
le_key="${domain_path}/privkey.pem"
|
||
|
||
if [[ -f "$le_cert" ]] && [[ -f "$le_key" ]]; then
|
||
# Copy certificates
|
||
cp -L "$le_cert" "${CONFIG_DIR}/server.crt"
|
||
cp -L "$le_key" "${CONFIG_DIR}/server.key"
|
||
|
||
# Set proper permissions
|
||
chmod 644 "${CONFIG_DIR}/server.crt"
|
||
chmod 640 "${CONFIG_DIR}/server.key"
|
||
chown root:"$SERVICE_USER" "${CONFIG_DIR}/server.key"
|
||
|
||
# Restart service
|
||
systemctl restart drip-server 2>/dev/null || true
|
||
break
|
||
fi
|
||
fi
|
||
done
|
||
HOOK_EOF
|
||
|
||
chmod +x "${hook_dir}/drip-server.sh"
|
||
print_success "$(msg renewal_hook_created)"
|
||
}
|
||
|
||
generate_self_signed_cert() {
|
||
# Check if certificate already exists
|
||
if [[ -f "$CERT_PATH" ]] && [[ -f "$KEY_PATH" ]]; then
|
||
print_warning "$(msg cert_exists)"
|
||
return
|
||
fi
|
||
|
||
print_step "$(msg generating_cert)"
|
||
|
||
# Ensure config directory exists
|
||
mkdir -p "$CONFIG_DIR"
|
||
|
||
# Generate 10-year self-signed ECDSA certificate
|
||
# Country: US (United States)
|
||
openssl ecparam -genkey -name prime256v1 -out "$KEY_PATH" 2>/dev/null
|
||
|
||
openssl req -new -x509 \
|
||
-key "$KEY_PATH" \
|
||
-out "$CERT_PATH" \
|
||
-days 3650 \
|
||
-subj "/C=US/ST=California/L=San Francisco/O=Drip Tunnel/OU=Server/CN=${DOMAIN}" \
|
||
-addext "subjectAltName=DNS:${DOMAIN},DNS:*.${DOMAIN}" \
|
||
2>/dev/null
|
||
|
||
# Set proper permissions
|
||
chmod 600 "$KEY_PATH"
|
||
chmod 644 "$CERT_PATH"
|
||
|
||
print_success "$(msg cert_generated)"
|
||
print_info "Certificate: $CERT_PATH"
|
||
print_info "Private Key: $KEY_PATH"
|
||
}
|
||
|
||
# ============================================================================
|
||
# System setup
|
||
# ============================================================================
|
||
create_service_user() {
|
||
print_step "$(msg creating_user)"
|
||
|
||
if id "$SERVICE_USER" &>/dev/null; then
|
||
print_warning "$(msg user_exists): $SERVICE_USER"
|
||
else
|
||
useradd -r -s /bin/false "$SERVICE_USER"
|
||
print_success "$(msg user_created): $SERVICE_USER"
|
||
fi
|
||
}
|
||
|
||
create_directories() {
|
||
mkdir -p "$CONFIG_DIR"
|
||
mkdir -p "$WORK_DIR"
|
||
chown "$SERVICE_USER:$SERVICE_USER" "$WORK_DIR"
|
||
}
|
||
|
||
create_systemd_service() {
|
||
print_step "$(msg creating_service)"
|
||
|
||
cat > /etc/systemd/system/drip-server.service << EOF
|
||
[Unit]
|
||
Description=Drip Tunnel Server
|
||
After=network.target
|
||
Documentation=https://github.com/Gouryella/drip
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=${SERVICE_USER}
|
||
Group=${SERVICE_USER}
|
||
Restart=on-failure
|
||
RestartSec=5s
|
||
|
||
# Resource limits
|
||
LimitNOFILE=65536
|
||
LimitNPROC=4096
|
||
|
||
# Working directory
|
||
WorkingDirectory=${WORK_DIR}
|
||
|
||
# Start command (uses config file)
|
||
ExecStart=${INSTALL_DIR}/drip server --config ${CONFIG_DIR}/config.yaml
|
||
|
||
# Logging
|
||
StandardOutput=journal
|
||
StandardError=journal
|
||
SyslogIdentifier=drip-server
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
|
||
systemctl daemon-reload
|
||
print_success "$(msg service_created)"
|
||
}
|
||
|
||
configure_firewall() {
|
||
print_step "$(msg configuring_firewall)"
|
||
|
||
local ports_to_open=("$PORT" "$TCP_PORT_MIN:$TCP_PORT_MAX")
|
||
|
||
# UFW
|
||
if command -v ufw &> /dev/null; then
|
||
ufw allow "$PORT/tcp" >/dev/null 2>&1 || true
|
||
ufw allow "${TCP_PORT_MIN}:${TCP_PORT_MAX}/tcp" >/dev/null 2>&1 || true
|
||
fi
|
||
|
||
# firewalld
|
||
if command -v firewall-cmd &> /dev/null; then
|
||
firewall-cmd --permanent --add-port="$PORT/tcp" >/dev/null 2>&1 || true
|
||
firewall-cmd --permanent --add-port="${TCP_PORT_MIN}-${TCP_PORT_MAX}/tcp" >/dev/null 2>&1 || true
|
||
firewall-cmd --reload >/dev/null 2>&1 || true
|
||
fi
|
||
|
||
print_success "$(msg firewall_ok)"
|
||
}
|
||
|
||
save_config() {
|
||
print_step "$(msg saving_config)"
|
||
|
||
cat > "$CONFIG_DIR/config.yaml" << EOF
|
||
# Drip Server Configuration
|
||
# Generated: $(date)
|
||
# DO NOT SHARE THIS FILE - Contains sensitive information
|
||
|
||
# Server settings
|
||
port: ${PORT}
|
||
public_port: ${PUBLIC_PORT}
|
||
domain: ${DOMAIN}
|
||
|
||
# Authentication
|
||
token: ${TOKEN}
|
||
metrics_token: ${METRICS_TOKEN}
|
||
|
||
# TLS certificate paths
|
||
tls_cert: ${CERT_PATH}
|
||
tls_key: ${KEY_PATH}
|
||
|
||
# TCP tunnel port range
|
||
tcp_port_min: ${TCP_PORT_MIN}
|
||
tcp_port_max: ${TCP_PORT_MAX}
|
||
EOF
|
||
|
||
chmod 640 "$CONFIG_DIR/config.yaml"
|
||
chown root:"$SERVICE_USER" "$CONFIG_DIR/config.yaml"
|
||
print_success "$(msg config_saved): $CONFIG_DIR/config.yaml"
|
||
}
|
||
|
||
# ============================================================================
|
||
# Service management
|
||
# ============================================================================
|
||
start_service() {
|
||
read -p "$(msg start_now) [Y/n]: " start_choice < /dev/tty
|
||
|
||
if [[ "$start_choice" =~ ^[Nn]$ ]]; then
|
||
return
|
||
fi
|
||
|
||
print_step "$(msg starting_service)"
|
||
|
||
systemctl enable drip-server >/dev/null 2>&1
|
||
systemctl start drip-server
|
||
|
||
sleep 2
|
||
|
||
if systemctl is-active --quiet drip-server; then
|
||
print_success "$(msg service_started)"
|
||
else
|
||
print_error "$(msg service_failed)"
|
||
echo ""
|
||
journalctl -u drip-server -n 20 --no-pager
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Final output
|
||
# ============================================================================
|
||
show_completion() {
|
||
print_panel "$(msg install_complete)"
|
||
|
||
echo -e "${CYAN}$(msg client_info):${NC}"
|
||
echo -e " ${BOLD}$(msg server_addr):${NC} ${DOMAIN}:${PORT}"
|
||
echo -e " ${BOLD}$(msg token_label):${NC} ${TOKEN}"
|
||
echo -e " ${BOLD}$(msg metrics_token_label):${NC} ${METRICS_TOKEN}"
|
||
echo ""
|
||
|
||
echo -e "${CYAN}$(msg service_commands):${NC}"
|
||
echo -e " ${GREEN}$(msg cmd_start):${NC} systemctl start drip-server"
|
||
echo -e " ${GREEN}$(msg cmd_stop):${NC} systemctl stop drip-server"
|
||
echo -e " ${GREEN}$(msg cmd_restart):${NC} systemctl restart drip-server"
|
||
echo -e " ${GREEN}$(msg cmd_status):${NC} systemctl status drip-server"
|
||
echo -e " ${GREEN}$(msg cmd_logs):${NC} journalctl -u drip-server -f"
|
||
echo -e " ${GREEN}$(msg cmd_enable):${NC} systemctl enable drip-server"
|
||
echo ""
|
||
}
|
||
|
||
# ============================================================================
|
||
# Main
|
||
# ============================================================================
|
||
main() {
|
||
clear
|
||
print_banner
|
||
if [[ "$SKIP_LANG_PROMPT" == "true" ]]; then
|
||
LANG_CODE="${LANG_CODE:-en}"
|
||
else
|
||
select_language
|
||
fi
|
||
|
||
echo -e "${BOLD}────────────────────────────────────────────${NC}"
|
||
|
||
check_root
|
||
check_os
|
||
check_arch
|
||
check_dependencies
|
||
check_existing_install
|
||
|
||
echo ""
|
||
download_binary
|
||
install_binary
|
||
ensure_command_access
|
||
|
||
# If updating, check if systemd service exists
|
||
if [[ "$IS_UPDATE" == true ]]; then
|
||
# Check if systemd service file exists
|
||
if [[ -f /etc/systemd/system/drip-server.service ]]; then
|
||
echo ""
|
||
print_step "Restarting drip-server service..."
|
||
systemctl start drip-server
|
||
|
||
if systemctl is-active --quiet drip-server; then
|
||
print_success "$(msg service_started)"
|
||
else
|
||
print_error "$(msg service_failed)"
|
||
journalctl -u drip-server -n 20 --no-pager
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
local new_version=$(get_version_from_binary "$INSTALL_DIR/drip")
|
||
print_panel "$(msg update_ok)"
|
||
print_info "$(msg current_version): $new_version"
|
||
echo ""
|
||
exit 0
|
||
else
|
||
# Service file doesn't exist, need to configure
|
||
print_warning "$(msg service_not_found)"
|
||
IS_UPDATE=false
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
configure_server
|
||
|
||
echo ""
|
||
setup_certificate
|
||
create_service_user
|
||
create_directories
|
||
create_systemd_service
|
||
configure_firewall
|
||
save_config
|
||
|
||
echo ""
|
||
start_service
|
||
|
||
show_completion
|
||
}
|
||
|
||
# Run
|
||
main "$@"
|