diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..c958544 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,221 @@ +# CI/CD Pipeline - NixOS VM Tests Only +# All tests run in isolated NixOS VMs with native services (no Docker) + +name: CI/CD + +on: + push: + branches: [main, develop, '001-*'] + pull_request: + branches: [main, develop] + +jobs: + # NixOS VM integration tests (PostgreSQL + MinIO native services) + nixos-vm-tests: + name: VM Test - ${{ matrix.test }} + runs-on: nix + strategy: + fail-fast: false + matrix: + test: + - backend-integration # Backend + PostgreSQL + MinIO + - full-stack # Complete API stack + - performance # Benchmarks + - security # Security suite + + steps: + - uses: actions/checkout@v4 + + # Configure Attic binary cache + - name: Configure Attic cache + run: | + attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }} + attic use lan:webref + + # Cache Nix store for faster VM builds + - name: Cache Nix store + uses: actions/cache@v4 + with: + path: ~/.cache/nix + key: nix-vm-${{ matrix.test }}-${{ hashFiles('flake.nix', 'flake.lock', 'nixos/tests.nix') }} + restore-keys: | + nix-vm-${{ matrix.test }}- + nix-vm- + + # Run NixOS VM test + - name: Run ${{ matrix.test }} + run: | + echo "🚀 Starting NixOS VM test: ${{ matrix.test }}" + nix build .#checks.${{ matrix.test }} -L --accept-flake-config + echo "✅ Test passed" + + # Push to Attic cache + - name: Push to Attic cache + if: success() + run: | + attic push lan:webref result + + # Archive logs on failure + - name: Archive test logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: vm-logs-${{ matrix.test }} + path: result/ + retention-days: 3 + + # Quick checks (no VM needed) + lint: + name: Linting & Formatting + runs-on: nix + + steps: + - uses: actions/checkout@v4 + + # Configure Attic cache + - name: Configure Attic cache + run: | + attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }} + attic use lan:webref + + # Cache node_modules for linting + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: frontend/node_modules + key: npm-${{ hashFiles('frontend/package-lock.json') }} + restore-keys: npm- + + - name: Backend - Ruff check + run: nix develop --command bash -c "cd backend && ruff check app/" + + - name: Backend - Ruff format check + run: nix develop --command bash -c "cd backend && ruff format --check app/" + + - name: Frontend - Install deps (if needed) + run: nix develop --command bash -c "cd frontend && [ -d node_modules ] || npm ci" + + - name: Frontend - ESLint + run: nix develop --command bash -c "cd frontend && npm run lint" + + - name: Frontend - Prettier check + run: nix develop --command bash -c "cd frontend && npx prettier --check ." + + - name: Frontend - Svelte check + run: nix develop --command bash -c "cd frontend && npm run check" + + - name: Nix - Flake check + run: nix flake check --accept-flake-config + + # Unit tests (fast, no services needed) + unit-tests: + name: Unit Tests + runs-on: nix + + steps: + - uses: actions/checkout@v4 + + # Configure Attic cache + - name: Configure Attic cache + run: | + attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }} + attic use lan:webref + + # Cache pytest discovery + - name: Cache pytest + uses: actions/cache@v4 + with: + path: backend/.pytest_cache + key: pytest-${{ hashFiles('backend/tests/**/*.py') }} + + # Cache node_modules + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: frontend/node_modules + key: npm-${{ hashFiles('frontend/package-lock.json') }} + restore-keys: npm- + + - name: Backend unit tests + run: | + nix develop --command bash -c " + cd backend && + pytest tests/unit/ -v \ + --cov=app \ + --cov-report=xml \ + --cov-report=term-missing \ + --cov-fail-under=80 + " + + - name: Frontend - Install deps (if needed) + run: nix develop --command bash -c "cd frontend && [ -d node_modules ] || npm ci" + + - name: Frontend unit tests + run: nix develop --command bash -c "cd frontend && npm run test:coverage" + + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-reports + path: | + backend/coverage.xml + backend/htmlcov/ + frontend/coverage/ + retention-days: 7 + + # Verify packages build + build: + name: Build Packages + runs-on: nix + + steps: + - uses: actions/checkout@v4 + + # Configure Attic cache + - name: Configure Attic cache + run: | + attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }} + attic use lan:webref + + - name: Build backend package + run: nix build .#backend -L --accept-flake-config + + - name: Push backend to Attic + if: success() + run: attic push lan:webref result + + - name: Build frontend package + run: nix build .#frontend -L --accept-flake-config + + - name: Push frontend to Attic + if: success() + run: attic push lan:webref result + + # Summary + summary: + name: CI Summary + runs-on: nix + needs: [nixos-vm-tests, lint, unit-tests, build] + if: always() + + steps: + - name: Results + run: | + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📊 CI Pipeline Results" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "NixOS VMs: ${{ needs.nixos-vm-tests.result }}" + echo "Linting: ${{ needs.lint.result }}" + echo "Unit Tests: ${{ needs.unit-tests.result }}" + echo "Build: ${{ needs.build.result }}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + if [[ "${{ needs.nixos-vm-tests.result }}" != "success" ]] || \ + [[ "${{ needs.lint.result }}" != "success" ]] || \ + [[ "${{ needs.unit-tests.result }}" != "success" ]] || \ + [[ "${{ needs.build.result }}" != "success" ]]; then + echo "❌ Pipeline Failed" + exit 1 + fi + + echo "✅ All Checks Passed" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 220ecc9..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,181 +0,0 @@ -name: CI/CD Pipeline - -on: - push: - branches: [main, develop, '**'] - pull_request: - branches: [main, develop] - -jobs: - backend-tests: - name: Backend Tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:16 - env: - POSTGRES_DB: webref_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - steps: - - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v27 - with: - nix_path: nixpkgs=channel:nixos-unstable - - - name: Setup Python dependencies - run: | - cd backend - python -m pip install --upgrade pip - pip install -e ".[dev]" - - - name: Run Ruff linter - run: | - cd backend - ruff check app/ - - - name: Run Ruff formatter check - run: | - cd backend - ruff format --check app/ - - - name: Run tests with coverage - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/webref_test - run: | - cd backend - pytest --cov=app --cov-report=xml --cov-report=term - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - file: ./backend/coverage.xml - flags: backend - name: backend-coverage - - frontend-tests: - name: Frontend Tests - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install dependencies - run: | - cd frontend - npm ci - - - name: Run ESLint - run: | - cd frontend - npm run lint - - - name: Run Prettier check - run: | - cd frontend - npx prettier --check . - - - name: Run Svelte check - run: | - cd frontend - npm run check - - - name: Run tests with coverage - run: | - cd frontend - npm run test:coverage - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - file: ./frontend/coverage/coverage-final.json - flags: frontend - name: frontend-coverage - - integration-tests: - name: Integration Tests - runs-on: ubuntu-latest - needs: [backend-tests, frontend-tests] - - services: - postgres: - image: postgres:16 - env: - POSTGRES_DB: webref_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - minio: - image: minio/minio - env: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - ports: - - 9000:9000 - options: >- - --health-cmd "curl -f http://localhost:9000/minio/health/live" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v27 - with: - nix_path: nixpkgs=channel:nixos-unstable - - - name: Run integration tests - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/webref_test - MINIO_ENDPOINT: localhost:9000 - MINIO_ACCESS_KEY: minioadmin - MINIO_SECRET_KEY: minioadmin - run: | - cd backend - pytest tests/integration/ - - nix-build: - name: Nix Build Check - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v27 - with: - nix_path: nixpkgs=channel:nixos-unstable - - - name: Check flake - run: nix flake check - - - name: Build dev shell - run: nix develop --command echo "Dev shell OK" - diff --git a/README.md b/README.md index 3fd3196..1dba081 100644 --- a/README.md +++ b/README.md @@ -16,20 +16,23 @@ This project follows a formal constitution that establishes binding principles f ## Development Environment -This project uses Nix for reproducible development environments: +This project uses Nix flakes for reproducible development environments: ```bash -# Enter development shell -nix-shell +# Enter development shell (from flake.nix) +nix develop # Or use direnv for automatic activation -echo "use nix" > .envrc -direnv allow +direnv allow # .envrc already configured ``` **Included tools:** -- Python 3 with setuptools -- uv (fast Python package manager) +- Python 3.12 with all backend dependencies (FastAPI, SQLAlchemy, pytest, etc.) +- Node.js + npm for frontend development +- PostgreSQL client tools +- MinIO client +- Ruff (Python linter/formatter) +- All project dependencies from flake.nix ## Project Structure @@ -44,7 +47,19 @@ webref/ │ ├── tasks-template.md # Task tracking template │ └── commands/ │ └── constitution.md # Constitution amendment workflow -├── shell.nix # Nix development environment +├── backend/ # FastAPI backend application +│ ├── app/ # Application code +│ ├── tests/ # pytest test suite +│ └── pyproject.toml # Python dependencies +├── frontend/ # Svelte + Konva.js frontend +│ ├── src/ # Application code +│ ├── tests/ # Vitest test suite +│ └── package.json # Node dependencies +├── nixos/ # NixOS configuration and tests +│ ├── tests.nix # NixOS VM integration tests +│ └── gitea-runner.nix # Gitea Actions runner config +├── flake.nix # Nix flake (dependencies & dev shell) +├── .envrc # direnv configuration └── README.md # This file ``` @@ -94,16 +109,37 @@ All code must meet these requirements before merge: ## Testing -```bash -# Run tests -pytest +### Unit Tests -# With coverage report -pytest --cov=webref --cov-report=html +```bash +# Backend tests +cd backend && pytest --cov=app --cov-report=html + +# Frontend tests +cd frontend && npm test # Coverage must be ≥80% per Constitutional Principle 2 ``` +### NixOS VM Integration Tests + +```bash +# Run all integration tests in isolated VMs +nix flake check + +# Run specific test +nix build .#checks.backend-integration +nix build .#checks.full-stack +nix build .#checks.performance +nix build .#checks.security + +# Interactive debugging +nix build .#checks.backend-integration.driverInteractive +./result/bin/nixos-test-driver +``` + +See [Tech Research](specs/001-reference-board-viewer/tech-research.md) for CI/testing architecture details. + ## Contributing 1. Read the [constitution](.specify/memory/constitution.md) diff --git a/flake.nix b/flake.nix index 4fe9ed2..872d253 100644 --- a/flake.nix +++ b/flake.nix @@ -129,6 +129,9 @@ ''; }; }; + + # NixOS VM tests + checks = import ./nixos/tests.nix { inherit pkgs; }; } ); } diff --git a/nixos/gitea-runner.nix b/nixos/gitea-runner.nix new file mode 100644 index 0000000..ea28235 --- /dev/null +++ b/nixos/gitea-runner.nix @@ -0,0 +1,112 @@ +{ config, pkgs, lib, ... }: + +{ + # Gitea Actions Runner Configuration + # This module configures a Gitea runner for CI/CD with Nix support + + services.gitea-actions-runner = { + package = pkgs.gitea-actions-runner; + + instances = { + # Main runner instance for webref project + webref-runner = { + enable = true; + + # Runner name (will appear in Gitea) + name = "nixos-runner-webref"; + + # Gitea instance URL + url = "https://your-gitea-instance.com"; + + # Runner token - Generate this from Gitea: + # Settings -> Actions -> Runners -> Create New Runner + # Store the token in a file and reference it here + tokenFile = "/var/secrets/gitea-runner-token"; + + # Labels define what jobs this runner can handle + # Format: "label:docker_image" or just "label" for host execution + labels = [ + # Native execution with Nix + "nix:native" + + # Ubuntu-like for compatibility + "ubuntu-latest:docker://node:20-bookworm" + + # Specific for this project + "webref:native" + ]; + + # Host packages available to the runner + hostPackages = with pkgs; [ + # Essential tools + bash + coreutils + curl + git + nix + + # Project-specific + nodejs + python3 + postgresql + + # Binary cache + attic-client + + # Container runtime (optional) + docker + docker-compose + ]; + }; + }; + }; + + # Enable Docker for service containers (PostgreSQL, MinIO, etc.) + virtualisation.docker = { + enable = true; + autoPrune.enable = true; + autoPrune.dates = "weekly"; + }; + + # Ensure the runner user has access to Docker + users.users.gitea-runner = { + isSystemUser = true; + group = "gitea-runner"; + extraGroups = [ "docker" ]; + }; + + users.groups.gitea-runner = {}; + + # Allow runner to use Nix + nix.settings = { + allowed-users = [ "gitea-runner" ]; + trusted-users = [ "gitea-runner" ]; + + # Enable flakes for the runner + experimental-features = [ "nix-command" "flakes" ]; + + # Optimize for CI performance + max-jobs = "auto"; + cores = 0; # Use all available cores + }; + + # Network access for downloading packages + networking.firewall = { + # If your runner needs to expose ports, configure them here + # allowedTCPPorts = [ ]; + }; + + # Systemd service optimizations + systemd.services."gitea-runner-webref-runner" = { + serviceConfig = { + # Resource limits (adjust based on your hardware) + MemoryMax = "8G"; + CPUQuota = "400%"; # 4 cores + + # Restart policy + Restart = "always"; + RestartSec = "10s"; + }; + }; +} + diff --git a/nixos/tests.nix b/nixos/tests.nix new file mode 100644 index 0000000..3f59506 --- /dev/null +++ b/nixos/tests.nix @@ -0,0 +1,211 @@ +{ pkgs, ... }: + +let + # Import the flake to get our packages + webref = builtins.getFlake (toString ../.); +in +{ + # Backend integration tests with PostgreSQL and MinIO + backend-integration = pkgs.nixosTest { + name = "webref-backend-integration"; + + nodes = { + machine = { config, pkgs, ... }: { + # PostgreSQL service + services.postgresql = { + enable = true; + ensureDatabases = [ "webref_test" ]; + ensureUsers = [{ + name = "webref"; + ensureDBOwnership = true; + }]; + authentication = '' + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + ''; + }; + + # MinIO service + services.minio = { + enable = true; + rootCredentialsFile = pkgs.writeText "minio-credentials" '' + MINIO_ROOT_USER=minioadmin + MINIO_ROOT_PASSWORD=minioadmin + ''; + }; + + # Ensure our dev environment is available + environment.systemPackages = with pkgs; [ + webref.devShells.${system}.default.inputDerivation + ]; + + # Network configuration + networking.firewall.enable = false; + }; + }; + + testScript = '' + start_all() + + # Wait for PostgreSQL + machine.wait_for_unit("postgresql.service") + machine.wait_for_open_port(5432) + + # Wait for MinIO + machine.wait_for_unit("minio.service") + machine.wait_for_open_port(9000) + + # Create test database + machine.succeed("sudo -u postgres psql -c 'CREATE DATABASE webref_test;'") + + # Run backend tests + machine.succeed(""" + cd /tmp/webref + export DATABASE_URL="postgresql://webref@localhost/webref_test" + export MINIO_ENDPOINT="localhost:9000" + export MINIO_ACCESS_KEY="minioadmin" + export MINIO_SECRET_KEY="minioadmin" + export MINIO_BUCKET="webref" + export MINIO_SECURE="false" + + ${pkgs.python3}/bin/python -m pytest backend/tests/ -v + """) + + machine.succeed("echo '✅ Backend integration tests passed'") + ''; + }; + + # Full stack test with backend + frontend + database + full-stack = pkgs.nixosTest { + name = "webref-full-stack"; + + nodes = { + server = { config, pkgs, ... }: { + # PostgreSQL + services.postgresql = { + enable = true; + ensureDatabases = [ "webref" ]; + ensureUsers = [{ + name = "webref"; + ensureDBOwnership = true; + }]; + }; + + # MinIO + services.minio = { + enable = true; + rootCredentialsFile = pkgs.writeText "minio-credentials" '' + MINIO_ROOT_USER=minioadmin + MINIO_ROOT_PASSWORD=minioadmin + ''; + }; + + # Backend API (FastAPI) + systemd.services.webref-backend = { + description = "WebRef Backend API"; + after = [ "postgresql.service" "minio.service" ]; + wantedBy = [ "multi-user.target" ]; + + environment = { + DATABASE_URL = "postgresql://webref@localhost/webref"; + MINIO_ENDPOINT = "localhost:9000"; + MINIO_ACCESS_KEY = "minioadmin"; + MINIO_SECRET_KEY = "minioadmin"; + SECRET_KEY = "test-secret-key-do-not-use-in-production"; + }; + + serviceConfig = { + ExecStart = "${pkgs.python3}/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000"; + WorkingDirectory = "/tmp/webref/backend"; + Restart = "always"; + }; + }; + + networking.firewall.allowedTCPPorts = [ 8000 9000 ]; + }; + + client = { config, pkgs, ... }: { + environment.systemPackages = [ pkgs.curl pkgs.jq ]; + }; + }; + + testScript = '' + start_all() + + # Wait for all services + server.wait_for_unit("postgresql.service") + server.wait_for_unit("minio.service") + server.wait_for_unit("webref-backend.service") + server.wait_for_open_port(8000) + + # Test API health + client.wait_for_unit("multi-user.target") + client.succeed("curl -f http://server:8000/health") + + # Test API endpoints + response = client.succeed("curl -s http://server:8000/health | jq -r .status") + assert "healthy" in response, f"Expected 'healthy', got {response}" + + server.succeed("echo '✅ Full stack test passed'") + ''; + }; + + # Performance benchmarks + performance = pkgs.nixosTest { + name = "webref-performance"; + + nodes = { + machine = { config, pkgs, ... }: { + services.postgresql.enable = true; + services.minio.enable = true; + + environment.systemPackages = with pkgs; [ + apache-bench + wrk + ]; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("postgresql.service") + + # Run performance tests + machine.succeed(""" + cd /tmp/webref/backend + ${pkgs.python3}/bin/pytest tests/performance/ --benchmark-only + """) + + machine.succeed("echo '✅ Performance tests passed'") + ''; + }; + + # Security tests + security = pkgs.nixosTest { + name = "webref-security"; + + nodes = { + machine = { config, pkgs, ... }: { + services.postgresql.enable = true; + environment.systemPackages = with pkgs; [ + sqlmap + nmap + ]; + }; + }; + + testScript = '' + start_all() + + # Run security test suite + machine.succeed(""" + cd /tmp/webref/backend + ${pkgs.python3}/bin/pytest tests/security/ -v + """) + + machine.succeed("echo '✅ Security tests passed'") + ''; + }; +} + diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 93d74b8..0000000 --- a/shell.nix +++ /dev/null @@ -1,71 +0,0 @@ -{ - pkgs ? import { }, -}: - -pkgs.mkShell { - packages = - [ - # Python with development packages - (pkgs.python3.withPackages ( - ps: - builtins.attrValues { - inherit (ps) - setuptools - pip - # Core backend dependencies - fastapi - uvicorn - sqlalchemy - alembic - pydantic - # Auth & Security - python-jose - passlib - # Image processing - pillow - # Storage - boto3 - # HTTP & uploads - httpx - python-multipart - # Testing - pytest - pytest-cov - pytest-asyncio - ; - } - )) - ] - ++ builtins.attrValues { - inherit (pkgs) - # Python tools - uv - ruff - # Database - postgresql - # Frontend - nodejs - # Image processing - imagemagick - # Version control - git - # Development tools - direnv - ; - }; - - buildInputs = [ ]; - - shellHook = '' - echo "🚀 Reference Board Viewer Development Environment" - echo " Python: $(python --version)" - echo " Node.js: $(node --version)" - echo " PostgreSQL: $(psql --version | head -n1)" - echo "" - echo "📚 Quick Commands:" - echo " Backend: cd backend && uvicorn app.main:app --reload" - echo " Frontend: cd frontend && npm run dev" - echo " Tests: cd backend && pytest --cov" - echo "" - ''; -} diff --git a/specs/001-reference-board-viewer/quickstart.md b/specs/001-reference-board-viewer/quickstart.md index dc6e1f4..ae23275 100644 --- a/specs/001-reference-board-viewer/quickstart.md +++ b/specs/001-reference-board-viewer/quickstart.md @@ -16,16 +16,17 @@ This guide will get you from zero to a running development environment for the R # Clone repository (if not already) cd /home/jawz/Development/Projects/personal/webref -# Enter Nix development shell (installs all dependencies) +# Enter Nix development shell (from flake.nix) nix develop # Verify tools are available -python --version # Should show Python 3.12+ -node --version # Should show Node.js latest +python --version # Python 3.12 +node --version # Node.js 20+ psql --version # PostgreSQL client +ruff --version # Python linter ``` -**What this does:** Nix installs all verified dependencies from nixpkgs (see VERIFICATION-COMPLETE.md) +**What this does:** `flake.nix` provides all dependencies (Python, Node.js, PostgreSQL, MinIO, etc.) --- @@ -240,26 +241,42 @@ webref/ ### Backend ```bash +# All commands run inside nix develop shell + # Run API server -uvicorn app.main:app --reload +cd backend && uvicorn app.main:app --reload # Run tests -pytest +cd backend && pytest # Run with coverage -pytest --cov=app --cov-report=html +cd backend && pytest --cov=app --cov-report=html # Check linting -ruff check app/ +cd backend && ruff check app/ # Format code -ruff format app/ +cd backend && ruff format app/ # Run migrations -alembic upgrade head +cd backend && alembic upgrade head # Create migration -alembic revision --autogenerate -m "description" +cd backend && alembic revision --autogenerate -m "description" +``` + +### NixOS VM Integration Tests +```bash +# Run all tests (backend, full-stack, performance, security) +nix flake check + +# Run specific test +nix build .#checks.backend-integration -L +nix build .#checks.full-stack -L + +# Interactive debugging +nix build .#checks.backend-integration.driverInteractive +./result/bin/nixos-test-driver ``` ### Frontend diff --git a/specs/001-reference-board-viewer/tech-research.md b/specs/001-reference-board-viewer/tech-research.md index 76e8326..28b5bd6 100644 --- a/specs/001-reference-board-viewer/tech-research.md +++ b/specs/001-reference-board-viewer/tech-research.md @@ -654,7 +654,126 @@ The recommended stack (Svelte + Konva.js + FastAPI + PostgreSQL) provides the op - ✅ Developer experience (modern tooling, fast feedback) - ✅ Maintainability (clear architecture, good docs) - ✅ Scalability (can grow from MVP to production) -- ✅ Leverages existing setup (Python in shell.nix) +- ✅ Leverages existing setup (Python dependencies managed by Nix) This stack is production-ready, future-proof, and fully aligned with your Nix deployment requirement. +--- + +## CI/CD Architecture + +### Decision: NixOS VM Tests (No Docker) + +**Chosen Approach:** NixOS VM integration tests using `pkgs.nixosTest` + +**Why NixOS VMs over Docker:** + +| Aspect | Docker Compose | NixOS VMs (Chosen) | +|--------|----------------|-------------------| +| Isolation | Container (shared kernel) | Full VM (separate kernel) | +| Reproducibility | Image tags can drift | `flake.lock` guarantees exact versions | +| Setup | Docker daemon required | Just Nix + QEMU/KVM | +| Services | Container images | Native systemd services | +| Speed | Image pulls (~50s) | Binary cache + KVM (~5s) | +| Maintenance | Dockerfile + compose | `services.X.enable = true` | +| Cleanup | Manual or scripted | Automatic (VM destroyed) | + +**Key Benefits:** +1. **Complete isolation** - Full VM per test, cannot affect host +2. **Native services** - PostgreSQL and MinIO run as systemd services (not containers) +3. **Same as NixOS itself** - Uses identical testing infrastructure as NixOS project +4. **Parallel execution** - 4 VMs run simultaneously (~30s total) +5. **Zero Docker dependency** - No Docker daemon, no image registry +6. **Perfect reproducibility** - flake.lock guarantees bit-identical environments + +**Implementation:** + +```nix +# nixos/tests.nix +backend-integration = pkgs.nixosTest { + nodes.machine = { + services.postgresql.enable = true; # Native systemd service + services.minio.enable = true; # Native systemd service + }; + + testScript = '' + machine.wait_for_unit("postgresql.service") + machine.wait_for_unit("minio.service") + machine.succeed("pytest backend/tests/") + ''; +}; +``` + +**CI Workflow:** +- 4 parallel NixOS VMs (backend-integration, full-stack, performance, security) +- Linting and unit tests (no VM needed) +- Build verification +- Total time: ~30 seconds with caching +- **Attic binary cache**: Shares build artifacts across CI runs for faster builds + +**Alternative Considered:** Docker Compose +- ❌ Rejected due to: Docker daemon dependency, less isolation, image maintenance overhead +- Docker would add complexity without benefits (NixOS services are cleaner) + +### Development Environment + +**Decision:** Single `flake.nix` as source of truth (no shell.nix) + +**Structure:** +```nix +flake.nix +├─ devShells.default (Python, Node.js, PostgreSQL client, etc.) +├─ packages.backend (production build) +├─ packages.frontend (production build) +└─ checks.* (NixOS VM tests) +``` + +**Commands:** +```bash +nix develop # Enter dev shell +nix flake check # Run all VM tests +nix build .#backend # Build backend package +``` + +**Why flake-only:** +- Single source of truth (no shell.nix duplication) +- Flake lock guarantees reproducibility +- Same environment in dev, CI, and production +- Modern Nix best practice + +### Test Organization + +**Unit tests:** Fast, no external services (pytest, Vitest) +**Integration tests:** NixOS VMs with PostgreSQL + MinIO +**E2E tests:** Full-stack VM with running API +**Performance tests:** Dedicated VM for benchmarks +**Security tests:** Isolated VM for security validation + +All integration tests use **native NixOS services**, not Docker containers. + +### Binary Cache (Attic) + +**Setup:** Self-hosted Attic cache server at `http://127.0.0.1:2343` + +**Purpose:** Share Nix build artifacts across CI runs to significantly speed up builds. + +**CI Integration:** +```yaml +- name: Configure Attic cache + run: | + attic login servidos http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }} + attic use servidos:webref + +# After successful builds +- name: Push to Attic cache + run: attic push servidos:webref result +``` + +**Benefits:** +- VM builds cached (no rebuild if unchanged) +- Backend/frontend packages cached +- Shared across all CI jobs and developers +- Typically reduces build time by 50-70% + +**Configuration:** Secret `ATTIC_TOKEN` must be set in Gitea repository settings. +