#!/usr/bin/env bash # ============================================ # 服务器基础环境安装脚本(公共依赖) # 安装: 系统工具 + Docker + Nginx + Certbot + 防火墙 # 可独立运行,也可被其他部署脚本 source 调用 # ============================================ # 避免重复 source if [ -n "$_BASE_SETUP_LOADED" ]; then return 0 2>/dev/null || true fi _BASE_SETUP_LOADED=1 # ===== 终端颜色 ===== RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' log() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } step() { echo -e "\n${CYAN}========== $* ==========${NC}"; } # ===== 检查 root ===== check_root() { if [ "$(id -u)" -ne 0 ]; then error "请使用 root 用户运行此脚本: sudo bash $0" exit 1 fi } # ===== 检测包管理器 ===== detect_pkg_mgr() { if [ -n "${PKG_MGR:-}" ]; then return fi if command -v apt-get &> /dev/null; then PKG_MGR="apt" elif command -v dnf &> /dev/null; then PKG_MGR="dnf" elif command -v yum &> /dev/null; then PKG_MGR="yum" else error "不支持的系统,需要 apt/dnf/yum 包管理器" exit 1 fi } # ===== 系统初始化 ===== init_system() { step "系统初始化" detect_pkg_mgr log "检测到包管理器: $PKG_MGR" log "正在更新系统软件包..." case "$PKG_MGR" in apt) export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get upgrade -y -qq apt-get install -y -qq curl wget git gnupg2 ca-certificates \ lsb-release software-properties-common openssl cron ;; dnf|yum) $PKG_MGR update -y -q $PKG_MGR install -y -q curl wget git gnupg2 ca-certificates openssl cronie ;; esac if [ ! -f /etc/timezone ] || [ "$(cat /etc/timezone 2>/dev/null)" != "Asia/Shanghai" ]; then timedatectl set-timezone Asia/Shanghai 2>/dev/null || true log "时区已设置为 Asia/Shanghai" fi log "系统初始化完成" } # ===== 安装 Docker ===== install_docker() { step "安装 Docker" if command -v docker &> /dev/null; then log "Docker 已安装: $(docker --version)" else detect_pkg_mgr log "正在安装 Docker (使用阿里云镜像)..." case "$PKG_MGR" in apt) curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg \ | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \ https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" \ > /etc/apt/sources.list.d/docker.list apt-get update -qq apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin ;; dnf) dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo dnf install -y -q docker-ce docker-ce-cli containerd.io docker-compose-plugin ;; yum) yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum install -y -q docker-ce docker-ce-cli containerd.io docker-compose-plugin ;; esac log "Docker 安装完成: $(docker --version)" fi systemctl enable --now docker log "Docker 服务已启动" if ! docker compose version &> /dev/null; then error "Docker Compose V2 不可用" error "请手动安装: apt install docker-compose-plugin" exit 1 fi log "Docker Compose 已就绪: $(docker compose version --short)" } # ===== 配置 Docker 镜像加速 ===== configure_docker_mirrors() { local mirrors="${DOCKER_REGISTRY_MIRRORS:-}" if [ -z "$mirrors" ]; then log "未配置 DOCKER_REGISTRY_MIRRORS,跳过镜像加速" return fi mkdir -p /etc/docker local json_array json_array=$(echo "$mirrors" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \ | grep -v '^$' | awk '{printf "\"%s\",", $0}' | sed 's/,$//') local need_restart=0 if [ -f /etc/docker/daemon.json ]; then if ! grep -q "registry-mirrors" /etc/docker/daemon.json; then need_restart=1 fi else need_restart=1 fi if [ "$need_restart" -eq 1 ]; then cat > /etc/docker/daemon.json < /dev/null; then log "Nginx 已安装: $(nginx -v 2>&1)" else log "正在安装 Nginx..." case "$PKG_MGR" in apt) apt-get install -y -qq nginx ;; dnf|yum) $PKG_MGR install -y -q nginx ;; esac log "Nginx 安装完成" fi systemctl enable --now nginx mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled /var/www/certbot if ! grep -q "sites-enabled" /etc/nginx/nginx.conf; then sed -i '/^http {/a \ include /etc/nginx/sites-enabled/*;' /etc/nginx/nginx.conf fi log "Nginx 配置就绪" } # ===== 安装 Certbot ===== install_certbot() { detect_pkg_mgr if command -v certbot &> /dev/null; then log "Certbot 已安装: $(certbot --version 2>&1)" return fi log "正在安装 Certbot..." case "$PKG_MGR" in apt) apt-get install -y -qq certbot python3-certbot-nginx ;; dnf) dnf install -y -q certbot python3-certbot-nginx ;; yum) yum install -y -q certbot python3-certbot-nginx ;; esac log "Certbot 安装完成" } # ===== 配置防火墙(基础端口)===== setup_firewall_base() { step "配置防火墙(基础端口)" if command -v ufw &> /dev/null; then ufw --force enable 2>/dev/null || true ufw allow ssh comment "SSH" 2>/dev/null || true ufw allow 80/tcp comment "HTTP" 2>/dev/null || true ufw allow 443/tcp comment "HTTPS" 2>/dev/null || true ufw reload 2>/dev/null || true log "UFW 防火墙规则已添加 (22, 80, 443)" elif command -v firewall-cmd &> /dev/null; then systemctl enable --now firewalld 2>/dev/null || true firewall-cmd --permanent --add-service=ssh 2>/dev/null || true firewall-cmd --permanent --add-service=http 2>/dev/null || true firewall-cmd --permanent --add-service=https 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true log "Firewalld 规则已添加 (22, 80, 443)" else warn "未检测到防火墙工具,请手动开放端口: 22, 80, 443" fi } # ===== 开放额外端口 ===== # 用法: firewall_allow_port <端口> [描述] firewall_allow_port() { local port="$1" local comment="${2:-Custom}" if command -v ufw &> /dev/null; then ufw allow "$port"/tcp comment "$comment" 2>/dev/null || true ufw reload 2>/dev/null || true elif command -v firewall-cmd &> /dev/null; then firewall-cmd --permanent --add-port="$port"/tcp 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true fi log "已开放端口: $port ($comment)" } # ===== 申请 SSL 证书 ===== # 用法: setup_ssl_cert <域名> <邮箱> [站点名称] setup_ssl_cert() { local domain="$1" local email="$2" local site_name="${3:-$domain}" step "配置 SSL 证书: ${domain}" install_certbot # 部署临时 Nginx 配置(仅 HTTP,用于 ACME 验证) cat > "/etc/nginx/sites-available/${site_name}" </dev/null | grep -q "certbot renew"; then (crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --post-hook 'systemctl reload nginx'") | crontab - log "已配置 SSL 证书自动续期 (每天 03:00 检查)" fi } # ===== 部署 Nginx 配置 ===== # 用法: deploy_nginx_conf <模板路径> <域名> <站点名称> # 模板中使用 __DOMAIN__ 作为域名占位符 deploy_nginx_conf() { local template="$1" local domain="$2" local site_name="$3" if [ ! -f "$template" ]; then error "Nginx 配置模板不存在: $template" exit 1 fi cp "$template" "/etc/nginx/sites-available/${site_name}" sed -i "s/__DOMAIN__/${domain}/g" "/etc/nginx/sites-available/${site_name}" ln -sf "/etc/nginx/sites-available/${site_name}" "/etc/nginx/sites-enabled/${site_name}" nginx -t systemctl reload nginx log "Nginx 反向代理配置已部署: ${site_name} → ${domain}" } # ===== 加载 base .env ===== load_base_env() { local base_dir="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" if [ -f "$base_dir/.env" ]; then set -a source "$base_dir/.env" set +a fi } # ============================================================= # 独立运行模式:直接安装全部基础环境 # ============================================================= _base_main() { echo -e "${CYAN}" echo " ____" echo " | __ ) __ _ ___ ___" echo " | _ \\ / _\` / __|/ _ \\" echo " | |_) | (_| \\__ \\ __/" echo " |____/ \\__,_|___/\\___| Server Base Setup" echo -e "${NC}" echo "" check_root local base_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 加载 .env if [ -f "$base_dir/.env" ]; then set -a; source "$base_dir/.env"; set +a elif [ -f "$base_dir/.env.example" ]; then cp "$base_dir/.env.example" "$base_dir/.env" set -a; source "$base_dir/.env"; set +a log "已从 .env.example 生成 .env" fi init_system install_docker configure_docker_mirrors install_nginx install_certbot setup_firewall_base echo "" log "===== 基础环境安装完成 =====" log "已安装: Docker $(docker --version 2>/dev/null | grep -o '[0-9.]*' | head -1)" log "已安装: Docker Compose $(docker compose version --short 2>/dev/null)" log "已安装: Nginx $(nginx -v 2>&1 | grep -o '[0-9.]*')" log "已安装: Certbot $(certbot --version 2>&1 | grep -o '[0-9.]*')" echo "" log "接下来可以部署各服务:" log " Gitea: cd /opt/gitea && bash deploy.sh" log " Certd: cd /opt/certd && bash deploy.sh" log " Vaultwarden: cd /opt/vaultwarden && bash deploy.sh" } # 仅直接运行时执行 main,被 source 时只加载函数 if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then set -euo pipefail _base_main "$@" fi