ci(forgejo): mirror .github/workflows to .forgejo/workflows
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Failing after 1s
CI / Web (React + Vite) (push) Failing after 8s

Forgejo Actions runner on the stage server picks up these jobs. Runs on
the same labels `[self-hosted, linux]` — same self-hosted box as the
Docker registry and the stage itself.

deploy-stage is simplified: no SSH round-trip (runner and stage are the
same host), just `cp` + `docker compose pull/up`.

POS job kept as-is; it's gated on tag/dispatch and a Windows runner, so
on Forgejo it'll simply not match any runner and stay queued — that's
fine, POS ships from tags only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nurdotnet 2026-04-23 16:19:24 +05:00
parent 495f0aabee
commit 3c17b963f3
4 changed files with 316 additions and 0 deletions

110
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,110 @@
name: CI
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
backend:
name: Backend (.NET 8)
runs-on: [self-hosted, linux]
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: food_market_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5441:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore
run: dotnet restore food-market.sln
- name: Build
run: dotnet build food-market.sln --no-restore -c Release
- name: Test
env:
ConnectionStrings__Default: Host=localhost;Port=5441;Database=food_market_test;Username=postgres;Password=postgres
run: dotnet test food-market.sln --no-build -c Release --verbosity normal || echo "No tests yet"
web:
name: Web (React + Vite)
runs-on: [self-hosted, linux]
defaults:
run:
working-directory: src/food-market.web
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: src/food-market.web/pnpm-lock.yaml
- name: Install
run: pnpm install --frozen-lockfile
- name: Build (tsc + vite)
run: pnpm build
- name: Upload dist
uses: actions/upload-artifact@v4
with:
name: web-dist-${{ github.sha }}
path: src/food-market.web/dist
retention-days: 14
# POS build costs 2x Windows minutes — run only on tags / manual trigger,
# not on every commit. Releases are built from tags anyway.
pos:
name: POS (WPF, Windows)
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore
run: dotnet restore src/food-market.pos/food-market.pos.csproj
- name: Build POS
run: dotnet build src/food-market.pos/food-market.pos.csproj --no-restore -c Release
- name: Publish self-contained win-x64
run: dotnet publish src/food-market.pos/food-market.pos.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
- name: Upload POS executable
uses: actions/upload-artifact@v4
with:
name: food-market-pos-${{ github.sha }}
path: publish
retention-days: 14

View file

@ -0,0 +1,75 @@
name: Deploy stage
on:
workflow_run:
workflows: ["Docker Images"]
types: [completed]
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-stage
cancel-in-progress: false
jobs:
deploy:
name: docker compose pull + up
runs-on: [self-hosted, linux]
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v4
- name: Write .env + copy compose (runner and stage are the same host)
env:
SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
PGPASS: ${{ secrets.STAGE_POSTGRES_PASSWORD }}
run: |
cat > /home/nns/food-market-stage/deploy/.env <<ENV
REGISTRY=127.0.0.1:5001
API_TAG=$SHA
WEB_TAG=$SHA
POSTGRES_PASSWORD=$PGPASS
ENV
cp deploy/docker-compose.yml /home/nns/food-market-stage/deploy/docker-compose.yml
- name: Pull + up
working-directory: /home/nns/food-market-stage/deploy
run: |
docker compose pull
docker compose up -d --remove-orphans
- name: Smoke test /health
run: |
for i in 1 2 3 4 5 6; do
sleep 5
if curl -fsS http://127.0.0.1:8080/health | grep -q '"status":"ok"'; then
echo "Health OK"
exit 0
fi
done
echo "Health failed"
exit 1
- name: Notify Telegram on success
if: success()
env:
BOT: ${{ secrets.TELEGRAM_BOT_TOKEN }}
CHAT: ${{ secrets.TELEGRAM_CHAT_ID }}
SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
run: |
curl -sS -X POST "https://api.telegram.org/bot$BOT/sendMessage" \
--data-urlencode "chat_id=$CHAT" \
--data-urlencode "text=Deploy stage OK — commit ${SHA:0:7}. https://food-market.zat.kz" \
> /dev/null
- name: Notify Telegram on failure
if: failure()
env:
BOT: ${{ secrets.TELEGRAM_BOT_TOKEN }}
CHAT: ${{ secrets.TELEGRAM_CHAT_ID }}
SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
run: |
curl -sS -X POST "https://api.telegram.org/bot$BOT/sendMessage" \
--data-urlencode "chat_id=$CHAT" \
--data-urlencode "text=Deploy stage FAILED — commit ${SHA:0:7}" \
> /dev/null

View file

@ -0,0 +1,113 @@
name: Docker Images
on:
push:
branches: [main]
paths:
- 'src/food-market.api/**'
- 'src/food-market.web/**'
- 'src/food-market.application/**'
- 'src/food-market.domain/**'
- 'src/food-market.infrastructure/**'
- 'src/food-market.shared/**'
- 'deploy/**'
- '.github/workflows/docker.yml'
workflow_dispatch:
permissions:
contents: read
packages: write
env:
LOCAL_REGISTRY: 127.0.0.1:5001
jobs:
api:
name: API image
runs-on: [self-hosted, linux]
steps:
- uses: actions/checkout@v4
- name: Login to ghcr
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
ACTOR: ${{ github.actor }}
run: |
for i in 1 2 3 4 5; do
if echo "$TOKEN" | docker login ghcr.io -u "$ACTOR" --password-stdin; then
exit 0
fi
echo "login attempt $i failed, retrying in 15s"
sleep 15
done
exit 1
- name: Build + push api
env:
OWNER: ${{ github.repository_owner }}
SHA: ${{ github.sha }}
run: |
docker build -f deploy/Dockerfile.api \
-t $LOCAL_REGISTRY/food-market-api:$SHA \
-t $LOCAL_REGISTRY/food-market-api:latest \
-t ghcr.io/$OWNER/food-market-api:$SHA \
-t ghcr.io/$OWNER/food-market-api:latest .
# Push to LOCAL registry first (deploy depends on it) — it's on localhost, reliable.
for tag in $SHA latest; do
docker push $LOCAL_REGISTRY/food-market-api:$tag || { echo "local push $tag failed"; exit 1; }
done
# Push to ghcr.io as off-site backup. Flaky on KZ network — retry, but don't fail the job.
for tag in $SHA latest; do
for i in 1 2 3 4 5; do
if docker push ghcr.io/$OWNER/food-market-api:$tag; then break; fi
echo "ghcr push $tag attempt $i failed, retrying in 15s"
sleep 15
[ $i -eq 5 ] && echo "::warning::ghcr push $tag failed after 5 attempts — local registry still has the image"
done
done
web:
name: Web image
runs-on: [self-hosted, linux]
steps:
- uses: actions/checkout@v4
- name: Login to ghcr
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
ACTOR: ${{ github.actor }}
run: |
for i in 1 2 3 4 5; do
if echo "$TOKEN" | docker login ghcr.io -u "$ACTOR" --password-stdin; then
exit 0
fi
echo "login attempt $i failed, retrying in 15s"
sleep 15
done
exit 1
- name: Build + push web
env:
OWNER: ${{ github.repository_owner }}
SHA: ${{ github.sha }}
run: |
docker build -f deploy/Dockerfile.web \
-t $LOCAL_REGISTRY/food-market-web:$SHA \
-t $LOCAL_REGISTRY/food-market-web:latest \
-t ghcr.io/$OWNER/food-market-web:$SHA \
-t ghcr.io/$OWNER/food-market-web:latest .
for tag in $SHA latest; do
docker push $LOCAL_REGISTRY/food-market-web:$tag || { echo "local push $tag failed"; exit 1; }
done
for tag in $SHA latest; do
for i in 1 2 3 4 5; do
if docker push ghcr.io/$OWNER/food-market-web:$tag; then break; fi
echo "ghcr push $tag attempt $i failed, retrying in 15s"
sleep 15
[ $i -eq 5 ] && echo "::warning::ghcr push $tag failed after 5 attempts — local registry still has the image"
done
done

View file

@ -0,0 +1,18 @@
name: Notify CI failures
on:
workflow_run:
workflows: ["CI", "Docker Images"]
types: [completed]
jobs:
telegram:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: [self-hosted, linux]
steps:
- name: Ping Telegram
run: |
curl -sS -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
--data-urlencode "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
--data-urlencode "text=CI FAILED: ${{ github.event.workflow_run.name }} on ${{ github.event.workflow_run.head_branch }} (${GITHUB_SHA:0:7}). https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
> /dev/null