Use single Gitea CI/CD pipeline
This commit is contained in:
@@ -10,6 +10,13 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
no_cache:
|
||||||
|
description: 'Build Docker images without cache'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
@@ -63,3 +70,177 @@ jobs:
|
|||||||
-w /workspace/frontend \
|
-w /workspace/frontend \
|
||||||
node:24-alpine \
|
node:24-alpine \
|
||||||
sh -lc 'npm ci && npm run build'
|
sh -lc 'npm ci && npm run build'
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy to award.noveria.net
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: verify
|
||||||
|
timeout-minutes: 30
|
||||||
|
if: |
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event_name == 'push' && gitea.ref == 'refs/heads/main')
|
||||||
|
env:
|
||||||
|
DEPLOY_PATH: /home/projekte_bao/vtuber-awards
|
||||||
|
DEPLOY_APP_PATH: /home/projekte_bao/vtuber-awards/app
|
||||||
|
DB_NETWORK: vtuber-awards_internal
|
||||||
|
LIVE_URL: https://award.noveria.net
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Resolve deploy metadata
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "sha=$(git rev-parse HEAD)" >> "$GITEA_OUTPUT"
|
||||||
|
echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITEA_OUTPUT"
|
||||||
|
git log -1 --oneline
|
||||||
|
|
||||||
|
- name: Verify production host layout
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker run --rm \
|
||||||
|
-v "${DEPLOY_PATH}:/deploy:ro" \
|
||||||
|
alpine:latest \
|
||||||
|
sh -lc '
|
||||||
|
test -f /deploy/compose.yaml
|
||||||
|
test -f /deploy/deploy/backend.Dockerfile
|
||||||
|
test -f /deploy/deploy/web.Dockerfile
|
||||||
|
test -d /deploy/app
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Sync code to production app directory
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker run --rm \
|
||||||
|
-v "${{ gitea.workspace }}:/src:ro" \
|
||||||
|
-v "${DEPLOY_APP_PATH}:/dest" \
|
||||||
|
alpine:latest \
|
||||||
|
sh -lc '
|
||||||
|
set -e
|
||||||
|
dest_owner="$(stat -c "%u:%g" /dest)"
|
||||||
|
find /dest -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
|
||||||
|
cd /src
|
||||||
|
find . -mindepth 1 -maxdepth 1 ! -name .git -exec cp -R {} /dest/ \;
|
||||||
|
chown -R "$dest_owner" /dest
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Build Docker images
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
no_cache="${{ github.event_name == 'workflow_dispatch' && inputs.no_cache || false }}"
|
||||||
|
build_args=""
|
||||||
|
if [ "$no_cache" = "true" ]; then
|
||||||
|
build_args="--no-cache"
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker run --rm \
|
||||||
|
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-w /workspace/vtube-awards \
|
||||||
|
docker:cli \
|
||||||
|
sh -lc "docker compose config >/dev/null && docker compose build ${build_args} api web"
|
||||||
|
|
||||||
|
- name: Backup database before migrations
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker run --rm \
|
||||||
|
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-w /workspace/vtube-awards \
|
||||||
|
docker:cli \
|
||||||
|
sh -lc '
|
||||||
|
set -e
|
||||||
|
mkdir -p backups
|
||||||
|
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
backup="backups/predeploy-${{ steps.meta.outputs.short_sha }}-${stamp}.dump"
|
||||||
|
docker compose exec -T postgres sh -lc '"'"'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc'"'"' > "$backup"
|
||||||
|
test -s "$backup"
|
||||||
|
find backups -name "predeploy-*.dump" -mtime +14 -delete
|
||||||
|
echo "Database backup written to $backup"
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Apply EF Core migrations
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker run --rm \
|
||||||
|
-e DEPLOY_APP_PATH="${DEPLOY_APP_PATH}" \
|
||||||
|
-e DB_NETWORK="${DB_NETWORK}" \
|
||||||
|
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-w /workspace/vtube-awards \
|
||||||
|
docker:cli \
|
||||||
|
sh -lc '
|
||||||
|
set -e
|
||||||
|
db="$(docker compose exec -T postgres printenv POSTGRES_DB)"
|
||||||
|
user="$(docker compose exec -T postgres printenv POSTGRES_USER)"
|
||||||
|
password="$(docker compose exec -T postgres printenv POSTGRES_PASSWORD)"
|
||||||
|
docker run --rm \
|
||||||
|
--network "${DB_NETWORK}" \
|
||||||
|
-v "${DEPLOY_APP_PATH}/Backend:/src/Backend" \
|
||||||
|
-w /src/Backend \
|
||||||
|
-e "VTSA_POSTGRES=Host=postgres;Port=5432;Database=${db};Username=${user};Password=${password}" \
|
||||||
|
mcr.microsoft.com/dotnet/sdk:8.0-alpine \
|
||||||
|
sh -lc "dotnet tool install --global dotnet-ef >/dev/null && export PATH=\$PATH:/root/.dotnet/tools && dotnet restore >/dev/null && dotnet ef database update"
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Restart production services
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker run --rm \
|
||||||
|
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-w /workspace/vtube-awards \
|
||||||
|
docker:cli \
|
||||||
|
sh -lc 'docker compose up -d --wait --force-recreate api web'
|
||||||
|
|
||||||
|
- name: Verify live health
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
retry() {
|
||||||
|
label="$1"
|
||||||
|
url="$2"
|
||||||
|
attempts=10
|
||||||
|
wait=3
|
||||||
|
for attempt in $(seq 1 "$attempts"); do
|
||||||
|
if body="$(curl -fsS --max-time 15 "$url")"; then
|
||||||
|
echo "$label OK on attempt $attempt"
|
||||||
|
echo "$body"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "$label failed on attempt $attempt/$attempts; waiting ${wait}s"
|
||||||
|
sleep "$wait"
|
||||||
|
done
|
||||||
|
echo "$label failed after $attempts attempts"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
retry "API health" "${LIVE_URL}/api/health"
|
||||||
|
db_body="$(curl -fsS --max-time 15 "${LIVE_URL}/api/health/database")"
|
||||||
|
echo "$db_body"
|
||||||
|
echo "$db_body" | grep -q '"canConnect":true'
|
||||||
|
echo "$db_body" | grep -q '"pendingMigrations":\[\]'
|
||||||
|
|
||||||
|
- name: Verify frontend assets
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
index="$(curl -fsS --max-time 15 "${LIVE_URL}/")"
|
||||||
|
js_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.js' | head -n 1)"
|
||||||
|
css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)"
|
||||||
|
test -n "$js_asset"
|
||||||
|
test -n "$css_asset"
|
||||||
|
curl -fsS --max-time 20 "${LIVE_URL}${js_asset}" >/dev/null
|
||||||
|
curl -fsS --max-time 20 "${LIVE_URL}${css_asset}" >/dev/null
|
||||||
|
echo "Frontend assets served: ${js_asset}, ${css_asset}"
|
||||||
|
|
||||||
|
- name: Show production containers
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-w /workspace/vtube-awards \
|
||||||
|
docker:cli \
|
||||||
|
sh -lc 'docker compose ps'
|
||||||
|
|||||||
@@ -1,204 +0,0 @@
|
|||||||
name: Deploy Production
|
|
||||||
run-name: Deploy VTubeAwards by @${{ gitea.actor }}
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: vtubeawards-production
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["CI - Build & Verify"]
|
|
||||||
types: [completed]
|
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
git_ref:
|
|
||||||
description: 'Git ref to deploy'
|
|
||||||
required: false
|
|
||||||
default: 'main'
|
|
||||||
type: string
|
|
||||||
no_cache:
|
|
||||||
description: 'Build Docker images without cache'
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Deploy to award.noveria.net
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
if: |
|
|
||||||
github.event_name == 'workflow_dispatch' ||
|
|
||||||
(github.event_name == 'workflow_run' &&
|
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
|
||||||
!contains(github.event.workflow_run.head_commit.message, '[skip ci]'))
|
|
||||||
env:
|
|
||||||
DEPLOY_PATH: /home/projekte_bao/vtuber-awards
|
|
||||||
DEPLOY_APP_PATH: /home/projekte_bao/vtuber-awards/app
|
|
||||||
COMPOSE_PROJECT: vtuber-awards
|
|
||||||
DB_NETWORK: vtuber-awards_internal
|
|
||||||
LIVE_URL: https://award.noveria.net
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.git_ref || 'main' }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Resolve deploy metadata
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo "sha=$(git rev-parse HEAD)" >> "$GITEA_OUTPUT"
|
|
||||||
echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITEA_OUTPUT"
|
|
||||||
echo "Deploying $(git rev-parse --short HEAD)"
|
|
||||||
git log -1 --oneline
|
|
||||||
|
|
||||||
- name: Verify production host layout
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
docker run --rm \
|
|
||||||
-v "${DEPLOY_PATH}:/deploy:ro" \
|
|
||||||
alpine:latest \
|
|
||||||
sh -lc '
|
|
||||||
test -f /deploy/compose.yaml
|
|
||||||
test -f /deploy/deploy/backend.Dockerfile
|
|
||||||
test -f /deploy/deploy/web.Dockerfile
|
|
||||||
test -d /deploy/app
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Sync code to production app directory
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
docker run --rm \
|
|
||||||
-v "${{ gitea.workspace }}:/src:ro" \
|
|
||||||
-v "${DEPLOY_APP_PATH}:/dest" \
|
|
||||||
alpine:latest \
|
|
||||||
sh -lc '
|
|
||||||
set -e
|
|
||||||
dest_owner="$(stat -c "%u:%g" /dest)"
|
|
||||||
find /dest -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
|
|
||||||
cd /src
|
|
||||||
find . -mindepth 1 -maxdepth 1 ! -name .git -exec cp -R {} /dest/ \;
|
|
||||||
chown -R "$dest_owner" /dest
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Build Docker images
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
no_cache="${{ github.event_name == 'workflow_dispatch' && inputs.no_cache || false }}"
|
|
||||||
build_args=""
|
|
||||||
if [ "$no_cache" = "true" ]; then
|
|
||||||
build_args="--no-cache"
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker run --rm \
|
|
||||||
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
-w /workspace/vtube-awards \
|
|
||||||
docker:cli \
|
|
||||||
sh -lc "docker compose config >/dev/null && docker compose build ${build_args} api web"
|
|
||||||
|
|
||||||
- name: Backup database before migrations
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
docker run --rm \
|
|
||||||
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
-w /workspace/vtube-awards \
|
|
||||||
docker:cli \
|
|
||||||
sh -lc '
|
|
||||||
set -e
|
|
||||||
mkdir -p backups
|
|
||||||
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
||||||
backup="backups/predeploy-${{ steps.meta.outputs.short_sha }}-${stamp}.dump"
|
|
||||||
docker compose exec -T postgres sh -lc '"'"'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc'"'"' > "$backup"
|
|
||||||
test -s "$backup"
|
|
||||||
find backups -name "predeploy-*.dump" -mtime +14 -delete
|
|
||||||
echo "Database backup written to $backup"
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Apply EF Core migrations
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
docker run --rm \
|
|
||||||
-e DEPLOY_APP_PATH="${DEPLOY_APP_PATH}" \
|
|
||||||
-e DB_NETWORK="${DB_NETWORK}" \
|
|
||||||
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
-w /workspace/vtube-awards \
|
|
||||||
docker:cli \
|
|
||||||
sh -lc '
|
|
||||||
set -e
|
|
||||||
db="$(docker compose exec -T postgres printenv POSTGRES_DB)"
|
|
||||||
user="$(docker compose exec -T postgres printenv POSTGRES_USER)"
|
|
||||||
password="$(docker compose exec -T postgres printenv POSTGRES_PASSWORD)"
|
|
||||||
docker run --rm \
|
|
||||||
--network "${DB_NETWORK}" \
|
|
||||||
-v "${DEPLOY_APP_PATH}/Backend:/src/Backend" \
|
|
||||||
-w /src/Backend \
|
|
||||||
-e "VTSA_POSTGRES=Host=postgres;Port=5432;Database=${db};Username=${user};Password=${password}" \
|
|
||||||
mcr.microsoft.com/dotnet/sdk:8.0-alpine \
|
|
||||||
sh -lc "dotnet tool install --global dotnet-ef >/dev/null && export PATH=\$PATH:/root/.dotnet/tools && dotnet restore >/dev/null && dotnet ef database update"
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Restart production services
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
docker run --rm \
|
|
||||||
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
-w /workspace/vtube-awards \
|
|
||||||
docker:cli \
|
|
||||||
sh -lc 'docker compose up -d --wait --force-recreate api web'
|
|
||||||
|
|
||||||
- name: Verify live health
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
retry() {
|
|
||||||
label="$1"
|
|
||||||
url="$2"
|
|
||||||
attempts=10
|
|
||||||
wait=3
|
|
||||||
for attempt in $(seq 1 "$attempts"); do
|
|
||||||
if body="$(curl -fsS --max-time 15 "$url")"; then
|
|
||||||
echo "$label OK on attempt $attempt"
|
|
||||||
echo "$body"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "$label failed on attempt $attempt/$attempts; waiting ${wait}s"
|
|
||||||
sleep "$wait"
|
|
||||||
done
|
|
||||||
echo "$label failed after $attempts attempts"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
retry "API health" "${LIVE_URL}/api/health"
|
|
||||||
db_body="$(curl -fsS --max-time 15 "${LIVE_URL}/api/health/database")"
|
|
||||||
echo "$db_body"
|
|
||||||
echo "$db_body" | grep -q '"canConnect":true'
|
|
||||||
echo "$db_body" | grep -q '"pendingMigrations":\[\]'
|
|
||||||
|
|
||||||
- name: Verify frontend assets
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
index="$(curl -fsS --max-time 15 "${LIVE_URL}/")"
|
|
||||||
js_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.js' | head -n 1)"
|
|
||||||
css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)"
|
|
||||||
test -n "$js_asset"
|
|
||||||
test -n "$css_asset"
|
|
||||||
curl -fsS --max-time 20 "${LIVE_URL}${js_asset}" >/dev/null
|
|
||||||
curl -fsS --max-time 20 "${LIVE_URL}${css_asset}" >/dev/null
|
|
||||||
echo "Frontend assets served: ${js_asset}, ${css_asset}"
|
|
||||||
|
|
||||||
- name: Show production containers
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
docker run --rm \
|
|
||||||
-v "${DEPLOY_PATH}:/workspace/vtube-awards" \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
-w /workspace/vtube-awards \
|
|
||||||
docker:cli \
|
|
||||||
sh -lc 'docker compose ps'
|
|
||||||
Reference in New Issue
Block a user