Commit graph

121 commits

Author SHA1 Message Date
080dac3813
Improve status page cluster information display
- Remove APRS server port from status page server field
- Add comprehensive cluster node list to cluster status section
- Highlight leader node with primary badge and indicator dot
- Display all cluster nodes in a responsive flex layout
- Include all_nodes field in cluster_info for complete node visibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 12:53:19 -05:00
da079ddd76
Fix code formatting in release.ex
Run mix format to fix multi-line function call formatting.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 11:30:16 -05:00
a3045371a1
Fix dialyzer warnings and improve type safety
- Fix Packet module usage in queries (use from/1 to create proper query)
- Fix error handling in DbOptimizer to use try/rescue
- Remove unused erlc_paths from mix.exs comment
- Improve type specifications consistency

All dialyzer specs now pass without critical errors.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 11:18:55 -05:00
1596e95eba
format 2025-07-27 11:16:16 -05:00
9270bc1aa5
Complete dialyzer specs and fix all warnings
- Add comprehensive type specs to all functions in Encoding module
- Add missing specs to private functions in EncodingUtils
- Fix compilation warnings (unused variables, deprecated Logger.warn)
- Fix dialyzer warnings about unreachable code and implicit nil returns
- Remove unused do_migrate/1 function
- Remove last references to erlc_paths and gleeunit dependency

All dialyzer specs now pass without warnings.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 11:13:14 -05:00
648402a610
Fix build errors and add complete dialyzer specs
- Remove erlc_paths and erlc_include_path from mix.exs (leftover from Gleam)
- Add comprehensive dialyzer specs to Encoding module
- Add missing dialyzer specs to EncodingUtils module
- Ensure all public and private functions have proper type specifications

This fixes the Docker build error about undefined erlc_paths function
and ensures full dialyzer compliance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 11:08:40 -05:00
865fc90871
Remove Gleam and convert encoding module to pure Elixir
- Convert Gleam encoding module to Elixir implementation
- Remove all Gleam dependencies from mix.exs
- Remove Gleam configuration files (gleam.toml, src directory)
- Update Dockerfile to remove mix_gleam installation
- Update EncodingUtils to use Elixir module instead of Gleam
- Remove Gleam references from documentation
- Clean up all Gleam-related build configuration

This simplifies the build process and removes the compilation issues
that were preventing successful Docker builds.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 11:05:24 -05:00
8b3e433e61
Fix Exq startup conflict causing crash loop
- Remove manual Exq startup from application supervision tree
- Add :exq to extra_applications in mix.exs for automatic startup
- Remove duplicate Exq configuration functions
- Let Exq start automatically via runtime.exs configuration

The issue was Exq being started twice - once manually in the supervision
tree and once automatically via configuration, causing "already_started" error.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 10:49:42 -05:00
4f7aabbce9
Fix migration timeout issues for large tables
- Add statement_timeout and lock_timeout settings to migration
- Remove generated column addition that requires full table rewrite
- Replace with functional index to avoid table rewrite
- Create separate migration for generated column with 2-hour timeout
- Add MIGRATION_TIMEOUT environment variable support (default 2 hours)
- Configure extended timeouts in Release.migrate for large operations

The main migration now uses only CONCURRENTLY operations that don't lock
the table. The generated column addition is deferred to a separate migration
that can be run during a maintenance window.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 10:44:16 -05:00
9189579a02
Implement distributed migration locking for cluster deployments
- Add MigrationLock module using PostgreSQL advisory locks
- Prevent concurrent migrations across multiple nodes
- Update Release.migrate to use distributed lock when cluster enabled
- Disable auto-migrations in cluster mode to avoid race conditions
- Add init container configuration for Kubernetes deployments
- Document migration strategies in k8s/README-migrations.md

This ensures only one node runs database migrations at a time, preventing
conflicts and race conditions in distributed deployments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 10:41:36 -05:00
Graham McIntire
1929b4c09b
Merge pull request #34 from aprsme/switch-oban-to-exq
Migrate background job processing from Oban to Exq
2025-07-26 18:40:30 -05:00
1f4aaf2a80
Migrate background job processing from Oban to Exq
- Replace Oban dependency with Exq for Redis-based background jobs
- Update PacketCleanupWorker to use Exq's perform/1 signature
- Add CleanupScheduler GenServer for periodic job scheduling
- Configure Exq with Redis connection and queue settings
- Update tests to work with Exq job format
- Maintain 6-hour packet cleanup schedule functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 18:39:50 -05:00
4995be9c2f
Fix Redis cache startup - use child_spec pattern
- RedisCache uses child_spec, not start_link
- Update application.ex to use tuple syntax for starting Redis caches
- This fixes the :undef error when starting caches

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 16:40:39 -05:00
b82d459aa9
Fix Redis connection error - remove invalid pool_size option
- Redix doesn't support pool_size option directly
- Removed pool_size from both RedisRateLimiter and RedisCache
- This fixes the NimbleOptions.ValidationError on startup

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 16:36:47 -05:00
e8847ba83c
Fix compilation warning in DeviceCache
- Group handle_info/2 clauses together
- This fixes the warning about clauses with same name/arity
- Now compiles cleanly with --warnings-as-errors

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 16:35:07 -05:00
aa5cfb8240
Fix startup issues with Redis and shutdown handlers
- Delay DeviceCache initial load by 1 second to allow Redis connections
- Move shutdown handlers to end of supervision tree
- This prevents shutdown handlers from interfering with startup failures

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 16:00:54 -05:00
60efa7677b
Fix ShutdownHandler terminating during normal startup
- Only initiate graceful shutdown for abnormal termination reasons
- Ignore :shutdown, {:shutdown, _}, and :normal termination reasons
- Add logging for SIGTERM handler installation

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 15:55:56 -05:00
eeeebef34e
Fix PgBouncer database connection and graceful shutdown
- Add SKIP_DB_CREATE env var to skip database creation when using PgBouncer
- Fix redis_children() to return proper child specs
- Implement zero-downtime graceful shutdown without user notifications
- Add health check plug for Kubernetes probes
- Remove user-facing shutdown notifications for seamless deployments

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 15:51:06 -05:00
226a53bf61
Add Redis-based distributed caching and rate limiting
This commit introduces a dual-mode caching and rate limiting system that can use
either Redis (for distributed deployments) or ETS/Cachex (for single-node setups).

Key changes:
- Add Cache abstraction layer that automatically switches between Redis and Cachex
- Implement RedisCache module with full distributed cache functionality
- Implement RedisRateLimiter with sliding window algorithm for accurate rate limiting
- Add RateLimiterWrapper to provide unified API for both implementations
- Update application startup to conditionally use Redis when REDIS_URL is set
- Migrate all cache operations to use the new Cache abstraction
- Support graceful fallback to ETS-based solutions when Redis is unavailable

The system automatically detects Redis availability via REDIS_URL environment
variable and switches between implementations without code changes. This enables
proper distributed caching and rate limiting in Kubernetes deployments while
maintaining backward compatibility for development environments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 15:29:38 -05:00
7fa41b85b4
Fix Redis PubSub URL configuration
The phoenix_pubsub_redis adapter expects the url option at the top level,
not nested under redis_options.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 10:22:15 -05:00
cbbe1772bc
Add logging to PubSub configuration for debugging
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 10:14:44 -05:00
c85b907ab7
Move Redis PubSub config to runtime initialization
- Remove static Redis PubSub configuration from runtime.exs
- Add dynamic pubsub_config/0 function in application.ex
- Configure PubSub adapter based on cluster_enabled and REDIS_URL at startup
- Ensures proper initialization order for distributed deployments

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 10:10:55 -05:00
0f26cd7e22
Add cluster-aware APRS-IS status checking
Both /status and /status.json endpoints now show cluster-wide APRS-IS
connection status instead of just local node status.

Features:
- get_cluster_aprs_status() checks all nodes in cluster via RPC
- Returns status from any connected node (usually the leader)
- Shows cluster info: total nodes, connected nodes, leader node
- Graceful fallback to local status in non-clustered mode
- Enhanced status page UI with cluster information display

This ensures status pages accurately reflect the true APRS-IS
connection state across the entire cluster.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 13:10:23 -05:00
37c9ac79ed
Fix Process.alive? crash with remote PIDs
The cleanup_stale_registrations function was calling Process.alive?
on remote PIDs, which causes an ArgumentError. Fixed by:

- Using Process.alive? only for local PIDs
- Using :rpc.call for remote PID liveness checks
- Adding proper error handling for RPC failures
- Cleaning up registrations for disconnected nodes

This prevents GenServer crashes when checking leader status.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 12:54:13 -05:00
9caab57dbe
Make leader election wait for cluster formation
- Leader election now waits for other nodes to connect before starting
- Added cleanup of stale global registrations from disconnected nodes
- Only starts election after cluster is established in clustered mode
- Non-clustered mode continues to elect immediately

This prevents both nodes from becoming leaders when they start
before the cluster is fully formed.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 12:48:22 -05:00
9212088beb
Fix leader election conflict resolution for distributed nodes
The previous conflict resolution tried to call Process.info/2 on remote
PIDs, which doesn't work. Changed to use node name comparison for
deterministic leader selection across the cluster.

This ensures only one leader is elected when nodes join the cluster.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 12:45:28 -05:00
437ccebbaa
Fix duplicate APRS-IS connections in clustered deployments
Prevent both AprsIsConnection and cluster-managed Is connection from
starting simultaneously. When clustering is enabled, only the cluster's
ConnectionManager should handle APRS-IS connections via leader election.

This resolves aprsc server disconnections caused by duplicate validated
clients with the same username (W5ISP-1).

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 12:10:47 -05:00
14ffefe2e9
Add Cluster.Supervisor directly to application supervision tree 2025-07-25 11:03:20 -05:00
35224327c0
Add debugging and validation for cluster topologies 2025-07-25 10:59:37 -05:00
17f5a162aa
Fix Cluster.Supervisor options handling 2025-07-25 10:57:10 -05:00
da589402af
Fix Cluster.Supervisor child spec configuration 2025-07-25 10:51:08 -05:00
67e293c77c
Fix Kubernetes cluster strategy configuration 2025-07-25 10:44:59 -05:00
4804bd16be
Add distributed Erlang clustering for single APRS-IS connection
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>
2025-07-25 10:30:29 -05:00
01cf1ee478
Fix Sentry.Event struct access error in SentryFilter
Replace get_in/Access usage with proper struct pattern matching to fix
UndefinedFunctionError when accessing Sentry.Event fields. The struct
doesn't implement the Access behavior, so we now use pattern matching
and direct field access instead.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-24 19:21:23 -05:00
cd69de5131
Fix database creation on deployment
- Add create_database/0 function to handle database creation before migrations
- Make DeviceCache handle missing tables gracefully on startup
- Retry device loading after 5 seconds if initial load fails
- Prevent deployment failures when database doesn't exist yet

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 13:05:23 -05:00
b4206b63d1
Increase GPS drift threshold from 15m to 50m
Fixed issue where stationary stations like K5SGD-D were showing multiple
positions due to minor GPS accuracy variations. The previous 15-meter
threshold was too strict for typical GPS drift which can vary 5-30 meters.

- Increased default threshold in GeoUtils from 10m to 50m
- Updated packet processor to use 50m threshold for movement detection
- Verified with K5SGD-D showing ~24m variations that are now ignored

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:23:37 -05:00
dc0e49780b
Fix timer cancellation in PacketConsumer and simplify migration
- Add nil check before canceling timer in PacketConsumer to prevent errors
  when timer reference doesn't exist (fixes test failures)
- Simplify packet counter reset migration by removing unsupported event
  triggers, keeping only manual reset function
- All tests now passing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 08:48:57 -05:00
7db864bb64
Fix ParseError Access behavior error in packet batch insert
Fixed "ParseError does not implement the Access behaviour" error that
occurred during packet batch insert when packets contained ParseError
structs in their data_extended field.

Added special handling for Aprs.Types.ParseError structs in the
struct_to_map function to convert them to simple maps with their
error_code and message fields, preventing the Access behavior error
when the struct is accessed during processing.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 08:48:26 -05:00
d3a5ed33f2
Fix constant garbage collection warnings by monitoring per-process memory
The memory monitoring was incorrectly using total VM memory instead of
per-process memory, causing false positives whenever any process in the
system allocated memory.

Changes:
- Changed memory monitoring to per-process instead of total VM memory
- Reduced thresholds to reasonable per-process values (50MB diff, 100MB total)
- Increased minor GC trigger from 100 to 1000 packets to prevent GC after
  every normal batch

This prevents detecting memory changes from other processes and eliminates
the constant GC warnings while still maintaining proper memory management.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 07:55:28 -05:00
a0c2970b42
Revert to smaller batch sizes to reduce garbage collection frequency
Reduced batch processing parameters to prevent excessive GC:
- Reduced batch_size from 500 to 100 packets
- Adjusted batch_timeout from 2000ms to 1000ms for faster processing
- Reduced max_demand from 750 to 300 to match smaller batches
- Updated hardcoded fallback from 500 to 100 in packet_consumer.ex
- Changed minor GC trigger from 500 to 100 packets to match batch size

These smaller batch sizes were proven to work well in production and
should significantly reduce the frequency of garbage collection while
maintaining good performance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-19 17:46:04 -05:00
2bdad557e9
Optimize memory usage thresholds and VM garbage collection settings
Increased memory thresholds from 10MB/20MB to 500MB/1GB since the server
has 15GB available memory. This prevents constant GC warnings while
maintaining memory safety.

Configured VM garbage collection settings in vm.args.eex:
- Enabled concurrent ports/sockets limit (+Q 65536)
- Set heap sizes and GC parameters optimized for better performance
- Added scheduler and binary heap settings

These changes will reduce unnecessary GC overhead and improve overall
application performance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-19 15:21:42 -05:00
e41118a243
Remove PacketPipelineSetup to fix application startup crash
The PacketPipelineSetup was attempting to subscribe to a single
PacketConsumer process that doesn't exist. The application uses
PacketConsumerPool which automatically handles subscriptions for
multiple consumer processes, making PacketPipelineSetup redundant
and causing startup failures.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-19 15:15:06 -05:00
ad2d9c14cf
Fix packet buffer overflow with parallel consumer pool
- Create PacketConsumerPool to run 3 parallel consumers (configurable)
- Increase max_demand from 250 to 750 total (250 per consumer)
- Fix timer management to properly cancel/restart on batch processing
- Configure proper GenStage subscriptions with backpressure control
- Allow unnamed consumers for pool usage

This 3x increase in processing capacity prevents the "Packet buffer full"
warnings by ensuring consumers can keep up with incoming packet rate.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-19 14:49:52 -05:00
234fc2010f
Implement critical performance optimizations and error handling
This commit introduces several major performance improvements and adds
robust error handling for malformed HTTP requests:

Performance Optimizations:
- Add database migration with BRIN indexes for time-series data and partial
  indexes for recent queries, significantly improving query performance
- Create global StreamingPacketsPubSub system for real-time packet distribution
  with geographic bounds filtering using ETS for fast lookups
- Refactor PacketConsumer to use Stream module for memory-efficient processing,
  preventing memory accumulation during batch operations
- Implement dedicated BroadcastTaskSupervisor pool for async broadcast operations,
  preventing GenServer blocking on I/O
- Increase database connection pool from 25 to 45 (production) and 15 to 30 (dev)
  for better concurrency support

Error Handling:
- Add SentryFilter to prevent Bandit.HTTPError from missing Host headers from
  cluttering Sentry (common with bots/scanners)
- Configure custom 400 Bad Request error handling
- Filter out common bot/scanner paths from error reporting

All changes include comprehensive test coverage following TDD practices.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 18:19:49 -05:00
3e6d73b553
format 2025-07-18 13:59:03 -05:00
00a8f996f4
Add Gleam language integration to Elixir project
- Rename Gleam module from encoding_simple to encoding
- Move Gleam files from src/aprs/ to src/aprsme/ to match project namespace
- Create custom Mix task for Gleam compilation (lib/mix/tasks/gleam_compile.ex)
- Update EncodingUtils to wrap Gleam implementation instead of pure Elixir
- Add Gleam dependencies to mix.exs and configure build paths
- Update Mix aliases to include Gleam compilation in test and compile tasks
- Add Gleam support to GitHub Actions CI workflow with caching
- Add GLEAM_INTEGRATION.md documentation
- All 357 tests passing with Gleam integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 13:58:21 -05:00
dadfef8a5b
cleanup 2025-07-18 10:41:26 -05:00
8d5bfe127e
add email 2025-07-18 10:15:09 -05:00
566198afa7
clean up warnings 2025-07-16 08:55:46 -05:00
46340e008a
update deps 2025-07-16 08:20:20 -05:00