{ lib, stdenv, mkShell, writeShellScriptBin, # Elixir/Erlang elixir, # Database postgresql_16, # Rust toolchain rustup, rust-analyzer, # Development tools git-lfs, inotify-tools, # LSPs and formatters elixir-ls, nixfmt, shellcheck, # Pre-commit hooks pre-commit-hooks, }: let # PostgreSQL data directory (local to project) pgDataDir = ".nix-postgres"; pgPort = "5432"; pgHost = "localhost"; # Service management flag servicesFlag = ".nix-services-started"; # Script to start PostgreSQL startServices = writeShellScriptBin "start-services" '' set -e echo "Starting development services..." # Check if already started if [ -f "${servicesFlag}" ]; then echo "Services already running (remove ${servicesFlag} to force restart)" exit 0 fi # Initialize PostgreSQL if needed if [ ! -d "${pgDataDir}" ]; then echo "Initializing PostgreSQL database..." ${postgresql_16}/bin/initdb -D ${pgDataDir} -U $USER --encoding=UTF8 --locale=en_US.UTF-8 # Configure PostgreSQL for development (fast but unsafe for production) cat >> ${pgDataDir}/postgresql.conf << EOF # Development configuration max_connections = 100 shared_buffers = 128MB fsync = off synchronous_commit = off full_page_writes = off EOF fi # Check if PostgreSQL is already running on this port if ${postgresql_16}/bin/pg_isready -h ${pgHost} -p ${pgPort} > /dev/null 2>&1; then echo "PostgreSQL already running on port ${pgPort}" else # Start PostgreSQL echo "Starting PostgreSQL on port ${pgPort}..." ${postgresql_16}/bin/pg_ctl -D ${pgDataDir} -l ${pgDataDir}/logfile -o "-p ${pgPort}" start # Wait for PostgreSQL to be ready for i in {1..30}; do if ${postgresql_16}/bin/pg_isready -h ${pgHost} -p ${pgPort} > /dev/null 2>&1; then break fi if [ $i -eq 30 ]; then echo "PostgreSQL failed to start within 30 seconds" echo "Check ${pgDataDir}/logfile for details" exit 1 fi sleep 1 done echo "PostgreSQL started successfully" fi # Create postgres role if it doesn't exist (needed by some tools) ${postgresql_16}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d postgres -tc \ "SELECT 1 FROM pg_roles WHERE rolname='postgres'" | grep -q 1 || \ ${postgresql_16}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d postgres -c \ "CREATE ROLE postgres WITH SUPERUSER LOGIN;" # Create development and test databases for db in prop_dev prop_test; do ${postgresql_16}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d postgres -tc \ "SELECT 1 FROM pg_database WHERE datname='$db'" | grep -q 1 || \ ${postgresql_16}/bin/createdb -h ${pgHost} -p ${pgPort} -U $USER $db done # Mark services as started touch ${servicesFlag} echo "" echo "Development environment ready!" echo "" echo "PostgreSQL: ${pgHost}:${pgPort} (databases: prop_dev, prop_test)" echo "" ''; # Script to stop PostgreSQL stopServices = writeShellScriptBin "stop-services" '' set -e echo "Stopping development services..." # Stop PostgreSQL if [ -d "${pgDataDir}" ]; then ${postgresql_16}/bin/pg_ctl -D ${pgDataDir} stop -m fast 2>/dev/null || true echo "PostgreSQL stopped" fi # Remove services flag rm -f ${servicesFlag} echo "Services stopped successfully" ''; # Pre-commit hooks configuration pre-commit-check = pre-commit-hooks.run { src = ../.; hooks = { # Elixir formatting mix-format = { enable = true; name = "mix format"; entry = "mix format --check-formatted"; files = "\\.(ex|exs)$"; pass_filenames = false; }; # Credo linting credo = { enable = true; name = "credo"; entry = "mix credo --strict"; files = "\\.(ex|exs)$"; pass_filenames = false; }; # Rust formatting and linting cargo-fmt = { enable = true; name = "cargo fmt"; entry = "cargo fmt --all --check"; files = "\\.rs$"; pass_filenames = false; }; cargo-clippy = { enable = true; name = "cargo clippy"; entry = "cargo clippy --all-targets -- -D warnings"; files = "\\.rs$"; pass_filenames = false; }; # Nix formatting nixfmt = { enable = true; entry = "${nixfmt}/bin/nixfmt"; }; # Shellcheck for shell scripts shellcheck.enable = true; }; }; in mkShell { name = "prop-dev"; # Development tools buildInputs = [ # Elixir/Erlang elixir # Database postgresql_16 # Rust toolchain rustup rust-analyzer # Development tools git-lfs # LSPs and formatters elixir-ls nixfmt shellcheck # Service management scripts startServices stopServices ] ++ lib.optionals stdenv.isLinux [ # Linux-only tools inotify-tools # For Mix file watching ]; # Environment variables shellHook = '' # Set Mix and Hex home to local directories (avoid polluting $HOME) export MIX_HOME="$PWD/.nix-mix" export HEX_HOME="$PWD/.nix-hex" mkdir -p "$MIX_HOME" "$HEX_HOME" # PostgreSQL connection export PGHOST="${pgHost}" export PGPORT="${pgPort}" export PGUSER="$USER" export DATABASE_URL="ecto://$USER@${pgHost}:${pgPort}/prop_dev" # Development secrets (DO NOT use in production) export SECRET_KEY_BASE="dev_secret_key_base_at_least_64_bytes_long_for_phoenix_to_accept_it_safely" export RELEASE_COOKIE="dev_release_cookie_for_distributed_erlang" # Development environment export MIX_ENV=dev export PHX_HOST=localhost export PORT=4000 # Ensure Erlang can find NIF libraries export ERL_AFLAGS="-kernel shell_history enabled" # Rust: set up stable toolchain if not already installed if ! rustup show active-toolchain 2>/dev/null | grep -q stable; then rustup default stable 2>/dev/null || true fi # Install Hex and Rebar3 if not already installed mix local.hex --force --if-missing > /dev/null 2>&1 mix local.rebar --force --if-missing > /dev/null 2>&1 # Auto-start services on first shell entry if [ ! -f "${servicesFlag}" ]; then ${startServices}/bin/start-services # Run migrations if databases exist and Mix deps are available if [ -d "deps" ] && mix ecto.migrate 2>/dev/null; then echo "Database migrations applied" fi fi # Install pre-commit hooks ${pre-commit-check.shellHook} # Welcome message echo "" echo "╔═══════════════════════════════════════════════════════════╗" echo "║ ║" echo "║ Prop — Microwave Propagation Prediction ║" echo "║ ║" echo "╚═══════════════════════════════════════════════════════════╝" echo "" echo "Available commands:" echo " mix phx.server - Start Phoenix development server" echo " mix test - Run test suite" echo " mix precommit - Run pre-commit checks (format, credo, test)" echo " start-services - Start PostgreSQL" echo " stop-services - Stop PostgreSQL" echo " mix ecto.reset - Reset database (drop, create, migrate, seed)" echo " cargo build - Build Rust crates (cd rust/prop_grid_rs)" echo " cargo test - Run Rust tests" echo "" echo "Services:" echo " PostgreSQL: ${pgHost}:${pgPort}" echo "" echo "Documentation:" echo " See CLAUDE.md and nix.md for more information" echo "" ''; }