towerops/nix/shell.nix
Graham McIntire 4b903df160
fix: Use stdenv.isLinux for platform check
Use stdenv.isLinux instead of lib.isLinux to properly check if
inotify-tools should be included. This fixes the undefined variable
error when evaluating the flake on macOS.
2026-02-07 12:31:50 -06:00

305 lines
8.6 KiB
Nix
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
lib,
stdenv,
mkShell,
writeShellScriptBin,
# Elixir/Erlang
elixir,
# Databases and caches
postgresql_16,
redis,
# SNMP tools
net-snmp,
# Build tools for C NIF
gcc,
gnumake,
pkg-config,
# 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";
# Redis data directory (local to project)
redisDataDir = ".nix-redis";
redisPort = "6379";
# Service management flag
servicesFlag = ".nix-services-started";
# Script to start PostgreSQL and Redis
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
cat >> ${pgDataDir}/postgresql.conf << EOF
# Development configuration
max_connections = 100
shared_buffers = 128MB
fsync = off # Faster for development (DO NOT use in production)
synchronous_commit = off
full_page_writes = off
EOF
fi
# 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"
exit 1
fi
sleep 1
done
# 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 towerops_dev towerops_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
# Enable TimescaleDB extension (dev database only, test uses plain PostgreSQL)
${postgresql_16}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d towerops_dev -c \
"CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" 2>/dev/null || \
echo " TimescaleDB not available (optional for development)"
echo " PostgreSQL started successfully"
# Start Redis
echo "📮 Starting Redis on port ${redisPort}..."
mkdir -p ${redisDataDir}
${redis}/bin/redis-server --daemonize yes \
--port ${redisPort} \
--dir ${redisDataDir} \
--save "" \
--appendonly no
# Wait for Redis to be ready
for i in {1..10}; do
if ${redis}/bin/redis-cli -p ${redisPort} ping > /dev/null 2>&1; then
break
fi
if [ $i -eq 10 ]; then
echo " Redis failed to start within 10 seconds"
exit 1
fi
sleep 1
done
echo " Redis started successfully"
# Mark services as started
touch ${servicesFlag}
echo ""
echo " Development environment ready!"
echo ""
echo "PostgreSQL: ${pgHost}:${pgPort} (databases: towerops_dev, towerops_test)"
echo "Redis: ${pgHost}:${redisPort}"
echo ""
'';
# Script to stop PostgreSQL and Redis
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
# Stop Redis
${redis}/bin/redis-cli -p ${redisPort} shutdown 2>/dev/null || true
echo " Redis stopped"
# Remove services flag
rm -f ${servicesFlag}
echo " Services stopped successfully"
'';
# Pre-commit hooks configuration
pre-commit-check = pre-commit-hooks.lib.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;
};
# Nix formatting
nixfmt = {
enable = true;
entry = "${nixfmt}/bin/nixfmt";
};
# Shellcheck for shell scripts
shellcheck.enable = true;
};
};
in
mkShell {
name = "towerops-dev";
# Development tools
buildInputs = [
# Elixir/Erlang
elixir
# Databases
postgresql_16
redis
# SNMP tools
net-snmp
# C NIF build tools
gcc
gnumake
pkg-config
# 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 = ''
# Color codes
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 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}/towerops_dev"
# Redis connection
export REDIS_URL="redis://${pgHost}:${redisPort}/0"
# 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 CLOAK_KEY="dev_cloak_key_base64_encoded_32_bytes_long="
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"
# Auto-start services on first shell entry
if [ ! -f "${servicesFlag}" ]; then
${startServices}/bin/start-services
# Run migrations if databases exist
if mix ecto.migrate 2>/dev/null; then
echo " Database migrations applied"
fi
fi
# Register trap to stop services on shell exit
trap '${stopServices}/bin/stop-services' EXIT
# Install pre-commit hooks
${pre-commit-check.shellHook}
# Welcome message
echo ""
echo -e "''${BLUE}''${NC}"
echo -e "''${BLUE} ''${NC}"
echo -e "''${BLUE}''${NC} ''${GREEN}Towerops Development Environment''${NC} ''${BLUE}''${NC}"
echo -e "''${BLUE} ''${NC}"
echo -e "''${BLUE}''${NC}"
echo ""
echo -e "''${GREEN}Available commands:''${NC}"
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 and Redis"
echo " stop-services - Stop PostgreSQL and Redis"
echo " mix ecto.reset - Reset database (drop, create, migrate, seed)"
echo ""
echo -e "''${GREEN}Services:''${NC}"
echo " PostgreSQL: ${pgHost}:${pgPort}"
echo " Redis: ${pgHost}:${redisPort}"
echo ""
echo -e "''${GREEN}Documentation:''${NC}"
echo " See CLAUDE.md and docs/nix.md for more information"
echo ""
'';
}