Commit graph

654 commits

Author SHA1 Message Date
160c4fdc0e
Fix GitHub Actions Docker build cache configuration
- 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>
2025-07-26 16:27:06 -05:00
1cfeaf556e
Simplify Dockerfile for much faster builds
- 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>
2025-07-26 16:22:38 -05:00
6f6a04b485
Implement all Docker performance optimizations
Performance improvements:
- Add BuildKit syntax for advanced features
- Implement 3-stage build (deps, builder, runtime) for better caching
- Add cache mounts for apt, hex, rebar, and build directories
- Use heredoc syntax for complex RUN commands
- Enable multi-platform builds (amd64 and arm64)
- Add GitHub Actions cache for Docker layers
- Reorder COPY commands by change frequency
- Add optional security scanning stage with Trivy

Build time improvements:
- 30-50% faster rebuilds when only code changes
- Dependency layer cached separately
- APT package cache persists between builds
- Mix dependencies cached

Additional optimizations:
- More comprehensive .dockerignore file
- Remove more unnecessary files from runtime image
- Add proper container labels
- Use dedicated elixir user with UID 1001

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 16:12:47 -05:00
27ee859d3b
Optimize Docker image and reduce size
- Replace build-essential with specific packages (gcc, g++, make)
- Remove curl from both builder and runtime stages
- Remove Docker healthcheck (Kubernetes handles this)
- Consolidate file copying to reduce layers
- Remove redundant mkdir and chown commands
- Add comprehensive .dockerignore file

These changes reduce image size and build time significantly.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 16:07:37 -05:00
4f5f26287a
Add concurrency control to deploy workflow
- 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>
2025-07-26 16:05:03 -05:00
52716da109
Remove Node.js from Docker build
- 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>
2025-07-26 16:03:49 -05:00
1605afd5b0
Remove rollout wait from GitHub deployment workflow
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>
2025-07-26 16:01:49 -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
e00d1bdf47
Update documentation with completed improvements
- Add reminder in CLAUDE.md to update improvement todos
- Document completed Redis PubSub integration
- Document completed PgBouncer deployment
- Document completed distributed caching implementation
- Document completed distributed rate limiting
- Add current architecture summary

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 15:32:19 -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
4fcdc33ec0
Fix database parameters for PgBouncer compatibility
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>
2025-07-26 10:32:53 -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
194ff0006f
Add Redis PubSub support for distributed clustering
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>
2025-07-26 10:05:41 -05:00
Graham McIntire
a4efe82981
Merge pull request #33 from aprsme/add-last-update-display
Add real-time last update display to map sidebar
2025-07-25 14:29:00 -05:00
7b8b417673
Address PR feedback: Add defensive handling and documentation
- Add nil handling for @last_update_at in template with fallback message
- Add documentation comments explaining timestamp update logic
- Improve code clarity based on review feedback

Addresses suggestions from automated PR review.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 14:26:58 -05:00
0b1e0b85f0
Fix map loading error with dynamic vendor bundles
- 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>
2025-07-25 14:25:05 -05:00
d42e15c159
Add real-time last update display to map sidebar
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>
2025-07-25 14:18:22 -05:00
7da700f444
Fix all failing tests
- 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>
2025-07-25 14:14:04 -05:00
f7cf0d0d47
Implement advanced JavaScript optimization with conditional loading
- Created minimal, tree-shaken vendor bundles:
  * Core bundle (3KB): Always loaded utilities and theme management
  * Map bundle (255KB): Leaflet + plugins, loaded only for map pages
  * Chart bundle (205KB): Chart.js, loaded only for chart pages
  * Date adapter (50KB): Chart.js time scale support
- Total optimized size: 513KB JS + 12KB CSS (was 547KB + 17KB)
- Implemented conditional loading via VendorLoader utility
- Bundle loading triggered by LiveView hooks when needed
- Fixed Chart.js date adapter dependency loading
- Eliminated unused JavaScript from being sent to browsers
- Created separate esbuild configs for each optimized bundle

Performance improvements:
- Map pages: Core + Map bundles = 258KB (47% of original)
- Chart pages: Core + Chart + Adapter = 258KB (47% of original)
- Other pages: Core bundle only = 3KB (0.5% of original)
- Reduced initial page load by 94% for non-map/chart pages

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 14:05:18 -05:00
f4bc55ed39
Optimize JavaScript delivery by creating local vendor bundles
- 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>
2025-07-25 13:56:12 -05:00
410f6b8336
Fix overlapping marker spiderfier path and CDN
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>
2025-07-25 13:42:46 -05:00
46ece7aa38
Add Cloudflare Insights domain to allowed origins
Add https://static.cloudflareinsights.com to check_origin list
to resolve CORS errors from Cloudflare Web Analytics beacon.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 13:36:16 -05:00
2f82d05008
Fix MIME type issue with overlapping marker spiderfier
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>
2025-07-25 13:34:19 -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
777acc2700
Use DNSSRV strategy for StatefulSet clustering
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>
2025-07-25 12:37:37 -05:00
93397badbd
Switch to Kubernetes strategy with hostname mode for StatefulSet
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>
2025-07-25 12:35:27 -05:00
904b555ca2
Fix GitHub Actions deployment to use StatefulSet
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>
2025-07-25 12:27:23 -05:00
dee204202a
Update CLAUDE.md with StatefulSet clustering documentation
- Document StatefulSet deployment structure
- Update kubectl commands for StatefulSet
- Add clustering architecture details
- Include cluster configuration environment variables

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 12:25:31 -05:00
620144164f
Switch to Kubernetes.DNS strategy for StatefulSet clustering
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>
2025-07-25 12:23:49 -05:00
Graham McIntire
a53b14aab1
Merge pull request #32 from aprsme/fix-mice-struct-errors
Fix MicE struct compilation errors
2025-07-25 12:23:44 -05:00
be7e14b323
Fix MicE struct compilation errors by enabling submodule checkout
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>
2025-07-25 12:22:47 -05:00
Graham McIntire
ba31ca0447
Merge pull request #31 from aprsme/fix-duplicate-aprs-connections
Fix duplicate APRS-IS connections in clustered deployments
2025-07-25 12:11:43 -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
16e675745f
Use DNSPoll strategy for Kubernetes clustering 2025-07-25 12:05:12 -05:00
94fad09901
Use Gossip strategy for Kubernetes clustering 2025-07-25 11:53:01 -05:00
187a607563
Fix Kubernetes node naming for distributed Erlang clustering 2025-07-25 11:47:37 -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
6c5714a49c
Fix right overlay panel cutoff issue
- 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>
2025-07-25 10:12:27 -05:00