From 21088fc9088286a7654653a138f4623c1ed614bc Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Thu, 30 Apr 2026 22:09:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=E6=96=B0=E5=A2=9E=20Forgejo=20?= =?UTF-8?q?NAS=20=E8=87=AA=E6=89=98=E7=AE=A1=20Git=20+=20CI/CD=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deploy/runner/docker-compose.forgejo.yml:Forgejo + act_runner,ROOT_URL 参数化 - .gitea/workflows/ci.yml:非 main 分支测试 - .gitea/workflows/deploy.yml:main 推送自动构建、创建 Forgejo Release、部署到 EC2 - .gitea/workflows/backup.yml:每日 02:00 备份 MySQL 到 NAS /volume1/backups Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/backup.yml | 36 ++++++++ .gitea/workflows/ci.yml | 30 +++++++ .gitea/workflows/deploy.yml | 100 +++++++++++++++++++++++ deploy/runner/docker-compose.forgejo.yml | 53 ++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 .gitea/workflows/backup.yml create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/deploy.yml create mode 100644 deploy/runner/docker-compose.forgejo.yml diff --git a/.gitea/workflows/backup.yml b/.gitea/workflows/backup.yml new file mode 100644 index 0000000..8fa10f7 --- /dev/null +++ b/.gitea/workflows/backup.yml @@ -0,0 +1,36 @@ +name: DB Backup + +on: + schedule: + - cron: '0 18 * * *' # UTC 18:00 = 北京时间 02:00 + workflow_dispatch: + +jobs: + backup: + runs-on: ubuntu-latest + steps: + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2.pem + chmod 600 ~/.ssh/ec2.pem + ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts + + - name: Dump MySQL to NAS + env: + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_USER: ${{ secrets.EC2_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + run: | + BACKUP_DIR=/volume1/backups/jiu-db + mkdir -p $BACKUP_DIR + FILENAME="jiu_db_$(date +%Y%m%d_%H%M%S).sql.gz" + ssh -i ~/.ssh/ec2.pem ${EC2_USER}@${EC2_HOST} \ + "docker exec jiu_mysql mysqldump -uroot -p${DB_PASSWORD} jiu_db" \ + | gzip > ${BACKUP_DIR}/${FILENAME} + find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete + echo "Saved: ${BACKUP_DIR}/${FILENAME}" + + - name: Cleanup SSH key + if: always() + run: rm -f ~/.ssh/ec2.pem diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..61b939a --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches-ignore: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: backend/go.mod + cache-dependency-path: backend/go.sum + + - name: Go tests + working-directory: backend + run: go test ./... + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Flutter tests + working-directory: client + run: flutter test diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..46cbd5b --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,100 @@ +name: Deploy + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: backend/go.mod + cache-dependency-path: backend/go.sum + + - name: Go tests + working-directory: backend + run: go test ./... + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Flutter tests + working-directory: client + run: flutter test + + - name: Build backend (linux/amd64) + working-directory: backend + run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o jiu-server . + + - name: Build Flutter Web + working-directory: client + run: flutter build web --release --dart-define=BASE_URL=https://jiu.51yanmei.com --dart-define=PUBLIC_URL=https://jiu.51yanmei.com + + - name: Package artifacts + run: | + tar -czf web.tar.gz -C client/build web + echo "VERSION=v$(date +%Y%m%d.%H%M)" >> $GITHUB_ENV + + - name: Create Forgejo Release + env: + FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} + FORGEJO_URL: ${{ secrets.FORGEJO_URL }} + run: | + # 创建 Release,获取 ID + RELEASE_ID=$(curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases" \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"${VERSION}\",\"name\":\"Release ${VERSION}\",\"draft\":false,\"prerelease\":false}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") + + # 上传 Go 二进制 + curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + -F "attachment=@backend/jiu-server" + + # 上传 Flutter Web + curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + -F "attachment=@web.tar.gz" + + echo "Release ${VERSION} created: ${FORGEJO_URL}/${{ gitea.repository }}/releases/tag/${VERSION}" + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2.pem + chmod 600 ~/.ssh/ec2.pem + ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts + + - name: Deploy to EC2 + env: + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_USER: ${{ secrets.EC2_USER }} + run: | + scp -i ~/.ssh/ec2.pem backend/jiu-server ${EC2_USER}@${EC2_HOST}:/tmp/jiu-server + rsync -avz --delete -e "ssh -i ~/.ssh/ec2.pem" \ + client/build/web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-web-new/ + ssh -i ~/.ssh/ec2.pem ${EC2_USER}@${EC2_HOST} << 'ENDSSH' + sudo systemctl stop jiu + cp /tmp/jiu-server /opt/jiu/backend/jiu-server + chmod +x /opt/jiu/backend/jiu-server + sudo systemctl start jiu + for i in $(seq 1 30); do + curl -sf http://localhost:8080/health && break + sleep 2 + done + rm -rf /opt/jiu/web-old + mv /opt/jiu/web /opt/jiu/web-old 2>/dev/null || true + mv /tmp/jiu-web-new /opt/jiu/web + sudo nginx -s reload + ENDSSH + + - name: Cleanup SSH key + if: always() + run: rm -f ~/.ssh/ec2.pem diff --git a/deploy/runner/docker-compose.forgejo.yml b/deploy/runner/docker-compose.forgejo.yml new file mode 100644 index 0000000..7cda440 --- /dev/null +++ b/deploy/runner/docker-compose.forgejo.yml @@ -0,0 +1,53 @@ +# 群晖 NAS 自托管 Forgejo + act_runner +# +# 使用方法: +# 1. 在同目录创建 .env(不要提交到 git),填入以下变量: +# FORGEJO_ROOT_URL=http://192.168.1.x:3000 +# NAS_IP=192.168.1.x +# RUNNER_TOKEN=(第5步获取) +# 2. 将 act-runner-config.yaml 复制到 NAS: +# /volume1/docker/forgejo/runner/config.yaml +# 3. Container Manager 导入此文件并启动(或 docker compose up -d) +# 4. 浏览器访问 http://NAS-IP:3000 完成 Forgejo 初始化(创建管理员账号) +# 5. 站点管理 → 配置 → 开启 Actions +# 仓库 Settings → Actions → Runners → 获取注册 Token → 填入 .env RUNNER_TOKEN +# 6. 重启 act-runner 容器完成 Runner 注册 +# +# 本地访问:http://NAS-IP:3000 +# Git SSH: ssh://git@NAS-IP:2222/用户名/jiu.git + +services: + forgejo: + image: codeberg.org/forgejo/forgejo:latest + container_name: forgejo + restart: always + environment: + USER_UID: 1000 + USER_GID: 1000 + FORGEJO__actions__ENABLED: "true" + FORGEJO__server__ROOT_URL: ${FORGEJO_ROOT_URL:-http://localhost:3000} + FORGEJO__server__SSH_DOMAIN: ${NAS_IP:-localhost} + FORGEJO__server__SSH_PORT: 2222 + ports: + - "3000:3000" + - "2222:22" + volumes: + - /volume1/docker/forgejo/data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + + act-runner: + image: gitea/act_runner:latest # Forgejo 兼容 Gitea act_runner + container_name: forgejo-act-runner + restart: always + depends_on: + - forgejo + environment: + CONFIG_FILE: /data/config.yaml + GITEA_INSTANCE_URL: http://forgejo:3000 # Docker 内部通信 + GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN} + GITEA_RUNNER_NAME: nas-runner + volumes: + - /volume1/docker/forgejo/runner:/data + - /var/run/docker.sock:/var/run/docker.sock + - /volume1/backups:/volume1/backups