#!/usr/bin/env bash set -euo pipefail # ============================================ # Gitea 全新服务器一键部署脚本 # 适用于 Ubuntu 20.04+ / Debian 11+ # 包含:系统更新 → Docker → Nginx → Certbot → Gitea + MySQL # ============================================ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" 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 deploy.sh" exit 1 fi } # ===== 1. 系统初始化 ===== init_system() { step "1/8 系统初始化" # 检测包管理器 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 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 镜像加速 ===== configure_docker_mirrors() { local mirrors="${DOCKER_REGISTRY_MIRRORS:-}" if [ -z "$mirrors" ]; then log "未配置 DOCKER_REGISTRY_MIRRORS,跳过镜像加速" return fi mkdir -p /etc/docker # 将逗号分隔的镜像列表转为 JSON 数组 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 # 生成或覆盖 daemon.json(保留 log 配置) cat > /etc/docker/daemon.json < /dev/null; then log "Docker 已安装: $(docker --version)" else log "正在安装 Docker (使用阿里云镜像)..." 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 log "Docker 安装完成: $(docker --version)" fi # 确保 Docker 服务运行 systemctl enable --now docker log "Docker 服务已启动" # 检查 Docker Compose V2 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)" } # ===== 3. 安装 Nginx ===== install_nginx() { step "3/8 安装 Nginx" 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 # 确保 Nginx 配置目录结构存在 mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled /var/www/certbot # 确保 nginx.conf 包含 sites-enabled if ! grep -q "sites-enabled" /etc/nginx/nginx.conf; then # 在 http { } 块内添加 include sed -i '/^http {/a \ include /etc/nginx/sites-enabled/*;' /etc/nginx/nginx.conf fi log "Nginx 配置就绪" } # ===== 4. 初始化 .env ===== init_env() { step "4/8 初始化配置" if [ ! -f .env ]; then if [ ! -f .env.example ]; then error "缺少 .env.example 模板文件" exit 1 fi cp .env.example .env # 自动生成随机密码 DB_PASS=$(openssl rand -base64 24 | tr -d '/+=\n' | head -c 32) DB_ROOT_PASS=$(openssl rand -base64 24 | tr -d '/+=\n' | head -c 32) sed -i "s/请替换为强密码/${DB_PASS}/" .env sed -i "s/请替换为ROOT强密码/${DB_ROOT_PASS}/" .env log "已生成 .env 文件,数据库密码已随机生成" echo "" warn "┌─────────────────────────────────────────────┐" warn "│ 请编辑 .env 文件,至少修改以下配置: │" warn "│ │" warn "│ GITEA_DOMAIN=你的域名 │" warn "│ CERTBOT_EMAIL=你的邮箱 │" warn "│ │" warn "│ 编辑命令: vi $SCRIPT_DIR/.env │" warn "│ 编辑完成后重新运行: bash deploy.sh │" warn "└─────────────────────────────────────────────┘" exit 0 fi # 加载并验证配置 set -a source .env set +a local has_error=0 if [[ -z "${GITEA_DOMAIN:-}" ]] || [[ "${GITEA_DOMAIN}" == "git.example.com" ]]; then error "请在 .env 中将 GITEA_DOMAIN 修改为你的实际域名" has_error=1 fi if [[ -z "${CERTBOT_EMAIL:-}" ]] || [[ "${CERTBOT_EMAIL}" == "admin@example.com" ]]; then error "请在 .env 中将 CERTBOT_EMAIL 修改为你的实际邮箱" has_error=1 fi if [[ "${DB_PASSWORD:-}" == *"请替换"* ]] || [[ -z "${DB_PASSWORD:-}" ]]; then error "DB_PASSWORD 未正确设置" has_error=1 fi if [ "$has_error" -eq 1 ]; then error "请修改 .env 后重新运行: vi $SCRIPT_DIR/.env" exit 1 fi log "配置检查通过: 域名=${GITEA_DOMAIN}" } # ===== 5. 创建数据目录 ===== create_dirs() { step "5/8 创建数据目录" local gitea_data="${GITEA_DATA_DIR:-/var/lib/gitea}" local mysql_data="${MYSQL_DATA_DIR:-/var/lib/mysql/gitea}" local backup_dir="${BACKUP_DIR:-/var/backups/gitea}" mkdir -p "$gitea_data" "$mysql_data" "$backup_dir" # Gitea 容器以 UID=1000 运行,确保数据目录归属正确 chown -R 1000:1000 "$gitea_data" # MySQL 容器以 mysql(999) 运行 chown -R 999:999 "$mysql_data" log "数据目录已就绪:" log " Gitea 数据: $gitea_data" log " MySQL 数据: $mysql_data" log " 备份目录: $backup_dir" } # ===== 6. 配置防火墙 ===== setup_firewall() { step "6/8 配置防火墙" local ssh_port="${SSH_PORT:-2222}" 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 allow "$ssh_port"/tcp comment "Gitea SSH" 2>/dev/null || true ufw reload 2>/dev/null || true log "UFW 防火墙规则已添加 (22, 80, 443, $ssh_port)" 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 --permanent --add-port="$ssh_port"/tcp 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true log "Firewalld 规则已添加 (22, 80, 443, $ssh_port)" else warn "未检测到防火墙工具,请手动开放端口: 22, 80, 443, $ssh_port" fi } # ===== 7. 配置 Nginx + SSL ===== setup_ssl() { step "7/8 配置 HTTPS (Nginx + Let's Encrypt)" local domain="${GITEA_DOMAIN}" local email="${CERTBOT_EMAIL}" # 安装 certbot if ! command -v certbot &> /dev/null; then 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 fi log "Certbot 已就绪" # 部署临时 Nginx 配置(仅 HTTP,用于证书验证) cat > /etc/nginx/sites-available/gitea </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 } # ===== 8. 启动服务 ===== start_services() { step "8/8 启动 Gitea 服务" log "正在拉取镜像(首次可能较慢)..." docker compose pull log "正在启动容器..." docker compose up -d log "等待 MySQL 就绪..." local max_wait=60 for i in $(seq 1 $max_wait); do if docker compose exec -T db mysqladmin ping -h localhost -u root -p"${DB_ROOT_PASSWORD}" --silent &> /dev/null; then log "MySQL 已就绪" break fi if [ "$i" -eq "$max_wait" ]; then warn "MySQL 启动超时,请检查日志: docker compose logs db" fi sleep 2 done log "等待 Gitea 就绪..." for i in $(seq 1 30); do if curl -sf http://127.0.0.1:3000/api/v1/version &> /dev/null; then log "Gitea 启动成功!" break fi if [ "$i" -eq 30 ]; then warn "Gitea 可能仍在初始化,请稍后检查" fi sleep 2 done } # ===== 输出完成信息 ===== show_info() { set -a source .env set +a echo "" echo -e "${GREEN}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Gitea 部署完成! ║${NC}" echo -e "${GREEN}╠══════════════════════════════════════════════════════╣${NC}" echo -e "${GREEN}║${NC}" echo -e "${GREEN}║${NC} Web 访问: ${CYAN}https://${GITEA_DOMAIN}${NC}" echo -e "${GREEN}║${NC} SSH 克隆: ${CYAN}ssh://git@${GITEA_DOMAIN}:${SSH_PORT:-2222}/用户名/仓库.git${NC}" echo -e "${GREEN}║${NC} HTTPS 克隆: ${CYAN}https://${GITEA_DOMAIN}/用户名/仓库.git${NC}" echo -e "${GREEN}║${NC}" echo -e "${GREEN}║${NC} 已启用功能:" echo -e "${GREEN}║${NC} ✓ HTTPS (Let's Encrypt 自动续期)" echo -e "${GREEN}║${NC} ✓ Git LFS 大文件存储" echo -e "${GREEN}║${NC} ✓ SSH 密钥认证" echo -e "${GREEN}║${NC} ✓ GPG 签名验证" echo -e "${GREEN}║${NC} ✓ MySQL 8.4 LTS 数据库" echo -e "${GREEN}║${NC}" echo -e "${GREEN}║${NC} 首次访问说明:" echo -e "${GREEN}║${NC} 1. 浏览器打开 https://${GITEA_DOMAIN}" echo -e "${GREEN}║${NC} 2. 进入安装向导(数据库配置已自动填写)" echo -e "${GREEN}║${NC} 3. 创建管理员账户" echo -e "${GREEN}║${NC} 4. 完成安装" echo -e "${GREEN}║${NC}" echo -e "${GREEN}║${NC} 常用命令:" echo -e "${GREEN}║${NC} 查看日志: cd ${INSTALL_DIR:-/opt/gitea} && docker compose logs -f" echo -e "${GREEN}║${NC} 重启服务: cd ${INSTALL_DIR:-/opt/gitea} && docker compose restart" echo -e "${GREEN}║${NC} 备份数据: bash ${INSTALL_DIR:-/opt/gitea}/backup.sh" echo -e "${GREEN}║${NC}" echo -e "${GREEN}║${NC} 服务器目录:" echo -e "${GREEN}║${NC} 部署文件: ${INSTALL_DIR:-/opt/gitea}" echo -e "${GREEN}║${NC} Gitea 数据: ${GITEA_DATA_DIR:-/var/lib/gitea}" echo -e "${GREEN}║${NC} MySQL 数据: ${MYSQL_DATA_DIR:-/var/lib/mysql/gitea}" echo -e "${GREEN}║${NC} 备份目录: ${BACKUP_DIR:-/var/backups/gitea}" echo -e "${GREEN}║${NC} SSL 证书: /etc/letsencrypt/live/${GITEA_DOMAIN}" echo -e "${GREEN}║${NC} Nginx 配置: /etc/nginx/sites-available/gitea" echo -e "${GREEN}║${NC}" echo -e "${GREEN}╚══════════════════════════════════════════════════════╝${NC}" echo "" warn "重要:创建管理员后请编辑 .env 将 DISABLE_REGISTRATION 改为 true 并重启" } # ===== 主流程 ===== main() { echo -e "${CYAN}" echo " ____ _ _" echo " / ___|(_) |_ ___ __ _" echo "| | _ | | __/ _ \\/ _\` |" echo "| |_| || | || __/ (_| |" echo " \\____|_|\\__\\___|\\__,_| Deploy Script" echo -e "${NC}" echo "" check_root init_system install_docker install_nginx init_env configure_docker_mirrors create_dirs setup_firewall setup_ssl start_services show_info log "===== 全部部署完成 =====" } main "$@"