No description
Find a file
Graham McIntire 9cb4c59638 dialyzer: replace suppressions with real fixes where possible
Changes to eliminate @dialyzer suppressions by fixing underlying causes:

NIF stubs (towerops_native.ex, mib_translator.ex):
- Change stubs to :erlang.nif_error(:nif_not_loaded) (no_return type).
  Real NIF replaces stubs at load time; calls to unloaded stubs now fail
  loudly instead of returning fake data. Lets dialyzer trust @spec.
- Remove @dialyzer :nowarn_function on three NIFs and on translate/1.

Discovery sync_* functions (snmp/discovery.ex, channels/agent_channel.ex):
- agent_channel passes %{device_id: _, interfaces: _} and %{id: _} maps
  into Discovery.sync_ip_addresses/sync_processors/sync_storage, which
  @spec'd only %Device{}. Add narrow map-type unions (ip_sync_device,
  snmp_device_ref) reflecting what the functions actually access.
- Remove @dialyzer :nowarn_function on three agent_channel helpers.

remote_ip.ex — real bug caught and fixed:
- `:ranch.get_addr(socket.transport_pid)` was always raising since
  Bandit uses ThousandIsland, not Ranch; the rescue _ -> nil silently
  returned nil every time. Switched to Phoenix's documented
  :peer_data connect_info (already enabled in endpoint.ex) via
  socket.assigns; remote IP now actually works.
- Remove remote_ip.ex entry from .dialyzer_ignore.exs.

Accounts / Organizations (Ecto.Multi opacity):
- Add @specs to Multi-building helpers, refactor into pipe chains.
- 6 @dialyzer :nowarn_function → 0, but 7 :no_opaque remain. Root
  cause is upstream: Ecto.Multi.new/0 returns a struct with a literal
  %MapSet{} whose @opaque internal representation trips dialyzer on
  every subsequent Multi.* call. Unfixable without an Ecto patch or
  bypassing Multi entirely. Comments document the specific upstream
  issue rather than a vague "Ecto.Multi opacity" claim.

Devices.ex:
- Adding @specs made it worse (call_without_opaque → contract_with_
  opaque); inlining the Multi didn't help either — same MapSet root
  cause. Suppression kept with a sharper comment.
2026-04-21 11:01:30 -05:00
.claude perf: disable Req retry for faster error tests 2026-03-10 16:19:31 -05:00
.forgejo fix(ci): modify existing sources.list to add non-free instead of creating new file 2026-04-17 17:42:34 -05:00
.gitlab/agents Add aprs.me project to home-cluster-agent configuration 2026-01-14 13:31:23 -06:00
assets feat: add WebMCP tool definitions for AI agent browser interaction 2026-04-17 14:17:28 -05:00
bin add bin/deploy script for production deployments 2026-03-13 20:05:11 -05:00
c_src fix: correct library linking order in Makefile 2026-01-30 13:24:25 -06:00
config fix: prevent Oban index bloat by reindexing primary key and scheduling index (#165) 2026-03-25 12:36:10 -05:00
docs fix: correct login URL path in markdown negotiation content 2026-04-17 15:50:31 -05:00
e2e chore(deps): update all dependencies (#235) 2026-04-15 13:03:25 -05:00
flux fix: add [skip ci] to flux image update commits to break CI loop 2026-02-15 08:18:06 -06:00
k8s chore: update towerops image to git.mcintire.me/graham/towerops-web:main-1776785720-0350ced [skip ci] 2026-04-21 15:37:36 +00:00
lib dialyzer: replace suppressions with real fixes where possible 2026-04-21 11:01:30 -05:00
mibs handle mib uploading 2026-01-18 16:29:24 -06:00
nix remove-gleam (#218) 2026-03-29 11:03:20 -05:00
policies Hide RF Links UI elements from network map (TOW-44) (#46) 2026-03-16 15:19:03 -05:00
priv feat: add /sitemap.xml and update robots.txt for agent discovery 2026-04-17 13:58:19 -05:00
rel fix: quote RELEASE_NODE to prevent word splitting 2026-02-17 13:19:15 -06:00
scripts complete overhaul of snmp engine 2026-01-30 10:41:07 -06:00
test dialyzer: replace suppressions with real fixes where possible 2026-04-21 11:01:30 -05:00
tools fix: input validation, SSRF, API hardening, and cookie security 2026-03-14 14:48:59 -05:00
towerops Add towerops/gitlab-agent-helm-release.yaml via glab file sync 2026-01-02 14:22:22 -06:00
vendor Update Elixir dependencies (#194) 2026-03-27 17:13:12 -05:00
.credo.exs no cluster unless in k8s, fix credo issues 2026-02-09 13:08:51 -06:00
.dialyzer_ignore.exs dialyzer: replace suppressions with real fixes where possible 2026-04-21 11:01:30 -05:00
.dockerignore nif ci fix complie in ci 2026-01-30 12:47:18 -06:00
.envrc.example feat: Add comprehensive Nix flakes integration 2026-02-07 12:26:54 -06:00
.formatter.exs format 2025-12-21 11:31:08 -06:00
.gitignore remove-gleam (#218) 2026-03-29 11:03:20 -05:00
.gitlab-ci.yml.nix feat: Add comprehensive Nix flakes integration 2026-02-07 12:26:54 -06:00
.pre-commit-config.yaml fix: make deduplicate_discovery_checks migration production-safe (#178) 2026-03-26 10:39:09 -05:00
.sourceignore Add .sourceignore to exclude non-k8s files from Flux 2026-01-02 14:43:57 -06:00
.stride.md update 2026-02-18 10:19:56 -06:00
.test-watch.exs add test watch 2026-01-03 12:35:26 -06:00
.tool-versions remove-gleam (#218) 2026-03-29 11:03:20 -05:00
AGENTS.md chore: remove sobelow dependency 2026-03-12 16:03:08 -05:00
CHANGELOG.txt db-performance-migrations (#234) 2026-04-04 14:22:25 -05:00
CLAUDE.md ci: add test gates for production deployments 2026-03-06 17:40:50 -06:00
DEPLOYMENT.md feat: branch-based deployment (main → staging, production → prod) 2026-03-06 12:22:05 -06:00
Dockerfile remove-gleam (#218) 2026-03-29 11:03:20 -05:00
dry.md refactor/dry-improvements (#202) 2026-03-28 10:56:34 -05:00
ENTITY_PHYSICAL_IMPLEMENTATION.md feature/entity-physical-inventory (#187) 2026-03-27 10:48:50 -05:00
findings_librenms.md feature/entity-physical-inventory (#187) 2026-03-27 10:48:50 -05:00
flake.lock fix: Make Nix flake compatible with macOS 2026-02-07 12:30:48 -06:00
flake.nix fix: complete Nix Docker image build and untrack .claude/settings.local.json 2026-02-07 14:14:26 -06:00
Makefile complete overhaul of snmp engine 2026-01-30 10:41:07 -06:00
mix.exs dialyzer: expand PLT, drop blanket codebase suppression 2026-04-21 09:33:22 -05:00
mix.lock chore: update dependencies 2026-04-21 08:30:52 -05:00
package-lock.json fix: resolve critical data loss and protobuf schema bugs (Phase 1) 2026-02-09 09:16:03 -06:00
package.json fix: resolve critical data loss and protobuf schema bugs (Phase 1) 2026-02-09 09:16:03 -06:00
README.md encryption updates 2026-02-01 17:05:55 -06:00
REFACTOR_SUMMARY.md refactor/dry-improvements (#202) 2026-03-28 10:56:34 -05:00
renovate.json fix: code quality improvements and test reliability (#139) 2026-03-24 09:12:59 -05:00
setup_stripe_meter.exs feat: add Stripe meter ID configuration 2026-03-06 11:08:06 -06:00
setup_stripe_production.sh feat: allow script to work with both test and live Stripe keys 2026-03-06 11:08:07 -06:00
shell.nix feat: Add comprehensive Nix flakes integration 2026-02-07 12:26:54 -06:00
tail_logs.sh add logs helper script 2026-01-11 15:45:02 -06:00
test_encode_decode.exs refactor: convert 6 Gleam modules to idiomatic Elixir with TDD (#196) 2026-03-28 09:52:07 -05:00

TowerOps

Network monitoring and alerting platform built with Phoenix LiveView.

Features

  • Multi-tenant architecture - Organizations with role-based permissions
  • Site hierarchy - Organize equipment across multiple sites
  • Automated monitoring - Real-time ping monitoring with configurable intervals
  • Time-series data - Efficient storage with TimescaleDB (optional)
  • Real-time updates - LiveView dashboard with PubSub
  • Equipment tracking - Monitor network devices by IP address

Quick Start

Prerequisites

  • Elixir 1.14+
  • PostgreSQL 14+
  • (Optional) TimescaleDB for production-grade time-series performance

Setup

# Install dependencies
mix setup

# Start the server
mix phx.server

Visit localhost:4000 from your browser.

TimescaleDB (Production Only)

Development: Uses standard PostgreSQL (no TimescaleDB required).

Production: TimescaleDB is automatically enabled for optimal performance with time-series data.

# Production deployment - install TimescaleDB first
brew tap timescale/tap && brew install timescaledb  # macOS
# Then run migrations with MIX_ENV=prod
MIX_ENV=prod mix ecto.migrate

See TIMESCALEDB.md for detailed installation and configuration.

How it works: Migrations detect the environment (MIX_ENV) and only enable TimescaleDB features (hypertables, compression, retention policies, continuous aggregates) in production.

Encryption Setup (Production)

TowerOps uses AES-256-GCM encryption for sensitive data (SNMP communities, MikroTik API passwords, etc.).

Development/Test

Encryption keys are pre-configured in config/dev.exs and config/test.exs. No action required.

Production

Set the CLOAK_KEY environment variable with a base64-encoded 32-byte key:

# Generate encryption key
openssl rand -base64 32

Important:

  • Store the generated key securely in 1Password or your secrets manager
  • Never commit the production key to version control
  • Losing the encryption key makes encrypted data unrecoverable

Kubernetes Deployment

If secret doesn't exist yet (new deployment):

# Generate CLOAK_KEY (store in 1Password first!)
CLOAK_KEY=$(openssl rand -base64 32)

# Create towerops-secrets with all required keys
kubectl create secret generic towerops-secrets \
  --from-literal=RELEASE_COOKIE=$(openssl rand -base64 32) \
  --from-literal=SECRET_KEY_BASE=$(mix phx.gen.secret) \
  --from-literal=CLOAK_KEY="$CLOAK_KEY" \
  -n towerops

If secret already exists (add CLOAK_KEY to existing secret):

# Store new key in 1Password first!
# Bash/Zsh:
CLOAK_KEY=$(openssl rand -base64 32)

# Fish shell:
set CLOAK_KEY (openssl rand -base64 32)

# Method 1: Using kubectl create with dry-run and apply
kubectl create secret generic towerops-secrets \
  --from-literal=CLOAK_KEY="$CLOAK_KEY" \
  --dry-run=client -o yaml | \
  kubectl apply -f - -n towerops

# Method 2: Direct inline generation (works in all shells)
kubectl create secret generic towerops-secrets \
  --from-literal=CLOAK_KEY="$(openssl rand -base64 32)" \
  --dry-run=client -o yaml | \
  kubectl apply -f - -n towerops

# Restart pods to pick up new key
kubectl rollout restart deployment/towerops -n towerops

Development

Database

mix ecto.create       # Create database
mix ecto.migrate      # Run migrations
mix ecto.reset        # Drop, create, and migrate

Testing

mix test              # Run all tests
mix test --trace      # Run with detailed output

Code Quality

mix format            # Format code with Styler
mix compile --warnings-as-errors

Firmware Version Tracking

The system automatically checks for latest firmware versions daily (2 AM dev, 4 AM prod). To manually trigger a firmware check:

# Start IEx console
iex -S mix phx.server

# Manually trigger firmware version fetch
Oban.insert(Towerops.Workers.FirmwareVersionFetcherWorker.new(%{}))

The worker will:

  1. Fetch the latest MikroTik RouterOS version from RSS feed
  2. Store version information in the database
  3. Enable firmware update indicators on device detail pages

Check the logs for fetch results:

# View recent Oban jobs
Towerops.Repo.all(Oban.Job) |> Enum.take(5)

Learn more