Files
meezi/.gitea/workflows/ci-cd.yml
T
soroush.asadi aec5b21f98
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m40s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 1m22s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m0s
CI/CD / CI · Admin Web (tsc) (push) Successful in 33s
CI/CD / CI · Website (tsc) (push) Successful in 43s
CI/CD / CI · Koja (tsc) (push) Successful in 47s
CI/CD / Deploy · all services (push) Failing after 2s
fix: lock compose project name to 'meezi', scope image prune to meezi only
Prevents runner workspace collisions with other projects (DrSousan etc.)
causing containers to be treated as orphans and stopped on deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:45:07 +03:30

361 lines
13 KiB
YAML

name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: meezi-cicd-${{ github.ref }}
cancel-in-progress: true
# ─────────────────────────────────────────────────────────────────────────────
# HOW THIS WORKS
# ─────────────────────────────────────────────────────────────────────────────
# Runner labels (in gitea docker-compose):
# ubuntu-latest:docker://node:20-alpine ← CI jobs run in real Docker containers
# self-hosted:host ← deploy runs directly on the server
#
# All images/packages served from Nexus at mirror.soroushasadi.com:
# Docker images → mirror.soroushasadi.com (docker-group: Docker Hub + MCR)
# NuGet → https://mirror.soroushasadi.com/repository/nuget-group/
# npm → https://mirror.soroushasadi.com/repository/npm-group/
#
# Docker daemon: merge docker/daemon-registry-mirror.example.json into daemon.json
# ─────────────────────────────────────────────────────────────────────────────
jobs:
api-build:
name: "CI · API (dotnet build + test)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/dotnet/sdk:10.0
options: >-
--add-host=gitea:host-gateway
services:
postgres:
image: mirror.soroushasadi.com/postgres:16-alpine
env:
POSTGRES_DB: meezi_test
POSTGRES_USER: meezi
POSTGRES_PASSWORD: meezi_test_pass
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
redis:
image: mirror.soroushasadi.com/redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout FETCH_HEAD
- name: Write NuGet config
run: |
cat > /tmp/nuget.ci.config << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nexus"
value="https://mirror.soroushasadi.com/repository/nuget-group/index.json"
protocolVersion="3"
/>
</packageSources>
</configuration>
EOF
- name: Restore
run: dotnet restore src/Meezi.API/Meezi.API.csproj --configfile /tmp/nuget.ci.config
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
- name: Build
run: dotnet build src/Meezi.API/Meezi.API.csproj --no-restore -c Release
- name: Test
run: dotnet test --no-build -c Release --logger "console;verbosity=minimal"
env:
ConnectionStrings__DefaultConnection: "Host=postgres;Port=5432;Database=meezi_test;Username=meezi;Password=meezi_test_pass"
ConnectionStrings__Redis: "redis:6379"
admin-api-build:
name: "CI · Admin API (dotnet build)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/dotnet/sdk:10.0
options: >-
--add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout FETCH_HEAD
- name: Write NuGet config
run: |
cat > /tmp/nuget.ci.config << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nexus"
value="https://mirror.soroushasadi.com/repository/nuget-group/index.json"
protocolVersion="3"
/>
</packageSources>
</configuration>
EOF
- name: Restore
run: dotnet restore src/Meezi.Admin.API/Meezi.Admin.API.csproj --configfile /tmp/nuget.ci.config
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
- name: Build
run: dotnet build src/Meezi.Admin.API/Meezi.Admin.API.csproj --no-restore -c Release
dashboard-check:
name: "CI · Dashboard (tsc)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/node:20-alpine
options: >-
--add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
SHA: ${{ github.sha }}
run: |
wget -q \
--header "Authorization: Bearer ${TOKEN}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${SHA}.tar.gz" \
-O /tmp/repo.tar.gz
tar -xzf /tmp/repo.tar.gz --strip-components=1
rm -f /tmp/repo.tar.gz
- name: Install dependencies
working-directory: web/dashboard
run: npm install --legacy-peer-deps --ignore-scripts --registry https://mirror.soroushasadi.com/repository/npm-group/ --strict-ssl=false
- name: TypeScript check
working-directory: web/dashboard
run: npx tsc --noEmit
env:
NEXT_PUBLIC_API_URL: http://localhost:5080
admin-web-check:
name: "CI · Admin Web (tsc)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/node:20-alpine
options: >-
--add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
SHA: ${{ github.sha }}
run: |
wget -q \
--header "Authorization: Bearer ${TOKEN}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${SHA}.tar.gz" \
-O /tmp/repo.tar.gz
tar -xzf /tmp/repo.tar.gz --strip-components=1
rm -f /tmp/repo.tar.gz
- name: Install dependencies
working-directory: web/admin
run: npm install --legacy-peer-deps --ignore-scripts --registry https://mirror.soroushasadi.com/repository/npm-group/ --strict-ssl=false
- name: TypeScript check
working-directory: web/admin
run: npx tsc --noEmit
env:
NEXT_PUBLIC_ADMIN_API_URL: http://localhost:5081
website-check:
name: "CI · Website (tsc)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/node:20-alpine
options: >-
--add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
SHA: ${{ github.sha }}
run: |
wget -q \
--header "Authorization: Bearer ${TOKEN}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${SHA}.tar.gz" \
-O /tmp/repo.tar.gz
tar -xzf /tmp/repo.tar.gz --strip-components=1
rm -f /tmp/repo.tar.gz
- name: Install dependencies
working-directory: web/website
run: npm install --legacy-peer-deps --ignore-scripts --registry https://mirror.soroushasadi.com/repository/npm-group/ --strict-ssl=false
- name: TypeScript check
working-directory: web/website
run: npx tsc --noEmit
env:
MEEZI_API_URL: http://localhost:5080
koja-check:
name: "CI · Koja (tsc)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/node:20-alpine
options: >-
--add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
SHA: ${{ github.sha }}
run: |
wget -q \
--header "Authorization: Bearer ${TOKEN}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${SHA}.tar.gz" \
-O /tmp/repo.tar.gz
tar -xzf /tmp/repo.tar.gz --strip-components=1
rm -f /tmp/repo.tar.gz
- name: Install dependencies
working-directory: web/koja
run: npm install --legacy-peer-deps --ignore-scripts --registry https://mirror.soroushasadi.com/repository/npm-group/ --strict-ssl=false
- name: TypeScript check
working-directory: web/koja
run: npx tsc --noEmit
env:
NEXT_PUBLIC_API_URL: http://localhost:5080
# ─────────────────────────────────────────────────────────────────────────────
# DEPLOY — only on push to main, only if ALL CI jobs pass.
# self-hosted:host — runs directly on your server where Docker is installed.
# ─────────────────────────────────────────────────────────────────────────────
deploy:
name: "Deploy · all services"
runs-on: self-hosted
env:
# act runner (host mode) starts with a minimal PATH — extend it so
# docker (/usr/bin or /usr/local/bin) and snap packages are found.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
needs:
- api-build
- admin-api-build
- dashboard-check
- admin-web-check
- website-check
- koja-check
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
timeout-minutes: 40
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout FETCH_HEAD
- name: Write .env
run: printf '%s' "$ENV_FILE" > .env
env:
ENV_FILE: ${{ secrets.ENV_FILE }}
- name: Build main images (api, web, website, koja)
run: docker compose build --parallel api web website koja
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
- name: Build admin images (admin-api, admin-web)
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.admin.yml \
build --parallel admin-api admin-web
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
- name: Start main services
run: |
docker compose up -d \
--no-deps \
postgres redis api web website koja
- name: Start admin services
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.admin.yml \
up -d \
--no-deps \
admin-api admin-web
- name: Wait for main API healthy
run: |
for i in $(seq 1 24); do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' meezi-api 2>/dev/null || echo "missing")
echo " [$i/24] $STATUS"
[ "$STATUS" = "healthy" ] && echo "✅ meezi-api healthy" && break
[ "$i" = "24" ] && echo "❌ meezi-api timeout" && docker compose logs --tail=40 api && exit 1
sleep 5
done
- name: Wait for admin API healthy
run: |
for i in $(seq 1 24); do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' meezi-admin-api 2>/dev/null || echo "missing")
echo " [$i/24] $STATUS"
[ "$STATUS" = "healthy" ] && echo "✅ meezi-admin-api healthy" && break
[ "$i" = "24" ] && echo "❌ meezi-admin-api timeout" && docker compose -f docker-compose.yml -f docker-compose.admin.yml logs --tail=40 admin-api && exit 1
sleep 5
done
- name: Show all running containers
if: always()
run: docker compose -f docker-compose.yml -f docker-compose.admin.yml ps
- name: Prune old meezi images
if: success()
# Only remove untagged (dangling) meezi images — never touches other projects
run: |
docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}' \
| grep '^hostexecutor-' \
| grep '<none>' \
| awk '{print $2}' \
| xargs -r docker rmi || true