Implement leader election to ensure only one APRS-IS connection across multiple Kubernetes replicas. This prevents duplicate packet processing and respects APRS-IS usage policies. Key changes: - Add leader election using :global registry - Create connection manager for dynamic APRS-IS management - Implement packet distribution from leader to all nodes - Add Kubernetes headless service for node discovery - Configure DNS-based clustering with libcluster - Update deployment to support 3 replicas with clustering 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
7.4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Prerequisites
Before setting up the project, ensure you have the following installed:
- Elixir 1.17+
- Erlang/OTP
- PostgreSQL with PostGIS extension
- Gleam (required for Gleam modules) - Install from https://gleam.run/getting-started/installing/
Project Overview
This is an Elixir Phoenix LiveView application that serves as a real-time APRS (Automatic Packet Reporting System) tracker and visualizer. It connects to the APRS-IS network to receive live amateur radio packets and displays them on an interactive map interface.
Development Commands
Setup
mix archive.install hex mix_gleam --force- Install mix_gleam archive (required first step)mix setup- Complete project setup (deps.get + ecto.setup + gleam compilation)mix deps.get- Install dependenciesmix ecto.setup- Create database, run migrations, and seed datamix ecto.reset- Drop and recreate databasemix phx.server- Start Phoenix server (http://localhost:4000)iex -S mix phx.server- Start server in interactive Elixir shell
Testing
mix test- Run full test suitemix test --stale- Run only tests affected by code changesmix test.watch- Continuous testing with file watchingmix test --cover- Generate test coverage reports
Code Quality
mix format- Format code according to .formatter.exsmix credo- Static code analysis and style checkingmix dialyzer- Static type analysis (must run and fix errors/warnings)mix sobelow- Security vulnerability scanning- CRITICAL: ALWAYS run
mix formatBEFORE committing - never commit unformatted code - MANDATORY: Run
mix compile --warnings-as-errorsand ensure it passes before considering any task complete
Assets (No Node.js)
mix assets.deploy- Build and minify frontend assets (Tailwind CSS + ESBuild)- Phoenix uses ESBuild directly without Node.js package managers
Architecture
Core Components
- Aprsme.AprsIsConnection - TCP connection to APRS-IS network with reconnection logic
- Aprsme.PacketConsumer - Processes incoming APRS packets using GenStage pipeline
- Aprsme.Packet - Database schema for APRS packets with PostGIS geographic data
- AprsmeWeb.MapLive.Index - Main real-time map interface using Phoenix LiveView
- Aprsme.Workers.PacketCleanupWorker - Oban background job for data cleanup
Data Flow
- APRS-IS connection receives packets via TCP
- PacketConsumer processes packets through GenStage pipeline
- Packets stored in PostgreSQL with PostGIS geographic indexing
- LiveView broadcasts real-time updates to connected clients via PubSub
- Background workers handle cleanup and maintenance tasks
Key Dependencies
- Phoenix LiveView for real-time UI without JavaScript
- PostGIS for geographic data storage and spatial queries
- Oban for background job processing
- GenStage for packet processing pipelines
- Tailwind CSS + ESBuild for frontend assets (no Node.js)
- Gleam for additional type-safe modules (requires mix_gleam archive)
Test-Driven Development
MANDATORY: Follow strict test-driven development (TDD) practices:
- Red Phase: Write failing tests first before implementing any functionality
- Green Phase: Write minimal code to make tests pass
- Refactor Phase: Improve code while keeping tests green
TDD Workflow
- Always write tests before implementing new features or fixing bugs
- Start with the simplest failing test case
- Write only enough code to make the test pass
- Refactor with confidence knowing tests will catch regressions
- Run
mix testfrequently during development - Use
mix test.watchfor continuous feedback
Testing Patterns
Tests use comprehensive mocking to prevent external connections:
- APRS-IS connections are mocked in test environment
- Database uses sandbox mode for isolation
- External API calls mocked with Mox library
- Write unit tests for business logic, integration tests for workflows
- Test edge cases and error conditions thoroughly
- Maintain high test coverage with
mix test --cover
Code Style Guidelines
- CRITICAL: Never write production code without tests first
- Use LiveView for UI interactions, minimize JavaScript
- Prefer pattern matching over if/case statements
- Follow idiomatic Elixir conventions
- CRITICAL: ALWAYS run
mix formatBEFORE committing - this is non-negotiable - Address any compiler warnings
- Run
mix dialyzerand fix all errors/warnings - MANDATORY: Run
mix compile --warnings-as-errorsand ensure it passes before considering any task complete - Use function composition over nested conditionals
- Write descriptive test names that explain behavior
Pre-Commit Checklist
- Run
mix format- ALWAYS do this first - Run
mix compile --warnings-as-errors- ensure no warnings - Run
mix test- ensure all tests pass - Only then commit and push your changes
Web Testing
- MANDATORY: When viewing any website or web application, always use Puppeteer to take screenshots and interact with the page
- Use
mcp__puppeteer__puppeteer_navigate,mcp__puppeteer__puppeteer_screenshot, and other Puppeteer tools - This ensures accurate visual feedback and proper testing of the user interface
Deployment
The application supports Kubernetes deployment with manifests in k8s/ directory and GitHub Actions CI/CD pipeline. Database migrations run automatically via init containers.
Kubernetes Commands
The app is deployed in a k3s cluster with the following structure:
- App name:
aprs - Namespace:
aprs - K8s manifests location:
~/dev/infra/clusters/aprs/
Common kubectl commands for debugging:
# Check pod status
kubectl get pods -n aprs
# Get logs from the app
kubectl logs -f deployment/aprs -n aprs
# Get logs from a specific pod
kubectl logs <pod-name> -n aprs
# Describe pod for events and details
kubectl describe pod <pod-name> -n aprs
# Restart the deployment
kubectl rollout restart deployment/aprs -n aprs
# Check deployment status
kubectl rollout status deployment/aprs -n aprs
# Execute commands in the pod
kubectl exec -it deployment/aprs -n aprs -- /app/bin/aprsme remote
# Check cluster membership
kubectl exec -it <pod-name> -n aprs -- /app/bin/aprsme eval "Node.list()"
# Check leader status
kubectl exec -it <pod-name> -n aprs -- /app/bin/aprsme eval "Aprsme.Cluster.LeaderElection.is_leader?()"
Clustering Architecture
The application uses distributed Erlang clustering to ensure only one APRS-IS connection across multiple replicas:
-
Leader Election: Uses
:globalregistry for distributed leader election- Only the elected leader maintains the APRS-IS connection
- Automatic failover when leader goes down
- Leader election managed by
Aprsme.Cluster.LeaderElection
-
Connection Management:
Aprsme.Cluster.ConnectionManagerstarts/stops APRS-IS based on leadership- Uses
DynamicSupervisorto manage connection lifecycle - Prevents duplicate connections and packet processing
-
Kubernetes Configuration:
- Headless service (
aprs-headless) for node discovery - DNS-based clustering via libcluster
- Environment variables:
CLUSTER_ENABLED=true- Enables clusteringRELEASE_NODE- Erlang node nameRELEASE_COOKIE- Erlang distribution cookie
- Headless service (
-
Deployment:
- Default replicas: 3 (configurable in
aprs-deployment.yaml) - Only leader processes APRS packets
- All nodes serve web traffic
- Default replicas: 3 (configurable in