Files
server-deploy/base/setup.sh

396 lines
12 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.
#!/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 <<EOF
{
"registry-mirrors": [${json_array}],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
log "Docker 镜像加速已配置:"
echo "$mirrors" | tr ',' '\n' | sed 's/^/ → /'
systemctl restart docker
log "Docker 已重启以应用镜像加速"
else
log "Docker 镜像加速已存在,跳过"
fi
}
# ===== 安装 Nginx =====
install_nginx() {
step "安装 Nginx"
detect_pkg_mgr
if command -v nginx &> /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}" <<NGINX_TEMP
server {
listen 80;
listen [::]:80;
server_name ${domain};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 200 'Service is being configured...';
add_header Content-Type text/plain;
}
}
NGINX_TEMP
ln -sf "/etc/nginx/sites-available/${site_name}" "/etc/nginx/sites-enabled/${site_name}"
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
# 申请证书
if [ ! -d "/etc/letsencrypt/live/${domain}" ]; then
log "正在申请 SSL 证书: ${domain} ..."
if ! certbot certonly --webroot \
-w /var/www/certbot \
-d "${domain}" \
--email "${email}" \
--agree-tos \
--non-interactive \
--no-eff-email; then
error "SSL 证书申请失败!"
error "请确认:"
error " 1. 域名 ${domain} 已解析到本服务器 IP"
error " 2. 服务器 80 端口可从外网访问"
exit 1
fi
log "SSL 证书申请成功"
else
log "SSL 证书已存在,跳过申请"
fi
# 配置自动续期
if ! crontab -l 2>/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