CI/CD Pipeline
CI Workflow (ci.yml)
Her push ve PR'da otomatik çalışır:
1. backend-lint
ruff check app/— Lintingruff format --check app/— Format kontrolümypy app/ --ignore-missing-imports— Type check
2. backend-test
- PostgreSQL 15 (TimescaleDB) + Redis 7 servisler başlatılır
alembic upgrade head— Migration'lar uygulanır (+ reversibility testi:downgrade -1→upgrade head)pytest tests/ -v --cov=app -m "not integration"— Unit + REST testleri- Blocking integration adımları (gerçek DB) —
@pytest.mark.integrationtestleri "not integration" filtresinde atlandığı için her biri ayrı blocking adım olarak koşar (Issue #615 sahte-yeşil önleme). PR-7 ile eklenen:tests/integration/test_invitations_db.py— kullanıcı davet yaşam döngüsünün gerçek-DB kanıtları: resend sonrası eski token geçersiz/yeni token geçerli, aynı token ikinci accept 400 (atomik tek-kullanım), pending-davet partial unique → 409, role_ids JSONB round-trip, cross-tenant revoke 404 (hiç-yazmadan), accept-409 rollback'inin token'ı yakmaması. Yeşil olmadan merge edilemez.
Migration-runtime fail-closed guard (issue #970)
Migration-runtime testleri (tests/fixtures/migration_runtime.py kullanan 12 dosya) DATABASE_URL yoksa _REQUIRES_DB skipif marker'ı ile collection anında atlanır — adım rc=0 ile yeşil görünür ama tek bir gerçek-DB assertion'ı koşmaz. Bu, Issue #615 sahte-yeşil sınıfıdır.
Ortak modül bunu import anında engeller:
- Ne zaman ateşlenir:
CI(veyaGITHUB_ACTIONS) truthy veDATABASE_URLpostgresql değil/boş →RuntimeError, adım kırmızı. - Lokalde: CI sinyali yoksa guard hiç çalışmaz; mevcut
pytest.skipdavranışı korunur. - Bu mesajı görürsen: ilgili adımın
env:bloğuna postgres servisine işaret edenDATABASE_URL'i ekle. Postgres servisi olmayan bir job'a (örn.migration-sanity, sqlite kullanır) bu dosyaları collect eden bir pytest adımı eklenmemeli. - Yeni migration-runtime testi eklerken: dosyaya ayrı bir blocking adım +
env:bloğundaDATABASE_URLşart. - Guard'ın kurulu olduğu ve tetikleyicisinin CI'da mevcut olduğu
tests/unit/test_migration_runtime_fixture.pyile her koşumda doğrulanır.
3. firmware-test
pio test -e native— PlatformIO unit testleri
4. frontend-lint
npm run lint— ESLintnpx tsc --noEmit— TypeScript kontrolü
5. frontend-test (PR #276 sonrası eklendi — BLOCKING)
npm test— Vitest, 43 test- Kapsam (WS refactor release gate):
frontend/src/config/site.test.ts—getWsUrl()SSR/localhost/prod/env overridefrontend/src/lib/websocket/client.test.ts— mixed-content guard + ctor exceptionfrontend/src/lib/websocket/context.test.tsx— Provider defansif init + cleanupfrontend/src/lib/websocket/WebSocketErrorBoundary.test.tsx— boundary fallback
- Yeşil olmazsa merge engellenir.
6. frontend-build
npm run build— Production build- Önemli:
NEXT_PUBLIC_WS_URLartık build-arg olarak verilmiyor (PR #276). Detay: Frontend Kritik Notlar.
7. docker-build (sadece PR)
- Docker Buildx ile tek-platform build (runner native amd64, push yok)
E2E Workflow (e2e.yml)
PR-7 (tenant onboarding) ile yeniden yapılandırıldı: e2e artık gerçek backend stack üzerinde koşar (önceden: mock API URL ile yalnız render smoke + @ci tag'li test yokken "no tests found → exit 0" vacuous-pass).
Tetikleyiciler
- PR:
frontend/**,e2e/**,backend/app/features/{signup,users,tenants}/**,backend/app/core/security/**,backend/scripts/seed_e2e_superadmin.py, workflow dosyası - Nightly: 02:00 UTC
- Manuel:
workflow_dispatch(base_urlverilirse ayrıprod-smokejob'ı — read-only production smoke)
@ci onboarding journeys (chromium) job akışı
- Servisler:
timescale/timescaledb:latest-pg15(host 5433) +redis:7-alpine(host 6380). Redis şart: signup OTP durumu Redis'te yaşar. - Backend: Python 3.11 +
pip install -r requirements.txt→alembic upgrade head(BACKFILL_* placeholder env, ci.yml emsali) →python scripts/seed_e2e_superadmin.py(idempotent superadmin seed; production'da fail-closed) → uvicorn127.0.0.1:8000+/healthzreadiness. - Frontend:
npm ci→npm run build(BACKEND_URL=http://127.0.0.1:8000build-time bake —next.config.jsrewrites/api/*'ı uvicorn'a proxy'ler) →next start -p 3000. - Playwright:
e2e/self-contained (e2e/package.json+e2e/playwright.config.ts) →npx playwright test --grep @ci --project=chromium.
Backend e2e ortam env'leri
| Env | Değer | Not |
|---|---|---|
ENVIRONMENT | test | Seed + echo guard'ları production'da fail-closed |
TENANT_SIGNUP_ENABLED | true | Fail-closed master flag — yalnız bu CI ortamında açılır |
SIGNUP_TEST_OTP_ECHO | true | OTP/e-posta/davet token'ları debug_* alanlarında echo edilir. Yalnız CI/e2e — backend çifte korumalı (production'da flag yok sayılır) ve deploy.yml bu flag'i bilerek wire etmez |
CELERY_ALWAYS_EAGER | true | Task'lar inline; dispatch'ler try/except sarılı — SMTP'siz ortamda istek 500'e dönmez |
MQTT_ENABLED | false | Broker gerekmez |
E2E_SUPERADMIN_EMAIL/PASSWORD | sabit CI değeri | Seed script ve spec'ler aynı değeri kullanır (e2e/fixtures/onboarding.ts sözleşmesi) |
Vacuous-pass kaldırıldı
--grep @ci koşumu artık "no tests found" durumunda fail eder (Playwright varsayılanı, --pass-with-no-tests verilmez). Yeşil rozet = en az 1 gerçek journey koştu. Kapsanan journey'ler: e2e/specs/signup-approval.spec.ts (signup → OTP → e-posta doğrulama → superadmin onayı → yeni tenant admin login) ve e2e/specs/invitation-accept.spec.ts (davet oluştur → kabul → yeni kullanıcı login).
Port / concurrency kararı
Job, self-hosted X64 host'unda sabit portlar bağlar (5433/6380/8000/3000) → job-level concurrency lock e2e-stack-ports-runner (cancel-in-progress: false, ci.yml backend-test-port-5432-runner emsal deseni) ile aynı anda tek e2e stack'i koşar. ci.yml'nin 5432 lock grubuna katılmadı: Backend Tests artık ubuntu-latest'te (izole VM) — ortak host yok, katılmak e2e'yi alakasız job'ların arkasına kuyruklardı. Postgres host portu 5433 seçildi: 5432 hem ci.yml emsalinin tarihsel çakışma portu hem de runner host'unda yerel Postgres'in en olası portu.
@ci onboarding journeys (chromium) job'ı main ruleset'inde required status check değildir (mevcut required listesi: Backend Lint/Tests, Frontend Lint/Build, Firmware, Alembic single-head, Nginx guard, Docker Build). Required'a çevirmek bilinçli kullanıcı kararıdır.
Deploy Workflow (deploy.yml)
CI başarılı olunca otomatik tetiklenir:
- SSH setup — webfactory/ssh-agent
- rsync — Dosya senkronizasyonu (exclude: .git, node_modules, .env, certs)
- .env oluştur — GitHub Secrets'tan
- TLS kontrol — SSL sertifika varlığı
- Infrastructure start — postgres, redis, emqx, minio
- Migration —
alembic upgrade ${MIGRATION_TARGET:-head}(PR #277 sonrası varsayılanhead) - Image pull — GHCR'dan amd64 image'lar çekilir (build CI runner'da yapılır; bkz. "Build CI Runner Migration")
- Container start —
docker compose up -d - Health check — Backend, frontend, nginx HTTPS (post-check 18×5s polling)
- Rollback guard — post-check fail ederse
:previoustag'e otomatik geri dönüş - Monitoring cron — health-check, container-monitor kurulumu
Soft-env writer'lar (opsiyonel secret → .env)
.env ana bloğundan sonra bir dizi soft-env adımı koşar: GitHub Secret set edilmişse ilgili satır .env'e eklenir, set edilmemişse hiçbir şey yazılmaz ve docker-compose.yml default'ları geçerli kalır (deploy kırılmaz — PR #449 dersi: zorunlu olmayan env hard-gate'e eklenmez). Mevcut gruplar: MQTT, SMTP, Driver Self-Service canary, OSOS KEK ve PR-7 ile eklenen tenant onboarding flag'leri:
| Secret | Compose default | Davranış |
|---|---|---|
TENANT_SIGNUP_ENABLED | false | Set edilmedikçe public /signup yüzeyi kapalı kalır (fail-closed master flag) |
PLATFORM_ADMIN_EMAILS | boş | Yeni başvuru bildirimi e-posta hedefleri (virgülle ayrılmış) |
PLATFORM_ADMIN_PHONES | boş | Yeni başvuru bildirimi WhatsApp hedefleri |
SIGNUP_TEST_OTP_ECHO bilerek wire edilmez: OTP/token echo flag'i yalnız CI/e2e içindir; production .env'sine taşıyan bir yol deploy.yml'de açılmaz (secret tanımlansa dahi yazılmaz).
Release Workflow (release.yml)
Git tag push (v*) ile tetiklenir:
- GitHub release + changelog oluşturma
- ghcr.io'ya Docker image push (tag + latest)
amd64 Build
Prod sunucusu (ZeusTR, x86_64, TR/İstanbul) amd64. CI/CD pipeline 2026-06-09 itibarıyla yalnız linux/amd64 build üretir; tüm job'lar [self-hosted, linux, X64] runner'da koşar. ARM build tamamen kaldırıldı (geri dönüş planlanmıyor).
Tarihçe: 2026-05-19..2026-06-09 arası prod ARM64 Hetzner CAX üzerindeydi ve pipeline yalnız
linux/arm64üretiyordu. Başkent EDAŞ MDM API'sinin yurtdışı/datacenter IP'lere uyguladığı port-443 coğrafi/ASN bloğu nedeniyle Zeus, x86_64 TR sunucuya (ZeusTR) taşındı; build mimarisi de amd64'e döndü. QEMU adımı kaldırıldı (native build'de no-op idi).
CI Konfigürasyonu
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
platforms: linux/amd64
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
Build Timeout
Multi-arch döneminde (2026-05 öncesi) QEMU emülasyonu frontend build'i 25-30 dakikaya çıkarıyordu. Native-only (tek mimari) geçişle native hız geri kazanıldı; timeout değerleri üst sınır güvenliği olarak korunuyor (cache cold start veya runner state pollution için marj):
| Job | Timeout (üst sınır) | Tipik süre (warm cache) |
|---|---|---|
| backend build | 60 dk | < 5 dk |
| frontend build | 60 dk | < 8 dk |
| modbus-poller build | 45 dk | < 3 dk |
| whatsapp-bridge build | 45 dk | < 2 dk |
CAX (ARM64) — Hetzner Geçişi (tarihsel, 2026-05)
Bu bölüm tarihseldir. 2026-06-09'da Zeus, ZeusTR (x86_64, TR) sunucusuna taşındı; aşağıdaki CAX/ARM64 karşılaştırması artık aktif mimariyi tanımlamaz, geçmiş kararın kaydıdır.
| Kriter | CPX (x86) | CAX (ARM64) |
|---|---|---|
| Fiyat (eşdeğer kapasite) | Yüksek | ~%30 daha düşük |
| RAM (eşdeğer fiyat) | 4GB | 8GB |
| TimescaleDB performans | Stabil | Stabil (testler geçti) |
| Build platform | linux/amd64,linux/arm64 | linux/arm64 (multi-arch kaldırıldı 2026-05-19) |
CAX'e geçiş özellikle 4GB → 8GB RAM upgrade'i için tercih edilmişti (TimescaleDB OOM-kill çözümü).
Build CI Runner Migration
Daha önce frontend/backend image build'leri deploy SSH session içinde yapılıyordu. Bu yaklaşımın sorunları:
- SSH connection timeout → silent build failure
- Sunucuda CPU/RAM kullanımı build sırasında piklenir, prod servisler etkilenir
- Build cache sunucu disk'inde tutulur → disk şişmesi
- Hata yakalama zayıf (output buffer kesilebilir)
Yeni Akış
- CI runner'da image build → ghcr.io'ya push
- Deploy adımı sadece
docker pull+docker compose up -dçalıştırır - Build hataları CI'da yakalanır, deploy hiç başlamaz
- name: Build & push backend
uses: docker/build-push-action@v6
with:
context: ./backend
platforms: linux/amd64
push: true
tags: |
ghcr.io/gucluceyhan/zeus-backend:latest
ghcr.io/gucluceyhan/zeus-backend:${{ github.sha }}
Tag Rotation + Rollback
Her başarılı build üç tag ile push'lanır:
| Tag | Anlam |
|---|---|
:latest | En son başarılı build |
:previous | Bir önceki başarılı build (rollback hedefi) |
:<sha> | Commit-spesifik tag (immutable referans) |
Rollback Akışı
Production'da bir sorun yakalandığında:
# Hızlı rollback (önceki sürüme dön)
docker pull ghcr.io/gucluceyhan/zeus-backend:previous
docker tag ghcr.io/gucluceyhan/zeus-backend:previous \
ghcr.io/gucluceyhan/zeus-backend:latest
docker compose up -d backend
:previous tag'i her başarılı deploy'da otomatik güncellenir (bir önceki :latest rotate edilir). Manuel rollback için :<sha> tag'i de kullanılabilir:
docker pull ghcr.io/gucluceyhan/zeus-backend:abc1234
Post-deploy Health Check
Deploy sonrası 90 saniye boyunca her 5 saniyede bir container sağlığı kontrol edilir. Bu süre içinde container healthy olmazsa otomatik rollback tetiklenir.
TIMEOUT=90
INTERVAL=5
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
STATUS=$(docker inspect --format='{{.State.Status}}' backend)
HEALTH=$(docker inspect --format='{{.State.Health.Status}}' backend)
if [ "$STATUS" = "running" ] && [ "$HEALTH" = "healthy" ]; then
echo "Deploy başarılı (${ELAPSED}s)"
exit 0
fi
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
done
echo "Deploy başarısız — rollback başlatılıyor"
docker tag ghcr.io/gucluceyhan/zeus-backend:previous \
ghcr.io/gucluceyhan/zeus-backend:latest
docker compose up -d backend
exit 1
Health check Dockerfile'da tanımlanır (her servis için ayrı): HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:8000/health || exit 1
MIGRATION_TARGET — Politika (PR #277 ile güncellendi)
headPR #277 ile deploy.yml:244 satırındaki MIGRATION_TARGET varsayılanı merge_sofar_mqtt'ten head'e taşındı. Önceden 0037 ve 0038 gibi yeni revisionlar otomatik deploy'a giremiyordu — workflow_dispatch override veya manuel SSH apply gerektiriyordu. Bu durum production drift riski (kod yeni şemayı bekler, DB eski şemada kalır) yaratıyordu.
Mevcut akış
# deploy.sh içinde — CI workflow tarafından sunucuya yazılıyor
MIGRATION_TARGET="${MIGRATION_TARGET:-head}"
$DOCKER_COMPOSE run --rm backend alembic upgrade "${MIGRATION_TARGET}"
head güvenle kullanılabilir çünkü single-head invariant CI guard hâlâ aktif (migration-sanity job, aşağıda). Multiple heads durumunda deploy başlamadan PR/merge engellenir.
Override politikası — ne zaman kullanılır
MIGRATION_TARGET'ı spesifik revision'a pin'lemek için iki yöntem:
workflow_dispatchinput — manuel deploy tetiklerkenMIGRATION_TARGET=0037_add_ocpp_tablesver.- Repo env değişkeni —
vars.MIGRATION_TARGETset edilirse${MIGRATION_TARGET:-head}fallback'i devreden çıkar.
Hangi durumda override:
| Senaryo | Hedef | Gerekçe |
|---|---|---|
| Hot-fix, yeni migration'ı staging'de test etmek istiyorsun | Bir önceki revision (örn. 0037_add_ocpp_tables) | Yeni migration'ı sadece prod'a değil staging'de önce uygula |
| Rollback sonrası alembic_version stuck | Spesifik revision id | Manuel kontrolü garanti et |
| Production'da rutin deploy | head (varsayılan) | Otomatik akış — override yapma |
Production'da MIGRATION_TARGET override etmek migration drift'in başlangıç noktasıdır. head varsayılanı ve migration-sanity guard'ı ile birlikte güvenli. Ancak yeni bir migration emergency rollback gerektiriyorsa kısa süreliğine spesifik revision'a pin'leyip ardından kaldırın.
migration-sanity Job (single-head invariant)
CI pipeline'ında pre-deploy doğrulama olarak çalışır. Multiple heads varsa deploy adımı tetiklenmez.
migration-sanity:
runs-on: [self-hosted, linux, X64]
steps:
- run: |
HEADS=$(alembic heads | wc -l)
if [ "$HEADS" -ne 1 ]; then
echo "ERROR: Multiple alembic heads — merge migration gerekli"
alembic heads
exit 1
fi
Bu guard tek satır head garantisi sağlar. Detay: Migration Rehberi — Multiple Heads.