Files
frpc-console/deploy.sh
T

663 lines
19 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
# ============================================================
# frpc-console 一键部署脚本
# 支持:Linux x86_64 / ARM64 / ARMv7
# 自动安装:git / curl / wget / Go / Docker
#
# 用法:
# ./deploy.sh # 完整交互流程
# ./deploy.sh --yes # 跳过确认,直接执行
# ./deploy.sh --check # 只检测环境,不执行
# ./deploy.sh --dry-run # 显示将执行的操作,不实际执行
# ============================================================
set -e
# ---------- 颜色检测 ----------
if [ -t 1 ]; then
# 交互式终端,启用颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'
else
# 非交互式终端(如管道),禁用颜色
RED=''
GREEN=''
YELLOW=''
BLUE=''
CYAN=''
MAGENTA=''
NC=''
fi
# ---------- 配置 ----------
REPO_URL="https://git.whitetop.xyz/lxh2875931338/frpc-console.git"
BRANCH="main"
WORK_DIR="/tmp/frpc-console-build"
DEFAULT_PORT=9300
DEFAULT_DEPLOY_DIR="/opt/frpc-console"
IMAGE_NAME="frpc-console"
GO_VERSION="1.25.0"
# ---------- 状态变量 ----------
OS=""
OS_VERSION=""
ARCH=""
GO_ARCH=""
HAS_GIT=false
HAS_CURL=false
HAS_WGET=false
HAS_GO=false
HAS_DOCKER=false
NEED_INSTALL_GIT=false
NEED_INSTALL_CURL=false
NEED_INSTALL_WGET=false
NEED_INSTALL_GO=false
PORT=${DEFAULT_PORT}
DEPLOY_DIR=${DEFAULT_DEPLOY_DIR}
DATA_DIR="${DEPLOY_DIR}/data"
CONTAINER_EXISTS=false
CONTAINER_RUNNING=false
SKIP_CONFIRM=false
CHECK_ONLY=false
DRY_RUN=false
# ---------- 打印函数 ----------
print_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
print_success() { echo -e "${GREEN}[✓]${NC} $1"; }
print_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
print_error() { echo -e "${RED}[✗]${NC} $1"; }
print_step() { echo -e "\n${CYAN}${NC} $1"; }
print_title() { echo -e "\n${MAGENTA}════════════════════════════════════════════════════════${NC}"; }
print_subtitle() { echo -e "${MAGENTA} $1${NC}"; }
# ---------- 解析命令行参数 ----------
parse_args() {
for arg in "$@"; do
case $arg in
--yes|-y)
SKIP_CONFIRM=true
;;
--check)
CHECK_ONLY=true
;;
--dry-run)
DRY_RUN=true
;;
--help|-h)
echo "用法: ./deploy.sh [选项]"
echo ""
echo "选项:"
echo " --yes, -y 跳过所有确认提示,直接执行"
echo " --check 只检测环境,不执行部署"
echo " --dry-run 显示将执行的操作,不实际执行"
echo " --help, -h 显示帮助信息"
echo ""
echo "示例:"
echo " ./deploy.sh # 完整交互流程"
echo " ./deploy.sh --yes # 无人值守部署"
echo " ./deploy.sh --check # 只检测环境"
exit 0
;;
*)
print_error "未知选项: $arg"
echo "使用 --help 查看帮助"
exit 1
;;
esac
done
}
# ---------- 检查 root ----------
check_root() {
if [ "$EUID" -ne 0 ]; then
print_error "请使用 root 权限运行此脚本"
echo " sudo bash deploy.sh"
exit 1
fi
}
# ---------- 检测操作系统 ----------
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
OS_VERSION=$VERSION_ID
else
print_error "无法识别操作系统"
exit 1
fi
}
# ---------- 检测 CPU 架构 ----------
detect_arch() {
ARCH=$(uname -m)
case $ARCH in
x86_64|amd64)
GO_ARCH="amd64"
;;
aarch64|arm64)
GO_ARCH="arm64"
;;
armv7l|armhf)
GO_ARCH="armv6l"
;;
*)
print_error "不支持的 CPU 架构: $ARCH"
exit 1
;;
esac
}
# ---------- 检测工具 ----------
check_tools() {
# git
if command -v git &> /dev/null; then
HAS_GIT=true
else
NEED_INSTALL_GIT=true
fi
# curl
if command -v curl &> /dev/null; then
HAS_CURL=true
else
NEED_INSTALL_CURL=true
fi
# wget
if command -v wget &> /dev/null; then
HAS_WGET=true
else
NEED_INSTALL_WGET=true
fi
# Go
if command -v go &> /dev/null; then
HAS_GO=true
else
NEED_INSTALL_GO=true
fi
# Docker
if command -v docker &> /dev/null; then
HAS_DOCKER=true
fi
}
# ---------- 检查容器状态 ----------
check_container() {
if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^frpc-console$"; then
CONTAINER_EXISTS=true
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^frpc-console$"; then
CONTAINER_RUNNING=true
fi
fi
}
# ---------- 获取系统包管理器 ----------
get_package_manager() {
case $OS in
opensuse*|suse*|opensuse-tumbleweed|opensuse-slowroll|opensuse-leap)
echo "zypper"
;;
ubuntu|debian|linuxmint)
echo "apt"
;;
centos|rhel|fedora|rocky|almalinux)
echo "yum"
;;
alpine)
echo "apk"
;;
arch|manjaro|endeavouros)
echo "pacman"
;;
*)
echo "unknown"
;;
esac
}
# ---------- 检测结果汇总 ----------
print_environment_summary() {
print_title
print_subtitle "环境检测结果"
echo ""
echo -e " ${CYAN}操作系统:${NC} $OS $OS_VERSION"
echo -e " ${CYAN}CPU 架构:${NC} $ARCH → Go 架构: $GO_ARCH"
echo ""
echo " ${CYAN}必要工具:${NC}"
if [ "$HAS_GIT" = true ]; then
echo -e " git ✅ 已安装 ($(git --version | awk '{print $3}'))"
else
echo " git ❌ 未安装 (将自动安装)"
fi
if [ "$HAS_CURL" = true ]; then
echo " curl ✅ 已安装"
else
echo " curl ❌ 未安装 (将自动安装)"
fi
if [ "$HAS_WGET" = true ]; then
echo " wget ✅ 已安装"
else
echo " wget ❌ 未安装 (将自动安装)"
fi
echo ""
echo " ${CYAN}Go 环境:${NC}"
if [ "$HAS_GO" = true ]; then
echo -e " go ✅ 已安装 ($(go version | awk '{print $3}'))"
else
echo " go ❌ 未安装 (将自动安装 Go ${GO_VERSION})"
fi
echo ""
echo " ${CYAN}Docker 环境:${NC}"
if [ "$HAS_DOCKER" = true ]; then
echo -e " docker ✅ 已安装 ($(docker --version | awk '{print $3}' | tr -d ','))"
else
echo " docker ❌ 未安装"
print_error "Docker 未安装,请先安装 Docker"
echo ""
echo " 快速安装:"
echo " curl -fsSL https://get.docker.com | bash"
echo " systemctl enable --now docker"
echo ""
exit 1
fi
# 容器状态
if [ "$CONTAINER_EXISTS" = true ]; then
echo ""
echo " ${CYAN}容器状态:${NC}"
if [ "$CONTAINER_RUNNING" = true ]; then
echo -e " frpc-console ✅ 运行中 (将停止并重建)"
else
echo -e " frpc-console ⏸️ 已停止 (将重建)"
fi
echo -e " ${YELLOW}数据目录中的数据库文件将被保留${NC}"
fi
echo ""
}
# ---------- 生成操作列表 ----------
generate_plan() {
PLAN=""
if [ "$NEED_INSTALL_GIT" = true ] || [ "$NEED_INSTALL_CURL" = true ] || [ "$NEED_INSTALL_WGET" = true ]; then
local pkgs=""
[ "$NEED_INSTALL_GIT" = true ] && pkgs="${pkgs} git"
[ "$NEED_INSTALL_CURL" = true ] && pkgs="${pkgs} curl"
[ "$NEED_INSTALL_WGET" = true ] && pkgs="${pkgs} wget"
PLAN="${PLAN} • 安装必要工具:${pkgs}\n"
fi
if [ "$NEED_INSTALL_GO" = true ]; then
PLAN="${PLAN} • 安装 Go ${GO_VERSION}\n"
fi
PLAN="${PLAN} • 拉取 frpc-console 源码 (${BRANCH} 分支)\n"
PLAN="${PLAN} • 编译 frpc-console 二进制\n"
PLAN="${PLAN} • 构建 Docker 镜像\n"
if [ "$CONTAINER_EXISTS" = true ]; then
PLAN="${PLAN} • 停止并删除旧容器\n"
fi
PLAN="${PLAN} • 启动 frpc-console 容器"
}
# ---------- 展示部署计划 ----------
print_deployment_plan() {
print_title
print_subtitle "部署计划"
echo ""
echo -e " ${CYAN}将执行以下操作:${NC}"
echo -e "$(echo -e "$PLAN")"
echo ""
echo -e " ${CYAN}配置信息:${NC}"
echo -e " ────────────────────────────────────"
echo -e " 监听端口 : ${PORT}"
echo -e " 部署目录 : ${DEPLOY_DIR}"
echo -e " 数据目录 : ${DATA_DIR}"
echo -e " ────────────────────────────────────"
if [ "$CONTAINER_EXISTS" = true ]; then
echo ""
echo -e " ${YELLOW}⚠ 检测到已存在的 frpc-console 容器${NC}"
if [ "$CONTAINER_RUNNING" = true ]; then
echo -e " 状态: 运行中 → 将被停止并重新创建"
else
echo -e " 状态: 已停止 → 将被删除并重新创建"
fi
echo -e " ${YELLOW}数据目录中的数据库文件将被保留${NC}"
fi
echo ""
}
# ---------- 确认部署 ----------
confirm_deploy() {
if [ "$SKIP_CONFIRM" = true ]; then
echo -e "${GREEN}▶ 已启用 --yes,自动确认${NC}"
return 0
fi
echo -e -n "${CYAN}确认执行? 输入 Y 继续,输入 n 自定义配置 [Y/n]: ${NC}"
read -r CONFIRM </dev/tty
case $CONFIRM in
n|N)
return 1
;;
*)
return 0
;;
esac
}
# ---------- 自定义配置 ----------
custom_config() {
print_title
print_subtitle "自定义配置"
echo ""
read -p "请输入监听端口 [${DEFAULT_PORT}]: " INPUT_PORT </dev/tty
PORT=${INPUT_PORT:-$DEFAULT_PORT}
read -p "请输入部署目录 [${DEFAULT_DEPLOY_DIR}]: " INPUT_DEPLOY_DIR </dev/tty
DEPLOY_DIR=${INPUT_DEPLOY_DIR:-$DEFAULT_DEPLOY_DIR}
DATA_DIR="${DEPLOY_DIR}/data"
echo ""
echo -e " ${CYAN}更新后的配置:${NC}"
echo -e " ────────────────────────────────────"
echo -e " 监听端口 : ${PORT}"
echo -e " 部署目录 : ${DEPLOY_DIR}"
echo -e " 数据目录 : ${DATA_DIR}"
echo -e " ────────────────────────────────────"
echo ""
}
# ---------- 实际执行部署 ----------
do_deploy() {
print_title
print_subtitle "开始部署"
echo ""
# ----- 安装必要工具 -----
if [ "$NEED_INSTALL_GIT" = true ] || [ "$NEED_INSTALL_CURL" = true ] || [ "$NEED_INSTALL_WGET" = true ]; then
print_step "安装必要工具..."
local pkgs=""
[ "$NEED_INSTALL_GIT" = true ] && pkgs="${pkgs} git"
[ "$NEED_INSTALL_CURL" = true ] && pkgs="${pkgs} curl"
[ "$NEED_INSTALL_WGET" = true ] && pkgs="${pkgs} wget"
case $OS in
opensuse*|suse*|opensuse-tumbleweed|opensuse-slowroll|opensuse-leap)
# OpenSUSE 使用 git-core 而非 git
if [ "$NEED_INSTALL_GIT" = true ]; then
zypper install -y git-core
# 移除 git 避免重复安装
pkgs=$(echo "$pkgs" | sed 's/ git / /g' | sed 's/git //g' | sed 's/ git$//')
fi
if [ -n "$pkgs" ]; then
zypper install -y $pkgs
fi
;;
ubuntu|debian|linuxmint)
apt update -qq && apt install -y $pkgs
;;
centos|rhel|fedora|rocky|almalinux)
yum install -y $pkgs
;;
alpine)
apk add $pkgs
;;
arch|manjaro|endeavouros)
pacman -S --needed --noconfirm $pkgs
;;
*)
print_error "无法识别包管理器,请手动安装: $pkgs"
exit 1
;;
esac
print_success "必要工具安装完成"
fi
# ----- 安装 Go -----
if [ "$NEED_INSTALL_GO" = true ]; then
print_step "安装 Go ${GO_VERSION} (${GO_ARCH})..."
GO_TMP="/tmp/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz"
if [ -f "$GO_TMP" ]; then
print_info "使用缓存: $GO_TMP"
else
print_info "正在下载..."
MIRRORS=(
"https://mirrors.aliyun.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz"
"https://mirrors.tuna.tsinghua.edu.cn/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz"
"https://golang.google.cn/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz"
"https://dl.google.com/go/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz"
)
DOWNLOADED=false
for MIRROR in "${MIRRORS[@]}"; do
print_info "尝试: $MIRROR"
if curl -# -f -L -o "$GO_TMP" "$MIRROR"; then
print_success "下载成功"
DOWNLOADED=true
break
else
print_warn "失败,尝试下一个镜像..."
rm -f "$GO_TMP"
fi
done
if [ "$DOWNLOADED" = false ]; then
print_error "所有镜像源均下载失败"
exit 1
fi
fi
rm -rf /usr/local/go
tar -C /usr/local -xzf "$GO_TMP"
export PATH=$PATH:/usr/local/go/bin
if [ ! -f /etc/profile.d/go.sh ]; then
echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh
fi
/usr/local/go/bin/go env -w GOPROXY=https://goproxy.cn,direct
/usr/local/go/bin/go env -w GOPRIVATE=git.whitetop.xyz
print_success "Go ${GO_VERSION} (${GO_ARCH}) 安装完成"
fi
# 确保 go 在 PATH 中
export PATH=$PATH:/usr/local/go/bin
# ----- 拉取代码 -----
print_step "拉取代码..."
rm -rf "$WORK_DIR"
git clone --depth 1 --branch "$BRANCH" "$REPO_URL" "$WORK_DIR"
cd "$WORK_DIR"
mkdir -p bin static
print_success "代码拉取完成"
# ----- 修复 go.mod -----
print_step "检查 go.mod..."
if grep -q "go 1.2[6-9]" go.mod 2>/dev/null; then
print_warn "检测到 go.mod 版本过高,自动降级到 go 1.21"
sed -i 's/go 1.2[6-9].*/go 1.21/' go.mod
fi
# ----- 下载依赖 -----
print_step "下载 Go 依赖..."
go mod download
print_success "依赖下载完成"
# ----- 编译 -----
print_step "编译 frpc-console..."
CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-o frpc-console .
if [ -f "frpc-console" ]; then
SIZE=$(du -h frpc-console | cut -f1)
print_success "编译完成 ($SIZE)"
else
print_error "编译失败"
exit 1
fi
# ----- 准备部署目录 -----
print_step "准备部署目录..."
mkdir -p "$DEPLOY_DIR"
mkdir -p "$DATA_DIR"
if [ ! -f "$DATA_DIR/frpc-console.db" ]; then
touch "$DATA_DIR/frpc-console.db"
print_info "新数据目录已创建"
else
print_info "已有数据目录,保留现有数据"
fi
cp frpc-console "$DEPLOY_DIR/"
print_success "二进制已复制到 $DEPLOY_DIR"
# ----- 构建 Docker 镜像 -----
print_step "构建 Docker 镜像..."
docker build -t "${IMAGE_NAME}:latest" .
print_success "Docker 镜像构建完成: ${IMAGE_NAME}:latest"
# ----- 停止旧容器 -----
if [ "$CONTAINER_EXISTS" = true ]; then
print_step "处理旧容器..."
if [ "$CONTAINER_RUNNING" = true ]; then
docker stop frpc-console 2>/dev/null || true
fi
docker rm frpc-console 2>/dev/null || true
print_success "旧容器已清理"
fi
# ----- 启动新容器 -----
print_step "启动 frpc-console 容器..."
docker run -d \
--name frpc-console \
--restart=always \
--network host \
-v ${DATA_DIR}:/app/data \
-e PORT=${PORT} \
-e TZ=Asia/Shanghai \
${IMAGE_NAME}:latest
if docker ps | grep -q frpc-console; then
print_success "容器启动成功!"
else
print_error "容器启动失败,请检查日志: docker logs frpc-console"
exit 1
fi
# ----- 检查 frpc 子进程 -----
print_step "检查 frpc 状态..."
sleep 3
if docker exec frpc-console ps aux 2>/dev/null | grep -q "[f]rpc -c"; then
print_success "frpc 进程运行正常"
else
print_warn "frpc 进程未运行(可能配置为空,请在 WebUI 中导入 TOML"
fi
# ----- 清理临时文件 -----
print_step "清理临时文件..."
rm -rf "$WORK_DIR"
print_success "临时文件已清理"
# ----- 输出信息 -----
print_title
print_success "frpc-console 部署完成!"
print_title
echo ""
echo -e " ${CYAN}📍 访问地址:${NC} http://$(hostname -I | awk '{print $1}'):${PORT}"
echo -e " ${CYAN}📂 数据目录:${NC} ${DATA_DIR}"
echo -e " ${CYAN}🐳 容器名称:${NC} frpc-console"
echo ""
echo -e " ${CYAN}常用命令:${NC}"
echo " docker logs frpc-console # 查看日志"
echo " docker restart frpc-console # 重启服务"
echo " docker stop frpc-console # 停止服务"
echo " docker start frpc-console # 启动服务"
echo ""
echo -e " ${YELLOW}首次访问需要注册管理员账户${NC}"
echo ""
print_title
}
# ---------- 主流程 ----------
main() {
clear 2>/dev/null || true
parse_args "$@"
check_root
# ---- 环境检测 ----
print_step "检测环境..."
detect_os
detect_arch
check_tools
check_container
# ---- 展示检测结果 ----
print_environment_summary
# ---- 如果只检测 ----
if [ "$CHECK_ONLY" = true ]; then
print_info "环境检测完成(--check 模式,不执行部署)"
exit 0
fi
# ---- 如果 Docker 未安装 ----
if [ "$HAS_DOCKER" = false ]; then
print_error "Docker 未安装,请先安装 Docker"
echo ""
echo " 快速安装:"
echo " curl -fsSL https://get.docker.com | bash"
echo " systemctl enable --now docker"
echo ""
exit 1
fi
# ---- 生成部署计划 ----
generate_plan
# ---- 展示部署计划 ----
print_deployment_plan
# ---- 确认或自定义 ----
if ! confirm_deploy; then
custom_config
print_deployment_plan
if ! confirm_deploy; then
print_info "已取消部署"
exit 0
fi
fi
# ---- 如果只是演练 ----
if [ "$DRY_RUN" = true ]; then
print_info "演练模式(--dry-run),不实际执行部署"
exit 0
fi
# ---- 执行部署 ----
do_deploy
}
# ---------- 入口 ----------
main "$@"