commit 5b0b7998ecb7993f801999df6e0cbc719236dd47 Author: Joywayer Date: Mon Apr 6 22:50:35 2026 +0800 初次提交,gitea diff --git a/gitea/.env.example b/gitea/.env.example new file mode 100644 index 0000000..993b2a7 --- /dev/null +++ b/gitea/.env.example @@ -0,0 +1,58 @@ +# ============================================ +# Gitea 部署环境变量配置 +# 复制此文件为 .env 并修改对应值 +# ============================================ + +# ---- 域名配置(必改!)---- +# 你的域名,需提前解析 A 记录到服务器 IP +GITEA_DOMAIN=git.example.com +# 用于申请 SSL 证书的邮箱 +CERTBOT_EMAIL=admin@example.com + +# ---- 服务器目录配置 ---- +# 部署目录(docker-compose.yml、脚本、.env 所在目录) +INSTALL_DIR=/opt/gitea +# Gitea 应用数据(仓库、LFS、附件、头像、SSH/GPG 密钥) +GITEA_DATA_DIR=/var/lib/gitea +# MySQL 数据库文件 +MYSQL_DATA_DIR=/var/lib/mysql/gitea +# 备份文件存放目录 +BACKUP_DIR=/var/backups/gitea + +# ---- Gitea 镜像配置 ---- +# 官方最新镜像地址: docker.gitea.com/gitea:1.25.5 +# Docker Hub 镜像: gitea/gitea:1.25 (默认,可被国内加速源加速) +# 如果服务器在海外或不需要加速,可切换为官方地址 +GITEA_IMAGE=gitea/gitea:1.25 + +# ---- Docker 镜像加速(国内服务器建议配置)---- +# 多个镜像用逗号分隔,留空则不配置加速 +# 常用镜像源(2026 年可用,按稳定性排序): +# https://docker.1ms.run — 1ms 公益加速 +# https://docker.m.daocloud.io — DaoCloud 公开镜像 +# https://docker.xuanyuan.me — 轩辕镜像 +# https://dockerpull.org — DockerPull 公益 +# https://docker.rainbond.cc — Rainbond 社区 +# https://docker.udayun.com — 优达云 +# https://docker.211678.top — 211678 加速 +# https://hub.rat.dev — RatDev 公益 +# 提示: 镜像源可能随时失效,部署前可 curl 测试连通性 +DOCKER_REGISTRY_MIRRORS=https://docker.1ms.run,https://docker.m.daocloud.io,https://dockerpull.org,https://docker.rainbond.cc,https://docker.udayun.com,https://hub.rat.dev + +# ---- 数据库密码(deploy.sh 自动生成,无需手动填)---- +DB_PASSWORD=请替换为强密码 +DB_ROOT_PASSWORD=请替换为ROOT强密码 + +# ---- 端口配置 ---- +# SSH 克隆端口(对外暴露) +SSH_PORT=2222 + +# ---- GPG 签名配置 ---- +GPG_SIGNING_NAME=Gitea +GPG_SIGNING_EMAIL=gitea@example.com + +# ---- 安全设置 ---- +# 部署完成并创建管理员账户后,建议设为 true 关闭公开注册 +DISABLE_REGISTRATION=false +# 是否要求登录才能浏览(私有部署建议 true) +REQUIRE_SIGNIN=false diff --git a/gitea/.gitignore b/gitea/.gitignore new file mode 100644 index 0000000..3dcecc5 --- /dev/null +++ b/gitea/.gitignore @@ -0,0 +1,3 @@ +data/ +backups/ +.env diff --git a/gitea/README.md b/gitea/README.md new file mode 100644 index 0000000..47603f7 --- /dev/null +++ b/gitea/README.md @@ -0,0 +1,368 @@ +# Gitea 云服务器部署指南 + +> 全新服务器从零搭建,含 Docker + MySQL + Nginx HTTPS + Git LFS + SSH 密钥 + GPG 签名 + +## 架构概览 + +``` + ┌──────────────────────────────────────────┐ + │ 云服务器 │ + 用户 ───HTTPS───→ │ Nginx:443 ──→ Gitea:3000 │ + 用户 ───SSH────→ │ 端口:2222 ──→ Gitea SSH │ + │ ↓ │ + │ MySQL:3306 │ + │ │ + │ /opt/gitea/ 部署文件 (compose等) │ + │ /var/lib/gitea/ 仓库+LFS+附件+密钥 │ + │ /var/lib/mysql/gitea/ MySQL 数据 │ + │ /var/backups/gitea/ 备份文件 │ + └──────────────────────────────────────────┘ +``` + +| 组件 | 版本 | 说明 | +|------|------|------| +| Gitea | 1.25 | Git 托管服务(含内置 SSH 服务器) | +| MySQL | 8.0 | 数据库(utf8mb4) | +| Nginx | 系统包 | HTTPS 反向代理 | +| Certbot | 系统包 | Let's Encrypt 自动 SSL 证书 | +| Docker | 最新 | 容器运行时 | + +### 已启用功能 + +- **HTTPS**:Nginx + Let's Encrypt 免费 SSL,自动续期 +- **Git LFS**:大文件存储,支持最大 4GB 单文件上传 +- **SSH 密钥**:内置 SSH 服务器,支持 Ed25519/RSA 等密钥 +- **GPG 签名**:提交签名验证,显示 "Verified" 徽标 + +--- + +## 一、前置条件 + +| 项目 | 要求 | +|------|------| +| 服务器 | 2核 2GB+ 内存,50GB+ SSD | +| 系统 | Ubuntu 20.04+ / Debian 11+ | +| 域名 | 已解析 A 记录到服务器 IP | +| 端口 | 80、443、2222 可从外网访问 | + +> 如果使用云厂商(阿里云/腾讯云/AWS),需要在**安全组**中放行上述端口。 + +--- + +## 二、一键部署(全新服务器) + +### 步骤 1:上传部署文件 + +```bash +# 在本地执行,将文件上传到服务器 +scp -r gitea/ root@服务器IP:/opt/gitea +``` + +### 步骤 2:首次运行,生成配置 + +```bash +ssh root@服务器IP +cd /opt/gitea +chmod +x deploy.sh backup.sh + +# 首次运行 → 自动安装系统依赖 + Docker + Nginx +# 然后生成 .env 配置文件(密码已随机生成)并退出 +bash deploy.sh +``` + +### 步骤 3:修改域名配置 + +```bash +vi .env +``` + +**必须修改的字段:** + +```ini +GITEA_DOMAIN=git.yourdomain.com # ← 你的实际域名 +CERTBOT_EMAIL=you@yourdomain.com # ← 你的邮箱(证书通知用) +``` + +可选修改: + +```ini +SSH_PORT=2222 # Git SSH 端口 +GPG_SIGNING_NAME=Gitea # GPG 签名显示名称 +GPG_SIGNING_EMAIL=git@xxx.com # GPG 签名邮箱 +DISABLE_REGISTRATION=false # 部署完成后改为 true +REQUIRE_SIGNIN=false # 私有部署改为 true +``` + +### 步骤 4:执行部署 + +```bash +bash deploy.sh +``` + +脚本会自动完成以下 8 个步骤: + +| 步骤 | 操作 | +|------|------| +| 1/8 | 系统更新,安装基础工具(curl, git, openssl 等)| +| 2/8 | 安装 Docker + Docker Compose V2 | +| 3/8 | 安装 Nginx | +| 4/8 | 验证 .env 配置 | +| 5/8 | 创建数据目录 | +| 6/8 | 配置防火墙(UFW/Firewalld)| +| 7/8 | 申请 SSL 证书 + 配置 Nginx HTTPS 反向代理 | +| 8/8 | 拉取镜像,启动 Gitea + MySQL 容器 | + +### 步骤 5:初始化 Gitea + +1. 浏览器打开 `https://git.yourdomain.com` +2. 进入安装向导(数据库已自动配置好) +3. **创建管理员账户**(务必设置强密码) +4. 点击 "安装 Gitea" + +### 步骤 6:安装后加固 + +```bash +cd /opt/gitea + +# 关闭公开注册 +sed -i 's/DISABLE_REGISTRATION=false/DISABLE_REGISTRATION=true/' .env +# 要求登录才能浏览 +sed -i 's/REQUIRE_SIGNIN=false/REQUIRE_SIGNIN=true/' .env + +# 重启生效 +docker compose up -d +``` + +--- + +## 三、功能使用指南 + +### SSH 密钥认证 + +```bash +# 1. 本地生成密钥(如果还没有) +ssh-keygen -t ed25519 -C "your@email.com" + +# 2. 复制公钥 +cat ~/.ssh/id_ed25519.pub + +# 3. 在 Gitea 中添加 +# → 头像 → 设置 → SSH/GPG 密钥 → 添加密钥 → 粘贴公钥 + +# 4. 测试连接 +ssh -T git@git.yourdomain.com -p 2222 + +# 5. 克隆仓库 +git clone ssh://git@git.yourdomain.com:2222/用户名/仓库.git +``` + +### GPG 签名提交 + +```bash +# 1. 生成 GPG 密钥 +gpg --full-generate-key +# 选择 RSA and RSA → 4096 → 填写名字和邮箱 + +# 2. 查看密钥 ID +gpg --list-secret-keys --keyid-format=long +# 输出中 sec 行的 XXXXXXXXXXXXXXXX 就是密钥 ID + +# 3. 导出公钥 +gpg --armor --export XXXXXXXXXXXXXXXX + +# 4. 在 Gitea 中添加 +# → 头像 → 设置 → SSH/GPG 密钥 → 添加 GPG 密钥 → 粘贴公钥 + +# 5. 配置 Git 使用 GPG 签名 +git config --global user.signingkey XXXXXXXXXXXXXXXX +git config --global commit.gpgsign true + +# 6. 签名提交(之后所有 commit 自动签名) +git commit -S -m "signed commit" +``` + +### Git LFS 大文件 + +```bash +# 1. 安装 Git LFS(本地) +git lfs install + +# 2. 跟踪大文件类型 +git lfs track "*.psd" +git lfs track "*.zip" +git lfs track "*.bin" + +# 3. 提交 +git add .gitattributes +git add large-file.psd +git commit -m "add large file via LFS" +git push +``` + +--- + +## 四、日常运维 + +### 查看状态与日志 + +```bash +cd /opt/gitea + +# 容器状态 +docker compose ps + +# 实时日志 +docker compose logs -f # 所有 +docker compose logs -f server # 仅 Gitea +docker compose logs -f db # 仅 MySQL +``` + +### 重启 / 停止 / 启动 + +```bash +docker compose restart # 重启所有 +docker compose restart server # 仅重启 Gitea +docker compose down # 停止并移除容器 +docker compose up -d # 启动 +``` + +### 升级 Gitea + +```bash +cd /opt/gitea + +# 1. 先备份 +bash backup.sh + +# 2. 拉取新镜像 +docker compose pull + +# 3. 重启 +docker compose up -d + +# 4. 检查日志 +docker compose logs -f server +``` + +### 定时自动备份 + +```bash +# 每天凌晨 3 点自动备份 +crontab -e +# 添加: +0 3 * * * /opt/gitea/backup.sh >> /var/backups/gitea/cron.log 2>&1 +``` + +备份内容: +- `db_日期.sql.gz` — MySQL 完整转储 +- `gitea_data_日期.tar.gz` — 仓库 + LFS + 附件 + 头像 (`/var/lib/gitea/`) +- `config_日期.tar.gz` — .env + docker-compose.yml + nginx 配置 + +### 恢复备份 + +```bash +cd /opt/gitea + +# 1. 停止服务 +docker compose down + +# 2. 恢复 Gitea 数据目录 +tar xzf /var/backups/gitea/gitea_data_20260406_030000.tar.gz -C /var/lib/ + +# 3. 恢复配置 +tar xzf /var/backups/gitea/config_20260406_030000.tar.gz -C /opt/gitea/ + +# 4. 启动 MySQL +docker compose up -d db +sleep 10 + +# 5. 恢复数据库 +gunzip < /var/backups/gitea/db_20260406_030000.sql.gz | \ + docker compose exec -T db mysql -u root -p"${DB_ROOT_PASSWORD}" + +# 6. 启动全部 +docker compose up -d +``` + +--- + +## 五、安全加固清单 + +- [ ] 域名已启用 HTTPS(deploy.sh 自动完成) +- [ ] SSL 证书自动续期(deploy.sh 自动配置 cron) +- [ ] 关闭公开注册(`DISABLE_REGISTRATION=true`) +- [ ] 启用登录才能浏览(`REQUIRE_SIGNIN=true`) +- [ ] 配置防火墙仅开放 80/443/2222(deploy.sh 自动完成) +- [ ] 云安全组已放行对应端口 +- [ ] 服务器 SSH 改为密钥登录,禁用密码 +- [ ] 定时备份已配置 +- [ ] MySQL root 密码为随机强密码(deploy.sh 自动生成) +- [ ] Gitea HTTP 端口仅监听 127.0.0.1(不对外暴露) + +--- + +## 六、常见问题 + +**Q: SSL 证书申请失败?** +- 确认域名 A 记录已解析到服务器 IP(用 `dig git.yourdomain.com` 验证) +- 确认 80 端口可从外网访问(云安全组 + 防火墙) +- 等 DNS 生效后重新运行 `bash deploy.sh` + +**Q: 忘记管理员密码?** +```bash +docker compose exec server gitea admin user change-password -u 管理员用户名 -p 新密码 +``` + +**Q: Git LFS push 失败 / 超时?** +- Nginx 已配置 `client_max_body_size 4G`(LFS 路径单独配置) +- 如仍超时,检查服务器带宽和磁盘空间 + +**Q: GPG 签名的提交不显示 "Verified"?** +- 确认 GPG 公钥已添加到 Gitea 用户设置 +- 确认 Git 提交邮箱与 GPG 密钥邮箱一致 + +**Q: 如何迁移到新服务器?** +1. 旧服务器执行 `bash backup.sh` +2. 复制部署目录 `/opt/gitea/`、数据 `/var/lib/gitea/`、备份 `/var/backups/gitea/` 到新服务器 +3. 新服务器执行 `bash deploy.sh` +4. 修改域名 DNS 指向新服务器 IP +5. 重新申请 SSL 证书:`certbot certonly --webroot -w /var/www/certbot -d 域名` + +--- + +## 服务器目录结构 + +``` +/opt/gitea/ # 部署目录(INSTALL_DIR) +├── docker-compose.yml # Gitea + MySQL 容器编排 +├── .env.example # 环境变量模板 +├── .env # 运行时配置(自动生成) +├── deploy.sh # 全新服务器一键部署脚本 +├── backup.sh # MySQL + 数据备份脚本 +├── .gitignore +├── README.md +└── nginx/ + └── gitea.conf # Nginx HTTPS 反向代理模板 + +/var/lib/gitea/ # Gitea 应用数据(GITEA_DATA_DIR) +├── gitea/ +│ ├── conf/app.ini # Gitea 运行配置(首次安装后生成) +│ ├── repositories/ # Git 仓库文件 +│ ├── lfs/ # Git LFS 对象存储 +│ ├── attachments/ # Issue / Release 附件 +│ ├── avatars/ # 用户头像 +│ └── ... # 其他运行时数据 +├── git/ # Git 用户 home +└── ssh/ # SSH 密钥 + +/var/lib/mysql/gitea/ # MySQL 数据文件(MYSQL_DATA_DIR) + +/var/backups/gitea/ # 备份文件(BACKUP_DIR) +├── db_日期.sql.gz # MySQL 转储 +├── gitea_data_日期.tar.gz # Gitea 数据快照 +├── config_日期.tar.gz # 配置备份 +└── cron.log # 定时备份日志 + +/etc/nginx/sites-available/gitea # Nginx HTTPS 配置(由 deploy.sh 生成) +/etc/letsencrypt/live/域名/ # SSL 证书(由 Certbot 管理) +``` diff --git a/gitea/backup.sh b/gitea/backup.sh new file mode 100644 index 0000000..ffe4346 --- /dev/null +++ b/gitea/backup.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================ +# Gitea 备份脚本 (MySQL 版) +# 备份内容:MySQL 数据库 + Gitea 数据(含 LFS) + 配置 +# 定时执行: crontab -e → 0 3 * * * /opt/gitea/backup.sh +# ============================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# 加载配置 +if [ ! -f .env ]; then + echo "[ERROR] .env 文件不存在" >&2 + exit 1 +fi +set -a +source .env +set +a + +BACKUP_DIR="${BACKUP_DIR:-/var/backups/gitea}" +GITEA_DATA_DIR="${GITEA_DATA_DIR:-/var/lib/gitea}" +DATE=$(date +%Y%m%d_%H%M%S) +KEEP_DAYS=30 + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } + +mkdir -p "$BACKUP_DIR" + +# 1. 备份 MySQL 数据库 +log "正在备份 MySQL 数据库..." +docker compose exec -T db mysqldump \ + -u root -p"${DB_ROOT_PASSWORD}" \ + --single-transaction \ + --routines \ + --triggers \ + --databases gitea \ + | gzip > "${BACKUP_DIR}/db_${DATE}.sql.gz" +log "数据库备份完成: db_${DATE}.sql.gz ($(du -h "${BACKUP_DIR}/db_${DATE}.sql.gz" | cut -f1))" + +# 2. 备份 Gitea 数据(仓库、LFS、附件、头像等) +log "正在备份 Gitea 数据目录(含 LFS)..." +tar czf "${BACKUP_DIR}/gitea_data_${DATE}.tar.gz" \ + -C "$(dirname "$GITEA_DATA_DIR")" \ + "$(basename "$GITEA_DATA_DIR")" +log "数据目录备份完成: gitea_data_${DATE}.tar.gz ($(du -h "${BACKUP_DIR}/gitea_data_${DATE}.tar.gz" | cut -f1))" + +# 3. 备份配置文件 +log "正在备份配置文件..." +tar czf "${BACKUP_DIR}/config_${DATE}.tar.gz" \ + -C "$SCRIPT_DIR" \ + .env docker-compose.yml nginx/ +log "配置备份完成: config_${DATE}.tar.gz" + +# 4. 清理过期备份 +log "清理 ${KEEP_DAYS} 天前的备份..." +deleted=$(find "$BACKUP_DIR" -type f -mtime +${KEEP_DAYS} -print -delete | wc -l) +log "已清理 ${deleted} 个过期文件" + +# 5. 输出备份摘要 +echo "" +log "===== 备份完成 =====" +log "备份目录: ${BACKUP_DIR}/" +ls -lh "${BACKUP_DIR}/"*"${DATE}"* 2>/dev/null || true +echo "" +log "总备份空间占用: $(du -sh "${BACKUP_DIR}" | cut -f1)" diff --git a/gitea/deploy.sh b/gitea/deploy.sh new file mode 100644 index 0000000..530f3ac --- /dev/null +++ b/gitea/deploy.sh @@ -0,0 +1,479 @@ +#!/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://get.docker.com | bash + 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.0 数据库" + 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 "$@" diff --git a/gitea/docker-compose.yml b/gitea/docker-compose.yml new file mode 100644 index 0000000..aab80dd --- /dev/null +++ b/gitea/docker-compose.yml @@ -0,0 +1,82 @@ +networks: + gitea: + external: false + +services: + server: + image: ${GITEA_IMAGE:-gitea/gitea:1.25} + container_name: gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + # ---- 数据库 (MySQL) ---- + - GITEA__database__DB_TYPE=mysql + - GITEA__database__HOST=db:3306 + - GITEA__database__NAME=gitea + - GITEA__database__USER=gitea + - GITEA__database__PASSWD=${DB_PASSWORD} + # ---- 域名与访问 ---- + - GITEA__server__DOMAIN=${GITEA_DOMAIN} + - GITEA__server__SSH_DOMAIN=${GITEA_DOMAIN} + - GITEA__server__ROOT_URL=https://${GITEA_DOMAIN} + - GITEA__server__HTTP_PORT=3000 + - GITEA__server__SSH_PORT=${SSH_PORT:-2222} + - GITEA__server__SSH_LISTEN_PORT=2222 + - GITEA__server__LFS_START_SERVER=true + # ---- Git LFS ---- + - GITEA__lfs__STORAGE_TYPE=local + # ---- SSH 密钥 ---- + - GITEA__server__START_SSH_SERVER=true + - GITEA__server__DISABLE_SSH=false + # ---- GPG 签名 ---- + - GITEA__repository__ENABLE_PUSH_CREATE_USER=true + - GITEA__repository_0X2E_signing__SIGNING_KEY=default + - GITEA__repository_0X2E_signing__SIGNING_NAME=${GPG_SIGNING_NAME:-Gitea} + - GITEA__repository_0X2E_signing__SIGNING_EMAIL=${GPG_SIGNING_EMAIL:-gitea@localhost} + - GITEA__repository_0X2E_signing__INITIAL_COMMIT=always + - GITEA__repository_0X2E_signing__DEFAULT_TRUST_MODEL=committer + # ---- 安全 ---- + - GITEA__service__DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-false} + - GITEA__service__REQUIRE_SIGNIN_VIEW=${REQUIRE_SIGNIN:-false} + - GITEA__service__DEFAULT_KEEP_EMAIL_PRIVATE=true + - GITEA__openid__ENABLE_OPENID_SIGNIN=false + - GITEA__mailer__ENABLED=false + restart: always + networks: + - gitea + volumes: + - ${GITEA_DATA_DIR:-/var/lib/gitea}:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "127.0.0.1:3000:3000" + - "0.0.0.0:${SSH_PORT:-2222}:2222" + depends_on: + db: + condition: service_healthy + + db: + image: mysql:8.0 + container_name: gitea-db + restart: always + environment: + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD} + - MYSQL_USER=gitea + - MYSQL_PASSWORD=${DB_PASSWORD} + - MYSQL_DATABASE=gitea + command: > + --character-set-server=utf8mb4 + --collation-server=utf8mb4_unicode_ci + --mysql-native-password=ON + --innodb-buffer-pool-size=256M + --max-allowed-packet=256M + networks: + - gitea + volumes: + - ${MYSQL_DATA_DIR:-/var/lib/mysql/gitea}:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_ROOT_PASSWORD}"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s diff --git a/gitea/nginx/gitea.conf b/gitea/nginx/gitea.conf new file mode 100644 index 0000000..89b853b --- /dev/null +++ b/gitea/nginx/gitea.conf @@ -0,0 +1,80 @@ +# Gitea Nginx 反向代理配置 +# 由 deploy.sh 自动部署到 /etc/nginx/sites-available/gitea +# __GITEA_DOMAIN__ 会被脚本替换为实际域名 + +# HTTP → HTTPS 重定向 +server { + listen 80; + listen [::]:80; + server_name __GITEA_DOMAIN__; + + # Let's Encrypt 证书验证 + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS 主站点 +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name __GITEA_DOMAIN__; + + # SSL 证书(Certbot 自动管理) + ssl_certificate /etc/letsencrypt/live/__GITEA_DOMAIN__/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/__GITEA_DOMAIN__/privkey.pem; + + # SSL 安全配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + + # HSTS(启用后浏览器会强制使用 HTTPS) + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; + + # 安全头 + add_header X-Frame-Options SAMEORIGIN always; + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy strict-origin-when-cross-origin always; + + # Git LFS 和大仓库推送需要足够大的 body 限制 + client_max_body_size 512M; + + # 代理超时(大仓库 clone/push 可能较慢) + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + + location / { + proxy_pass http://127.0.0.1:3000; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket 支持(Gitea 实时通知) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + } + + # Git LFS 大文件upload需要特殊处理 + location ~ ^/.*\.git/info/lfs/ { + proxy_pass http://127.0.0.1:3000; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # LFS 上传可能很大 + client_max_body_size 4G; + proxy_request_buffering off; + } +}