Files
dock/ru
2025-10-21 14:26:53 +08:00

774 lines
24 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 设置错误处理
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # 无颜色
# 日志函数
log_info() {
echo -e "${BLUE}[信息]${NC} $1"
}
log_success() {
echo -e "${GREEN}[成功]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[警告]${NC} $1"
}
log_error() {
echo -e "${RED}[错误]${NC} $1"
}
# 检查端口是否被占用
check_port() {
local port=$1
local protocol=${2:-tcp}
if [[ "$port" -lt 1024 || "$port" -gt 65535 ]]; then
log_error "端口号 $port 超出范围 (1024-65535)"
return 1
fi
local port_in_use=false
# 检查端口占用
if command -v netstat &>/dev/null; then
if netstat -tuln | grep -q ":${port}[[:space:]]"; then
log_warning "端口 $port 被占用 (netstat)"
port_in_use=true
fi
fi
if command -v ss &>/dev/null; then
if ss -tuln | grep -q ":${port}[[:space:]]"; then
log_warning "端口 $port 被占用 (ss)"
port_in_use=true
fi
fi
# 检查 Docker 容器
if command -v docker &>/dev/null; then
if docker ps --format "table {{.Ports}}" | grep -q ":${port}->"; then
log_warning "端口 $port 被 Docker 容器占用"
port_in_use=true
fi
fi
if [[ "$port_in_use" == "true" ]]; then
return 1
fi
return 0
}
# 检查 Docker 是否安装
check_docker() {
log_info "检查 Docker 环境..."
if ! command -v docker &>/dev/null; then
log_error "Docker 未安装,请先安装 Docker"
exit 1
fi
if ! docker info &>/dev/null; then
log_error "Docker 服务异常,请检查 Docker 状态"
exit 1
fi
local compose_available=false
if command -v docker-compose &>/dev/null; then
compose_available=true
log_info "使用 docker-compose"
elif docker compose version &>/dev/null; then
compose_available=true
log_info "使用 docker compose"
fi
if [[ "$compose_available" == "false" ]]; then
log_error "Docker Compose 未安装"
exit 1
fi
log_success "Docker 环境检查通过"
}
# 创建目录结构
create_directories() {
local dirs=("/data/rustdesk/server" "/data/rustdesk/api" "/data/rustdesk/db")
for dir in "${dirs[@]}"; do
if [[ ! -d "$dir" ]]; then
sudo mkdir -p "$dir"
log_info "创建目录: $dir"
else
log_info "目录已存在: $dir"
fi
done
sudo chmod 755 /data/rustdesk
sudo chmod 755 /data/rustdesk/server
sudo chmod 755 /data/rustdesk/api
sudo chmod 755 /data/rustdesk/db
if [[ "$(id -u)" -ne 0 ]]; then
sudo chown -R "$(id -u):$(id -g)" /data/rustdesk
fi
}
# 修复密钥对问题
fix_keypair() {
local server_dir="/data/rustdesk/server"
log_info "检查并修复密钥对问题..."
# 备份现有密钥(如果存在)
if [[ -f "$server_dir/id_ed25519" || -f "$server_dir/id_ed25519.pub" ]]; then
log_warning "发现现有密钥文件,进行备份..."
local backup_dir="/data/rustdesk/backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"
cp -f "$server_dir/id_ed25519" "$backup_dir/" 2>/dev/null || true
cp -f "$server_dir/id_ed25519.pub" "$backup_dir/" 2>/dev/null || true
log_info "旧密钥已备份到: $backup_dir"
fi
# 删除现有密钥文件
rm -f "$server_dir/id_ed25519" "$server_dir/id_ed25519.pub"
# 生成新的密钥对
log_info "生成新的 RustDesk 密钥对..."
local max_retries=3
local retry_count=0
while [[ $retry_count -lt $max_retries ]]; do
if docker run --rm --entrypoint /usr/bin/rustdesk-utils lejianwen/rustdesk-server-s6:latest genkeypair > /tmp/rustdesk_keys 2>/dev/null; then
# 解析生成的密钥
KEY_PUB=$(grep "Public Key:" /tmp/rustdesk_keys | awk '{print $3}')
KEY_PRIV=$(grep "Secret Key:" /tmp/rustdesk_keys | awk '{print $3}')
# 验证密钥格式
if [[ -n "$KEY_PUB" && -n "$KEY_PRIV" ]]; then
# 保存密钥到文件
echo "$KEY_PUB" > "$server_dir/id_ed25519.pub"
echo "$KEY_PRIV" > "$server_dir/id_ed25519"
# 设置文件权限
chmod 600 "$server_dir/id_ed25519"
chmod 644 "$server_dir/id_ed25519.pub"
# 验证密钥文件
if [[ -f "$server_dir/id_ed25519" && -f "$server_dir/id_ed25519.pub" ]]; then
local pub_content=$(cat "$server_dir/id_ed25519.pub")
local priv_content=$(cat "$server_dir/id_ed25519")
if [[ "$pub_content" == "$KEY_PUB" && "$priv_content" == "$KEY_PRIV" ]]; then
log_success "密钥对生成并验证成功"
log_info "公钥: $KEY_PUB"
log_info "私钥: ${KEY_PRIV:0:20}..."
rm -f /tmp/rustdesk_keys
return 0
fi
fi
fi
fi
retry_count=$((retry_count + 1))
log_warning "密钥生成失败,重试第 $retry_count 次..."
sleep 2
done
log_error "密钥对生成失败,使用空密钥(容器将自行生成)"
KEY_PUB=""
KEY_PRIV=""
rm -f /tmp/rustdesk_keys
return 1
}
# 生成随机密码
generate_password() {
local length=12
tr -dc 'A-Za-z0-9!@#$%' < /dev/urandom | head -c $length
}
# 检查端口占用情况
check_ports_availability() {
local ports=("$api_port" "$hbbs_port" "$hbbr_port" "21115" "21118" "21119")
log_info "检查端口占用情况..."
for port in "${ports[@]}"; do
if ! check_port "$port"; then
log_warning "端口 $port 被占用,可能影响服务启动"
else
log_info "端口 $port 可用"
fi
done
}
# 获取用户输入
get_user_input() {
local default_project="rustdesk-server"
local default_api_port="21114"
local default_hbbs_port="21116"
local default_hbbr_port="21117"
# 读取项目名称
while true; do
read -p "请输入项目名称(默认: $default_project: " input_project
project_name=$(echo "$input_project" | xargs)
project_name=${project_name:-$default_project}
if [[ "$project_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
break
else
log_error "项目名称只能包含字母、数字、连字符和下划线"
fi
done
# 读取API端口
while true; do
read -p "请输入API服务端口默认: $default_api_port: " input_port
api_port=$(echo "$input_port" | xargs)
api_port=${api_port:-$default_api_port}
if [[ "$api_port" =~ ^[0-9]+$ ]] && [[ "$api_port" -ge 1024 && "$api_port" -le 65535 ]]; then
if ! check_port "$api_port"; then
log_warning "端口 $api_port 已被占用"
read -p "是否继续使用此端口?(y/N): " use_occupied_port
if [[ "$use_occupied_port" =~ ^[Yy]$ ]]; then
break
fi
else
break
fi
else
log_error "端口号必须是 1024-65535 之间的数字"
fi
done
# 读取ID服务器端口
while true; do
read -p "请输入ID服务器端口默认: $default_hbbs_port: " input_port
hbbs_port=$(echo "$input_port" | xargs)
hbbs_port=${hbbs_port:-$default_hbbs_port}
if [[ "$hbbs_port" =~ ^[0-9]+$ ]] && [[ "$hbbs_port" -ge 1024 && "$hbbs_port" -le 65535 ]]; then
if ! check_port "$hbbs_port"; then
log_warning "端口 $hbbs_port 已被占用"
read -p "是否继续使用此端口?(y/N): " use_occupied_port
if [[ "$use_occupied_port" =~ ^[Yy]$ ]]; then
break
fi
else
break
fi
else
log_error "端口号必须是 1024-65535 之间的数字"
fi
done
# 读取中继服务器端口
while true; do
read -p "请输入中继服务器端口(默认: $default_hbbr_port: " input_port
hbbr_port=$(echo "$input_port" | xargs)
hbbr_port=${hbbr_port:-$default_hbbr_port}
if [[ "$hbbr_port" =~ ^[0-9]+$ ]] && [[ "$hbbr_port" -ge 1024 && "$hbbr_port" -le 65535 ]]; then
if ! check_port "$hbbr_port"; then
log_warning "端口 $hbbr_port 已被占用"
read -p "是否继续使用此端口?(y/N): " use_occupied_port
if [[ "$use_occupied_port" =~ ^[Yy]$ ]]; then
break
fi
else
break
fi
else
log_error "端口号必须是 1024-65535 之间的数字"
fi
done
# 询问是否使用随机密码
read -p "是否生成随机管理员密码?(y/N): " use_random_pwd
if [[ "$use_random_pwd" =~ ^[Yy]$ ]]; then
admin_password=$(generate_password)
log_info "已生成随机密码"
else
while true; do
read -sp "请输入管理员密码(最少 8 位): " admin_password
echo
if [[ -n "$admin_password" && ${#admin_password} -ge 8 ]]; then
read -sp "请确认管理员密码: " admin_password_confirm
echo
if [[ "$admin_password" == "$admin_password_confirm" ]]; then
break
else
log_error "两次输入的密码不一致"
fi
else
log_error "密码不能为空且至少 8 位"
fi
done
fi
# 获取本机 IP
local_ip=$(hostname -I | awk '{print $1}')
public_ip=$(curl -s --connect-timeout 5 ifconfig.me || echo "无法获取公网IP")
echo
log_info "配置摘要:"
log_info "项目名称: $project_name"
log_info "API服务端口: $api_port"
log_info "ID服务器端口: $hbbs_port"
log_info "中继服务器端口: $hbbr_port"
log_info "管理员密码: ${admin_password:0:4}******"
log_info "本地 IP: $local_ip"
log_info "公网 IP: $public_ip"
echo
read -p "确认开始部署?(Y/n): " confirm
if [[ "$confirm" =~ ^[Nn]$ ]]; then
log_info "部署已取消"
exit 0
fi
}
# 生成 Docker Compose 配置文件
generate_compose_file() {
local project_name="$1"
local api_port="$2"
local hbbs_port="$3"
local hbbr_port="$4"
local admin_password="$5"
local key_pub="$6"
local key_priv="$7"
# 生成 JWT 密钥
local jwt_key=$(openssl rand -base64 32 2>/dev/null || echo "default_jwt_key_$(date +%s)")
local file_path="/data/rustdesk/docker-compose.yml"
# 如果密钥为空,则不设置密钥环境变量,让容器自己生成
local key_envs=""
if [[ -n "$key_pub" && -n "$key_priv" ]]; then
key_envs=" - KEY_PUB=$key_pub
- KEY_PRIV=$key_priv"
log_info "使用自定义密钥对"
else
log_info "未提供密钥对,容器将自动生成"
fi
cat > "$file_path" << EOF
# RustDesk Server 配置
# 生成时间: $(date)
# 项目名称: $project_name
networks:
rustdesk-net:
driver: bridge
services:
rustdesk-server:
container_name: ${project_name}-rustdesk
hostname: ${project_name}-server
image: lejianwen/rustdesk-server-s6:latest
ports:
- "${api_port}:21114" # API 服务器
- "21115:21115" # 其他服务
- "${hbbs_port}:21116" # ID 服务器 (hbbs)
- "${hbbr_port}:21117" # 中继服务器 (hbbr)
- "21118:21118" # 其他服务
- "21119:21119" # 其他服务
- "${hbbs_port}:21116/udp" # UDP 端口
environment:
- RELAY=${local_ip}:${hbbr_port}
- ENCRYPTED_ONLY=1
- MUST_LOGIN=n
- TZ=Asia/Shanghai
# 设置中文语言
- LANG=zh_CN.UTF-8
- LANGUAGE=zh_CN:zh
- LC_ALL=zh_CN.UTF-8
# 自定义端口 - 关键配置!
- PORT=${hbbs_port}
- BIND_PORT=${hbbr_port}
# 强制所有连接通过中继服务器
- ALWAYS_USE_RELAY=Y
# 密钥配置
$key_envs
# API 配置
- RUSTDESK_API_RUSTDESK_ID_SERVER=${local_ip}:${hbbs_port}
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=${local_ip}:${hbbr_port}
- RUSTDESK_API_RUSTDESK_API_SERVER=http://${local_ip}:${api_port}
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
- RUSTDESK_API_JWT_KEY=${jwt_key}
# 数据库配置
- DB_URL=/db/db_v2.sqlite3
volumes:
- /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data
- /data/rustdesk/db:/db
networks:
- rustdesk-net
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:21114"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
EOF
log_success "Docker Compose 配置文件已生成: $file_path"
}
# 检查容器运行状态
check_container_health() {
local container_name="$1"
local max_checks=20
local check_interval=5
log_info "检查容器 $container_name 运行状态..."
for ((i=1; i<=max_checks; i++)); do
# 检查容器是否存在
if ! docker ps -a | grep -q "$container_name"; then
log_error "容器 $container_name 不存在"
return 1
fi
# 检查容器状态
local status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
case "$status" in
"running")
# 检查关键进程
if docker exec "$container_name" pgrep hbbs >/dev/null 2>&1 && \
docker exec "$container_name" pgrep hbbr >/dev/null 2>&1; then
log_success "容器运行正常 (关键进程运行中)"
# 额外检查服务是否真正就绪
if docker exec "$container_name" curl -f http://localhost:21114 >/dev/null 2>&1; then
log_success "API 服务就绪"
return 0
else
log_info "等待API服务就绪... ($i/$max_checks)"
fi
elif [[ $i -gt 5 ]]; then
log_info "等待关键进程启动... ($i/$max_checks)"
fi
;;
"exited")
local exit_code=$(docker inspect --format='{{.State.ExitCode}}' "$container_name")
log_error "容器已退出,退出码: $exit_code"
log_info "查看容器日志:"
docker logs "$container_name" | tail -30
return 1
;;
"restarting")
log_info "容器正在重启... ($i/$max_checks)"
;;
*)
log_error "容器状态异常: $status"
return 1
;;
esac
if [[ $i -eq $max_checks ]]; then
log_warning "容器状态检查超时,但继续部署流程"
return 0
fi
sleep $check_interval
done
return 0
}
# 部署服务
deploy_service() {
local project_name="$1"
local admin_password="$2"
local file_path="/data/rustdesk/docker-compose.yml"
log_info "开始部署 RustDesk 服务..."
# 使用 docker-compose 或 docker compose
local compose_cmd
if command -v docker-compose &>/dev/null; then
compose_cmd="docker-compose"
else
compose_cmd="docker compose"
fi
# 停止并删除现有容器(如果存在)
if docker ps -a --filter "name=${project_name}-rustdesk" | grep -q "${project_name}-rustdesk"; then
log_info "停止并删除现有容器..."
cd /data/rustdesk
sudo $compose_cmd -f "$file_path" down || true
sleep 5
fi
# 部署服务
cd /data/rustdesk
log_info "启动服务..."
if ! sudo $compose_cmd -f "$file_path" up -d; then
log_error "服务启动失败"
return 1
fi
sleep 10
# 检查容器运行状态
if ! check_container_health "${project_name}-rustdesk"; then
log_warning "容器启动过程中遇到问题,尝试继续配置..."
fi
# 等待服务完全启动
log_info "等待服务初始化完成..."
sleep 15
# 重置管理员密码
log_info "设置管理员密码..."
local password_retries=5
local password_success=false
for ((i=1; i<=password_retries; i++)); do
if docker exec "${project_name}-rustdesk" sh -c "./apimain reset-admin-pwd \"$admin_password\"" 2>/dev/null; then
log_success "管理员密码设置成功"
password_success=true
break
else
log_warning "密码设置失败,重试第 $i 次... (等待服务完全启动)"
sleep 10
fi
done
if [[ "$password_success" == "false" ]]; then
log_warning "密码设置失败,可能需要手动设置"
log_info "请手动执行: docker exec ${project_name}-rustdesk ./apimain reset-admin-pwd \"$admin_password\""
fi
return 0
}
# 验证服务连通性
test_service_connectivity() {
local api_port="$1"
local hbbs_port="$2"
local hbbr_port="$3"
log_info "测试服务连通性..."
# 测试API服务
if curl -s --connect-timeout 10 "http://localhost:${api_port}" > /dev/null; then
log_success "API 服务连通正常"
else
log_warning "API 服务连通异常"
fi
# 测试端口连通性
if command -v nc &>/dev/null; then
if nc -z -w 3 localhost "$hbbs_port"; then
log_success "ID服务器端口 $hbbs_port 连通正常"
else
log_warning "ID服务器端口 $hbbs_port 连通异常"
fi
if nc -z -w 3 localhost "$hbbr_port"; then
log_success "中继服务器端口 $hbbr_port 连通正常"
else
log_warning "中继服务器端口 $hbbr_port 连通异常"
fi
fi
}
# 验证部署
verify_deployment() {
local project_name="$1"
local api_port="$2"
local hbbs_port="$3"
local hbbr_port="$4"
log_info "验证部署结果..."
# 检查容器状态
if docker ps --filter "name=${project_name}-rustdesk" --format "table {{.Names}}\t{{.Status}}" | grep -q "Up"; then
log_success "容器运行正常"
else
log_error "容器未运行"
return 1
fi
sleep 10
# 检查关键服务进程
if docker exec "${project_name}-rustdesk" pgrep hbbs > /dev/null 2>&1; then
log_success "hbbs (ID服务器) 运行正常"
else
log_warning "hbbs (ID服务器) 异常"
fi
if docker exec "${project_name}-rustdesk" pgrep hbbr > /dev/null 2>&1; then
log_success "hbbr (中继服务器) 运行正常"
else
log_warning "hbbr (中继服务器) 异常"
fi
# 测试服务连通性
test_service_connectivity "$api_port" "$hbbs_port" "$hbbr_port"
# 检查端口监听情况
log_info "检查端口监听状态..."
if netstat -tuln | grep -q ":${hbbs_port}[[:space:]]"; then
log_success "ID服务器端口 $hbbs_port 监听正常"
else
log_warning "ID服务器端口 $hbbs_port 未监听"
fi
if netstat -tuln | grep -q ":${hbbr_port}[[:space:]]"; then
log_success "中继服务器端口 $hbbr_port 监听正常"
else
log_warning "中继服务器端口 $hbbr_port 未监听"
fi
return 0
}
# 显示部署结果
show_deployment_info() {
local project_name="$1"
local api_port="$2"
local hbbs_port="$3"
local hbbr_port="$4"
local admin_password="$5"
local key_pub="$6"
local local_ip=$(hostname -I | awk '{print $1}')
local public_ip=$(curl -s --connect-timeout 5 ifconfig.me || echo "无法获取公网IP")
echo
log_success "🎉 RustDesk 部署完成!"
echo
echo "=================== 访问信息 ==================="
echo -e "Web管理界面: ${GREEN}http://${local_ip}:${api_port}${NC}"
if [[ "$public_ip" != "无法获取公网IP" ]]; then
echo -e "公网访问: ${GREEN}http://${public_ip}:${api_port}${NC}"
fi
echo
echo "=================== 账号信息 ==================="
echo -e "管理员账号: ${GREEN}admin${NC}"
echo -e "管理员密码: ${GREEN}${admin_password}${NC}"
echo
# 重新读取实际的公钥
local actual_pub_key=""
if [[ -f "/data/rustdesk/server/id_ed25519.pub" ]]; then
actual_pub_key=$(cat "/data/rustdesk/server/id_ed25519.pub")
fi
if [[ -n "$actual_pub_key" ]]; then
echo "=================== 密钥信息 ==================="
echo -e "公钥 (KEY): ${GREEN}${actual_pub_key}${NC}"
echo
fi
echo "=================== 服务器配置 ==================="
echo -e "ID 服务器: ${GREEN}${local_ip}:${hbbs_port}${NC}"
echo -e "中继服务器: ${GREEN}${local_ip}:${hbbr_port}${NC}"
echo -e "API 服务器: ${GREEN}http://${local_ip}:${api_port}${NC}"
echo
echo "=================== 客户端配置步骤 ==================="
echo "1. 打开 RustDesk 客户端"
echo "2. 点击 ID/中继服务器 设置"
echo "3. 填写以下信息:"
echo " - ID 服务器: ${local_ip}:${hbbs_port}"
echo " - 中继服务器: ${local_ip}:${hbbr_port}"
if [[ -n "$actual_pub_key" ]]; then
echo " - Key: ${actual_pub_key}"
fi
echo "4. 点击 '应用' 保存"
echo "5. 重启 RustDesk 客户端"
echo "==================================================="
echo
echo "=================== 管理命令 ==================="
echo -e "查看服务状态: ${YELLOW}docker ps -f name=${project_name}${NC}"
echo -e "查看服务日志: ${YELLOW}docker logs ${project_name}-rustdesk${NC}"
echo -e "停止服务: ${YELLOW}cd /data/rustdesk && docker compose down${NC}"
echo -e "重启服务: ${YELLOW}cd /data/rustdesk && docker compose restart${NC}"
echo "================================================"
echo
log_warning "请确保防火墙已开放以下端口:"
echo -e " - API服务端口: ${YELLOW}${api_port}${NC}"
echo -e " - ID服务器端口: ${YELLOW}${hbbs_port}${NC}"
echo -e " - 中继服务器端口: ${YELLOW}${hbbr_port}${NC}"
echo -e " - 其他端口: ${YELLOW}21115, 21118, 21119${NC}"
# 显示诊断信息
echo
echo "=================== 诊断信息 ==================="
log_info "如果客户端显示'未就绪',请检查:"
echo "1. 防火墙端口是否开放"
echo "2. 客户端配置是否正确"
echo "3. 服务日志: docker logs ${project_name}-rustdesk"
echo "================================================"
}
# 主函数
main() {
echo
log_info "开始 RustDesk 服务器部署"
echo "========================================"
# 检查 Docker
check_docker
# 创建目录
create_directories
# 获取用户输入
get_user_input
# 检查端口
check_ports_availability
# 修复密钥对问题(强制重新生成)
fix_keypair
# 生成配置文件
generate_compose_file "$project_name" "$api_port" "$hbbs_port" "$hbbr_port" "$admin_password" "$KEY_PUB" "$KEY_PRIV"
# 部署服务
if deploy_service "$project_name" "$admin_password"; then
# 验证部署
verify_deployment "$project_name" "$api_port" "$hbbs_port" "$hbbr_port"
# 显示部署结果
show_deployment_info "$project_name" "$api_port" "$hbbs_port" "$hbbr_port" "$admin_password" "$KEY_PUB"
log_success "部署脚本执行完成"
# 显示最终检查
echo
log_info "最终状态检查:"
docker ps -f "name=${project_name}-rustdesk"
else
log_error "部署失败,请检查上述错误信息"
log_info "故障排除建议:"
log_info "1. 检查 Docker 日志: docker logs ${project_name}-rustdesk"
log_info "2. 检查端口占用: netstat -tulpn | grep 2111"
log_info "3. 检查防火墙设置"
log_info "4. 尝试手动重启: cd /data/rustdesk && docker compose down && docker compose up -d"
exit 1
fi
}
# 脚本入口
main "$@"