001-reference-board-viewer #1

Merged
jawz merged 43 commits from 001-reference-board-viewer into main 2025-11-02 15:58:57 -06:00
3 changed files with 241 additions and 181 deletions
Showing only changes of commit 07f4ea8277 - Show all commits

136
flake.nix
View File

@@ -6,37 +6,41 @@
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = { self, nixpkgs, flake-utils }: outputs =
flake-utils.lib.eachDefaultSystem (system: { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (
system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
pythonEnv = pkgs.python3.withPackages (ps: with ps; [ pythonEnv = pkgs.python3.withPackages (
# Core backend dependencies ps: with ps; [
fastapi # Core backend dependencies
uvicorn fastapi
sqlalchemy uvicorn
alembic sqlalchemy
pydantic alembic
pydantic-settings # Settings management pydantic
psycopg2 # PostgreSQL driver pydantic-settings # Settings management
# Auth & Security psycopg2 # PostgreSQL driver
python-jose # Auth & Security
passlib python-jose
bcrypt # Password hashing backend for passlib passlib
email-validator # Email validation for pydantic bcrypt # Password hashing backend for passlib
# Image processing email-validator # Email validation for pydantic
pillow # Image processing
# Storage pillow
boto3 # Storage
# HTTP & uploads boto3
httpx # HTTP & uploads
python-multipart httpx
# Testing python-multipart
pytest # Testing
pytest-cov pytest
pytest-asyncio pytest-cov
]); pytest-asyncio
]
);
in in
{ {
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
@@ -45,25 +49,25 @@
pythonEnv pythonEnv
uv uv
ruff ruff
# Database # Database
postgresql postgresql
# Frontend # Frontend
nodejs nodejs
nodePackages.npm nodePackages.npm
# Image processing # Image processing
imagemagick imagemagick
# Storage # Storage
minio minio
minio-client minio-client
# Development tools # Development tools
git git
direnv direnv
# Optional: monitoring/debugging # Optional: monitoring/debugging
# redis # redis
]; ];
@@ -89,7 +93,7 @@
echo " App: http://localhost:5173" echo " App: http://localhost:5173"
echo " MinIO UI: http://localhost:9001" echo " MinIO UI: http://localhost:9001"
echo "" echo ""
# Set up environment variables # Set up environment variables
export DATABASE_URL="postgresql://localhost/webref" export DATABASE_URL="postgresql://localhost/webref"
export PYTHONPATH="$PWD/backend:$PYTHONPATH" export PYTHONPATH="$PWD/backend:$PYTHONPATH"
@@ -98,12 +102,24 @@
# Apps - Scripts that can be run with `nix run` # Apps - Scripts that can be run with `nix run`
apps = { apps = {
default = {
type = "app";
program = "${pkgs.writeShellScript "help" ''
echo "Available commands:"
echo " nix run .#lint - Run linting checks"
echo " nix run .#lint-fix - Auto-fix linting issues"
''}";
meta = {
description = "Show available commands";
};
};
# Unified linting for all code # Unified linting for all code
lint = { lint = {
type = "app"; type = "app";
program = "${pkgs.writeShellScript "lint" '' program = "${pkgs.writeShellScript "lint" ''
set -e set -e
# Backend Python linting # Backend Python linting
echo "🔍 Linting backend Python code..." echo "🔍 Linting backend Python code..."
if [ -d "backend" ]; then if [ -d "backend" ]; then
@@ -115,7 +131,7 @@
echo " Not in project root (backend/ not found)" echo " Not in project root (backend/ not found)"
exit 1 exit 1
fi fi
# Frontend linting (if node_modules exists) # Frontend linting (if node_modules exists)
if [ -d "frontend/node_modules" ]; then if [ -d "frontend/node_modules" ]; then
echo "" echo ""
@@ -128,18 +144,21 @@
else else
echo " Frontend node_modules not found, run 'npm install' first" echo " Frontend node_modules not found, run 'npm install' first"
fi fi
echo "" echo ""
echo " All linting checks passed!" echo " All linting checks passed!"
''}"; ''}";
meta = {
description = "Run linting checks on backend and frontend code";
};
}; };
# Auto-fix linting issues # Auto-fix linting issues
lint-fix = { lint-fix = {
type = "app"; type = "app";
program = "${pkgs.writeShellScript "lint-fix" '' program = "${pkgs.writeShellScript "lint-fix" ''
set -e set -e
echo "🔧 Auto-fixing backend Python code..." echo "🔧 Auto-fixing backend Python code..."
if [ -d "backend" ]; then if [ -d "backend" ]; then
cd backend cd backend
@@ -150,7 +169,7 @@
echo " Not in project root (backend/ not found)" echo " Not in project root (backend/ not found)"
exit 1 exit 1
fi fi
if [ -d "frontend/node_modules" ]; then if [ -d "frontend/node_modules" ]; then
echo "" echo ""
echo "🔧 Auto-fixing frontend code..." echo "🔧 Auto-fixing frontend code..."
@@ -158,33 +177,52 @@
${pkgs.nodePackages.prettier}/bin/prettier --write src/ ${pkgs.nodePackages.prettier}/bin/prettier --write src/
cd .. cd ..
fi fi
echo "" echo ""
echo " Auto-fix complete!" echo " Auto-fix complete!"
''}"; ''}";
meta = {
description = "Auto-fix linting issues in backend and frontend code";
};
}; };
}; };
# Package definitions (for production deployment) # Package definitions (for production deployment)
packages = { packages = rec {
# Backend package # Backend package
backend = pkgs.python3Packages.buildPythonApplication { backend = pkgs.python3Packages.buildPythonApplication {
pname = "webref-backend"; pname = "webref-backend";
version = "1.0.0"; version = "1.0.0";
pyproject = true;
src = ./backend; src = ./backend;
build-system = with pkgs.python3Packages; [
setuptools
];
propagatedBuildInputs = with pkgs.python3Packages; [ propagatedBuildInputs = with pkgs.python3Packages; [
fastapi fastapi
uvicorn uvicorn
sqlalchemy sqlalchemy
alembic alembic
pydantic pydantic
pydantic-settings
psycopg2
python-jose python-jose
passlib passlib
pillow pillow
boto3 boto3
httpx httpx
python-multipart python-multipart
email-validator
bcrypt
]; ];
meta = {
description = "Reference Board Viewer - Backend API";
homepage = "https://github.com/yourusername/webref";
license = pkgs.lib.licenses.mit;
};
}; };
# Frontend package # Frontend package
@@ -192,7 +230,7 @@
pname = "webref-frontend"; pname = "webref-frontend";
version = "1.0.0"; version = "1.0.0";
src = ./frontend; src = ./frontend;
npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Update after first build npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Update after first build
buildPhase = '' buildPhase = ''
npm run build npm run build
''; '';
@@ -200,7 +238,14 @@
mkdir -p $out mkdir -p $out
cp -r build/* $out/ cp -r build/* $out/
''; '';
meta = {
description = "Reference Board Viewer - Frontend SPA";
homepage = "https://github.com/yourusername/webref";
license = pkgs.lib.licenses.mit;
};
}; };
default = backend;
}; };
# NixOS VM tests # NixOS VM tests
@@ -208,4 +253,3 @@
} }
); );
} }

View File

@@ -1,4 +1,4 @@
{ config, pkgs, lib, ... }: { pkgs, ... }:
{ {
# Gitea Actions Runner Configuration # Gitea Actions Runner Configuration
@@ -6,36 +6,36 @@
services.gitea-actions-runner = { services.gitea-actions-runner = {
package = pkgs.gitea-actions-runner; package = pkgs.gitea-actions-runner;
instances = { instances = {
# Main runner instance for webref project # Main runner instance for webref project
webref-runner = { webref-runner = {
enable = true; enable = true;
# Runner name (will appear in Gitea) # Runner name (will appear in Gitea)
name = "nixos-runner-webref"; name = "nixos-runner-webref";
# Gitea instance URL # Gitea instance URL
url = "https://your-gitea-instance.com"; url = "https://your-gitea-instance.com";
# Runner token - Generate this from Gitea: # Runner token - Generate this from Gitea:
# Settings -> Actions -> Runners -> Create New Runner # Settings -> Actions -> Runners -> Create New Runner
# Store the token in a file and reference it here # Store the token in a file and reference it here
tokenFile = "/var/secrets/gitea-runner-token"; tokenFile = "/var/secrets/gitea-runner-token";
# Labels define what jobs this runner can handle # Labels define what jobs this runner can handle
# Format: "label:docker_image" or just "label" for host execution # Format: "label:docker_image" or just "label" for host execution
labels = [ labels = [
# Native execution with Nix # Native execution with Nix
"nix:native" "nix:native"
# Ubuntu-like for compatibility # Ubuntu-like for compatibility
"ubuntu-latest:docker://node:20-bookworm" "ubuntu-latest:docker://node:20-bookworm"
# Specific for this project # Specific for this project
"webref:native" "webref:native"
]; ];
# Host packages available to the runner # Host packages available to the runner
hostPackages = with pkgs; [ hostPackages = with pkgs; [
# Essential tools # Essential tools
@@ -44,15 +44,15 @@
curl curl
git git
nix nix
# Project-specific # Project-specific
nodejs nodejs
python3 python3
postgresql postgresql
# Binary cache # Binary cache
attic-client attic-client
# Container runtime (optional) # Container runtime (optional)
docker docker
docker-compose docker-compose
@@ -75,16 +75,19 @@
extraGroups = [ "docker" ]; extraGroups = [ "docker" ];
}; };
users.groups.gitea-runner = {}; users.groups.gitea-runner = { };
# Allow runner to use Nix # Allow runner to use Nix
nix.settings = { nix.settings = {
allowed-users = [ "gitea-runner" ]; allowed-users = [ "gitea-runner" ];
trusted-users = [ "gitea-runner" ]; trusted-users = [ "gitea-runner" ];
# Enable flakes for the runner # Enable flakes for the runner
experimental-features = [ "nix-command" "flakes" ]; experimental-features = [
"nix-command"
"flakes"
];
# Optimize for CI performance # Optimize for CI performance
max-jobs = "auto"; max-jobs = "auto";
cores = 0; # Use all available cores cores = 0; # Use all available cores
@@ -102,11 +105,10 @@
# Resource limits (adjust based on your hardware) # Resource limits (adjust based on your hardware)
MemoryMax = "8G"; MemoryMax = "8G";
CPUQuota = "400%"; # 4 cores CPUQuota = "400%"; # 4 cores
# Restart policy # Restart policy
Restart = "always"; Restart = "always";
RestartSec = "10s"; RestartSec = "10s";
}; };
}; };
} }

View File

@@ -4,189 +4,203 @@
# Backend integration tests with PostgreSQL and MinIO # Backend integration tests with PostgreSQL and MinIO
backend-integration = pkgs.testers.nixosTest { backend-integration = pkgs.testers.nixosTest {
name = "webref-backend-integration"; name = "webref-backend-integration";
nodes = { nodes = {
machine = { config, pkgs, ... }: { machine =
# PostgreSQL service { pkgs, ... }:
services.postgresql = { {
enable = true; # PostgreSQL service
ensureDatabases = [ "webref" ]; services.postgresql = {
ensureUsers = [{ enable = true;
name = "webref"; ensureDatabases = [ "webref" ];
ensureDBOwnership = true; ensureUsers = [
}]; {
authentication = '' name = "webref";
local all all trust ensureDBOwnership = true;
host all all 127.0.0.1/32 trust }
host all all ::1/128 trust ];
''; 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
'';
};
# Install required packages
environment.systemPackages = with pkgs; [
python3
python3Packages.pytest
python3Packages.fastapi
postgresql
curl
];
# Network configuration
networking.firewall.enable = false;
}; };
# MinIO service
services.minio = {
enable = true;
rootCredentialsFile = pkgs.writeText "minio-credentials" ''
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
'';
};
# Install required packages
environment.systemPackages = with pkgs; [
python3
python3Packages.pytest
python3Packages.fastapi
postgresql
curl
];
# Network configuration
networking.firewall.enable = false;
};
}; };
testScript = '' testScript = ''
start_all() start_all()
# Wait for PostgreSQL # Wait for PostgreSQL
machine.wait_for_unit("postgresql.service") machine.wait_for_unit("postgresql.service")
machine.wait_for_open_port(5432) machine.wait_for_open_port(5432)
# Wait for MinIO # Wait for MinIO
machine.wait_for_unit("minio.service") machine.wait_for_unit("minio.service")
machine.wait_for_open_port(9000) machine.wait_for_open_port(9000)
# Verify PostgreSQL is working # Verify PostgreSQL is working
machine.succeed("sudo -u postgres psql -c 'SELECT 1;'") machine.succeed("sudo -u postgres psql -c 'SELECT 1;'")
# Verify MinIO is working # Verify MinIO is working
machine.succeed("curl -f http://localhost:9000/minio/health/live") machine.succeed("curl -f http://localhost:9000/minio/health/live")
machine.succeed("echo ' Backend integration test passed'") machine.succeed("echo ' Backend integration test passed'")
''; '';
}; };
# Full stack test with backend + database # Full stack test with backend + database
full-stack = pkgs.testers.nixosTest { full-stack = pkgs.testers.nixosTest {
name = "webref-full-stack"; name = "webref-full-stack";
nodes = { nodes = {
machine = { config, pkgs, ... }: { machine =
# PostgreSQL { pkgs, ... }:
services.postgresql = { {
enable = true; # PostgreSQL
ensureDatabases = [ "webref" ]; services.postgresql = {
ensureUsers = [{ enable = true;
name = "webref"; ensureDatabases = [ "webref" ];
ensureDBOwnership = true; ensureUsers = [
}]; {
name = "webref";
ensureDBOwnership = true;
}
];
};
# MinIO
services.minio = {
enable = true;
rootCredentialsFile = pkgs.writeText "minio-credentials" ''
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
'';
};
environment.systemPackages = with pkgs; [
python3
curl
jq
];
networking.firewall.enable = false;
}; };
# MinIO
services.minio = {
enable = true;
rootCredentialsFile = pkgs.writeText "minio-credentials" ''
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
'';
};
environment.systemPackages = with pkgs; [
python3
curl
jq
];
networking.firewall.enable = false;
};
}; };
testScript = '' testScript = ''
start_all() start_all()
# Wait for services # Wait for services
machine.wait_for_unit("postgresql.service") machine.wait_for_unit("postgresql.service")
machine.wait_for_unit("minio.service") machine.wait_for_unit("minio.service")
machine.wait_for_open_port(5432) machine.wait_for_open_port(5432)
machine.wait_for_open_port(9000) machine.wait_for_open_port(9000)
# Test database connectivity # Test database connectivity
machine.succeed("sudo -u postgres psql -c 'SELECT version();'") machine.succeed("sudo -u postgres psql -c 'SELECT version();'")
# Test MinIO API # Test MinIO API
machine.succeed("curl -f http://localhost:9000/minio/health/live") machine.succeed("curl -f http://localhost:9000/minio/health/live")
machine.succeed("echo ' Full stack test passed'") machine.succeed("echo ' Full stack test passed'")
''; '';
}; };
# Performance benchmarks # Performance benchmarks
performance = pkgs.testers.nixosTest { performance = pkgs.testers.nixosTest {
name = "webref-performance"; name = "webref-performance";
nodes = { nodes = {
machine = { config, pkgs, ... }: { machine =
services.postgresql.enable = true; { pkgs, ... }:
services.minio.enable = true; {
services.postgresql.enable = true;
environment.systemPackages = with pkgs; [ services.minio.enable = true;
python3
]; environment.systemPackages = with pkgs; [
}; python3
];
};
}; };
testScript = '' testScript = ''
start_all() start_all()
machine.wait_for_unit("postgresql.service") machine.wait_for_unit("postgresql.service")
machine.succeed("echo ' Performance test passed'") machine.succeed("echo ' Performance test passed'")
''; '';
}; };
# Security tests # Security tests
security = pkgs.testers.nixosTest { security = pkgs.testers.nixosTest {
name = "webref-security"; name = "webref-security";
nodes = { nodes = {
machine = { config, pkgs, ... }: { machine =
services.postgresql = { { pkgs, ... }:
enable = true; {
ensureDatabases = [ "webref" ]; services.postgresql = {
ensureUsers = [{ enable = true;
name = "webref"; ensureDatabases = [ "webref" ];
ensureDBOwnership = true; ensureUsers = [
}]; {
name = "webref";
ensureDBOwnership = true;
}
];
};
# Create system user for testing
users.users.webref = {
isSystemUser = true;
group = "webref";
};
users.groups.webref = { };
environment.systemPackages = with pkgs; [
python3
nmap
];
}; };
# Create system user for testing
users.users.webref = {
isSystemUser = true;
group = "webref";
};
users.groups.webref = {};
environment.systemPackages = with pkgs; [
python3
nmap
];
};
}; };
testScript = '' testScript = ''
start_all() start_all()
machine.wait_for_unit("postgresql.service") machine.wait_for_unit("postgresql.service")
# Wait for PostgreSQL setup scripts to complete (database and user creation) # Wait for PostgreSQL setup scripts to complete (database and user creation)
import time import time
machine.wait_for_unit("postgresql-setup.service", timeout=30) machine.wait_for_unit("postgresql-setup.service", timeout=30)
time.sleep(2) # Give it a moment to finalize time.sleep(2) # Give it a moment to finalize
# Verify database role exists # Verify database role exists
machine.succeed("sudo -u postgres psql -c '\\du' | grep webref") machine.succeed("sudo -u postgres psql -c '\\du' | grep webref")
# Verify database is accessible with webref user # Verify database is accessible with webref user
machine.succeed("sudo -u webref psql webref -c 'SELECT 1;'") machine.succeed("sudo -u webref psql webref -c 'SELECT 1;'")
machine.succeed("echo ' Security test passed'") machine.succeed("echo ' Security test passed'")
''; '';
}; };