towerops/docs/nix.md
Graham McIntie 397271ce03 docs: update nix guide for TimescaleDB, add org settings and device monitoring docs
- nix.md: TimescaleDB now enabled for both dev and test databases
- nix.md: added troubleshooting section for TimescaleDB reinitialization
- New: docs/features/organization-settings.md (tabbed interface, all tabs documented)
- New: docs/features/device-monitoring.md (schema field reference, activity feed fields, SNMP socket management)
2026-02-14 11:28:57 -06:00

648 lines
14 KiB
Markdown

# Nix Flakes Guide for Towerops Web
This document provides comprehensive guidance for using Nix flakes with the towerops-web project.
## Table of Contents
- [Overview](#overview)
- [Quick Start](#quick-start)
- [Installation](#installation)
- [Development Workflow](#development-workflow)
- [Building](#building)
- [CI/CD Integration](#cicd-integration)
- [Cachix Setup](#cachix-setup)
- [Troubleshooting](#troubleshooting)
- [Updating Dependencies](#updating-dependencies)
## Overview
The towerops-web project uses Nix flakes for:
- **Reproducible builds**: Identical builds across development, CI, and production
- **Development environment**: One command (`nix develop`) provides fully configured environment
- **Docker images**: Pure Nix OCI images (~150-200 MB vs ~500 MB Debian-based)
- **Binary caching**: Cachix eliminates redundant compilation across machines
- **Dependency management**: All dependencies pinned in `flake.lock`
### Architecture
```
flake.nix # Main flake definition
├── nix/
│ ├── c-nif.nix # C NIF shared library (cached separately)
│ ├── build.nix # Mix release derivation
│ ├── docker.nix # OCI image using dockerTools.buildLayeredImage
│ └── shell.nix # Development shell with PostgreSQL, Redis, LSPs
├── flake.lock # Locked dependency versions
├── .envrc.example # direnv configuration example
└── shell.nix # Compatibility shim for nix-shell
```
## Quick Start
### With Nix Installed
```bash
# Enter development environment (auto-starts PostgreSQL and Redis)
nix develop
# Or with direnv (automatic on cd)
cp .envrc.example .envrc
direnv allow
# Start Phoenix server
mix phx.server
```
### First-Time Setup
1. **Install Nix** (if not already installed):
```bash
# macOS or Linux
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
```
2. **Clone the repository**:
```bash
git clone <repository-url>
cd towerops-web
```
3. **Enter development environment**:
```bash
nix develop
```
On first run, this will:
- Download and cache all dependencies
- Initialize PostgreSQL in `.nix-postgres/`
- Start Redis in `.nix-redis/`
- Create `towerops_dev` and `towerops_test` databases
- Run database migrations
- Install pre-commit hooks
4. **Start developing**:
```bash
mix phx.server
# Navigate to http://localhost:4000
```
## Installation
### Nix Installation
#### macOS
```bash
# Using Determinate Systems installer (recommended)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Or using official installer
sh <(curl -L https://nixos.org/nix/install)
```
#### Linux
```bash
# Using Determinate Systems installer (recommended)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Or using official installer
sh <(curl -L https://nixos.org/nix/install) --daemon
```
#### Verify Installation
```bash
nix --version
# Should output: nix (Nix) 2.x.x
```
### direnv Installation (Optional but Recommended)
direnv automatically loads the Nix environment when you `cd` into the project directory.
```bash
# macOS
brew install direnv
# Linux (Ubuntu/Debian)
sudo apt-get install direnv
# Add to your shell rc file (~/.bashrc, ~/.zshrc, etc.)
eval "$(direnv hook bash)" # or zsh, fish, etc.
```
Then in the project directory:
```bash
cp .envrc.example .envrc
direnv allow
```
## Development Workflow
### Entering the Development Environment
**With direnv** (automatic):
```bash
cd /path/to/towerops-web
# Environment loads automatically
```
**Without direnv** (manual):
```bash
nix develop
```
### Available Services
The development shell automatically starts:
- **PostgreSQL 16 + TimescaleDB**: Runs on `localhost:5432`
- Databases: `towerops_dev`, `towerops_test`
- Data directory: `.nix-postgres/`
- TimescaleDB extension enabled for both dev and test databases
- PostgreSQL built with `withPackages` to include TimescaleDB
- `shared_preload_libraries = 'timescaledb'` set in config
- **Redis**: Runs on `localhost:6379`
- Data directory: `.nix-redis/`
- No persistence (dev mode)
### Service Management
```bash
# Start services (if not auto-started)
start-services
# Stop services
stop-services
# Check if services are running
pg_isready -h localhost -p 5432
redis-cli ping
```
### Environment Variables
Automatically set in the dev shell:
- `DATABASE_URL`: PostgreSQL connection string
- `REDIS_URL`: Redis connection string
- `SECRET_KEY_BASE`: Development secret (DO NOT use in production)
- `CLOAK_KEY`: Development encryption key (DO NOT use in production)
- `MIX_ENV`: Set to `dev`
- `PHX_HOST`: Set to `localhost`
- `PORT`: Set to `4000`
### Pre-commit Hooks
Automatically installed in the dev shell:
- `mix format --check-formatted` - Elixir code formatting
- `mix credo --strict` - Elixir linting
- `nixfmt` - Nix file formatting
- `shellcheck` - Shell script linting
Run manually:
```bash
pre-commit run --all-files
```
## Building
### Build the Elixir Release
```bash
nix build .#towerops
# Output in ./result/
# Run the release
./result/bin/towerops start
# Or use RPC
./result/bin/towerops rpc "1 + 1"
```
### Build the Docker Image
```bash
nix build .#dockerImage
# Output in ./result
# Load into Docker
docker load < result
# Run the container
docker run --rm -p 4000:4000 \
-e DATABASE_URL="..." \
-e SECRET_KEY_BASE="..." \
registry.gitlab.com/towerops/towerops:latest
```
### Build the C NIF Separately
```bash
nix build .#towerops-nif
# Output in ./result/lib/towerops_nif.so
# Check dependencies
ldd result/lib/towerops_nif.so
```
### Check Build Closure Size
```bash
# Show all runtime dependencies and sizes
nix path-info -rS .#dockerImage
# Show only the image size
nix path-info -S .#dockerImage
```
## CI/CD Integration
### GitLab CI Configuration
The project includes a Nix-based GitLab CI configuration in `.gitlab-ci.yml.nix`.
**Requirements**:
- GitLab Runner with Nix installed
- Docker socket mounted (for `docker load`)
- Cachix authentication token in `CACHIX_AUTH_TOKEN` variable
### Setting Up NixOS GitLab Runner
1. **Install GitLab Runner on NixOS**:
```nix
# /etc/nixos/configuration.nix
services.gitlab-runner = {
enable = true;
services = {
nix = {
registrationConfigFile = "/etc/gitlab-runner/registration";
dockerImage = "nixos/nix:latest";
dockerVolumes = [
"/nix/store:/nix/store:ro"
"/nix/var/nix/db:/nix/var/nix/db:ro"
"/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro"
];
tagList = [ "nix" ];
};
};
};
```
2. **Enable experimental features**:
```nix
nix.settings.experimental-features = [ "nix-command" "flakes" ];
```
### Activating Nix CI/CD
Once the NixOS runner is configured:
```bash
# Backup current CI config
mv .gitlab-ci.yml .gitlab-ci.yml.docker
# Activate Nix CI config
mv .gitlab-ci.yml.nix .gitlab-ci.yml
# Commit and push
git add .gitlab-ci.yml
git commit -m "ci: migrate to Nix builds"
git push
```
## Cachix Setup
Cachix provides binary caching to speed up builds across machines.
### 1. Create Cachix Account
Visit https://cachix.org and sign up.
### 2. Create Cache
```bash
# Install cachix
nix-env -iA cachix -f https://cachix.org/api/v1/install
# Create cache
cachix create towerops
# Generate keypair
cachix generate-keypair towerops
# Get public key
cachix get towerops
# Save the public key output
```
### 3. Update flake.nix
Replace the placeholder public key in `flake.nix`:
```nix
nixConfig = {
extra-substituters = [
"https://cache.nixos.org"
"https://towerops.cachix.org"
];
extra-trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"towerops.cachix.org-1:YOUR_ACTUAL_PUBLIC_KEY_HERE"
];
};
```
### 4. Configure Local Machine
```bash
cachix use towerops
```
This updates `~/.config/nix/nix.conf` to use the cache.
### 5. Push Builds to Cache
```bash
# Push a specific build
nix build .#dockerImage
nix path-info --json .#dockerImage | jq -r '.[].path' | cachix push towerops
# Push all outputs
nix flake show --json | jq -r '.packages."x86_64-linux" | keys[]' | \
xargs -I {} nix build .#{} --print-build-logs
nix flake show --json | jq -r '.packages."x86_64-linux" | keys[]' | \
xargs -I {} nix path-info --json .#{} | jq -r '.[].path' | cachix push towerops
```
### 6. Configure CI/CD
Add `CACHIX_AUTH_TOKEN` to GitLab CI/CD variables:
1. Go to GitLab project → Settings → CI/CD → Variables
2. Add variable:
- Key: `CACHIX_AUTH_TOKEN`
- Value: (get from `cachix authtoken`)
- Masked: Yes
- Protected: Yes (optional)
## Troubleshooting
### Services Won't Start
**PostgreSQL fails to initialize**:
```bash
# Remove corrupted data directory
rm -rf .nix-postgres
# Re-enter shell to reinitialize
exit
nix develop
```
**Port already in use**:
```bash
# Check what's using the port
lsof -i :5432 # PostgreSQL
lsof -i :6379 # Redis
# Kill the process or change port in nix/shell.nix
```
### Nix Build Failures
**Error: experimental feature 'flakes' is not enabled**:
```bash
# Add to ~/.config/nix/nix.conf
experimental-features = nix-command flakes
# Or use --extra-experimental-features
nix --extra-experimental-features "nix-command flakes" develop
```
**Error: out of disk space**:
```bash
# Clean old generations
nix-collect-garbage -d
# Clean build artifacts
nix-store --gc
```
**C NIF compilation fails**:
```bash
# Check net-snmp is available
nix-shell -p net-snmp --run "pkg-config --libs netsnmp"
# Rebuild NIF separately
nix build .#towerops-nif --rebuild
```
### TimescaleDB Not Loading
If TimescaleDB was added after initial setup, you need to reinitialize PostgreSQL:
```bash
# Stop services and remove old data directory
stop-services
rm -rf .nix-postgres .nix-services-started
# Re-enter shell to reinitialize with TimescaleDB
exit
nix develop
```
This is required because `shared_preload_libraries` is set during `initdb` and the TimescaleDB shared library must be available in the PostgreSQL package.
### Mix Dependencies
**Error: Mix dependencies not found**:
```bash
# Ensure flake.lock is up to date
nix flake update
# Rebuild with fresh dependencies
nix build .#towerops --rebuild
```
**Vendored Oban packages**:
The `vendor/` directory is included in the Nix build. If you update vendored packages:
```bash
# Rebuild to pick up changes
nix build .#towerops --rebuild
```
### Docker Image Issues
**Image too large**:
```bash
# Check closure size
nix path-info -rS .#dockerImage
# Identify large dependencies
nix path-info -rS .#dockerImage | sort -k2 -h | tail -20
```
**Missing files in image**:
```bash
# List image contents
docker run --rm registry.gitlab.com/towerops/towerops:latest find / -name "*.mib" | head
```
### direnv Issues
**direnv: error .envrc is blocked**:
```bash
direnv allow
```
**Environment not loading**:
```bash
# Check direnv status
direnv status
# Reload manually
direnv reload
```
## Updating Dependencies
### Update Nix Flake Inputs
```bash
# Update all inputs
nix flake update
# Update specific input
nix flake update nixpkgs
# Update and rebuild
nix flake update && nix build .#towerops
```
### Update Elixir/Erlang Version
Edit `nix/shell.nix` and `nix/build.nix` to use different versions:
```nix
# Before
elixir
erlang_28
# After (if nixpkgs has newer versions)
elixir_1_20
erlang_29
```
Then rebuild:
```bash
nix flake update nixpkgs
nix build .#towerops --rebuild
```
### Pin Specific Nixpkgs Version
Edit `flake.nix` to pin to a specific commit:
```nix
inputs = {
# Pin to specific nixpkgs commit
nixpkgs.url = "github:NixOS/nixpkgs/abc123def456";
# Or pin to a release branch
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
};
```
### Update Mix Dependencies
Mix dependencies are managed in `mix.exs` and `mix.lock` as usual. Nix will use these files:
```bash
# Update Mix dependencies
mix deps.update --all
# Commit updated mix.lock
git add mix.lock
git commit -m "deps: update Elixir dependencies"
# Rebuild with new dependencies
nix build .#towerops --rebuild
```
## Advanced Topics
### Cross-Platform Builds
Build for multiple architectures:
```bash
# Build for Linux ARM64 (Apple Silicon servers)
nix build .#packages.aarch64-linux.dockerImage
# Build for macOS on Linux (if configured)
nix build .#packages.x86_64-darwin.towerops
```
### Custom Nix Overlays
Create `nix/overlays.nix` to override nixpkgs packages:
```nix
final: prev: {
# Example: Use specific Elixir version
elixir = prev.elixir.override {
version = "1.20.0";
};
}
```
Reference in `flake.nix`:
```nix
_module.args.pkgs = import nixpkgs {
inherit system;
overlays = [ (import ./nix/overlays.nix) ];
};
```
### Development Without Nix
If Nix is not available, use the traditional development setup:
```bash
# Install Elixir via asdf
asdf install erlang 28.3
asdf install elixir 1.19.5-otp-28
# Install PostgreSQL and Redis
brew install postgresql@16 redis # macOS
# Or: sudo apt-get install postgresql-16 redis # Linux
# Start services manually
brew services start postgresql@16
brew services start redis
# Run development server
mix deps.get
mix ecto.setup
mix phx.server
```
## Resources
- [Nix Flakes Documentation](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake.html)
- [NixOS Packages Search](https://search.nixos.org/packages)
- [Cachix Documentation](https://docs.cachix.org/)
- [direnv Documentation](https://direnv.net/)
- [Elixir on Nix](https://nixos.wiki/wiki/Elixir)
## Getting Help
- **Nix Discord**: https://discord.gg/RbvHtGa
- **NixOS Discourse**: https://discourse.nixos.org/
- **Project Issues**: File issues on GitLab for project-specific problems