name: CI on: push: branches: - "**" pull_request: branches: - main env: CARGO_TERM_COLOR: always RUSTFLAGS: "-D warnings" # sccache caches compilation artifacts within a job so that compiling # both --features server and --features web shares common crate work. RUSTC_WRAPPER: /usr/local/bin/sccache SCCACHE_DIR: /tmp/sccache # Cancel in-progress runs for the same branch/PR concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # --------------------------------------------------------------------------- # Stage 1: Code quality checks (run in parallel) # --------------------------------------------------------------------------- fmt: name: Format runs-on: docker container: image: rust:1.89-bookworm steps: - name: Checkout run: | git init git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" git fetch --depth=1 origin "${GITHUB_SHA}" git checkout FETCH_HEAD - run: rustup component add rustfmt # Format check does not compile, so sccache is not needed here. - run: cargo fmt --check env: RUSTC_WRAPPER: "" clippy: name: Clippy runs-on: docker container: image: rust:1.89-bookworm steps: - name: Checkout run: | git init git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" git fetch --depth=1 origin "${GITHUB_SHA}" git checkout FETCH_HEAD - name: Install sccache run: | curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.9.1/sccache-v0.9.1-x86_64-unknown-linux-musl.tar.gz \ | tar xz --strip-components=1 -C /usr/local/bin/ sccache-v0.9.1-x86_64-unknown-linux-musl/sccache chmod +x /usr/local/bin/sccache - run: rustup component add clippy # Lint both feature sets independently. # sccache deduplicates shared crates between the two compilations. - name: Clippy (server) run: cargo clippy --features server --no-default-features -- -D warnings - name: Clippy (web) run: cargo clippy --features web --no-default-features -- -D warnings - name: Show sccache stats run: sccache --show-stats if: always() audit: name: Security Audit runs-on: docker if: github.ref == 'refs/heads/main' container: image: rust:1.89-bookworm steps: - name: Checkout run: | git init git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" git fetch --depth=1 origin "${GITHUB_SHA}" git checkout FETCH_HEAD - run: cargo install cargo-audit env: RUSTC_WRAPPER: "" - run: cargo audit env: RUSTC_WRAPPER: "" # --------------------------------------------------------------------------- # Stage 2: Tests (only after all quality checks pass) # --------------------------------------------------------------------------- test: name: Tests runs-on: docker needs: [fmt, clippy, audit] container: image: rust:1.89-bookworm steps: - name: Checkout run: | git init git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" git fetch --depth=1 origin "${GITHUB_SHA}" git checkout FETCH_HEAD - name: Install sccache run: | curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.9.1/sccache-v0.9.1-x86_64-unknown-linux-musl.tar.gz \ | tar xz --strip-components=1 -C /usr/local/bin/ sccache-v0.9.1-x86_64-unknown-linux-musl/sccache chmod +x /usr/local/bin/sccache - name: Run tests (server) run: cargo test --features server --no-default-features - name: Run tests (web) run: cargo test --features web --no-default-features - name: Show sccache stats run: sccache --show-stats if: always() # --------------------------------------------------------------------------- # Stage 2b: E2E tests (only on main / PRs to main, after quality checks) # --------------------------------------------------------------------------- e2e: name: E2E Tests runs-on: docker needs: [fmt, clippy, audit] if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request' container: image: rust:1.89-bookworm # MongoDB and SearXNG can start immediately (no repo files needed). # Keycloak requires realm-export.json from the repo, so it is started # manually after checkout via docker CLI. services: mongo: image: mongo:latest env: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example ports: - 27017:27017 searxng: image: searxng/searxng:latest env: SEARXNG_BASE_URL: http://localhost:8888 ports: - 8888:8080 env: KEYCLOAK_URL: http://localhost:8080 KEYCLOAK_REALM: certifai KEYCLOAK_CLIENT_ID: certifai-dashboard MONGODB_URI: mongodb://root:example@mongo:27017 MONGODB_DATABASE: certifai SEARXNG_URL: http://searxng:8080 steps: - name: Checkout run: | git init git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" git fetch --depth=1 origin "${GITHUB_SHA}" git checkout FETCH_HEAD - name: Install system dependencies run: | apt-get update -qq apt-get install -y -qq --no-install-recommends \ unzip curl docker.io \ libglib2.0-0 libnss3 libnspr4 libdbus-1-3 libatk1.0-0 \ libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 \ libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \ libcairo2 libasound2 libatspi2.0-0 libxshmfence1 - name: Start Keycloak run: | docker run -d --name ci-keycloak --network host \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \ -e KC_DB=dev-mem \ -e KC_HEALTH_ENABLED=true \ -v "$PWD/keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json:ro" \ -v "$PWD/keycloak/themes/certifai:/opt/keycloak/themes/certifai:ro" \ quay.io/keycloak/keycloak:26.0 start-dev --import-realm echo "Waiting for Keycloak..." for i in $(seq 1 60); do if curl -sf http://localhost:8080/realms/certifai > /dev/null 2>&1; then echo "Keycloak is ready" break fi if [ "$i" -eq 60 ]; then echo "Keycloak failed to start within 60s" docker logs ci-keycloak exit 1 fi sleep 2 done - name: Install sccache run: | curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.9.1/sccache-v0.9.1-x86_64-unknown-linux-musl.tar.gz \ | tar xz --strip-components=1 -C /usr/local/bin/ sccache-v0.9.1-x86_64-unknown-linux-musl/sccache chmod +x /usr/local/bin/sccache - name: Install dioxus-cli run: cargo install dioxus-cli --locked - name: Install bun run: | curl -fsSL https://bun.sh/install | bash echo "$HOME/.bun/bin" >> "$GITHUB_PATH" - name: Install Playwright run: | export PATH="$HOME/.bun/bin:$PATH" bun install bunx playwright install chromium - name: Build app run: dx build --release - name: Start app and run E2E tests run: | export PATH="$HOME/.bun/bin:$PATH" # Start the app in the background dx serve --release --port 8000 & APP_PID=$! # Wait for the app to be ready echo "Waiting for app to start..." for i in $(seq 1 60); do if curl -sf http://localhost:8000 > /dev/null 2>&1; then echo "App is ready" break fi if [ "$i" -eq 60 ]; then echo "App failed to start within 60s" exit 1 fi sleep 1 done BASE_URL=http://localhost:8000 bunx playwright test --reporter=list kill "$APP_PID" 2>/dev/null || true - name: Upload test report if: always() uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report/ retention-days: 7 - name: Cleanup Keycloak if: always() run: docker rm -f ci-keycloak 2>/dev/null || true - name: Show sccache stats run: sccache --show-stats if: always() # --------------------------------------------------------------------------- # Stage 3: Deploy (only after tests pass, only on main) # --------------------------------------------------------------------------- deploy: name: Deploy runs-on: docker needs: [test, e2e] if: github.ref == 'refs/heads/main' container: image: alpine:latest steps: - name: Trigger Coolify deploy run: | apk add --no-cache curl curl -sf "${{ secrets.COOLIFY_WEBHOOK }}" \ -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"