Files
server-deploy/gitea/deploy.sh
2026-04-07 17:06:12 +08:00

484 lines
16 KiB
Bash
Executable File
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
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 <<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/^/ → /'
# 重启 Docker 使配置生效
systemctl restart docker
log "Docker 已重启以应用镜像加速"
else
log "Docker 镜像加速已存在,跳过"
fi
}
# ===== 2. 安装 Docker =====
install_docker() {
step "2/8 安装 Docker"
if command -v docker &> /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 <<NGINX_TEMP
server {
listen 80;
listen [::]:80;
server_name ${domain};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 200 'Gitea is being configured...';
add_header Content-Type text/plain;
}
}
NGINX_TEMP
ln -sf /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/gitea
# 移除默认站点避免冲突
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
# 申请 SSL 证书
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 端口可从外网访问"
error "解决后重新运行 deploy.sh"
exit 1
fi
log "SSL 证书申请成功"
else
log "SSL 证书已存在,跳过申请"
fi
# 部署正式 Nginx 配置
cp "$SCRIPT_DIR/nginx/gitea.conf" /etc/nginx/sites-available/gitea
sed -i "s/__GITEA_DOMAIN__/${domain}/g" /etc/nginx/sites-available/gitea
nginx -t
systemctl reload nginx
log "Nginx HTTPS 反向代理配置完成"
# 设置证书自动续期
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
}
# ===== 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 "$@"