- Re-add Docker Buildx setup (required for cache)
- Use 'type=inline' for cache-to (embeds cache in image)
- Cache from the latest image instead of separate buildcache tag
This fixes the 'Cache export is not supported' error.
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove BuildKit advanced features that were slowing builds
- Remove multi-platform builds (only build for amd64)
- Remove security scanning stage
- Remove complex caching mounts
- Simplify to basic 2-stage build
- Use registry cache instead of GitHub Actions cache
This should reduce build time from 10+ minutes to 2-3 minutes.
Co-Authored-By: Claude <noreply@anthropic.com>
- Cancels any in-progress deployment when a new commit is pushed
- Prevents multiple deployments from running simultaneously
- Uses concurrency group based on branch ref
- Saves CI/CD resources and prevents conflicting deployments
Co-Authored-By: Claude <noreply@anthropic.com>
- Phoenix uses standalone ESBuild and Tailwind binaries
- No npm dependencies needed (package.json was empty)
- Removes ~150MB from Docker image
- Speeds up build time by removing Node.js installation step
Co-Authored-By: Claude <noreply@anthropic.com>
The CI/CD pipeline now initiates the deployment but doesn't wait for it to complete, allowing the workflow to finish quickly even if pods take time to start.
Co-Authored-By: Claude <noreply@anthropic.com>
- 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>
- 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>
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>
Remove PostgreSQL session-specific parameters that are not supported
by PgBouncer connection pooling. Keep only application_name parameter
which is passed through properly.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
- 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>
Configures Phoenix.PubSub.Redis adapter when clustering is enabled
and REDIS_URL environment variable is set. This allows PubSub
messages to be shared across Elixir nodes in a distributed
deployment, enabling proper real-time updates in multi-node setups.
- Add phoenix_pubsub_redis dependency (~> 3.0)
- Configure Redis adapter in runtime.exs for production
- Set pool size to 10 for better performance
- Include node name in Redis configuration
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fixed "Leaflet library failed to load" error by deferring window.L access
- Changed map.ts to access window.L dynamically instead of at module load time
- Added const L = window.L; inside functions that use Leaflet
- This allows the vendor bundle to load asynchronously before map initialization
The issue was that the TypeScript module was trying to access window.L
immediately when imported, before the vendor bundle had loaded Leaflet.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This adds a "Last Update" section below the navigation that shows:
- Relative time ("less than a minute ago")
- Exact UTC timestamp ("2025-07-25 19:04 UTC")
Updates automatically whenever LiveView receives packet updates or map bounds changes.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fixed GeoUtils.significant_movement? test: Updated test coordinates
to actually move 55 meters (over 50m threshold) instead of 11 meters
- Fixed MapLive movement notification tests: Changed packet maps to use
atom keys instead of string keys to match expected format
- Fixed JavaScript URL interpolation in layout template: Replaced
incorrect HEEx syntax with static paths for vendor bundles
- Made movement tests more resilient by using receive blocks instead
of assert_push_event for better reliability in test environment
All 376 tests now pass successfully with 0 failures.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Download external libraries locally to reduce CDN dependencies
- Create combined vendor bundles for JavaScript and CSS
- Replace CDN script tags with local bundles in layout template
- Add build scripts for vendor bundle creation and optimization
- Configure esbuild to process vendor bundles separately
- Total bundle size: ~512KB JS + 17KB CSS (optimized)
Benefits:
- Reduced external requests and CDN dependencies
- Better caching control and reliability
- Eliminated CORS and SRI integrity issues
- Faster page loads with bundled resources
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Use the correct entry point from package.json (dist/oms.js) instead
of oms.min.js, and switch back to unpkg with the verified working
path to resolve content corruption issues.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Switch from unpkg.com to jsdelivr.net CDN to resolve MIME type
mismatch error. The unpkg CDN was serving the JavaScript file
with 'text/plain' content type instead of 'application/javascript'.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
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>
- 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>
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>
Switch to Kubernetes.DNSSRV strategy which is specifically designed
for StatefulSets and uses DNS SRV records for node discovery.
This strategy:
- Doesn't require Kubernetes API permissions (no RBAC needed)
- Automatically filters out the current node
- Works with StatefulSet stable DNS names
- Uses SRV records from the headless service
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Based on libcluster documentation, StatefulSets should use the standard
Kubernetes strategy with mode: :hostname, not the DNS strategy.
This properly discovers pods using their stable hostnames and filters
out the current node from connection attempts.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update kubectl commands in deploy workflow to target statefulset
instead of deployment, matching the recent migration to StatefulSet.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The DNSPoll strategy was attempting to connect to IPs instead of DNS names.
Kubernetes.DNS strategy is specifically designed for StatefulSet deployments
and will properly discover pods using their stable DNS names.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updates Claude workflow to pull git submodules recursively, which resolves
compilation errors for Aprs.Types.MicE struct that is defined in the
vendor/aprs submodule.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
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>
- Added missing CSS for slideover panel with proper width and positioning
- Fixed overflow issues by adding box-sizing: border-box
- Adjusted Track button padding from px-6 to px-4 to prevent cutoff
- Added whitespace-nowrap and flex-shrink-0 to prevent button text wrapping
- Ensured proper panel width of 352px on desktop with transform animations
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>