feat(deploy): 新增 Forgejo NAS 自托管 Git + CI/CD 配置
Deploy / deploy (push) Has been cancelled

- 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 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-04-30 22:09:00 +08:00
parent 50b139c97a
commit 21088fc908
4 changed files with 219 additions and 0 deletions
+36
View File
@@ -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
+30
View File
@@ -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
+100
View File
@@ -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
+53
View File
@@ -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