- Add flake.nix + flake.lock pinning nixpkgs-unstable - Add nix/shell.nix with PostgreSQL, Rust, Elixir LSP, pre-commit hooks - Add shell.nix flake-compat shim for non-flake nix-shell users - Bump Dockerfile to Elixir 1.20.2 / OTP 29.0.3 - Add nix.md with setup and troubleshooting docs - Gitignore Nix runtime state and generated pre-commit config Co-Authored-By: Claude <noreply@anthropic.com>
280 lines
8.1 KiB
Nix
280 lines
8.1 KiB
Nix
{
|
|
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 ""
|
|
'';
|
|
}
|